# Mario Bros (JU) — Game Flow from Power-On ## Status **Pass 3 complete; promoted.** `prg.s` is a traced disassembly, corrected against a Mesen Code Data Logger capture, with every address-derived label replaced by a name, every `jsr`/`jmp` operand symbolic and every hardware register named through `symbols.inc`. `make` prints `Byte-identical ROM verified.` for the whole ROM. Every data block in the PRG is interpreted in [section 8, the Pass 3 data-table inventory](#8-pass-3-data-table-inventory): each block says what the bytes are and which instruction in this dump reads them. Eight blocks that earlier revisions had named turned out to be code the tracer could not reach, or fragments of the two inline dispatch tables; those are left unnamed in `prg.s` and recorded here as what they are. This document describes the execution flow of the disassembled Mario Bros (JU) PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `prg.s` in this folder. The rebuild is verified **byte-identical** to the original ROM, header and CHR included (`make` prints `Byte-identical ROM verified.`). Mario Bros is a two-player platformer: Mario runs, jumps, and throws water balloons at turtles, beetles, paratroopa and piranha plants while the stage scrolls. The engine is built around three ideas that show up everywhere below: * a **frame heartbeat** — the NMI sets a flag and the main loop spends it, and a second flag stops the NMI handler from doing its per-frame work twice; * an **inline dispatch table** — the handler table sits in the middle of the code stream, right after the call that reads it, so the static tracer cannot follow it; * a **script interpreter** — the enemies are driven by byte-coded scripts that the engine walks with a 16-bit pointer. --- ## 1. Reset Vector — CPU $FFFC → $C000 **`Reset`** ($C000–$C04E) | Step | Code | Purpose | |------|------|---------| | 1 | `cld` / `sei` | Clear decimal mode, disable interrupts | | 2 | `lda PPUSTATUS` / `bpl WaitVblank1` | Wait for the first VBlank (PPU warm-up) | | 3 | `stx PPUCTRL` / `stx PPUMASK` | Blank the PPU | | 4 | `dex` / `txs` | Stack pointer to $01FF | | 5 | `ldx Zp70` | Read the warm-start signature | | 6 | $C015–$C025 | Clear RAM $0000–$07FF through the `Zp00`/`Zp01` pointer | | 7 | $C026–$C02B | If the signature was zero (a cold boot) store the marker $5F at `$0500`; a warm start writes the old marker back | | 8 | `jsr InitVramAddress` / `jsr SetVramAddressF400` | Point the PPU at the two VRAM areas the game uses | | 9 | `sta PPUSCROLL` ×2 | Scroll to zero | | 10 | `sty GameRunningFlag` (=$01) | The game is running | | 11 | `lda #$0F` / `sta APUSTATUS` | Enable all five APU channels | | 12 | `lda #$90` / `sta PPUCTRL` / `sta Zp09` | NMI on, background and sprites on; the PPUMASK shadow starts at $06 | | 13 | `lda #$06` / `sta Zp0A` | The PPUMASK shadow's second half | | 14 | `lda #$00` / `sta FramePendingFlag` | No frame is pending yet | Step 7 is the warm-start check: `$0500` is read *before* the RAM clear, so a cold boot (signature zero) writes the marker $5F and a warm start writes the old marker back. --- ## 2. The Main Loop and the Frame Dispatch **`ClearFramePending`** ($C04F–$C07C) is the main loop: ``` ClearFramePending: lda GameRunningFlag bne ClearFramePending_Loop ... ClearFramePending_Loop: jsr NopPadding jsr DecFrameCounter jsr UpdateGameplay lda #$01 / sta NmiPhaseFlag EndFrame: lda FramePendingFlag beq EndFrame_Loop inc FrameCounter lda #$00 / sta NmiPhaseFlag jmp ClearFramePending EndFrame_Loop: jsr TestRamFlagPair0500 jmp EndFrame ``` The loop spends the frame-pending flag the NMI sets: it advances the frame counter, runs the gameplay update, and only then clears the flag, so the NMI cannot interrupt the middle of a frame. **`UpdateGameplay`** ($C4B8) is the gameplay work list. It tests the `GameRunningFlag` and the mode index, and then copies the six per-actor parameters from the frame-countdown block into the actor block, walks the actor list, and dispatches each actor's behaviour. --- ## 3. The NMI Handler **`NmiHandler`** ($C07D–$C0B5) ``` NmiHandler: pha / txa / pha / tya / pha lda #$00 / sta OAMADDR lda #$02 / sta OAMDMA lda NmiPhaseFlag beq NmiHandler_Loop jsr UpdateOamShadow jsr DrawStageText jsr DrawHudRow jsr DrawTileRow2 jsr SetScroll jsr ReadControllerPort ``` The handler pushes every register, starts an OAM DMA from `$0200`, and then tests `NmiPhaseFlag` ($22). The flag is set while the main loop is working and cleared by it, so the per-frame work (the OAM shadow, the stage text, the HUD, the tile row, the scroll and the controller read) happens once per frame and not once per NMI. This is the same guard the other games in this repo use to stop a `$2002` read from re-arming the NMI inside one scanline. --- ## 4. The Dispatch Architecture The engine does **not** use a jump table in the data segment. It uses an **inline** table: the handler table is the data that follows the call, and the call itself pops the address of that data off the stack. **`IndirectCall`**-style dispatch is not used here, but the *inline table* idiom is: the handler pointers sit in the middle of the code stream, so the static tracer cannot follow them, and the Code Data Logger can only tell that the bytes were fetched. The main table is `GameStateHandlerTable` ($C510) and its continuation blocks ($C520, $C527, $C595, $C599, $C5A2) — twelve 16-bit handler pointers that the engine walks with the index in `Zp41`. The table is interrupted by the four bytes at $C595–$C598, which the Code Data Logger saw as data. The other dispatch is the **script interpreter**. `SetupScriptPointer` ($CB85) takes a table base and a byte offset, `AdvanceScriptPointer` ($CBAE) steps it by $20, and `FetchScriptByte` ($E9FA) reads the next byte of the script. `RunScript` ($E9D3) is the interpreter loop, and the command byte it fetches is compared against $AA and $BB to pick the command's meaning. --- ## 5. The Controller **`ReadControllerPort`** ($CCC5) strobes `$4016` and reads the port; **`ReadControllerBits`** ($CCD3) shifts the eight bits out. The value is compared with the previous one so that a button has to be held before it is accepted, and the result lands in `Zp10`/`Zp11`, the Mario sprite state the NMI handler tests. --- ## 6. The Sound Engine The sound engine lives at the top of the PRG ($F8A7–$FD27) and works from per-note state in the zero page: | Address | What it holds | |---------|---------------| | $D0–$D3 | the per-note state | | $E0 | the envelope counter | | $E4 | the envelope value | | $E5 | the note table index | | $E6 | the note index the current note was read for | | $E9/$EA | the two values the APU sweep registers are loaded with | | $EB–$EF | the envelope state | | $F0/$F1 | the volume table indices | | $F2 | the channel state | | $F3 | the main state byte | | $F8–$FF | the eight note parameters | `SetNoteOffset00`…`SetNoteOffset04` ($F776–$F782) are the four channel entries: each loads an offset, a volume of $40 and the note's X and Y, then copies four bytes into the APU's table. `NoteParameterTable` ($FCC8) holds the note records, `NoteFrequencyTable` ($F904) the frequencies, and `PulseDutyTable` ($F992) the four pulse-channel control values. --- ## 7. Memory Map (Key Zero Page / RAM) | Address | Name | What it holds | |---------|------|---------------| | $20 | `FramePendingFlag` | Set by the NMI, spent by the main loop | | $22 | `NmiPhaseFlag` | The NMI has already run this frame | | $26 | `ModeIndex` | The mode the game is in | | $28 | `ActorActionFlag` | An actor action is running | | $2A | `FrameCountdown` | The frame countdown, reloaded with 10 | | $2C | `ActorType` | The type of the actor being worked on | | $2D | `ActorIndex` | The index of the actor being worked on | | $2F | `FrameCounter` | The frame counter; its low bit is the frame parity | | $30 | `GameRunningFlag` | The game is running | | $33 | `ScriptStepCounter` | The actor scripts count down through their steps | | $34 | `ScriptParam` | The parameter the next script step is called with | | $35 | `ScriptCommand` | The script command byte | | $3F | `PlayerSlot` | The player slot being worked on | | $45 | `ActorYPosition` | The actor's Y position | | $46 | `ActorXPosition` | The actor's X position | | $4A | `ActorYTarget` | The Y position the animation counts towards | | $A0/$A1 | `ScriptPtrLo`/`ScriptPtrHi` | The pointer to the actor script block | | $A2 | `ScriptStep` | The byte offset inside the script block | | $B4 | `ActorPalette` | The four palette bytes | | $B8 | `ActorTileIndex` | The actor's tile index | | $B9 | `ScrollX` | The horizontal scroll position | | $BB | `ActorCount` | The number of actors in use | | $BF | `ActorCommand` | The actor command byte | | $C4 | `ActorState` | The actor's state byte | The full zero-page map, with the reason every neutral `ZpXX` slot was left neutral, is in `zp_aliases.inc`. --- ## 8. Pass 3 Data-Table Inventory Every data block the tracer emitted in this PRG is listed below — 35 blocks in all. "Read by" gives the address of the instruction in this dump that fetches the block; where no instruction reaches the bytes the row says so and records what the bytes are instead. Several blocks that earlier revisions of this project had named are in fact **code** that the tracer could not reach, or fragments of the inline dispatch tables; those are recorded as such here. Where a block is a fragment of a table whose other parts the tracer emitted as code, the row names the table and the fragment it is. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C510-$C51D` | 14 | `GameStateHandlerTable` | The first seven of the **twelve** 16-bit little-endian handler pointers of the inline table that follows `jsr DispatchViaStackPointer` at `$C50D`: `$D34A`, `$E14A`, `$D3F9`, `$D3A8`, `$C5A3`, `$D5E5`, `$E453`. The table runs `$C510`-`$C527`; the index is the gameplay mode in `Zp40`, which the code sets to 5 at `$C4ED` and to 10 at `$C4FF`, so the table must have at least eleven entries. | The dispatcher at `$CD9E` pops the table address off the stack (the return address of its own `jsr` plus one) and walks it with the index in `Zp40`; no absolute operand in this dump points into the block | | `$C520-$C524` | 5 | `GameStateHandlerTableEntry8` | The continuation of that same inline table: the pointers `$D451` and `$E129`, plus the low byte of `$D45C`. The bytes at `$C51E` were decoded as `sbc $D5` and the bytes at `$C525` as an undocumented `nop $8B,x`, so the tracer split the table. | as above | | `$C527` | 1 | `GameStateHandlerTableEntry11` | The high byte of the twelfth and last pointer of that inline table, `$E28B`. | as above | | `$C595-$C596` | 2 | `EnemyTypeHandlerTable` | The pointer `$D47D` of the second inline table, the one that follows `jsr DispatchViaStackPointer` at `$C590`; that table runs `$C593`-`$C5A2` and holds eight pointers — `$D40B`, `$D47D`, `$D491`, `$D496`, `$D49B`, `$D4A0`, `$D4AF`, `$D448` — one per enemy slot, indexed by `Zp50` | The dispatcher pops the table address off the stack and walks it with the index in `Zp50` | | `$C599` | 1 | `EnemyTypeHandlerTable2` | The low byte of the pointer `$D496` of the same table | as above | | `$C5A2` | 1 | `EnemyTypeHandlerTable3` | The high byte of the pointer `$D448` of the same table | as above | | `$C632-$C635` | 4 | (unnamed) | code the tracer could not reach: `lda $51` / `bne $C630` — the tail of the routine at `$C62C` | none | | `$CA5A-$CA5D` | 4 | (unnamed) | code the tracer could not reach: `lda #$86` / `ldx #$F2`, the path that writes tile `$86` at PPU address `$F286` in the tile-drawing routine that starts at `$CA3B` | none | | `$D491-$D4FD` | 109 | (unnamed) | code the tracer could not reach — 51 instructions. It holds the entry points the enemy-type table points at (`$D491`, `$D496`, `$D49B`, `$D4A0`, `$D4AF`): `inc $50` / `jmp $D34A`, `inc $50` / `jmp $E14A`, `inc $50` / `jmp $D3F9`, `inc $50` / `lda #$00` / `sta $55` … / `jmp $D3A8`, and the enemy-spawn routine itself at `$D4AF` | none — the pointers into it are read out of the inline table, not with an absolute operand | | `$D62D-$D633` | 7 | (unnamed) | code the tracer could not reach: `lsr a` four times, then `sta $00,x` / `inx` | none | | `$D9B3-$D9B5` | 3 | `ScoreTextOffsetTable` | The low byte of the PPU address at which each of the three score digits is drawn (`$10`, `$30`, `$20`); the high byte is supplied by the caller | `lda $D9B3,y` at `$D9AD` -> `Zp00` | | `$DDB0-$DDB5` | 6 | `EnemyActiveFlagTable` | Four bytes (`$08`, `$16`, `$24`, `$32`) — the value written into the enemy-active flag for each of the four enemy slots; the last two bytes of the block are code (`lda #$08`) | `lda $DDB0,x` at `$DDAA` -> `jmp SetEnemyActiveFlag` | | `$E078-$E0B7` | 64 | `EnemyTileSequenceTable` | 64 tile indices in `$0A`-`$24` — the tile sequence an enemy walks through while it moves; the last sixteen entries are `$24`, the blank tile | no absolute operand in this dump points into the block, so the instruction that walks it is not established | | `$E0D0-$E128` | 89 | `EnemyScriptBlock` | The byte-coded enemy scripts. The script interpreter at `$E14A`-`$E1BE` reads the stream one byte at a time: a byte of `$AA` is a two-parameter command, a byte of `$FF` ends the script and restarts it, and any other byte is the head of a four-byte record (three parameters, copied to `ScriptParam` `$34`, `Zp32` `$32`, `$04F3` and `$04FC`). The block holds the whole 85-byte script plus three trailing bytes | `lda $E0D0,x` at `$E14F`, `$E157`, `$E15D`, `$E190`, `$E196`, `$E1A7` and `$E1AE`; the script pointer itself is `Zp31` | | `$E16C-$E16F` | 4 | (unnamed) | code the tracer could not reach: `bcc $E172` / `lda #$03` | none | | `$E18C-$E18F` | 4 | (unnamed) | code the tracer could not reach: `bne $E192` / `ldx #$41` | none | | `$E1A4-$E1A6` | 3 | (unnamed) | code the tracer could not reach: `sty $04C0` | none | | `$E45C-$E463` | 8 | `StageHandlerPointerTable` | Four 16-bit little-endian code addresses — `$E464`, `$E48A`, `$E49A`, `$E4AA` — the four stage-setup routines, indexed by the stage counter `$04B4` | the addresses are reached through `jsr DispatchViaStackPointer` at `$E459`, which the code enters with A = `$04B4` | | `$E494-$E499` | 6 | `StageSubHandlerPointerTable` | Three 16-bit little-endian code addresses — `$E51A`, `$E5CE`, `$E670` — the sub-routines a stage script can call, indexed by `$04BA` | `jsr DispatchViaStackPointer` at `$E491`, entered with A = `$04BA` | | `$E4A4-$E4A9` | 6 | `StageSubHandlerPointerTable2` | Three 16-bit little-endian code addresses — `$E554`, `$E646`, `$E6B8` — the second stage sub-routine set | `jsr DispatchViaStackPointer` at `$E4A1`, entered with A = `$04BA` | | `$E4F1-$E4FB` | 11 | (unnamed) | code the tracer could not reach: `lda $1E` / `sta $00` / `lda #$09` / `sta $01` / `jsr $DE83` / `ldx #$A4` | none | | `$E558-$E5CD` | 118 | `StageOamSetupCode` | Ten instructions of code the tracer could not reach (`lda #$2F` / `sta $1E` / `lda #$07` / `sta $00` / `ldy #$E5` / `cpy $C8`), then 102 bytes of data: six tile indices at `$E568` and the 24-byte OAM template at `$E574` | the template is fetched by `lda $E574,x` at `$E53D`; the six bytes at `$E568` have no reader in this dump | | `$E568-$E573` | 6 | `StageSpriteTileList` | Six CHR tile indices, `15 16 0A 1B 12 18`, that belong to the same stage-setup pattern set as the OAM template | no absolute operand in this dump points into it | | `$E574-$E58B` | 24 | `StageOamTemplate` | Eight three-byte OAM records, each (Y, tile, X): `40 13 20`, `40 12 28`, `48 15 20`, `48 14 28`, `50 17 20`, `50 16 28`, `78 13 41`, `78 12 41` — the eight objects the stage puts on the screen | `lda $E574,x` / `sta $0200,x` at `$E53D`/`$E540`, X = 23 down to 0 | | `$E69E-$E69F` | 2 | (unnamed) | code the tracer could not reach: `inc $05` | none | | `$E6B8-$E708` | 81 | `StageScriptSetupCode` | Seven instructions of code the tracer could not reach (`lda #$01` / `sta $1F` / `ldx #$17` / `ldy #$22` / `lda $04B6,x` / `bne $E680`), then the stage-layout tables | the tables have their own consumers, listed below | | `$E6C5-$E6E4` | 32 | `StageColumnParameterTable` | One byte per column of the stage layout (32 columns), read with the column index in Y while the stage script is written into the nametable | `lda $E6C5,y` at `$E2D1` | | `$E6E5-$E708` | 36 | `StageRowParameterTable` | Three byte arrays at `$E6E5`, `$E6E6` and `$E6E7`, one entry per row of the stage layout, read with the row index in X; a row whose entries are zero is left blank | `lda $E6E5,x` at `$E2E8`, `lda $E6E6,x` at `$E2F1`, `lda $E6E7,x` at `$E2FA` | | `$ECCC-$ECEB` | 32 | `ActorInitialStateTemplate` | The 32-byte initial state of the actor block at `$B0`-`$CF`, copied verbatim when a stage starts. The eight four-byte groups are `01 00 02 00`, `33 05 8C 23`, `00 00 00 00`, `4C F6 00 80`, `00 00 01 0A`, `02 01 00 00`, `00 00 00 00`, `00 00 04 03`; the block holds the actor's command byte (`$B1`), palette (`$B4`), tile (`$B6`) and counters, but the field order inside a group is not established | `lda $ECCC,x` at `$ECBD` / `sta ZpB0,x` (X = `$1F` down to 0) copies all 32 bytes to `ZpB0`-`ZpCF` | | `$ECF5-$ECFA` | 6 | (unnamed) | code the tracer could not reach: `jsr $DFBD` / `jmp $C75A` | none | | `$EE53-$EE69` | 23 | `SoundEffectRoutineTable` | Ten 16-bit **big-endian** addresses into the sound engine — `$F763`, `$F76D`, `$F77B`, `$F78B`, `$F79B`, `$F7A4`, `$F7B3`, `$F7C2`, `$F7CB`, `$F7DA` — in groups of four, three and three, each group closed by a `$00` | `lda $EE53,x` at `$EE22` and `$EE2B` reads the high byte and then the low byte of the address; the index is the timer script counter `$04C6` | | `$EF67-$F8A6` | 2368 | `StageSpriteLayoutTable` | The stage layout and sprite data: from `$EF67` a run of four-byte OAM records (Y, tile, attribute, X) — `77 8A 03 70`, `77 8B 03 78`, `A7 8A 03 20` and so on — that place the stage's objects on the screen, and the sub-tables below | the four-byte records are read through the stage-setup pointer, so no absolute operand points at them; the sub-tables have their own consumers | | `$F4B2-$F4C8` | 23 | `EnemyTileGroupTable` | Groups of tile indices, one group per enemy type, each group closed by `$FF`: `06 0C 00 0C FF`, `00 26 2C FF`, `06 52 54 52 56 FF`, `0A 6C 6C 6F 6F 6C 6C 72 72 FF` | `lda $F4B2,y` at `$CEA5` and `$CEB2` — `$FF` means "this enemy has no more tiles", otherwise the tile is stored to `ZpB6` and the index advanced | | `$F574-$F583` | 16 | `EnemySpawnVramAddressTable` | Eight 16-bit little-endian PPU addresses — `$212D`, `$21F7`, `$2203`, `$22AB`, `$2132`, `$21E8`, `$221C`, `$22B4` — the nametable cells at which an enemy may be placed | `cmp $F574,x` at `$CF8D` and `$CF95`, `lda $F574,x` at `$EB32` and `lda $F575,x` at `$EB3A` | | `$F68D-$F6B4` | 40 | `ScoreDigitOamTemplateTable` | Eight four-byte OAM entries, one per score digit, the last one all `$FD`/`$FE` as the terminator | `lda $F68D,y` at `$D04F` and `$D059` copies four bytes per digit into the sprite buffer at `$0591` | | `$F901` | 1 | `PulseNotePeriodEntry0Low` | The low byte of the first entry of the pulse note period table | read through the note lookup `lda $F901,y` at `$F8DB` | | `$F903` | 1 | `PulseNotePeriodRestEntry` | The low byte of the second entry of the pulse note period table; the entry is a rest, and the engine skips a note whose low period byte is zero | read through `lda $F901,y` at `$F8DB` | | `$F904-$F96A` | 103 | `PulseNotePeriodTable` | The pulse note period table, two bytes per entry: the even byte holds the period's bits 8-10 (the engine ORs `$08` into it for the length counter) and the odd byte holds bits 0-7. The first entries are `01 C4` ($1C4), `00 00` (the rest), `00 69`, `00 D4`, `00 C8`, `00 BD`, `00 B2`, `00 A8`, `00 9F`, `00 8D`, `00 85`, `00 7E`, `00 76`, `00 70`, `01 AB` … — a descending chromatic ladder, `00 69` = C6 at 1055 Hz, `00 70` = B5 at 988 Hz, `01 AB` = C4 at 261 Hz. The table continues into the lower octaves; the tail from `$F930` on does not keep the even alignment, so its entry boundaries are not established | `lda $F900,y` at `$F8E3` and `lda $F901,y` at `$F8DB`, with Y = twice the note number | | `$F94E-$F96A` | 29 | `TriangleChannelParameterTable` | Per-channel parameter bytes for the triangle voice: the engine reads eight consecutive bytes at `$F94E` plus the channel's own offset, so each channel has its own eight-byte window | `lda $F94E,y` at `$F98E`, Y = the channel's base `$068D` plus the octave; the base itself comes from the double lookup `lda $FE64,y` / `tay` / `lda $FE64,y` / `sta $068D` at `$FD41`/`$FD45`/`$FD48` | | `$F992-$F9A1` | 16 | `PulseChannelControlTable` | The APU pulse channel control bytes — duty and volume — for the sound effects: `$8C`, `$84`, `$83`, `$8D`, `$8D`, `$83`, `$83`, `$8B`, `$8C`, `$83`, `$8B`, `$8C`, `$8A`, `$8A`, `$8B`, `$8B` | `lda $F99D,y` at `$FA14` loads one as the sweep/envelope control, `ldy $F997,x` at `$FA73` loads one as the duty/volume value | | `$FB32-$FB37` | 6 | `SoundEffectNoteIndexTable` | Six bytes, `26 22 26 22 26 22` — the note indices the short "hit" effect steps through, three note/pause pairs | `lda $FB32,y` at `$FB1E`, Y = `$06F0` & 7 | | `$FB3D` | 1 | (unnamed) | a single `$8E` byte — the low half of a `stx` operand; no instruction in this dump reads it | none | | `$FE64-$FFFF` | 412 | `SoundNoteOffsetTable` | The sound engine's note data and the vectors. The first eight bytes are per-channel offsets (`$08`, `$0D`, `$12`, `$17`, `$1C`, `$21`, `$26`, `$2B`) into the note records that follow; each offset is used twice, once as the note base and once as the index of the five-byte record that holds the note's parameters. The last six bytes of the block are the NMI, RESET and IRQ vectors | `lda $FE64,y` at `$FD41` turns the offset into a pointer, and `lda $FE65,y` at `$FD4B`, `$FE66,y` at `$FD50`, `$FE67,y` at `$FD55`, `$FE68,y` at `$FD5A` read the note record | | `$FE6C-$FFEF` | 900 | `SoundNoteRecordTable` | The note records the offsets select — five bytes each (note base, period, envelope, sweep, volume) — followed by the rest of the sound engine's stream data | the records are reached through the offsets in `$FE64` | **How the structures were derived.** `DispatchViaStackPointer` at `$CD9E` pops the return address of its own `jsr` off the stack, so the table it reads always begins one byte past the last byte of that `jsr` instruction. Working the five call sites out gives the table bases `$C510`, `$C593`, `$E45C`, `$E494` and `$E4A4`, and the index register at each site is what the table is keyed on (`Zp40`, `Zp50`, `$04B4`, `$04BA`). The record size of each of them is therefore two bytes — a 16-bit handler address — and the address of each target was read off the table and matched against the routine names already in the source. The OAM templates follow from the copying loops (`lda base,x` / `sta $0200,x` with a known count), which fix both the record size and the field order. The note tables follow the same pattern as the sibling sound engines in `NROM/Hogan's Alley (JU)` and `NROM/Millipede (U)`, which share the code at `$F7xx`-`$FDxx`: the note base byte is used twice, once as the note offset and once as the record index, which fixes the record size at five bytes. The period table's contents were checked against the 2A03's own pitch formula — the entries `00 69`, `00 70` and `01 AB` are C6, B5 and C4 to within one LSB — which is what identifies it as a period table rather than an arbitrary byte run. --- ## 9. Naming and Residue * **1,025 labels**, no `Label_` residue and no `SubC` residue, and no raw `$XXXX` operand on any `jsr`, `jmp` or branch. * **6,210 instructions / 12,847 bytes = 78.4 %** of the PRG is code; the other 3,522 bytes are data. * Every instruction carries the canonical audit comment `; CPU:$C123 PRG:0123 AD 21 03`, and every one of them matches `prg.bin` byte for byte. * The routine names came from reading this dump's own code. The internal branch targets are derived from the branch that reaches them (`beq` → `OnZero`, `bne` → `OnNonzero`, a backwards branch → `Loop`) and are prefixed with the owning routine, e.g. `UpdateGameplay_OnNonzero`. * The zero-page slots that keep their neutral `ZpXX` name are listed with the reason in `zp_aliases.inc`. ### The Pass 1 correction The static trace alone classified only 72.0 % of the PRG as code. A Mesen Code Data Logger capture was taken of this ROM (`mario_mesen.cdl`, 10,915 code bytes) and `_tools/cdl_reclass.py` rewrote the split to match what the CPU actually fetched: **1,067 bytes moved from data to code** (1,010 from the capture and 57 more forced with `--override` because two branch targets — `$E4CB` and `$E639` — are code the capture never reached) and 22 moved the other way. The three branch targets the tool could not prove were turned into labels by `_tools/resolve_raw_branches.py`. The biggest single win is `$D508`–`$D56D`, a hundred bytes the tracer had left as data that decode as a coherent routine, and `$CA3B`–`$CA5D`, which is the tile-drawing routine. --- ## 10. Promotion to `NROM/` (2026-10-20) * `git mv` from `_wip/NROM/` to `NROM/`. * `header.s` and `chr.s` added; `linker.cfg` now links the HEADER, PRG and CHR areas, so the whole ROM is compared against `rom.nes`. * `rom.nes` is a symlink to `Mario Bros (JU).nes`. * `make` prints `Byte-identical ROM verified.` — the header and the 8 KB of CHR are proven, not just the PRG. There is deliberately **no `make regen` target** here. The Pass 1 split in this project is the static trace *corrected* by a Code Data Logger capture, and the base the correction was applied to is not what the current `_tools/nesdis.py` produces, so a regen pipeline could not reproduce `prg.s` byte for byte. `prg.s` is the maintained source of truth; the tools that produced it are named above. ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry. **RESET** is at `$C000` (`Reset`). The CPU comes here out of reset with the PPU and APU in their power-on state and the zero page and RAM uninitialised. The reset path calls: | Address | Routine | |---------|---------| | `$C002` | `WaitVblank1` (falls through) | | `$C01D` | `ClearRamPageLoop` (falls through) | | `$C02B` | `ClearRamPageLoop_Loop` (falls through) | | `$CA1B` | `InitVramAddress` | | `$CA2B` | `SetVramAddressF400` | | `$C04F` | `ClearFramePending` (falls through) | | `$CA22` | `SetVramAddress2400B` | **NMI** is at `$C07D` (`NmiHandler`). It is the per-frame handler; what it calls: | Address | Routine | |---------|---------| | `$CB58` | `UpdateOamShadow` | | `$EE6A` | `DrawStageText` | | `$CCFF` | `DrawHudRow` | | `$CA66` | `DrawTileRow2` | | `$CE09` | `SetScroll` | | `$CCC5` | `ReadControllerPort` | | `$CAF7` | `UpdateEnemyScroll` | **IRQ** is at `$C000` (`Reset`) - the same address as RESET, so the IRQ vector reaches the reset path.