The technical breakdown of Wolfenstein GX is long. This post is short. It’s the trailer — nine seconds at a time, animated, no preamble. If you want to know how any of these effects work, the deep-dive post has the code. If you want to know what the game actually looks like before you commit to reading 4,000 words about RGB lightmaps and BFS pathfinding, this is the place.
All of it: one .gx file, ~6,200 lines, transpiled to C, raylib for graphics, runs at 60 FPS native and in your browser.
Let’s go.
The opening

The splash screen is its own little render pass — animated background, music, “PRESS ANY KEY TO CONTINUE” with the percentage-based bottom margin so it doesn’t get clipped on web at oddly-scaled viewports. Title screens are usually an afterthought; in a single-file game the title screen is a state in the same state machine that runs the gameplay. STATE_MENU returns early before the gameplay input handler runs, so the splash uses digit keys (1/2/3 for quality) without conflicting with weapon hotkeys later.
Light through an opening door

Watch what happens when the door opens: the corridor brightens, because the torch on the far side now reaches cells that were previously occluded.
This isn’t faked with a tint or a particle effect. The lightmap is a per-cell RGB array, recomputed at the exact moment the door slides past 50% of its open animation. Closed → light source can’t see those cells. Open → it can. The threshold-flip detector is one line:
var now_blocking = (g_door_timer[i] < 0.5)
if (was_blocking != now_blocking) { lightmap_dirty = true }
Per door cycle that’s exactly two recomputes: open-half and close-half. The whole feature is ~20 lines. The corridor goes from AMBIENT-only to torch-tinted on a timed beat that lines up with what your eye expects.
See-through iron bars

This is my favorite engine trick in the whole project. Iron bars are a special door type that doesn’t block the renderer. The DDA raycaster hits the bars, records “there are bars here” in a per-column scratch buffer, and keeps going to find the actual back wall. A separate post-pass paints the bars on top with alpha-cut transparency.
Result: you can see through the bars to the alcove behind. The goal sits there, lit by its own torch, visible from across the level. It telegraphs the objective without breaking the engine — and it costs five lines in cast_ray() plus one overlay pass.
Soldiers that flank you

Soldiers run a 7-state machine (IDLE → TARGET → SHOOT → RUN → HURT → DYING → DEAD) with a chase strategy that picks one of two modes per frame:
- Line-of-sight clear? Zigzag straight at the player.
- Lost sight? BFS over the 32×32 grid to the last known position, walk the first step, replan every 0.5 s.
The whole AI — perception, planning, movement, door coordination, stuck recovery — is about 80 lines of GX. It plays well at 60 FPS with dozens of enemies because every cost is bounded: BFS is O(cells) on a 1024-cell map, the 0.5 s throttle staggers replans naturally across enemies, and three i32[1024] scratch arrays cover all the working memory with zero allocation.
The pathfinder calls enemy_try_open_door() on each waypoint, so the door is already swinging by the time the soldier reaches it. The chase looks fluid instead of stuttering at every threshold.
Knife

Three weapons in the game. The knife is the fun one. Press 2 to equip, left-click to stab. Or — and this is the cute bit — press V from any weapon: the game auto-switches to knife, plays the full slash animation (idle → raise → stab → recoil), hits in front like a normal knife, then auto-reverts to whatever you were holding.
The animation flows through one shared start_knife_attack() helper. Equipping, hot-swapping, V-stab — all the same code path with different return-to-weapon state.
RPG: a flying flashlight

A rocket flying down a dark corridor lights the walls as it passes. This isn’t precomputed. The rocket owns a slot in the dynamic lightmap — radius 2.5, color (0.9, 0.85, 0.7) — and updates the slot’s position every frame as it travels.
if (g_rocket_active[i]) {
if (g_rocket_dyn_id[i] < 0) { g_rocket_dyn_id[i] = dyn_light_acquire() }
// write current x/y/intensity/color to that slot
} else if (g_rocket_dyn_id[i] >= 0) {
dyn_light_release(g_rocket_dyn_id[i]); g_rocket_dyn_id[i] = -1
}
The acquire/release is symmetric and idempotent — leak-proof by construction. Rockets, exploding barrels, the boss’s low-HP fire halo, all use the same dyn-light pool. Adding a new dynamic light to anything in the game is two function calls.
Explosion

Barrels you can shoot. Rockets that detonate on impact. The explosion sprite itself is forced to WHITE (self-emissive — the explosion is its own light source, not modulated by the world’s lightmap), but the dynamic light at the impact point is what sells the punch. Flickering intensity ramp from 1 → 0 over the explosion lifetime, color (1.0, 0.6, 0.2), radius 4.0.
For a single frame the corridor walls are washed orange, the soldier in the doorway is rim-lit from the blast, and then it fades. Same sample_light() everything else uses. Same lightmap. The explosion didn’t need a special renderer — it just registered as a dyn light and let the existing pipeline do the work.
The boss

The boss has a 9-state machine including a multi-phase death: head explosion → body stagger → final collapse, each with its own duration and visual. It also has super-armor states (BOSS_PREPARE, BOSS_SHOOT) where its sprite gets tint overrides regardless of world lighting — bright pulses through the lightmap shading.
When its HP drops below a threshold, the boss registers a flickering fire halo at its feet ((1.0, 0.4, 0.1), flickering at 0.5 + 0.1·sin(timer·6)). Your lightmap reads orange near him in his death throes. The boss is the only enemy that casts presence through the lighting — your eyes can tell where he is even before you turn the corner.
You died

Every damage source — bullets, explosions, fire, melee — routes through one helper:
fn hit_player(damage: f32) {
if (g_player_dead) { return }
g_player_hp -= damage
if (g_pain_cooldown <= 0.0) { play_player_pain(); g_pain_cooldown = 0.4 }
if (g_player_hp <= 0) {
g_player_dead = true; g_death_timer = 3.0
show_hud_msg("YOU DIED")
}
}
400 ms cooldown so continuous-damage sources (like standing in a torch fire) don’t spam audio at frame rate. f32 damage so dt-scaled fractional damage accumulates correctly instead of truncating to zero each frame. One callsite owns HP math + death transition + pain audio. Centralizing this fixed a real bug — fire damage used to be applied via inline arithmetic that had no death check, and the player could keep “burning” past 0 HP indefinitely.
The “YOU DIED” overlay fades in over the death timer; the game state machine transitions back to menu. No special-case code per damage source. Cache the predicate, not the result.
Try it
It’s running in your browser right now over in the Showcase section. Click the canvas to capture the mouse, then: WASD + Shift to move, mouse to look, Esc to release, 1/2/3 for weapons, V for quick stab, F2/F3/F4 for quality presets.
Want the source? projects/wolfenstein.gx in the GX repo. Want the engineering breakdown? Building Wolfenstein in 6,200 Lines of GX walks every system.
DOOM is next. That’s going to be a longer post.