Doomformer compiles integer neural networks into DOOM maps

Doomformer is a GitHub project that compiles integer neural networks into ZDoom maps and runs them in ViZDoom. The arithmetic is done by voodoo dolls, the copies of the player that DOOM spawns for extra player-1 starts. Sector light levels and plane heights hold the sums. Python then reads every exposed value back from the running game and compares it with a NumPy integer reference at tolerance 0, so a single value off by one fails the run.
The README is upfront about speed. The approach is about 23,000 times slower than NumPy on a small MLP when measured as engine time, and 65,000 times slower with a timer around the whole call. A handwritten digit takes 163 seconds of real-time DOOM in one map at the default schedule (--opt 0) and 8 seconds at --opt 3.
The main demo is an MNIST digit reader. The marine holds FIRE and inks a 3x3 brush of floor cells under him; READ makes the level write the 784 pixels into the network, and 1,355 voodoo dolls in the same map compute it. The network runs 784 pixels to 64 hidden units, then 45 pairwise score comparisons, then 10 win counts. Only the digit that wins all nine of its comparisons gets a doll through, and the line that doll crosses starts the level script that lights the digit on a 7-segment display. With --digit, Python stands in for the human: it presses the marine's keys and USE once, then only advances the clock and reads. In the example run, DOOM reads a 7 correctly, and all 248 values it computed equal the NumPy integer reference. The draw command builds its map with --opt 3, so a READ takes 273 game tics (8 s of real-time DOOM at 35 tics/s, 3 s wall); at --opt 0 it takes 5,705 tics.
On accuracy, the README separates exactness from correctness. On the first 100 MNIST test digits DOOM reads 99 correctly, and 966 of 1,000 sampled from all 10,000. The integer network scores 97.16 % on all 10,000, against 97.58 % for the float model. Thirty scripted walked digits (0 to 9, three stroke variants each) all read correctly and exactly at --opt 3, and the network is also trained on that stroke distribution: the first version, a 784-32-10 network trained on MNIST alone, read only 41.08 % of such strokes. Through real X key events a drawn 7 read exactly at the default --opt 3.
A second demo is a museum map. Running doomformer museum build writes museum.wad, which can be played without Python. You step on seven floor pads to light a digit and press READ. 136 voodoo dolls compute a small hand-written network in which each digit's score counts the segments that agree with it. MUTATE rewires one connection inside the running level: the weight from segment a to digit 7 goes from +1 to -1, by changing the specials of its two weight lines. The same 7 then reads as 1, and RESTORE puts it back. The museum verify command shows all three states as EXACT, each over 148 values. The author has run the whole museum map only in ViZDoom. Its self-test map logs exact values on a GitHub runner in GZDoom 4.14.2, LZDoom 3.88b, UZDoom 5.0.3 and ZDoom 2.8.1, and in Zandronum 3.2.1 only when every sector tag is below 32,768.
The README spends most of its length on verification. Tests tamper with the setup: raising one weight line in a small compiled map from 5 to 6 must fail by exactly +1 on exactly the inputs whose dolls cross that line, and a readout that echoes a value Python wrote must fail. Readback is guarded by default, so dropping a page cannot silently reuse an earlier answer. A fuzz of 40 random nets run in DOOM gave 306 passes and 0 mismatches. The fuzz --edge campaign put weights, biases and inputs at range limits across 15,000 nets, making 113,605 passes and comparing 1,987,632 values with no mismatch. The fuzzer and independent runs found four faults in the repo's own maps: two scheduling bugs and two plane limits, which check and compile now refuse. A static extractor reads the net back out of a compiled map without starting DOOM; of 23,622 single edits to a small map at --opt 0 it detects 17,906, and the 98 undetected edits that were run in DOOM on 12 inputs were all exact. A mutation harness killed 297 of the 1,004 sites it ran (out of 5,578). The README calls the method translation validation: a test of each compile, not a proof that the compiler is right.
The division of labour is spelled out. The dolls do every multiply-add, activation and comparison. ACS scripts address cells, move input gates, rewind the dolls, sense the world and copy results out; none of the network's arithmetic runs in ACS, except the toolkit's df.Agent, which takes its argmax there. At --opt 3 an ACS script also copies each layer's light levels into the next layer's gate heights, so the phrase "no ACS between layers" holds only at --opt 0 to 2. The README also lists other modes, including text demos that sample letters from DOOM-computed logits, a spiking-network fly loop and a doomformer machine in which dolls run a program counter and registers.
Key facts
- Doomformer compiles integer neural networks into ZDoom maps and runs them in ViZDoom; voodoo dolls do the arithmetic, light levels and plane heights hold the sums.
- Python checks every exposed value against a NumPy integer reference at tolerance 0; the MNIST demo matched on all 248 values, using 1,355 voodoo dolls.
- It is slow: about 23,000 times slower than NumPy on a small MLP (engine time), and 163 seconds of real-time DOOM per digit at --opt 0 versus 8 seconds at --opt 3.
- DOOM reads 99 of the first 100 MNIST test digits correctly and 966 of 1,000 sampled from all 10,000; the integer network scores 97.16 % on the full test set.
- A museum map with 136 voodoo dolls lets you rewire one weight inside the running level, so the same 7 reads as 1 until you restore it.
Why it matters
This is a stunt with a serious method behind it. It shows an integer neural network running on nothing but the DOOM engine's own mechanics: voodoo dolls crossing lines, sector lights and plane heights holding values. The README's emphasis is not on usefulness but on exactness. Every exposed value is compared against a NumPy integer reference with zero tolerance, and the project documents how it tries to break its own checks.
Who it affects
It is aimed at people who like DOOM modding and engine tricks, and at readers curious about how far a game engine can be pushed as a computer. The museum map is playable without Python, so it also reaches people who just want to see a level read a digit. The README does not claim any use for real inference, and it calls the approach slow itself.
How to use it
Install with pip install git+https://github.com/mottopanikeiku/doomformer; it needs Python 3.11+ on Linux or macOS. doomformer hello runs a small MLP with every value checked in about 2 seconds. doomformer draw --digit 7 takes about 15 seconds: the marine walks a 7 and the level reads it. Without --digit, a window opens and you walk and draw yourself. doomformer doctor checks ViZDoom, Freedoom and the display, builds ACC if it is absent and compiles an ACS probe. The first build needs git, cmake, a C compiler and network access to github.com. A Colab notebook needs no setup, but you draw on a browser canvas and doomformer mnist --image feeds it to DOOM, with no marine. doomformer museum build writes museum.wad, and doomformer museum verify reproduces the MUTATE and RESTORE runs.
How solid is it
The code is public, and the README reports its own evidence in detail: 306 passes with 0 mismatches on 40 random nets, 113,605 passes with no mismatch in the edge campaign, 0 false negatives for the static schedule checker against DOOM on 5,397 maps, and four faults the fuzzer and independent runs found in the repo's own maps, since fixed by refusing the offending plane settings. These are the README's own figures; the numbers come from the project, not from an outside review.
Risks and caveats
Exact is not the same as right: the README says EXACT means DOOM equals the integer network and says nothing about whether the digit is correct. The check is integer equality over the runs that were made and says nothing about inputs that were not run. The method is translation validation, a test of each compile and not a proof that the compiler is right. The mutation harness killed 297 of the 1,004 sites run; of the 707 survivors, 590 match a rule and 117 are input gaps, and none is proven equivalent. The author has run the whole museum map only in ViZDoom. Speed is the other limit: 163 seconds of in-game time per digit at the default schedule.
“EXACT means DOOM equals the integer network. It says nothing about whether the digit is right.”
— Doomformer README