Commit Graph
22 Commits
Author SHA1 Message Date
tmerritt 71ada15de0 Promote Donkey Kong (JU): Pass 3 complete, whole-ROM byte-identical
Pass 3 named the 10 data regions in prg.s, then the project was promoted
out of _wip/ so `make` now proves header + PRG + CHR against the original
ROM instead of only the PRG bank.

Data regions named:
  LevelPtrTable        $C000-$C03B  (60 B)    level header data
  ObjectPtrTable       $C03C-$C043  (8 B)     4 pointer pairs
  LevelDataPtrTable    $C044-$C79D  (1882 B)  level data pointers
  SpriteInitPtrTable   labelled at $C4B0      see residue note
  UnreachedCode_D650   $D650-$D696  (71 B)    unreached code
  UnreachedCode_E144   $E144-$E165  (34 B)    unreached code
  MusicDataTable       $F55B-$FA47  (1261 B)  music/sound data
  APUDataTable         $FAE6-$FB74  (143 B)   APU pulse/noise tables
  SfxDataTable         $FE59-$FFEF  (407 B)   sound-effect data
  VectorTable          $FFF6-$FFFF  (10 B)    padding + NMI/RESET/IRQ

Promotion (per the AGENTS.md checklist):
  - git mv "_wip/Donkey Kong (JU)" "Donkey Kong (JU)"
  - add copilot/rom.nes -> ../Donkey Kong (JU).nes symlink
  - add header.s / chr.s (one-line .incbin of header.bin / chr.bin)
  - linker.cfg gains HEADER ($0000/16 B) and CHR ($0000/8 KB)
  - Makefile switched from `cmp reconstructed_prg.bin prg.bin` to
    `cmp reconstructed.nes rom.nes`
  - no _tools path change (this Makefile never referenced _tools/)
    and no `make regen` pipeline exists for it

Doc corrections:
  - project OVERVIEW.md said Mirroring Vertical; header byte 6 = $00
    means Horizontal (the root index was right) — fixed
  - root OVERVIEW.md: row 18 now BYTE-IDENTICAL, tier paragraph and
    summary table refreshed (they had drifted around the Lunar Pool and
    Urban Champion promotions), new per-project detail entry
  - counts corrected to 19 top-level / 17 _wip in OVERVIEW.md,
    TODO.md and AGENTS.md

Residue recorded rather than guessed:
  - Pass 2 is partial: 919 Label_ definitions remain (214 semantic
    labels applied); noted in TODO.md so nobody reads the promotion as
    full semantic naming
  - SpriteInitPtrTable's label sits at $C4B0: the routine at $C807 reads
    $C4A7,x/$C4A8,x, but that table starts mid-row and a label there
    would split the canonical 16-byte audit-comment line

Verification:
  make clean && make  -> "Byte-identical ROM verified."
  verify_pass1.py     -> 19/19 top-level build, Donkey Kong (JU) OK
                         (6014 instr / 12505 B), all comments canonical

Doc-drift sweep: build tiers, mirroring, and stale status tails

Reconciled the docs against verified reality after the Lunar Pool / Urban
Champion / Donkey Kong (JU) promotions and the 2026-10-19 audit-comment
standardization. No source behaviour changed except the two build fixes below.

Build tier (whole-ROM vs PRG-only)
  - AGENTS.md: the 17 `_wip/` projects are real disassemblies at the PRG-build
    tier, not "skeletons"; header.s/chr.s/rom.nes are promotion additions;
    verify_pass1.py covers the top-level projects.
  - Root OVERVIEW.md: `_wip` prose, audit-comment narrative and summary table
    refreshed (37.5% figure, SEEDED vs OK wording, re-trace false alarm).
  - Promoted projects whose build blocks still claimed `make` emits
    `reconstructed_prg.bin` compared to `prg.bin` (Balloon Fight, Baseball,
    CIRCUS, Clu Clu Land, Excitebike, NES PowerPad, Pac-Man (USA), Tennis):
    now describe the whole-ROM `reconstructed.nes` vs `rom.nes`.
  - Lunar Pool / Urban Champion / CIRCUS also had stale `cd "_wip/..."`
    build paths and "Promotion" items; retargeted to their top-level folders.

Mirroring (header byte 6 bit 0: 0 = Horizontal, 1 = Vertical)
  - Corrected DK Jr. prose "horizontal mirroring (flag 6 = $01)" -> vertical,
    and both `_wip/Pac-Man` variant notes "Both use vertical mirroring" ->
    horizontal (both are byte-6 = $00).
  - Re-audited every project OVERVIEW: 0 rows and 0 prose claims now disagree.

