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

Last updated: October 9, 2026

Scene engine (bank $C2)

Bank $C2 holds a second, self-contained game mode that GameLoop_Main hands over to for locations $01F0 and up, and, from $C2:8000 on, the menu. This page calls it the scene mode, after its routines' C2Scene_ prefix; what the locations it runs show in the game is not traced yet, so the descriptions below stay with what the code does. Its text decoder has a page of its own: Text windows.

The scene mode has its own direct-page and low-WRAM layout (names in include/ram_menu.inc and include/constants_menu.inc, prefixes Menu, C2Scene and TextWin). It runs with DP=$0000 and DB=$00.

See also: Frame cycle and NMI, Architecture.

Entry points

Address Vector Goes to
$C2:0000 BankC2_Entry0000 BankC2_SceneBoot (JML from GameLoop_Main for Loc_Id $01F0–$81EF)
$C2:0003, $0006 BankC2_Entry0003/0006 TextWin_Init
$C2:0009, $000C BankC2_Entry0009/000C TextWin_Step
$C2:8000 BankC2_Entry8000 BankC2_MenuEntry, the menu
$C2:8002 BankC2_Entry8002 Menu_ReadPad, the joypad reader every NMI calls
$C2:8004 BankC2_Entry8004 Menu_Unk8C36, a command dispatcher (not matched)

Boot and the main loop

BankC2_SceneBoot ($C2:000F) turns interrupts, NMI, DMA and HDMA off, sets forced blank and FastROM, writes the PPU registers (BankC2_InitHwRegs), clears the direct page, installs the scene's own interrupt handlers (C2Scene_InstallInterrupts), empties the VRAM queue and blacks out the palette, then jumps into C2Scene_Main ($C2:23A8).

C2Scene_Main clears VRAM, the tasks and the sprite list, places the start position and scroll from the field's entry tile, loads the scene (C2Scene_LoadScene), turns the NMI on and enters C2Scene_MainLoop, which waits a frame and dispatches on C2Scene_Mode through C2Scene_ModeTable ($C2:23DB, 10 words):

Mode Handler What it does
0, 1, 7 C2Scene_ModeIdle Send a pending sound command, nothing else
2 C2Scene_Mode2 Leave the scene: interrupts off, then JML to ReentryVectors, restarting the field's game loop (set by script op $05)
3 C2Scene_Mode3 Act on a C2Scene_ListA entry (probably an exit): start its script, or set up a location change and fall into mode 2
4 C2Scene_Mode4 Everything off, for good (probably waits for a reset)
5, 9 C2Scene_Mode5 Run the menu over the scene: save the scene, JSL BankC2_Entry8000, then rebuild it
6 C2Scene_Mode6 Run C2Scene_MapView (a BG mode 7 map of the scene; see Mode 6) between mosaic fades
8 C2Scene_Mode8 Run C2Scene_Dial (a BG mode 7 dial; see Mode 8) over a saved scene

The per-frame work is in the NMI, not the loop: C2Scene_NmiHandler ($C2:015E) uploads OAM, the palette and the PPU shadows, flushes the VRAM queue (C2Scene_VramQFlush), reads the joypad, then runs every task and rebuilds the sprite list for the next frame.

Tasks

The moving parts are tasks: 64 records of 64 bytes (C2Scene_Task at C2Scene_TaskRecords, $0B30) whose first word is a handler address. C2Scene_TaskRunAll ($C2:051D) calls every live handler once per frame; a handler that returns C=1 is done and its record is freed. C2Scene_TaskSpawn and C2Scene_TaskSpawnScript ($C2:048A–$C2:051C) start new ones, the Low variants in records 0–3 only. Motion is 16.16 fixed point (C2Scene_TaskMove), and positions wrap within the 1536 × 1024-pixel scene map (C2Scene_WrapTaskPos).

Matched task handlers include the screen fades and mosaics (C2Scene_TaskUnk20A2/2105/2194/ 21F8, started by script ops $28–$2B), the palette fade (C2Scene_TaskPalFade), the tile upload (C2Scene_TaskUnk1CF5), the sound zone task, and the tasks of the party and of two objects:

  • C2Scene_LeaderTask ($C2:3404 on): the first party member's sprite, walked with the D-pad in 8-pixel steps while BG1 and BG2 scroll with it; before each step it reads the property nibbles of the tiles ahead (C2Scene_GetTileProps) to block the step or set the trigger flags.
  • C2Scene_MemberTask ($C2:3AE2 on): the other party members, following with a route planner (C2Scene_MemberRoute, C2Scene_PathBlocked).
  • C2Scene_ObjATask and C2Scene_ObjBTask ($C2:42DD–$C2:5627): two objects the party can get into. Object A can rise, be steered with the D-pad and land again; its header says that what it is comes "from this behaviour only (a vehicle, probably)".
  • Two watchers, C2Scene_ObjWatch and C2Scene_TrigWatch ($C2:309E–$C2:3403): state machines that select an object when a button is held and the party's box overlaps it, or look the party's tile up in the trigger lists C2Scene_ListA–C.

