DOOMformer: Run an Integer Neural Network in DOOM
See how DOOMformer compiles an integer neural network into a DOOM map, checks every result against NumPy, and exposes the trade-offs of exact in-game computing.

DOOMformer: Run an Integer Neural Network in DOOM
How DOOMformer Turns a DOOM Map Into a Neural Network
Running a neural network inside DOOM sounds like a gimmick. DOOMformer makes it concrete: it compiles an integer neural network into a ZDoom map, runs the map in ViZDoom, then reads the game state back and compares it to a NumPy integer reference. Every checked value has to match exactly - no wiggle room. The tolerance is zero.
That precision does come with a price. On the project's small multilayer perceptron, the game-engine computation runs about 23,000× slower than NumPy. For a handwritten digit, the MNIST demo takes 163 seconds of in-game time with the default schedule, or about eight seconds with the project's more optimized setting. None of this is meant as a replacement for normal machine-learning runtimes. It's a demonstration of compilation, game mechanics, and exact validation in an environment that wasn't built for this in the first place.
The interesting question isn't just whether DOOM can calculate - it's how the map represents values, how the project checks those values, and what the performance numbers actually mean in practice. This guide walks through the mechanism, the MNIST example, how to try it, and where the project's "exact" claim stops.
What is DOOMformer?
DOOMformer is a Python project that compiles integer neural networks into ZDoom maps and runs them in ViZDoom. Instead of executing the network with normal Python or NumPy operations, it leans on things the game engine already exposes. Python prepares the input, launches or drives the game, reads the values the map makes available, and compares them to an integer reference implementation.
A key idea is to treat game objects and map properties as computational resources. Voodoo dolls - extra copies of the player created by DOOM for additional player-one starts - are used in the arithmetic. Sector light levels and floor/ceiling plane heights carry numbers. In the project's visual explanation, activations show up like stair steps, and attention is represented via a ceiling. The map isn't just a fancy display for a separate model; the game state is part of the computation.
Why use DOOM as a computing environment?
DOOM is a good testbed for this kind of experiment because its engine exposes a programmable world: actors, sectors, geometry, and scripts. That was designed for gameplay, not numerical computing - but DOOMformer repurposes it as a constrained machine. Values have to be encoded in whatever the engine can represent, and the "operations" have to be expressed through map behavior.
That constraint is the point. The project turns a theoretical question into something you can actually run: can a familiar game environment be used as a compiler target, and can you validate the output rather than just watching it? The result ends up being part compiler experiment, part game-mechanics demo, and part verification exercise.
What it does not claim
DOOMformer's "exactness" claim is narrower than "the model is correct." It means the values read from the running game match the values produced by the corresponding NumPy integer reference. It does not mean the network always predicts the right class, that it generalizes well, or that DOOM is a sensible platform for real inference.
So it's possible to get an "exact match" while still being wrong on the final digit. The project's main value is showing how to compile and translate-validate a computation. It's not trying to be a competitive image-classification runtime.
How does a DOOM map perform neural network arithmetic?
This starts with a representation problem: game engines don't hand you nice arrays of neural-network values. DOOMformer maps numbers onto the game's state. According to the project, you can represent a number using a crowd of voodoo dolls, while light levels and plane heights carry sums. The geometry and actors then provide a way to route and transform that information inside the computation.
If you want a mental model, it's easier to think in terms of a pipeline than as "a bunch of clever tricks." The input gets translated into the map's representation. The compiled game logic performs the network's operations. Then Python reads back the exposed state and checks it. Each stage has to agree on what a "value" means; if the compiler encodes something one way and the reader interprets it another, the validation step should catch the mismatch.
From pixels to a decision
The MNIST demo starts with a 28-by-28 handwritten digit image: 784 pixel values. The network processes those pixels through 64 hidden units, then runs 45 pairwise score comparisons and produces 10 win counts - one per digit class.
Those comparisons form a tournament-style decision process. The digit that wins through all nine of its comparisons is the predicted class. A doll crossing a particular line triggers the level script that lights the corresponding digit on a seven-segment display. In other words, the game isn't only calculating hidden values - it also turns the final result into something a person can see in the level.
The map as both machine and interface
This environment has two roles. First, it provides the state the compiled computation uses. Second, it gives you a way to inspect the result visually. In the drawing demo, you can have a marine mark a digit on a floor canvas. Hit READ and the 784 pixels get pushed into the network; the level shows the predicted digit. With --digit, Python supplies the input and advances the simulation without you drawing anything.
For the digit computation, the project reports 1,355 voodoo dolls in the same map. That scale helps explain why this is deliberately bounded: it's not a compact "real neural net" implementation hidden inside DOOM. It's a specific mapping of an integer model onto game mechanics, designed so intermediate work can be exposed and checked.
How does DOOMformer prove its results are exact?
A screenshot-level result isn't enough. The display could be hard-coded, produced by some unrelated script, or computed incorrectly in a way that still looks plausible. DOOMformer's approach is translation validation: it reads values from the running game and compares them to a NumPy integer reference.
For MNIST, it checks the input pixels, hidden-layer values, pairwise comparisons, and the final answer. The project reports that the run checks all 248 values. The README describes 119 values read alongside their negated twins, plus the 10 outputs. If even one exposed value differs by one, the run fails. The stated tolerance is zero.
What zero tolerance establishes
Zero tolerance matters because it makes the acceptance rule explicit. This isn't "looks close enough" or "within a small error." For the values that are actually included in the check, DOOM has to match the integer reference exactly.
And it's still worth being clear about the boundary. The comparison validates the exposed values for the computation being tested. It doesn't prove that every possible map, input, compiler transformation, or network configuration is correct. Validation is only as broad as what the project chooses to read and compare.
Exact arithmetic is not the same as accurate classification
DOOMformer draws a useful distinction: EXACT means the game matches the integer network. It doesn't say whether the digit is right. If the reference model misclassifies an ambiguous image, a perfect match just confirms that DOOM reproduced that mistake faithfully.
That separates two questions that often get mixed together. First: did the compiled target preserve the intended computation? Second: is that computation a good model for the task? DOOMformer's checks address the first question. Model quality still needs the usual evaluation against labeled data and an accuracy metric.
Key takeaways
- A matching display is not proof; checking intermediate values is stronger.
- Zero tolerance applies to the values the project actually reads and tests.
- Exact execution doesn't guarantee correct prediction or a useful model.
- The reference implementation is part of the test: it defines what the game is expected to calculate.
How slow is it, and what do the performance numbers mean?
DOOMformer is dramatically slower than a conventional numerical library. The project reports that its small multilayer perceptron takes about 23,000× as long as NumPy when comparing engine time. With a timer around the whole call, the difference is about 65,000×. Those two figures aren't contradictory - they come from what's being timed. Game execution alone is not the same as "Python-to-game call including everything around it."
The digit demo makes the trade-off easier to picture. With the default schedule, a READ takes 5,705 game tics (about 163 seconds at 35 tics per second). With --opt 3, the reported run takes 273 tics - around eight seconds of in-game time. The README also reports roughly three seconds of wall-clock time for that run. These are different clocks, so it's best not to treat them as interchangeable.
What the optimization changes
The project's draw command builds its map with --opt 3, which is why you see the faster figure. The source calls the optimized setting a faster schedule, but the details aren't enough here to pin the speedup on a specific low-level transformation. The practical takeaway is simple: configuration changes how much simulated game time you need, and benchmarks should name the setting they used.
This is also a general warning for mixed systems. When your setup includes a simulator, a runtime, and a host-language wrapper, it matters what workload you ran, what configuration you used, what timing boundary you measured, and what unit you're reporting. A performance number can be "accurate" and still be misleading if it hides the context.
Why the slowness is part of the result
If the goal were serving predictions to users, the slowdown would kill the idea. But DOOMformer is testing something else: whether you can represent and verify a neural network in an environment that wasn't designed for this at all. The cost shows the gap between an expressive computational target and a purpose-built numerical runtime.
There's a real trade-off. DOOM makes the computation visible and provides a memorable test bed - but you pay with simulation time, map complexity, and a big performance penalty. That's fine for an experiment whose output is insight and repeatable validation. It's a bad fit when latency, throughput, energy use, or operating cost matters.
How can you run DOOMformer, and what is it useful for?
The project supports Python 3.11 or later on Linux and macOS. Installation uses pip with the Git repository as the source. The first build needs Git, CMake, a C compiler, and network access to GitHub. The doctor command checks ViZDoom, Freedoom, and display availability, builds ACC if needed, and compiles an ACS probe before reporting success.
That diagnostic step is more than housekeeping. A package can "install" even while native tools, game assets, or display configuration are missing. Checking dependencies early helps you separate an installation problem from a problem that shows up later during the actual network run.
Try a small computation first
The README gives two quick starting points: doomformer hello runs a small MLP with every value checked, and doomformer draw --digit 7 runs the digit demo where the marine draws a 7 automatically. The first is the better smoke test because it keeps the workload small. The second is the better demo because it shows the input-to-display path.
On Linux, Fedora users get an example package command covering Git, CMake, GCC, G++, and Make. There's also a Colab notebook that avoids local installation. In that notebook, a browser canvas supplies the drawing, and the image is passed to DOOM without you manually running a marine-controlled drawing session. As with most hosted notebooks, you trade local control for convenience.
Where the project is useful
DOOMformer makes sense if you're studying unusual compiler targets, game-engine scripting, integer computation, or translation validation. It gives you a concrete way to compare a reference implementation with a compiled execution target. The visual feedback also makes an abstract systems idea easier to explain and test.
It's not a deployment pattern for image classification, embedded inference, or any service that cares about real-world latency. For those, a standard runtime is faster, easier to maintain, and generally better supported. The key question isn't "can DOOM do it?" - it can. The question is whether using DOOM answers a technical question worth the operational overhead.
Conclusion
DOOMformer turns a familiar game into an unusual compiler target: voodoo dolls participate in arithmetic, map properties encode values, and Python checks the resulting computation against NumPy with zero tolerance. Its biggest contribution isn't speed or accuracy of classification. It's the explicit split between what a computation is supposed to produce and what a different execution environment actually produces.
It's worth looking at if you care about compilers, verification, or the limits of unconventional computing platforms. Keep the performance numbers grounded, keep the "exact" claim within what's actually tested, and use conventional runtimes when you want production inference. The takeaway question is simple: what can this strange target help you inspect that a normal implementation would hide?