Promotes Donkey Kong Jr

Adds remaining NROM as _wip
Update gitignore to not keep object files
Adds AGENTS.md as this is an AI helped project
This commit is contained in:
tmerritt committed 2026-10-16 13:46:20 -04:00
1 parent a29b03b00b
commit 9aef410424
412 files changed
+33427 -221

No files matched your search

+324
View File
@@ -0,0 +1,324 @@
# 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/`.