# Donkey Kong Jr. (JU) — Game Flow from Power-On This document describes the execution flow of the disassembled Donkey Kong Jr. (JU) PRG bank (16 KB, mapped at CPU **$C000–$FFFF**), starting from power-on. It is based entirely on `copilot/prg.s` (13,634 lines) in the repository, originally generated by `pass1_trace.py` from the interrupt vectors and refined through Pass 2/3 semantic labeling. The rebuild is verified **byte-identical** to the original `rom.nes` (`make` in `copilot/` produces `reconstructed.nes` matching `rom.nes` exactly). Listing: project **11** in the top-level `OVERVIEW.md` index. NROM cartridge (mapper 0), iNES flags: 1 × 16 KB PRG, 1 × 8 KB CHR, horizontal mirroring (flag 6 = $01). ROM SHA-256: `7d5f77878af7646296d994da14b25213741f6e0e0bc5deadd32a406d044a5788` Current label status — **all 1,035 top-level labels are semantically named**; every data region is emitted as explicit `.byte` data with its ROM provenance in comments (no `.incbin` blocks remain), and 688 local `Label_` branch targets are routine-internal. --- ## 1. Reset Vector — CPU $FFFC → $C66E **`ResetHandler`** ($C66E–$C6B3) Unlike most projects in this repository (whose reset handlers sit at $C000 or nearby entrypoints such as $C40B), Donkey Kong Jr.'s reset entry is at **$C66E**. On power-on or reset, the 6502 jumps to `$C66E`. The reset routine performs cold-start hardware initialization: | Step | Code | Purpose | |------|------|---------| | 1 | `sei / cld` | Disable interrupts, clear decimal mode | | 2 | `lda #$10 / sta PPUCTRL` | Disable NMI and PPU rendering immediately | | 3 | `ldx #$FF / txs` | Initialize stack pointer | | 4 | Poll `$2002` until bit 7 set | Wait for one VBlank period (PPU warm-up) | | 5–8 | Indirect clear loop via `$00/$01` pointer | Clear all of RAM `$0000–$07FF` (8 pages, walked downward from $0700) | | 9 | `jsr InitializePpuOamAndDmc` ($C6B6) | PPU, shadow-OAM, and DMC initialization (see §2) | | 10 | `lda #$6F / sta TitleCursorY` | Initialize the title-cursor sprite Y position ($6F) | | 11 | `lda #$18 / sta GameModeTimer` | Set attract-sequence timer (24) | | 12 | `lda #$01 / sta PlayerLivesOrModeCount, sta PendingGameMode` | Arm the pre-game attract path | | 13 | `lda #$00 / sta GameMode` | Set current game mode to 0 | | 14 | Toggle `$2000` bit 7 via `PPUControlShadow` | **Enable NMI** — actual execution begins | | 15 | `Label_C6B0: jsr AdvanceRandomLfsr / jmp` | Idle loop; the reset path never returns | **Architectural note:** After reset, the CPU idles forever (`jmp Label_C6B0`); the loop body's only activity is `jsr AdvanceRandomLfsr` to churn the 8-byte PRNG. *All* game logic — attract screen, titles, gameplay, sound — runs entirely inside the **NMI handler**, which is the opposite of most NES titles in this collection (where the NMI flag merely paces a foreground main loop). --- ## 2. Initialization — `InitializePpuOamAndDmc` ($C6B6–$C6E5) Called from `ResetHandler`: 1. **PPU control**: `PPUCTRL = $10`, saved to `PPUControlShadow` ($10) 2. **PPU mask**: `PPUMASK = $06` (background + sprite rendering), shadow at $11 3. **Scroll**: both scroll registers cleared, shadows at $12/$13 4. **Shadow OAM fill**: fills `$0200–$02FF` with `$FF` (all sprites hidden) 5. `jsr ClearGameplayNametable` ($CA16) — clears the background nametable 6. `APUDMALOAD = $00` (`$4011`, DMC volume/level reset) --- ## 3. Interrupt Vectors — CPU $FFFA–$FFFF Located at the end of the bank (PRG offset $3FFA): | Vector | Address | Handler | |--------|---------|---------| | NMI | $C6E6 | `NmiHandler` | | RESET | $C66E | `ResetHandler` | | IRQ | $FFF0 | `IrqHandler` (single `asl a` byte; see the deferred-task note in §6.1) | The IRQ's apparent "oddity" is now explained: `DeferredTaskHandlerTable` deliberately addresses its entry 0 at mirrored CPU **$BFFE**, which is the IRQ-vector word at PRG $3FFE. The table leverages the address mirror so that a zero task type falls through to the unused `asl a` byte at $FFF0 — a register-saving trick rather than a real handler. --- ## 4. NMI / Frame Heartbeat — `NmiHandler` ($C6E6–$C76D) The NMI is the *entire* per-frame scheduler. It: 1. **Preserves state**: pushes A, X, Y, **plus zero page $00–$07** (an 11-byte save; other projects only save the registers) 2. **Suspends NMI**: clears bit 7 of `PPUControlShadow` and writes `$2000` — prevents re-entrancy while the frame logic runs 3. **OAM DMA**: `jsr PerformOamDma` ($EEB0) — sprite DMA from shadow OAM $0200 4. **VRAM queue flush**: `jsr FlushQueuedVramCommands` ($EE9C) — drains the $0300 command queue during rendering 5. `jsr WriteInvertedRenderingMask` ($CA7A) — redraws PPUMASK state 6. `jsr UpdateAudioFrame` ($F91F) — per-frame audio channel update 7. **Controllers**: `jsr ReadControllers` ($F219) — serial pad read for both ports 8. **Frame logic dispatch** (see §5) based on `PendingGameMode` ($47), `GameMode` ($46), and `GameplaySubmode` ($4D) 9. `jsr ProcessQueuedTask` ($EE3A) — pops one pending task from the $0300 queue (see §6.1) 10. **Resumes NMI**: re-enables bit 7 of `PPUControlShadow` / `$2000` — allows the next frame 11. **Restores $00–$07, A, X, Y, `rti`** --- ## 5. Frame State Machine (inside the NMI) The frame-mode dispatch at $C71B–$C748: ``` lda PendingGameMode ($47) │ ├─ non-zero ── pending attract boot ──┬─ PlayerLivesOrModeCount == 0 → UpdateGameModeTransition ($C897) │ │ (sound timers only) │ └─ PlayerLivesOrModeCount != 0 → UpdateTitleScreenAndStartSelection ($C773) │ + TickSoundTimers ($F1AF) │ └─ zero ── lda GameMode ($46) │ ├─ zero → UpdateAttractModeState ($C932) (attract / inter-stage sequencer) │ └─ non-zero ── lda GameplaySubmode ($4D) ├─ zero → UpdateGameplayFrame ($CD6E) (normal gameplay frame chain) └─ non-zero → UpdateStageTransition ($CFC1) (stage-intro / submode-6 path) ``` After the mode dispatch, `ProcessQueuedTask` ($EE3A) executes one queued gameplay task, then $2000 bit 7 is restored. --- ## 6. Subsystems ### 6.1 Deferred Task / VRAM Queue Systems ($0331, $0300) Two cooperating RAM queues at $0300 fill during VBlank-adjacent time and are consumed during rendering: - **Deferred gameplay tasks**: `EnqueueTask` ($EE7F) appends a tagged task byte (address in $0301, data in $0302). `ProcessQueuedTask` ($EE3A) pops one per frame: the task's high nibble selects a handler from `DeferredTaskHandlerTable` ($C000), the low nibble becomes its final data byte, and the dispatcher performs an indirect `jmp ($00):($02)` with a synthetic `$39EE` return address. Queue positions wrap at offset $2E. - **`DeferredTaskHandlerTable` ($C000)** — five typed handlers: | Task type | Handler | Address | |-----------|---------|---------| | 1 | `InitializeBoundingBox` | $F044 | | 2 | `UpdateExistingBoundingBox` | $F040 | | 3 | `TestObjectBoundingBoxOverlap` | $F138 | | 4 | `QueueSelectedHudFields` | $EF50 | | 5 | `FillNametableAndClearAttributes` | $EEBB | Entry 0 (task type 0) is deliberately routed through the mirror at $BFFE to the IRQ-vector word — see §3. - **VRAM command streams**: `QueueSingleVramByte` ($F1C5), `QueueRectangularVramTransfer` ($EFCE), and `QueuePlayerSelectVramStream` ($CA33) build queued VRAM writes; `FlushQueuedVramCommands` ($EE9C) walks the $0331 handoff pointer into `ProcessVramCommandStreamAndRestoreScroll` ($EF3C), the generic $2006/$2007 writer used by every screen-draw path. - **Sprite management**: `PerformOamDma` ($EEB0) performs the shadow-OAM ($0200–$02FF) → $2003/$4014 DMA each frame; `HideInactiveObjectSprites` ($EC17)/`HideObjectSpriteGroup` ($CA08) park inactive slots. ### 6.2 Attract / Inter-Stage Sequencer ($C932–$C9FF) `UpdateAttractModeState` ($C932) runs whenever `GameMode == 0`. It is a **countdown-driven scene sequencer** using `GameStateTimer` ($39) as a descending timer with fixed thresholds: | GameStateTimer value | Action | |------------|--------| | $84 | scoreboard/interlude phase (decrements per frame) | | $6E–$6F | stage transition setup (`AdvanceAttractModeSequence`, $C969) | | $74 | full PPU re-prep (`HideObjectSpriteGroup` $CA08 + `ClearGameplayNametable` $CA16 + `QueuePlayerSelectVramStream` $CA33) | | $68 | `jsr InitializeGameplayRam` ($CA82), **`GameMode = $01`** — gameplay begins | At $C988 it disables rendering and uploads per-stage VRAM via `StageBackgroundVramStreamPointerTable` ($C3BD, one 2-byte stream pointer per stage index) plus the fixed stream at $F8B6, guarded by `GameModeTimer` ($48) — this draws the "How High Can You Get??"-style inter-stage screens before the stage demo starts, then queues rendering via `QueueCollisionAndVramTasks` ($ECA4) and `QueueSingleVramByte` ($F1C5). ### 6.3 Title / Game Start ($C773–$C896) `UpdateTitleScreenAndStartSelection` ($C773) runs while `PendingGameMode != 0` and `PlayerLivesOrModeCount != 0`: 1. **APU gate**: audio enable `$06A2`-checked; a start-up delay in $05B4 raises `APUCTRL = $0F` (all channels on) 2. First pass (`TitleScreenInitialized` ($52) == 0): disables rendering, uploads the title background via the `TitleScreenVramCommandStream` at $F266, then builds the cursor sprite ($0200/$0201/$0203 with Y from `TitleCursorY` ($ED) = $6F) 3. **SELECT** ($20 mask): advances `TitleCursorY` by $10 (wraps $AF → $6F), sets `TitleInputRepeatTimer` ($7D) = $0A, latches `StartButtonLatch` ($6A) 4. **START** ($10 mask): latches `PreviousStartButtonState` ($EC), clears the bounding-box work area ($24–$2E), derives `SelectedPlayerMode` ($EF) from `TitleCursorY` (`lsr` ×4 minus 6) and a matching `GameModeTimer` ($1C or $18), then `jsr CollisionUpdateStep_EDFF` ($EDFF) 5. **Transition to gameplay**: reseeds the PRNG ($18 = $0F, $19 = $13), clears `PendingGameMode`, `GameMode`, and the $0580–$0587 record arrays, sets `StageOrScreenIndex` ($4A) = $01, derives `DifficultyOrRoundIndex` ($4B) from `SelectedPlayerMode` parity, sets `PlayerLivesOrModeCount` ($4C) per `TwoPlayerModeFlag` ($4E); final call `HideObjectSpriteGroup` and clears the $05B4 startup delay `UpdateGameModeTransition` ($C897) is the alternate boot chain (`PlayerLivesOrModeCount == 0`): it ticks sound timers and compares `GameStateTimer` against $75 in the same phased style as the attract sequencer. ### 6.4 Gameplay Submodes ($CD6E–$D0FF) With `GameMode != 0`, the NMI dispatches on `GameplaySubmode` ($4D): - **Submode 0 — `UpdateGameplayFrame` ($CD6E)**: the full gameplay frame chain. Player state machine on `PlayerState` ($6B, states $03/$04/$05 special-cased): - `PlayerUpdateStep_EE10` ($EE10), `DecodePlayerDirectionalInput` ($CE6C) — input decode and object ambient updates - `UpdatePlayerAuxiliarySprite` ($CEA2), `InitializePlayerStateTransition` ($D251), `DispatchPlayerState` ($D412), `ProcessStageCollisions` ($D2FC), `UpdateStageEntities` ($CEF4), `DetectPlayerLoss` ($CED4) — player state, movement, and collision phase - `HideInactiveObjectSprites` ($EC17), `UpdateGameplaySubmode` ($CF9D), `RequestLowAltitudeSound` ($CEC9) — object OAM build and sound requests - death/loss handling: `UpdatePlayerDeathSequence` ($D1AE), `UpdateBonusCountdown` ($CF37) - `TickSoundTimers` ($F1AF) - Player-state bookkeeping: `PlayerStateReferenceY` ($76) copied from shadow OAM $0210, `PlayerStatePhase` ($78), `PlayerStateTransitionActive` ($79), `PlayerStateCounter` ($7E), plus the vertical-physics groups `PlayerVerticalSpeedInteger` ($7A)/`PlayerFallThreshold` ($7B)/`PlayerVerticalAcceleration*` ($7C/$82/$83)/`PlayerGravity*` ($86/$87) - SELECT exits via the `PendingGameMode = 1` path at $CDE4 (return to title) - `PauseActive` ($EB)/`PauseDebounceTimer` ($EE) gate the frame body - **Submode != 0 — `UpdateStageTransition` ($CFC1)**: stage-intro / submode-6 path. In its early phase it gates on `PlayerState` and `PlayerStatePhase`; once the reference sprite at $0210 passes Y threshold $28, it issues a per-stage sound request into `SecondarySoundRequestFlags` ($FD) from `StageOrScreenIndex` ($02 for stage 1, $04 for stage 2, otherwise $08 — stage music), uploads a $50-byte VRAM stream (PPU address $1866 with a $00 fill value — pattern-table erase for the intro) via `QueueRectangularVramTransfer`, and sets `PlayerInputDirection` ($54) = $03. Submode 6 branches to `BeginStageClearSequence` ($D05F); entry also flows through `InitializePlayerBoundingBox` ($D04E) and `BuildPlayerCollisionBounds` ($D2BD). ### 6.5 Controllers ($F219–$F265) Standard NES serial read through $4016 with the classic DPCM-corruption filter: - `ReadControllers`: strobe on/off, then `ReadControllerPort` for X = 0 and 2 - `ReadControllerPort`: 8-bit shift loop in Y, then a two-sample consistency merge — a polarity-safe trick that keeps the record stable against mid-frame pad reads - Results in `ControllerRawState` ($14) / `ControllerFilteredState` ($15) and `$16/$17` for port 2 - The $0F-mask ladder implements "button pressed this frame" persistence for directional input vs start/select ### 6.6 Sound Engine ($F1AF–$FE55) - `UpdateAudioFrame` ($F91F): called from the NMI as the main audio service — it manages the APU channel frame state and audio effects - `TickSoundTimers` ($F1AF): per-frame channel timer service; `SoundTimerDivider` ($34) drives the frame-based `SoundTimerArray` ($35+) - Effect state and data: `Pulse1Effect*` ($F0–$F2), `AudioEffectTimer`/`AudioPitchAccumulator` ($F3/$F4), `Triangle*` ($F5/$F6), music sequencer state `MusicDataPointer*` ($F7/$F8)/`MusicNoisePosition` ($F9)/`MusicPulsePosition` ($FA)/`AudioLfoPhase` ($FB), and request flags `SoundRequestFlags` ($FC)/`SecondarySoundRequestFlags` ($FD)/`ObjectSoundRequestFlags` ($FE) - Sound-effect data at the end of the bank: `SoundEffectDataPointer*` ($FE52/$FE53), `SoundEffectNoiseOffset` ($FE54), `SoundEffectSequenceData` at $FE55 - The title/APU chain programs `APUCTRL = $0F` (all channels on) at start ### 6.7 Pseudo-Random Generator ($F1F8–$F218) An **8-byte LFSR** at $18–$1F: - Taps: bit 1 of `RandomLfsr0` XOR bit 1 of `RandomLfsr1` - Shifted bit through the full `ror` chain over all eight bytes - Stepped continuously in the reset idle loop (`Label_C6B0`) - Seeded to `$0F $13 $00…` at game start --- ## 7. Memory Map (Key Zero Page / RAM) | Address | Name | Use | |---------|------|-----| | $00–$0F | `Zp00`–`Zp0F` | Shared scratch / indirect pointers (routine-local by design) | | $10 | `PPUControlShadow` | $2000 shadow | | $11 | `PPUMaskShadow` | $2001 shadow | | $12–$13 | `PPUScrollXShadow` / `PPUScrollYShadow` | $2005 shadows | | $14/$16 | `ControllerRawState` / `Controller2RawState` | Raw serial pad bytes, ports 1/2 | | $15/$17 | `ControllerFilteredState` / `Controller2FilteredState` | Filtered pad bytes, ports 1/2 | | $18–$1F | `RandomLfsr0`–`RandomLfsr7` | 8-byte pseudo-random shift register | | $20–$23 | `CollisionBounds0`–`CollisionBounds3` | Active collision bounds | | $24–$27 | `BoundingBoxFlags` / `BoundingBoxCoord1–3` | Pending bounding box (queued tasks) | | $2C/$2E | `PlayerVerticalFraction` / `PlayerVerticalPosition` | Player Y integration | | $34–$38 | `SoundTimerDivider` / `SoundTimerArray` / `SharedActionTimer` / `PlayerSpriteVisibilityTimer` | Sound timers, shared timers | | $39 | `GameStateTimer` | Attract/title countdown | | $3A | `ObjectActiveArray` | Object active/hidden flags for sprite hiding | | $3C | `ActionCooldown` | Object cooldown | | $3E–$45 | `CollisionLeft/Top/Right/Bottom`, `CollisionMatchX/Y/Right/Bottom` | Collision results | | $46 | `GameMode` | Current mode (0 = attract, 1 = gameplay) | | $47 | `PendingGameMode` | Boot pending flag | | $48 | `GameModeTimer` | Mode timer (attract/title countdowns) | | $49 | `CurrentPlayerIndex` | Player selector | | $4A | `StageOrScreenIndex` | Current stage (1-based) | | $4B | `DifficultyOrRoundIndex` | Difficulty/round selector | | $4C | `PlayerLivesOrModeCount` | Lives / mode count | | $4D | `GameplaySubmode` | Gameplay submode selector | | $4E | `TwoPlayerModeFlag` | 2-player selection | | $4F–$51 | `DemoInputIndex` / `DemoInputTimer` / `DemoInputCode` | Attract-demo scripted input | | $52–$53 | `TitleScreenInitialized` / `LevelObjectProgress` | Boot stage, level-object progress | | $54–$57 | `PlayerInputDirection` / `PreviousInputDirection` / `PlayerPositionFraction` / `ObjectPositionFraction` | Input + fixed-point positions | | $64–$6A | `BonusTickDivider` / `BonusCounter` / `DeathAnimation*` ($66/$67) / `PlayerCollisionResult` ($68) / `CollidingObjectOamOffset` ($69) / `StartButtonLatch` ($6A) | Bonus ticks, death animation, collision results | | $6B–$70 | `PlayerState`, `PreviousPlayerState`, `PlayerFacing`, `PreviousPlayerFacing`, `PlayerClimbPhase`, `PlayerClimbDirection` | Player state machine + climbing | | $71–$77 | `PlayerMovementSubstate`, `MovementCollisionCode`, `PlayerCollisionAnchor*` ($73/$74), `PlayerClimbOffset`, `PlayerStateReferenceY` ($76), `PlayerLandingTile` | Movement, collision anchors, landing | | $78–$7E | `PlayerStatePhase` ($78), `PlayerStateTransitionActive` ($79), `PlayerVerticalSpeedInteger` ($7A), `PlayerFallThreshold` ($7B), `PlayerVerticalAcceleration` ($7C), `TitleInputRepeatTimer` ($7D), `PlayerStateCounter` ($7E) | State phase + vertical physics | | $7F–$87 | `PlayerVerticalVelocityFraction` ($7F), `PlayerVerticalAcceleration*` ($82/$83), `PlayerGravity*` ($86/$87) | Vertical velocity/gravity fixed-point | | $8C–$9F | `CarriedObjectActive` ($8C), `CollectibleSpriteTileArray` ($8D), `StageObjectDataPointer*` ($91–$93), `CollectedObject*` ($94–$9F) | Stage objects and collectibles | | $A0–$BB | `StagePrimaryObjectCount`, `ObjectMotionStep` ($A1), `StageSecondaryObject*`, `EnemyStateArray` ($A3), `EnemyAlternateStateArray` ($A6), `EnemyDirectionArray` ($B4), `EnemySpawnDelayArray` ($B9), `Platform*` state ($AC–$BE), `StageSecondaryObjectCount` ($B0), `SpawnedEnemyCount` ($B1), `StageObjectMode` ($B2) | Stage-entity pools | | $C3–$CF | `SpecialObjectIndex` ($C3), `SpecialObjectX` ($C4), `StageObjectAnimationMode` ($CE), `StageObjectOamOffset` ($CF) | Special objects | | $D0–$E6 | `BonusSpriteActive` ($D0), `VramMetatileBuffer` ($D1), `StageRopeAddress*` ($DA/$DB), `PendingLevelObjectTask` ($E7) | VRAM buffers, ropes, level-object tasks | | $E7–$EE | `DemoInputActive` ($E8), `GameplayInitializationPending` ($E9), `MovementFrameCounter` ($EA), `PauseActive` ($EB), `PreviousStartButtonState` ($EC), `TitleCursorY` ($ED), `PauseDebounceTimer` ($EE) | Flow/pause/title state | | $EF | `SelectedPlayerMode` | Player-count selection from the title | | $F0–$FE | `Pulse1Effect*` ($F0–$F2), `AudioEffectTimer` ($F3), `AudioPitchAccumulator` ($F4), `Triangle*` ($F5/$F6), `MusicDataPointer*` ($F7/$F8), `MusicNoisePosition` ($F9), `MusicPulsePosition` ($FA), `AudioLfoPhase` ($FB), `SoundRequestFlags` ($FC), `SecondarySoundRequestFlags` ($FD), `ObjectSoundRequestFlags` ($FE) | APU effects and music sequencer state | | $FF | `GameplayEventFlags` | Shared gameplay event/request flags | | $0100–$01FF | Scratch and object arrays | Object work area | | $0200–$02FF | Shadow OAM | Cleared to $FF at reset; DMA'd every frame | | $0300–$03FF | Deferred task / VRAM queue | Command-stream bytes and task dispatch slots ($0300 cursor, $0301 wrap index, $0302 entries×2, $0331 handoff pointer) | | $0520+ | Stage object RAM | Object/config groups copied in via the `ObjectConfig*` tables | | $0580–$05B5 | Per-mode record arrays | Attract/stage state records written by the title and attract sequences | | $06A2, $05B4 | APU gate / startup delay flags | Sound enable gating used at title and gameplay entry | A handful of bytes still carry deliberately neutral names (`Zp28`–`Zp5C`, `Zp58`–`Zp62`, `Zp80`–`ZpE6` members) where reuse across routines is suspected; the Pass 1 trace recorded 1,757 zero-page accesses across 186 distinct base addresses. --- ## 8. Code/Data Map Pass-1 trace statistics (recorded during generation): **6,135 decoded instructions**, **12,358 classified code bytes**, **266 reachable routine entry points**, **68 referenced PRG data labels**, **0 overlapping-instruction conflicts**. As of the current Pass 3 source, every data region is emitted as explicit `.byte` data with its ROM provenance documented — no `.incbin` blocks remain. | Region | Class | Size | |--------|-------|------| | $C000–$C66D | data (`DeferredTaskHandlerTable`, `HudDigitDescriptorTable`, `StagePlayerStartYTable`, `ObjectConfig*` tables, `StageBackgroundVramStreamPointerTable` at $C3BD) | 1,646 bytes | | $C66E–$F137 | code (reset, NMI, mode dispatch, attract, title, gameplay) | 10,954 bytes | | $F138–$F1AE | data (bounding-box overlap data) | 119 bytes | | $F1AF–$F265 | code (sound timers, LFSR, controllers) | 183 bytes | | $F266–$F91E | data (`TitleScreenVramCommandStream` at $F266, screen VRAM streams and stage data — 1,721 bytes) | 1,721 bytes | | $F91F–$F957 | code (`UpdateAudioFrame`) | 57 bytes | | $F958–$F959 | data | 2 bytes | | $F95A–$F9F7 | code | 158 bytes | | $F9F8–$FA63 | data | 108 bytes | | $FA64–$FE50 | code (deferred task / VRAM queue engine, $EE39–$FE50) | 1,005 bytes | | $FE51–$FFEF | data (`SoundEffectDataPointer*` $FE52–$FE53, `SoundEffectNoiseOffset` $FE54, `SoundEffectSequenceData` $FE55…) | 415 bytes | | $FFF0–$FFFF | IRQ byte (reused by `DeferredTaskHandlerTable` entry 0) + filler + vectors | 16 bytes | --- ## 9. Summary of Execution Flow ``` Power On / Reset │ ▼ ResetHandler ($C66E) ──── sei, clear RAM, init PPU/shadow OAM, arm attract │ ▼ enable $2000 bit 7 (NMI on) │ ▼ Idle loop $C6B0 ────────► AdvanceRandomLfsr, forever (reset path never returns) (all action is in NMI) NMI ($C6E6) [once per frame] ├─ push A/X/Y + $00–$07 save ├─ PerformOamDma ($EEB0) + FlushQueuedVramCommands ($EE9C) ├─ WriteInvertedRenderingMask ($CA7A) + UpdateAudioFrame ($F91F) ├─ ReadControllers ($F219) ├─ Mode dispatch ─┬─ PendingGameMode → UpdateTitleScreenAndStartSelection ($C773) │ │ / UpdateGameModeTransition ($C897) │ ├─ GameMode == 0 → UpdateAttractModeState ($C932) ─┐ │ └─ GameMode != 0 → UpdateGameplayFrame ($CD6E) │ │ / UpdateStageTransition ($CFC1) │ ├─ ProcessQueuedTask ($EE3A) ──► DeferredTaskHandlerTable ($C000) │ └─ restore CPU/ram state, rti │ │ InitializeGameplayRam ($CA82) at GameStateTimer ($39) = $68 ◄──────────┘ │ ▼ GameMode = 1; NMI dispatches gameplay chains per GameplaySubmode (player state machine on $6B, collision bounds, stage entities, deferred task queue, DispatchPlayerState/ProcessStageCollisions) ``` --- ## 10. Notes on Provisional Areas - **Status:** Pass 3 — all 1,035 top-level labels (`ResetHandler`, `NmiHandler`, `UpdateGameplayFrame`, `UpdateStageTransition`, `ProcessQueuedTask`, `DeferredTaskHandlerTable`, state tables, etc.) carry semantic names, and all data regions are explicit `.byte` blocks with Pass 3 structure annotations. Remaining `ZpXX` names are deliberate (routine-reused scratch), documented in §7. - **IRQ / deferred-task quirk:** The table entry 0 at mirrored $BFFE reuses the IRQ-vector word; the $FFF0 `asl a` byte is a table addressing artifact, not a real handler (see §3 and §6.1). - **Remaining unknowns:** the detailed semantics of the `ObjectConfig*` per-difficulty groups copied into $0520+ RAM, the exact music-sequence encoding inside the $F266/$F95A data streams, and a few still-neutral `ZpXX` scratch bytes. Build verification: ```bash cd "/home/tmerritt/Projects/disassembly/Donkey Kong Jr. (JU)/copilot" make # builds reconstructed.nes, then cmp against rom.nes → "Byte-identical ROM verified." ``` The rebuild is **byte-identical** to `rom.nes` (ROM SHA-256 at the top of this document). The Pass-1 source verifier (`verify_pass1.py`, which re-decoded every annotated instruction byte and re-hashed the full 24,592-byte ROM against `rom.nes`) passed during generation but was not carried into `copilot/`.