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

Last updated: October 9, 2026

Menu (bank $C2)

The second half of bank $C2, from $C2:8000 on, is the menu. The field (Field_PauseAndMenuInput, Field_SceneChangeTick, Field_RunBankC2Mode5) and the scene mode's mode 5 call it with JSL BankC2_Entry8000 and an argument 0–6. Most of the menu's own screens are not matched yet, so this page covers the frame around them: the entry, the thread scheduler, the interrupt handlers and the helpers the screens share.

The menu runs with DP=$0000 and DB=$7E unless stated (names in include/ram_menu.inc and include/constants_menu.inc, prefix Menu).

See also: Scene engine, Frame cycle and NMI.

Entry points

Address Vector Goes to
$C2:8000 BankC2_Entry8000 BankC2_MenuEntry, A = the argument (0–6)
$C2:8002 BankC2_Entry8002 BankC2_ReadPadLong → Menu_ReadPad, the joypad reader
$C2:8004 BankC2_Entry8004 BankC2_CommandLong → Menu_Unk8C36, A = a command (not matched)

Entering and leaving

BankC2_MenuEntry ($C2:800E) turns interrupts off, saves DP, DB and P, keeps A and X (Menu_EntryArg, Menu_EntryX), forces blank and turns NMI, DMA and HDMA off.

  • Argument 0 on a new game (Menu_DataInitDone still 0): it fills the new game's data (Menu_InitNewGameData), clears two SRAM bytes and leaves through Menu_ExitRestart to GameLoop_Main. No menu is shown.
  • Otherwise: it moves the stack to Menu_StackTop, runs Menu_InitSystems, starts the play-time clock, picks the mode from Menu_EntryModes (arguments 0–6 give modes $0E, $00, $0C, $0D, $0F, $07, $0B), turns the NMI and interrupts on and hands over to Menu_RunThreads, which never returns.

Menu_InitSystems ($C2:8096) copies Menu_InterruptVectors ($C2:8406: JML Menu_Nmi, JML Menu_Irq) over the interrupt trampolines at $0500, sends five sound-driver commands ("meanings not traced"), sets up the PPU and clears RAM (Menu_InitPpuAndRam), builds the party lists (Menu_BuildPartyLists) and runs six more set-up routines that are not matched yet.

The menu leaves in one of two ways. Menu_Exit ($C2:82B2) frees every thread, fades out, restores the stack, P, DB and DP and returns with RTL to the caller of BankC2_Entry8000. Menu_ExitToGame ($C2:8320) fades out and jumps through ReentryVectors entry 0 to GameLoop_Main (the warm restart).

The thread scheduler

The menu's screens run as up to eight cooperative threads. Menu_RunThreads ($C2:8190) keeps them in eight 8-byte slots (Menu_Thread, at $00B0), each with a 64-byte stack from $0A00 on. It starts Menu_MainThread in slot 1 and Menu_UnkE91B (not matched) in slot 3, then once a frame:

  1. waits until Menu_FrameReady is non-zero (the menu NMI has done its upload);
  2. for each slot in use, counts its .Wait down and runs the thread when it reaches 0: a new thread starts at its entry on a fresh stack, a waiting one gets its stack, X, Y, P and DP back and returns from its yield;
  3. after the last slot, clears Menu_FrameReady.

A thread gives the CPU back by jumping to one of three sub-entries:

Sub-entry What it does
Menu_Yield ($C2:821E, JSR) Wait one frame
Menu_YieldFrames ($C2:822B, JSR) Wait A frames (0 means 256)
Menu_ThreadEnd ($C2:8216, JMP) Free the slot

Menu_StartThread ($C2:8249) puts a new thread in a slot (it starts on the next pass) and Menu_KillThreads ($C2:8255) frees the slots whose bits are set in A. Each thread also keeps one word, Menu_ThreadVar, across its yields. The header notes that DB is set only at the start of each frame, so a thread starts or resumes with the DB the thread before it left.

The main thread. Menu_MainThread ($C2:826C, slot 1) calls one handler a frame: entry Menu_ThreadVar of the current mode's list in Menu_ModeLists ($C2:8292, 16 modes). A handler moves on by changing Menu_ThreadVar or Menu_Mode. The mode lists themselves are not matched yet; modes 8–10 point at a bare RTS and are "probably not used".

Interrupts and the frame

