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

13 KiB
Raw Blame History

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-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.