Files
disasm/NES PowerPad Test Card (U)/OVERVIEW.md
T
tmerritt 3ef16a6f99 Promote NES PowerPad Test Card (U) from _wip to top-level project
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
2026-10-16 19:24:12 -04:00

8.2 KiB
Raw Blame History

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:

  1. Calls a chain of initialization subroutines in sequence:
    • ResetInit ($E09B): sei/cld, disable PPU rendering, wait for two vblanks
    • ClearRAM ($E0C9): clear 8 pages of zero-page RAM
    • ClearVRAM ($E0EB): clear nametable memory
    • LoadPalette ($E107): load 32 bytes of palette data into PPU palette RAM
    • ClearVRAM_Fill23 ($E142): fill VRAM with $23 (blank tile attribute)
    • InitAPU ($E16B): initialize APU pulse/triangle channels
    • ClearVRAM_FillFF ($E187): fill VRAM with $FF
    • Five LoadPatternTable routines ($E19A–$E1CB): load pattern tables from $F000 region
    • SetPPUCTRL_NMI ($E219): enable NMI bit in PPUCTRL
  2. Main loop (Label_E058 at $E058): calls ClearScreen ($E4FA), WaitForVBlank ($E092), checks $07 (frame counter high nibble), loops back to $E000 if zero, otherwise calls ResetScroll ($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:

  1. Save registers (PHA / TXA / PHA / TYA / PHA).
  2. Calls DisableNMI ($E1FB) — clears NMI bit in PPUCTRL shadow.
  3. Increments Zp03 (frame counter).
  4. Reads PPUSTATUS ($2002), stores to Zp02 (vblank flag).
  5. Calls ReadPad ($E338) — reads controller pad data.
  6. Calls ShiftPads ($E362) — shifts pad data into state variables.
  7. Calls ProcessPads ($E398) — processes pad transitions, updates scores.
  8. Calls DrawScore ($E3E1) — draws score digits.
  9. Calls DrawLives ($E407) — draws lives indicators.
  10. 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 (Reset at $E058) spins waiting for vblank and frame counter changes.
  • PadBitTableLives at $E4B4 is a shared lookup table — DrawLives reads from base $E4B4, DrawScore reads 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, add header.s / chr.s + full-ROM make target (as e.g. Burger Time (U)), then join the root OVERVIEW.md numbered inventory.
  • Optional deeper Pass 3 work — extract the pattern pointer data at $F000+ into labeled .byte blocks; refine zero-page aliases with more specific roles.