Files
disasm/Joypad Test Cartridge (U)/OVERVIEW.md
T
tmerritt ae3d71cef2 Complete Pass 3 on Wild Gunman (U) and Joypad Test Cartridge (U)
- Wild Gunman (U): Updated OVERVIEW.md to Pass 2/3, added SpriteOamTable comment at , fixed build path references
- Joypad Test Cartridge (U): Updated OVERVIEW.md to Pass 2/3, fixed build path references
- Donkey Kong Jr. (JU): Fixed root OVERVIEW.md summary table (was Pass 1, actually Pass 2/3 per project OVERVIEW.md)
- Root OVERVIEW.md: Updated summary table (Wild Gunman & Joypad Test Cartridge → Pass 2/3), updated observation notes

All projects verified byte-identical.
2026-10-16 19:11:04 -04:00

183 lines
8.5 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.nes` (24,592 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 | Complete | Data regions identified and documented (VRAM burst write table at $0302, pointer table at $0000, test pattern/button mapping data in $CE98–$FFFF); remaining `Label_Cxxxx` references are internal branch targets within routines |
---
## 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 "Joypad Test Cartridge (U)/copilot"
make # → ca65 + ld65 + cmp reconstructed.nes rom.nes → "Byte-identical ROM verified."
```
---
## 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 complete** — moved to top-level `Joypad Test Cartridge (U)/` folder with full ROM build (`header.s`, `chr.s`, `linker.cfg`, `Makefile`).
- **Optional deeper Pass 3 work** — extract button mapping and test pattern tables from untraced regions into labeled `.byte` blocks; refine remaining zero-page aliases.