A Render Effects screen that shows what every graphics toggle is set to (DOOM-0205)
Options -> Renderer -> Render Effects lists the flashlight, SSAO,
de-tile, dirt/grime, wet liquid, volumetric fog and the profiler, each
with its current setting beside it. The toggles were hidden behind
hotkeys, so the only way to know what was on was to remember — which
caused a false bug report when two effects were off in a saved config
and the game looked wrong for no visible reason.
Menus can now be opened from the command line in developer builds, so they can be screenshotted (DOOM-0318)
-devmenu <name> opens a named settings screen as the level loads —
main, options, renderer, effects, video, sound or developer. Automated
screenshots could already reach any view of the world but never a menu,
because opening one needs a keypress and the desktop will not let a
script press keys inside the game. Two long-open menu items had been
waiting on someone checking them by hand for that reason.
-inspect and -freeze command-line flags for the developer view (DOOM-0294)
The Inspect preset (monsters ignore you, nothing can hurt you) and the
freeze-monsters switch were reachable only through the developer menu, so
an automated capture run could not use them. A capture taken in a live
level is not a measurement: an A/B of the wet-liquid layer reported 15% of
pixels moved where the real signal was 13.8%, the rest being a monster
walking through frame and the health counter ticking down.
Volumetric fog now takes its colour from the room: green over nukage, a red haze through Hell (DOOM-0011 L4)
Two area profiles feed the fog march. A nukage pool fogs green, and a Hell level (Inferno, or DOOM II from map 20) gains a thin red haze over everything. They stack — a goo room on a Hell level reads green through red. Torch shafts keep their own warm colour rather than taking the room's tint. Ultra's ray-traced view only; measured at under 0.02 ms.
Add a make compile_commands.json target for the static analysers
cppcheck and clang-tidy take a file's language from its extension, so a
whole-tree sweep was parsing the 10k-line Vulkan back-end as C and
analysing none of it. A compilation database gives each file the
compiler, standard and include paths the real build used.
Drifting fog now rolls through a torch's glow and cuts it, instead of sliding behind a still patch of light (DOOM-0300)
Light reaching the fog is attenuated along its own path from the lamp, so
billows passing in front of a torch actually dim it. Before this the glow was
a fixed shape painted over moving fog; now the two move together.
Torches and lava now light the fog around them (ray-traced views) (DOOM-0011)
A lamp, a light panel or a pool of nukage now glows into the air near
it instead of only lighting the walls. Which lights can reach which
patch of air is worked out once when the level loads, using the game's
own line-of-sight test, so a torch never shines through a wall and the
effect costs no extra ray tracing while you play. Indoors, where there
is no sky to light the mist, a nearby light is now the thing you see
by.
Developer screenshot: F12 saves the frame you are looking at (DOOM-0294)
F12 (or Developer → Capture Screenshot) writes exactly what is on
screen, HUD and all, to dev-shots/shot-NNNN.png — full display
resolution, in any 3D view, raster or ray-traced. The Classic tier
saves DOOM's own .pcx instead, since the software renderer never
builds an image for the GPU to present. Developer builds only
(make DEV=1).
Developer view: a testing menu for jumping to any level and looking at it (DOOM-0294)
An Options → Developer menu that jumps straight to any level in either
game at any skill, switches the monsters' attention off (or freezes
them) so a map can be walked and looked at rather than fought through,
opens locked doors, picks a path-tracer diagnostic view by name, and
prints where you are standing as the -warpto line that reproduces
the spot. A Play/Inspect switch flips the whole set at once, so the
same jump can also be used to test a level as a player meets it.
Developer builds only (make DEV=1): a released binary is compiled
without any of it.
Fog now billows: a near-white base with banks of visibly varying thickness drifting past (Ultra/Solid ray-traced view) (DOOM-0011)
The haze is no longer even. Two layers of slowly drifting 3-D noise thicken and thin the air, so mist pools in one part of a room and clears in another, and a bank passes in front of and behind a pillar as you turn. Costs about 6% of frame time on an RX 6600; the ; key still dials it Off / Low / Medium / High.
Fog now drifts indoors through doorways and windows (DOOM-0011)
Indoor fog used to stop dead at a roofline. It now fades in near an
opening onto the outdoors and thins as you walk deeper in, while a
sealed room that merely shares a wall with the outside stays clear —
the game works out the route through doorways, not the straight line
through the wall.
Floor fog outdoors — mist that pools around your feet (DOOM-0272)
A second fog layer that fades with distance from the camera as well as
with height, so the air at your feet can be genuinely misty without the
far end of a courtyard turning white. Outdoors only for now; indoor
rooms follow once the engine can tell a room with a window from a room
buried three doors deep.
Test coverage for the mus2mid happy path, font metrics, asset-path resolution and degenerate emitter input
mus2mid_test asserted only rejection paths; it now checks three hand-built scores byte for byte, covering note on/off encoding, the track-length back-patch, percussion-channel mapping, the DMX 0x80 clamp quirk and the MIDI-channel allocator's skip of channel 9. Also new: rb_text vertical metrics and the atlas-doubling retry, rb_materials' rb_asset_root/rb_asset_path joining, and emissive_derive's null/zero-area guard.
Headless boot-smoke CI gate that actually runs the engine (DOOM-0203)
A new -bootsmoke [N] engine flag boots the game on the GPU-free software
renderer, simulates N tics (default ~3 s) through the real game loop, and
exits cleanly. CI now runs it after the build + unit tests (under SDL dummy
drivers against a free Freedoom IWAD), so a boot or per-frame render
regression is caught automatically instead of only on a manual play-test.
Say how to fix it when the HD art is missing (DOOM-0042)
Ultra falling back to the 1997 art looks like a lighting regression in a
screenshot rather than a missing asset directory, so the message now
names DOOMASSETDIR as the thing to set.
Fog wisps churn at Silent Hill 2's rate, and each map gets its own drift heading (DOOM-0300)
The fog's glow read as a painted patch because the wisp pattern needed 24
seconds to cross one noise cell, where Silent Hill 2's fog fully
restructures in under 2.2. Both octaves now drift 15x faster and are
exactly opposed, so the fog dissipates and reforms in place instead of
blowing past -- measured at 0.4% of the change explained by translation,
against SH2's own 0-1%. Each level also draws its own heading, seeded
from the map so captures stay reproducible.
Bake the sun's fixed direction into a load-time clearance field and delete L2's per-sample ray. (DOOM-0289)
The volumetric fog's sun visibility is now baked into a load-time
field instead of a shadow ray fired from every fog sample, so the fog
costs 3.2% of a frame instead of 44% -- 28 fps back up to 42 on the
test machine, with the picture unchanged.
The play area is never framed by a border -- a fresh install fills the screen (DOOM-0285)
The old default drew the 1993 decorative frame around a shrunken picture. The status bar stays; only the border goes.
Ultra's ray-traced view is about 17% faster — 41 to 48 FPS on the test machine (DOOM-0197)
The processor and the graphics card were taking turns instead of working
at the same time: each frame spent 3.6ms preparing moving doors and lifts
and only then started waiting on the card. That preparation now happens
while the card is still drawing the previous frame, which is time the
frame no longer spends. Nothing about the picture changes.
Indoor mist now reaches further in from an opening, and no longer halves at the threshold (DOOM-0281)
Fog standing in a doorway was capped at half the density of the air just
outside it, so it visibly halved the moment it crossed the opening; and it
faded out within about two door-widths, so anyone standing back in a room
saw none of it. Mist now carries most of the outdoor strength at an
opening and reaches roughly twice as far inside. A sealed room stays
clear, which is guaranteed by how the two dials are defined rather than by
the values chosen.
Ultra's ray-traced fog is ~7.5 ms/frame cheaper — 31 to 41 FPS with fog on (DOOM-0276)
The fog used to fire a test ray straight up from every one of its 24 samples per pixel, just to ask "is there sky above here?". DOOM is flat-mapped — one ceiling per spot on the floor — so that answer was already in the small per-level map the fog builds for its doorway seep. It now reads it from there instead. Measured on an RX 6600 in E1M1 at 50% render scale: fog costs +35% of frame time before and +4% after. The one visible cost is that the line between misty outdoor air and clear indoor air now follows that map's 64-unit grid, so it can sit up to half a cell from the wall.
nee_sampling_test's unbiasedness bound is derived from the sample count instead of a flat 0.5%
The old fixed tolerance sat only ~3.9 sigma from the estimator's true standard error on two of the five weight sets, well short of the 6 sigma the neighbouring frequency check uses. The bound is now computed from the exact estimator variance, so it scales with N and the weights; all five sets currently land within 1.6 sigma.
The Windows build no longer fails to compile
Two faults, both from the developer-capture work and both invisible on
Linux. -warpto used a 64-bit whole number without asking for the
header that defines one — Linux picked it up by accident from a
neighbouring header, the Windows cross-compiler did not. The F12
screenshot folder was then created with a two-argument call that
Windows spells with one, the way -cdrom already does elsewhere in the
engine. Every Windows build since those flags landed stopped with an
error. Caught by the release build, which makes both versions before it
publishes anything.
The lighting self-test now covers the glowing-sprite light path it was skipping (DOOM-0122)
-rtverify proves the renderer's lighting maths is unbiased by comparing
two estimators that should agree. Both were being handed a light list that
stopped short of the sprite lights — fireballs, glowing pickups — so the
newer half of the lighting code was never actually checked. Both estimators
now receive the real split, and the verify run reports how many lights of
each kind it covered, so a future gap is visible rather than silent.
-devshot N now captures the Classic tier as well as Solid and Ultra (DOOM-0294)
Classic never builds a Vulkan swapchain image, so the flag was a silent
no-op there -- and a harness that finds no new file will happily read a
stale one. All three tiers now write dev-shots/shot-NNNN.png under one
shared naming helper.
Bound the scaled patch blitter and the Classic menu's text rows (DOOM-0230)
The scaled patch draw added for the menu work never got the on-screen
bounds check its two siblings have, and the menu text helper that
calls it clipped sideways but not vertically. Off-screen text is now
dropped with a one-off note instead of writing outside the frame.
Skip the frame when the video texture cannot be locked
A failed SDL_LockTexture leaves the pixel pointer untouched, and the
Classic renderer wrote the whole frame through it anyway.
Ordinary walls no longer glow like lights, and DOOM's own light fixtures now do (DOOM-0307)
A curated list of light-source wall textures replaces the brightness test
that decided this before. That test rated a texel by its strongest colour
channel, and DOOM's palette ramps nearly all end at 255 in some channel,
so the palest cement highlight scored exactly as pure fire did — 82 of
DOOM II's 428 wall textures cast light, including all nine CEMENTs and
every Wolfenstein wall. It was wrong in the other direction too: the
game's own light panels (LITE3, LITE5, LITEBLU, COMPSTA) sat below the
threshold and emitted nothing, so the lamps were dark while the walls
glowed. Both are fixed; sprites, flats and liquids are unchanged.
A torch revealed by a door that opens now lights the fog in front of it, instead of waiting for a level reload (DOOM-0296) (DOOM-0296)
Which torches can light which patches of air was worked out once, when
the level loaded — so a torch behind a shut door stayed unknown to the
fog even after you opened the door. The answer is now recalculated
whenever a door or lift finishes moving.
Honest note on what you will see: on the stock maps, very little. This
was measured rather than assumed — opening every door in DOOM 2's MAP01
changes the picture by about a five-hundredth of the threshold the
project uses to decide a look has changed at all. Vanilla DOOM rarely
puts a torch behind a shut door and close enough to matter. It is a
correctness fix that stops a wrong answer being possible, and any map or
texture set that does place a bright light behind a door gets it for
free.
The renderer's -rtverify self-test failed on DOOM 2 and passed on DOOM 1, on the same build (DOOM-0297)
The check was under-sampled, not the renderer wrong. DOOM 2's lights are
fewer and more clustered, so the estimator needs more samples there to
settle; the test now takes them (262144 vs 16384) and both games pass the
same unchanged 0.50% bar. Raising the sample count can only make the test
stricter, so nothing was loosened to get there.
A nukage pool glowed only in patches, because the per-texel emissive mask was applied to liquids too. (DOOM-0302)
Nukage pools looked like they had random glowing spots in them. Now the whole pool glows evenly.
The -shotverify capture is not tic-deterministic, so a cold-cache run blesses a different golden. (DOOM-0287)
Adds a -noinput flag so an automated test or profiling run ignores the
keyboard, mouse and gamepad and leaves the pointer to whoever is using the
desktop. The -shotverify / -shotcompare capture modes imply it, which
also makes their golden-image comparison trustworthy: a stray mouse
movement during a capture used to turn the camera and change the image.
Screenshot captures are no longer time-dependent, so the visual-regression gate compares like with like (DOOM-0011)
The drifting fog and the liquid ripples both ride a wall clock, which made every capture a slightly different image.
Fog now rolls into a room whose wall opens during play (DOOM-0281)
The seep field that decides how far fog reaches indoors was flooded once
at level load from the doors' spawn state, so a wall that opened in play
left the room behind it permanently clear. It is now re-flooded when an
opening actually appears or vanishes, and the fog eases across to the new
answer over about a second so the mist drifts in through the opening
instead of popping into place. A door that shuts eases it back out.
Ultra's ray-traced view; costs nothing on a map where nothing has moved.
Fog and seep no longer read the empty-space sentinel along a level's far edges (DOOM-0276)
The seep map's grid was sized with a rounding-down divide, which left its outer "nothing here" ring overlapping real air at the top and right edges of a level. E1M1's grid was one column short. Found by the review of the change above, and it had to be fixed for that change to be correct.
Fix the correctness and doc-drift findings from the 2026-07-26 audit + indie-review sweep. (DOOM-0263)
The baked global-illumination pass was reading the sky as if it were a wall; several smaller bugs and six stale documentation claims are fixed too.
game_select_test drives the real IWAD selection loop rather than a copy of it (DOOM-0244) (DOOM-0244)
The preference scan moved from D_DetectIwads into iwad_select_reps() in iwad_detect.h, parameterised by an "is it installed?" predicate — access() for the engine, an in-memory set for the test. Changing the real selection order now fails the test, which it could not do before.
rb_text_test bakes the bundled Oxanium instead of skipping itself when no system font is installed (DOOM-0243) (DOOM-0243)
It used to look for DejaVu at three distro paths and, finding none, print "skipped" and exit 0 — a green result that ran none of its assertions. It now bakes the embedded assets/Oxanium-SemiBold.ttf the engine itself ships, so a missing font is impossible; fonts-dejavu-core is no longer a CI dependency.
Test suite no longer passes vacuously — assert() retired for a check() helper (DOOM-0242) (DOOM-0242)
assert() is deleted by -DNDEBUG along with any call inside it, so the four tests that wrote assert(function_under_test(...) == x) would have printed "all passed" while running nothing. Reproduced, then fixed by moving the whole suite onto tests/check_util.h, whose check() is an ordinary function that also records a failure and keeps going instead of aborting and hiding every later case.
Corrected mold from a required to an optional dependency in the README.
The README listed the mold linker among the required Linux dependencies. The Makefile detects it and falls back to the default linker when it's absent, so a first-time builder was being sent after a package they don't need.
The local CI mirror now runs the same boot smoke as GitHub Actions.
packaging/ci-local.sh announced itself as "exactly what GitHub Actions runs", but its container mode ran only build + unit tests and skipped the DOOM-0203 headless boot smoke. A boot regression could pass locally and still turn CI red. The container path now installs Freedoom and runs the smoke, as the workflow does.
release.sh now updates README's "Latest release" line (DOOM-0202 debt sweep).
The release standard says the version lives in three places in lockstep — the git tag, the CHANGELOG heading, and README's "Latest release" line — all moved by the release tool. The tool only ever moved two of them, so every release shipped with a stale README. It now rewrites the README line too, and fails loudly if it can't find it.
Golden-image gate no longer inherits your fog setting (DOOM-0202).
The -shotverify / -shotcompare capture pins a canonical render configuration so a screenshot comparison can't be poisoned by a play-test tweak left in ~/.doomrc. Volumetric fog (rt_fog) shipped after that pin was written and was never added to it, so the fog level leaked back in — exactly the config-dependence the pin exists to prevent. It is now pinned with the rest.
Golden-image visual-regression gate is now config-independent (DOOM-0208).
The -shotverify / -shotcompare modes rendered the normal path, which
inherited the live ~/.doomrc — so a play-test tweak (a brighter
rt_brightness, a stuck-on flashlight, a flipped effect toggle) silently
poisoned the golden and made the gate fail on an unchanged render. They
now pin a canonical, config-independent RT configuration (the shipped
defaults) whenever armed, so a capture is reproducible regardless of the
user's live config. Re-blessed the E1M1 Ultra-RT golden under the
canonical config (fresh compare is bit-exact, mae=0.000).
Fix three low-severity bounds nits: donut NULL-deref, sfx off-by-one, basedefault snprintf. (DOOM-0220)
Three small safety tidy-ups: a malformed WAD donut, a sound-index edge, and a long $HOME path can no longer misbehave.
Fix -warp E/M bounds so -warp 3 on a non-commercial IWAD can't NULL-deref. (DOOM-0219)
Typing doom -warp 3 on DOOM 1 crashed at startup; now it's handled gracefully.
Guard RB_BuildPSprites against a negative sprite lump index. (DOOM-0218)
A missing weapon-sprite frame could read out of bounds; now it's skipped, matching the world-sprite path.
Drain the GPU before RB_Vulkan_BuildLevel frees/recreates live buffers. (DOOM-0217)
Loading a new level could, in rare timing, free graphics memory the GPU was still using; now the GPU is drained first.
Clamp menu value-name indices (showMessages/fpsCorner) against a hand-edited config. (DOOM-0216)
Editing ~/.doomrc to an out-of-range value could crash the game when opening the menu; the value is now clamped.