- 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
13 KiB
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 cmps 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
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-seedrecovered 34 table-referenced routines. - Forced data override:
$CB70+$3C(a cascade ofbneinstructions 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.
$CB8Flsroverlaps$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.bytewith 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)
- Promotion — move to a top-level
<name>/copilot/folder, add full-ROM build (header.s + chr.s + vectors), and write the power-on flowOVERVIEW.mdin 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
-
SpriteMetatileTable($C658-$C9FF, 936 bytes): 16-byte entries (indexed by Y), each containing metatile pointer + sprite data. Referenced bylda $C658,yat $C1C1 etc. -
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. -
ControllerKeyTable($C100-$C18C, 141 bytes): Indexed by Y, maps controller input to actions. -
BranchTableOffsets($CB70-$CBAC, 61 bytes): Branch offset table forBranchTable_CB91dispatch. Confirmed DATA by CDL (never executed as opcodes). Entry point is $CB91 viajmp $CB91at $CADE. -
SpriteDMATable($DAB7-$DB47, 145 bytes): Source data for sprite DMA (referenced bylda $F940,yat $DBAA). -
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
ControllerKeyTablemapping - 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 applywith 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
- RESET/NMI:
- Zero-page semantic aliases: 10 aliases added to
locals.inc(seelocals.incheader 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)
- Controller:
- 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.