Files
disasm/_wip/Hogan's Alley (JU)/OVERVIEW.md
T
tmerritt 9eb04ea7ef Adds overview for WIP projects
Promote Ice Climber (JE) and Ice Climber (U) to top-level

- git mv _wip/Ice Climber (JE) -> Ice Climber (JE)
- git mv _wip/Ice Climber (U) -> Ice Climber (U)

- Added header.s / chr.s (one-line .incbin each)
- Added HEADER/PRG/CHR linker.cfg with proper memory layout
- Added copilot/rom.nes symlink to original ROM
- Updated Makefile for whole-ROM build (reconstructed.nes vs rom.nes)
- Updated _tools paths from ../../../_tools/ to ../../_tools/

Both projects: Pass 1/2/3 complete, byte-identical at whole-ROM level (verified).

Root OVERVIEW.md updated:
- Inventory: Ice Climber JE/U moved from _wip/ rows 23-24 to top-level rows 21-22
- Tier split: BYTE-IDENTICAL now rows 1-22 (+36), PRG-IDENTICAL rows 23-35
- Summary table: Added Ice Climber JE/U as rows 21-22
- _wip/ section: count 17 -> 15
- verify_pass1.py scope: 19 -> 22 top-level projects

All 22 top-level projects verified byte-identical.

Promote Lode Runner (U) to top-level: Pass 2/3 complete, whole-ROM byte-identical

- git mv _wip/Lode Runner (U) -> Lode Runner (U)

- Added header.s / chr.s (one-line .incbin each)
- Added HEADER/PRG/CHR linker.cfg with proper memory layout
- Added copilot/rom.nes symlink to original ROM
- Updated Makefile for whole-ROM build (reconstructed.nes vs rom.nes)

Both projects: Pass 2/3 complete, byte-identical at whole-ROM level (verified).

Root OVERVIEW.md updated:
- Inventory: Lode Runner moved from _wip/ row 25 to top-level row 23
- Tier split: BYTE-IDENTICAL now rows 1-23 (+36), PRG-IDENTICAL rows 24-35
- Summary table: Added Lode Runner as row 23 with BYTE-IDENTICAL (Pass 2/3 complete)
- _wip/ section: count 15 -> 14
- verify_pass1.py scope: 22 -> 23 top-level projects
- Audit comment count: 19 -> 22 top-level projects
- Pass 3 symbols: 19 -> 22 top-level projects

All 23 top-level projects verified byte-identical.

TODO.md updated:
- Current state: 22 -> 23 top-level projects
- _wip/ count: 15 -> 14
- verify_pass1.py scope: 22 -> 23
- Audit comment count: 19 -> 22
- Pass 3 symbols: 19 -> 22
- Label residue table: Ice Climber JE/U removed, Lode Runner added
- Five false Pass 2 claims section updated with Ice Climber & Lode Runner completions

Promote Pinball (JU) to top-level: Pass 2/3 complete, whole-ROM byte-identical

- git mv _wip/Pinball (JU) -> Pinball (JU)

- Added header.s / chr.s (one-line .incbin each)
- Added HEADER/PRG/CHR linker.cfg with proper memory layout
- Added copilot/rom.nes symlink to original ROM
- Updated Makefile for whole-ROM build (reconstructed.nes vs rom.nes)

Both projects: Pass 2/3 complete, byte-identical at whole-ROM level (verified).

Root OVERVIEW.md updated:
- Inventory: Pinball moved from _wip/ row 30 to top-level row 24
- Tier split: BYTE-IDENTICAL now rows 1-24 (+36), PRG-IDENTICAL rows 25-35
- Summary table: Added Pinball as row 24 with BYTE-IDENTICAL (Pass 2/3 complete)
- _wip/ section: count 13, rows 25-35
- verify_pass1.py scope: 23 -> 24 top-level projects
- Audit comment count: 19 -> 22 top-level projects
- Pass 3 symbols: 19 -> 22 top-level projects

All 24 top-level projects verified byte-identical.

TODO.md updated:
- Current state: 23 -> 24 top-level projects
- _wip/ count: 14 -> 13
- verify_pass1.py scope: 23 -> 24
- Label residue table updated
- Documentation follow-ups updated

Promote Millipede (U) and Popeye (JU) (PRG 0) to top-level

Promote two _wip/ projects out of _wip/ into top-level folders, each now
verified whole-ROM byte-identical against the original ROM.

- Millipede (U): moves all copilot files up, adds header.s, chr.s,
  linker.cfg with PRG+HEADER+CHR areas, rom.nes copy, and switches the
  Makefile from PRG-only cmp to whole-ROM cmp. Makefile _tools depth
  adjusted (../../../../_tools/ -> ../../_tools/).
