A 1987 cassette tape, taken apart line by line. All 34,239 bytes on it are accounted for — and the game turned out to be written in BASIC.
Those bytes of code are not what you would expect. Only 997 of them are machine code; the other 3,935 are a tokenised MSX-BASIC program — the game itself. And the 1,566 unidentified bytes are not a gap in the map: every byte on the tape has been assigned a role. Those are the ones whose contents we could not name, and it is proven that the game never displays them. They are wanted, dead or alive: what has been ruled out, with what measurement, and how to have a go.
The block that carries the game is not machine code: it is a tokenised MSX-BASIC program, 63 lines long, with just 45 bytes of Z80 at the end to start the interpreter. Everything that had to be fast is done by four routines called with USR — and the most important of them is seventeen bytes long.
So the usual guarantee does not apply here. Instead of reassembling, the commented listing is re-tokenised and must produce the tape's bytes exactly. It does.
The panel is drawn with six zeroes, and the routine that prints the score only writes four digits: columns 5 and 6 keep the zeroes they came with. Shooting a bandit adds 20 to the counter and the screen reads 2000.
Line 730 plays a sound and jumps back into the loop, and nothing in the program ever goes there: no GOTO, no GOSUB, no THEN, and it is not in the dispatch list of line 640 either. The line before it ends in a jump, so it cannot be fallen into. It is a leftover from an earlier version, still on the tape.
Losing a life returns to line 600, not 599 — and 599 is the one that reloads the forty bullets. They are per level, not per life. Run out and line 300 just returns without even making a sound: you cannot shoot anything any more, and the lives you have left go one after another while you watch.
On the tape the first line is numbered 65535, above the highest line number MSX-BASIC accepts, and the Z80 startup writes a 4 over it. Without that patch the program breaks: verified in the emulator, the first GOSUB aborts with Undefined line number in 520. And what that line does is POKE &HFBB1,1, which disables CTRL+STOP — so the running game cannot be halted, and therefore cannot be listed.
In the interpreter's variable area the tape carries eleven bytes with the exact shape of an MSX-BASIC variable entry: a double-precision I holding 37025. This program never uses one — it uses I%, an integer. Behind it are 285 bytes of uninitialised RAM which follow the power-on pattern of the machine.
That pattern gives away something else. It depends on the absolute address, and it fits 282 of the 285 (98.9%) at 0x8000 but only 226 (79.3%) at 0x83E8 — so on the programmer's machine the game already lived at 0x8000, and the tape's load address is only a detour to avoid overwriting the loader while it loads.
These aren't screen captures. They are drawn from the game's own data, following step by step what the program does to build the screen. Each level is a 32-column map, taller than the screen; the game shows a 16-row window of it and moves that window.
There is no turbo loading here: the tape is made of standard blocks, so the bytes come straight out of the tape file and no emulator is needed to decode them. What the emulator is used for is proof. The game's memory image is rebuilt by hand — one block relocates itself on startup — and then openMSX loads the real tape and its RAM is dumped at the exact instant the game starts. The two match, byte for byte.
From there, code is traced by following control flow from the known entry points, never by disassembling linearly: 18 KB of graphics decoded as instructions would throw everything out of alignment. Regions known to be data are declared off-limits, because one bad seed sends the tracer into the artwork and inflates coverage with nonsense.
Two separate checks guard the result. Reproducibility: every listing must give back the original binary — reassembled for the machine code, re-tokenised for the BASIC. And a budget: every byte must be either traced code or a data range named in the notes. The second exists because the first cannot see misinterpretation — if graphics get marked as code, the bytes still come out identical and only the listing lies.