# Donkey Kong 3 (JUE) — Game Flow from Power-On This document describes the execution flow of the disassembled Donkey Kong 3 (JUE) 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 ROM (whole-ROM compare). --- ## 1. Reset Vector — CPU $FFFC → $C000 **`ResetHandler`** ($C000–$C08C) On power-on or reset, the 6502 jumps to `$C000`. The reset routine performs cold-start hardware initialization and enters the main loop dispatcher: | Step | Code | Purpose | |------|------|---------| | 1–2 | `cld` / `sei` | Clear decimal mode, disable interrupts | | 3–6 | Poll `$2002` twice (`bpl *-3`) | Wait for two full VBlank periods (PPU warm-up) | | 7–8 | `ldx #$00` / `txs` | Initialize stack pointer to $01FF | | 9–11 | `ldx FrameCounter` / `ldy #$07` / `sty WorkPointerOrValueHi` | FrameCounter ($27) → X; Y=$07 → WorkPointerOrValueHi ($02) | | 12 | `ldy #$00` / `sty WorkPointerOrValueLo` | Y=$00 → WorkPointerOrValueLo ($01) | | 13–21 | Clear RAM $0000–$07FF | 8 pages via (WorkPointerOrValueLo),Y; dec WorkPointerOrValueHi loops pages | | 22 | `txa` / `bne FinishResetInitialization` | If FrameCounter was non-zero, skip full clear | | 23 | `ldx #$5F` | Alternate init value for warm start | | 24 | `stx Ram_06F0` | Store signature at $06F0 | | 25 | `jsr ClearNametable2400WithTileFF` ($C1A2) | Fill nametable $2400 with $FF | | 26 | `jsr HideAllOamSprites` ($C1EA) | Hide all 64 sprites (Y=$F4) | | 27 | `sty $2005` ×2 | PPUSCROLL = 0,0 | | 28 | `iny` / `sty GameMode` | Y=$01 → GameMode ($3A) = title mode | | 29 | `lda #$0F` / `sta $4015` | Enable all APU channels | | 30 | `lda #$90` / `sta $2000` / `sta PPUCTRL` | PPUCTRL: NMI on, sprite $0000, BG $1000, 8×8 sprites | **Warm-start signature:** `FrameCounter` ($27) and `Ram_06F0` survive the RAM clear. If `FrameCounter` ≠ 0 on reset, the game skips the full 2 KB clear and uses X=$5F. --- ## 2. Main Loop Dispatcher — `SelectMainLoopAudioPath` ($C047) After reset initialization, control flows to **`SelectMainLoopAudioPath`** ($C047), the main loop dispatcher: ``` SelectMainLoopAudioPath ($C047): lda GameMode ; $3A beq RunAudioOnlyFrame ; 0 = title/attract: audio only lda PlayerModeOrScreenPhase ; $3C cmp #$01 bne RunMainGameFrame ; !=1: gameplay frame RunAudioOnlyFrame ($C051): jsr UpdateAudioEngine ; $F820 (banked audio) RunMainGameFrame ($C054): jsr DecrementGameplayTimers ; $C3B0 jsr UpdateTitleAndGameplayState ; $C0BA jsr RequestNmiProcessing ; $C05F beq SelectMainLoopAudioPath ; loop until NMI signals frame ready ``` **`RequestNmiProcessing`** ($C05F): Sets `NmiFrameDelay` ($32) = 1, signaling NMI to do work. **`WaitForFrameReady`** ($C063): Called after `RequestNmiProcessing` returns (via `beq` loop): ``` jsr AdvancePseudoRandomState ; $C42F (PRNG) lda FrameReadyFlag ; $30 beq WaitForFrameReady ; wait for NMI to set it inc FrameCounter ; $27 dec NmiFrameDelay ; $32 lda #$00 / sta FrameReadyFlag ; clear for next frame rts ``` --- ## 3. Interrupt Vectors — CPU $FFFA–$FFFF Located at PRG offset $3FFA (end of 16 KB bank): | Vector | Address | Handler | |--------|---------|---------| | NMI | $C08D | `NmiHandler` | | RESET | $C000 | `ResetHandler` | | IRQ | $C000 | `ResetHandler` (reused — unused placeholder) | --- ## 4. NMI / Frame Heartbeat — `NmiHandler` ($C08D) The NMI fires once per frame and performs PPU updates, controller reading, and signals frame completion: ``` NmiHandler ($C08D): pha / txa / pha / tya / pha ; preserve A,X,Y lda #$00 / sta $2003 ; OAMADDR = 0 lda #$02 / sta $4014 ; OAM DMA from $0200 lda NmiFrameDelay ($32) beq CompleteNmiAndRestoreRegisters ; if 0: minimal NMI (title screen) jsr RunQueuedPpuTransfer ; $C1F5 — process queued PPU writes jsr UpdatePpuTransferQueue ; $C370 — update queue pointers jsr ResetPpuScroll ; $C424 — reset scroll registers jsr UpdateControllerState_C328 ; $C328 — read controllers ldy #$01 / sty FrameReadyFlag ; $30 = 1 (signal main loop) CompleteNmiAndRestoreRegisters ($C0B0): lda #$01 / sta NmiWorkMode ; $33 = 1 (signal NMI work done) pla / tay / pla / tax / pla / rti ``` **Two NMI modes:** - **NmiFrameDelay = 0** (title/attract): Only OAM DMA, no game logic. - **NmiFrameDelay = 1** (gameplay): Full PPU queue flush, controller read, scroll reset, sets `FrameReadyFlag`. --- ## 5. Title / Gameplay State Machine — `UpdateTitleAndGameplayState` ($C0BA) ``` UpdateTitleAndGameplayState ($C0BA): lda GameMode ($3A) bne HandleStartAndPlayerSelection ; !=0: player select lda ControllerButtons ($1A) and #$10 ; START button beq ClearScreenSubstate ldy ScreenSubstate ($36) bne DispatchCurrentScreenState iny / sty ScreenSubstate ; first START press: advance substate ldy CurrentScreenState ($3B) cpy #$0D / beq StartGameplayCountdown cpy #$06 / bne DispatchCurrentScreenState ldx #$07 jsr CopyPlayerStateDefaults ; init player timers ; ... sets CurrentScreenState=$0D, PPUMASK=$0E, PPUCTRL=$81 jsr ClearAudioRequestFlags beq SelectAlternateAudioMode ; audio request $02 ClearScreenSubstate ($C104): lda #$00 / sta ScreenSubstate DispatchCurrentScreenState ($C108): lda CurrentScreenState ($3B) jsr DispatchScreenStateHandler ($C3C6) ScreenStateHandlerTable ($C10D): 14 entries (2 bytes each) .word Handler_$C663, $C6AB, $C6D4, $C6EB, $C755, $C7C2, $C7DD, $C8C6, $C8C8 .word $C93E, $C94B, $C998, $C9AF, $C9C7, $C07C ``` **GameMode = 0 (Title):** - Waits for START press (`ControllerButtons & $10`) - `ScreenSubstate` tracks first press vs. subsequent - `CurrentScreenState` selects which title screen handler (14 states) - State $0D = start gameplay countdown (sets `GameplayTimer`=$14, `CurrentScreenState`=$0C) - State $06 = player select (copies defaults, sets up audio) **GameMode ≠ 0 (Player Select / Gameplay):** - `HandleStartAndPlayerSelection` ($C129): reads START/SELECT, sets `GameMode`/`CurrentScreenState` - `DispatchGameModePhase` ($C142): dispatches on `PlayerModeOrScreenPhase` ($3C) - Screen state handlers at $C18D via second table ($C192): 15 entries for gameplay screens --- ## 6. Key Subsystems ### 6.1 PPU Transfer Queue — `$C1F5` / `$C370` - `QueuedPpuTransferCount` ($34) tracks pending transfers - Source pages from `QueuedPpuTransferSourcePageTable` ($C20A) - Destination offsets from `QueuedPpuTransferDestinationOffsetTable` ($C206) - `RunQueuedPpuTransfer` processes one per NMI; `UpdatePpuTransferQueue` refills ### 6.2 Controller Input — `UpdateControllerState_C328` ($C328) - Reads `$4016` (strobe) + shift register - Stores buttons in `ControllerButtons` ($1A), pressed edges in `ControllerPressedOrScriptInput` ($1B) - Second controller in `SecondControllerPressed` ($1D) ### 6.3 Audio Engine — `UpdateAudioEngine` ($F820) - Banked at $F800+ (called via trampoline) - 4-channel sequencer: `AudioChannelStateArray` ($F0–$F3), sequence pointers ($F9/$FA) - Note/period tables at $F970, $FAB3 - Sound effects via `StartSoundEffect` ($FAE6) ### 6.4 Screen State Handlers — `DispatchScreenStateHandler` ($C3C6) - Title: 14 handlers (attract, copyright, high score, player select, etc.) - Gameplay: 15 handlers (level intro, gameplay, bonus, intermission, game over, etc.) - Each handler manages its own objects, timers, and transitions ### 6.5 Object / Actor System - `ObjectState` ($0300+): 16+ slots, fields for type, position, timer, animation - `ActorFlags` ($84), `ActorX` ($85), `ActorY` ($86), `ActorStateArray` ($70+) - Collision detection via `ScreenCollisionState` ($44), `CollisionMask` ($BF) ### 6.6 PRNG — `AdvancePseudoRandomState` ($C42F) - Called once per frame in `WaitForFrameReady` - Used for enemy spawn patterns, bonus timing --- ## 7. Memory Map (Key Zero Page / RAM) | Address | Name (from `locals.inc`) | Use | |---------|--------------------------|-----| | $00 | `ControllerSerialBits` | Controller shift register bits | | $01–$02 | `WorkPointerOrValueLo/Hi` | General 16-bit pointer (RAM clear, PPU) | | $03 | `SourceRecordPointerLo` | PPU transfer source pointer | | $1A | `ControllerButtons` | Current button state | | $1B | `ControllerPressedOrScriptInput` | Edge-detected presses | | $20–$27 | `GameplayTimerArray` | 8 gameplay timers | | $21 | `GameplayTimer` | Main gameplay countdown | | $25 | `ScreenSelectionPositionOrDelay` | Menu cursor / delay | | $27 | `FrameCounter` | Global frame counter | | $28–$2F | `SavedGameplayTimerArray` | Backup timers | | $30 | `FrameReadyFlag` | Set by NMI, cleared by main loop | | $31 | `CollisionResultFlag` | Collision detection result | | $32 | `NmiFrameDelay` | NMI work trigger (0=skip, 1=do work) | | $33 | `NmiWorkMode` | NMI completion signal | | $34 | `QueuedPpuTransferCount` | Pending PPU transfers | | $36 | `ScreenSubstate` | Title screen sub-state | | $37 | `StartInputLatch` | START button latch | | $38 | `SelectedPlayerAndScreenFlags` | Player/screen selection | | $3A | `GameMode` | 0=title, 1=player select, 2+=gameplay | | $3B | `CurrentScreenState` | Active screen handler index | | $3C | `PlayerModeOrScreenPhase` | Player mode / screen phase | | $3D | `ReturnScreenState` | Return destination | | $40–$41 | `SelectedPlayerIndex`, `SelectedScreenVariant` | Menu selections | | $50+ | `GameplayObjectState`… | Object pool ($0300+) fields mirrored in ZP | | $F0–$FF | Audio engine ZP | Channel state, sequences, envelopes | | $0200 | `OamBuffer` | OAM DMA source (64 sprites) | | $0300+ | `ObjectState`… | Main object/actor array | | $06F0 | `Ram_06F0` | Warm-start signature | | $0700+ | Extended RAM | Actor timers, collision, audio buffers | --- ## 8. Data Tables | Table | Address | Description | |-------|---------|-------------| | `ScreenStateHandlerTable` | $C10D | 14 title screen handlers | | `GameplayScreenStateHandlerTable` | $C192 | 8 gameplay screen handlers | | `QueuedPpuTransferSourcePageTable` | $C20A | PPU transfer source banks | | `QueuedPpuTransferDestinationOffsetTable` | $C206 | PPU dest offsets (alias at $C206; the data itself starts at $C207) | | `ObjectRowSpawnTable` | $D0B4 | per-screen-row actor spawn records | | `ObjectMovementIncrementTable` | $CD85 | per-frame movement increment stream | | `ActorBehaviourParameterTable` | $E0F0 | per-actor behaviour parameters | | `MetaspriteLayoutPointerTable` | $F757 | 16 metasprite layout pointers | | `AnimationBaseTileTable` | $F6B0 | 8 base tile numbers for the animation frames | | `AudioPitchOrPeriodTable_F970` | $F970 | Note→period lookup | | `AudioSequenceIndexTable_FAB3` | $FAB3 | Sequence index table | | `AudioPitchOffsetTable` | $FFC3 | per-note pitch offset | | `GameplayXorMaskTable` | $FF4C | 16 XOR masks | The full, interpreted inventory of every data block in the PRG is in the **Pass 3 data-table inventory** below. --- --- ## 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. A region no instruction operand lands in is recorded as **unreferenced data** with its ROM provenance; a region whose bytes decode as a run of 6502 instructions is recorded as **code the tracer could not reach**, because that is what it is - the Pass-1 trace never entered it, so the tracer emitted it as data. The Pass-1 tracer's `; ---- data $XXXX-$YYYY ----` markers are gone from this source, so the blocks below were recovered from the emitted `.byte` rows and their CPU addresses; the consumers were found by resolving every operand - literal **and** label - to an address. | Address | Size | Name | Structure | Read by | |---------|------|------|-----------|---------| | `$C10D-$C128` | 28 B | `ScreenStateHandlerTable` | fourteen little-endian 16-bit handler addresses for the fourteen title-screen states: `$C663, $C6AB, $C6D4, $C6EB, $C755, $C7C2, $C7DD, $C8C6, $C93E, $C94B, $C998, $C9AF, $C9C7, $C07C`. `DispatchScreenState` at `$C18D` indexes it with the screen state. | `DispatchScreenState` at `$C18D` -> `jsr DispatchScreenStateHandler` at `$C18F`; the handlers themselves are the targets of the `jsr`s at `$C663`, `$C6AB`, `$C6D4`, `$C6EB`, `$C755`, `$C7C2`, `$C7DD`, `$C8C6`, `$C93E`, `$C94B`, `$C998`, `$C9AF`, `$C9C7`, `$C07C` | | `$C192-$C1A1` | 16 B | `GameplayScreenStateHandlerTable` | eight little-endian 16-bit handler addresses for the eight gameplay screen states: `$C9EB, $CA2B, $CA41, $CA56, $CA5B, $CA60, $CA6E, $CAEB`. | `DispatchScreenState` at `$C18D` -> `jsr DispatchScreenStateHandler` at `$C18F`; the handlers are the targets of the `jsr`s at `$C9EB`, `$CA2B`, `$CA41`, `$CA56`, `$CA5B`, `$CA60`, `$CA6E`, `$CAEB` | | `$C207-$C20E` | 8 B | (the queued PPU transfer descriptors) | eight bytes `$16,$3A,$5E,$82,$F0,$F0,$F0,$F0`. They are the two five-entry tables the transfer routine needs: the destination offsets `$C207-$C20B` and the source pages `$C20A-$C20E` (`$82` then four `$F0`s). The two share the byte at `$C20A`, and the `QueuedPpuTransferDestinationOffsetTable` alias in `prg.s` points at `$C206`, whose first byte is also the last byte of the `jmp BeginPpuCommandStream` instruction at `$C204` - the alias is one byte below the data. | `lda QueuedPpuTransferSourcePageTable,y` at `$C1F9` and `ldx QueuedPpuTransferDestinationOffsetTable,y` at `$C1FC` (both indexed by the queued transfer count) | | `$C751-$C754` | 4 B | `ScreenVariantInitialStateTable` | four bytes `$00,$03,$03,$05` - the initial screen state of each screen variant. | `lda ScreenVariantInitialStateTable,y` at `$C74B` | | `$C9EB-$CB2A` | 320 B | (unreached code) | 320 bytes that decode as a 95-instruction run of 6502 code (`A5 85 ...`): the tail of the `GameplayScreenStateHandlerTable` handler at `$C9EB` and the routines it calls, none of which the Pass-1 trace reached from the reset, NMI or IRQ vectors. | – | | `$CD85-$CDAB` | 39 B | `ObjectMovementIncrementTable` | a stream of per-frame increments, terminated by `$AA`: `$FD,$FE,$FE,$FE,$FE,$FF,$FF,$FE,$FF,$FF,$FF,$FF,$00,$FF,$00,$FF,$00,$AA`, then a second stream `$00,$01,$00,$01,$00,$01,$01,$01,$01,$02,$01,$01,$02,$02,$02,$03,$02,$03,$02,$03` and the terminator `$AA`. The routine at `$CC4D` walks the stream with an index in `$030B`, adds each increment to `ObjectField8`, and stops on `$AA`. | `lda ObjectMovementIncrementTable,y` at `$CC53` (the `$AA` end marker is tested by `cmp #$AA` at `$CC56`) | | `$CE3F-$CE76` | 56 B | (unreferenced) | Unreferenced data (ROM provenance PRG `$0E3F-$0E76`). The bytes fall into 4-byte groups that look like packed coordinates (`3A C8 B8 2A`, `D8 C8 1A E8`, `98 4A B8 A8`, ...); the record layout is not established because no instruction in this dump reads the block. | – | | `$CF84-$CFB0` | 45 B | `ObjectTimerReloadTable` + `ObjectTimerValueTable` | 22 two-byte records, each a (countdown, value) pair, terminated by `$AA,$A4`: `$06,$01`, `$0A,$02`, `$0E,$03`, `$80,$04`, `$FD,$FD`, `$FD,$FE`, `$FE,$FF`, `$FF,$00`, `$FF,$01`, `$00,$01`, `$01,$02`, `$02,$03`, `$FE,$FE`, `$FF,$00`, `$FF,$01`, `$00,$01`, `$01,$02`. The countdown is the reload value of the per-actor timer and the second byte is the value written when it expires. | `lda ObjectTimerReloadTable,y` at `$CEFA` and `lda ObjectTimerReloadTable,y` at `$CF01` (the second byte of each pair) | | `$D01A-$D022` | 9 B | `ObjectSpawnXTable` / `ObjectSpawnYTable` / `ObjectSpawnTypeTable` | the spawn descriptor of one actor: X `$01`, Y `$00`, and a seven-entry type list `$01,$02,$19,$01,$02,$32,$00`. | `lda ObjectSpawnXTable,y` at `$D003`, `lda ObjectSpawnYTable,y` at `$D009`, `lda ObjectSpawnTypeTable,y` at `$D00F` | | `$D0B4-$D107` | 84 B | `ObjectRowSpawnTable` | 21 four-byte records, one per screen row: `$03,$FB,$00,$02`, `$03,$FB,$05,$02`, `$04,$FC,$0A,$02`, `$04,$FC,$0F,$04`, `$04,$FD,$14,$04`, `$02,$FE,$17,$04`, `$AA,$03,$FA,$00`, `$02,$04,$FA,$05`, ... The routine at `$D071` indexes the table with the actor's **screen Y** (`$034B`), copies four consecutive bytes into the actor record, and stops on the `$AA` marker. | `lda ObjectRowSpawnTable,y` at `$D074`, `$D07F` and `$D086` (the `$AA` end marker is tested by `cmp #$AA` at `$D077`) | | `$D1C7-$D1D6` | 16 B | `ObjectMovementStepTable` + `ObjectMovementLimitTable` | two parallel 8-byte tables. The first is the per-step movement distance `$03,$03,$04,$04,$06,$06,$07,$08`; the second is the per-step limit `$08,$0A,$04,$04,$03,$04,$02,$01`. The two are added together and stored as the actor's coordinate and its limit. | `lda ObjectMovementStepTable,x` at `$D18E`, `adc ObjectMovementStepTable,y` at `$D192`, `lda ObjectMovementLimitTable,x` at `$D197` and `adc ObjectMovementLimitTable,y` at `$D19B` | | `$D2D4-$D2F1` | 30 B | `PackedHudValueTable` | four-byte records whose first two bytes are packed HUD values: `$00,$10`, `$81,$20`, `$02,$30`, `$83,$40`, `$FF,$FF`, ... The first byte of a record is XOR-ed with the packed HUD byte before it is stored. | `lda PackedHudValueTable,y` at `$D2A6` and at `$D2AF` (the `eor PackedHudOrObjectValue` at `$D2A9` is applied to the first byte of the pair) | | `$D3B5-$D3D4` | 32 B | `ActorSpriteTileTable` / `ActorSpriteAttributeTable` / `ActorSpritePaletteTable` / `ActorSpritePatternTable` | one 32-byte table of **eight 4-byte records**, read at four different bases so that one instruction picks up each of the four bytes of the selected record: `$3C,$F8,$00,$00`, `$3C,$F8,$00,$00`, `$5C,$F8,$5F,$00`, `$5C,$F8,$5F,$00`, `$6C,$D8,$00,$04`, `$74,$D8,$00,$04`, `$7C,$D8,$00,$04`, `$84,$D8,$00,$04`. The record index is the low three bits of the packed HUD byte, scaled by four. | `lda ActorSpriteTileTable,y` at `$D35F`, `lda ActorSpriteAttributeTable,y` at `$D365`, `lda ActorSpritePaletteTable,y` at `$D374` and `lda ActorSpritePatternTable,y` at `$D37A` (all with Y = (packed & 7) x 4) | | `$D619-$D6F2` | 218 B | `CollisionResponseMaskTable` | 214 bytes of bit masks (`$0F` x 77, `$10` x 66, `$1F` x 38, `$11` x 16) followed by the four bytes `$D6,$C5,$C5,$AC` at `$D6EF`. The masks are indexed by the actor's collision mask. | `lda CollisionResponseMaskTable,y` at `$D4F5` (Y = `CollisionMask` & `$7F`); the byte just before the table at `$D617` is the dispatch type tested by `bpl` at `$D4E9` | | `$D73A-$D759` | 32 B | `InitialOamSpriteTable` | 32 bytes copied into the sprite shadow at `$02E0` at the start of a round: `$6F,$E5,$03,$70`, `$6F,$E6,$03,$88`, `$6F,$E6,$03,$78`, `$6F,$E5,$03,$80`, `$77,$E5,$03,$70`, `$77,$E7,$03,$88`, `$77,$E7,$03,$78`, `$77,$E5,$03,$80` - eight 4-byte OAM records (Y, tile, attribute, X). | `lda InitialOamSpriteTable,x` at `$D72E` (the loop copies 32 bytes into `$02E0`) | | `$D968-$D991` | 42 B | `ActorBehaviourFlagTable` | 42 bytes of packed behaviour flags, grouped in triples: `$08,$00,$08`, `$04,$88,$14`, `$08,$10,$08`, `$14,$88,$10`, `$08,$14,$10`, `$00,$08,$0C`, `$48,$1C,$08`, `$18,$08,$1C`, `$48,$18,$08`, `$1C,$08,$08`, `$AA,$06,$16`, `$06,$17,$06`, `$18,$06,$19`, `$06,$1A,$AA`, `$A5`. The high bits of each byte select the branch the actor takes; `$AA` ends the table. | `lda ActorBehaviourFlagTable,y` at `$D7B9`, `$D7D1` and `$D7DA` (the `$AA` end marker is tested by `cmp #$AA` at `$D7BC`) | | `$DB38-$DB87` | 80 B | `ActorSpriteOamTemplate` | 80 bytes of sprite descriptor data, in the same 4-byte shape as the OAM records: `$66,$90,$91,$92`, `$93,$24,$8C,$94`, `$95,$A7,$A8,$98`, `$99,$8B,$9A,$A9`, `$AA,$9D,$9E,$8B`, `$9F,$A0,$A1,$A2`, `$8C,$8B,$24,$F1`, `$F2,$24,$8C,$76`, `$8B,$24,$24`. | – | | `$DB88-$DB9F` | 24 B | `ActorSpriteLayoutPointerLoTable` | 24 bytes: the low bytes of the actor sprite-layout pointers, the high bytes being the 16 bytes at `$DB98`. | `lda ActorSpriteLayoutPointerLoTable,y` at `$DA30` and at `$DA3C` | | `$DBA0-$DBA5` | 6 B | `ActorSpriteLayoutPointerTable` | 6 bytes, the low halves of a second set of sprite-layout pointers. | `lda ActorSpriteLayoutPointerTable,y` at `$DA5F` and at `$DA6B` | | `$DBA6-$DBA6` | 1 B | `ActorSourcePointerLoTable` | one byte, the low half of a source pointer; the matching high byte is the byte at `$DBA7`. | `lda ActorSourcePointerLoTable,x` at `$DAC9` | | `$DBA7-$DBB6` | 16 B | `ActorSourceValueTable` | 16 bytes, the high halves of the same pointers. | `lda ActorSourceValueTable,x` at `$DACE` | | `$DBC2-$DBC5` | 4 B | `ActorRecordPointerLoTable` / `ActorRecordValueTable` / `ActorRecordCountTable` / `ActorRecordTileTable` | four parallel byte tables, read at four different bases so that one lookup fetches the four fields of an actor record: the source pointer low byte, the packed value, the record count (Y) and the tile number (X). | `lda ActorRecordPointerLoTable,x` at `$D9B6`, `lda ActorRecordValueTable,x` at `$D9BB`, `ldy ActorRecordCountTable,x` at `$D9C0`, `lda ActorRecordTileTable,x` at `$D9C3` | | `$DC24-$DC36` | 19 B | `PackedScorePointerLoTable` / `PackedScoreValueTable` / `PackedScoreCountTable` / `PackedScorePointerHiTable` | four parallel byte tables read at four bases, giving the low and high bytes of a source pointer, a value and a count for the packed score/HUD expander. | `lda PackedScorePointerLoTable,x` at `$DADE`, `lda PackedScoreValueTable,x` at `$DAE3`, `lda PackedScoreCountTable,x` at `$DAE8`, `lda PackedScorePointerHiTable,x` at `$DAED` -> `jmp ExpandPackedScoreOrHudData` at `$DAF2` | | `$DECD-$DED4` | 8 B | `GameplayObjectStateTimerTable` | eight bytes `$24,$20,$1C,$18,$14,$10,$0E,$0C` - the reload value of the gameplay object's timer, one per object state. | `lda GameplayObjectStateTimerTable,y` at `$DE8A` -> `sta GameplayObjectTimer` at `$DE8D` | | `$E0DB-$E0EF` | 21 B | (unreferenced) | Unreferenced data (ROM provenance PRG `$20DB-$20EF`): eleven bytes that are 16-bit code addresses (`$E7D3`, `$E225`, `$E3C9`, `$E3D6`, `$E31D`, `$E430`, `$E720`, `$E73C`, `$E735`, `$E510`) plus a stray `$E1`. | – | | `$E0F0-$E10F` | 32 B | `ActorBehaviourParameterTable` | 32 bytes of per-actor parameters: `$00,$04,$01,$03,$07,$02,$05,$07,$04,$07,$10,$00,$10,$00,$10,$0A` and `$00,$1E,$00,$28,$00,$14,$20,$32,$20,$00,$02,$01,$01,$00,$02,$00`. | `lda ActorBehaviourParameterTable,y` at `$E037` | | `$E110-$E694` | 1429 B | (unreached code) | 1429 bytes that decode as a long run of 6502 instructions (`A6 0E A5 27 29 03 F0 78 ...`): the actor update routines, none of which the Pass-1 trace reached. | – | | `$E69B-$E7C4` | 298 B | (unreached code) | 298 bytes that decode as 88 consecutive 6502 instructions (`A5 16 4A 4A 4A 4A A8 29 08 F0 0A ...`): more actor code. | – | | `$E85E-$E869` | 12 B | `ScreenObjectInitialXTable` / `ScreenObjectInitialYTable` / `ScreenObjectInitialStateTable` | the three parallel one-byte tables that give a screen object's initial X, Y and state: X `$3C`, Y `$02`, state `$02,$38,$03,$01,$34,$03,$FF,$30,$02,$FE`. | `lda ScreenObjectInitialXTable,y` at `$E7E6`, `lda ScreenObjectInitialYTable,y` at `$E7EC`, `lda ScreenObjectInitialStateTable,y` at `$E7F2` | | `$EA5E-$EA9E` | 65 B | (unreached code) | 65 bytes that decode as 6502 code (`4B FF 4B A0 00 A9 03 85 11 B9 A0 04 F0 0A ...`): the tail of the actor code above, past the last label the Pass-1 trace reached. | – | | `$EB8E-$EBC5` | 56 B | (unreached code) | 56 bytes that decode as 22 consecutive 6502 instructions. | – | | `$EC5F-$EC68` | 10 B | (head of the sprite data) | the first ten bytes of the sprite area, ASCII `"FHGI"LLLL` - a quoted four-character tag followed by four `L`s. | – | | `$EC69-$EC78` | 16 B | `ActorOamDescriptorTable` | 16 bytes of actor OAM descriptor data. | – | | `$EC79-$ED02` | 138 B | (sprite data) | 138 more bytes of the actor OAM / sprite descriptor area, in the same 4-byte record shape. | – | | `$ED03-$ED12` | 16 B | `AnimationFrameTable` | 16 bytes of animation frame data: `$01,$00,$00,$00,$01,$01,$00,$00,$01,$02,$00,$00,$01,$03,$00,$00`. | – | | `$ED13-$EF7B` | 617 B | (sprite data) | the remainder of the animation and sprite data, ending with a run of 16-bit code addresses (`$ED67`, `$ED6F`, `$ED6B`, `$ED73`, `$ED57`, `$ED5F`, `$ED87`). | – | | `$EF7C-$EF8C` | 17 B | `HudDigitGlyphPointerTableLo` + `HudDigitGlyphPointerTableHi` | 17 bytes: the low byte of a HUD digit glyph pointer and the 16 high bytes that go with it. | `lda HudDigitGlyphPointerTableLo,y` at `$C49B` | | `$EF8D-$F5D3` | 1607 B | (sprite / animation data) | 1607 bytes of sprite and animation data: the per-actor animation frames, the OAM descriptors and the character tables. | – | | `$F5D4-$F5E3` | 16 B | `GameplayLookupIndexTable` | 16 bytes, the index written into the gameplay lookup slot at `$0305`. | `lda GameplayLookupIndexTable,y` at `$C2CA` and at `$C2D3` | | `$F5E4-$F60C` | 41 B | (gameplay lookup data) | 41 more bytes of the gameplay lookup table. | – | | `$F60D-$F61C` | 16 B | `GameplayLookupValueTable` | 16 bytes, the values the gameplay lookup returns. | `lda GameplayLookupValueTable,y` at `$C2E9`, `$C2F2` and `$C2FD` | | `$F61D-$F68A` | 110 B | (gameplay lookup data) | 110 more bytes of the gameplay lookup table. | – | | `$F68B-$F69A` | 16 B | `GameplayLookupFlagTable` | 16 bytes, the flag bytes of the gameplay lookup. | `lda GameplayLookupFlagTable,y` at `$C309` and at `$C312` | | `$F69B-$F6AE` | 20 B | (gameplay lookup data) | 20 more bytes of the gameplay lookup table. | – | | `$F6AF-$F6AF` | 1 B | `AnimationLayoutIndexTable` | one byte `$00`, the animation layout index; the code doubles it to index the base-tile table. | `lda AnimationLayoutIndexTable,y` at `$C235` | | `$F6B0-$F6BF` | 16 B | `AnimationBaseTileTable` | 16 bytes, eight 2-byte base tile numbers: `$0004`, `$0008`, `$000C`, `$0010`, `$0014`, `$0018`, `$001C`, `$0020`. | `lda AnimationBaseTileTable,y` at `$C230` (Y = tile number x 2) | | `$F6C0-$F756` | 151 B | (metasprite data) | 151 bytes of metasprite layout data. | – | | `$F757-$F768` | 18 B | `MetaspriteLayoutPointerTable` + `MetaspriteLayoutPointerTableHi` | 18 bytes: the low byte of a metasprite layout pointer and the 16 high bytes that go with it, giving 16 pointers (`$F773`, `$F780`, `$F784`, `$F78B`, `$F795`, `$F79C`, `$F7D3`, `$F7DA`, `$F7E1`, `$F7EB`, `$F7F5`, `$F7F9`, `$F7FA`, ...). | `lda MetaspriteLayoutPointerTable,y` at `$C23A` and `lda MetaspriteLayoutPointerTableHi,y` at `$C23F` | | `$F768-$F81F` | 184 B | (metasprite data) | 184 more bytes of metasprite layout data. | – | | `$F834-$F835` | 2 B | (unreferenced) | Unreferenced data (ROM provenance PRG `$3834-$3835`): `$A2,$00`, the bytes of a `LDX #$00` that the Pass-1 split left as data. | – | | `$F973-$F98A` | 24 B | `AudioPulse2PeriodLowTable` + `AudioPulse2PeriodHighTable` | 24 bytes of APU period data: the low bytes of the second pulse channel's period at `$F97B` and the 15 high bytes at `$F97C` (`$08,$9F,$48,$5C,$09,$67,$09,$1C,$09,$EE,$08,$C8,$08,$A8,$48`). | `lda AudioPulse2PeriodLowTable,y` at `$F900` -> `sta APUPULSE2TIMERLO` at `$F903`; `lda AudioPulse2PeriodHighTable,y` at `$F906` -> `sta APUPULSE2TIMERHI` at `$F909` | | `$FAB6-$FB39` | 132 B | `AudioSequenceEntryTable` + `AudioSequenceOffsetTable` + `AudioSequenceNoteTable` | 132 bytes of sound-sequence data. The entry table at `$FAD4` holds the sequence descriptors (a zero byte ends the list), the offset table at `$FAF6` the sequence start offsets, and the note table at `$FB17` the note data itself. | `lda AudioSequenceEntryTable,y` at `$FB50` (a zero byte ends the list - `bne` at `$FB53`), `ldx AudioSequenceOffsetTable,y` at `$FB5D`, `lda AudioSequenceNoteTable,y` at `$FB60` | | `$FC93-$FCFF` | 109 B | (audio note data) | 109 bytes of note/period data for the sound engine: `$80,$48,$7E,$60,$7E,$52,$7E,$52,$00,$80,$48,$7E,$48,$7E,$60,$7E,...` - pairs of a period and a duration, `$00` ending a phrase. | `UpdateAudioEngine` at `$F820` | | `$FDA4-$FDBA` | 23 B | `AudioNoteIndexTable` / `AudioPeriodBaseTable` / `AudioSequencePointerLoTable` / `AudioSequencePointerHiTable` / `AudioEnvelopeTable` / `AudioDurationTable` / `AudioWaveformTable` / `AudioPitchTable` | eight parallel byte tables, each 16 bytes long and overlapping the next by fifteen bytes, so that one lookup fetches all eight fields of a note descriptor: note index, period base, the low and high bytes of the sequence pointer, the envelope, the duration, the waveform and the pitch. | `lda AudioNoteIndexTable,y` at `$FBEB`, `lda AudioPeriodBaseTable,y` at `$FBEF`, `lda AudioSequencePointerLoTable,y` at `$FBF4`, `lda AudioSequencePointerHiTable,y` at `$FBF9` and the three following bytes at `$FBFE`, `$FC03` and `$FC08` | | `$FF4C-$FF5B` | 16 B | `GameplayXorMaskTable` | 16 bytes XOR-ed into a value by the actor code at `$EA5B`. | `eor GameplayXorMaskTable` at `$EA5B` | | `$FFC3-$FFD2` | 16 B | `AudioPitchOffsetTable` | 16 bytes, the per-note pitch offset added to the period base to get the note's period. | `lda AudioPitchOffsetTable,y` at `$FC64` | | `$FFE2-$FFF2` | 17 B | `AudioSamplePointerLoTable` + `AudioSamplePointerHiTable` | 17 bytes: the low byte of a 16-bit sample-data pointer and the 16 high bytes that go with it. | `lda AudioSamplePointerLoTable,y` at `$F84F` and `lda AudioSamplePointerHiTable,y` at `$F854` | | `$FFF3-$FFFF` | 13 B | (tail of the audio data + vectors) | the last of the audio data, followed by the three vectors. | the CPU's own vector fetch | ### Notes on the inventory * The Pass-1 split of this ROM put a large amount of **code** into the data regions: the blocks at `$C9EB-$CB2A`, `$E110-$E694`, `$E69B-$E7C4`, `$EA5E-$EA9E` and `$EB8E-$EBC5` all decode as long runs of 6502 instructions and are listed above as *code the tracer could not reach*. They are left exactly as the tracer emitted them, because re-splitting them is a Pass-1 job and not a naming job; the inventory records them so that the next pass does not have to rediscover them. * The same ROM-wide trick of storing a 16-bit value in two overlapping byte arrays is used all over the sprite and audio tables: the "low" and "high" tables start one byte apart and overlap by fifteen bytes, so a single indexed read at two bases returns the low and the high byte of the same entry. * The `ScreenStateHandlerTable` at `$C10D` has fourteen entries and the `GameplayScreenStateHandlerTable` at `$C192` has eight; the earlier README figure of "15 gameplay screen handlers" was a miscount and is corrected here. --- ## 9. Summary of Execution Flow ``` Power On / Reset │ ▼ ResetHandler ($C000) ────► cld/sei, wait 2 VBlanks, init stack │ ├─ Clear $0000–$07FF (8 pages via WorkPointerOrValueLo/Hi) │ └─ Warm-start: if FrameCounter≠0, skip clear, X=$5F │ ├─ stx Ram_06F0 (signature) ├─ jsr ClearNametable2400WithTileFF ($C1A2) ├─ jsr HideAllOamSprites ($C1EA) ├─ PPUSCROLL = 0,0 ├─ GameMode = 1 (title) ├─ APU enable ($4015=$0F) ├─ PPUCTRL = $90 (NMI on) │ ▼ SelectMainLoopAudioPath ($C047) ─────────────────────────────┐ │ │ ├─ GameMode=0: jsr UpdateAudioEngine ($F820), loop │ │ │ └─ GameMode≠0: │ jsr DecrementGameplayTimers ($C3B0) │ jsr UpdateTitleAndGameplayState ($C0BA) │ jsr RequestNmiProcessing ($C05F) → NmiFrameDelay=1 │ jsr WaitForFrameReady ($C063) ◄── wait for NMI ────┘ │ ├─ jsr AdvancePseudoRandomState ($C42F) ├─ wait for FrameReadyFlag ($30) ├─ inc FrameCounter ($27), dec NmiFrameDelay └─ clear FrameReadyFlag, rts beq SelectMainLoopAudioPath (loop) NMI ($C08D) [once per frame] ├─ Preserve A,X,Y ├─ OAM DMA ($4014 from $0200) ├─ lda NmiFrameDelay ($32) │ ├─ 0: CompleteNmiAndRestoreRegisters (title) │ └─ 1: RunQueuedPpuTransfer ($C1F5) │ UpdatePpuTransferQueue ($C370) │ ResetPpuScroll ($C424) │ UpdateControllerState_C328 ($C328) │ ldy #$01 / sty FrameReadyFlag ($30) ├─ lda #$01 / sta NmiWorkMode ($33) └─ Restore A,X,Y, rti Title Screen ($C0BA, GameMode=0) ├─ Wait for START (ControllerButtons & $10) ├─ ScreenSubstate tracks first/second press ├─ CurrentScreenState selects handler (14 states at $C10D) ├─ State $0D: StartGameplayCountdown → GameplayTimer=$14, State=$0C └─ State $06: CopyPlayerStateDefaults → init timers, audio Player Select / Gameplay ($C129, GameMode≠0) ├─ HandleStartAndPlayerSelection: START/SELECT → GameMode, CurrentScreenState ├─ DispatchGameModePhase ($C142): PlayerModeOrScreenPhase ($3C) → handler ├─ DispatchScreenState ($C18D): 15 gameplay handlers ($C192 table) └─ Gameplay loop: objects, collision, scoring, level progression ``` --- ## 10. Naming and Residue **Pass 1/2/3 complete** — `prg.s` has **zero** `Label_` definitions. All routines carry semantic names from Pass 2 (`ResetHandler`, `NmiHandler`, `UpdateTitleAndGameplayState`, `DispatchScreenStateHandler`, etc.) and internal branch targets use `_Block[_n]` convention. **Data tables.** Every data block in the PRG has been interpreted and recorded in the **Pass 3 data-table inventory** below: what the bytes are, the record size, and the instruction in this dump that reads them. The blocks that decode as 6502 code are recorded there as *code the tracer could not reach* rather than as tables. **Pass 3 symbols applied (2026-10-19):** `_tools/apply_symbols.py` substituted **75 operands** (17 single-name + 58 hardware) from `symbols.inc`/`locals.inc`. Zero residue. **Overlapping table aliases** (expressed as relocatable symbols): - `QueuedPpuTransferDestinationOffsetTable = RunQueuedPpuTransfer + $11` ($C206) - `AudioPitchOrPeriodTable_F970 = Flow_F96F + $01` ($F970) - `AudioSequenceIndexTable_FAB3 = Flow_FA85 + $2E` ($FAB3) **Vectors** at PRG $3FFA: `.word NmiHandler, ResetHandler, ResetHandler` Build verification: `make` in `copilot/` produces `reconstructed.nes`, compared whole-ROM against `rom.nes` (byte-identical). ## Power-on flow analysis Derived from the disassembly by `_tools/derive_poweron.py`; the call graph is walked from the reset entry.