# Urban Champion (JU) — Pass 2 Complete ROM: `_wip/Urban Champion (JU)/Urban Champion (JU).nes` (24,592 bytes, iNES). Status: **Pass 2 complete — all 519 labels renamed to semantic names, zero-page semantic aliases added, 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. **Pass 3** — name data tables/structures; release provisional flags. 2. **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 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`.