Things that only turned up by doing this, most of which reading the code would never have caught.
Work in progress: see What is missing.
The build was green. The ROM came out byte for byte as asked, every check passed — and the scoring screen had a cyan band repeating «LT» running right across it, in row 8.
Slot 0x0DF looked free by the rule the whole allocation used: it is the middle row of a mahjong tile, and those are never drawn in the centre third. That is true. What is also true, and nobody had checked, is that the scoring screen uses it twenty-four times in row 8 as the top edge of a grid of boxes. There it is invisible:
original : pattern FF 00 00 00 00 00 00 00 colour 10
one hairline, black ink on a transparent ground
patched : pattern 00 8E 84 84 84 84 84 E4 colour 17
the LT digraph, black on CYAN
Put a letter in an invisible thing and the invisible thing appears.
The immediate fix was to move that digraph to slot 0x0FA, which no dump draws in that third — and which is safe for a reason that had to be read out of the binary, not assumed. The worry was the dora indicator: it is a random tile, so if it were copied into the third at its own slot numbers, any slot could be overwritten. It is not. pinta_los_dos_indicadores at 0x7024 reads the drawing from the third below and writes it to fixed destinations — ld de,02850h and ld de,028d0h, which are slots 0x0A-0x0F and 0x1A-0x1F. Whatever the dora is, it never reaches the allocation.
The real fix is make pantallas, described in How it works. The lesson is older than this patch and keeps being true: a check that only reads the bytes cannot see the screen.
When a hand ends with nobody winning, a sign comes up, and the cartridge clears the cells it is about to cover first. Those cells were chosen to cover exactly what the Japanese labels occupied. In English the labels sit elsewhere, so what survived the clearing was a «D» stuck to EAST 1, a stray «A» above the box, and a DORA dangling below.
With the right clearing the block grows from 39 bytes to 40 and no longer fits its hole, so it is relocated and its loader repointed.
Two things came out of this that are worth keeping. The first: before "fixing" a visual fault, measure where the drawing comes from. The second is the trap that follows a relocation — anything that still asks for the block at its old address writes rubbish. The emulator script that forced this screen was still pointing at 0x782C, which by then was 0xFF, and the format-B interpreter walked off through video memory. Scripts now read the address out of the ld hl itself.
The dora indicator came out as a plain white rectangle and looked exactly like a patch that had eaten a tile. It is the white dragon, which is a blank tile by definition.
And the reason the patch could not have broken it: pinta_los_dos_indicadores copies the six characters of that tile out of video memory in the third below, not out of the ROM, so patching the third above cannot touch it.
The first tutorial test came out with green squares where the As should be. Slot 0x030 on that screen is not the A: it is the green inside of the end-screen's box — eighteen of them per row when the block is decompressed — and that screen's own block restores it from the ROM.
It cannot be fixed there, because that green is seen, for the 64 frames the wall of tiles takes to cover it. The tutorial loads its own A instead.
Getting the missing tiles into the tutorial's screen takes 1.66 seconds, measured with tools/omsx_mide_la_carga.tcl (1.24 before the tiles that were already there had to be reloaded too, see below). Long enough that the order matters: clear the screen first, then load. The first attempt did it the other way and showed more than a second of the END label with circles written over its letters.
Loading into one third and letting the cartridge's own routine copy it to the other two costs 864 bytes instead of 2,592 — and that routine is safe to call from inside the interrupt, because that is where the cartridge calls it from.
Two cold boots give identical traces, instant for instant. Two consecutive laps of the same power-on do not: dumping 0xE000-0xE3FF at the scoring screen of both gives 72 differing bytes of 1,024 (tools/omsx_repite_la_demo.tcl) — one lap ends with a score of 29,000, the next 30,000, and the riichi stick counter reads 01 and 00.
That killed a good idea. The tutorial was going to comment on the hand the player had just watched. It comments on the flow instead, which is the state machine and is fixed, and quotes no figures at all.
The wall is shuffled from a seed whose first byte is the Z80's refresh register R (0x41C6) and whose second is the frame counter. R counts instruction fetches, so any change in the code that runs before the deal changes the hand the demo plays — not only a change to the game itself. Adding the fourth line to the title menu, sixteen more bytes for the list interpreter to write, was enough: the demo now plays a different hand from the one it played with the previous build, and the table and scoring captures on this site changed with it. Two cold boots of the same ROM still give identical traces.
The tutorial screen came with twenty tiles already drawn, and they were the set the top third of the table shows: the opponent's, rotated 180 degrees. Next to the honours loaded upright they looked cut off at the bottom. The six slots of SOU1 and PIN1 are, byte for byte, the rotation of the player's — the check that settled it. Loading the player's blocks as they are and copying 126 slots across VRAM cost 54 bytes of code and no data. Details in The tutorial.
The empty slot's bottom row is slots 0xFE and 0xFF, and those are the list interpreter's «new destination» and «end». Every tile after a gap lost its bottom row and five bytes went into the third third's pattern table — invisible for as long as the tiles were rotated. The generator now refuses those two values as data. Details in The tutorial.
The cartridge marks the difficulty with two or three characters stacked vertically — the initials of アマチュア, プロ and セミプロ. The patch writes them horizontally in red, and three characters is what there is: AMA, PRO, SEM. «SEM» on the table is not a truncation bug; it is the whole cell.
Slots 0x08, 0x09 and 0x0A of the block at 0x92E3 are byte-for-byte copies of 0x0C, 0x0D and 0x0E. That block is stored uncompressed, so putting letters in it costs nothing in size. It is what saves you when a compressed block overshoots its hole by a handful of bytes.
The word «TSUMO» on the scoring screen was three characters short of a three-byte hole, and it had been open across three sessions. It closed by accident: SHANPON drops from six cells to four in digraphs, which freed exactly the byte the tsumo needed. Moving its ld hl one position was the whole fix.