Kajar notes of ChronoRET, rebuilding Chrono Trigger one matched routine at a time

24 September 2026: the cursor that draws itself

One big routine: BattleMenu_UpdateCursorOverlay, 828 bytes, the last stub in the command-menu neighborhood. Every frame, after input is processed, it decides what the pointing hand (or hands) on screen should look like, and where.

One routine, four jobs

It starts with dispatch. If no party member is choosing a command, hide the cursor and stop. If the player is picking a target, draw target cursors. Otherwise branch on the open submenu: main, tech or item. The main menu path does nothing: its cursor was already drawn earlier in the frame by BattleMenu_DrawCursorSprites, so it skips to the end. The other three paths each solve a different problem, so it reads like four small routines stitched together.

Dual and triple techs get more than one hand

Combo techs need the cursor to show who is involved. The tech path checks the highlighted tech's id. Below $39 it is a single-character tech and gets one hand. At $39 and above, it reads a byte that packs the partners into two nibbles (low nibble first partner, high nibble second, $F meaning nobody) and places one cursor over the caster and one over each partner.

A byte split into nibbles: high nibble the second partner, low nibble the first

The partner byte of a combo tech: one nibble per partner, $F in the high nibble for a double tech.

Every placement uses the same formula: the battler's base screen position, plus a second per-battler offset (probably the live animated position), minus 16 to center the sprite. It appears six times, unrolled each time instead of in a helper. On a 3.58 MHz CPU, saving a JSR/RTS per cursor was apparently worth the bytes.

The item list only redraws when it has to

The item path manages the up/down scroll arrows and the row highlight, and keeps a cached copy of the last value drawn for each. If the live value matches the cache, nothing is touched. Only on a change does it redraw, and only then does it ask for the tile buffer to be uploaded to video memory.

At the top of the list the up arrow is hidden; at the bottom (scroll position $FA) the down arrow is hidden. When the down arrow is visible it uses attribute $A9 instead of the usual $29, unlike the up arrow. That looks deliberate: "more below" gets the emphasis, "more above" doesn't.

Picking a target, and "target all"

Single-target selection draws one cursor over the chosen battler. Only in the plain Attack menu, and only when the target is an enemy, it goes through one of two cross-bank message calls that drive the enemy info panel. A per-enemy flag table at $E1DE80 decides whether the panel gets the real target id or $FF. Neither callee is matched yet, so their exact behavior is a guess.

Multi-target selection took the longest to understand. There are only four cursor sprites, but a "hit everything" tech can target more battlers than that. The routine doesn't draw them all in one frame. It scans the selection list three entries at a time, saves where it stopped ($960B), and continues from there next frame, wrapping to the start after six. The cursors alternate between groups of targets on successive frames: limited sprites, so the work and the drawing are spread across frames, a trade-off the SNES forces on many games.

The 11-entry selection list on two rebuilds: entries 0-2 marked on one, 3-5 on the next

The target-all sweep: sprites 1-3 alternate between entries 0-2 and 3-5 while sprite 0 stays on the first target. Entries 6-10 are never reached.

Matching notes

Nothing exotic, but the size made bookkeeping matter. There are forty-two local .labels, all branch targets inside this one routine. Several branches are just out of 8-bit range, so the original uses a short branch to a nearby JMP. Those trampolines (.tech_single_jmp, .to_tail and others) have to sit exactly where they are, or every address after them shifts.

The day in numbers

Routine Address Bytes
BattleMenu_UpdateCursorOverlay $C1:17DD–$C1:1B18 828
Total 828

Bank $C1: 7,028 of 63,904 bytes (11.00%). Project-wide: 26,997 bytes (7.51% of all game code). $C1:1320 to $C1:1B18 is now one continuous matched region.

Next

The command menu has no stubs left. The next piece is the target-mode handler table at $C1:1FF8, a dozen-plus BattleTgt_* routines the menu calls but that are only referenced by address so far. First a small gap: $C1:1B19–$C1:1B54, between this routine and Battle_StopSfx.

← Back to devlog