Ants Projects Hub
← All projects

Beta · forked from id-Software/DOOM

DOOM Ants

A ray-traced reimagining of id's DOOM on modern Linux.

BetaWINLNXLatest: v0.7.2 ·
The classic DOOM title screen with the New Game menu, rendered by DOOM AntsClassic DOOM gameplay in a widescreen view — a marine facing a computer-room level over a blue floorThe same DOOM level with hardware ray tracing — realistic shadows and dynamic lighting darkening the room

In 1997, id Software released the source code to DOOM. It is a beautiful piece of 1990s engineering: it fakes 3D with a clever 2D trick and draws every single pixel on the CPU, because in 1993 there was nothing else to draw them with.

DOOM Ants takes that code and turns the lights on.

The two halves

First, make it run today. The original code expects a 32-bit machine, the X11 graphics system of the era and a sound interface that no longer exists. That part is finished — it builds and plays on 64-bit Linux and Windows.

Then, the interesting part. Evolve the renderer toward real 3D using the ray-tracing hardware in a modern graphics card, without losing what DOOM feels like to play. That part is in progress.

Three ways to see it

You switch between them from the in-game menu, mid-game.

  • Classic — the original 1997 software renderer, pixel for pixel, now with widescreen support.
  • Solid — the same DOOM world drawn on the graphics card, with dynamic lights and contact shadows.
  • Ultra — a full path tracer. Ray-traced lighting and shadows, HD physically based materials, a flashlight that moves with you, ambient occlusion, and grimier surfaces that no longer repeat visibly across a wall.

Ultra needs a graphics card with ray-tracing support. It is developed and tested on an AMD RX 6600, which is a mid-range card rather than an expensive one.

What you need to play

A DOOM data file, which is not included and cannot be — the game's assets are still commercial property. The shareware doom1.wad is freely available and works; so does a retail doom.wad or doom2.wad from a copy you own.

Downloads for Linux and Windows are on the buttons above. Classic mode runs anywhere; Solid and Ultra need a working Vulkan driver.

Where it stands

Playable, and honestly early. The engine runs, all three renderers work, and the path-traced mode already looks like something the original could not have imagined. What is still moving is everything that makes Ultra consistent — the target is a solid 60 frames a second floor while looking like that.

The original code is © id Software and this inherits its GPL v2 licence. This is a fork of their released source, and all the credit for DOOM itself is theirs.

Changed

  • Levels load faster, noticeably so on large custom maps (DOOM-0399) Building each room's wall list used to re-scan every wall in the map once per room. Verified to produce identical results on all 68 original maps.