- Popeye (JU) (PRG 0): same promotion treatment.
- AGENTS.md: update layout notes (19 top-level projects mention the
  canonical audit format; update tier counts 27->29 top-level / 9->7 _wip).
- OVERVIEW.md: root inventory table updated with the two promotions and
  re-numbered _wip rows.
- TODO.md: handoff updates.
- Remaining _wip projects: assorted pass 2/3 edits (prg.s, locals.inc,
  reconstructed.lbl, reconstructed_prg.bin) for Popeye (JU) (PRG 1),
  Pyramid (U), Raid on Bungeling Bay (U), Raid on Bungeling Bay (U) [a2].
2026-10-19 19:58:41 -04:00

302 lines
12 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.
# Hogan's Alley (JU) — Game Flow from Power-On
This document describes the execution flow of the disassembled Hogan's Alley (JU) 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` at the PRG level.
---
## 1. Reset Vector — CPU $FFFC → $C000
**`Reset`** ($C000–$C085)
On power-on or reset, the 6502 jumps to `$C000`. The reset routine performs cold-start hardware initialization:
| Step | Code | Purpose |
|------|------|---------|
| 1–2 | `cld` / `sei` | Clear decimal mode, disable interrupts |
| 3–6 | Poll `$2002` twice (`bpl *-3`) | Wait for two full VBlank periods (PPU warm-up) |
| 7–8 | `ldx #$00` / `txs` | Initialize stack pointer to $01FF |
| 9–21 | Clear RAM $0000–$07FF | Uses Zp00/Zp01 as 16-bit pointer; clears 8 pages of 256 bytes |
| 22–23 | Warm-start signature check | Loads `$05EC` into X; if non-zero, skips full clear and sets X=$5F |
| 24 | `jsr $C221` | Initialize PPU/APU registers |
| 25 | `jsr $C231` | Additional hardware init |
| 26–27 | PPU scroll init | Write $00 to `$2005` twice (X/Y scroll) |
| 28 | `lda #$0F` / `sta $4015` | Enable all APU channels |
| 29 | `jsr $FFF7` | Call audio engine init (trampoline to $F984) |
| 30 | `lda #$90` / `sta $2000` / `sta Zp08` | PPUCTRL: NMI on, sprites at $0000, BG at $1000, + $90 shadow in Zp08 |
| 31 | `lda #$02` / `sta Zp09` | Zp09 = $02 |
| 32–36 | Copy sprite data | Copy 12 bytes from $E36A to $05F0 (OAM staging area) |
| 37 | `bmi Label_C07E` | Branch to main loop (always taken after copy) |
**Warm-start signature:** Byte at `$05EC` survives RAM clear. On soft reset, if non-zero, the game skips the full 2 KB clear and initializes X=$5F instead of X=$00.
---
## 2. Interrupt Vectors — CPU $FFFA–$FFFF
Located at the end of the bank (PRG offset $3FFA):
| Vector | Address | Handler |
|--------|---------|---------|
| NMI | $C088 | `NmiHandler` |
| RESET | $C000 | `Reset` |
| IRQ | $C000 | `Reset` (reused — unused placeholder) |
---
## 3. NMI / Frame Heartbeat — `NmiHandler` ($C088)
The NMI fires once per frame and drives the frame pacing. It implements a two-phase frame structure:
```
NmiHandler ($C088):
php / pha / txa / pha / tya / pha ; preserve registers
lda #$00 / sta $2003 ; OAMADDR = 0
lda #$02 / sta $4014 ; OAM DMA from $0200
lda Zp22 ; NMI phase flag
bne Label_C0AB ; if phase 1: skip game logic
jsr $C2BD ; update game objects
jsr $C24E ; update targets
jsr $C3A8 ; update shots
jsr $C282 ; update sprites
jsr $C3D0 ; update HUD/score
Label_C0AB: ; phase 1 entry / phase 0 exit
ldy #$01 / sty Zp22 / sty Zp20 ; set phase=1, clear main flag
pla / tay / pla / tax / pla / plp / rti ; restore, return
```
**Phase 0 (Zp22=0):** Full game logic update — objects, targets, shots, sprites, HUD.
**Phase 1 (Zp22=1):** Minimal — only OAM DMA, then returns. The main loop toggles Zp22.
---
## 4. Main Game Loop — $C07E
After initialization, control reaches the per-frame loop at **$C07E**:
```
Label_C07E:
lda Zp20 ; main loop flag (set by NMI phase 0)
bne Label_C065 ; if set, run frame logic
jsr $C6BD ; attract/demo mode logic
jmp $C07E ; loop
```
When NMI phase 0 completes, it sets **Zp20 = $01** (at $C076 in `Label_C070`). The main loop sees this, branches to **`Label_C065`** ($C065), which dispatches based on game state:
```
Label_C065:
lda Zp24 ; game mode/state
beq Label_C06D ; if 0: audio-only frame (jsr $FFF4)
lda Zp26 ; sub-state
beq Label_C070 ; if 0: gameplay frame
Label_C06D:
jsr $FFF4 ; audio engine update (trampoline to $FFD4)
rts
Label_C070:
jsr $C337 ; controller input
jsr $C0B8 ; state dispatcher
inc Zp2F ; frame counter
lda #$00 / sta Zp20 / sta Zp22 ; clear flags for next frame
```
**`Label_C0B8` ($C0B8)** — State dispatcher:
```
lda Zp24
beq Label_C0BF ; if game mode 0: title/gameplay
jmp $C156 ; else: other modes (game over, etc.)
```
**`Label_C0BF` ($C0BF)** — Title/Gameplay dispatcher:
```
lda Zp16 / and #$10 / beq Label_C11A ; check bit 4 of Zp16
ldy $05FC / bne Label_C11F ; check $05FC
iny / sty $05FC
ldy Zp25 / cpy #$07 / beq Label_C106 ; state 7
cpy #$03 / beq Label_C0E4 ; state 3
cpy #$04 / beq Label_C0E4 ; state 4
cpy #$0A / beq Label_C0E4 ; state A
cpy #$0F / bne Label_C11F ; state F
```
States 3, 4, A → **`Label_C0E4`** (gameplay init); State 7 → **`Label_C106`**; others → **`Label_C11F`** (continue gameplay).
---
## 5. Key Subsystems
### 5.1 Gameplay Initialization — `Label_C0E4` ($C0E4)
```
ldx #$05
Label_C0E6:
lda Zp2A,x / sta $0430,x ; copy Zp2A–Zp2F to $0430–$0435
dex / bpl Label_C0E6
jsr $C14A ; copy stack page $01FF to $0217
jsr $CDB4 ; initialize round data
lda #$25 / sta Zp45 ; set timer
jsr $CE4B ; spawn targets
lda Zp25 / sta Zp1C ; save state
lda #$07 / sta Zp25 ; set state = 7
jmp $C10E
```
### 5.2 State 7 Handler — `Label_C106` ($C106)
```
lda #$11 / sta Zp25 ; set state = $11
lda #$30 / sta Zp1D ; set timer
```
### 5.3 Main Gameplay Loop — `Label_C10E` ($C10E)
```
jsr $C746 ; update targets
jsr $D291 ; update shots
jsr $FFF4 ; audio engine
jmp $D2B4 ; update sprites/OAM
```
### 5.4 Controller Input — `$C337`
Reads controller via `$4016` (strobe + shift), stores button state in Zp16/Zp17.
### 5.5 Audio Engine — `$FFF4` / `$FFF7`
Trampolines at $FFF4 (update) and $FFF7 (init) jump to banked code at $F9D4/$F984.
### 5.6 Target System — `$C746`, `$CDB4`, `$CE4B`
Manages the 5–6 shootable targets (cans, birds, etc.) — position, state, scoring.
### 5.7 Shot System — `$D291`, `$D2B4`
Player shots: tracking, collision with targets, scoring.
---
## 6. Memory Map (Key Zero Page / RAM)
| Address | Name (from `locals.inc`) | Use |
|---------|--------------------------|-----|
| $00–$01 | `Zp00` / `Zp01` | 16-bit pointer for RAM clear (Reset) |
| $08 | `Zp08` | PPUCTRL shadow ($90) |
| $09 | `Zp09` | Init value $02 |
| $16 | `Zp16` | Controller buttons (bit 4 = start pressed) |
| $1C | `Zp1C` | Saved game state |
| $1D | `Zp1D` | Timer/counter |
| $20 | `Zp20` | Main loop flag (set by NMI phase 0) |
| $22 | `Zp22` | NMI phase flag (0=game logic, 1=OAM only) |
| $24 | `Zp24` | Game mode (0=title/play, non-zero=other) |
| $25 | `Zp25` | Sub-state (3,4,7,A,F,11, etc.) |
| $26 | `Zp26` | Sub-state flag |
| $29 | `Zp29` | Reset signature ($B6) |
| $2A–$2F | `Zp2A`–`Zp2F` | Gameplay params, copied to $0430 |
| $2F | `Zp2F` | Frame counter |
| $30 | `Zp30` | Frame ready flag (set by NMI) |
| $45 | `Zp45` | Gameplay timer ($25 init) |
| $0430–$0435 | — | Gameplay parameter shadow |
| $05EC | — | Warm-start signature byte |
| $05F0–$05FB | — | OAM staging (12 bytes from $E36A) |
| $05FC | — | Title screen state |
| $06F0 | — | Reset signature mirror |
| $4014 | OAMDMA | OAM DMA trigger |
| $4015 | APUSTATUS | APU channel enable |
| $4016 | JOY1 | Controller 1 strobe/read |
---
## 7. Object Pools / Data Structures
| Region | Purpose |
|--------|---------|
| $0430–$0435 | Gameplay parameters (target positions, timers) |
| $05F0–$05FB | Sprite/OAM data for 4–6 objects |
| $0400–$04FF | Sprite data (positions, states) |
| $0200–$02FF | OAM buffer (DMA source) |
| $E36A–$E375 | Initial sprite data (copied to $05F0 at reset) |
---
## 8. Summary of Execution Flow
```
Power On / Reset
│
▼
Reset ($C000) ────► cld/sei, wait 2 VBlanks, init stack
│
├─ Clear $0000–$07FF (8 pages via Zp00/Zp01)
│
├─ Check $05EC warm-start signature
│ ├─ Zero: full clear, X=$00
│ └─ Non-zero: X=$5F (skip clear)
│
├─ jsr $C221, $C231 (PPU/APU init)
├─ PPU scroll = 0,0
├─ APU enable ($4015=$0F)
├─ jsr $FFF7 (audio init → $F984)
├─ PPUCTRL = $90 (NMI on)
├─ Copy $E36A→$05F0 (12 bytes sprite data)
│
▼
Main Loop ($C07E) ─────────────────────────────────────────┐
│ │
├─ lda Zp20 (NMI phase 0 flag) │
│ ├─ Zero: jsr $C6BD (attract/demo), loop │
│ └─ Non-zero: branch to Label_C065 │
│ │
▼ │
Label_C065 ($C065) ───► lda Zp24 (game mode) │
│ ├─ 0: jsr $FFF4 (audio only), rts │
│ └─ Non-zero: lda Zp26 │
│ ├─ 0: Label_C070 (gameplay frame) │
│ └─ Non-zero: jsr $FFF4 (audio), rts │
│ │
▼ │
Label_C070 ($C070) ──► jsr $C337 (controller) │
│ jsr $C0B8 (state dispatch) │
│ inc Zp2F (frame counter) │
│ clear Zp20, Zp22 │
│ │
▼ │
Label_C0B8 ($C0B8) ──► lda Zp24 │
│ ├─ 0: Label_C0BF (title/play dispatcher) │
│ └─ Non-zero: jmp $C156 (other modes) │
│ │
▼ │
Label_C0BF ($C0BF) ──► state machine on Zp25 │
│ ├─ 3,4,A: Label_C0E4 (gameplay init) │
│ ├─ 7: Label_C106 (state 7 handler) │
│ └─ else: Label_C11F (continue) │
│ │
▼ │
Gameplay states (3,4,A→7→11→...) ──► Label_C10E │
│ jsr $C746 (targets) │
│ jsr $D291 (shots) │
│ jsr $FFF4 (audio) │
│ jmp $D2B4 (sprites) │
│ │
▼ (loop back to $C07E) │
NMI ($C088) [once per frame]
├─ Preserve registers
├─ OAM DMA ($4014 from $0200)
├─ lda Zp22 (phase flag)
│ ├─ 0: jsr $C2BD (objects), $C24E (targets), $C3A8 (shots),
│ │ $C282 (sprites), $C3D0 (HUD)
│ └─ 1: skip game logic
├─ ldy #$01 / sty Zp22 / sty Zp20 (toggle phase, signal main)
├─ Restore registers, rti
```
---
## 9. Naming and Residue
**Pass 1 complete** — 9,810 lines, 568 `Label_` definitions, canonical audit comments, 41.8% code.
**Pass 2 outstanding** — Root inventory: *needs CDL reclass*. The 55 seeded routine entries from table references include `$E9F1` which was a false positive (data table base, not dispatch) and was forced back to data 2026-10-19. 31 of the remaining 54 have no direct `jsr`/`jmp` reference.
**Pass 3 outstanding** — No semantic zero-page aliases; `locals.inc` holds 171 neutral `ZpXX` names.
**Indirect dispatch** (not followed statically):
- `jmp ($0014)` at $C360
- `jmp ($0610)` at $F927
- `jmp ($0612)` at $F941
**Promotion outstanding** — Add `header.s`/`chr.s` + HEADER/PRG/CHR `linker.cfg`, switch `make` to whole-ROM `cmp`, move `_tools` paths from `../../../_tools/` to `../../_tools/`.
Build verification: `make` in `copilot/` produces `reconstructed_prg.bin`, compared against `prg.bin` (PRG byte-identical).