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

188 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
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.