Moves NES PowerPad Test Card (U) from _wip/ to NES PowerPad Test Card (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 (~7.2% code / ~92.8% data) Pass 2 complete: routine headers for all ~50 labels, semantic naming for key routines (Reset, NmiHandler, VramWrite, ReadPad, ProcessPads, etc.), zero-page aliases in locals.inc (Zp00-Zp15) Pass 3 complete: data tables named (PaletteData at , PadBitTableLives at ) Root OVERVIEW.md updated: - Removes NES PowerPad Test Card from _wip skeleton table (21 → 20 skeletons) - Adds NES PowerPad Test Card as #15 in numbered inventory (BYTE-IDENTICAL, Pass 2/3) - Updates summary count from 14 to 15 projects
8.2 KiB
NES PowerPad Test Card (U) — Game Flow from Power-On
This document describes the execution flow of the disassembled NES PowerPad Test Card (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,650 lines with headers) |
| Output | reconstructed_prg.bin (16,384 bytes) |
Interrupt vectors (CPU $FFFA–$FFFF)
| Vector | Address | Handler |
|---|---|---|
| NMI | $E06E | NmiHandler |
| RESET | $E000 | Reset |
| IRQ / BRK | $E000 | 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 NES PowerPad Test Cart (U) [!].nes.
3. Pass Status
| Pass | Status | Description |
|---|---|---|
| Pass 1 | Complete | Code/data split — entire PRG bank classified as traced code (~7.2%) or explicit .byte data (~92.8%) with ; CPU:$xxxx PRG:$xxxx annotations |
| Pass 2 | Complete | Routine headers added for all labeled positions; key routines semantically named (Reset, NmiHandler, VramWrite, ReadPad, ProcessPads, etc.); zero-page aliases in locals.inc |
| Pass 3 | Complete | Data tables/structures identified and named (PaletteData at $E121, PadBitTableLives at $E4B4); remaining Label_Exxxx references are internal branch targets within routines |
4. Reset — CPU $FFFC → $E000
Reset ($E000)
On power-on or reset:
- Calls a chain of initialization subroutines in sequence:
ResetInit($E09B): sei/cld, disable PPU rendering, wait for two vblanksClearRAM($E0C9): clear 8 pages of zero-page RAMClearVRAM($E0EB): clear nametable memoryLoadPalette($E107): load 32 bytes of palette data into PPU palette RAMClearVRAM_Fill23($E142): fill VRAM with $23 (blank tile attribute)InitAPU($E16B): initialize APU pulse/triangle channelsClearVRAM_FillFF($E187): fill VRAM with $FF- Five
LoadPatternTableroutines ($E19A–$E1CB): load pattern tables from $F000 region SetPPUCTRL_NMI($E219): enable NMI bit in PPUCTRL
- Main loop (
Label_E058at $E058): callsClearScreen($E4FA),WaitForVBlank($E092), checks$07(frame counter high nibble), loops back to $E000 if zero, otherwise callsResetScroll($E22D) and jumps back to $E058.
Key point: The main loop is driven by vblank waits and frame counter checks. All per-frame work (controller reading, pad processing, screen drawing) happens inside the NMI handler.
5. NMI / Frame Heartbeat — NmiHandler ($E06E)
The NMI fires once per frame:
- Save registers (
PHA / TXA / PHA / TYA / PHA). - Calls
DisableNMI($E1FB) — clears NMI bit in PPUCTRL shadow. - Increments
Zp03(frame counter). - Reads
PPUSTATUS($2002), stores toZp02(vblank flag). - Calls
ReadPad($E338) — reads controller pad data. - Calls
ShiftPads($E362) — shifts pad data into state variables. - Calls
ProcessPads($E398) — processes pad transitions, updates scores. - Calls
DrawScore($E3E1) — draws score digits. - Calls
DrawLives($E407) — draws lives indicators. - Restore registers (
PLA / TAX / PLA / TAY / PLA / RTI).
6. Key Subroutines
| Label | Address | Description |
|---|---|---|
Reset |
$E000 | Cold-start initialization and main loop entry |
NmiHandler |
$E06E | Per-frame NMI: pad read, process, score/lives draw |
WaitForVBlank |
$E092 | Waits for vblank flag, clears it |
ResetInit |
$E09B | Cold-start hardware init (PPU, APU, vblank waits) |
ClearRAM |
$E0C9 | Clears 8 pages of zero-page RAM |
ClearVRAM |
$E0EB | Clears nametable memory with zeros |
LoadPalette |
$E107 | Loads 32-byte palette into PPU palette RAM |
ClearVRAM_Fill23 |
$E142 | Fills VRAM with blank tile value $23 |
InitAPU |
$E16B | Initializes APU pulse/triangle channels |
ClearVRAM_FillFF |
$E187 | Fills VRAM with $FF |
LoadPatternTable1–5 |
$E19A–$E1CB | Load pattern tables from $F000 region |
VramWrite |
$E1CC | Generic VRAM write via indirect pointer |
EnableNMI |
$E1F1 | Enables NMI bit in PPUCTRL shadow |
DisableNMI |
$E1FB | Disables NMI bit in PPUCTRL shadow |
SetPPUCTRL_NMI |
$E219 | Sets NMI bit ($80) in PPUCTRL shadow |
ResetScroll |
$E22D | Resets PPU scroll X/Y to zero |
InitRoundParams |
$E236 | Initializes round parameters |
DrawPlayer1Pad |
$E270 | Draws player 1 pad indicators |
DrawPlayer2Pad |
$E2B0 | Draws player 2 pad indicators |
ClearRows |
$E2F0 | Clears screen rows with blank tiles |
ClearGameVars |
$E32D | Clears game variables $06–$09 |
ReadPad |
$E338 | Reads controller pad via shift register |
ShiftPads |
$E362 | Shifts pad data into state variables |
ProcessPads |
$E398 | Processes pad transitions, updates scores |
DrawScore |
$E3E1 | Draws score digits on screen |
DrawLives |
$E407 | Draws lives indicators on screen |
ShiftVar1–4 |
$E433–$E47E | Shift pad bits into score accumulators |
UpdatePads |
$E47F | Updates pad state, checks button transitions |
LoadPatternTable6–7 |
$E4A0–$E4B3 | Load additional pattern tables |
ProcessPadBits |
$E4FA | Process pad bit data for transitions |
SetAPUNoise |
$E52E | Sets APU noise channel |
ClearAPUNoise |
$E534 | Clears APU noise channel |
ClearScreen |
$E53A | Clears screen with blank tile $5F |
7. Zero-Page Aliases (locals.inc)
| Alias | Address | Role |
|---|---|---|
Zp00 |
$00 | PPUCTRL shadow register |
Zp01 |
$01 | PPUMASK shadow register |
Zp02 |
$02 | VBlank flag |
Zp03 |
$03 | Frame counter / NMI tick |
Zp05 |
$05 | Score/digit temp |
Zp06 |
$06 | Pad 1 state byte 1 |
Zp07 |
$07 | Pad 1 state byte 2 |
Zp08 |
$08 | Pad 1 state byte 3 |
Zp09 |
$09 | Pad 1 state byte 4 |
Zp0A |
$0A | Pad 2 state byte 1 |
Zp0B |
$0B | Pad 2 state byte 2 |
Zp0C |
$0C | Pad temp / swap variable |
Zp0D |
$0D | Pad mode / draw flag |
Zp0E |
$0E | Shift counter / temp |
Zp0F |
$0F | Delay counter |
Zp10 |
$10 | VRAM pointer low byte |
Zp11 |
$11 | VRAM pointer high byte |
Zp12 |
$12 | Score digit 1 accumulator |
Zp13 |
$13 | Score digit 2 accumulator |
Zp14 |
$14 | Score digit 3 accumulator |
Zp15 |
$15 | Score digit 4 accumulator |
Full list in copilot/locals.inc.
8. Data Tables
| Table | Address | Description |
|---|---|---|
PaletteData |
$E121 | 32-byte palette data loaded into PPU palette RAM |
PadBitTableLives |
$E4B4 | 70-byte pad bit lookup table; DrawScore reads from offset $0C ($E4C0) |
| Pattern pointers | $F000–$F013 | Pointer pairs loaded by LoadPatternTable1–5 routines |
9. Build
cd "_wip/NES PowerPad Test Card (U)/copilot"
make # → ca65 + ld65 + cmp reconstructed_prg.bin prg.bin → "BYTE-IDENTICAL"
10. Notes
- The PRG is approximately 7% code / 93% data. Most of the PRG bank ($C000–$DFFF, $E54F–$FFFF) is $FF-filled test pattern data.
- The NMI handler drives per-frame updates; the main loop (
Resetat $E058) spins waiting for vblank and frame counter changes. PadBitTableLivesat $E4B4 is a shared lookup table —DrawLivesreads from base $E4B4,DrawScorereads from offset $0C ($E4C0).- IRQ vector points to $E000 (Reset) and is effectively unused.
11. Next Steps
- Promotion — move to a top-level
NES PowerPad Test Card (U)/folder, addheader.s/chr.s+ full-ROMmaketarget (as e.g.Burger Time (U)), then join the rootOVERVIEW.mdnumbered inventory. - Optional deeper Pass 3 work — extract the pattern pointer data at $F000+ into labeled
.byteblocks; refine zero-page aliases with more specific roles.