We ported the original Doom to SQL
We Ported the Original Doom to SQL
By Lukas Vogel | September 22, 2026 | min read CedarDB Cloud is coming soon! Join the Waitlist
⚡ TL;DR
We have successfully ported the game logic and rendering engine of the original 1993 Doom entirely into SQL, executing the entire experience inside a database.
| Component | Performance / Spec |
|---|---|
| Game Loop | Fixed 35 FPS |
| Renderer | Up to 60 Hz (on AMD Ryzen 7 7840U) |
| Resolution | 320x200 frame buffer |
| Host Language | Python (I/O only) |
Try it yourself: You can play the shareware version of Episode 1 right now! 🇪🇺 [EU Servers] | 🇺🇸 [US Servers] Note: Deathmatch mode is active with four slots available on a first-come, first-served basis.
Pro Tip: If the game queue is full, you can still use SQL queries to inspect the live game state in real-time while you wait.
From Raycasting to BSP Trees
Previously, I created something that rendered ASCII art resembling Doom at 30 FPS. While popular, critics correctly noted that it was Doom actually closer to Wolfenstein 3D because it relied on a raycasting approach.
Real Doom is fundamentally different. It utilizes BSP (Binary Space Partitioning) trees, which allow for:
- Efficient depth ordering.
- The use of textures.
- Arbitrary wall angles.
- Variable floor and ceiling heights.
Determined to do it right (and utilizing some time during parental leave), I developed SQLDoom.
🛠️ The Ground Rules
To ensure this wasn't just a "cheat," I established a strict set of constraints:
- Visual Fidelity: It must look like the authentic Doom.
- Game Feel: It must play and behave like the original.
- Pure Output: The only acceptable SQL output is a table or a bitmap containing exact RGB values per pixel.
- Limited Client Scope: A client in another language is allowed, but only for:
- Parsing user input.
- Driving the game tics.
- Displaying the resulting bitmap.
- DB Flexibility: User-defined functions (UDFs) within the database are permitted.
System Architecture
I used a pygame script to handle the interface, triggering a game tic 35 times per second. The logic and rendering, however, reside entirely in the DB.
The logic runs on a strict 35 Hz loop, but the renderer is a pure function of the state tables, meaning the client can request frames as fast as the hardware allows.
📦 Loading Game Data
The .wad file format used by Doom is surprisingly relational. The hierarchy looks like this:
VERTEXES LINEDEF SIDEDEF SECTOR THINGS.
Converting a WAD into a database was a straightforward process involving roughly 1,000 lines of Python. To demonstrate, here is a query that renders the famous E1M1 map from a top-down perspective:
WITH wall AS (
SELECT round (( v1.x ) * t / 32.0 ) / 48 ) AS col, -- 48 units per column
round (( v1.left_sd_id 0 AS solid -- one-sided lines are pass-through
FROM linedefs l, generate_series ( 0 , 32 ) AS t -- walk each line in 32 steps
JOIN vertexes v1 ON ( v1.map_id = 1 )
SELECT string_agg (
CASE
WHEN ( col , row ) IN ( SELECT col , row FROM wall WHERE solid ) THEN '#'
WHEN ( col , row ) IN ( SELECT col , row FROM wall ) THEN '.'
ELSE ' '
END , ''
ORDER BY col
)
FROM generate_series ( - 16 , 79 ) AS col, generate_series ( - 51 , - 21 ) AS row
GROUP BY row
ORDER BY row DESC;
Output:
#####################
# ..................#
# .##.### #
################
####### ####.........##.##........#######
.####### ### # ###########.#################
# #### # # ## #.# ####......
# #####
🕹️ The Game Loop
Porting the "feel" of Doom is the hardest part. Consider the complexity of a single rocket launcher blast:
- Input: Polling movement, turning, and firing.
- AI: Enemies navigating and attacking.
- Physics: Projectiles moving through space.
- Combat: Calculating blast radii for explosions.
- Visuals: Sprite rendering, animations, view bobbing, and the HUD.
The Timing Budget
The original game operated on a fixed clock. The math for the budget per tic is:
In SQLDoom, we maintain this logic to keep the game constants accurate, but we decouple the rendering. We use camera interpolation between tics so the movement feels smooth even if the frame rate exceeds 35 FPS.
The Tic Sequence
Because game tics are procedural, we use cedarscript (a language similar to PL/pgSQL) to plan the execution order.
Example Tic Logic:
doom_cs_clock ( map , p );
let mut plan = doom_cs_plan ( map , p ); -- returns a bitmask of functions to trigger
let use_queued = doom_tic_use ( map , p , plan );
if ( plan & 2 ) != 0 OR use_queued {
active = doom_cs_activate_specials ( map );
}
if ( plan & 4 ) != 0 OR active != 0 {
doom_cs_doors ( map , p );
}
doom_tic_move ( map , p );
![Image: GIF of rocket launcher gibbing soldiers] Caption: Rule 2 in action: The visceral feel of Doom, powered by SQL.