Files
disasm/Joypad Test Cartridge (U)/OVERVIEW.md
T
tmerritt b43b4a149d Promote Joypad Test Cartridge (U) from _wip to top-level project
Moves Joypad Test Cartridge (U) from _wip/ to Joypad Test Cartridge (U)/ with full ROM build:
- Adds header.s, chr.s, linker.cfg (HEADER+PRG+CHR segments)
- Updates Makefile to build and verify reconstructed.nes vs rom.nes
- Adds rom.nes copy + copilot/rom.nes symlink per repo convention
- Verified byte-identical build

Pass 1 complete: full code/data classification (~3.5% code / ~96.5% data)
Pass 2 complete: routine headers for all labels, semantic naming for key
routines (Reset, NmiHandler, VramWrite, PpuScrollLatch, VramPtrWrite,
RetAddrUpdate, etc.), zero-page aliases in locals.inc

Root OVERVIEW.md updated:
- Removes Joypad Test Cartridge from _wip skeleton table (22 -> 21 skeletons)
- Adds Joypad Test Cartridge as #14 in numbered inventory (BYTE-IDENTICAL, Pass 2)
- Updates summary count from 13 to 14 projects
2026-10-16 19:01:42 -04:00

8.4 KiB
Raw Blame History

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_prg.bin (16,384 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 Partial Some data table references named in code; larger data regions (test patterns, button mappings) remain as .byte blocks

4. Reset — CPU $FFFC → $C000

Reset ($C000)

On power-on or reset:

  1. Wait for three PPU warm-up vblanks — WaitVblank2 ($C005) and WaitVblank3 ($C00A) each poll $2002 bit 7, following an initial poll at $C000.
  2. Clear RAM — ClearRAM1_Loop ($C013) clears pages $0000–$07FF via indirect indexed store (STA VramPtrLo,X / STA $0100,X etc.).
  3. Initialize PPU — InitPPU_1 ($C031): STA $2000 with $10 (PPUCTRL, NMI disabled), shadow in PpuCtrlShadow ($EF); STA $2001 with $00 (PPUMASK off), shadow in PpuMaskValue ($EE).
  4. OAM DMA — OamDma ($C03F): STA $4014 with $02 (DMA from $0200–$02FF).
  5. Clear controller state — ClearController ($C044): clears $2003 (OAMADDR) and state variables.
  6. JSR PpuScrollLatch ($CE3C) — latch scroll and control registers.
  7. LDX #$FF / TXS — initialize stack pointer.
  8. JSR PpuMaskSetup ($CD2F) — set PPUMASK from shadow.
  9. Clear RAM again — ClearRAM2_Loop ($C05D) clears pages $0000–$06FF.
  10. Re-initialize PPU — InitPPU_2 ($C078): same as InitPPU_1.
  11. VRAM write — VramWriteSetup1 ($C086): set up address/length for a VRAM write via VramWrite ($CDEE).
  12. JSR ClearOAM_Routine ($C174) — clear OAM with sprite flush value $F4.
  13. JSR VramPtrWrite ($CD3F) — write VRAM data via pointer table.
  14. ToggleFlag ($C095) — toggle VramToggleFlag ($CF).
  15. JSR PpuScrollLatch ($CE3C) and JSR VramPtrWrite ($CD3F) again.
  16. 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:

  1. Save registers (PHA / TXA / PHA / TYA / PHA).
  2. JSR PpuCtrlSetup ($CD17) — latch PPUSTATUS, clear NMI bit in PpuCtrlShadow ($EF), write to PPUCTRL.
  3. JSR OamDmaSetup ($CD24) — set OAMADDR=0, trigger OAM DMA ($4014=$02).
  4. Check $0300 (vblank flag) — if nonzero, JSR VramBurstWrite ($CE4F) to burst-write VRAM data from table at $0302.
  5. JSR PpuScrollLatch ($CE3C) — latch scroll and control.
  6. Clear NmiFlagByte ($E0) to 0.
  7. 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 VramPtrTableRead at $CD99)
  • VRAM burst write table at $0302 (read by VramBurstWrite at $CE4F)
  • Button mapping / test pattern tables in the $C000–$FFFF range

9. Build

cd "_wip/Joypad Test Cartridge (U)/copilot"
make        # → ca65 + ld65 + cmp reconstructed_prg.bin prg.bin → "BYTE-IDENTICAL"

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 with VramPtrWrite ($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 — move to a top-level Joypad Test Cartridge (U)/ folder, add header.s / chr.s + full-ROM make target (as e.g. Burger Time (U)), then join the root OVERVIEW.md numbered inventory.
  • Pass 3 continuation — name data tables (button mappings at $C000–$FFFF, test patterns), refine zero-page aliases for the untraced code region.