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:3404on): 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:3AE2on): the other party members, following with a route planner (C2Scene_MemberRoute,C2Scene_PathBlocked).C2Scene_ObjATaskandC2Scene_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_ObjWatchandC2Scene_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 listsC2Scene_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–$52throughC2Script_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, andC2Script_GoToLocation(op$05), which sets up a location change in the field's location block and selects mode 2.C2Scene_LoadScenestarts the scene's first script onC2Scene_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), andC2Scene_SprDrawListturns 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_TaskBg3Driftmoves layer 3 a pixel left every 4th frame and down every 8th;C2Scene_TaskBg3SlideandC2Scene_TaskBg3SlideFastmove it at a fixed velocity;C2Scene_TaskBg3Wavebends it into a horizontal wave with HDMA (one sine period over 64 lines, repeated down the screen), andC2Scene_TaskBg3DriftWavedrifts 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) andC2Scene_TaskBg1Pan(layer 1, a pixel every 4th frame) have no reference found. - Windows:
C2Scene_Win1Init/Win1StepandC2Scene_Win2Init/Win2Stepset up HDMA for window 1 or 2 and haveBankC3_Entry0008build 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_NoiseInitfills a line buffer with the first$1C0bytes of ROM bank$C0("code, used as noise") and scrolls layers 1 and 2 by them;C2Scene_NoiseStep,NoiseShowandNoiseHidemove 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, andC2Scene_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_MapViewsets BG mode 7, clears VRAM, the VRAM queue and the tasks, draws a label into sprite tiles (C2Scene_MapViewLabel, a string picked byLoc_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_MapDrawPixelsdraws 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_TaskMapZoomzooms 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 endsC2Scene_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.
Menu side
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.