None of the party tasks, objects or watchers has a reference in the bank's code; their headers say they are probably started from scene data.

Scripts and animations

Tasks are driven by two byte-codes, both with the same handler convention (Z=0 with the bytes to advance in A, or Z=1 to stop for this frame and run the same op again):

  • Scene scripts: C2Scene_TaskRunScript ($C2:0F63) runs ops $00–$52 through C2Script_OpTable, all matched ($C2:0F63–$C2:1C83). They cover task and RAM bytes (set, clear, add, compare and branch), spawning scripts and tasks, calls and returns, waits, positions, velocities and animations, layer scrolls and redraws, VRAM queue entries, sound commands, the trigger lists, and C2Script_GoToLocation (op $05), which sets up a location change in the field's location block and selects mode 2. C2Scene_LoadScene starts the scene's first script on C2Scene_ScriptBuf ($7F:0400).
  • Animation scripts: C2Anim_Run ($C2:0E1D) runs a task's sprite script with eight ops (C2Anim_OpTable): clear, increment or decrement a task byte, jump back, show a frame for a number of frames, wait, queue a VRAM upload, end.

Drawing

  • BG layers. A layer's tilemap is built from 16×16 metatiles: a 96 × 64 metatile map per layer in bank $7E. C2Scene_DrawBgLayer ($C2:09C5) redraws the visible part of layer 1 or 2 column by column, sending each column to VRAM at once (so under forced blank or in vertical blank); C2Scene_Unk0568 / C2Scene_Unk066C ($C2:0568, $C2:066C) scroll a layer horizontally or vertically by a number of pixels and queue the new edge column or row.
  • Sprites. A doubly linked list of 64 16-byte nodes (C2Scene_SprNodes): every frame the NMI frees all nodes (C2Scene_SprResetLists), the tasks add what they show (C2Scene_SprAdd), and C2Scene_SprDrawList turns the list into OAM entries.
  • VRAM queue. 8-byte entries flushed by the NMI with DMA channel 0 (C2Scene_VramQFlush).

Loading a scene

Each scene has a header in bank $C6 (C2SceneRom_HeaderTable) whose bytes pick packed data from pack tables in the same bank; Decomp_ToWramVec unpacks each into WRAM. C2Scene_LoadScene ($C2:2C1D) loads the graphics and palettes into VRAM and CGRAM (C2Scene_LoadVram), then the metatiles, the maps, the tile-property pack at $7E:7000, the pack at $7E:7200 (it starts with a map of sound zones), the trigger lists, the script and two more packs, draws BG1 and BG2 and spawns the script task. What the packs hold is inferred from where they go. C2Scene_SaveState / C2Scene_RestoreState ($C2:2E23, $C2:2E72) copy the scene's state to bank $7F and back around the menu, and C2Scene_ReloadScene reloads its graphics.

Screen effects

