DOOMFORMER: the marine fights a horde of demons under a hellish sky

Integer neural networks compiled into ZDoom maps. Voodoo dolls do the arithmetic, and every value is checked against NumPy with tolerance 0.

A number is a crowd of voodoo dolls on a conveyor belt. An activation is a staircase they climb. A weight is a line they cross, and each crossing changes a light level or a floor height. When the dolls stop, Python reads the values back and compares each one with a NumPy integer reference. One value off by one and the run fails.

Walk a digit, DOOM reads it

The marine holds FIRE to ink the floor, then presses READ. The level writes the 784 pixels into an MNIST network, and 1,355 voodoo dolls in the same map compute it. The winning digit's doll lights the display. Python checks all 248 values against NumPy.

30 of 30 scripted digits read right and exact. A READ takes 273 game tics, about 8 s. How it works · drawpad.mp4, 16 s, 1.5 MB

# arrows walk, hold Ctrl to draw, Space: READ
doomformer draw
# headless: the marine walks a 7
doomformer draw --digit 7

His brain is the level

The marine's brain is compiled into the level he fights in. The dots on the right are 1,345 voodoo dolls of him. A level script senses the nearest demon, the dolls pick an action, and the level turns him and fires. Python only advances the clock and reads. 50 of 50 decisions exact.

Time-lapse. The demons are frozen while he thinks. Details · brain_is_the_level_raw.mp4, 53 s, 22.6 MB

Commands

pip install git+https://github.com/mottopanikeiku/doomformer   # Python 3.11+, Linux or macOS

The first run builds ACC, the ACS compiler, so git, cmake and a C compiler must be installed.

CommandWhat DOOM doesTimeSkill level
doomformer helloa small MLP, 5 random inputs2 sI'm too young to die
doomformer draw --digit 7the marine walks a 7, the level reads it15 s
doomformer mnistreads 4 test digits, or your own image1.5 to 3 minHey, not too rough
doomformer demoa transformer writes a level name15 to 60 sHurt me plenty
doomformer demo --nightmarethe 2-layer model, 106 tile maps3.5 to 7 minNightmare!
doomformer ask "what does iddqd do"answers from 189 DOOM factsabout 11 minUltra-Violence

Times are from a 16-core arm64 Linux machine. DEMON CANYON, the name --nightmare writes, is a hand-picked sample, and the default demo seed is picked. ask answers 94.7 % of its training questions and 35.4 % of new wordings. Every command

How it works

  1. your networkPyTorch / NumPy
  2. integersquantised
  3. a DOOM maplanes, gates, lava lines, doors
  4. ViZDoom runs itvoodoo dolls walk
  5. read backlight levels and plane heights
  6. EXACTevery value equals the NumPy integer reference

DOOM turns every extra player start into a voodoo doll: a copy of the player that rides conveyors and triggers every line it crosses. An activation of 5 is five dolls. A staircase lets a doll past each step only if the value clears it, which gives ReLU, rounding and clamping in one shape.

The engine has sharp edges. A second floor move on one sector in the same tic is lost. A teleport cancels lines crossed earlier in the same tic. Things far from a BSP node land in the wrong room. The full list is in HOW_IT_WORKS.md.

Computers in DOOM maps exist already. counter.wad (2006) counts with voodoo dolls, conway.wad runs Game of Life, and Danny Spencer's calculator adds numbers. Doomformer adds a compiler for neural networks, a list of engine behaviours that break exactness, and the tolerance-0 check.

Bigger experiments

Each run is exact against its reference. Scope and picked seeds are in DEMOS.md.

DOOM dreams of DOOM

A 2-layer name model writes DEMON CANYON, a hand-picked sample. A Python generator builds the level, and a marine whose policy runs on 55 games plays it. Halfway through, 3 of his neurons close. 120 decisions, all exact.

trilogy.mp4, 48 s, 11.5 MB

DOOM plays DOOM

A 1-layer policy walks the generated level: doors, two kills, exit. Python supplies the compass and enemy positions. 102 decisions, all exact. Seed 118 of 120, picked.

doom_beats_doom.mp4, 37 s, 10.4 MB

DOOM learns

A 38-weight perceptron inside one map. Its weights are ceiling heights, and 156 dolls raise and lower them from an aimbot's labels. Python bins the features and gives the labels. The film is an older run.

one_game.mp4, 44 s, 19.3 MB

Lobotomy

One 7-pellet blast to his DOOM brain makes him a pacifist: 0 kills over 74 decisions, all exact. It is a pick from 3,000 random blasts.

lobotomy.mp4, 29 s, 4.7 MB

The big one

A 4-layer transformer writes UNHOLY STRONGHOLD over 10,166 tile runs and 208,598 voodoo dolls. The runs share a pool of games.

full_brain.mp4, 28 s, 2.6 MB

A fruit-fly circuit

255 neurons derived from the FlyWire connectome drive the marine through four stations: eat, refuse, groom, take off. The dynamics are simplified and the input spikes synthetic. 169 of 169 steps exact.

FLY.md · fly_alive.mp4, 71 s, 5.8 MB

A whole transformer in one map

Embedding, attention, MLP and logits with no Python between layers. Each dot is one of 3,548 voodoo dolls writing SHIL. A small model: 1 layer, d=8.

Time-lapse. onemap_shil_raw.mp4, 58 s, 6.5 MB

Generation without Python

The winner's doll writes the next token and the level rewinds itself. Python only advances the clock and reads. It wrote DAN. ViZDoom draws at most 4,096 objects, so some dolls are not shown (engine limit).

selfgen.mp4, 56 s, 6.9 MB

The showreel

All of it in one minute: voodoo dolls on lava, attention gates, the DOOM brain naming a level, the marine playing it, learning, lobotomy, a transformer in one map and his brain inside the level.

showreel.mp4, 56 s, 25.1 MB

Run it yourself

From a clone, after pip install -e .. demos/README.md has times and sample output.

Build your own

Compile a network into a map and let its outputs open doors, flash lights or shove the marine.

import numpy as np
import doomformer as df

# 2 inputs -> [a AND b, a OR b] -> 2 outputs, all integers
net = df.MLP(
    weights=[np.array([[1, 1], [1, 1]]), np.array([[1, 0], [0, 1]])],
    biases=[np.array([-1, 0]), np.array([0, 0])],
    thresholds=[np.array([1]), np.array([1])],
    signed_input=False,
)
with df.compile(net) as brain:
    for x in ([0, 0], [1, 0], [1, 1]):
        # computed by voodoo dolls; raises df.MismatchError on any difference from net.reference
        print(x, "->", brain(np.array(x)).tolist())
    brain.record([np.array([0, 1]), np.array([1, 1])], "dolls.gif")   # film the dolls doing it

The guide is BUILD_YOUR_OWN.md, and there are twelve small runnable examples.

Limits