# Clu Clu Land (JU) — Game Flow from Power-On This document describes the execution flow of the disassembled Clu Clu Land (JU) PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `copilot/prg.s` in the repository. The rebuild is verified **byte-identical** to the original `prg.bin`. --- ## 1. ROM and Memory Layout | Property | Value | |----------|-------| | Format | iNES / NES 2.0 | | PRG | 1 × 16 KB (mapped at CPU $C000–$FFFF) | | CHR | 1 × 8 KB | | Mirroring | Horizontal | | PRG bank start | CPU $C000 | | Source file | `copilot/prg.s` (9,150 lines) | | Output | `reconstructed.nes` (whole ROM: 16-byte header + 16,384-byte PRG + 8,192-byte CHR) | ### Interrupt vectors (CPU $FFFA–$FFFF) | Vector | Address | Handler | |--------|---------|---------| | NMI | $C1F1 | `NMIHandler` | | RESET | $C196 | `ResetEntry` | | IRQ / BRK | $C196 | `IRQHandler` (reuses `ResetEntry`) | --- ## 2. Build Status `make` in `copilot/` assembles `prg.s` with `ca65`, links with `ld65`, and verifies against `prg.bin`. **Verification result: BYTE-IDENTICAL** — `reconstructed.nes` matches `rom.nes` exactly (header + PRG + CHR). **Pass 3 status.** The routine names are still the address-derived `ObjectPath_*` / `RunGameTaskRoutine_*` / `CorePath*` family produced by the automated passes, so the *code* is not semantically named yet. The **data** side is done: every data block in the PRG has been interpreted, given a ROM-specific name where the evidence supports one, and recorded in the **Pass 3 data-table inventory** below with the instruction that reads it. --- ## 3. Reset — CPU $FFFC → $C196 **`ResetEntry`** ($C196) On power-on or reset: 1. `CLD` — clear decimal mode. 2. `LDA #$00 / STA PPUCTRL` — disable PPU rendering/NMI. 3. `SEI` — disable maskable interrupts. 4. `LDX #$FF / TXS` — initialize stack pointer to $FF. 5. **Wait for two VBlanks** — `InterruptPathLoop_C1A2` (`LDX #$02`, then poll `PPUSTATUS` bit 7 twice via `DEX`/`BNE`). This is the PPU warm-up delay. 6. **Clear RAM** — `LDY #$24` (or `$18` if `StartupPreserveFlag0`/$48 or `StartupPreserveFlag1`/$49 is nonzero), then `STA $0000,Y` in a loop clearing zero page + one page. The Y start value ($24 vs $18) is selected by the two preserve flags. 7. `JSR RunGameTaskRoutine_CBA6` ($C1C2) — run an initial game task routine with Y=$07, X=$06. 8. `LDA #$55 / STA FrameShiftRegister0` ($10) — initialize frame shift register 0. 9. `LDA #$12 / STA PPUCTRL / STA ShadowPPUCTRL` ($40) — set PPU control to $12 (NMI enabled, $2000 base) and shadow. 10. `LDA #$06 / STA PPUMASK / STA ShadowPPUMASK` ($41) — enable background + sprites, set mask shadow. 11. `LDA #$0F / STA APUSTATUS / STA $0100` — enable all four APU channels, write to $0100. 12. `JSR RunGameTaskRoutine_C3E4` ($C1DF) — run the C3E4 game task routine. 13. `LDA ShadowPPUCTRL / ORA #$80 / STA ShadowPPUCTRL / STA PPUCTRL` — set NMI enable bit in shadow and PPUCTRL (enables NMI). 14. Falls into **`InterruptPathDispatch_C1EB`** ($C1EB) — the permanent main loop. --- ## 4. Main Loop — `InterruptPathDispatch_C1EB` ($C1EB) Once NMI is enabled, ResetEntry never returns. It enters an infinite loop: ``` InterruptPathDispatch_C1EB ($C1EB): JSR EnginePath_F58D ; $C1EB: 20 8D F5 - frame/input processing JMP InterruptPathDispatch_C1EB ; $C1EE: 4C EB C1 - loop forever ``` The real-time work happens in the **NMI** (see section 5). The main loop only repeatedly calls `EnginePath_F58D` and spins. `EnginePath_F58D` ($F58D) shifts the frame shift registers ($10–$17) right based on controller input bits ($10/$11 bit 2), producing a carry that drives rotation. --- ## 5. NMI / Frame Heartbeat — `NMIHandler` ($C1F1) The NMI fires once per frame and is the central real-time driver. It: 1. **Save A** (`PHA`) and **save WorkPtr0Lo** ($00) onto the stack. 2. **PPU control**: `LDA ShadowPPUCTRL ($40) / AND #$7F` (clear NMI bit), store back to shadow and $2000 — this **temporarily disables NMI** during the handler to prevent re-entry. 3. **PPU mask**: `LDA ShadowPPUMASK ($41) / AND #$E7` (clear sprite/background enable bits), store to shadow and $2001 — rendering off during updates. 4. **Game state update**: `JSR UpdateGameStateRoutine_C996` ($C207) — core per-frame game state logic. 5. **OAM DMA**: write $00 to OAMADDR ($2003), then $02 to OAMDMA ($4014) — DMA $0200–$02FF to sprite RAM. 6. **Resource/level data**: `JSR ResourceAndLevelData` ($F20B) — PPU command buffer processing / nametable updates. 7. **Object paths**: `JSR ObjectPath_E113` ($E113), then `JSR ObjectPath_DDC1` ($DDC1) — object movement/collision. 8. **Restore PPU**: write ShadowPPUCTRL ($40) to $2000; `LDA ShadowPPUMASK / ORA #$18` (re-enable background + sprites), store to shadow and $2001 — rendering back on. 9. **Audio driver**: `JSR AudioDriverCode` ($F7C1) — process all audio channels. 10. **Engine path**: `JSR EnginePath_F5AE` ($F5AE) — controller strobing + input processing (`ProcessResourceStateRoutine_F5BE`). 11. **Object path**: `JSR ObjectPath_DDC1` ($DDC1) again. 12. **Game task**: `JSR RunGameTaskRoutine_C368` ($C368) — main game task dispatcher. 13. **PPU status poll**: `NMIReadPPUStatus_C234` — read PPUSTATUS (reset address latch). 14. **Re-enable NMI**: `LDA ShadowPPUCTRL / ORA #$80 / STA ShadowPPUCTRL / STA PPUCTRL`. 15. **Restore**: `PLA / STA WorkPtr0Lo ($00)`, `PLA`, `RTI`. ### AudioDriverCode ($F7C1) Called from NMI. Processes four audio state routines in sequence: - `ProcessAudioStateRoutine_F898` - `ProcessAudioStateRoutine_FA2E` - `ProcessAudioStateRoutine_FACF` - `ProcessAudioStateRoutine_FB22` Then clears audio request bytes $FB–$FF, handles the noise channel ($07FE, $4011), and returns. --- ## 6. Game Task Dispatcher — `RunGameTaskRoutine_C368` ($C368) Called each NMI frame. This is the primary gameplay dispatcher with multiple entry points ($C368, $C3BC, $C3E4, $C402, $C463, $C47A, etc.). It branches on game state variables ($92, $B0, $8A, $8D, $8C, etc.) to: - `ProcessResourceStateRoutine_F577` — resource processing - `CoreGameCode` ($C245) — the large central object-processing routine (see section 7) - `RunGameTaskRoutine_C6BE`, `C7EF`, `C7F3`, `C809`, `C816`, `C81A`, `C824`, `C830`, `C84B` — various sub-tasks - `ProcessResourceStateRoutine_F40C / F408 / F31A / F77D` — resource/command buffer updates The dispatcher uses a dense network of `CorePath*` / `UpdateGameState*` / `UpdatePlayerState*` / `RunGameTaskDispatch_*` branch targets driven by zero-page state ($8A–$8F, $90–$93, $A7, $AC–$AF, $B0, etc.). --- ## 7. Core Object Processing — `CoreGameCode` ($C245) The largest single routine in the bank. Called from the task dispatcher. It processes objects in two main phases: **Phase 1** ($C245–$C264): - `JSR RunGameTaskRoutine_CBB9` - `JSR ObjectPath_D34E` - `JSR UpdateObjectRecord_D27D` - Object record reads from `CurrentObjectPtrLo/Hi` ($08/$09): `ReadObjectRecord_DADA`, `ReadObjectRecord_CD7B`, `ReadObjectRecord_D11E` **Phase 2** ($C264–$C2C2): - `JSR ObjectPath_D06D`, `RunGameTaskRoutine_CBC6`, `ObjectPath_D34E`, `UpdateObjectRecord_D27D` - Second set of object record reads - Long chain of object buffer/update routines: `ObjectPath_D08F`, `ObjectPath_DD10`, `ObjectPath_DC5A`, `UpdateObjectBuffer_DB52`, `ObjectPath_D856`, `ObjectPath_D8BA`, `UpdateObjectBuffer_D64F`, `ObjectPath_D97B`, `UpdateObjectBuffer_D0C8`, `ObjectPath_CFA1`, `ObjectPath_D0B1`, `ProcessObjectStateRoutine_E6F3`, `ObjectPath_DA91`, `UpdateObjectBuffer_DBC7`, `ObjectPath_D0DF`, `ObjectPath_D1D6`, `UpdateObjectBuffer_DD8F`, `UpdateObjectBuffer_D0F6`, `UpdateGameStateRoutine_C47A`, `ObjectPath_D209`, then `RTS`. **Object loop** (`CorePathLoop_C2C3` at $C2C3): - `JSR RunGameTaskRoutine_C84B` - Clears $0080–$009F (32 bytes) via indexed store - `RoundOrSubstate` ($88) = 0 - Player/game state initialization based on `ContextIndex` ($4A) --- ## 8. PPU Command / Nametable Processing — `ResourceAndLevelData` ($F20B) Called from NMI. Processes an embedded command stream at $0301: - Reads PPU address high/low from $0301,Y - Writes a counted block of bytes to $2007 (PPUDATA) - Handles $3F-prefixed commands (palette) specially — re-writes PPUADDR - This is the nametable/palette update pipeline driven during VBlank. Supporting PPU helpers: - `SetDefaultScroll` ($FFEB) — writes $08, $00 to PPUSCROLL - `LoadPalette` style routines via `ProcessResourceStateRoutine_F40C/F408/F31A/F77D` - `UpdateCommandBufferRoutine_F3A1` --- ## 9. Object System Object processing is spread across many `ObjectPath_*` and `UpdateObjectBuffer_*` / `UpdateObjectRecord_*` / `ReadObjectRecord_*` / `ProcessObjectState*` routines in the $C000–$EFFF range: | Group | Address Range | Purpose | |-------|---------------|---------| | ObjectPath_D06D / D08F / D0B1 / D0DF / D1D6 / D209 | $Cxxx | Object coordinate/state update | | ObjectPath_D34E | $CD3E | Object processing core | | ObjectPath_D856 / D8BA / D97B | $CDxx | Object buffer/rendering | | ObjectPath_DC5A / DD10 / DDC1 | $CDxx–$DDxx | Object collision/movement | | ObjectPath_E113 | $E113 | Object entry (called from NMI) | | UpdateObjectRecord_D27D | $CD27D | Object record maintenance | | ReadObjectRecord_DADA / CD7B / D11E | $CDxx–$DAxx | Object record reading | | ProcessObjectStateRoutine_E6F3 / D1CE / D211 / D42C / D44A / D94D / DA26 | $CDxx–$E6xx | Object state machine dispatch | | UpdateObjectBuffer_DB52 / D64F / DBC7 / DD8F / D0F6 | $CDxx–$DDxx | OAM buffer writes | | ProcessCurrentObjectRoutine_CBE3 | $CBE3 | Current object processing | Objects use `CurrentObjectPtrLo/Hi` ($08/$09) and `CurrentObjectIndex` ($0A) for indirect access. Object state arrays live in zero page ($90–$AF, $D0–$D8) and RAM ($0400–$07FF). --- ## 10. Zero-Page / RAM Symbols | Address | Symbol | Use | |---------|--------|-----| | $00–$01 | WorkPtr0Lo/Hi | General indirect pointer (also saved across NMI) | | $02–$03 | WorkPtr1Lo/Hi | Secondary pointer | | $04–$05 | DataPtrLo/Hi | Data pointer | | $06–$07 | ListPtrLo/Hi | List pointer | | $08–$09 | CurrentObjectPtrLo/Hi | Current object pointer | | $0A | CurrentObjectIndex | Current object index | | $0B–$0C | AlternateObjectPtrLo/Hi | Alternate object pointer | | $0D–$0E | TempDirection / TempClass | Temporary direction/class | | $0F | LoopCounter | Loop counter | | $10–$17 | FrameShiftRegister0–7 | Frame shift registers (input/frame state) | | $20–$23 | CollisionRecordA0–A3 | Collision records | | $24–$27 | VectorComponent0–3 | Vector components | | $2C–$2F | TempField2C–2F | Temporary fields | | $33–$34 | TempField33/34 | Temporary fields | | $3F | TempField3F | Temporary field | | $40 | ShadowPPUCTRL | $2000 mirror | | $41 | ShadowPPUMASK | $2001 mirror | | $42–$43 | ScrollOrRenderState0/1 | Scroll/render state | | $44–$47 | ControllerButtonsBase/AltBase | Controller state ($44,$46 indexed) | | $48–$49 | StartupPreserveFlag0/1 | Startup preserve flags | | $4A | ContextIndex | Context index | | $4B | ModeToggleFlag | Mode toggle | | $4C | ActiveContext | Active context | | $4D | PendingStateIndex | Pending state index | | $4E | StateTransitionFlag | State transition flag | | $4F | InputLockFlag | Input lock flag | | $50 | GlobalTickDivider | Global tick divider | | $51–$68 | TimerArrayBase / GameVar* | Timers and gameplay variables | | $75–$7D | PlayerTimerBase / PlayerTimer1/2 / StateTimer / InputRepeatDelay | Player timers | | $80–$8F | MatchState80–StateCounter8F | Match/game state (RoundOrSubstate=$88, GameState=$8A, GameSubstate=$8B) | | $90–$93 | PlayerStateBase / PlayerState1 / CountdownValue / CountdownDivider | Player state | | $9F | ObjectStateArray9F | Object state array | | $A0–$A9 | StateVarA0–A9 | State variables | | $AC–$AF | StateVarAC–AF | State variables | | $B0 | ToggleStateB0 | Toggle state | | $B2 | StateVarB2 | State variable | | $D0–$D8 | ObjectVarD0–D8 | Object variables | | $DA | ObjectVarDA | Object variable | | $E9–$EF | ObjectVarE9–EF | Object variables | | $F0–$F3 | AudioPulse1State–AudioNoiseState | Audio channel states | | $F4 | AudioPeriodOrEnvelope | Audio period/envelope | | $F5–$F6 | AudioSequencePtrLo/Hi | Audio sequence pointer | | $F7 | AudioSequenceCursor | Audio sequence cursor | | $F8–$F9 | AudioChannelCursor/Index | Audio channel state | | $FA | AudioSequenceState | Audio sequence state | | $FB–$FF | AudioRequest0–4 | Audio request flags | --- ## 11. Audio System **AudioDriverCode** ($F7C1) — NMI-called audio driver: - Strobes JOY2 ($4017 = $FF) to select noise mode - Calls four channel processors: $F898, $FA2E, $FACF, $FB22 (pulse 1, pulse 2, triangle, noise) - Clears audio request flags $FB–$FF - Noise channel: reads $07FE, applies envelope from AudioNoiseState ($F3), writes to $4011 Audio workspace ($F0–$FF): channel states, sequence pointers/cursors, request flags. Audio data tables (`AudioDataTables` at $F441 / $FF34): pitch/envelope lookup tables at $FF34–$FF40 and $FF51–$FFF0. --- ## 12. Data Regions The full, interpreted inventory of every data block in the PRG is in **Pass 3 data-table inventory** below; this table is only the summary. | Label | Address | Description | |-------|---------|-------------| | `DeveloperCreditText` | $C000 | ASCII "T.SAWANO" credit string | | `ScreenNametableStreamPointerTable` | $C008 | 30 pointers to the per-screen nametable update streams ($E75D–$F11D) | | `ScreenModeFlagTable` | $C044 | one flag byte per screen (30 entries) | | `HudMarkerOamTemplate` | $C0A1 | four 4-byte OAM records for the HUD markers | | `ObjectMoveDeltaLowTable` / `ObjectMoveDeltaHighTable` | $C0B1 / $C0B9 | low/high bytes of the 16-bit per-direction movement delta | | `ObjectStageTimerLimitTable` | $C0C1 | per-stage timer threshold | | `ObjectStageSpeedTable` | $C0C9 | per-stage speed value | | `ObjectSpawnCountTable` | $C0D1 | per-stage spawn count | | `ObjectStageCountTable` / `ObjectStageValueTable` | $C0D5 / $C0DD | per-stage count and value written into the object record | | `FieldColumnPositionTable` | $C8EB | 8 + 8 screen column positions | | `ObjectMoveStepTable` … `ObjectSlotIndexByDirectionTable` | $CC2C–$CCA7 | the movement tables of the object engine | | `NametableTileGroupTable` | $DE95 | 16 groups of 4 nametable tile indices | | `AudioNotePeriodTable` / `AudioNoteParameterTable` | $F81F / $F82B | note → period and note → parameter maps | | VectorTable | $FFFA–$FFFF | NMI=$C1F1, RESET=$C196, IRQ=$C196 | --- ## Pass 3 data-table inventory Every `.byte`/`.word` region in `prg.s` is listed here with what its bytes are and the instruction in this dump that reads it. "Unreferenced" means no operand of any instruction in the dump lands inside the block; such a block is either data nothing in this ROM reads, or it is carried for the game's own reasons and is recorded here with its ROM provenance. A block whose record layout is not established says so in the *Structure* column rather than being given a name it has not earned. Blocks whose reader is an **indexed** operand (`lda $XXXX,x`, `lda $XXXX,y`) are listed with the base address the instruction carries. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C000-$C007` | 8 B | `DeveloperCreditText` | ASCII `"T.SAWANO"` (8 bytes, no terminator). Unreferenced: no instruction in the dump reads it; the credit is not drawn from this copy. | – | | `$C008-$C043` | 60 B | `ScreenNametableStreamPointerTable` | 30 little-endian 16-bit pointers to the per-screen nametable update streams: `$E75D, $E79A, $E7E1, $E826, $E869, $E8A6, $E8DF, $E916, $E949, $E988, $E9CD, $EA10, $EA4F, $EA96, $EAE7, $EB32, $EB7B, $EBB0, $EBE7, $EC1E, $EC56, $ECF3, $ED1E, $ED5E, $EDAF, $EE27, $EE90, $EF91, $F117, $F11D`. One entry per screen; the pointer table and the flag table below are parallel. | `lda $C008,x` / `lda $C009,x` at `$C8AE`/`$C8B3` (X = `$85`×2) and at `$C9E1`/`$C9E6` (X = `$86`×2); the pointer is then walked by `ObjectPath_DF2F` at `$DF2F` | | `$C044-$C061` | 30 B | `ScreenModeFlagTable` | one byte per screen, 30 entries, parallel to the pointer table above (`$86` indexes both). The entry is copied verbatim into `$3F` and then tested for zero, so it is a flag, not a count: entries 21 (`$C059`) and 24 (`$C05C`) are `$00` and the rest are non-zero. The per-screen meaning of each flag is not established. | `lda $C044,y` at `$CA4F` (Y = `$86`) → `sta $3F`; tested at `$C53B` (`LDA $3F` / `BNE`) and at `$CE1B` (`LDA $3F` / `BNE`) | | `$C062-$C0A0` | 63 B | `UnreferencedData_C062` | Unreferenced data (ROM provenance PRG `$0062-$00A0`). The bytes fall into 4-byte groups that look like sprite descriptors — e.g. `$28,$EF,$06,$00`, `$2A,$AF,$06,$00`, `$20,$54,$03,$00`, `$29,$B6,$02,$00` — i.e. the NES OAM record layout (Y, tile, attribute, X); the group that ends the run is `$23,$06,$06,$00`, `$23,$13,$06,$00`. The record layout is not established because no instruction in this dump reads the block. | – | | `$C0A1-$C0B0` | 16 B | `HudMarkerOamTemplate` | four 4-byte NES OAM records (Y, tile, attribute, X): `(0F,E4,00,18)`, `(0F,E5,00,28)`, `(0F,E4,01,D0)`, `(0F,E5,01,E0)`. The two pairs are the left (`X=$18/$28`) and right (`X=$D0/$E0`) HUD markers, each two tiles wide. | `lda $C0A1,y` at `$C8E1` — the 16 bytes are copied **in reverse** into the OAM shadow at `$02E0-$02EF` (Y counts `$0F` down to `$00`) | | `$C0B1-$C0B8` | 8 B | `ObjectMoveDeltaLowTable` | low byte of a 16-bit movement delta, one entry per direction (8 entries; the code uses indices 0, 3, 4 and 7). Values `$FF,$FF,$FF,$FF,$00,$00,$00,$01`. | `lda $C0B1,x` at `$D7FD` (added to the object's low coordinate byte); the same bytes are the low half of the 16-bit subtractand in `sbc $C0B5,x` at `$D61F` | | `$C0B9-$C0C0` | 8 B | `ObjectMoveDeltaHighTable` | high byte of the same 16-bit movement delta, one entry per direction: `$A0,$80,$40,$00,$60,$80,$C0,$00`. | `lda $C0B9,x` at `$D7F5` (added to the object's high coordinate byte) | | `$C0C1-$C0C8` | 8 B | `ObjectStageTimerLimitTable` | 8 per-stage threshold bytes, indexed by the stage/player index in `$82`: `$64,$3C,$2C,$1C,$44,$3C,$34,$2C`. The code compares `table[X]-$05` against the object's countdown field. | `lda $C0C1,x` at `$D529` and at `$D55F` | | `$C0C9-$C0D0` | 8 B | `ObjectStageSpeedTable` | 8 per-stage bytes, indexed by `$82`: `$80,$50,$30,$10,$40,$30,$20,$10`. Written straight into object field `$05`. The field's meaning (a speed, a height) is not established. | `lda $C0C9,x` at `$D4FE` | | `$C0D1-$C0D4` | 4 B | `ObjectSpawnCountTable` | 4 counts, indexed by `$83`: `$02,$02,$04,$04`. The count is the loop bound of the object-spawn loop at `$C8CA`. | `lda $C0D1,x` at `$C8BD` → `sta $0F` | | `$C0D5-$C0DC` | 8 B | `ObjectStageCountTable` | 8 per-stage bytes, indexed by `$82`: `$01,$01,$01,$01,$01,$02,$02,$02`; written into object field `$E16F[X]-$01`. | `lda $C0D5,x` at `$D2A8` | | `$C0DD-$C0E4` | 8 B | `ObjectStageValueTable` | 8 per-stage bytes, indexed by `$82`: `$00,$80,$B8,$D8,$F4,$14,$34,$54`; written into object field `$E16F[X]`. | `lda $C0DD,x` at `$D2A2` | | `$C0E5-$C0E8` | 4 B | `UnreferencedData_C0E5` | Unreferenced data (ROM provenance PRG `$00E5-$00E8`): `$40,$02,$10,$00`. | – | | `$C0E9-$C195` | 173 B | `UnreferencedData_C0E9` | Unreferenced data (ROM provenance PRG `$00E9-$0195`). The bytes fall into a repeating 16-byte shape whose 4th–5th byte is a 16-bit value (`$0022`, `$0110`, `$0068`, `$007C`, `$00F6`, `$00D0`, `$00C0`, `$0040`, `$0070`, `$0013`), so it looks like a list of fixed-size object template records; the record size and field meanings are not established because no instruction in this dump reads the block. | – | | `$C51B-$C52A` | 16 B | `UnreferencedData_C51B` | Unreferenced data (ROM provenance PRG `$051B-$052A`). | – | | `$C52B-$C53A` | 16 B | `VramTransferDestinationHighTable` | 8 destination high bytes, one per queued VRAM transfer: `$29,$2B,$1D,$19,$0E,$1B,$0F,$0E`. The destination low byte of the same descriptor comes from `$FFF8`, the length is `$1E` and the source page is `$C5`. | `lda $C52B,x` at `$C7B7` → `sta $01`, then `jsr UpdateCommandBufferRoutine_F3A1` | | `$C8EB-$C8FA` | 16 B | `FieldColumnPositionTable` | 16 screen column positions in two 8-entry halves: `$1C,$34,$4C,$64,$94,$AC,$C4,$DC` and `$20,$38,$50,$68,$80,$98,$B0,$C8` (i.e. 28, 52, 76, 100, 148, 172, 196, 220 and 32, 56, 80, 104, 128, 152, 176, 200 — two fields of eight columns, the first skipping the middle of the screen). | `lda $C8EB,x` at `$C90C` and `lda $C8F3,x` at `$C916` (X = `$16`&7 and `$17`&7); the pair becomes the X and Y of the object the collision scan at `$C94D` compares against | | `$CC2C-$CC3B` | 16 B | `ObjectMoveStepTable` | 16-entry per-direction table, index `$0C`+(`type`&3) (the four diagonal directions); the entry is written into object field `$05` and field `$35`. | `lda $CC2C,x` at `$CD38` and at `$CD47` | | `$CC38-$CC47` | 16 B | `ObjectMoveLimitTable` | 16-entry per-direction table; written into object field `$08` and field `$28`. | `lda $CC38,x` at `$CD18` and at `$CD4E` | | `$CC48-$CC57` | 16 B | `ObjectMoveBoundaryTable` | 16-entry per-direction table; written into object field `$38`. | `lda $CC48,x` at `$CD3F` | | `$CC58-$CC67` | 16 B | `ObjectVelocityStepXTable` | 16-entry per-direction table; **added** to the object's X byte (field `$01`) and the sum stored in field `$21` — the X velocity accumulator. | `lda $CC58,x` at `$CCFA` | | `$CC68-$CC77` | 16 B | `ObjectVelocityStepYTable` | 16-entry per-direction table; added to the object's Y byte (field `$03`), sum stored in field `$23`. | `lda $CC68,x` at `$CD06` | | `$CC78-$CC87` | 16 B | `ObjectSpeedXTable` | 16-entry per-direction table; stored verbatim in object field `$25`. | `lda $CC78,x` at `$CD11` | | `$CC84-$CC93` | 16 B | `ObjectAccelerationXTable` | 16-entry per-direction table; added to the object's X byte, sum stored in field `$31`. | `lda $CC84,x` at `$CD21` | | `$CC94-$CCA3` | 16 B | `ObjectAccelerationYTable` | 16-entry per-direction table; added to the object's Y byte, sum stored in field `$33`. | `lda $CC94,x` at `$CD2D` | | `$CCA4-$CCA7` | 4 B | `ObjectSlotIndexByDirectionTable` | 4 slot indices `$04,$05,$08,$09`, chosen by the index of the lowest set bit of the object's direction mask. | `ldx $CCA4,y` at `$CD68` | | `$CD71-$CD76` | 6 B | `ObjectSpawnParameterTable` | 6 bytes, `$21,$24,$24,$12,$24,$24`, in two 3-byte groups; the two pointers below address `$CD71` and `$CD74`. | – (reached through the pointer table at `$CD77`) | | `$CD77-$CD7A` | 4 B | `ObjectSpawnParameterPointerTable` | two 16-bit pointers `$CD71` and `$CD74` (low bytes `$71,$74` at `$CD77`, high bytes `$CD,$CD` at `$CD79`), indexed by the direction in `$0D`. | `lda $CD77,y` at `$CDF5` and `lda $CD79,y` at `$CDFA` → the pair becomes `$02`/`$03`, the source pointer for `UpdateCommandBufferRoutine_F3A1` | | `$CE44-$CE4D` | 10 B | `ObjectLayoutOffsetTable` | two 5-byte records (`$21,$00,$00,$10,$08` and `$12,$00,$00,$08,$10`): the first byte of each is copied to `$04`+`$0A`, the next four to `$04`+`$0C..$0F`. | `lda $CE44,x` at `$CED0` and in the copy loop at `$CED8` | | `$CE4E-$CE4F` | 2 B | `ObjectSpeedScaleTable` | 2 bytes `$40,$80`. | `lda $CE4E,y` at `$CEFF` | | `$CF28-$CF33` | 12 B | `UnreferencedData_CF28` | Unreferenced data (ROM provenance PRG `$0F28-$0F33`). | – | | `$CF34-$CF37` | 4 B | `ObjectDriftDeltaXTable` | 4 per-direction low bytes `$02,$FD,$02,$FD`. | `lda $CF34,x` at `$D02C` | | `$CF38-$CF3B` | 4 B | `ObjectDriftDeltaYTable` | 4 per-direction high bytes `$60,$A0,$60,$A0`. | `lda $CF38,x` at `$D024` | | `$D10D-$D111` | 5 B | `ObjectSizeClassBoundaryTable` | 4 ascending boundary values `$02,$03,$07,$0E` (+ one spare byte). The scan at `$D158` finds the first boundary that is not below the object's size, i.e. the table maps a size to one of four classes. | `cmp $D10D,y` at `$D158` | | `$D112-$D11D` | 12 B | `ObjectSizeClassParameterTable` | 12 bytes, one per size class: `$0F,$11,$0A,$0B,$0B,$0C,$11,$0D,$0E,$0F,$0F,$10`. | `lda $D112,x` at `$D175` | | `$D184-$D1AD` | 42 B | `ObjectAttributePointerTable` | 21 little-endian 16-bit values, indexed by `A`×2: `$1627,$1A27,$2627,$2A27,$1717,$1717,$1725,$0505,$252A,$0A0A,$2A21,$0101,$2123,$0303,$2327,$0707,$2725,$0505,$2521,$1111,$1121,$210F`. The value becomes a 16-bit pointer in `$EE`/`$EF`. | `lda $D184,y` at `$D1B7` and at `$D1BD` | | `$D1F5-$D208` | 20 B | `AsciiTileIndexTable` | 20 bytes: four 5-byte groups, each four consecutive ASCII codes followed by a `$22` separator — `$50-$53` (`PQRS`), `$54-$57` (`TUVW`), `$58-$5B` (`XYZ[`), `$5C-$5F` (`\]^_`). Inside the four-letter runs the ASCII code is also the tile index. Unreferenced. | – | | `$D255-$D25C` | 8 B | `UnreferencedData_D255` | Unreferenced data (ROM provenance PRG `$1255-$125C`). | – | | `$D25D-$D25E` | 2 B | `ObjectSpawnSlotTable` | 2 bytes `$00,$01`. | `ldx $D25D,y` at `$D261`, at `$D2BD` and at `$DCFF` | | `$D31F-$D32A` | 12 B | `ObjectSpawnTimerTable` | 12 bytes `$10,$D0,$12,$13,$14,$15,$17,$18,$19,$1A,$1B,$17`; the first byte is copied to `$03F3`. | `lda $D31F,x` at `$D3CA` | | `$D45C-$D469` | 14 B | `ObjectFieldOffsetTable` | 14 bytes; the four bytes at `$D46A-$D46D` are the base value and the limit of the field timer, and `$D46C`/`$D46D` are the two record-field offsets. | `ldy $D470,x` at `$D5D8`/`$D7B9`/`$D7C9` and `ldy $D472,x` at `$D5DD`/`$D7BE`/`$D7CE` | | `$D46A-$D46B` | 2 B | `ObjectStateTimerTable` | 2 bytes, the initial value of the per-object state timer `$5F`. | `lda $D46A,x` at `$D78F` | | `$D46C-$D475` | 10 B | (part of `ObjectFieldOffsetTable`) | record-field offsets `$08,$08,$10,$04,$01,$03` and `$19,$1A,$01,$03`. | `cmp $D46D,x` at `$D794`, `ldy $D474,x` at `$D48F` | | `$D551-$D554` | 4 B | `UnreferencedData_D551` | Unreferenced data (ROM provenance PRG `$1551-$1554`): `$A9,$10,$91,$08` — these are the bytes of the instruction sequence `LDA #$10 / STA $91` at `$D551`, so the tracer split an instruction boundary here; the block is left as data and no instruction reads it. | – | | `$D8B6-$D8B9` | 4 B | `ObjectSpriteSlotTable` | 4 bytes `$00,$02,$01,$03`; the entry selected by the low two bits of the object's angle. | `lda $D8B6,x` at `$D965` | | `$D979-$D97A` | 2 B | `ObjectDeltaTable` | 2 bytes `$03,$FD`, added to the object's X or Y byte. | `lda $D979,x` at `$D9F5` and at `$DA17` | | `$DACA-$DACD` | 4 B | `ObjectCoordinateDeltaXTable` | 4 bytes `$0C,$FC,$00,$00`, added to the object's X byte. | `lda $DACA,x` at `$DAFD` | | `$DACE-$DAD1` | 4 B | `ObjectCoordinateDeltaYTable` | 4 bytes `$00,$00,$0C,$FC`, added to the object's Y byte. | `lda $DACE,x` at `$DB07` | | `$DAD2-$DAD5` | 4 B | `ObjectSpeedDeltaXTable` | 4 bytes `$F0,$00,$08,$08`, added to the object's X byte. | `lda $DAD2,x` at `$DB44` | | `$DAD6-$DAD9` | 4 B | `ObjectSpeedDeltaYTable` | 4 bytes `$00,$F0,$00,$08`, added to the object's Y byte. | `lda $DAD6,x` at `$DB4B` | | `$DBC4-$DBC6` | 3 B | `ObjectTileIdTable` | 3 bytes `$A8,$B0,$B8` — the tile indices the game matches an object's sprite against. | `cmp $DBC4,x` at `$DC1F` and `lda $DBC4,x` at `$DC4D` | | `$DDF9-$DE14` | 28 B | `ObjectAttributeTable` | 28 bytes in four 7-byte groups; every group is (attribute, low byte, high byte, …) — e.g. `$28,$28,$20,$23,$00,$1F,$00` — and the code reads it as four separate byte tables at `$DDF9`, `$DDFD`, `$DE01`, `$DE05`, `$DE09`, `$DE0D`, `$DE11`, each indexed by X = 0…3, so each group is a per-direction triple (attribute / X / Y). | `lda $DDF9,x` at `$DE21`, `lda $DDFD,x` at `$DE1C`, `lda $DE01,x` at `$DE61`, `lda $DE05,x` at `$DE5A`, `lda $DE09,x` at `$DE6C`, `lda $DE0D,x` at `$DE17`, `ldy $DE11,x` at `$DE26` | | `$DE95-$DED4` | 64 B | `NametableTileGroupTable` | 16 groups of 4 nametable tile indices; group *g* is selected by the **high nibble** of the command byte in a nametable update stream (`Y = (cmd & $F0) >> 2`, i.e. `g*4`). Groups 0–7 (`$30,$35,$31,$4A` … `$42,$43,$3D,$44`) are drawn as a 2×2 block (two tiles at the address, two at address+`$20`); groups 8–15 (`$4E,$FA,$FA,$4E`, `$6E,$86,$86,$6F`, `$FC,$FC,$FC,$FC`, `$4F,$F9,$F9,$4F`, `$4E,$FB,$FB,$4E`, `$60,$7C,$7C,$61`, `$FC,$FC,$FC,$FC`, `$4F,$F9,$F9,$4F`) are drawn as a 1×4 run. The command's low nibble chooses the VRAM region: `PPUADDR` high byte = `$20` \| (cmd & `$0F`). | `lda $DE95,y` at `$DEFE`, `$DF05`, `$DF20` and `$DF27` inside the nametable-update routine at `$DED5` | | `$DF47-$DF52` | 12 B | `ObjectPatternFlagTable` | 12 bytes: a 8-entry match list `$00,$02,$40,$42,$FC,$F3,$CF,$3F` (compared against the sprite pattern) followed by a 4-entry value list `$01,$04,$10,$40` (OR-ed into the result). | `cmp $DF47,x` at `$DFD2`, `lda $DF4F,x` at `$DFD7`, `ora $DF4F,x` at `$DFE3`, `and $DF4B,x` at `$DFF2` | | `$E005-$E044` | 64 B | `ObjectHeightLookupTable` | 64 bytes, non-decreasing: `0,0,1,1,1,1,2,2,2,3,3,3,3,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,9,9,9,9,9,9,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12`. It is the inverse function "index → height" for a value in `$00-$40`. | `lda $E005,y` at `$E05A` | | `$E167-$E16E` | 8 B | (part of `ObjectFieldOffsetTable2`) | the first 8 bytes of the object field-offset block: `$01,$19,$03,$1A,$19,$01,$1A,$03`. | – | | `$E16F-$E176` | 8 B | `ObjectRecordFieldIndexTable` | 8 record-field indices `$03,$1A,$19,$01,$1A,$03,$12,$12`; the index picks the object-record field the per-stage values of `$C0DD`/`$C0D5` are written to. | `ldy $E16F,x` at `$D29D` and at `$DB95` | | `$E177-$E17A` | 4 B | (part of `ObjectFieldOffsetTable2`) | `$14,$14,$11,$11`. | – | | `$E17B-$E17E` | 4 B | `ObjectMovementFieldIndexTable` | 4 record-field indices `$13,$13,$19,$19`; the field compared against during the movement check. | `ldy $E17B,x` at `$D01F`, `$D48F` and `$E23B` | | `$E225-$E228` | 4 B | `ObjectCoordinateDeltaTable` | 4 bytes `$F8,$00,$10,$18`, added to the object's coordinate byte. | `adc $E225,x` at `$E256` | | `$E387-$E396` | 16 B | `ObjectMaskTable` | 16 bytes `$00,$00,$00,$00,$0C,$F4,$0C,$F4,$F4,$0C,$F4,$0C,$00,$00,$00,$00`. | `lda $E387,x` at `$E3C3` | | `$E397-$E3A6` | 16 B | (second half of `ObjectMaskTable`) | 16 bytes `$80,$00,$80,$00,$40,$C0,$40,$C0,$81,$80,$80,$81,$81,$80,$80,$81`. | `lda $E38F,x` at `$E3CA`, `lda $E397,x` at `$E3D0`, `lda $E39F,x` at `$E3BC` | | `$E418-$E427` | 16 B | `ObjectPatternTable` | five 16-byte tables in one 80-byte block; the first is `$00,$04,$02,$04` repeated four times. | `lda $E418,x` at `$E4F0` and at `$E50D` | | `$E428-$E467` | 64 B | (four more tables of `ObjectPatternTable`) | `$40,$40,$40,$C0,$80,$80,$80,$00,$C0,$80,$00,$00,$00,$40,$C0,$C0` / `$00,$03,$01,$02` ×4 / `$F8,$F8,$04,$10,$10,$10,$04,$F8,$10,$10,$04,$F8,$F8,$F8,$04,$10` / `$04,$FC,$F8,$FC,$04,$0C,$10,$0C,$04,$0C,$10,$0C,$04,$FC,$F8,$FC`. | `lda $E428,x` at `$E4F7`, `lda $E438,x` at `$E506`, `lda $E448,x` at `$E514`, `lda $E458,x` at `$E520` | | `$E52D-$E537` | 11 B | `ObjectAnimationTable` | 11 bytes `$02,$01,$03,$00,$02,$03,$03,$01,$01,$11,$24`; the byte at `$E532` is an animation step count. | `lda $E52D,y` at `$E55A`, `ldy $E532,x` at `$E582` | | `$E5A2-$E5A9` | 8 B | `ObjectTileTable` | two 4-byte tables: `$0F,$01` and `$04,$04,$04,$04`; the first selects the tile, the second the number of tiles drawn. | `lda $E5A2,x` at `$E5B0`, `lda $E5A6,x` at `$E5BA` | | `$E5CC-$E699` | 206 B | `ObjectSpriteLayoutTable` | 206 bytes: a run of 4-byte OAM records — `(24,24,24,24)`, `(6E,86,86,6F)`, `(24,24,24,24)`, `(34,24,24,24)`, `(24,24,70,87)`, `(88,71,24,24)`, … `(24,24,24,24)` — i.e. NES OAM records (Y, tile, attribute, X) for the static field furniture, ending with a `$00` marker. The records at `$E682` are the last group. | `lda $E682,y` at `$E733` | | `$E75D-$F20A` | 2734 B | (the nametable update streams) | 30 nametable update streams, one per screen, each a run of 2-byte records — (command byte, address low byte) — terminated by `$FF`. The command's low nibble selects the VRAM region (`PPUADDR` high = `$20` \| (cmd & `$0F`)), its high nibble selects one of the 16 tile groups in `NametableTileGroupTable`, and the address byte is the column/row position inside that region. The 30 streams are addressed by `ScreenNametableStreamPointerTable`. | `ObjectPath_DF2F` at `$DF2F` walks the stream: `lda ($00),y` at `$DF40` reads the command and `cmp #$FF` at `$DF42` ends the stream; the tiles are written by `sta PPUDATA` at `$DF01`/`$DF08`/`$DF23`/`$DF2A` | | `$F81F-$F86A` | 76 B | `AudioNotePeriodTable` + `AudioNoteParameterTable` + `AudioEnvelopeTable` | the pulse-channel note data. The **note** table is 16 entries at `$F81C-$F82B` — `$8C,$85,$7E,$77,$70,$69,$62,$5B,$54,$4D,$46,$3F,$38,$31,$2A,$23`, i.e. 140, 133, 126, … 35, a descending note-period sequence (the first three bytes are the tail of the never-executed instruction at `$F81C`, so they are data, not code). The **parameter** table is the 16 bytes at `$F82C-$F83B`: `$04,$0E,$04,$2E,$0E,$2E,$2C,$0E,$2C,$2A,$0E,$2A,$28,$2C,$28,$26`. The **envelope** table is the 16-byte groups from `$F83A` on: `$51,$92,$92,$92`, `$53,$93,$93,$93`, `$54,$94,$94,$54`, … | `cmp $F81B,y` at `$F875` searches the note table, `lda $F82B,y` at `$F87A` fetches the parameter, `lda $F83A,y` at `$F973` and `ldx $F83A,y` at `$F976` fetch the envelope | | `$FA5B-$FAB4` | 90 B | `AudioPulseEnvelopeTable` | 90 bytes in three 16-byte groups plus a tail: `$00,$54,$00,$5F,$00,$6A,$00,$71,$00,$6A,$00,$71,$00,$7E,$00,$86` (attack/decay rates), `$00,$7E,$10,$12,$12,$12,$15,$15,$15,$13,$13,$13,$13,$16,$16,$16` (envelope levels), `$16,$16,$14,$14,$14,$14,$17,$17,$17,$17,$15,$15,$15,$15,$18,$19` and `$19,$1A,$1A,$1A,$1B,$1B,$1C,$1D,$1E,$1F,$13,$15,$16,$16,$18,$18`, then `$1A,$1A,$07,$05,$05,$05,$05,$05,$06,$06,$06,$04,$04,$04,$04,$05` and `$05,$05,$09,$09,$0A,$0A,$07,$07,$08,$08`. | `lda $FA5B,y` at `$FA4B`, `lda $FA6C,y` at `$FABF`, `lda $FA9C,y` at `$FACA` | | `$FC65-$FC77` | 19 B | (head of `AudioSequenceData`) | the start of the sound-effect sequence data; the 12 bytes at `$FC65-$FC70` are `$0C,$16,$20,$1C,$24,$12,$28,$08,$07,$F7,$FF,$00` — a table of per-channel period steps. | `lda $FC65,y` … `lda $FC6A,y` at `$FB39`–`$FB52` | | `$FC78-$FEFF` | 632 B | `AudioSequenceData` | the sound-effect note/sequence data: pairs of a note index and a duration, e.g. `$05,$FD,$3E,$1B,$4E,$07,$69,$FD,$3D,$00,$CA,$FD,$6E,$00,$AD,$FE`. | `lda $FC6x,y` at `$FB39`–`$FB52` | | `$FF00-$FF07` | 8 B | `AudioPulse2Table` | 8 bytes, the second pulse channel's period data. | `lda $FF00,y` at `$F80B` | | `$FF08-$FF33` | 44 B | (more of `AudioSequenceData`) | the remainder of the note data. | – | | `$FF34-$FF40` | 13 B | `AudioNoisePeriodTable` | 13 bytes `$04,$08,$10,$20,$40,$18,$30,$05,$0A,$14,$28,$50,$1E` — two doubling sequences (4, 8, 16, 32, 64 and 5, 10, 20, 40, 80) with a wrap value each. | `lda $FF34,y` at `$FF4D` | | `$FF41-$FF50` | 16 B | (part of `AudioDataTables`) | the noise-channel envelope data. | – | | `$FF51-$FF6F` | 31 B | `AudioVoiceEnvelopeTable` | 31 bytes of envelope steps: `$10,$15,$16,$16,$16,$15,$15,$17,$17,$17,$16,$16,$18,$18,$18,$16,$16,$1A,$1A,$1A,$16,$16,$1C,$1C,$1C,$16,$16,$1E,$1E,$1E`. | – | | `$FF70-$FF8F` | 32 B | `AudioPulse1EnvelopeTable` | 32 bytes of per-step envelope values: `$0A,$0A,$0A,$0A,$08,$08,$08,$08,$08,$08,$08,$08,$09,$09,$09,$09,$09,$09,$07,$07,$07,$07,$07,$07,$07,$07,$08,$08,$08,$08,$08,$08`. | `lda $FF70,y` at `$FAF9` | | `$FF90-$FF99` | 10 B | `AudioTriangleTable` | 10 bytes `$10,$11,$1C,$1A,$18,$16,$14,$12,$11` — the triangle channel's period steps. | `lda $FF90,y` at `$FB08` | | `$FF9A-$FFB8` | 31 B | `AudioNoisePeriodTable2` | 31 bytes, monotonically increasing from `$50` to `$57` — the noise channel's period ramp. | `lda $FF9A,y` at `$FBCB` | | `$FFB9-$FFC1` | 9 B | `AudioWaveformTable` | 9 bytes `$57,$01,$01,$01,$BE,$01,$01,$01,$8E` — the duty/waveform selection bytes. | `lda $FFB9,y` at `$FA29` | | `$FFC2-$FFD0` | 15 B | `AudioDutyTable` | 15 bytes `$FE,$00,$00,$FE,$00,$00,$FE,$6B,$6B,$AA,$6F,$6F,$AE,$03,$05`. | `lda $FFC2,y` at `$F9C0` | | `$FFD1-$FFF7` | 39 B | (rest of the audio data) | the remaining audio tables: `$00,$95,$5D,$49,$9C,$63,$4F,$A2,$03,$00,$9D,$27,$A2,$84,…` (period values) and `$A0,$60,$00,$20,$A0,$60,$00,$20,$20,$20,$20,$21,$00,$4F,$0F`. | – | | `$FFF8-$FFF9` | 2 B | `VramTransferDestinationLowTable` | 2 bytes `$4F,$0F` — the destination low byte of the queued VRAM transfer whose high byte comes from `$C52B`. | `lda $FFF8,x` at `$C7B2` | | `$FFFA-$FFFF` | 6 B | `VectorTable` | NMI `$C1F1`, RESET `$C196`, IRQ `$C196`. | the CPU's own vector fetch | ### Notes on the inventory * The Pass-1 tracer's `; ---- data $XXXX-$YYYY ----` markers are gone from this source (the regions were hand-labelled), so the blocks above were recovered from the emitted `.byte`/`.word` rows and their CPU addresses, and the consumers were found by resolving every operand — literal **and** label — to an address. * The nametable update stream format was derived from the code at `$DED5`: `AND #$0F / ORA #$20 / STA PPUADDR` gives the VRAM region from the command's low nibble, and `(cmd & $F0) >> 2` gives the tile-group index into `NametableTileGroupTable`. * The four HUD sprites were identified from the OAM record layout (Y, tile, attribute, X) and from the fact that the 16 bytes are copied into the sprite shadow at `$02E0-$02EF`. * The ASCII credit at `$C000` is not the credit the game shows: the CHR tiles at those indices are not letters, and no instruction reads the string. * Blocks whose *Structure* column says "Unreferenced" or "not established" are not claimed to be understood; they are recorded, with their ROM provenance, so that the inventory covers the whole PRG. --- ## 13. Summary of Execution Flow ``` Power On / Reset │ ▼ ResetEntry ($C196) ───────────────────────────────────┐ │ CLD, disable PPU/NMI, SEI, stack=$FF │ │ wait 2× VBlank (PPU warm-up) │ │ clear RAM (zero page + one page) │ │ JSR RunGameTaskRoutine_CBA6 (init task) │ │ FrameShiftRegister0=$55, PPUCTRL=$12 (NMI on) │ │ PPUMASK=$06 (bg+sprites on), APU=$0F │ │ JSR RunGameTaskRoutine_C3E4 │ │ enable NMI bit in PPUCTRL │ ▼ │ InterruptPathDispatch_C1EB ($C1EB) ───────────────────┤ │ │ ├─ JSR EnginePath_F58D (frame shift regs) │ └─ JMP InterruptPathDispatch_C1EB ◄─────────────┘ (spin forever) │ NMI ($C1F1) [once per frame] │ │ │ ├─ save A, WorkPtr0Lo ($00) │ ├─ PPUCTRL = shadow & #$7F (NMI off during H) │ ├─ PPUMASK = shadow & #$E7 (rendering off) │ ├─ JSR UpdateGameStateRoutine_C996 ($C996) │ ├─ OAM DMA ($0200–$02FF → $4014) │ ├─ JSR ResourceAndLevelData ($F20B) [PPU cmds] │ ├─ JSR ObjectPath_E113 ($E113) │ ├─ JSR ObjectPath_DDC1 ($DDC1) │ ├─ PPUCTRL = shadow; PPUMASK = shadow | #$18 │ │ (rendering back on) │ ├─ JSR AudioDriverCode ($F7C1) [4 channels] │ ├─ JSR EnginePath_F5AE ($F5AE) [controller I/O]│ ├─ JSR ObjectPath_DDC1 ($DDC1) [again] │ ├─ JSR RunGameTaskRoutine_C368 ($C368) [game task]│ │ │ ├─ read PPUSTATUS │ ├─ PPUCTRL = shadow | #$80 (re-enable NMI) │ ├─ restore A, WorkPtr0Lo ($00) │ └─ RTI │ Game Task Dispatcher ($C368) [from NMI] ├─ branches on $92,$B0,$8A,$8D,$8C,$8F,... │ ├─ CoreGameCode ($C245) — main object processor │ ├─ many RunGameTaskRoutine_* / ProcessResource* │ └─ returns to NMI (which continues to RTI) ``` --- ## 14. Notes - **Build verification:** `make` in `copilot/` produces `reconstructed.nes`, compared whole-ROM against `rom.nes`. - Vectors confirm NMI at $C1F1 and both RESET/IRQ at $C196 (IRQ reuses the reset handler). - The main loop (`InterruptPathDispatch_C1EB`) is a tight infinite loop calling only `EnginePath_F58D`; all substantive per-frame work is done in the NMI handler. - NMI temporarily disables itself (`AND #$7F` on PPUCTRL) and rendering (`AND #$E7` on PPUMASK) while it updates PPU state and objects, then re-enables both before RTI — a classic NES VBlank update pattern. - Many routine labels carry address-based names (`ObjectPath_D34E`, `RunGameTaskRoutine_C368`, `ProcessObjectStateRoutine_E6F3`, etc.) reflecting the automated first-pass disassembly; the **routines** are not semantically named yet. The **data blocks** are: see the Pass 3 data-table inventory. - The disassembly includes extensive inline `.byte` data blocks interleaved with code; every one of them is now covered by the Pass 3 data-table inventory below, with the instruction that reads it or the reason it has none. - `locals.inc` provides verified zero-page names; `symbols.inc` provides the NES hardware register set. ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry.