- Wild Gunman (U): Updated OVERVIEW.md to Pass 2/3, added SpriteOamTable comment at , fixed build path references - Joypad Test Cartridge (U): Updated OVERVIEW.md to Pass 2/3, fixed build path references - Donkey Kong Jr. (JU): Fixed root OVERVIEW.md summary table (was Pass 1, actually Pass 2/3 per project OVERVIEW.md) - Root OVERVIEW.md: Updated summary table (Wild Gunman & Joypad Test Cartridge → Pass 2/3), updated observation notes All projects verified byte-identical.
8.5 KiB
8.5 KiB
Joypad Test Cartridge (U) — Game Flow from Power-On
This document describes the execution flow of the disassembled Joypad Test Cartridge (U) 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 prg.bin.
1. ROM and Memory Layout
| Property | Value |
|---|---|
| Format | iNES |
| PRG | 1 × 16 KB (mapped at CPU $C000–$FFFF) |
| CHR | 1 × 8 KB |
| Mirroring | Horizontal |
| PRG bank start | CPU $C000 |
| Source file | copilot/prg.s (~1,360 lines with headers) |
| Output | reconstructed.nes (24,592 bytes) |
Interrupt vectors (CPU $FFFA–$FFFF)
| Vector | Address | Handler |
|---|---|---|
| NMI | $CCF7 | NmiHandler |
| RESET | $C000 | Reset |
| IRQ / BRK | $C000 | Reuses Reset |
2. Build Status
make in copilot/ assembles prg.s with ca65, links with ld65, and verifies against prg.bin.
Verification result: BYTE-IDENTICAL — reconstructed_prg.bin matches prg.bin exactly (all 16,384 bytes), and prg.bin matches the PRG bank of Joypad Test Cartridge (U).nes.
3. Pass Status
| Pass | Status | Description |
|---|---|---|
| Pass 1 | Complete | Code/data split — ~580 bytes traced code (~3.5%) or explicit .byte data (~96.5%) with ; CPU:$xxxx PRG:$xxxx annotations |
| Pass 2 | Complete | Routine headers added for all labeled positions; key routines semantically named (Reset, NmiHandler, VramWrite, PpuScrollLatch, etc.); zero-page aliases in locals.inc |
| Pass 3 | Complete | Data regions identified and documented (VRAM burst write table at $0302, pointer table at $0000, test pattern/button mapping data in $CE98–$FFFF); remaining Label_Cxxxx references are internal branch targets within routines |
4. Reset — CPU $FFFC → $C000
Reset ($C000)
On power-on or reset:
- Wait for three PPU warm-up vblanks —
WaitVblank2($C005) andWaitVblank3($C00A) each poll$2002bit 7, following an initial poll at $C000. - Clear RAM —
ClearRAM1_Loop($C013) clears pages $0000–$07FF via indirect indexed store (STA VramPtrLo,X/STA $0100,Xetc.). - Initialize PPU —
InitPPU_1($C031):STA $2000with $10 (PPUCTRL, NMI disabled), shadow inPpuCtrlShadow($EF);STA $2001with $00 (PPUMASK off), shadow inPpuMaskValue($EE). - OAM DMA —
OamDma($C03F):STA $4014with $02 (DMA from $0200–$02FF). - Clear controller state —
ClearController($C044): clears $2003 (OAMADDR) and state variables. JSR PpuScrollLatch($CE3C) — latch scroll and control registers.LDX #$FF / TXS— initialize stack pointer.JSR PpuMaskSetup($CD2F) — set PPUMASK from shadow.- Clear RAM again —
ClearRAM2_Loop($C05D) clears pages $0000–$06FF. - Re-initialize PPU —
InitPPU_2($C078): same as InitPPU_1. - VRAM write —
VramWriteSetup1($C086): set up address/length for a VRAM write viaVramWrite($CDEE). JSR ClearOAM_Routine($C174) — clear OAM with sprite flush value $F4.JSR VramPtrWrite($CD3F) — write VRAM data via pointer table.ToggleFlag($C095) — toggleVramToggleFlag($CF).JSR PpuScrollLatch($CE3C) andJSR VramPtrWrite($CD3F) again.- Main loop — spins, toggling $CF each iteration and calling PPU/VRAM update routines.
5. NMI / Frame Heartbeat — NmiHandler ($CCF7)
The NMI fires once per frame:
- Save registers (
PHA / TXA / PHA / TYA / PHA). JSR PpuCtrlSetup($CD17) — latch PPUSTATUS, clear NMI bit inPpuCtrlShadow($EF), write to PPUCTRL.JSR OamDmaSetup($CD24) — set OAMADDR=0, trigger OAM DMA ($4014=$02).- Check
$0300(vblank flag) — if nonzero,JSR VramBurstWrite($CE4F) to burst-write VRAM data from table at $0302. JSR PpuScrollLatch($CE3C) — latch scroll and control.- Clear
NmiFlagByte($E0) to 0. - Restore registers (
PLA / TAX / PLA / TAY / PLA / RTI).
6. Key Subroutines
| Label | Address | Description |
|---|---|---|
Reset |
$C000 | Cold-start initialization: vblank waits, RAM clear, PPU init, OAM DMA |
WaitVblank2 |
$C005 | Wait for second PPU warm-up vblank |
WaitVblank3 |
$C00A | Wait for third PPU warm-up vblank |
ClearRAM1_Loop |
$C013 | Clear RAM pages $00–$07 |
InitPPU_1 |
$C031 | Initialize PPUCTRL and PPUMASK |
OamDma |
$C03F | Trigger OAM DMA via $4014 |
ClearController |
$C044 | Clear OAMADDR and controller state |
PpuScrollLatch |
$CE3C | Latch PPU scroll X/Y and control register |
InitStack |
$C052 | Initialize stack pointer to $FF |
PpuMaskSetup |
$CD2F | Set PPUMASK from shadow register |
ClearRAM2_Loop |
$C05D | Clear RAM pages $00–$06 |
InitPPU_2 |
$C078 | Re-initialize PPU control and mask |
VramWriteSetup1 |
$C086 | Set up VRAM write parameters |
ClearOAM_Call |
$C08F | Call OAM clear routine |
VramPtrWrite1 |
$C092 | Write VRAM data via pointer table |
ToggleFlag |
$C095 | Toggle VramToggleFlag ($CF) |
SetupScroll2 |
$C097 | Latch PPU scroll and control |
VramPtrWrite2 |
$C09A | Write VRAM data via pointer table again |
ClearOAM_Routine |
$C174 | Clear OAM ($0200–$02FF) with $F4 |
NmiHandler |
$CCF7 | Per-frame NMI: PPU updates, OAM DMA, VRAM burst write |
NmiContinue |
$CD0A | Continue NMI: latch PPU scroll, clear flag |
PpuCtrlSetup |
$CD17 | Latch PPUSTATUS, write PPUCTRL from shadow |
OamDmaSetup |
$CD24 | Set OAMADDR=0, trigger OAM DMA |
PpuMaskSetup |
$CD2F | Set PPUMASK from shadow |
VramPtrWrite |
$CD3F | Write VRAM via pointer table (uses RetAddrUpdate) |
VramWriteLoop |
$CD45 | Loop writing bytes from indirect pointer to PPUDATA |
VramPtrTableRead |
$CD99 | Read from pointer table at $0000 |
RetAddrUpdate |
$CDC8 | Get return address from stack, advance by 2 |
VramWrite |
$CDEE | Set PPU address, write data bytes to PPUDATA |
VramBurstWrite |
$CE4F | Burst-write VRAM from table at $0302 |
7. Zero-Page Aliases (locals.inc)
| Alias | Address | Role |
|---|---|---|
VramPtrLo |
$00 | VRAM write pointer low byte |
VramPtrHi |
$01 | VRAM write pointer high byte |
VramLen |
$02 | VRAM write data length |
PpuScrollX |
$03 | PPU horizontal scroll |
RetAddrTempLo |
$05 | Return address temp lo |
RetAddrTempHi |
$06 | Return address temp hi |
PpuMaskShadow |
$0E | PPUMASK shadow |
PpuCtrlShadow |
$EF | PPUCTRL shadow (NMI bit cleared) |
VramToggleFlag |
$CF | Frame toggle flag |
NmiFlagByte |
$E0 | NMI flag byte |
JoyBtnStateBase |
$4E | Base for indexed button states |
FrameCounterLo |
$53 | Frame counter low byte |
FrameCounterHi |
$54 | Frame counter high byte |
Full list in copilot/locals.inc.
8. Data Regions
The PRG is approximately 3.5% code / 96.5% data. The data regions include:
- $C09E–$C173 (214 bytes): Main loop inline code/data (pointer table references, VRAM write parameters)
- $C17F–$CCF6 (2,936 bytes): Test patterns, button mapping tables, screen data
- $CD39–$CD3E (6 bytes): PPU mask toggle code fragment
- $CE98–$FFFF (12,648 bytes): Test pattern data, button mapping tables, screen layout data
Notable data structures:
- Pointer table at $0000–$0001 (read by
VramPtrTableReadat $CD99) - VRAM burst write table at $0302 (read by
VramBurstWriteat $CE4F) - Button mapping / test pattern tables in the $C000–$FFFF range
9. Build
cd "Joypad Test Cartridge (U)/copilot"
make # → ca65 + ld65 + cmp reconstructed.nes rom.nes → "Byte-identical ROM verified."
10. Notes
- The NMI handler drives per-frame updates; the main loop spins waiting for NMI phase changes.
RetAddrUpdate($CDC8) implements a return-address modification trick: it reads the return address from the stack, advances it by 2, and stores it back. This is used withVramPtrWrite($CD3F) to implement runtime-computed control flow (effectively a coroutine/threaded dispatch).- The IRQ vector points to $C000 (Reset) and is effectively unused.
- Large portions of the PRG are test pattern and button mapping data, not executable code.
11. Next Steps
- Promotion complete — moved to top-level
Joypad Test Cartridge (U)/folder with full ROM build (header.s,chr.s,linker.cfg,Makefile). - Optional deeper Pass 3 work — extract button mapping and test pattern tables from untraced regions into labeled
.byteblocks; refine remaining zero-page aliases.