Fixed

  • Starting with a savegame the game cannot read now returns to the title screen instead of crashing (DOOM-0429) It was left with no level loaded at all and crashed a moment later.

  • A power loss while saving settings can no longer leave them empty (DOOM-0400) The new settings are now flushed to the disk before replacing the old ones.

  • A failed read from a WAD is reported instead of silently returning an untouched buffer (DOOM-0400)

  • A WAD given on the command line with a very short name no longer reads past the start of it (DOOM-0400)

  • Saving no longer destroys the previous save if the write is interrupted (DOOM-0399) The new save is written alongside the old one and swapped in only once it is safely on disk.

  • Loading a save from a different build says so instead of doing nothing (DOOM-0399) The menu simply closed, with no way to tell a refused load from one that worked.

  • Maps that the original game played but this build refused now load again (DOOM-0399) A wall marked as having two sides but supplying only one is treated as one-sided rather than rejected.

  • A lift or floor that lowers and changes its surface no longer quits the game (DOOM-0398) The floor copied a leftover value into the sector when no neighbouring floor sat at the height it was moving to, and standing in that sector ended the session.

  • A custom map's odd sector setting no longer quits the game (DOOM-0398) One unrecognised number in a downloaded map used to end the session as soon as a player stood on that floor. It is now ignored, as in every modern DOOM port. No original map contains one.

  • A crushing lift caught on something no longer sinks to a random height or freezes (DOOM-0398)

  • A room that runs out of ceiling slots is released instead of being left unusable for the rest of the level (DOOM-0398)

  • A switch click is played from the switch rather than from an unrelated position (DOOM-0398)

  • Switches keep working even if the internal switch table loses its end marker (DOOM-0398) Previously that would have left every switch in the game silently doing nothing.

  • A map thing whose type is not positive no longer spawns a stray player or aborts the level load (DOOM-0397) A THINGS record of type -1 matched MT_PLAYER's doomednum and spawned a player-shaped monster with no player behind it; type 0 quit the game during level load. Both are now ignored.

  • A map with no start position for a player is refused by name instead of crashing (DOOM-0397) On the first level the game crashed on a null pointer; on a level change it read memory it had just released. It now says which player's start is missing and stops.

  • Telling monsters to forget you no longer crashes the game with a BFG shot in flight (DOOM-0397) Developer builds only. A missile stores its owner in the same field a monster stores its prey, and clearing it left the BFG's spray with nothing to aim from.

  • Hostile sound data, network packets and a bare -net switch can no longer take the game down (DOOM-0386) A sound in an add-on file could claim an absurd recording speed, which made the game try to reserve about eleven gigabytes and get itself killed by the system. Sounds outside a sensible range now fall back to the standard one, and an oversized conversion is refused. A game packet from the network could claim to hold more player input than it actually did, and the extra was read out of leftover memory and treated as movement and button presses; a packet is now checked against its real size. And starting the game with -net and nothing after it crashed instead of explaining what was missing.

  • A corrupted or hostile WAD or config file is now turned away instead of trusted (DOOM-0384) The WAD reader checked that a file's table of contents fitted inside the file, then believed every entry in it. An add-on file could claim a piece was enormous, or sat at a negative position, and the reader would try. Each piece is now checked against the real file, and a file too short to hold a header is refused rather than read as leftover memory. Separately, the settings file could change a setting's type -- putting text where a number belongs, or the reverse -- which wrote past the setting or left the game following a bad address. Such a line is now ignored with a message and the built-in default kept.

  • Startup no longer reads past the end of the sprite-name table (DOOM-0423) Sprite setup counted the game's sprite names by scanning for an end marker the table did not have, so every launch read a little past it. Nothing visible went wrong, because the value just past the table happened to be the marker it wanted. The table now carries the marker itself.

  • A malformed level no longer crashes the game as it loads (DOOM-0422) A level whose wall data omitted a required side made the engine follow a pointer that was never set, killing the game the moment the level loaded. Such a level is now turned away with a message naming the offending wall. No level in either shipped game is affected.

  • Saving a game now checks it has room before each write, instead of reporting an overrun after it happened (DOOM-0374) Saving wrote into a fixed half-megabyte buffer with nothing stopping it running past the end; the check that was meant to catch this only ran once every byte had been written. On a large custom level that is ordinary play, not an attack. A save that will not fit is now refused with a message naming what did not fit, before anything is overwritten.

  • A graphics card that can run Solid but not ray tracing now falls back to Solid (DOOM-0380) If your settings asked for Ultra on such a card, the game dropped all the way to the original 1993 renderer, with the Solid view sitting unused. It now steps down one tier at a time and stops at the best one your machine can actually run. A setting that names no tier at all still gives you Classic.

  • The ray-tracing self-test no longer passes when it measured nothing (DOOM-0376) This test is the gate the project requires before any ray-tracing change ships. Both of its scores started at zero and stayed there when there was nothing to measure, and zero counted as a pass — so a run that tested nothing reported success twice. It now reports how much it actually measured, says INCONCLUSIVE when that is nothing, and its exit code matches its verdict, so a script or a build step can rely on it instead of a human reading the output.

  • The pre-push check now says when it could not run a check (DOOM-0375) Before every push, a script runs the same checks the build server runs. If it could not run one — no game file to test against, no Windows compiler installed — it skipped it silently and still reported both jobs green, and the push went through. It now names each check that did not run, reports the run as partial, and refuses the push, telling you apart from a real failure.

  • Asset and WAD tooling: a crash-on-typo path, a silent rm -rf, and dead code stage_hero.py dereferenced the result of importlib.util.spec_from_file_location and its .loader without a null check, turning a wrong path into an opaque AttributeError; it shelled out to rm -rf through subprocess with no error check, now shutil.rmtree; and it carried an unused LICENSES constant. wad_seg_probe.py gains an explicit strict= on a deliberately ragged zip() and loses an ambiguous l identifier.

  • A failing rb_materials test read uninitialised memory instead of reporting tests/rb_materials_test.cpp read order[1] unconditionally, but rb_apply_budget writes only n_loaded entries and none at all on its out-of-memory path. Since check() deliberately does not abort, the array was read uninitialised exactly when an earlier assertion had already failed — undefined behaviour in the test precisely when it was diagnosing a regression.

  • Dead code removed from the 1997 engine: eight defects, no behaviour change A redundant null check in p_doors.c after an identical early return; a duplicated viewactive = true in g_game.c; dead patch assignments in r_data.c (x2) and dead thinker assignments in p_enemy.c and p_telept.c, each immediately overwritten by the following for initialiser; unused numplaying in d_net.c and totalwidth in r_data.c; and the frac/fracstep pair in R_DrawFuzzColumn, which the fuzz effect never reads because it samples the framebuffer rather than the source column. The engine now builds with zero compiler warnings.

  • r_vulkan.cpp no longer fails to compile under clang A block-scope extern int rb_profile; conflicted with the file-scope extern "C" definition of the same symbol in the same translation unit. GCC accepts this and clang rejects it outright, so the file was uncompilable on any clang-based toolchain while the normal build stayed green.

  • A mistyped -rtview or -rippletime value no longer silently selects a different setting Both flags parsed their argument with atoi/atof, which report no error: -rtview hgh became view 0 and -rippletime fast pinned the ripple clock at zero, in each case overriding the default with nothing printed. r_backend.c's own comment already promised the opposite. A value that is not wholly a number is now refused and the default kept, per DOOM-0026 INV-3. New rb_argparse.h + tests/rb_argparse_test.cpp (28 assertions, proved red before the fix).

  • A release with nothing written down about it is refused instead of published. (DOOM-0358) The release tool took its description straight from the changelog and never checked there was anything there, so cutting a release with an empty changelog section published it with a completely blank body. It now stops before tagging and says which section came back empty.

  • A release can no longer ship a build older than the code it is tagged against. (DOOM-0356) The release tool used to treat any file with the right name as "already built", so it could publish a binary made before the fix the release was cut for -- which is how 0.7.1 first went out. Each build is now stamped with the commit it came from and only reused for that commit, and every published file is downloaded again afterwards and checked against what was built.

  • The version now moves everywhere at once when a release is cut. (DOOM-0357) The changelog heading, its links and the README's "Latest release" line used to move only on one of the tool's two routes, so a changelog written by hand left the README advertising the previous version. All of them are now brought up to date on either route, the tool refuses to tag while any of them lags behind, and a preview build deliberately leaves the README pointing at the last full release.

