Running Doom Inside SQL Server

Not a raycaster. Not a tribute. Actual Doom — real WAD, real BSP, real E1M1 — rendered by a relational database.

Appendix: RUN.md

Running Doom in SQL Server

Fourteen steps, in this order. Each owns a disjoint set of objects, so any one of them can be re-run on its own afterwards without disturbing the others.

doom_schema.sql is NOT in the list: E1M1_freedoom.sql already embeds it. Running it separately would drop the map data.

# File                   Creates

1 E1M1_freedoom.sql      schema + E1M1 geometry + 158 textures

2 doom_render.sql        render_frame, sector_at, framebuffer

3 doom_present.sql       frame_bmp, le4, screen_pixel, render_and_time

4 doom_game.sql          player, input_state, line_collide, 
                         p_tick, game_loop

5 doom_sprites.sql       sprite tables + render_sprites

6 E1M1_sprites.sql       sprite frames and columns

6b doom_sprites_fast.sql replaces render_sprites, adds thing_sector

7 doom_client.sql        p_frame, p_input_set

8 doom_doors.sql         door, p_use, p_door_tick; 
                         takes over build_collision, 
                         move_blocked, p_frame

9 doom_sky.sql           render_sky; final p_frame 
                         (all later stages are OBJECT_ID-guarded, 
                         so nothing redefines it again)

10 E1M1_sprites.sql      reload it: now 444 frames, 
                         including the weapon HUD sprites

11 doom_weapons.sql      inventory, pickups, firing, render_psprite

12 doom_statusbar.sql    hud_patch, hud_column, render_hud

13 hud.sql               37 status bar patches (map-independent)
sqlcmd -S . -d doom -i E1M1_freedoom.sql
sqlcmd -S . -d doom -i doom_render.sql
sqlcmd -S . -d doom -i doom_present.sql
sqlcmd -S . -d doom -i doom_game.sql
sqlcmd -S . -d doom -i doom_sprites.sql
sqlcmd -S . -d doom -i E1M1_sprites.sql
sqlcmd -S . -d doom -i doom_sprites_fast.sql
sqlcmd -S . -d doom -i doom_client.sql
sqlcmd -S . -d doom -i doom_doors.sql
sqlcmd -S . -d doom -i doom_sky.sql
sqlcmd -S . -d doom -i E1M1_sprites.sql      # reload: weapon sprites added
sqlcmd -S . -d doom -i doom_weapons.sql
sqlcmd -S . -d doom -i doom_statusbar.sql
sqlcmd -S . -d doom -i hud.sql

doom_doors.sql must come after doom_game.sql and doom_client.sql: it redefines build_collision, move_blocked and p_frame. 
Re-running either of those later reverts the world to static. After loading it, run EXEC dbo.build_collision; — line_collide now stores sector ids rather than cached opening heights, so doors can move without invalidating it.

After doom_sprites_fast.sql, run EXEC dbo.build_thing_sector; once — it precomputes which sector each thing stands in, which keeps a scalar UDF out of the per-frame query.

doom_sprites.sql creates the tables and DELETEs their contents, so E1M1_sprites.sql must follow it. p_frame calls render_sprites only if it exists, so the game still runs without either.

Play

.\doom_client.ps1 -Server . -Database doom

W/S move, 
A/D turn, 
Q/E strafe, 
Shift run, 
SPACE open doors, 
CTRL fire, 
1–4 select weapon, 
R respawn, 
Esc quit.

-Tics 12 advances more simulation per displayed frame; -Scale 4 for a bigger window.

Healthcheck

EXEC dbo.doom_healthcheck;   -- optional but recommended
EXEC dbo.render_and_time;    -- run twice; the first pays a ~780ms compile
EXEC dbo.render_and_time;

Expect roughly: render_ms ~290, present_ms ~35, and pixels_lit close to 64,000 — with sky and the status bar in, the frame is fully covered.

pixels_lit = 0 means the texture tables are empty — the rasteriser INNER JOINs them, so it completes silently with no rows. Re-run E1M1_freedoom.sql.

Other maps

python wad2sql.py FREEDOOM1.WAD --list
python wad2sql.py FREEDOOM1.WAD E1M3 -o E1M3.sql
python gen_sprites.py FREEDOOM1.WAD E1M3 -o E1M3_sprites.sql

Sprite data is per-map: only the sprites that map’s things reference are extracted, plus the player’s weapons. The status bar graphics (hud.sql) are map-independent and do not need regenerating.

Needs wad2sql.py, wadtex.py, wadsprite.py, doom_schema.sql and doom_textures.sql in the same directory. Works on any vanilla-format IWAD or PWAD; extended (ZDBSP-compressed) nodes are detected and rejected.

Known gaps

  • No monster AI: things are drawn, collide and can be shot, but nothing moves or fights back.
  • Lifts, switches and walk-triggered lines: only manual (use) doors so far.
  • Locked doors refuse: there is no key inventory yet.
  • The status bar face is static; Doom’s reacts to damage and health.
  • Masked middle textures on two-sided lines (grates, vines) aren’t drawn, though their alpha masks are stored.
  • game_loop runs at ~12-15 tics/sec, not 35: WAITFOR DELAY rounds up to SQL Server’s ~15.6ms timer resolution. The client’s p_frame avoids this by batching tics per frame instead.

Tested against FreeDoom (BSD-licensed) so the whole thing can be shared without redistributing commercial game data. It works with a retail *DOOM.WAD* too — same parser, same schema.

Sourcecode is now available: https://github.com/sqlartist/DoomSQL

More from Trevor Dwyer