The machinery: where the room came from, how the text is written, and why the texts are generated instead of typed.
Work in progress: see What is missing.
src/parche_en/parche.txt definitions you can read
| construye_parche_en.py
v
work/parche_en/*.asm the blocks, as assembly
| parchea.py spliced into a copy of the listing,
v assembled, compared with the original
work/mahjong_en.rom the translated ROM
| ips.py
v
parche/mahjong_en.ips the only thing distributed
The last step is checked, not assumed: ips.py check applies the patch to the original and demands the result be the ROM that was built, byte for byte.
The text of the table lives in the gap inside the tiles. Each mahjong tile is six characters, two wide by three tall, and the rows of tiles fall across two thirds of the screen. In the middle third you see the bottom row of the tiles above and the top row of the tiles below — the middle row of each tile is never drawn there.
That is where the cartridge writes its labels. Crossing the tile table with the name tables of 124 video-memory dumps: of the 97 character slots the status bar uses, 56 are exactly the middle pair of some tile. The same slot number is half a mahjong tile in one third and a kanji in another.
Counting what phase one spent, what has to be preserved (the box's cyan bar, the menu cursor) and the twelve letters of the bar's own alphabet that no label actually uses, the budget came to 49 slots.
With 8×8 letters the message box holds seven characters a line, and what the Japanese says does not fit. Three shapes were costed before anything was written:
| slots needed | verdict | |
|---|---|---|
| 8×8, one letter per slot | 26 | fits, but 7 characters a line |
| 3×7, two letters per slot | 49 | fits, 14 characters a line |
| 5px, "a letter and a half" | 66 | does not fit |
The middle one wins and the last one loses for the same reason: with two letters per slot the cost is one slot per distinct PAIR of letters; with a letter and a half the letters straddle two slots, almost every cell is unique, and the count explodes. The calculation does not need the font — a slot is determined by which pieces of letter fall in it and at what offset.
Two consequences, both of which bit:
As it stands the count is 49 distinct pairs for 49 slots, with no margin at all.
Often. English is longer than katakana, and a compressed block with letters in it compresses worse.
The generator handles it with reubica=ROUTINE: the block moves to the free area at the end of the cartridge and the ld hl that loads it is repointed. Blocks carry their own VRAM destination inside, so it does not matter where they are read from. Two variants were needed:
puntero_en=BLOCK, for blocks that are not loaded by an ld hl at all but through a pointer table — the two bytes of their entry get repointed instead.The free area is the 8,219 bytes of 0xFF at the end of the cartridge (0x9FE5-0xBFFF), of which the patch now uses 8,049 — 170 are left, measured as the 0xFF still at the end. What that assumes: that the original chip had those 8 KB writable. On an emulator and on a flashcart — which is how it is played — it works for certain.
There is also an escape valve worth knowing about: slots 0x08, 0x09 and 0x0A of the uncompressed block at 0x92E3 are byte-for-byte copies of 0x0C, 0x0D and 0x0E. Because that block is stored uncompressed, putting letters there costs no size at all — which is what saves you when a compressed block overshoots by a handful of bytes.
src/parche_en/parche.txt is the whole patch in readable form. Its full syntax is in the docstring of construye_parche_en.py; the shapes it knows are:
| section | what it writes |
|---|---|
[patrones] / [colores] | character shapes and colours, with reubica= |
[formatoA] / [formatoB] | the two drawing-list formats the cartridge has |
[codigo] | a named instruction substitution, one line at a time |
[libre] | declares the free area |
[asmlibre] | new code at the end of it, labels resolved by the assembler |
[diapositiva] | a new format-A list — this is how a tutorial screen is written |
[tilesnuevos] | pulls tiles out of a compressed block, recompresses them at a new address and rewrites the tile table, so they can still be asked for by name |
Plus raw destinations @0xADDR and the token <glyph@font>, for writing straight into the VDP's pattern and colour tables, and the fichas command, which pulls tile drawings out of the cartridge's own table.
Three generators own their sections of parche.txt and are the source of truth for them: the two digraph allocators and the logo slicer. Those sections are regenerated, never edited.
The build proves the ROM is what was asked for. It cannot prove the screen is right, and once it was not: see the cyan band.
make pantallas closes that hole. It dumps the original cartridge's screens and the patched ones and compares them cell by cell, with a rule that needs no guessing:
If a cell draws the same slot number in both versions, and the drawing was invisible before and is visible now, a letter has been written into the furniture.
What separates furniture from an ordinary translation is exactly that. When the patch translates a label it rewrites the list, so that cell ends up holding a different slot. Furniture keeps its own, because nobody touched it — all that changed is the drawing underneath.
One exception, checked by hand and written into the tool: the title logo, whose 54-slot canvas is deliberately redrawn.