Security

  • Saving settings can no longer be tricked into overwriting another file (DOOM-0400) The temporary file the game wrote alongside your settings had a predictable name and followed a symbolic link, so anything else on the machine could aim that write at a file of its choosing.

  • Reloading a WAD that has changed size is refused instead of corrupting memory (DOOM-0400) Only reachable through the developer-only "-file ~name.wad" option. A crafted file could crash the game outright.

  • A crafted map or savegame can no longer trick the loader into an undersized allocation (DOOM-0399)

  • A map naming something that is not a floor texture is now refused (DOOM-0381) A map could name any piece of data in the WAD as its floor, and the original renderer turned that name into a position in a list without checking it was a floor at all — then wrote to that position. The 3D renderer already guarded this; the original one now does too. Every map in both official WADs was booted to confirm real maps are unaffected.

  • Loading a saved game now checks every number in it that picks an entry from a list (DOOM-0373) Some of those numbers were checked and some were not, which is the confusing kind of bug: the guard exists, so it looks finished. Two of the unchecked ones let a save file you were handed write outside its list — your current weapon and your floor and wall textures among them. All of them now go through the same check the file already used elsewhere.

  • Doors, floors, switches and platforms no longer act on a wall that has no second side (DOOM-0372) The map loader deliberately records "no side here" as a marker value, and eight places used that marker as a position in a list without checking. The worst was a door: pressing use on a one-sided wall with a door action on it produced a bad pointer that the engine then wrote through every frame while the door moved. All eight are closed.

  • The demo clips at the title screen can no longer pick a player slot that does not exist (DOOM-0371) Those clips come from the WAD file, and one byte of a clip chose which of the four player slots the game wrote to, unchecked. Loading a hostile WAD was enough to trigger it — the title screen plays them on its own. Demo playback also had no end marker check, so a clip missing its terminator was read past the end of its own data, four bytes per frame, until it happened to stop.

  • Two parts of a map file are now checked against their real size (DOOM-0370) Both were trusted for more than they contained, so a crafted map could make the game read far outside its own memory and, in one case, write to it. The second of the two is not even an attack: many ordinary map editors produce a short or empty one, and the game now fills in the missing part with the safe answer rather than reading whatever sat next to it.

  • Five fixed-size lists in the original game code are now bounded (DOOM-0369) Each added to a list without ever checking it was full, so a map downloaded from the internet could overflow one and write over memory it did not own — which rarely crashes outright, it just corrupts whatever sat next to it. One of the five needed no hostile map at all: large community levels routinely have more scrolling walls than the list could hold.

  • CI workflow hardened: SHA-pinned action, least-privilege token, no persisted credentials actions/checkout was pinned to the mutable v7 tag, which can be repointed at any commit; it is now pinned to the commit v7.0.1 resolves to, with the version in a trailing comment. The workflow had no permissions: block, so it inherited the repository default — it now declares contents: read, which is all either job needs. Both checkouts set persist-credentials: false, since neither job pushes. zizmor goes from 7 findings to 0.

Full changelog · 10 earlier releases →