Files
disasm/Donkey Kong Jr. (JU)/OVERVIEW.md
T
tmerritt 9aef410424 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
2026-10-16 13:46:20 -04:00

24 KiB
Raw Blame History

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:

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/.