Files
disasm/Urban Champion (JU)/OVERVIEW.md
T
tmerritt 4b9cb7231c Urban Champion (JU): Promote to top-level with full-ROM build
- 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
2026-10-18 20:10:03 -04:00

192 lines
13 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.
# 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`.