A 1988 cassette tape, taken apart one instruction at a time. All 40,449 bytes of the game are accounted for.
Only 13% being code doesn't mean anything is missing: this game is 87% data. The graphics, the animation tables and twenty-nine screen maps of 512 bytes each.
Finish the game after cheating and Topo won't let you enjoy the ending. The routine that draws the victory screen checks a flag and, if it's set, writes a reproach over it:
CASTIGO_TRAMPAS:
ld a,(08f1eh) ; bandera de tramposo
cp 000h
jp z,BUCLE_FINAL ; limpia -> final normal
ld hl,07f94h ; si no: "POR QUE NO PRUEBAS SIN POKES"
ld de,019e0h ; encima de la ultima linea del area de juego
ld bc,00020h
call 0005ch ; y a la pantalla
And it doesn't land in empty space: it overwrites exactly the line where the game invites you to play Alehop, the company's next title. The cheater gets the invitation withdrawn.
Our first guess was that Topo had left the trap «armed and waiting». The data says otherwise.
Start-up initialises fifteen variables in a row —8F09,
8F0A… through 8F1C and 8F1D— and skips the very
next one. 0x8F1E is the only variable the game reads and never initialises.
If it holds zero, that's because the tape loads filler into that area: leftover bytes from the animation tables, of which only 3 out of 160 are zero. One happens to land right there.
So this isn't a clever trap: it's a lucky bug. They wrote the check, forgot to clear the variable, and the luck of the filler saved them from their own game insulting every honest player.
The only published cheat for the game appeared in MSX Book II
(Brazil, 1988) and reads POKE &HB4CC,0. It does nothing: that address
already holds zero. The right one is &H84CC, where the
DEC A that takes your life lives. In the dot-matrix typeface of those
books, 8 and B are nearly the same shape.
| Lives before | Lives after | |
|---|---|---|
| Unpatched | 9 | 8 |
With 0x84CC = 0 | 9 | 9 |
Confirmed in the emulator by forcing the life-loss routine.
The underwater level doesn't use different tiles. On the MSX video chip, colour 0 isn't black: it's transparent, and a single backdrop register shows through. On entering level 4 the game writes a 12 into that register and the whole display changes at once.
ENTRA_NIVEL4:
call EMPIEZA_NIVEL
ld a,00ch ; color 12 = verde
ld (0f3ebh),a ; registro de color de fondo
call 00062h ; BIOS CHGCLR: aplicalo
ld a,001h
ld (08f11h),a ; y enciende el modo flotar
They re-skinned an entire level with one byte.
The chests and skulls that drop items don't work the way they look. The game keeps a list of coordinates per screen and checks whether your shot lands on one — without looking at what's drawn there.
Of the 30 spots in the whole game, only 17 sit on something visible. The other 13 are invisible, in mid-air or over scenery. The wings that let you fly come from just two of them, both over plain background: the wings tile never appears in any of the 29 maps.
And there's a trap: on screen 26, one of those hidden spots drops a lethal tile. Shooting at everything has a price.
The text stored in the game reads things like
PVES SERES HORRIBLES or MVSICA:GOMINOLAS, and yet it reads
correctly on screen. The reason is that the codes for U and V draw exactly the same
glyph: it makes no difference which one you type.
DA_VIDA:
ld a,(08f12h) ; A = vidas actuales
inc a ; una mas
cp 00ah ; ¿ha llegado a 10?
ret z ; SI -> se va... SIN GUARDAR
ld (08f12h),a ; NO -> guarda
The ret z sits before the ld that stores. With 9
lives, picking up another does nothing. The real cap is 9.
When you collect something, the game clears it from the map by searching
with CPIR from the start of the screen, not from where you are. It
finds the first one of that kind, whichever it is.
Screen 27 has six extra lives on display: whichever you take, the top-left one always disappears while the one you actually touched stays drawn.
The game sets the stack once, at start-up. But game over restarts with a jump that lands after that instruction, so the new game begins with the pointer wherever the previous one left it. Measured in the emulator over eight games in a row:
| Restart | Stack pointer |
|---|---|
| 1 | 0x8FFF |
| 3 | 0x8FFD |
| 5 | 0x8FF9 |
| 8 | 0x8FD7 |
It goes down and never comes back up. The game's variables start at 0x8F00, so after enough games without a reset the stack would run into them. In 1988, with seven minutes of loading per game, getting there was hard. Today it's trivial.
Each screen is a 512-byte block: 32 columns by 16 rows, one tile byte per cell, uncompressed. Its memory address is below each one.
The reward for clearing all 28 screens.
A disassembly is easy to get wrong. Read some graphics as if they were instructions and you get pages of invented code that looks perfectly real. It happened to us: an automatic detector flagged leftovers from another build as game code, a quick eyeball said they looked right, and it had to be undone once we found nothing ever calls them.
That's why the project leans on four automatic checks:
| Check | What it catches |
|---|---|
| Tests | 36 of them, including a set that verifies the docs aren't lying |
| Reproducibility | That the source rebuilds the original binary byte for byte |
| Trace sanity | That graphics weren't marked as code — the check above misses this |
| Byte budget | That not a single byte is left unaccounted for |
And on something more important: much of what's claimed here wasn't deduced by reading but by watching the game run. Using the openMSX emulator we set watchpoints on memory to see which code touched what, sampled the processor during play to know what actually executes, and captured the real display to check it against what the code says.
That's how the lives counter was identified, for instance: not by deduction, but by confirming that a single routine reads it and that this routine writes the result into the exact status-bar cell where the number appears.