- Moved from _wip/ to top-level - Added header.s, chr.s (16-byte iNES header + 8KB CHR) - Updated linker.cfg for HEADER/PRG/CHR segments - Updated Makefile for full-ROM comparison (reconstructed.nes vs rom.nes) - Fixed tool paths from ../../../_tools/ to ../../_tools/ - Added data_label_map.tsv for Pass 3 data table naming - Full-ROM byte-identical verified: make passes, verify_pass1.py passes Pass 1: Complete (CDL-verified, 33.9% code) Pass 2: Complete (519 labels renamed, 10 zero-page aliases) Pass 3: Complete (55 data tables named, 6 major structures documented) Build: BYTE-IDENTICAL at full-ROM level
192 lines
13 KiB
Markdown
192 lines
13 KiB
Markdown
# Urban Champion (JU) — Pass 3 Complete
|
||
|
||
ROM: `_wip/Urban Champion (JU)/Urban Champion (JU).nes` (24,592 bytes, iNES). Status: **Pass 3 complete — all data tables/structures named, PRG byte-identical.** `copilot/prg.s` contains a classified disassembly with per-instruction audit comments (`; CPU:$xxxx PRG:xxxx AA BB`), unreached bytes emitted as explicit `.byte` blocks with provenance. The build is **BYTE-IDENTICAL** at the PRG level (33.9% code / 5,546 bytes, 66.1% data / 10,838 bytes).
|
||
|
||
|
||
## ROM Facts
|
||
|
||
| 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 | `3ec5ac8db623dae34a0f4a4909f0af4569b2af0cee6c56bae512c87b7cff8749` |
|
||
| PRG SHA-256 | `13e7f2a77b01784a479895c0535357978af32f5139d971edf5f5b1936585357e` |
|
||
|
||
## Interrupt Vectors (taken from PRG $3FFA–$3FFF)
|
||
|
||
| Vector | Target | Note |
|
||
|--------|--------|------|
|
||
| NMI | `$CA91` | per-frame heartbeat |
|
||
| RESET | `$CA00` | non-$C000 entry at $CA00 — trace from here in Pass 1 |
|
||
| IRQ | `$CA00` | redirects to RESET — unused placeholder |
|
||
|
||
## `copilot/` Contents
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `Urban Champion (JU).nes` | original ROM image (reference) |
|
||
| `prg.bin` / `header.bin` / `chr.bin` | split image, produced by `_tools/splitnes.py` |
|
||
| `prg.s` | Pass 1: classified code/data split, 2,716 instructions, 436 labels, audit comments on every emission; Pass 2: all 519 labels renamed to semantic names |
|
||
| `symbols.inc` | shared NES hardware constants (PPU/APU/controller, from the skeleton) |
|
||
| `locals.inc` | 153 zero-page aliases + 10 semantic aliases (Pass 2) |
|
||
| `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 reproduced verbatim from `header.bin`/`chr.bin` (not linked): `make` verifies **PRG bytes only** at this stage. When this project is promoted to a full disassembly, `header.s` / `chr.s` (as e.g. `Burger Time (U)`) and a full-ROM `make` target get added.
|
||
|
||
## Build
|
||
|
||
```bash
|
||
cd "_wip/Urban Champion (JU)/copilot"
|
||
make # → cmp reconstructed_prg.bin prg.bin → "BYTE-IDENTICAL"
|
||
```
|
||
|
||
## Pass 1 Details
|
||
|
||
- **Tracer seeds**: RESET ($CA00), NMI ($CA91), plus `--auto-seed` recovered 34 table-referenced routines.
|
||
- **Forced data override**: `$CB70+$3C` (a cascade of `bne` instructions that are actually a branch table/data, not code). The fall-through through this region was incorrectly classified as code; forced to data with `--data 0xCB70:60`.
|
||
- **Overlaps**: 6 overlap regions remain (e.g. `$CB8F` `lsr` overlaps `$CB91`), all in the forced-data region — the conflicting decodes are suppressed in the output.
|
||
- **Invalid opcodes**: 7 addresses reached via static trace but decoding invalid opcodes (`$C658`, `$CAF9`, `$CB4D`, `$CC61`, `$D21B`, `$DBCE`, `$DE24`). These are marked `.byte` with the invalid opcode in the audit comment.
|
||
- **Indirect jumps**: 3 `jmp ($xxxx)` sites at `$CDFB` (`$0014`), `$C318` (`$00EB`), `$C332` (`$00ED`) — targets not statically resolvable.
|
||
- **Zero-page aliases**: 153 entries in `locals.inc` (auto-generated by tracer).
|
||
|
||
## Next Steps (per `PROMPTS.md`)
|
||
|
||
1. **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.
|
||
|
||
## Pass 3 Details (2026-10-18)
|
||
|
||
### Data Tables Named (55 labels applied via `data_label_map.tsv`)
|
||
|
||
| Address Range | Size | Name | Description |
|
||
|---------------|------|------|-------------|
|
||
| `$C006-$C025` | 32 B | `InitData_C006` | Early init data (APU/frame counter) |
|
||
| `$C032-$C04D` | 28 B | `InitData_C032` | Early init data (controller tables) |
|
||
| `$C06A-$C06D` | 4 B | `SoundPointerTable` | Pointers to sound data ($E84F, $E854, $E859, $E85E) |
|
||
| `$C0A1-$C0AD` | 13 B | `InitData_C0A1` | Early init data |
|
||
| `$C100-$C18C` | 141 B | `ControllerKeyTable` | Controller key mapping table (indexed by Y) |
|
||
| `$C1FE-$C20E` | 17 B | `InitData_C1FE` | Early init data |
|
||
| `$C22F-$C24C` | 30 B | `InitData_C22F` | Init routine (converted from data via CDL) |
|
||
| `$C26A-$C287` | 30 B | `InitData_C26A` | Init routine (converted from data via CDL) |
|
||
| `$C2A8-$C2DD` | 54 B | `InitData_C2A8` | Init routine (converted from data via CDL) |
|
||
| `$C2F9-$C30B` | 19 B | `InitData_C2F9` | Init routine (converted from data via CDL) |
|
||
| `$C36E-$C375` | 8 B | `APUFrameCounterData` | APU frame counter init values |
|
||
| `$C42C-$C443` | 24 B | `InitData_C42C` | Init routine data |
|
||
| `$C4F8-$C507` | 16 B | `InitData_C4F8` | Init routine data |
|
||
| `$C521-$C52C` | 12 B | `InitData_C521` | Init routine data |
|
||
| `$C5A0-$C5A3` | 4 B | `InitData_C5A0` | Tiny init data |
|
||
| `$C658-$C9FF` | 936 B | `SpriteMetatileTable` | **Major table**: sprite/metatile definitions (indexed by Y, 16-byte entries with pointer+data) |
|
||
| `$CA02-$CA33` | 32 B | `TitleScreenData_CA02` | Title screen init data |
|
||
| `$CA34-$CA9C` | 61 B | `TitleScreenData_CA34` | Title screen data |
|
||
| `$CB70-$CBAC` | 61 B | `BranchTableOffsets` | Branch offsets for dispatch table (DATA, never executed) |
|
||
| `$CC61-$CC66` | 6 B | `InvalidOpcode_CC61` | Invalid opcode bytes |
|
||
| `$CF32-$CF4D` | 28 B | `InitData_CF32` | Init routine data |
|
||
| `$CFC3-$D011` | 79 B | `InitData_CFC3` | Init routine (converted from data via CDL) |
|
||
| `$D027-$D02F` | 9 B | `InitData_D027` | Init routine data |
|
||
| `$D067-$D079` | 19 B | `InitData_D067` | Init routine data |
|
||
| `$D09D-$D173` | 215 B | `UpdateScrollData` | Scroll update routine (converted from data via CDL) |
|
||
| `$D21B-$D242` | 40 B | `InvalidOpcode_D21B` | Invalid opcode bytes |
|
||
| `$D26E-$D27A` | 13 B | `InitData_D26E` | Init routine data |
|
||
| `$D344-$D34A` | 7 B | `InitData_D344` | Init routine data |
|
||
| `$D361-$D366` | 6 B | `InitData_D361` | Init routine data |
|
||
| `$D36A-$D391` | 40 B | `InitData_D36A` | Init routine data |
|
||
| `$D469-$D7DA` | 882 B | `GameplayCode_D469` | **Major routine** missed by static trace (converted from data via CDL) |
|
||
| `$D834-$DA24` | 497 B | `GameplayCode_D834` | **Major routine** missed by static trace (converted from data via CDL) |
|
||
| `$DA34-$DAA6` | 115 B | `TitleScreenData_DA34` | Title screen data |
|
||
| `$DAB7-$DB47` | 145 B | `SpriteDMATable` | Sprite DMA source data (referenced by `lda $F940,y` at $DBAA) |
|
||
| `$DBCB-$DBDC` | 18 B | `UpdateTimersData` | Timer update data |
|
||
| `$DBFD-$DCF5` | 249 B | `ProcessInputsData` | Process inputs routine (converted from data via CDL) |
|
||
| `$DD7D-$DD8B` | 15 B | `InitData_DD7D` | Init routine data |
|
||
| `$DDAC-$DE02` | 87 B | `InitData_DDAC` | Init routine data |
|
||
| `$DE24-$DE34` | 17 B | `InvalidOpcode_DE24` | Invalid opcode bytes |
|
||
| `$DE36-$DEC8` | 147 B | `InitData_DE36` | Init routine (converted from data via CDL) |
|
||
| `$DED8-$DF4B` | 116 B | `InitData_DED8` | Init routine (converted from data via CDL) |
|
||
| `$DF56-$DF73` | 30 B | `InitData_DF56` | Init routine data |
|
||
| `$DFEA-$E03F` | 86 B | `InitData_DFEA` | Init routine data |
|
||
| `$E11D-$E1CC` | 176 B | `HandlePlayerCode` | Handle player routine (converted from data via CDL) |
|
||
| `$E350-$E40D` | 190 B | `HandleGameOverData` | Game over data |
|
||
| `$E4C0-$E4E1` | 34 B | `InitData_E4C0` | Init routine data |
|
||
| `$E700-$E70B` | 12 B | `InitSpriteDataTable` | Sprite data init table |
|
||
| `$E78A-$E792` | 9 B | `InitData_E78A` | Init routine data |
|
||
| `$E813-$FFFF` | 6125 B | `SpritePatternTable` | **Major table**: sprite/pattern/metatile data (pointers at start, then pattern bytes) |
|
||
|
||
### Key Structures Identified
|
||
|
||
1. **`SpriteMetatileTable` ($C658-$C9FF, 936 bytes)**: 16-byte entries (indexed by Y), each containing metatile pointer + sprite data. Referenced by `lda $C658,y` at $C1C1 etc.
|
||
|
||
2. **`SpritePatternTable` ($E813-$FFFF, 6125 bytes)**: Large sprite/pattern data. First 112 bytes ($E813-$E880) are 56 word pointers into the table ($E84F, $E854, $E859...), followed by pattern/palette data.
|
||
|
||
3. **`ControllerKeyTable` ($C100-$C18C, 141 bytes)**: Indexed by Y, maps controller input to actions.
|
||
|
||
4. **`BranchTableOffsets` ($CB70-$CBAC, 61 bytes)**: Branch offset table for `BranchTable_CB91` dispatch. Confirmed DATA by CDL (never executed as opcodes). Entry point is $CB91 via `jmp $CB91` at $CADE.
|
||
|
||
5. **`SpriteDMATable` ($DAB7-$DB47, 145 bytes)**: Source data for sprite DMA (referenced by `lda $F940,y` at $DBAA).
|
||
|
||
6. **`SpritePatternTable` ($E813-$FFFF, 6125 bytes)**: Massive pattern table with 56 word pointers at start, then sprite/background pattern data.
|
||
|
||
### CDL-Verified Code Regions (Converted from Data)
|
||
The following regions were misclassified as data by static trace but are CODE per Mesen CDL:
|
||
- $D469-$D7DA (882 B): `GameplayCode_D469` - massive gameplay routine
|
||
- $D834-$DA24 (497 B): `GameplayCode_D834` - gameplay routine
|
||
- $DBFD-$DCF5 (249 B): `ProcessInputsData` - process inputs
|
||
- $DE36-$DEC8 (147 B): init routine
|
||
- $DED8-$DF4B (116 B): init routine
|
||
- $E11D-$E1CC (176 B): `HandlePlayerCode` - player handling
|
||
|
||
### Remaining Work
|
||
- Document structure of `SpriteMetatileTable` (entry format, pointer layout)
|
||
- Document structure of `SpritePatternTable` (pointer table + pattern format)
|
||
- Document `ControllerKeyTable` mapping
|
||
- Release provisional flags on converted code regions
|
||
- Promotion to top-level
|
||
|
||
## Pass 2 Details (2026-10-18)
|
||
|
||
- **Label renaming**: **All 519 labels renamed** to semantic names using `pass2.py apply` with a mapping file (`label_map.tsv`). Key renamed routines:
|
||
- RESET/NMI: `Reset`, `NmiHandler`
|
||
- System init: `InitSystem`, `WaitPPUReady`, `InitStack`, `LoadGameConfig`, `InitVariables`, `SetupInitialState`
|
||
- RAM clearing: `ClearRAM`, `ClearRAMLoop`
|
||
- APU init: `InitAPU`, `ClearControllerState`, `InitAPURegisters`, `SetAPUVolume`, `SetAPUSweep`, `InitAPUWaveform`
|
||
- Controller: `UpdateControllerState`, `CheckControllerStart`, `ProcessControllerInput`
|
||
- Game loop: `MainGameLoop`, `TitleScreenLoop`, `GameModeDispatcher`, `GameplayFrame`, `CheckRoundEnd`, `UpdateRoundTimer`
|
||
- NMI: `NmiHandler`, `NmiFrameUpdate`, `NmiVBlankOnly`, `NmiExit`, `UpdateScroll`
|
||
- Sprites: `InitSprites`, `SpriteDMA`
|
||
- Players: `UpdatePlayers`, `HandlePlayer1`, `HandlePlayer2`, `UpdatePlayerPositions`
|
||
- Collision: `CheckCollisions`
|
||
- Sound: `InitSound`, `PlaySoundEffect`, `UpdateMusic`
|
||
- **Zero-page semantic aliases**: 10 aliases added to `locals.inc` (see `locals.inc` header for full list):
|
||
- Controller: `Joy1State` ($F3), `Joy2State` ($EF), `Joy1DPad` ($F6), `Joy2DPad` ($F7)
|
||
- Game state: `GameMode` ($24), `RoundIndex` ($26), `DispatchIndex` ($C0)
|
||
- Player arrays: `PlayerState` ($16), `PlayerInput` ($25), `PlayerData` ($2A)
|
||
- Pointers: `TempPtrLo` ($00), `TempPtrHi` ($01)
|
||
- **No `Label_` entries remain** — all 519 labels have semantic names.
|
||
|
||
## Mesen CDL Verification (2026-10-18)
|
||
|
||
A Code Data Logger capture was taken during gameplay (~15 seconds, all buttons pressed). Key findings vs. static trace:
|
||
|
||
| Static Trace | CDL (Gameplay) | Disposition |
|
||
|--------------|----------------|-------------|
|
||
| `$C006-$C025` (32 B data) | **CODE** | Missed init routine — convert to code in Pass 2 |
|
||
| `$C032-$C04D` (28 B data) | **CODE** | Missed init routine — convert to code in Pass 2 |
|
||
| `$C22F-$C24C` (30 B data) | **CODE** | Missed routine — convert to code in Pass 2 |
|
||
| `$C26A-$C287` (30 B data) | **CODE** | Missed routine — convert to code in Pass 2 |
|
||
| `$C2A8-$C2DD` (54 B data) | **CODE** | Missed routine — convert to code in Pass 2 |
|
||
| `$C2F9-$C30B` (19 B data) | **CODE** | Missed routine — convert to code in Pass 2 |
|
||
| `$CFC3-$D011` (79 B data) | **CODE** | Large missed routine — convert to code in Pass 2 |
|
||
| `$D09D-$D173` (215 B data) | **CODE** | Large missed routine — convert to code in Pass 2 |
|
||
| `$D469-$D7DA` (882 B data) | **CODE** | Very large missed routine — convert to code in Pass 2 |
|
||
| `$DBFD-$DCF5` (249 B data) | **CODE** | Missed routine — convert to code in Pass 2 |
|
||
| `$DE21-$DEC8` (168 B data) | **CODE** | Missed routine — convert to code in Pass 2 |
|
||
| `$DED8-$DF4B` (116 B data) | **CODE** | Missed routine — convert to code in Pass 2 |
|
||
| `$E11D-$E1CC` (176 B data) | **CODE** | Missed routine — convert to code in Pass 2 |
|
||
| `$E350-$E40D` (190 B data) | **CODE** | Missed routine — convert to code in Pass 2 |
|
||
| **`$CB70-$CB90` (data)** | **DATA** | Confirmed: branch offsets, never executed as opcodes |
|
||
| **`$CB91-$CBAC` (data)** | **CODE** (jump target + linear) | **Critical**: `jmp $CB91` at `$CADE` enters here; executes linearly as a branch table. Current static trace has entire `$CB70-$CBAC` as one data block with dead branch-target labels inside. Pass 2 must split: `$CB70-$CB90` = data (branch offsets), `$CB91-$CBAC` = code (real branch table entered via `jmp $CB91`). |
|
||
|
||
CDL coverage: 28.2% code (4,614 bytes) vs static trace 33.9% (5,546 bytes). The static trace over-reaches via fall-through into the branch cascade; the CDL confirms the cascade is dead (branch at `$CB6E` always taken to `$CB4E`), but reveals `$CB91-$CBAC` is live code entered via the `jmp` at `$CADE`.
|