9 October 2026: event commands, battle effects, the dial and the menu threads
Two more batches went through review overnight: the second half of the field's event-script commands in $C0, and the part of the battle engine that resolves an action's targets and runs its effects in $C1. Together 10,245 bytes in 143 routines, all independently verified.
Event commands, part two
The field's event scripts drive almost everything that happens outside battle. This batch ($C0, 5,214 bytes) covers the commands that move the party, give and take items and gold, add and remove party members, warp to other locations, play music and sound effects, fade the screen, shake it, and set up characters and sprites. The credits use a few of them too.
Two finds along the way:
- A block of RAM the code had called
Map_Unk7F3700turned out to hold small event scripts: one walks a party member back to the leader, three walk a member to a tile and turn it, and one remembers a script position. - Several commands hand work to the menu code in bank
$C2, and their headers first claimed the Y register came back unchanged. It does not: the menu side never saves it, and a later restore even clears its high byte. Review caught this in twelve headers.
Targets and effects
When a tech or an item is used, the battle engine first works out who it hits, then runs its effects one part at a time. This batch ($C1, 5,031 bytes) covers the target resolution, the runner that steps through a tech's parts, the MP check, the first effect types and the table that dispatches them, and a set of fixed-fraction scaling helpers. Five of the eight scalers have no caller at all.
Review found two quirks worth keeping:
- Two scratch variables in the target routine overlap, so a second entry in an area record would be read from the wrong place. No area record in the game has a second entry, so it never matters.
- On one path the final store reads entry $FF of the target list, well past its end, because a counter is 0 when 1 is subtracted from it.
Housekeeping
- CI now really checks the ROM's hash. Before, it looked for a file that does not exist in the repository and quietly skipped the check.
- The function notes in the symbol list now start at the beginning of a sentence instead of halfway through.
- The caller-finding tool's tests were pinned to code that has since been matched. They now test the same thing without depending on how much is matched.
- Every page of this wiki was checked against the current source and brought up to date, and five new system pages describe the frame cycle, the field event scripts, the scene engine, the text windows and battle actions.
The map view and the dial
A third batch ($C2, 4,850 bytes) followed in the early hours: two of the $C2 scenes' special modes and the menu's own interrupt handler.
- The map view (mode 6) builds a Mode 7 picture of the whole scene, one pixel across and two down per map tile, zooms out from the leader, shows position markers over a blue gradient and zooms back in on a button press.
- The dial (mode 8) lets the player turn a hand to one of seven location markers. The hand is a triangle drawn line by line into the window registers' HDMA tables, and the pick becomes object A's next destination, which settles a question the scene-object batch had left open.
Most of review's findings were one recurring slip: headers saying a routine takes either X width when it really needs 16-bit X. All 20 were fixed and passed a second review.
main now stands at 143,313 bytes, 39.89% of the game's code, all 1,933 matched routines independently verified.
The effect arithmetic
Next came the arithmetic behind the battle effects in $C1 (4,347 bytes): restoring HP and MP, curing and setting status, the hit roll, damage and its formulas, the hit, evade and defence adjustments, the timed-status lists, drain, steal, and the bank's small service table for finding, adding and removing items and gold.
The code has some real bugs, all kept as found and documented:
- Damage kinds 8 to 10 never work as intended, because one test is inverted.
- One effect can never miss: its miss flag is stored to the wrong byte.
- Two of the timed-status lists are never started; the code that would start them cannot be reached.
- Adding an item to a full inventory overwrites the first slot.
- Gold is capped at 9,999,999, but adding to a total of 9,961,472 or more can lose money instead when the low part overflows: 9,999,999 + 30,000 leaves 9,964,463.
Review sent six headers back, mostly for wording that was close but not exact about edge cases. All six were fixed and passed a second review.
The last event commands
The final batch of event-script commands in $C0 (5,611 bytes) completed the field's command table: the remaining handlers, the 256-entry table itself (32 of its entries are never used), the party's walking and running animations with their 65-entry table, the message window, the animated map tiles, and the field's interrupt handler, which uploads one tile-animation job each time it runs. A routine that had been called an audio sync turned out to touch no sound at all; it refreshes the HDMA tables and is now named for that.
Four headers needed fixes, among them an address range that stopped two bytes short and a register claimed to be "any" when a callee needs it zero. All four passed a second review.
main now stands at 153,271 bytes, 42.66% of the game's code, all 2,022 matched routines independently verified.
The menu's threads and the scene effects
The last batch of the day was the biggest in $C2 so far (5,349 bytes, 540 of them empty $FF fill at the end of the scene code). It covers the scene engine's screen effects (BG3 drifts, waves and slides, window effects, a noise effect, BG2 line waves, saving and restoring the party), the menu's entry point, and the machinery the rest of the menu runs on.
The menu is built from cooperative threads. A scheduler runs up to eight of them, each with its own 64-byte stack. A thread runs until it calls a yield routine, which saves its registers on its own stack and hands over to the next one; it carries on from the same spot a frame (or a set number of frames) later. The cursors, the scrolling-list arrows and the fades are each a thread. One detail the review caught: the scheduler never resets the data bank between threads, so each thread starts with whatever bank the previous one left.
The menu's scheduler: eight slots, each with a 64-byte stack from $0A00, run in order once a frame until each yields or ends.
Quirks kept as found include a block copy that writes one byte past the end of its buffer, a cursor drawn one pixel low because a helper is called in the wrong register width, and a list arrow that can sit nine pixels lower instead of eight.
Review found 22 header errors, most of them about what a register holds on exit. The most common: an instruction that switches the index registers to 8 bits wipes their high bytes for good, so a header cannot say Y comes back unchanged after calling such a routine. The same slip turned up in three older menu routines, which were fixed too. It took three rounds, but every routine in the batch is now independently verified.
main now stands at 158,620 bytes, 44.15% of the game's code, all 2,135 matched routines independently verified.
Hit modifiers, experience and tech points
Back in $C1 (3,521 bytes), the 21 small handlers that the damage routine runs after working out a hit turned out to be hit modifiers: most scale the damage, some set a status, change HP or leave the target on 1 HP. The same batch covers what happens after a battle: experience and level-ups with their HP and MP growth, tech points, and learning single and combo techs. One quirk, kept as found: when one award of tech points pays for a tech and reaches into the next, part of it is taken off the next tech's cost a second time. Reserve characters gain at most one level per battle.
Review again turned up the Y register slip (the random number routine switches the index registers to 8 bits, so nothing that rolls a random number can return Y unchanged), and two more older routines with the same error were fixed with the batch.
Bank $C0 is complete
The last 3,234 bytes of $C0 are matched, so every byte of the bank now comes from source. They hold a routine run from the field's interrupt that draws what is probably the cursor of a message choice, 400 bytes of cut-off code left over from older builds, and four lookup tables: an angle table, a direction-to-facing table and two sine tables, each checked against its formula entry by entry.
Counting code and data separately
With $C0 complete, the progress figure showed it as 106% matched, because the share counted every matched byte, tables and padding included, against a survey of code bytes only. The progress tool now tells code and data apart and measures the share with code alone; data is reported next to it. The survey for $C0 also turned out 922 bytes too high. The headline figure is therefore lower than this post's earlier sections give: by the new measure, main now stands at 154,931 of 358,356 code bytes, 43.23%, plus 10,444 bytes of data, all 2,184 matched routines independently verified.
Matched bytes over time, from the project's progress history. The steep rise from 8 October is the parallel workflow.
Catching one kind of mistake for good
One slip kept coming back in review: a header saying a routine leaves the Y register unchanged, when somewhere along the way (often in a routine it calls) the index registers are switched to 8 bits, which wipes Y's high byte for good. A sweep of all 618 such claims in the source found 23 that were wrong, all fixed and re-reviewed. The lint now checks every claim of this kind itself, following each path through the routine and the routines it calls, so a wrong one cannot get past the checks again. On its first run against the open batches it caught one more.
Why "Y unchanged" fails: switching the index registers to 8 bits clears their high bytes, and switching back does not restore them.
Enemy behaviour scripts
Enemies decide what to do with small scripts: a list of tests ("is my HP at most half?", "is a party member in the same row?", a random chance) followed by what to choose and what to run. The handlers for all three are now matched in $C1 (5,451 bytes): 34 tests, the choose handlers, and 23 actions, from attacking and using a tech to swapping in a waiting enemy, leaving the battle or changing the enemy's own stats. Many of the values they read are still unnamed, so most names are careful guesses, marked as such. A few are real bugs, kept as found: one test reads the knock-out flag from the wrong byte, and two actions skip a check and read from the wrong address when no helper is present.
The message window's frame and the palette animator
In $FD (5,805 bytes), the routines that build the field's per-line display tables are matched. They draw a band across the screen that opens line by line from the middle of a fixed position, near the top or the bottom depending on where the speaker stands, and it turned out to be the message window: the field's message code opens it by up to four lines a frame, to 40, and closes it again. The other half is the palette animator that runs every frame, copying, rotating, pulsing and fading colours, with a quirk where a pulse goes black for one frame where full brightness was meant.
main now stands at 166,187 of 358,356 code bytes, 46.37%, plus 10,444 bytes of data, all 2,290 matched routines independently verified.
The menu's commands and the main menu
The last batch of the day ($C2, about 5,000 bytes) fills in most of what the menu does before any screen opens: the command dispatcher the field and battle code call to change the party, refill records, check the save slots at boot and equip items; every setup routine the menu runs when it starts (graphics, OAM, the item tables, the window graphics and palette); the recalculation of a character's stats from their equipment; the save-slot summaries and labels; the menu's animated character sprites; and the main menu itself. The handlers of the other menu modes are next.
Two quirks worth knowing: the routine that multiplies by 64 only keeps A intact when called with a 16-bit accumulator, and one save-slot summary writes three flag bytes over the start of data it has just copied.
main now stands at 170,895 of 358,356 code bytes, 47.69%, plus 10,748 bytes of data, all 2,403 matched routines independently verified.
Next
Matching continues with the menu's mode handlers in $C2, the enemy scripts' target handlers in $C1 and the rest of the palette animator in $FD, while a survey maps the banks no one has started yet.