Urban Champion (JU): Pass 1 complete (CDL verified), Pass 2 complete - 519 labels renamed to semantic names via pass2.py apply - 10 zero-page semantic aliases added to locals.inc - Branch table at CB70-CBAC restructured per CDL (CB70-CB90=data, CB91-CBAC=code) - E07F-E09D region fixed (misaligned decode at E08D) - BYTE-IDENTICAL at PRG level verified Tools: - cdl_reclass.py: overrides now win over CDL conflicts - nesdis.py: fixed a: prefix parsing for JSR/JMP operands; IND mode emits no a: prefix - verify_pass1.py: all 17 top-level projects pass, no crashes
107 lines
7.3 KiB
Markdown
107 lines
7.3 KiB
Markdown
# 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 `<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 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`.
|