How the simulation works
These rules are read from the disassembly of the VGA executable, not inferred from play, and each address is the instruction a statement came from.
The C# rebuild implements them with the same citations. The last part lists what is still open.
The model
All of this comes from the dispatch loop at 0x140DE and the loader at
0x108C0.
- A runtime cell is two bytes: a value byte and a facing or sub-state byte.
- Horizontal stride 2, vertical stride
0x100, so a row holds 128 cells: the leftmost 40 are the cave, and a parallel plane sits at+0x80. - The scan runs top to bottom, left to right, 22 rows of 40.
- Values below 4 are inert and skip dispatch. Every other value calls a handler
from the table at
0x13C50, indexed by2 * value. - A step waits for at least 6 ticks (
0x1334F) of an interrupt that runs at 60 Hz, from PIT divisor0x4DAE, so the simulation runs at 10 Hz.
Every "random" behaviour is drawn inside a step, so a frequency quoted per second means little without the step rate.
The eleven rules that were wrong
The first engine here was written from the cave data and the game's help text. An independent audit on 15 September 2026 found that all eleven of its asserted rules diverged from the executable.
The worst was the simplest. Authored 0x20 is the player: the loader
at 0x1093A gives it a two-cell sprite and stores its position in two
globals. Authored 0x74 becomes the quota-gated exit at
0x14316. The first engine had them swapped, so Rockford began
every cave standing on the exit.
The rest were subtler, and taught more:
- Facing was taken to be the low two bits of every authored value. The loader translates each value explicitly, and facing lives in the second runtime byte.
- Enemies were given three movement attempts. The original makes two and then turns on the spot.
- Fire was allowed to push in four directions. The original pushes only horizontally.
A rule set wrong in eleven places describes a different game, so that engine was retired rather than patched.
The lesson that generalises. Every wrong rule came from reasoning about what the level design implied, and every correction from reading the code. Inference from data quickly gives an answer that hangs together, which is no evidence that it is right.
The dispatch table
Most of what follows came from reading the whole table rather than sampling it. The version here is abridged.
| Runtime | Handler | What it is |
|---|---|---|
00-03 | none | Inert: space, dirt, steel, ordinary wall |
07 | 0x14316 | Closed exit, opens on the quota |
08-0B | 0x14445 | Activatable downward-producing entity: the acid tap |
0C-0F | 0x140AE | Activatable upward-producing entity: the fire tap |
11-14, 1B-1E | 0x1467C, then 0x14126 or 0x14130 | Explosion phases, to treasure and to space |
15, 16 | 0x14632, 0x14677 | Expanding wall, and its deferred form |
30-33 | 0x14301 | Open exit |
40-43 | 0x1414A, 0x14223, 0x142F7 | Treasure: resting, falling, deferred |
44-47 | 0x13EF7, 0x13FD0, 0x140A4 | Boulder: resting, falling, deferred |
48-4B | 0x13E50 | Amoeba |
74-77 | 0x1467F | The timed converting wall |
78-7B | 0x13ED8 | The first worm's egg |
7C-7F | 0x14613 | The second worm's egg |
8C | 0x11B54 | Enemy family, clockwise |
90 | 0x11B91 | Enemy family, anticlockwise |
A0-A7 | 0x13E78 | The first worm's body |
B0-B7 | 0x145B3 | The second worm's body |
F0-F4 | five, below | Deferred seeds |
State chains, and why the game has projectiles
The handler at 0x1467C is a bare inc on the cell: a value
in one of its runs advances a step per scan visit until a terminal handler replaces
it. That is the whole animation system, and how things transform.
| Chain | Terminal | Becomes |
|---|---|---|
11 12 13 | 14 at 0x14126 | Treasure |
17 18 19 | 1A at 0x1412B | Treasure |
1B 1C 1D | 1E at 0x14130 | Space |
1F 20 21 22 | 23 at 0x14322 | Falling acid: falls on, or blows up a creature below it |
28 29 2A | 2B at 0x1434E | Settled acid: moves itself down one cell as 1F |
2C 2D 2E | 2F at 0x14473 | Fire: moves itself up one cell as 2C |
34 35 36 | 37 at 0x14135 | Boulder |
38 39 3A | 3B at 0x1413A | Calls 0x1356C, which writes 90, the anticlockwise enemy |
3C 3D 3E | 3F at 0x14140 | Boulder |
6C 6D 6E | 6F at 0x14441 | Back to 6C: an endless four-frame loop |
70 71 72 | 73 at 0x14145 | Back to 70: another endless loop |
The two loops are the bonus collectibles: authored 78, the treasure
bonus, loads as 6C, and authored 7C, the time bonus, as
70.
The acid and fire rows are projectiles, which the first engine lacked. Acid falls a cell at a time and fire rises, each re-entering its chain after the move. Both come from taps, the acid tap and the fire tap (the prior art's magic lamp), and an armed tap emits one every eight steps.
Fire and acid destroy each other: 0x1448A tests the cell above the fire
against 20-2B, the acid's whole state range, and throws a
blast. Fire striking treasure turns it into a creature: 0x1355E writes
8C, the clockwise family, which yields treasure when killed.
Five values that exist only because of the scan order
This is the most satisfying thing in the dispatch table. The handlers for
F0 to F4 do exactly one thing: replace the value with
another. They make no motion, sound or side effect, and play gives no hint of why
they are there.
| Seed | Handler | Becomes |
|---|---|---|
F0 | 0x1443D | 28, acid settled |
F1 | 0x145AF | 2C, fire head |
F2 | 0x146D0 | 25, acid blocked twice |
F3 | 0x13ED4 | A7, the first worm's oldest segment |
F4 | 0x1460F | B7, the second worm's oldest segment |
The scan order imposes a rule on every handler in the game: a handler writing into a cell the scan has not reached yet must write something inert. Fire moving left writes the live value, because the scan has already passed that cell this step. Fire moving right writes the seed, because a live value there would advance again in the same step and the projectile would lose a frame.
The effect is visible from outside, which matters for tests: a projectile created downwards or rightwards is one phase further on by the end of the step that created it, because the scan catches up with it.
The ranged action
The help text says fire is used to "pick up treasures, push things over, and blow
up dirt". In the code it acts on the adjacent cell in the facing direction without
moving the player, and pushes only horizontally. Firing at a tap
arms it. Walking into a worm's egg or firing at it sets that worm's counter to
0x4B0, 1200 ticks, so it hatches on the next scan; left alone, an egg
hatches after twenty seconds anyway.
The worms
This page once listed B0-B7 as an unsettled "segmented
moving entity"; it is the second worm's body. Both worms were read in full and
ported on 17 September 2026, and the prior art's account of them proved right in
every particular.
A routine, not a cell
The scan never moves a worm. The main loop calls a routine for each one once a
step, at 0xEDBC and 0xEDBF, ahead of the scan, and the
cells the scan sees are the trail already laid. 0x1194B and
0x11A68 are the same routine twice. Each keeps its own direction,
length, head cell, tail cell and two flags in globals, and both use the enemies'
delta table at ds:0x1656.
Hatching
Each worm starts as an egg. Runtime 78-7B watches
ds:0x16AA and becomes A0; runtime
7C-7F watches ds:0x16AC and becomes
B0. Hatching, at 0x13EE4, sets the length to one. The
loader at 0x109FB seeded the head much earlier with the egg's
cell.
The body
A0 is the newest segment and A7 the oldest, and
B0 to B7 work the same way. The handler at
0x13E78 ages every segment by one and erases any that pass
A7, except the tail pointer's cell. That one stays pinned at
A7 until the movement routine erases it as the ninth, which is why a
worm's drawn length equals its counted one.
A worm boxed in with a body behind it reverses (0x119F4): head and
tail swap, and it retraces the cells it laid. Boxed in with no body, it freezes
(0x13EB1), which stops a one-cell worm ageing itself out of
existence.
What they change
The two worms are opposites: one spoils a cave and the other mends it. Neither writes the thing it is making. Each writes the first value of one of the chains above, so the change takes four more scans and can be seen happening.
| Worm | Treasure | Boulder | Enemy 90 |
|---|---|---|---|
Evil, authored 70, body A0-A7 | 3C, then boulder | 38, then enemy 90, one time in eight | - |
Good, authored 84, body B0-B7 | - | 17, then treasure, every time | 34, then boulder |
Anything in a worm's way costs one number from the shared random generator, to choose which way to turn, and one more if both sides are blocked; clear space costs none. The evil worm also rolls its one in eight for every boulder it meets. The good worm converts boulders without that roll.
The two headings, ds:0x1610 and ds:0x1618, are not
cleared between caves, so a worm hatches facing wherever the last worm faced,
possibly several caves earlier. The rebuild carries them across too.
When a cave ends
The end of a cave is decided outside the scan, and this part was read from the EGA executable. The game plays one of three cartoons:
- WIN: Rockford entered the open exit with the quota met, and the cave was not ended by a key.
- LOSE: he died with time on the clock.
- TIME: he died with the clock at zero or below.
Running out of time kills him as a hazard does, so only the clock tells LOSE from TIME. Each frame shows for 9 ticks (150 ms), and a cartoon runs for 1 to 6 seconds.
The VGA executable sets the same flags in the same places, but its cartoon routine has not been read. The formats page has the timings and where the rebuild differs. The rebuild's outcome screen makes the same split, with "cave clear" for a completed cave and "out of time" for a death on the clock.
What is not settled
- Effective random frequencies during play, because input and sound draw from the same generator.
- Behaviour under interrupt delay, pause, and 16-bit timer wraparound.
- Whether the EGA and VGA releases simulate identically. They are known to differ outside the simulation: the VGA debug keyboard has nine keys and a cheat flag, the EGA one three keys and no cheat.
Run against the original, but not in play
All of it matches the disassembly as read. The cave loader and the main loop's step have since been run beside the original's own code in an interpreter, fifty passes a cave in all forty caves, with nobody at the controls. The loader agrees on every cell; the step agrees for most caves, and the small residual is traced to state the harness cannot set up (details). Real play, with input, has not been compared, so a misreading that only input reaches would not have been caught.
A second check would be cicodis, a static
recompiler someone has already put the game through. It was examined on 20
September 2026 but not built or run. It translates the EGA executable, byte for
byte bar twelve bytes of inert stack padding, while every address here is VGA, and
the two releases were built separately, so they cannot be lined up by address.
Its translation is also wrong in at least one place: its patched treasure routine
zeroes the quota on the first gem, where the original's JNS at
0x1FAF3 clamps only on a negative.
A cicodis build that dumps its 40 by 22 grid once a step could still be compared
with this engine cell by cell, from the same recorded input, with no need to agree
on memory layout. One difference is known in advance: the rebuild seeds every
cave's generator with 0x04D2, where the original carries one stream
from cave to cave.