Menu_Nmi ($C2:840E) runs on its own stack with DP = Menu_NmiDp ($0D00). It counts frames, forces blank and, when the threads have a frame ready, turns HDMA off, runs the upload (Menu_Unk85D6, not matched) and sets Menu_FrameReady to $FF, which the scheduler waits for. Then it reads the pad and ticks the play-time clock (Menu_NmiPad, with the menu's auto-repeat: $20 frames before the first repeat, then 5), and sets the BG mode and the screen brightness. Menu_Irq ($C2:8472) is a bare RTI.

Menu_ReadPad ($C2:84D2) reads joypad 1 (Menu_PollPad), maps it through the configurable button map (Menu_MapButtons) and advances the play-time clock Menu_TickPlayTime ($C2:85AE: frames, seconds, two-digit minutes and hours, with a flag once it passes 99:59:59). The scene NMI and code in other banks call it too, the battle's play-time hit modifier among them.

Shared helpers

  • Set-up. Menu_InitPpuAndRam ($C2:940D) puts the PPU into the menu's starting state and clears VRAM and most of bank $7E with its DMA helpers; Menu_InitNewGameData fills the data of a new game (inferred from what it fills) and Menu_ClearConfigAndFlags clears the flag block, the play time and the settings, then loads the default settings and button map ($C2:956E–$C2:960A).
  • Fades. Menu_Fade ($C2:83D0) steps the brightness by A each frame, yielding between steps, until it reaches 0 or 15; Menu_FadeOut uses a step of −2, and Menu_StartFadeIn starts Menu_FadeInThread (+2 a frame) in slot 0.
  • VRAM queue. Menu_QueueVramRom / Menu_QueueVramWram ($C2:8385) append an 8-byte upload record from bank $C2 or $7E to Menu_VramQueue; Menu_FlushVramQueue ($C2:8663) sends them, newest first, with DMA channel 7 and empties the queue.
  • Palette and gradient. Menu_UploadPalette ($C2:86A9) sends the 512 bytes of Menu_PalBuf to CGRAM when Menu_PalDirty is set. Menu_BuildGradient ($C2:86DD) builds an HDMA table of colour-math lines for channel 0 in the buffer not built last, from Menu_GradSpec and the steps in Menu_GradientSteps.
  • Inventory. Menu_AddItem, Menu_RemoveItem and Menu_FindItem ($C2:8791–$C2:8815) add to and take from the 256 item slots, a count capped at 99.
  • Characters and party. Menu_BuildPartyLists lists the present characters and counts the active members (positions 0–2) and all of them; Menu_LoadCharRecA ($C2:8839) copies a character's $50-byte record to Menu_CurCharRec, and Menu_LoadPartyCharRec does it for the current member.
  • Items and equipment. Menu_ItemCategory ($C2:8881) sorts an item id into one of five categories by id range, and Menu_ItemDataPtrA / B give the address of its record in bank $CC ("what the records hold is not traced"). Menu_CountEquippedBy counts the characters that have an item equipped, and Menu_ListEquipItems ($C2:8953) lists the inventory items of one id range that Menu_Unk93A8 accepts (not matched; "probably 'the current character cannot equip it'" when it refuses).
  • Cursors and list arrows ($C2:89E4–$C2:8C35). Threads in slot 6 draw the blinking pointing-hand cursor sprites at the positions of Menu_CursorPos records, in four variants (Menu_StartCursorA–D; D shows one cursor per active party member). Menu_ListArrowsThread (slot 4) blinks the up and down arrows of a scrolling list: the up arrow when the list's top is not 0, the down arrow when it is below Menu_ListRows.

Quirks the headers keep include Menu_CursorStepD calling Menu_CursorPosAB with a 16-bit A, so its cursors sit one pixel lower than the others, and a missing CLC in Menu_ListArrowsThread that can put the down arrow one pixel lower.

What is not matched yet

From NEXT.md, in reach order: the command dispatcher Menu_Unk8C36 (behind BankC2_Entry8004, which calls the inventory routines), the set-up routines Menu_InitSystems calls, thread 3 (Menu_UnkE91B) and the mode handler lists. The menu NMI's upload Menu_Unk85D6 is also still open. After $C2:8C36 only the set-up at $C2:940D–$C2:960A and Menu_CursorRecOffset ($C2:EA27) are matched.