The scene scripts in bank $C3 call small routines in bank $C2 with op $34 and start task handlers with op $35 ($C2:754D–$C2:7B59 and $C2:7BC4–$C2:7DE3; none of them is referenced from bank $C2 code). They run inside C2Scene_TaskRunAll, from the scene NMI.

  • BG3 tasks: C2Scene_TaskBg3Drift moves layer 3 a pixel left every 4th frame and down every 8th; C2Scene_TaskBg3Slide and C2Scene_TaskBg3SlideFast move it at a fixed velocity; C2Scene_TaskBg3Wave bends it into a horizontal wave with HDMA (one sine period over 64 lines, repeated down the screen), and C2Scene_TaskBg3DriftWave drifts it as well. They wait while object A is in mode 8, and also scroll layer 3 by the leader's last move. C2Scene_TaskBg3LineWave (a wave on every odd line) and C2Scene_TaskBg1Pan (layer 1, a pixel every 4th frame) have no reference found.
  • Windows: C2Scene_Win1Init / Win1Step and C2Scene_Win2Init / Win2Step set up HDMA for window 1 or 2 and have BankC3_Entry0008 build the window table from the task's position and size into one of two buffers, so the table being written is not the one on screen ("the shape is not traced").
  • Line waves: C2Scene_Bg2HWaveInit / Bg2HWaveStep (and the unreferenced vertical pair) run a wave up layer 2: each step moves the lines up by one and adds a new bottom line from a sine. C2Scene_NoiseInit fills a line buffer with the first $1C0 bytes of ROM bank $C0 ("code, used as noise") and scrolls layers 1 and 2 by them; C2Scene_NoiseStep, NoiseShow and NoiseHide move through the noise, show it or hide it.
  • Placement and motion: random positions below the view or near fixed points, one of 16 random velocities, a gravity step (1/8 pixel a frame added to the Y velocity), a random nudge, and a test of whether the task is in view.
  • Other: clear, save and restore the party, unpack a BG pack, set or test flag bits, set the music byte Menu_Config1E, and C2Scene_BuildPatternTiles, ten tiles whose patterns get denser from pair to pair ("probably the steps of a dissolve").

The scene objects also use three direction helpers ($C2:2273–$C2:232C), with 256 steps per turn: C2Scene_Unk2273 and C2Scene_Unk2277 give the cosine and sine of a direction (from Rom_SineTable256, ±128 at the quarter turns), and C2Scene_Unk229D the direction from one point to another on the wrapping scene map. Their header suggests the names C2Scene_Cos256, C2Scene_Sin256 and C2Scene_DirToPoint.

Mode 6: the map view

C2Scene_Mode6 ($C2:258D) fades out with a mosaic, turns interrupts, DMA and HDMA off, copies the direct page and the 64 task records to bank $7F and runs C2Scene_MapView ($C2:631F– $C2:6A33); afterwards it copies both back, reloads the graphics and fades in with a mosaic. The source names it a map view, "inferred from what it builds":

  • C2Scene_MapView sets BG mode 7, clears VRAM, the VRAM queue and the tasks, draws a label into sprite tiles (C2Scene_MapViewLabel, a string picked by Loc_Id $1F0–$1F7), sets up a blue fixed-colour gradient behind the picture with HDMA (C2Scene_MapViewGradient) and builds the picture (C2Scene_MapViewBuild).
  • The picture is a small map of the whole scene: C2Scene_MapDrawPixels draws the scene's two BG maps one pixel per metatile across and two down, 192 mode-7 tiles in all, uploaded a band at a time.
  • The task C2Scene_TaskMapZoom zooms out from the leader's position (C2Scene_StartX/Y) over 32 steps of the mode-7 matrix (C2Scene_MapZoomMatrix), then shows marker sprites for the leader, object A and the location (C2Scene_TaskLeaderMarker, C2Scene_TaskObjAMarker, C2Scene_TaskLocMarker). Pad bit 3 (C2Scene_MapViewExitBtn) zooms back in; at the end it sets mode 7, which ends C2Scene_MapView's wait.

Mode 8: the dial

C2Scene_Mode8 ($C2:261D) fades out, sends sound command $82, saves the scene (C2Scene_SaveState) and runs C2Scene_Dial ($C2:6A34–$C2:754C). Afterwards it restores the scene and, unless two flag bytes at $7F:01F4/$7F:01F5 are set ("who sets them is not traced"), reloads the graphics and fades in; with them set it goes straight back to the loop in whatever mode the dial left.

The dial is a BG mode 7 screen whose per-line matrix is animated by C2Scene_TaskSwirl, with seven marker sprites (C2Scene_TaskDialMark) around a centre and a hand drawn with window 1, the fixed colour added inside it (C2Scene_DialDrawHand). Each marker stands for a location in C2Scene_DialLocs (or C2Scene_DialLocsLate, once a flag reaches C2Scene_DialLateFlag). C2Scene_TaskDial reads the pad: right or up (or one other button) turns the hand back a marker, left or down (or another button) forward one; confirming a marker that is not the current location stores its Loc_Id in C2Scene_ObjANextLoc and ends the dial, confirming the current one plays a sound ("probably a refusal sound"), and cancel stores C2Scene_DialNoDest and ends it.

The menu in the second half of the bank has a page of its own: Menu.

What is not matched yet

Everything from $C2:0000 to the menu's start at $C2:8000 is matched, the $FF fill at $C2:7DE4 included. What is left in the bank is the menu's; see Menu.