- 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.
183 lines
8.5 KiB
Markdown
183 lines
8.5 KiB
Markdown
# 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.
|