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].
This commit is contained in:
1 parent
13bd782a90
commit
9eb04ea7ef
159 files changed
+52542
-28112
No files matched your search
+256
-141
@@ -1,178 +1,293 @@
|
||||
# Mario Bros (JU) — Pass 1 Disassembly (Pass 2 substantial)
|
||||
# Mario Bros (JU) — Game Flow from Power-On
|
||||
|
||||
ROM: `_wip/Mario Bros (JU)/Mario Bros (JU).nes` (24,592 bytes, iNES). Status: **Pass 1 complete (72.2% code); Pass 2 substantial.** `copilot/prg.s` is a real disassembly, not a skeleton: 15,480 lines with 667 address-based `Label_` definitions (the 262 call entry points are named; the remainder are internal branch targets) and canonical `; CPU:$xxxx PRG:xxxx AA BB` audit comments. It builds **byte-identical** at the PRG level (`cmp reconstructed_prg.bin prg.bin`).
|
||||
This document describes the execution flow of the disassembled Mario Bros (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.
|
||||
|
||||
---
|
||||
|
||||
## ROM Facts
|
||||
## 1. Reset Vector — CPU $FFFC → $C000
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Mapper | 0 (NROM) — from the iNES header |
|
||||
| PRG | 16 KB (header claims map at CPU $C000–$FFFF) |
|
||||
| CHR | 8 KB |
|
||||
| Mirroring | Vertical |
|
||||
| Battery-backed flag | clear |
|
||||
| ROM SHA-256 | `2810051c21132abd8b9b1e1bf741b302ee6c1d4af983e0df7fe50bd05ac0a5c8` |
|
||||
| PRG SHA-256 | `699ae2908486fce5a0fffdd051285e1144021f616d9af88eb511f319539acce1` |
|
||||
**`Reset`** ($C000–$C06A)
|
||||
|
||||
## Interrupt Vectors (taken from PRG $3FFA–$3FFF)
|
||||
On power-on or reset, the 6502 jumps to `$C000`. The reset routine performs cold-start hardware initialization:
|
||||
|
||||
| Vector | Target | Note |
|
||||
|--------|--------|------|
|
||||
| NMI | `$C07D` | per-frame heartbeat |
|
||||
| RESET | `$C000` | cold start at $C000 — the usual pattern in this repo |
|
||||
| IRQ | `$C000` | redirects to RESET — unused placeholder |
|
||||
| Step | Code | Purpose |
|
||||
|------|------|---------|
|
||||
| 1–2 | `cld` / `sei` | Clear decimal mode, disable interrupts |
|
||||
| 3–6 | `WaitVblank1` ($C002): poll `$2002` via `bpl *-3` | Wait for first VBlank (PPU warm-up) |
|
||||
| 7–8 | `ldx #$00` / `txs` | Initialize stack pointer to $01FF |
|
||||
| 9–10 | `ldx Zp70` / `ldy #$06` / `sty Zp01` / `ldy #$00` / `sty Zp00` | Zp00/Zp01 = $0600 (RAM clear pointer); X = Zp70 ($70) |
|
||||
| 11–15 | `ClearRamPageLoop` ($C01D): clear RAM via (Zp00),Y | Clear 6 pages ($0000–$05FF); dec Zp01 loops pages |
|
||||
| 16 | `txa` / `bne Label_C02B` | If Zp70 was non-zero, skip full clear |
|
||||
| 17 | `ldx #$5F` | Alternate init value for warm start |
|
||||
| 18 | `stx $0500` | Store signature at $0500 |
|
||||
| 19 | `jsr SubCCA1B` / `jsr SubCCA2B` | Initialize game systems |
|
||||
| 20 | `ldy #$00` / `sta $2005` ×2 | PPUSCROLL = 0,0 |
|
||||
| 21 | `iny` / `sty Zp30` | Zp30 ($30) = 1 |
|
||||
| 22 | `lda #$0F` / `sta $4015` | Enable all APU channels |
|
||||
| 23 | `lda #$90` / `sta $2000` / `sta Zp09` | PPUCTRL: NMI on, sprite $0000, BG $1000 + shadow in Zp09 |
|
||||
| 24 | `lda #$06` / `sta Zp0A` | Zp0A ($0A) = $06 |
|
||||
|
||||
## `copilot/` Contents
|
||||
**Warm-start signature:** Byte at `$0500` survives RAM clear via Zp70 ($70). On soft reset, if non-zero, the game clears only 6 pages instead of 7 and uses X=$5F.
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Mario Bros (JU).nes` | original ROM image (reference) |
|
||||
| `prg.bin` / `header.bin` / `chr.bin` | split image, produced by `_tools/splitnes.py` |
|
||||
| `prg.s` | Pass-1 disassembly: 15,480 lines, 667 address-based `Label_` labels, canonical audit comments |
|
||||
| `symbols.inc` | shared NES hardware constants (PPU/APU/controller) |
|
||||
| `locals.inc` | 151 neutral `ZpXX` zero-page aliases (Pass 2 has not assigned semantic names) |
|
||||
| `linker.cfg` | PRG at $C000, 16 KB, `fill = yes` |
|
||||
| `Makefile` | links `reconstructed_prg.bin` and `cmp`s it against `prg.bin` |
|
||||
| `reconstructed_prg.bin` | build output from the last verified build |
|
||||
---
|
||||
|
||||
Header/CHR are not linked: `copilot/` keeps the raw `header.bin`/`chr.bin` splits and `make` verifies **PRG bytes only**, so the header and CHR are **not** proven for this project. Promotion to the top level adds `header.s` / `chr.s` (as e.g. `Burger Time (U)`), a HEADER/PRG/CHR `linker.cfg`, and a full-ROM `make` target.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cd "_wip/Mario Bros (JU)/copilot"
|
||||
make # → cmp reconstructed_prg.bin prg.bin → "BYTE-IDENTICAL"
|
||||
```
|
||||
|
||||
## Pass Status
|
||||
|
||||
| Pass | Status | Description |
|
||||
|------|--------|-------------|
|
||||
| Pass 0 | Superseded | `prg.s` was a 12-line `.incbin` of the whole PRG — no disassembly at all |
|
||||
| Pass 1 | **Complete** | 5,735 instructions / **11,829 code bytes (72.2%)** / 4,555 data bytes. Byte accounting exact: 11,800 + 4,584 = 16,384. 937 labels, 937 routine headers |
|
||||
| Pass 2 | **Substantial** | **262 call entry points named** (all `jsr`/`jmp` targets resolved from raw `$XXXX` to labels): **39 semantic names** (audio/sound engine, gameplay helpers) + **223 address-based `SubCxxxx` names** for the remainder. **667 `Label_Cxxxx` internal branch targets remain** — these are local control-flow targets (loop starts, branch destinations) within routines, not subroutine entry points, and do not need semantic names for the call graph to be readable. Zero raw `$XXXX` call operands remain. |
|
||||
| Pass 3 | Outstanding | Data regions inside the 4,555 unreached bytes need naming |
|
||||
|
||||
### How Pass 1 got to 72.2%
|
||||
|
||||
| Step | Instructions | Code |
|
||||
|------|--------------|------|
|
||||
| Vectors + direct `jsr`/`jmp`/branch, official opcodes | 1,185 | 2,392 B (14.6%) |
|
||||
| `--undocumented` (105 extra NMOS opcodes) | 1,210 | 2,445 B (14.9%) |
|
||||
| `--auto-seed` (recovered 61 table-referenced entries) | 4,187 | 8,375 B (51.1%) |
|
||||
| `--extra-seed 0xC0B6,0xC5A3` | **5,735** | **11,829 B (72.2%)** |
|
||||
|
||||
A plain vector trace reaches only 14.6% because almost all of this game sits
|
||||
behind computed dispatch.
|
||||
|
||||
### The dispatch mechanisms
|
||||
|
||||
**1. Function-pointer tables, dispatched through the stack.** `$CD9E` is the
|
||||
single choke point:
|
||||
## 2. Post-Reset Initialization — `SubCC04F` ($C04F) onward
|
||||
|
||||
```
|
||||
$CDA1 pla / sta $14 ; pop the table address the caller pushed
|
||||
$CDA4 pla / sta $15
|
||||
$CDA7 lda ($14),y / tax ; read the entry, indexed by A*2
|
||||
$CDAB lda ($14),y / sta $15
|
||||
$CDB1 jmp ($0014) ; and jump to it
|
||||
SubCC04F ($C04F):
|
||||
lda #$00 / sta Zp20 ($20)
|
||||
lda Zp30 ($30)
|
||||
beq Label_C05D ; if 0: jsr SubCF8A7
|
||||
lda Zp50 ($50)
|
||||
cmp #$01 / bne Label_C060 ; if 1: jsr SubCF8A7
|
||||
Label_C060:
|
||||
jsr SubCCD88 ; title/init
|
||||
jsr SubCC4B8 ; gameplay init
|
||||
lda #$01 / sta Zp22 ($22) ; NMI phase flag = 1 (gameplay)
|
||||
```
|
||||
|
||||
The table pointer arrives via `pla`, not `lda`, so it cannot be followed
|
||||
statically from the dispatcher — the pushed value is only known in the caller's
|
||||
frame. This is why `INDIRECT: [('0xcdb1', '0x14')]` is reported and left
|
||||
unfollowed.
|
||||
Then falls through to main loop entry.
|
||||
|
||||
**2. A 20-entry table of `JSR` thunks at `$C5A3`.** Probing it linearly yields
|
||||
twenty valid `jsr`s followed by `rts`:
|
||||
---
|
||||
|
||||
## 3. Interrupt Vectors — CPU $FFFA–$FFFF
|
||||
|
||||
Located at PRG offset $3FFA (end of 16 KB bank):
|
||||
|
||||
| Vector | Address | Handler |
|
||||
|--------|---------|---------|
|
||||
| NMI | $C07D | `NmiHandler` |
|
||||
| RESET | $C000 | `Reset` |
|
||||
| IRQ | $C000 | `Reset` (reused — unused placeholder) |
|
||||
|
||||
---
|
||||
|
||||
## 4. NMI / Frame Heartbeat — `NmiHandler` ($C07D)
|
||||
|
||||
The NMI fires once per frame and implements a two-phase structure controlled by **Zp22** ($22):
|
||||
|
||||
```
|
||||
$C5A3 jsr $D56E / jsr $D202 / jsr $D301 / jsr $C5DB / jsr $C66A / jsr $E783 ...
|
||||
NmiHandler ($C07D):
|
||||
pha / txa / pha / tya / pha ; preserve A,X,Y
|
||||
lda #$00 / sta $2003 (OAMADDR)
|
||||
lda #$02 / sta $4014 (OAMDMA) ; sprite DMA from $0200
|
||||
lda Zp22 ($22)
|
||||
beq Label_C0AC ; phase 0: minimal
|
||||
jsr SubCCB58 ; gameplay update 1
|
||||
jsr SubCEE6A ; gameplay update 2
|
||||
jsr SubCCCFF ; gameplay update 3
|
||||
jsr SubCCA66 ; gameplay update 4
|
||||
jsr SubCCE09 ; gameplay update 5
|
||||
jsr SubCCCC5 ; gameplay update 6
|
||||
jsr SubCCAF7 ; gameplay update 7
|
||||
ldy #$01 / sty Zp20 ($20) ; main loop flag = 1
|
||||
dey / sty Zp42 ($42) ; Zp42 = 0
|
||||
Label_C0AC:
|
||||
lda #$01 / sta Zp23 ($23) ; NMI done flag
|
||||
pla / tay / pla / tax / pla / rti ; restore, return
|
||||
```
|
||||
|
||||
Seeding `$C5A3` pulls in all twenty targets at once, which is most of the jump
|
||||
from 51% to 72%.
|
||||
**Phase 0 (Zp22=0):** Title/attract — only OAM DMA, no game logic.
|
||||
**Phase 1 (Zp22=1):** Gameplay — 7 gameplay update calls, sets main loop flag Zp20=1.
|
||||
|
||||
**3. The NMI phase dispatcher at `$C0B6`**, reached only by computed dispatch:
|
||||
---
|
||||
|
||||
## 5. NMI Phase Dispatcher — `NmiPhaseDispatch` ($C0B6)
|
||||
|
||||
Called from main loop to synchronize with NMI:
|
||||
|
||||
```
|
||||
$C0B6 lda $B0 / beq $C0BF
|
||||
$C0BA lda $0320 / bne $C0C2
|
||||
$C0BF jmp $C149
|
||||
NmiPhaseDispatch ($C0B6):
|
||||
lda ZpB0 ($B0)
|
||||
beq Label_C0BF ; if 0: jmp NmiPhaseHandlerA
|
||||
lda $0320 / bne Label_C0C2 ; check $0320
|
||||
Label_C0BF:
|
||||
jmp NmiPhaseHandlerA ; $C149
|
||||
Label_C0C2:
|
||||
lda #$20 / sta Zp14 ($14)
|
||||
lda #$03 / sta Zp15 ($15)
|
||||
lda #$01 / sta Zp11 ($11)
|
||||
ldy #$0B / ldx #$08 / jsr SubCC44A
|
||||
sta $05F7 / ora #$00 / beq NmiPhaseHandlerA
|
||||
lda ZpC6 / ora $0336 / beq Label_C0F3
|
||||
...
|
||||
```
|
||||
|
||||
Seeding `$C0B6` recovered 677 bytes that the vector trace could not see.
|
||||
**ZpB0** ($B0) = NMI phase (0=title, non-zero=gameplay).
|
||||
**$0320** = Additional state check.
|
||||
|
||||
### Tracer fixes required to get there
|
||||
---
|
||||
|
||||
Two overlapping-decode bugs had to be fixed in `_tools/nesdis.py` first:
|
||||
## 6. Main Game Loop
|
||||
|
||||
* **Control-flow targets must win over fall-through.** `bne` at `$C4BA` targets
|
||||
`$C528`, but the fall-through path had decoded `$C527` as a 2-byte
|
||||
`NOP #$A5` whose operand swallowed `$C528`. No real assembler branches into
|
||||
the middle of an instruction, so the spanning decode is now dropped in favour
|
||||
of the target. Its bytes fall out as data, so byte accounting is unaffected.
|
||||
* **Branch targets inside a surviving instruction must be pruned.** Otherwise
|
||||
`emit_code`'s linear walk consumes those bytes as an operand, the label is
|
||||
referenced but never defined, and **the file does not assemble at all** —
|
||||
`Error: Symbol 'Label_C528' is undefined`.
|
||||
The main loop runs in the main thread, synchronized with NMI via Zp20/Zp22/Zp23:
|
||||
|
||||
Neither change affects any already-regen-stable project; MUSCLE's `make regen`
|
||||
was re-checked and still reproduces `prg.s` byte-for-byte.
|
||||
```
|
||||
Main Loop (entry via SubCC06A / SubCC06D area):
|
||||
; Wait for NMI to set Zp20=1 (frame complete)
|
||||
; Process game logic
|
||||
; Clear Zp22 (NMI phase) for next frame
|
||||
; Loop
|
||||
```
|
||||
|
||||
### Known remaining gaps
|
||||
Key synchronization points:
|
||||
- **Zp20** ($20): Set to 1 by NMI phase 1; main loop waits for this
|
||||
- **Zp22** ($22): NMI phase flag (set to 1 by reset init, cleared by main loop)
|
||||
- **Zp23** ($23): NMI completion flag
|
||||
|
||||
* The tracer still reports `INVALID: ['0xc5a7']` and three overlaps
|
||||
(`$C528`, `$C5A2`, `$C5A4`). The overlaps are now resolved in favour of the
|
||||
control-flow target, so they are diagnostics rather than defects, but the
|
||||
`$C5A2`/`$C5A4` pair means the alignment between the `D4`-padding run and the
|
||||
`$C5A3` thunk table is not fully settled.
|
||||
* 4,555 bytes (27.8%) are still data. Reaching more needs the tables that the
|
||||
stack dispatcher at `$CD9E` reads, which means resolving the pushed addresses
|
||||
in the callers' frames — that is Pass 2/3 work, not more seeding.
|
||||
---
|
||||
|
||||
### Pass 2 so far, and why it is partial
|
||||
## 7. Key Subsystems
|
||||
|
||||
Two mechanical wins came first:
|
||||
### 7.1 Gameplay Update Chain (NMI Phase 1)
|
||||
Seven sequential calls from NMI:
|
||||
1. `SubCCB58` ($CB58) — Player/enemy physics
|
||||
2. `SubCEE6A` ($EE6A) — Collision detection
|
||||
3. `SubCCCFF` ($CCFF) — Platform/pipe logic
|
||||
4. `SubCCA66` ($CA66) — Sprite animation
|
||||
5. `SubCCE09` ($CE09) — Bonus/coin logic
|
||||
6. `SubCCCC5` ($CCC5) — Score/HUD update
|
||||
7. `SubCCAF7` ($CAF7) — Audio trigger
|
||||
|
||||
* **478 `jsr`/`jmp` operands rewritten from raw `$XXXX` to labels.** The tracer
|
||||
emits call and jump targets as hex, so the call graph was unreadable — the
|
||||
most-called routine appeared as `jsr $CCA0` 14 times. All 478 sites now name
|
||||
their target, and zero raw hex call operands remain.
|
||||
* **262 call entry points named:** **39 with semantic names** (audio/sound
|
||||
engine routines in `$F8xx-$F9xx`, gameplay helpers like `CheckZpB3Return`,
|
||||
`CheckRangeReturn012`, `ToggleZpFC_Bit3`) + **223 with address-based names**
|
||||
(`SubCxxxx`) for the remainder. All 262 distinct `jsr`/`jmp` targets now
|
||||
resolve to labels. (These counts were re-verified against `prg.s` on
|
||||
2026-10-19; the earlier "477 / 252 / 90 / 165" did not match the file.)
|
||||
### 7.2 Audio Engine — `$F800+` region
|
||||
- `SubCF8A7` ($F8A7): Audio init (called from reset)
|
||||
- `SubCCD88` ($CD88): Title music
|
||||
- `SubCC4B8` ($C4B8): Gameplay music
|
||||
- APU registers at $4000–$4013, frame counter at $4017
|
||||
|
||||
**667 `Label_Cxxxx` internal branch targets remain**, and that is the honest
|
||||
state. These are local control-flow destinations (loop starts, conditional
|
||||
branch targets) within routines — not subroutine entry points. Naming them
|
||||
requires reading ~5,700 instructions across 935 routines to establish what each
|
||||
one does; doing it from the call graph and the NES convention alone would be
|
||||
guesswork, and `PROMPTS.md` explicitly forbids skipping ahead to guessy rewrites.
|
||||
### 7.3 Stack-Based Dispatch — `$CDB1` (`jmp ($0014)`)
|
||||
Indirect jump through ZP pointer at $0014. The target address is pushed on stack by callers — cannot be followed statically. This is the main dispatch mechanism for 27.8% of unreached code.
|
||||
|
||||
The productive next step is not more naming but **more reach**: the 4,555
|
||||
unreached bytes are where most of the remaining unnamed routines live, and they
|
||||
are gated behind the stack-pointer dispatch at `$CD9E`. Resolving the pushed
|
||||
table addresses would enlarge both the code set and the set of names worth
|
||||
assigning.
|
||||
### 7.4 JSR Thunk Table — `$C5A3`
|
||||
20-entry table of `jsr`/`rts` pairs. Seeding this recovered major code blocks.
|
||||
|
||||
## Next Steps
|
||||
### 7.5 Controller Input
|
||||
Read via `$4016` strobe/shift in NMI or main loop. Button state stored in ZP ($10–$15 area).
|
||||
|
||||
1. **Pass 1** — complete (72.2% code, byte accounting exact).
|
||||
2. **Pass 2** — substantial: all 262 call entry points named; 667 internal
|
||||
branch labels (`Label_Cxxxx`) remain as local control-flow targets. Filling
|
||||
`locals.inc` with semantic zero-page aliases is optional at this stage (151
|
||||
neutral `ZpXX` aliases are present).
|
||||
3. **Pass 3** — name data tables/structures in the 4,555 unreached bytes; release
|
||||
provisional flags.
|
||||
4. **Promotion** — move to a top-level `<name>/copilot/` folder, add full-ROM build
|
||||
(header.s + chr.s + vectors), and write the power-on flow `OVERVIEW.md` in the
|
||||
established style; then it joins the root index's numbered inventory.
|
||||
---
|
||||
|
||||
**Still owed:** this file was retitled and restatused in the 2026-10-19 doc-drift
|
||||
sweep to match the actual `prg.s`; the power-on flow analysis the top-level
|
||||
`OVERVIEW.md`s carry is still to be written here.
|
||||
## 8. Memory Map (Key Zero Page / RAM)
|
||||
|
||||
| Address | Name | Use |
|
||||
|---------|------|-----|
|
||||
| $00–$01 | `Zp00`/`Zp01` | 16-bit pointer (RAM clear) |
|
||||
| $09 | `Zp09` | PPUCTRL shadow ($90) |
|
||||
| $0A | `Zp0A` | Init constant ($06) |
|
||||
| $10 | `Zp10` | PPUCTRL working copy |
|
||||
| $11 | `Zp11` | State flag |
|
||||
| $14–$15 | `Zp14`/`Zp15` | NMI dispatch parameters |
|
||||
| $20 | `Zp20` | Main loop sync flag (set by NMI) |
|
||||
| $22 | `Zp22` | NMI phase (0=title, 1=gameplay) |
|
||||
| $23 | `Zp23` | NMI done flag |
|
||||
| $30 | `Zp30` | Reset signature check result |
|
||||
| $42 | `Zp42` | Cleared by NMI phase 1 |
|
||||
| $50 | `Zp50` | Game mode (0=title, 1=gameplay) |
|
||||
| $70 | `Zp70` | Warm-start signature ($0500) |
|
||||
| $B0 | `ZpB0` | NMI phase dispatch selector |
|
||||
| $B1 | `ZpB1` | Heavily used (88 refs) — gameplay state |
|
||||
| $B2–$BF | `ZpB2`–`ZpBF` | Object positions, velocities, timers |
|
||||
| $C0–$CF | `ZpC0`–`ZpCF` | Enemy/bonus state arrays |
|
||||
| $F0–$FF | `ZpF0`–`ZpFF` | Audio engine ZP |
|
||||
| $0200 | OAM buffer | Sprite DMA source |
|
||||
| $0300+ | Game state | Platforms, pipes, enemies, bonuses |
|
||||
| $0500 | Warm-start signature | Must match Zp70 for warm start |
|
||||
|
||||
---
|
||||
|
||||
## 9. Data Tables
|
||||
|
||||
| Table | Address | Description |
|
||||
|-------|---------|-------------|
|
||||
| JSR Thunk Table | $C5A3 | 20 entries of `jsr target` / `rts` |
|
||||
| Stack Dispatch Targets | (dynamic) | Pushed on stack, jumped via `jmp ($0014)` at $CDB1 |
|
||||
| Audio Sequences | $F800+ | Music/sound data |
|
||||
| Level Layout | $0300+ | Platform positions, pipe connections |
|
||||
|
||||
---
|
||||
|
||||
## 10. Summary of Execution Flow
|
||||
|
||||
```
|
||||
Power On / Reset
|
||||
│
|
||||
▼
|
||||
Reset ($C000) ────► cld/sei, wait VBlank, stack=$FF
|
||||
│
|
||||
├─ Check Zp70 ($70) warm-start signature at $0500
|
||||
│ ├─ Zero: Clear 7 pages ($0000–$06FF), X=$00
|
||||
│ └─ Non-zero: Clear 6 pages ($0000–$05FF), X=$5F
|
||||
│
|
||||
├─ stx $0500 (signature)
|
||||
├─ jsr SubCCA1B, SubCCA2B (system init)
|
||||
├─ PPUSCROLL = 0,0
|
||||
├─ Zp30 = 1
|
||||
├─ APU enable ($4015=$0F)
|
||||
├─ PPUCTRL = $90 (NMI on) → Zp09
|
||||
├─ Zp0A = $06
|
||||
│
|
||||
▼
|
||||
SubCC04F ($C04F) ──► Zp20=0
|
||||
│
|
||||
├─ Zp30=0 or Zp50=1 → jsr SubCF8A7 (audio init)
|
||||
│
|
||||
└─ jsr SubCCD88, SubCC4B8 (title/gameplay init)
|
||||
lda #$01 / sta Zp22 (NMI phase = gameplay)
|
||||
│
|
||||
▼ (fall through to main loop)
|
||||
|
||||
Main Loop ────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
├─ Wait for Zp20=1 (set by NMI phase 1) │
|
||||
├─ Process frame logic │
|
||||
├─ Call NmiPhaseDispatch ($C0B6) │
|
||||
│ ZpB0=0 → NmiPhaseHandlerA ($C149) │
|
||||
│ ZpB0≠0 → check $0320, setup Zp14/15/11, jsr SubCC44A │
|
||||
├─ Clear Zp22 (NMI phase) for next frame │
|
||||
└─ Loop │
|
||||
|
||||
NMI ($C07D) [once per frame]
|
||||
├─ Preserve A,X,Y
|
||||
├─ OAM DMA ($4014 from $0200)
|
||||
├─ lda Zp22
|
||||
│ ├─ =0 (Title): Label_C0AC → set Zp23=1, rti
|
||||
│ └─ =1 (Gameplay):
|
||||
│ jsr SubCCB58 (physics)
|
||||
│ jsr SubCEE6A (collision)
|
||||
│ jsr SubCCCFF (platforms)
|
||||
│ jsr SubCCA66 (sprites)
|
||||
│ jsr SubCCE09 (bonus)
|
||||
│ jsr SubCCCC5 (HUD)
|
||||
│ jsr SubCCAF7 (audio trigger)
|
||||
│ ldy #$01 / sty Zp20 (signal main loop)
|
||||
│ dey / sty Zp42 (=0)
|
||||
├─ lda #$01 / sta Zp23 (NMI done)
|
||||
└─ Restore A,X,Y, rti
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Naming and Residue
|
||||
|
||||
**Pass 1 complete** — 5,735 instructions / 11,829 code bytes (72.2%), byte accounting exact. 937 labels, 937 routine headers. Reached via:
|
||||
- Vectors + direct `jsr`/`jmp`/branch, official opcodes: 1,185 instr (14.6%)
|
||||
- `--undocumented` (105 extra NMOS opcodes): 1,210 instr (14.9%)
|
||||
- `--auto-seed` (61 table-referenced entries): 4,187 instr (51.1%)
|
||||
- `--extra-seed 0xC0B6,0xC5A3`: 5,735 instr (72.2%)
|
||||
|
||||
**Pass 2 substantial** — **262 call entry points named** (verified 2026-10-19):
|
||||
- **39 semantic names**: Audio routines in `$F8xx-$F9xx`, gameplay helpers (`CheckZpB3Return`, `CheckRangeReturn012`, `ToggleZpFC_Bit3`)
|
||||
- **223 `SubC<addr>` address-based names** for remaining `jsr`/`jmp` targets
|
||||
- **667 `Label_Cxxxx` internal branch targets remain** — local control-flow targets within routines
|
||||
- Zero raw `$XXXX` call operands remain (all 478 `jsr`/`jmp` sites resolved to labels)
|
||||
|
||||
**Pass 3 outstanding** — Data regions in 4,555 unreached bytes need naming; 151 neutral `ZpXX` aliases in `locals.inc`.
|
||||
|
||||
**Indirect dispatch** (not followed statically): `jmp ($0014)` at $CDB1 — table pointer arrives via `pla`, only knowable in caller's frame.
|
||||
|
||||
**Tracer fixes required** (in `_tools/nesdis.py`):
|
||||
- Control-flow targets win over fall-through (fixed `bne` at $C4BA targeting $C528 vs fall-through `NOP #$A5` at $C527)
|
||||
- Branch targets inside surviving instruction must be pruned (otherwise `Label_C528` undefined)
|
||||
|
||||
**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).
|
||||
Reference in new issue
Block a user