The cartridge's attract mode plays a hand of demo mahjong and starts again. It never explains anything. This patch hooks twenty screens onto the end of that loop that teach you to play — with all thirty-four tiles drawn. And you do not have to wait for the demo: key 4 on the title screen starts them at once.
Work in progress: see What is missing.
State 14 of the cartridge — the end-of-game label — finishes with a single jp to the routine that returns to state 0. That jump, and only that one, is the hook. Everything else comes for free:
tools/omsx_corta_el_attract.tcl: key 1 on slide 2 reaches state 8 in 0.08 seconds, and the title comes up clean.What is not possible is adding a sixteenth state. The dispatch table at 0x40ED is glued to the state 0 code, and the dispatcher reads it off its own return address with a pop hl. There is no slot to add.
The title menu has a fourth entry, 4-key TUTORIAL. The three entries above it move up one row to make room (rows 17, 19 and 21, PLAY SELECT to 15), and the label block, 119 bytes where its hole was 103, is relocated into the free area.
The cartridge's keyboard door (0x4ABC) already read keys 1 to 4: it folds them into an index and looks it up in the difficulty table, where key 4 landed on an empty entry and did nothing. The patch swaps the call after that fold for one of the same size to a routine in the free area: for keys 1 to 3 it carries on exactly as before; for key 4 it drops the return into the door and enters the tutorial. Entering from the title means loading what the tutorial takes for granted after the END label and the title does not have — the scoring screen's alphabet in slots 0x30-0x7F of the first third, and its colour, white on black. Everything else, the tiles, the honours, the letter A, the tutorial loads itself. Then it sets state 14 with the countdown at one and the cartridge's own clock does the rest. Pressing 4 while the tutorial is already running does nothing.
Measured in openMSX: state 14 is reached 0.035 seconds after the key, the twenty slides are pixel for pixel the ones you get after the demo, and the title that follows is identical to the one before.
| screen | what it teaches | |
|---|---|---|
| 1 | HOW TO PLAY | two players, you and the computer |
| 2 | THE TILES | the three suits, one to nine — twenty-seven tiles drawn |
| 3 | WINDS AND DRAGONS | the seven honours, each with its name beside it |
| 4 | YOUR HAND | thirteen tiles; draw one, throw one |
| 5 | THE TABLE | his hand face down, the discards, your hand |
| 6 | THE GOAL | four sets and one pair |
| 7 | A RUN | three in order, same suit — with the winds as counter-example |
| 8 | A TRIPLET | three the same; even a wind or a dragon |
| 9 | THE PAIR | with the white dragon, which is a blank tile |
| 10 | TURN BY TURN | the shape of a turn |
| 11 | CALLING | pon, chi and kan drawn: what you hold, what he throws |
| 12 | RIICHI | the thirteen-tile hand and the tile it waits for |
| 13 | TO WIN | tsumo and ron |
| 14 | THE MENU | what each of the five entries does and when to pick it |
| 15 | BE CAREFUL | the river that leaves you furiten |
| 16 | THE HAND ENDS | someone calls, or the throws run out |
| 17 | THE COUNT | both hands turned over and broken into sets |
| 18 | THE SCORE | the dora indicator and the next one |
| 19 | WHAT YOU JUST SAW | the hand the demo has just played |
| 20 | the keys | one, two or three to start |
Twenty screens at 6.4 seconds is a little over two minutes. The whole patch now uses 8,049 of the cartridge's 8,219 spare bytes: 170 are left, measured as the 0xFF still at the end of the ROM.
Every line comes from the cartridge's own rulebook — the one written for the disassembly, with each rule anchored to the routine that decides it. So the tutorial says two players and thirty thousand points, and the house's own furiten, because that is what this cartridge does.
Two examples of the difference that makes:
There are no digits loaded on that screen. Slots 0x10-0x19 are pieces of the END label, and loading a digit font would cost another block. So the tutorial writes its numbers as words — «THIRTY THOUSAND POINTS», «IT COSTS ONE THOUSAND» — and never quotes a figure.
Which turned out to be the right call anyway, for a reason that had to be measured. The plan had been for the last screen to comment on the hand the player had just watched, since the tutorial comes straight after the demo. The demo does not play the same hand on every lap. Dumping 0xE000-0xE3FF at the scoring screen of two consecutive laps of the same power-on (tools/omsx_repite_la_demo.tcl) gives 72 differing bytes of 1,024 — one lap's score 29,000, the next 30,000. (Two cold boots do give identical traces; that is a different thing, and it was the thing that had been measured before.) So the last screen comments on the flow, which is the state machine and is fixed, and quotes nothing.
The screen the tutorial runs on was missing sixteen of the thirty-four tiles: the seven honours, whose usual slots are exactly the ones the patch's alphabet takes, and circles 3 to 9, which have no drawing there at all. The first version named the winds and dragons without showing them, which for a tile game is not a tutorial.
The way out is that a tile's slot number is not sacred. Load the drawing into any free slot and ask for it by the new number. Counting the slots that screen never draws left two clear stretches:
| tiles | in the ROM | in the tutorial |
|---|---|---|
| honours | 0x2E-0x57 | 0x4E-0x77 |
| circles 3-9 | 0x58-0x81 | 0x02-0x2B |
| the tile back, displaced by the circles | 0x28-0x2D | 0x78-0x7D |
Three details, each of which cost a test:
tools/omsx_mide_la_carga.tcl; it was 1.24 until the tiles that were already there had to be reloaded too, see below), so the screen is cleared before the load. The first attempt did it the other way and showed the END label with the circles already written over its letters.Nothing needs restoring afterwards: the routine that ends the tutorial reloads the whole screen. Checked with a dump six seconds later — the Konami logo comes up clean.
The first 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, and that screen's block restores it from the ROM.
It cannot be fixed in that block, because the green is seen — for the 64 frames the wall of tiles takes to cover it. So the tutorial loads its own A into the first third and lets the cartridge's routine spread it. Reading the code would never have caught this one.
The twenty tiles the screen came with — characters, bamboos, circles 1 and 2 and the empty slot, slots 0x82-0xFF — were the opponent's set, the one the top third of the table shows rotated 180 degrees: kanji on top, number below, the shadow in the top-left corner and three black rows underneath. Next to the honours and circles 3-9, loaded upright from the player's block, they read as tiles cut off at the bottom. Measured, not eyeballed: the six slots of SOU1 and PIN1 in the tutorial's dump are, byte for byte, the 180-degree rotation of the ones in the bottom third's blocks at 0x9343 and 0x99F9, patterns and colours alike.
The fix costs 54 bytes of code and none of data: the tutorial loads those two blocks as they are from the ROM — they land in the third third, their own — and copies slots 0x82-0xFF into the first third before loading the rest and replicating. Recompressing twenty tiles into the free area would have cost over a thousand. That copy is why the load takes 1.66 seconds now instead of 1.24.
Straightening the tiles uncovered a second fault that had been there since August. The empty slot is slots 0xFA-0xFF, so its bottom row is FE FF — and those are the control codes of the list interpreter at 0x409F: 0xFE reads a new destination, 0xFF ends the list. The interpreter swallowed the next two bytes as an address, left the bottom row of every tile after a gap unpainted — in THE GOAL, the three bamboos and the three circles — and wrote the rest of the row into the pattern table of the third third. With the rotated set that row was almost black and nobody saw it; upright, it is the base of the tile.
The generator now paints the gap with the black slot and refuses 0xFE or 0xFF as data in any list, and a test shows the old generator produced exactly feff in that row.