# 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 `/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`.