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

183 lines
8.4 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.
# 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
```bash
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.