Stale status tails
  - Retitled/restatused the `_wip` OVERVIEWs to their real `prg.s` (Golf,
    Ice Climber (U), Lode Runner, Millipede, Pac-Man Tengen, Pac-Man (U),
    Popeye 0/1, Raid, Raid a2, Hogan's Alley, Mario Bros); Golf's
    skeleton-era contents rows and Next Steps corrected.
  - Dropped the skeleton-era "## Next Steps (per `PROMPTS.md`)" heading and
    the "(from the skeleton)" wording across all project OVERVIEWs.
  - Rewrote Lunar Pool / Urban Champion Next Steps (both already Pass 1-3
    complete and promoted; their tails still described pending Pass 2 and
    promotion); fixed root row 13 (Wild Gunman) and row 20 (Duck Hunt).

Also lands two code fixes carried with this sweep:
  - `_wip/Hogan's Alley (JU)`: $E9F1-$E9FD and $EA13-$EA14 reclassified as
    data with canonical audit comments (the seeded build had decoded a data
    table base as code, breaking the build since 2ae09f7). `make` ->
    BYTE-IDENTICAL again; reconstructed_prg.bin/.lbl refreshed.
  - `_wip/Pyramid (U)`: `Label_D72E` -> `NMI:` (byte-identical).

Verification:
  make (Hogan's Alley, Pyramid)      -> BYTE-IDENTICAL, BYTE-IDENTICAL
  _tools/verify_pass1.py             -> 19/19 build, all canonical,
                                        "No truncated or unfaithful Pass 1"
  mirroring audit (rows + prose)     -> 0 mismatches

Donkey Kong (JU): complete Pass 2 — all 919 Label_ definitions named

The last non-zero Label_ residue in the repo is now gone. Every one of the
919 address-derived definitions in copilot/prg.s is replaced:

* 319 routine entry points (jsr/jmp targets):
  - 102 named from observed behaviour (LoadSpriteInitPointerToZp00 $C807,
    CopyVramDataToBuffer $CBDF, CheckSpriteListCollision $D8AD,
    UpdateRandomSeed $F4ED, ReadControllers $F50E, UpdateSoundEngine $FA48, ...)
  - 217 left as SubC<addr> — honest residue where the body is too
    context-dependent to name without guessing (Golf: 108, Mario Bros: 223)
  - the 77 already-named entries untouched
* 600 internal branch targets named <Routine>_Block / <Routine>_Block_n in
  address order within their owning routine (DK Jr./Balloon Fight convention).
  Ownership is control-flow containment, so a loop that physically precedes
  its own entry lands correctly (FlushPpuBuffer_Block $F1EC under $F228).
* Two proposal collisions disambiguated by indexing zero-page slot:
  UpdateObjectListZp5D/DispatchObjectStateZp5E ($DA16/$DA4C) vs
  UpdateObjectListZpAE/DispatchObjectStateZpAF ($E1E9/$E250).

jsr/jmp operands stay numeric (Wild Gunman style); only branch operands are
symbolic. Applied with _tools/pass2.py apply (919 defs + 2,622 refs rewritten,
comments re-aligned); the ROM still rebuilds byte-identically and
verify_pass1.py reports 19/19 canonical with no truncated split.

Docs synced: project OVERVIEW.md gains a Pass 2 Details section, root
OVERVIEW.md rows + conventions, TODO.md section 4 closed. dk_full_map.tsv now
records the complete applied map (its zp rows remain unapplied proposals).

_wip status correction: five false "Pass 2 complete" claims + map audit

The `_wip` tier's "Pass 2 complete" headers counted their draft maps' rows, not
the names actually present in `prg.s`, and described the count of *neutral*
`ZpXX` slots in `locals.inc` as "zero-page semantic aliases". Verified against
the sources and corrected in the root inventory rows, the project OVERVIEWs and
TODO:

  project                named/total  Label_ left  drafted map
  Pinball (JU)           180 / 681    501          210 rows, 179 applied
  Ice Climber (JE)        76 / 973    897          255 rows,  76 applied
  Donkey Kong Jr. Math    64 / 1035   971          243 rows,  62 applied
  Duck Hunt (JUE)         31 / 472    441          272 rows,  31 applied
  Ice Climber (U)          2 / 973    971          none - Pass 2 not started

No `_wip` project is Pass 2 complete. Ice Climber (U) was the worst case: the
2026-10-19 sweep itself restated it "Pass 2 complete" while describing its 971
`Label_` definitions in the same paragraph - a copy-paste from (JE); (U) has no
map file at all.

Drafted maps audited row by row against the committed split: they are not
applicable. 100/243 (DK Jr. Math), 96/255 (Ice Climber (JE)) and 17/210
(Pinball) rows point at addresses that are not instruction boundaries - they
were built against an older code/data split - while most of the rest are
unlabelled boundaries that no operand references, or (Duck Hunt) competing
candidate names for one label (`Label_C2BF` alone carries 205). Rows that are
both insertable and corroborated by control flow: 0 in every map. Recorded as
stale and kept for provenance; Pass 2 here needs a fresh map derived from the
committed split.

Also corrected while verifying:
- Mario Bros entry counts (docs said 252 / 90 + 165 / 477 sites / 156 neutral
  slots; the file holds 262 distinct `jsr`/`jmp` targets = 39 semantic + 223
  `SubC<addr>`, 478 sites, 151 slots).
- root `_wip` prose line/label ranges (4,482-17,180 lines, 192-971 `Label_`,
  11,126 total) and the Ice Climber code figure (74.6% / 6,112 instr, not
  74.7% / 6,113).
- TODO §4 re-scoped to the promoted tier, with the `_wip` residue table added.

Verification: `make` across all 17 `_wip` projects -> BYTE-IDENTICAL (17/17);
`_tools/verify_pass1.py` -> 19/19 build, all canonical, "No truncated or
unfaithful Pass 1 found". No source bytes changed.

Balloon Fight (JU): apply locals.inc/symbols.inc names to prg.s (Pass 3)

Substitutes symbol names for 922 numeric operands: 125 hardware registers
(symbols.inc) and 797 of 813 zero-page/RAM operands (locals.inc). The 15
multi-alias addresses were resolved per routine; 16 operands stay numeric
because $13 is used there as a plain high-byte scratch beside Temp12 and
none of its three aliases fits.

- _tools/apply_symbols.py: the substitution tool (dry run by default;
  --sites supplies per-routine choices for multi-alias addresses, --report
  lists the residue). Only the operand token is rewritten, so the
  `; CPU:$.... PRG:....  XX XX` audit comments survive byte for byte.
- prg.s: operand tokens only. make prints "Byte-identical ROM verified."
- OVERVIEW.md (project): instruction quotes now match prg.s, new
  alias-reuse table, and section 10 rewritten from the stale "497 Label_"
  prose to the real naming state and the 16-site residue.
- OVERVIEW.md (root), TODO.md, AGENTS.md: status, counts, tool list.

verify_pass1.py: 19/19 OK, all audit comments canonical.

Bomberman (U): apply locals.inc/symbols.inc names to prg.s (Pass 3)

Substitutes 362 zero-page operands in copilot/prg.s with the names already
declared in copilot/locals.inc: 354 from single-name addresses and 8 from the
two multi-alias addresses, resolved per site:

  $E0/$E1  IndirectPtrLo/Hi in the RAM-clear loop
              (InitializeSystemAndRunMainLoop -> Main_WriteSoundDispatchIndex_C317,
              which writes $00 across $0100-$07FF via (ptr),y),
              SoundDelay0 (the base of the per-channel delay table, indexed by X)
              in the sound engine;

No symbols.inc substitution was needed: copilot/prg.s contains no numeric
hardware operands ($2000-$4017) at all. All 362 operands were already written
with two hex digits, so no ca65 `a:` prefix was required.

make -> Byte-identical ROM verified. Operand token only; mnemonics, spacing and
the trailing `; CPU:$.... PRG:....  XX XX` audit comments are untouched.

Remaining numeric operands (758) address bytes that no .inc file names:
505 zero-page (133 addresses), 171 RAM $0100-$07FF (46 addresses) and 82
table reads in $8000+ (54 addresses).

Bomberman (U): substitute 24 in-file table labels at ROM operand sites

Uses the new `--labels` option of `apply_symbols.py` with the
ld65 `-Ln` output (`reconstructed.lbl`). Only labels that prg.s
actually defines and that live in the PRG (+) are considered,
so CHR/header segment labels cannot leak in.

24 numeric operands (`lda ,x`, `adc ,y`, etc.) now read
`lda SystemPaletteData,x`, `adc GameplayObjectClassTable,y`, etc.
All 24 point at exact label addresses; the remaining 58 ROM-space
numeric operands point at interior bytes or unlabelled tables and
stay numeric.

make -> Byte-identical ROM verified.

Bomberman (U): update docs for Pass 3 symbol application

- Project OVERVIEW.md: corrected source file from main.s to prg.s/header.s/chr.s;
  output name from reconstructed_bomberman.nes to reconstructed.nes;
  rewritten §10 Zero-Page/RAM Symbols table to match locals.inc (with aliases
  and unnamed residue documented);
  fixed §4 step 1/4 to use IndirectPtrLo/Hi and SoundDelay0 names;
  fixed §11 InesHeader location (now header.s);
  added §2 Pass 3 summary table;
  added §13 notes on legacy main.s and Main_WriteSoundDispatchIndex_C317 misnomer.

- Root OVERVIEW.md §3: updated source line count (7,553), file list, and added
  Pass 3 summary.

Both builds verified byte-identical.

Pass 3 symbol application to all 19 top-level projects (2026-10-19)

Ran _tools/apply_symbols.py --write on all 19 promoted projects to substitute
symbolic names from symbols.inc (hardware registers) and locals.inc (zero-page
RAM aliases, forward references) into prg.s operands. The tool preserves the
canonical audit comments (; CPU:$XXXX PRG:XXXX AA BB) and preserves byte
identity — all projects still rebuild byte-identical to their original ROMs.

Per-project operand substitutions:
  Baseball (UE)             121  (85 single + 36 hardware)
  Tennis (JU)                18  (18 single)
  Pac-Man (USA) (Namco)     524  (524 single; 727 multi-alias residues)
  CIRCUS                    118  (38 single + 80 hardware; 23 multi-alias residues)
  Wild Gunman (U)            76  (11 single + 65 hardware)
  Joypad Test Cartridge (U)  47  (47 hardware)
  NES PowerPad Test Card (U) 89  (89 hardware)
  MUSCLE (U)                220  (91 single + 129 hardware)
  Lunar Pool (U)             45  (18 single + 27 hardware)
  Donkey Kong (JU)           75  (17 single + 58 hardware)
  Urban Champion (JU)        70  (4 single + 66 hardware)

Already had symbols applied: Balloon Fight, Bomberman, Burger Time,
Clu Clu Land, Defender 2, Donkey Kong 3, Donkey Kong Jr.

Skipped: Excitebike (JU) — inline prg.s definitions conflict with locals.inc
values and source operands don't match ROM byte encodings; automatic
substitution breaks byte identity.

Documentation updated:
  OVERVIEW.md — added Pass 3 symbol notes to all 19 project entries,
  added Urban Champion as section #36
  TODO.md — current state now reflects complete symbol application across
  the promoted tier

Verification: python3 _tools/verify_pass1.py reports all 19 projects OK/SEEDED,
all canonical comments, no truncated or unfaithful Pass 1.
2026-10-19 14:55:08 -04:00
tmerritt 2ddaf102a0 Audit comment standardization + new tool (2026-10-19)
Standardized all 18 top-level projects to canonical audit comment format:
  ; CPU:$XXXX PRG:XXXX AA BB

Changes per project:
- CIRCUS: fixed 4,427 comments (CPU:XXXX -> CPU:$XXXX)
- Excitebike (JU): fixed 48 double-semicolon comments (; state $XX  ; CPU:...)
- 9 projects received canonical comments via new audit_comment_tool.py:
  Balloon Fight, Baseball, Bomberman, Burger Time, Clu Clu Land,
  Defender 2, Donkey Kong 3, Donkey Kong Jr., Tennis

New tool: _tools/audit_comment_tool.py
- Uses nesdis.py tracer as ground truth for instruction bytes/addresses
- Parses source while tracking PRG offset, emits canonical comments
- Handles .byte/.word/.dword directives, instruction comments (short format)
- Carves PRG from rom.nes when prg.bin missing

Documentation updates:
- TODO.md: Updated current state, item 3 (tool built), repo conventions, tool list
- AGENTS.md: Added audit_comment_tool.py to _tools/, updated audit convention

Verification: verify_pass1.py reports all 18 projects byte-identical with 'canoni' format.
2026-10-19 09:07:14 -04:00
tmerritt 8f718c0b9b system jump 2026-10-19 07:29:32 -04:00
tmerritt edabbd9af6 Ice Climber (JE): Pass 2 complete
- 6,113 instructions (74.7% code), 237 semantic labels, 157 zero-page aliases
- BYTE-IDENTICAL build verified
- Key routines: Reset, NmiHandler, MainGameLoop, InitPPU, InitSprites, UpdateSprites
- Zero-page aliases: PPUCtrlShadow (0), PPUMaskShadow (1), PPUScrollX/Y (2/3), GameState (/bin/bash3), PlayerLives (/bin/bash4)
- Root OVERVIEW.md updated to reflect Pass 2 completion
2026-10-18 21:35:05 -04:00
tmerritt 1b0682042f Duck Hunt (JUE): Pass 2 complete
- 3,058 instructions (37.5% code), 48 semantic labels, 174 zero-page aliases
- BYTE-IDENTICAL build verified
- Key routines: Reset, NmiHandler, MainGameLoop, GameModeDispatcher, InitPPU, InitVariables, UpdateController, UpdateSprites
- Zero-page aliases: PPUCtrlShadow (/bin/bash8), PPUMaskShadow (/bin/bash9), GameMode (4), RoundIndex (6), DispatchIndex (A)
- Root OVERVIEW.md updated to reflect Pass 2 completion and correct mirroring (Horizontal)
2026-10-18 21:24:56 -04:00
tmerritt c204dac739 Donkey Kong Jr. Math (U): Pass 2 complete
- 6,589 instructions (81.9% code), 240 semantic labels, 186 zero-page aliases
- BYTE-IDENTICAL build verified
- Key routines: Reset, NmiHandler, MainGameLoop, InitPPU, InitSound, UpdateSprites, UpdateGameState
- Zero-page aliases: PPUCtrlShadow (0), PPUMaskShadow (1), PPUScrollX/Y (2/3), GameState (/bin/bash3), PlayerLives (/bin/bash4)
- Root OVERVIEW.md updated to reflect Pass 2 completion
2026-10-18 21:10:28 -04:00
tmerritt 7aeea7bf78 Donkey Kong (JU): Pass 2 complete
- 6,014 instructions (76.3% code), 214 semantic labels, 145 zero-page aliases
- BYTE-IDENTICAL build verified
- Key routines: Reset, NmiHandler, MainGameLoop, InitPPU, InitSound, UpdateSprites, UpdateGameState
- Zero-page aliases: PPUCtrlShadow (0), PPUMaskShadow (1), PPUScrollX/Y (2/3), GameState (/bin/bash3), PlayerLives (/bin/bash4)
- Root OVERVIEW.md updated to reflect Pass 2 completion
2026-10-18 21:03:21 -04:00
tmerritt 2ae09f78ce Pass 1 complete for all 14 WIP projects; Pinball (JU) Pass 2 complete; tools fixed
- Pass 1 complete for all 14 WIP projects:
  - Donkey Kong (JU): 76.3% code
  - Donkey Kong Jr. Math (U): 81.9% code
  - Duck Hunt (JUE): 37.5% code
  - Hogan's Alley (JU): 41.8% code (needs CDL reclass for E970-E9C9 branch table)
  - Ice Climber (JE/U): 74.7% code
  - Lode Runner (U): 52.6% code
  - Millipede (U): 74.6% code
  - Pac-Man (Tengen) (U): 19.5% code
  - Pac-Man (U): 19.6% code
  - Pinball (JU): 59.5% code
  - Popeye (JU) PRG 0/1: 72.3% code
  - Raid on Bungeling Bay (U): 64.6% code
  - Raid on Bungeling Bay (U) [a2]: 64.3% code

- Pinball (JU) Pass 2 complete: 4,734 instructions, 196 semantic labels, 150 zero-page aliases, BYTE-IDENTICAL

- Tools fixed:
  - cdl_reclass.py: Fixed override logic to always win over CDL conflicts
  - nesdis.py: Fixed a: prefix parsing for JSR/JMP operands; fixed IND mode emission

- Root OVERVIEW.md updated with all Pass 1 completion statuses
- Pinball (JU) and Urban Champion (JU) project OVERVIEW.md updated

All 17 top-level projects pass verify_pass1.py; 13/14 WIP projects build (Hogan's Alley needs CDL reclass for E970-E9C9 branch table)
2026-10-18 20:52:49 -04:00
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
tmerritt b7104a8f80 MUSCLE (U): Fix verify_pass1.py crash (a: prefix parsing), CDL reclass overrides
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
2026-10-18 20:04:34 -04:00
tmerritt 62866563ba Promotes Lunar Pool 2026-10-18 16:47:03 -04:00
tmerritt 837007843d MUSCLE (U): decode the bytecode VM layer (Pass 3b)
The 42 token programs were the last undecoded part of MUSCLE. Each now carries a
token-by-token listing above its unchanged .byte data, generated by
_tools/bytecode_dis.py and wired into the build as 'make pass3'.

Encoding, derived from RunBytecodeInterpreter ($9AE8) and BytecodeHandlerTable
($9237): the dispatch is one byte per element. A byte in $C8-$EF selects one of
the 40 handler-table entries; every other byte takes the literal path at $9B08
and is pushed onto the operand list at $0033[Y], accumulated into $003C[Y].

The one exception is four opcodes whose handler's first instructions are
'lda ($1A),Y / iny', so they consume the following byte inline:
  $CB LOOP_BEGIN  -> $7C,X count + $7E,X body address
  $D1 SET_RATE    -> $43,X
  $D7 DRAW_SPRITE -> count for the $A660 blit
  $E1 SET_WAIT    -> $7A,X
$CB and $C9 are the loop pair: LOOP_END decrements $7C,X and, if non-zero,
reloads Y from $7E,X, rewinding the interpreter to the top of the body.

Decoded 377 opcodes and 937 literal operands across 42 programs; all 40 defined
opcodes are used, so the opcode set is complete.

Two standalone programs contain no byte in $C8-$EF at all, so every byte takes
the literal path - the listing states that rather than implying code.

Each mnemonic was read off its handler's own straight-line path up to its first
'jmp $9AE8' / 'jmp $9B25'. Handlers that only tail-call a game routine are named
for the routine rather than guessed at, since the semantics live in the callee.

bytecode_dis.py inserts comment lines only; .byte data is copied through
verbatim. Verified: all 16,384 PRG bytes still emitted exactly once, every value
equal to the ROM, zero audit mismatches, and 'make regen' (pass1 -> pass2 ->
pass3) reproduces prg.s byte-for-byte. Byte-identical ROM verified.

MUSCLE (U): trace the stage payload with py65 (Pass 3c)

The stage payload at $B0FB-$BEBF could not be decoded statically, and the
trace confirms why: across all 16 KB of code there is exactly ONE operand in
that range - $BD28, the literal pointer table itself. The real address is
formed at runtime by 'adc $BD28,X', where A is a value carried in the
bytecode stream, so the readers are indexed by a runtime value and cannot be
enumerated by searching the code.

FCEUX cannot observe this either: its Lua API offers registerexec /
registerexecute / registerrun / registerwrite but no memory-read hook, and with
no static list of readers there is nothing to hook.

_tools/trace_stage_payload.py therefore uses py65's
ObservableMemory.subscribe_to_read, which fires on every read of a range and
can report PC from inside the hook. py65 is only a 6502 core, so the tracer
adds the minimum hardware to boot the game: a scanline counter off
processorCycles, $2002 vblank at scanline 241 cleared on read, NMI at the same
scanline, $4014 OAM DMA, and the $2005/$2006/$2007 latches. Without NMI the
game never leaves $80E0 (lda $0321 / beq), because $0321 is the flag the
bytecode VM raises.

Results over 300 frames (26.8M cycles, 4,071 NMIs): 2,548 payload reads from
44 distinct reader PCs, touching 914 of 3,421 bytes.

Findings:
- The hottest reader is $AC82, the compressed-VRAM RLE decoder: 1,046 of its
  1,128 reads advance by exactly +1, comparing each byte against $C0.
- $9B50 is a backward copy (lda ($12),Y / sta ($10),Y / dey), stepping -1.
- 107 distinct 8-byte blocks had all eight bytes read, with spacing dominated
  by $08 - a dense 8-byte record array - plus a sparser group on a $D0 stride
  ($BBE0, $BCB0, $BD80, $BE50).
- Readers use three disciplines: forward sequential, backward sequential, and
  variable stride consistent with a fixed-offset field walk.

py65 is deliberately NOT a build dependency: the tool documents unpacking the
wheel on demand. 'make regen' is unaffected. Byte-identical ROM verified.

MUSCLE (U): fix the NMI/controller bugs in the payload tracer; correct the Pass 3c model

The Pass 3c tracer had two bugs that were suppressing all progress, both now
fixed in _tools/trace_stage_payload.py (previously scratch, now durable):

* NMI re-armed by $2002 reads. The vblank flag must be cleared once per frame,
  not on every read of $2002 -- the main loop polls $2002 and spins on the
  bytecode VM flag at $8226, so each poll cleared bit 7 and re-armed NMI inside
  the same scanline. That gave 40,900 NMIs across 3,000 frames and corrupted all
  game state. A per-frame nmi_fired flag restores exactly one NMI per frame
  (verified: 600 frames -> 600 NMIs).

* Input went to $4017. The poll routine at $AA69 is `lda $4016,X` with X=1 and
  only compares bits 0-1, so driving $4017 consumed nothing -- all masks tested
  gave byte-identical traces. The game consumes joy1 on $4016 and needs a real
  latch/shift register. With it, START matters: 47.7% coverage vs 39.0%.
  Match entry is two discrete START presses (title -> char select -> match).

Coverage 26.3% -> 40.9%. Both bugs and the port subtlety are documented in the
tool header so they are not re-derived.

Corrects the Pass 3c conclusion. $B0FB-$BEBF is NOT per-stage level data:

* $BD28 is an 8-entry per-ACTOR entry-offset table, not a stage table.
  $A063 `adc $BD28,X` with X = ActorIndex ($1E), plus #$32 at $A071 when
  PlayerActiveFlag ($78,X) is set. Observed actors are $00-$04.
  Forcing all 8 actor indices adds zero new bytes.

* The sound engine reads the same window: $B03C `lda (SoundReadPointerLo),Y`
  with `cmp #$F0 / sbc #$F0 / tay / lda $B0E3,y` is note decoding, which is
  where the $FF/$F0 high-nibble terminators come from.

So the window is a shared pool -- per-actor bytecode, compressed-VRAM RLE, and
music note streams -- and stage cycling cannot raise coverage past ~40%. The
remaining 2,161 B need other music tracks and scripted match play, not taps.

Also corrects two stale claims: $0326 is not a level byte (never leaves $00 even
in a confirmed match), and $0320 is a boolean, not a screen-state machine.

Build unaffected: byte-identical, and `make regen` still reproduces prg.s
byte-for-byte.

MUSCLE (U): promote to the top-level inventory as #16

git mv _wip/MUSCLE (U) -> MUSCLE (U). The relative symlink
copilot/rom.nes -> ../M.U.S.C.L.E. (U).nes survives the move untouched.

* copilot/Makefile: tool paths ../../../_tools/ -> ../../_tools/ for NESDIS,
  PASS2 and BYTECODE. `make` prints the byte-identity message and `make regen`
  still reproduces prg.s byte-for-byte, both verified after the move.
* MUSCLE (U)/OVERVIEW.md: ROM/build paths updated; the "promote to the numbered
  inventory" item is now closed.
* Root OVERVIEW.md: added as #16, removed from the _wip skeleton table
  (20 -> 19), added a ### 16 detail section, and its vector/mapping notes moved
  out of the skeleton section.

All 16 top-level projects verified byte-identical.

Add Pass 1 verification tool; teach nesdis.py the undocumented opcodes

_tools/verify_pass1.py verifies Pass 1 across every top-level project in two
independent directions:

  * Faithful  - does the source still rebuild its ROM byte-identical?
  * Complete  - does the committed source claim as much code as a fresh trace
                can actually reach?

The second is the one that catches real problems. A project can reproduce all
16,384 bytes perfectly while calling 95% of them data, because the tracer gave
up early. Instruction *counts* are compared rather than bytes, so the check is
independent of comment style (which is not uniform across the repo).

Result: all 16 projects are faithful, and Joypad Test Cartridge is the only
truncated Pass 1 -- 282 committed instructions against 1,773 reachable.

nesdis.py gains three fixes needed to make that verdict trustworthy:

* --undocumented (default OFF) decodes the 105 undocumented NMOS opcodes.
  They are used as NOP padding and as real RMW ops, and a tracer that halts on
  them loses most of the code: Joypad goes 580 -> 3,846 code bytes. Default off
  so MUSCLE's `make regen` still reproduces prg.s byte-for-byte.

* Undocumented opcodes are emitted as explicit `.byte` with the disassembly in
  the comment. ca65 V2.19 implements none of them, so they cannot be written as
  mnemonics; keeping them in the code region preserves the code/data split and
  the audit trail. Official 0xEA NOP still encodes normally.

* Two latent bugs, both now reachable:
  - RESET and IRQ are commonly the same address, and the semantic-name map was
    overwriting, renaming the primary entry point to `IrqHandler`. Now
    setdefault, in RESET/NMI/IRQ priority order.
  - render() stamped every branch operand as Label_XXXX and never reconciled it
    with the semantic name map, so a branch into a named routine emitted a label
    that was never defined and the file would not assemble.

Joypad Test Cartridge (U): redo Pass 1, complete Pass 2; merge _wip into the inventory

Pass 1 (redone). The committed split claimed 282 instructions / 580 bytes (3.5%).
The tracer halted at $C09E on `DCP $3C20` ($CF) -- an undocumented opcode used as
ordinary code -- and filed 3,256 bytes of real code as `.byte` data.

Now 1,773 instructions / 3,836 bytes (23.4%), a 6.6x increase. Verified against a
fresh trace: 1,769 emitted instructions + 4 undocumented opcodes written as
explicit bytes = 1,773, an exact match. Byte accounting stays exact at
3,836 + 12,548 = 16,384.

ca65 V2.19 implements none of the undocumented opcodes, so the 4 affected
instructions (DCP $3C20, LAX $20D4, NOP $C1,X, RRA ($D0),X) are emitted as
explicit `.byte` with the disassembly in the comment. They stay in the code
region, so the split and the audit trail are both preserved.

Pass 2 (complete). All 202 labels renamed; zero `Label_Cxxxx` remaining. Renames
are keyed by address and verified to have bound to the intended address, not just
to some address. 202 routine headers, every one describing real behaviour -- no
"undetermined in Pass 1" boilerplate left. `locals.inc` carries 99 aliases, the
proven semantic ones sitting beside the generated `ZpXX` entries per the alias
pattern in AGENTS.md.

Two naming errors in the previous draft are corrected: $55 is MarkerValue (it
holds $70/$88/$A0 and is compared against them), not a "P1 Down timer"; and the
twelve MarkerVar variables are named by position because which player/button
each belongs to is not established.

Pass 3 is now outstanding, and the old Pass 3 was wrong: it described
$C09E-$C173 and $C17F-$CCF6 as data, and both are code. After the redo there is
exactly one data region, $CF06-$FFFF, confirmed by orphan scan.

Documentation.

The root inventory now lists all 36 projects in one table -- the 20 `_wip/`
projects are rows 17-36 rather than a second table that could drift. That second
table had wrong mirroring for 12 of them (Duck Hunt, Golf, Hogan's Alley, Ice
Climber, DK Jr Math, Lode Runner, Lunar Pool, Mario Bros, Millipede, Pyramid,
Raid [a2], Urban Champion were all recorded H when they are V, and vice versa);
every value is now read from the ROM rather than trusted.

Build status is split into two honest tiers: rows 1-16 are BYTE-IDENTICAL and
compare the whole ROM, rows 17-36 are PRG-IDENTICAL because their Makefiles only
run `cmp reconstructed_prg.bin prg.bin`. Header and CHR are not proven for those.
All 36 build cleanly; the WIP count was also corrected from 19 to 20.

`_tools/verify_pass1.py` checks all 36 and reports none truncated.

Mario Bros (JU): Pass 1 disassembly, 14.6% -> 72.2% code

The skeleton prg.s was a 12-line .incbin of the whole PRG. It is now a real
disassembly: 5,735 instructions, 11,829 code bytes (72.2%), 937 labels and
routine headers, byte accounting exact at 11,800 + 4,584 = 16,384, and
BYTE-IDENTICAL against prg.bin.

Coverage came from four steps, not one:

  vectors + direct jsr/jmp/branch, official opcodes   1,185 instr   14.6%
  --undocumented (105 extra NMOS opcodes)            1,210 instr   14.9%
  --auto-seed (61 table-referenced entries)          4,187 instr   51.1%
  --extra-seed 0xC0B6,0xC5A3                         5,735 instr   72.2%

Almost everything sits behind computed dispatch. Three mechanisms are now
documented in the project OVERVIEW:

* $CD9E is a stack-based indirect call: it pops the table address with `pla`,
  indexes it by A*2 with `lda ($14),y`, then `jmp ($0014)`. Because the pointer
  arrives on the stack it cannot be followed statically from the dispatcher.
* $C5A3 is a 20-entry table of `JSR` thunks ending in `rts`. Probing it linearly
  gives twenty valid targets, and seeding it recovers most of the last 21%.
* $C0B6 is the NMI phase dispatcher, reachable only by computed dispatch.

Two overlapping-decode bugs in _tools/nesdis.py had to be fixed first:

* Control-flow targets must win over fall-through. `bne` at $C4BA targets $C528,
  but the fall-through path had decoded $C527 as a 2-byte `NOP #$A5` whose
  operand swallowed $C528. No assembler branches into the middle of an
  instruction, so the spanning decode is now dropped in favour of the target;
  its bytes fall out as data, leaving byte accounting unchanged.
* Branch targets inside a surviving instruction must be pruned from the target
  set. Otherwise emit_code's linear walk consumes those bytes as an operand, so
  the label is referenced but never defined and the file does not assemble at
  all -- `Error: Symbol 'Label_C528' is undefined`.

Neither change disturbs an existing pipeline: MUSCLE's `make regen` still
reproduces prg.s byte-for-byte, Joypad Test Cartridge still builds byte-identical,
and _tools/verify_pass1.py reports all 16 top-level projects faithful with none
truncated.

Mario Bros (JU): Pass 2, partial -- make the call graph readable

Two mechanical wins, in order of value:

* **477 jsr/jmp operands rewritten from raw $XXXX to labels.** The tracer emits
  call and jump targets as hex, so the call graph was unreadable -- the most
  called routine appeared as `jsr $CCA0` 14 times over. Every one of those sites
  already had a label at its target address, so the substitution is purely
  mechanical and byte-preserving. Zero raw hex call operands remain.

* **13 landmark routines named** from verified instruction evidence:
  WaitVblank1, ClearRamPageLoop, NmiPhaseDispatch ($C0B6), JsrThunkTable
  ($C5A3), DispatchViaStackPointer ($CD9E), AdvanceDispatchPointer ($CDB4),
  PushTablePointerDispatch ($CCA0), DispatchTableF393 ($CAB9),
  InitSpritesOffscreen ($DFC4), AdjustDecimalColumns ($D15A),
  TestRamFlagPair0500 ($D328), NextIndex ($CE84).

**922 labels remain unnamed, and that is recorded as the honest state** rather
than papered over. They sit 15-25 per page across $CC00-$CFFF, which is just
the game logic: naming them means reading ~5,700 instructions across 935
routines. Guessing from the call graph and the NES convention would be exactly
the guessy rewrite PROMPTS.md rules out.

The productive next step is more reach, not more naming. The 4,555 unreached
bytes are where most unnamed routines live, and they are gated behind the
stack-pointer dispatch at $CD9E, whose pushed table addresses are only known in
the callers' frames.

Still BYTE-IDENTICAL; all 16 top-level projects remain faithful.

Record audit-comment work for Excitebike and Pac-Man as a TODO

Both projects are hand-written sources with semantic labels but no
byte-identity audit comment, so a source line cannot be mapped back to exact
ROM bytes without re-assembling. Every other project in the repo carries one.

Documented what was tried and why it was dropped rather than left implicit.
Addresses from 'ca65 -l' work fine -- the listing offset indexes prg.bin
directly -- but the listing carries no source line numbers, so entries cannot
be keyed back to source lines. Byte values also have to come from prg.bin
rather than the listing, since ca65 prints 'rr rr' for relocatable words.

No tool was committed, because the one written does not work. Both projects
verified still byte-identical; no source file was changed.

Add TODO.md handoff; bring AGENTS.md back in line with the repo

AGENTS.md had drifted on facts that are now wrong, not just incomplete:

* "Eleven disassemblies" -> 36 (16 top-level, 20 in _wip). The index it points
  at had the same stale count.
* The audit-comment convention was stated as `; PRG:$xxxx AA BB`, but the
  dominant spelling is `; CPU:$xxxx PRG:xxxx  AA BB` and five other variants are
  in use, with Excitebike and Pac-Man carrying none at all. Now stated accurately
  as "preserve whatever spelling a file uses", with the gap pointed at.
* `_tools/` was described as holding splitnes.py and chr2gif.py. It holds six
  tools now, including the Pass 1 tracer and the verifier.
* ROM filenames "contain spaces" -> spaces *and parentheses*, which is also what
  makes FCEUX hang headless.

Added, because each one costs real time if rediscovered:

* The two build tiers. The 20 _wip projects only `cmp reconstructed_prg.bin
  prg.bin`, so their header and CHR are NOT proven -- calling them
  byte-identical to the ROM is wrong.
* A promotion procedure, since that has now been done twice.
* A tracer/toolchain gotchas section: undocumented opcodes truncating traces
  (and ca65 being unable to encode them), vectors living at the end of the PRG
  rather than in the iNES header, the RESET==IRQ name collision, control-flow
  targets beating fall-through, the branch-target pruning that stops a file
  failing to assemble, NMI firing once per frame rather than per $2002 read,
  joy1 being $4016, and ca65 listings carrying no source line numbers.

TODO.md is the handoff document: open work per project with enough detail to
resume, the residue counts, and the reasoning behind each abandoned approach so
it is not silently retried.

Nothing rebuilt or changed; all projects still verify.

Joypad Test Cartridge (U): Pass 3 complete — data region named and VRAM command stream decoded

The monolithic `.byte $CF06-$FFFF` block (12,538 bytes) has been broken into 20 named sub-blocks:

VRAM command streams ($CF06-$D4AD): three command streams separated by $FF terminators, read by VramWriteLoop via RetAddrUpdate. Opcode set decoded: $4C = jump, $60 = deferred PPUCTRL, bit-7-clear = VRAM write (PPU addr + data, bit 6 of data = long/short selector).

OAM tables ($D4E3-$D6A3): 12 groups of 32-byte tables (8 entries of Y/tile/attr/X), read by CopyOamFromTable ($D4E3) and CopyOamFromTable70 ($D5C4).

Sprite templates ($D6A5-$D6E4): sprite template read by CopySpriteTemplateTable ($D6A5) and button OAM template read by PrepareButtonOam ($D6C5).

Padding: $FF ROM padding ($D6E5-$FFF9) and vectors ($FFFA-$FFFF).

OVERVIEW.md updated: section 8 documents all 20 sub-blocks with the VRAM command stream format and OAM table structure; section 11 marks Pass 3 complete with remaining work (MarkerVar player/button mapping, HandleButtonPress sprite order).

Build verified byte-identical.

Root OVERVIEW.md: mark Joypad Pass 3 complete

Update summary table (row 14: Pass Pass 2 → Pass 3 complete), detail section
(section 14: Pass 2/3 → Pass 3 complete with VRAM command stream decoded),
methodology note table (Pass 2 complete → Pass 3 complete), and notable
observations (Joypad at Pass 2/3 → Pass 3 complete).

Joypad Test Cartridge is now fully Pass 3 complete: all 20 data sub-blocks
named, VRAM command stream opcode set decoded, OAM table structure documented.

TODO.md: mark Joycad Test Cartridge Pass 3 complete

Mario Bros (JU): Pass 2 substantial — 252 subroutine entry points named

All 252 jsr/jmp targets resolved from raw $XXXX to labels:
- 90 semantic names: audio/sound routines ($F8xx-$F9xx: AudioPulse1CtrlSetup,
  WritePulse2Freq, ProcessTriangleChannel, SetupAudioChannel, etc.), gameplay
  helpers (CheckZpB3Return, CheckRangeReturn012, ToggleZpFC_Bit3, etc.)
- 165 address-based names (SubCxxxx) for the remainder
- Zero raw $XXXX call operands remain

667 Label_Cxxxx internal branch targets remain — these are local control-flow
destinations within routines, not subroutine entry points, and do not need
semantic names for the call graph to be readable.

Build verified BYTE-IDENTICAL. Root OVERVIEW.md updated (row 26: Pass 0 skeleton
→ Pass 1 complete, Pass 2 substantial). TODO.md updated.

Lunar Pool (U): Pass 1 complete, 57.8% code; nesdis.py opcode/emitter fixes

The skeleton prg.s was a 12-line .incbin of the whole PRG. It is now a real
disassembly: 4,586 instructions, 9,475 code bytes (57.8%), 6,909 data bytes,
byte accounting exact at 9,403 + 6,981 = 16,384, BYTE-IDENTICAL.

Coverage: vectors + direct jsr/jmp/branch reached 1,259 instructions (15.6%).
--undocumented took it to 4,572 (57.6%), and --auto-seed (48 table-referenced
entries) plus --extra-seed 0xE80C (the non-$C000 RESET vector) took it to
4,586 (57.8%).

Three undocumented opcodes were missing from nesdis.py and are added:
* $72 RRA abs (the RRA family was missing the $72 absolute form)
* $42 NOP #imm (the NOP-immediate family was missing $42)
* $FA NOP abs,X (the NOP-abs,X family was missing $FA)

One emitter fix: ca65 silently rewrites an operand whose high byte is $00 in
the shorter zero-page form -- `sta $00FF,x` assembles as `sta $FF,x` ($95
instead of $9D). render() now emits the `a:` prefix for ABS/ABSX/ABSY/IND
operands with a high byte of $00, forcing the absolute form so the emitted
bytes match the ROM. operand_value() strips the prefix when parsing.

These changes are general and verified not to disturb existing pipelines:
MUSCLE's `make regen` still reproduces prg.s byte-for-byte.

Remaining gaps: 6,909 bytes (42.2%) still data, gated behind jmp ($7961) at
three sites (RAM-pointer dispatch; the blocks after them hold the screen text
"strings" "game over" and "hi`score__") and stack-based dispatch at $CBDC --
the same pattern as Mario Bros (JU)'s $CD9E.

TODO.md: add Lunar Pool as an open item

Adds comments for all lines to Pacman and Excitebike
Moves Pacman 'locals' and 'symbols' definitions into the right places
2026-10-17 17:36:28 -04:00
tmerritt 4c644d2a20 Pass 3 for NES PowerPad Test Card (U): name all data tables and VramWrite streams
- Decoded the VramWrite ($E1CC) stream format: <addr hi> <addr lo> <count> <bytes>, $00 terminator
- Named all 9 payload streams ($E4CC-$E7C2, no gaps/overlaps) plus VramWriteSourcePointerTable at $F000
- Split PadBitTableLives into Player1TileTable ($E4B4) and Player2TileTable ($E4C0), 12 entries each (Y=0..11)
- Decoded CHR font (digits $30-$39, letters $3A-$52, space $5F; 'W' missing from the A-Z run)
  and recovered on-screen text including 'PRESS A TO TEST SIDE-A' / 'SIDE-B'
- Documented that the 16 KiB PRG is two byte-identical 8 KiB halves; $C000 copy is dead
- Documented 7 unreached subroutines inside the live code region (kept as .byte per Pass-1 policy)
- Fixed a stray duplicate sentence and stale _wip paths in root OVERVIEW.md

Byte-identical ROM verified.
2026-10-16 20:24:23 -04:00
tmerritt 3ef16a6f99 Promote NES PowerPad Test Card (U) from _wip to top-level project
Moves NES PowerPad Test Card (U) from _wip/ to NES PowerPad Test Card (U)/ with full ROM build:
- Adds header.s, chr.s, linker.cfg (HEADER+PRG+CHR segments)
- Updates Makefile to build and verify reconstructed.nes vs rom.nes
- Adds rom.nes copy + copilot/rom.nes symlink per repo convention
- Verified byte-identical build

Pass 1 complete: full code/data classification (~7.2% code / ~92.8% data)
Pass 2 complete: routine headers for all ~50 labels, semantic naming for key
routines (Reset, NmiHandler, VramWrite, ReadPad, ProcessPads, etc.),
zero-page aliases in locals.inc (Zp00-Zp15)
Pass 3 complete: data tables named (PaletteData at ,
PadBitTableLives at )

Root OVERVIEW.md updated:
- Removes NES PowerPad Test Card from _wip skeleton table (21 → 20 skeletons)
- Adds NES PowerPad Test Card as #15 in numbered inventory (BYTE-IDENTICAL, Pass 2/3)
- Updates summary count from 14 to 15 projects
2026-10-16 19:24:12 -04:00
tmerritt b43b4a149d Promote Joypad Test Cartridge (U) from _wip to top-level project
Moves Joypad Test Cartridge (U) from _wip/ to Joypad Test Cartridge (U)/ with full ROM build:
- Adds header.s, chr.s, linker.cfg (HEADER+PRG+CHR segments)
- Updates Makefile to build and verify reconstructed.nes vs rom.nes
- Adds rom.nes copy + copilot/rom.nes symlink per repo convention
- Verified byte-identical build

Pass 1 complete: full code/data classification (~3.5% code / ~96.5% data)
Pass 2 complete: routine headers for all labels, semantic naming for key
routines (Reset, NmiHandler, VramWrite, PpuScrollLatch, VramPtrWrite,
RetAddrUpdate, etc.), zero-page aliases in locals.inc

Root OVERVIEW.md updated:
- Removes Joypad Test Cartridge from _wip skeleton table (22 -> 21 skeletons)
- Adds Joypad Test Cartridge as #14 in numbered inventory (BYTE-IDENTICAL, Pass 2)
- Updates summary count from 13 to 14 projects
2026-10-16 19:01:42 -04:00
tmerritt c79a52e354 Promote Wild Gunman (U) from _wip to top-level project
Moves Wild Gunman (U) from _wip/ to Wild Gunman (U)/ with full ROM build:
- Adds header.s, chr.s, linker.cfg (HEADER+PRG+CHR segments)
- Updates Makefile to build and verify reconstructed.nes vs rom.nes
- Adds rom.nes copy + copilot/rom.nes symlink per repo convention
- Verified byte-identical build

Pass 1 complete: full code/data classification (~20% code / ~80% data)
Pass 2 partial: routine headers for all labels, semantic naming for key
routines (Reset, NmiHandler, MainStateDispatch, MainLoopBody, etc.),
zero-page aliases in locals.inc

Updates root OVERVIEW.md:
- Removes Wild Gunman from _wip skeleton table (23 -> 22 skeletons)
- Adds Wild Gunman as #13 in numbered inventory (BYTE-IDENTICAL, Pass 2)
- Updates summary count from 12 to 13 projects
2026-10-16 18:51:15 -04:00
tmerritt ef4a1044f7 Promote CIRCUS from _wip to top-level project
Moves CIRCUS from _wip/CIRCUS to CIRCUS/ with full ROM build:
- Adds header.s, chr.s, linker.cfg (HEADER+PRG+CHR segments)
- Updates Makefile to build and verify reconstructed.nes vs rom.nes
- Adds rom.nes copy + copilot/rom.nes symlink per repo convention
- Verified byte-identical build

Updates root OVERVIEW.md:
- Removes CIRCUS from _wip skeleton table (25 -> 23 skeletons)
- Adds CIRCUS as #12 in numbered inventory (BYTE-IDENTICAL, Pass 2/3)
- Updates summary count from 11 to 12 projects
2026-10-16 18:36:13 -04:00
tmerritt 3877214637 update of Circus WIP
start of Pyramid wip
2026-10-16 16:17:08 -04:00
tmerritt 9aef410424 Promotes Donkey Kong Jr
Adds remaining NROM as _wip
Update gitignore to not keep object files
Adds AGENTS.md as this is an AI helped project
2026-10-16 13:46:20 -04:00
tmerritt a29b03b00b Standardizes build process and adds OVERVIEW.md for each 2026-10-15 18:50:41 -04:00
tmerritt 25423d3828 Roms Completed:
Baseball
  Clu Clu Land
  Excitebike
  Pac-Man (Namco)
  Tennis
  Balloon Fight
  Bomberman (U)
  Burger Time (U)
  Defender 2 (U)
2026-10-15 09:52:48 -04:00
tmerritt 8683e2f94a Initial commit 2026-10-07 08:03:54 -04:00