Writing it again, from the instructions
The first engine here was written from the cave data and the game's help text, and all eleven of its rules turned out to be wrong. The second was written from the disassembly, one handler at a time. This page sets out what "match" means for it, what it is made of, and how far to trust it.
Ported. The simulation is complete: every value in the original's handler table now dispatches, the worms and both bonuses included, and the on-screen display is done. The cave loader and the simulation step have been run against the original's own code, in an interpreter, with nobody at the controls. A recorded run of real play has not yet been compared.
What "match" is taken to mean
The target is behavioural equivalence with the original's simulation, not bit-exact 8086 emulation. That means:
- the same authored-to-runtime tile mapping
- the same scan order and the same intermediate states
- the same random generator, drawn the same number of times in the same places
- the same per-cave quotas, time limits and scores, read from the executable rather than typed in
Where behaviour depends on interrupt timing rather than simulation steps, the port models the interrupt counter instead of approximating it. The random generator matters most: the same sequence, drawn at the same moments, is what makes an amoeba grow the same way twice.
The shape of it
The source code is on GitHub at github.com/dbhq-uk/rockford-engine, under the MIT licence. None of the original's files are in it.
| Project | What it is |
|---|---|
Rockford.Formats | Readers for the cave file, the EGA sheets, the VGA containers, and the four metadata tables in the executable |
Rockford.Engine | The simulation, one file per behaviour: boulders, treasure, amoeba, entities, worms, player, projectiles, clock, sound; plus the viewport and the host that runs a game around one cave |
Rockford.Cli | A terminal front end, for watching the rules work without a browser, and a read-only inspector for recorded sessions |
Rockford.Web | The browser front end. Both builds are served at /play/ |
Rockford.Reference | An 8086 interpreter with enough DOS, video card and timer to run the original executable, so the port can be checked against the original's own code |
Rockford.Tests | The rules, asserted against the original's behaviour |
The engine is split by behaviour, not by layer, on purpose. A file holding every rule about falling is easy to check against the falling handler in the disassembly; a file holding "the simulation" is not.
What is done
- The random generator, ported from its own routine.
- Cave metadata: quota, time limit and both score tables, read from the binary.
- The runtime cell grid and the authored-to-runtime loader.
- Boulders and treasure, all eight states, with both kinds of blast.
- The amoeba, with the per-neighbour draw and the real outcomes.
- Explosions: two kinds, per-kind immunity, explicit phases.
- Enemies: two families, two movement attempts and then a stationary turn.
- Walls: expanding, the timed converter, and quicksand.
- The player: pre-movement adjacency death, the walking push with its random test, and fire, which pushes only sideways.
- The exit and scoring.
- The scrolling viewport, with its latches. The original shows a 20 by 11 window of a 40 by 22 cave, and the window does not centre on the player.
- The taps and both projectiles. Firing at a tap arms it, and an armed tap emits one projectile every eight steps. Fire and acid destroy each other.
- The two worms. The evil one turns treasure back into boulders and boulders into the enemy that explodes into nothing; the good one walks that enemy back to boulders and boulders on to treasure. Each worm is a routine the main loop calls once a step, ahead of the scan, with six globals of its own, and the cells the scan sees are the trail it has laid. The "segmented mover" in earlier notes is how the worms are built, not a third creature.
- The treasure bonus and the time bonus, both collectable.
- The on-screen display: lives, the world's own treasure name, the count, the score and the clock. The original prints four unlabelled numbers on one text row.
Every value in the handler table at 0x13C50 now dispatches: each
of the 45 routines it names is ported, or is the one bare ret they
share.
What is not done
Nothing in the handler table is left to port. What remains are questions of evidence that reading the code cannot settle:
- How often the random generator is drawn during real play, since input and sound draw from it too.
- How the game behaves when interrupts are late, when it is paused, and when the 16-bit timer wraps.
- Whether the EGA and VGA releases behave the same.
- A recorded run of real play, with input, compared step by step with this engine. The comparison so far runs with nobody at the controls; see the limit below.
The tests, and what they are for
On 12 October 2026, dotnet test ran 512 tests: 488 passed and 24
were skipped. Each rule test asserts a behaviour taken from a specific handler.
Their job is not to prove the code runs. When a reading of the disassembly turns
out wrong, as has happened repeatedly, the correction lands in one place and
everything that depended on it fails loudly.
Two more hold the engine to itself. A fingerprint of the whole state of scripted runs was captured before the browser was allowed to observe the engine, and it still matches; a recorded session replays to the same fingerprint at every step. That shows the engine repeats itself exactly and that observing it changed nothing, not that it matches the original.
Tests that need the original files skip cleanly without them, so anyone without the game can run the suite. CI builds with warnings as errors and runs the suite three times: once with no game data, to hold that rule, and once against each of the VGA and EGA releases, because the two keep the same tables at different addresses.
The honest limit
This engine matches the disassembly as read, and has been checked against the original only where an interpreter can run the original's own code beside it. The first engine was wrong in eleven places while passing its own tests, so that caveat is meant literally.
Part of the gap is closed. Rockford.Reference boots the original
executable in an interpreter, and the title screen it draws matches the
decoder's pixel for pixel. Run there, the original's cave loader agrees with the
port's on all forty caves and every authored byte value. Its main-loop step has
also been run beside the port's, fifty passes in each of the forty caves with
nobody at the controls, comparing all 2,816 cells and the random generator after
every pass. They agree for most caves. In a few the port makes one extra random
draw, for a landing's pitch past a gate at ds:0x1684 the harness
cannot set, and a handful of cells in a parallel plane read FFFE
where the port has FFFF. Both are harness limits, not shown to be
faults in the port. Real play, with input, has not been compared.
A second candidate reference turned up in the prior-art search. Gabriel Valky's cicodis has put this game through a static recompiler, and the executable it translates is the same program as the EGA image here, bar twelve bytes of stack padding. It has not been built or run here. Its addresses match this project's for data but not for code, because the port follows the VGA release and the two releases were compiled separately. Its translation is also wrong in at least one place: the patched treasure routine zeroes the quota on the first gem, where the original leaves 39. So it is something to compare against cave grid by cave grid, not an oracle. More on cicodis.