11 KiB
Donkey Kong Jr. Math (U) — Promoted to Top Level (2026-10-19)
ROM: Donkey Kong Jr. Math (U)/Donkey Kong Jr. Math (U).nes (24,592 bytes, iNES). Status: Pass 1 complete — 81.9% code; Pass 2 complete; Pass 3 complete; Promoted to top level 2026-10-19. copilot/prg.s is a real disassembly: 6,589 instructions (13,416 bytes as code), 1,035 label definitions all named — 64 semantic routine names + 295 SubC<addr> entry points + 676 <Routine>_Block[_n] internal branch targets. locals.inc holds 186 zero-page slots with 13 semantic aliases added. All 13 multi-alias ZP operands substituted in prg.s. 18 data tables/structures named in the 2,968-byte data region. It builds byte-identical at the whole-ROM level (cmp reconstructed.nes rom.nes).
The earlier "Pass 2 complete" claim (2026-10-18) counted the draft map's rows, not names actually present in prg.s. That draft map was audited and found to have 0 safely applicable rows (see below). Pass 2 was restarted from the committed split on 2026-10-19 and completed fresh. Pass 3 (data table naming + full ZP substitution) completed same day. Promotion to top level completed 2026-10-19.
ROM Facts
| Property | Value |
|---|---|
| Mapper | 0 (NROM) — from the iNES header |
| PRG | 16 KB (header claims map at CPU $C000–$FFFF) |
| CHR | 8 KB |
| Mirroring | Vertical |
| Battery-backed flag | clear |
| ROM SHA-256 | 263e5e9602943cfcb28c4d4492375a0e103c1d46c6f641ffab4da6f1f8422b4a |
| PRG SHA-256 | de0612081266e6ad556d6c98dd7ad9c362ceac8531d9446239813feb1961221b |
Interrupt Vectors (taken from PRG $3FFA–$3FFF)
| Vector | Target | Note |
|---|---|---|
| NMI | $C4A9 |
per-frame heartbeat |
| RESET | $C41E |
enters at $C41E inside the first pages (the Donkey Kong Jr.-style non-$C000 entry) |
| IRQ | $FFF0 |
lands in the vector-page padding — unused placeholder |
copilot/ Contents
| File | Purpose |
|---|---|
Donkey Kong Jr. Math (U).nes |
original ROM image (reference) |
prg.bin / header.bin / chr.bin |
split image, produced by _tools/splitnes.py |
rom.nes |
symlink to ../Donkey Kong Jr. Math (U).nes (space-free reference for cmp) |
prg.s |
Pass 1/2/3: classified code/data split, 6,589 instructions, 1,035 labels all named (64 semantic + 295 SubC<addr> + 676 _Block), canonical audit comments |
symbols.inc |
shared NES hardware constants (PPU/APU/controller) |
locals.inc |
186 zero-page slots, 13 semantic aliases (e.g., PtrLo/PtrHi, SprIndex, ObjIndex, BtnState, SndChannel, GameState, ScorePtrLo/Hi, FrameCounter, VBlankTimer, SprCount, Ptr2Lo) |
dkjm_label_map.tsv / dkjm_full_map.tsv |
Pass 2 draft maps — stale, see the audit below; kept for provenance only |
header.s / chr.s |
one-line .incbin wrappers for header.bin / chr.bin |
linker.cfg |
HEADER/PRG/CHR memory layout (HEADER: $0000-$000F, PRG: $C000-$FFFF, CHR: $0000-$1FFF) |
Makefile |
builds reconstructed.nes and cmps it against rom.nes |
reconstructed.nes |
build output from the last verified build |
Header and CHR are now linked: make verifies whole-ROM byte identity against rom.nes.
Build
cd "Donkey Kong Jr. Math (U)/copilot"
make # → cmp reconstructed.nes rom.nes → "Byte-identical ROM verified."
Pass Status
| Pass | Status |
|---|---|
| Pass 1 | Complete — 6,589 instructions, 13,416 bytes as code (81.9%), 2,968 bytes as data, byte accounting exact (16,384) |
| Pass 2 | Complete — 1,035 label definitions all named (48 semantic + 295 SubC<addr> + 676 _Block); 186 zero-page slots with 13 semantic aliases in locals.inc |
| Pass 3 | Complete — 13 multi-alias ZP operands fully substituted in prg.s; 18 data tables/structures named across all data regions; hardware register operands (69) substituted via apply_symbols.py |
| Promotion | Complete (2026-10-19) — added header.s/chr.s + HEADER/PRG/CHR linker.cfg, switched make to whole-ROM cmp, moved _tools paths |
Pass 2 Status and Map Audit (corrected 2026-10-19)
The earlier "Pass 2 complete" text was wrong on every number it gave, because it read the draft map instead of the source:
- "240 semantic labels" was the map's row count (
dkjm_full_map.tsvhas 243labelrows / 240 distinct old names).prg.sactually named 64 definitions; the other 971 were stillLabel_. - "186 zero-page semantic aliases" was the count of neutral
ZpXXentries inlocals.inc. There were zero semantic zero-page aliases. - Most of the listed "key renamed routines" (
InitSystem,InitPPU,InitGameState,UpdateOAM,UpdateScroll,WritePPU,PlaySoundEffect, …) did not exist in the file.
Draft-map audit (dkjm_full_map.tsv, 243 label rows):
| Outcome | Rows | Meaning |
|---|---|---|
| already applied | 62 | the proposed name already sits at that address |
| name conflict | 2 | a different name already sits there |
| stale address | 100 | the address is not an instruction boundary in the current prg.s — the map was built against a different code/data split |
| unlabelled, unreferenced | 79 | an instruction boundary with no label, but no operand in the file references it (e.g. $C442, called InitGame_1 in the map, is really the fall-through tail of ClearZeropage_1) |
| safely applicable | 0 | no row is both insertable and corroborated by control flow |
The map is therefore kept only as provenance. Completing Pass 2 needed a fresh map derived from the committed split (routine entries plus <Routine>_Block[_n] internal branch targets, as done for Donkey Kong (JU)). This was done on 2026-10-19.
Pass 2 Naming Summary (2026-10-19)
-
48 semantic routine names preserved from Pass 1:
Reset,WaitVBlank_1,ClearZeropage_1,MainGameLoop,InitPPU_1,InitSprites_1,ClearSpriteRAM,NmiHandler,InitAPU,InitPPU_3,UpdateSprites,UpdateSprites2_1,UpdateSprites3_1,UpdateSprites4,ClearZeropage_3,InitSpriteData,UpdateSprites2_3,UpdateSprites2_4,UpdatePPUMask,InitSprites_3,InitLevel,InitLevel2,UpdateGameState,CheckLevelComplete,CheckLevelComplete2,UpdatePlayerPosition_1,UpdateSpritePosition,CheckController,UpdateGameState2_1,UpdateGameState3_1,CheckGameState2,CheckButtonPress,CheckAnswer,CheckAnswer2,CheckAnswer3_1,UpdateScore_3,UpdateScore2_1,UpdateScore3,UpdateGameState2_3,CheckAnswer3_3,UpdateGameState3_3,UpdatePlayerScore,CheckAnswer4,UpdateGameState4_3,UpdateGameState5,UpdatePlayerPosition_3,UpdateGameState6,UpdateGameState7,InitGame_3,UpdateGameState8,UpdateSpritePosition61,UpdateSpritePosition66,UpdateSpritePosition82,InitGame_4,UpdateDisplay2,PlayMusic,UpdateSoundChannel6,UpdateMusicChannel16,UpdateMusicChannel22,UpdateAPU10,IrqHandler. -
295
SubC<addr>entry points for all remainingjsr/jmptargets that lacked semantic names (e.g.,SubCC559at $C559,SubCD03Dat $D03D, etc.). -
692
<Routine>_Block[_n]internal branch targets derived from control flow within each routine (e.g.,Reset_Block,NmiHandler_Block…NmiHandler_Block_5,SubCC559_Block…SubCC559_Block_7, etc.).
Zero-Page Semantic Aliases (13, added 2026-10-19)
| Alias | ZpXX | Address | Role |
|---|---|---|---|
PtrLo |
Zp00 |
$00 | general-purpose indirect pointer low byte (sta (Zp00),y) |
PtrHi |
Zp01 |
$01 | general-purpose indirect pointer high byte |
Ptr2Lo |
Zp02 |
$02 | secondary indirect pointer low byte (lda (Zp02),y) |
VBlankTimer |
Zp10 |
$10 | vblank wait counter / frame timer (InitAPU, InitPPU) |
SprCount |
Zp39 |
$39 | sprite loop counter (dec in UpdateSprites3_1/4) |
SprIndex |
Zp4A |
$4A | sprite/object index (UpdateSprites*, score routines) |
ObjIndex |
Zp5F |
$5F | object/sprite data index (lda Zp5F,x in player/sprite routines) |
BtnState |
Zp51 |
$51 | button/input state (CheckController, CheckButtonPress, CheckAnswer) |
ScorePtrLo |
ZpA0 |
$A0 | score/VRAM pointer low byte (SubCD44A, sound routines) |
ScorePtrHi |
ZpA1 |
$A1 | score/VRAM pointer high byte |
SndChannel |
ZpEA |
$EA | sound/music channel index (ldx ZpEA in music routines) |
GameState |
ZpEC |
$EC | game state/mode (InitLevel2, UpdateGameState*, UpdateMusicChannel22) |
FrameCounter |
ZpFF |
$FF | frame counter / temp (InitGame_3, UpdateGameState6, music routines) |
Pass 3 Data Table Naming (2026-10-19)
18 data tables/structures named across all data regions:
| Label | Address Range | Size | Description |
|---|---|---|---|
LevelDataPtrTable |
$C000–$C02F | 48 bytes | Level data pointer table |
SpriteTileTable |
$C030–$C0BF | 144 bytes | Sprite tile/attribute data |
MathProblemTable |
$C0C0–$C16F | 176 bytes | Math problem definitions |
LevelLayoutData |
$C170–$C28F | 288 bytes | Level layout/screen data |
SpriteAnimTable |
$C290–$C31F | 144 bytes | Sprite animation frame data |
CollisionTable |
$C320–$C37F | 96 bytes | Collision detection data |
PaletteTable |
$C380–$C3DF | 96 bytes | Color palette data |
SpritePosInitTable |
$C3E0–$C41D | 62 bytes | Initial sprite positions |
SpriteTempData |
$C686–$C688 | 3 bytes | Temporary sprite data |
SoundTempData |
$D6C8–$D6CB | 4 bytes | Sound engine temp data |
MusicSeqPtrTable |
$F416–$F486 | 113 bytes | Music sequence pointer table |
SoundEffectTable |
$F4A3–$F4C3 | 33 bytes | Sound effect data |
VRAMCmdBuffer |
$F544–$FA5B | 1,304 bytes | VRAM command buffer for PPU updates |
FontTileData |
$F7F4–$F8FF | 268 bytes | Font/glyph tile data |
TitleScreenData |
$F904–$FA53 | 336 bytes | Title screen layout data |
APURegisterBuffer |
$FB00–$FB60 | 97 bytes | APU register shadow buffer |
ObjectSpriteTable |
$FEAA–$FFB9 | 288 bytes | Object sprite definitions |
EnemySpriteData |
$FFBA–$FFEA | 65 bytes | Enemy sprite data |
All data regions now have descriptive labels; the byte-identity audit trail is preserved.
Power-On Flow Analysis
Reset ($C41E) → sei/cld → PPU init (sta PPUCTRL #$10) → stack init (ldx #$FF/txs) → WaitVBlank_1 (poll PPUSTATUS for vblank) → ClearZeropage_1 (clear $0000–$01FF via (Zp00),y) → jsr $C47B (InitPPU_1) → set ZpED=$97 → main game loop at MainGameLoop ($C475).
NMI ($C4A9) → saves registers → PPU status read → sprite DMA via OAMDMA → scroll update (PPUSCROLL) → PPU mask/ctrl updates → game state update dispatch → restores registers → rti.
IRQ ($FFF0) → rti immediately (unused placeholder in vector-page padding).
Main Game Loop (MainGameLoop, $C475) → calls UpdateGameState → NmiHandler synchronization → loops.
Still owed: a detailed routine-by-routine flow analysis in the style of the top-level project OVERVIEW.mds.