b08d65877f3dccdd6814b721ec39a28d8289af1b
39
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b08d65877f | Pass 1 _wip: 23 projects at Pass 1, byte-identical builds verified | ||
|
|
691b2bc23c |
Xevious (U): Pass 1 disassembly
The first _wip project to get a real disassembly. 4,165 instructions /
8,444 code bytes (25.8%), 24,324 data bytes, 0 unclassified -- every byte
of the 32 KB PRG is now classified as code or data.
The split needed three stages, all recorded in prg.s's header:
* nesdis.py --undocumented --auto-seed at base $8000 gave 3,272
instructions / 6,555 bytes. The undocumented opcodes matter here: the
trace stops dead at the first one it cannot decode.
* Four hand-seeded entries ($C08B, $DC1E, $E0D5, $E134). Each is a
branch target from decoded code that sat inside an untraced data run,
so the tracer never reached it.
* A merged four-session Mesen Code Data Logger capture (kept as
xevious_mesen.cdl) promoted 1,902 bytes from data to code and
demoted 22 the trace had called code.
Two tool bugs fell out and are fixed here:
* nesdis.py emitted the undocumented single-byte NOPs ($1A/$3A/$5A/
$7A/$DA/$FA) as `nop`, which ca65 assembles as the *official* $EA --
the rebuild stopped being byte-identical at $DF82. The check now
keys on the opcode byte, not just the addressing mode. All three
`make regen` pipelines (MUSCLE, Pyramid, Urban Champion) are
unaffected and still reproduce prg.s byte for byte.
* cdl_reclass.py left the original data-block header in place when the
CDL split that block, so the file ended up with two region headers
describing overlapping spans (20 of them here). The header is now
dropped with the block it describes; the promoted projects that use
the tool are byte-for-byte unchanged.
The project README has the power-on flow analysis. The most interesting
find is at $FC45: the reset path compares RAM $0100-$0107 against the
ASCII string "Kei Cros", and the copyright text at $FF1E reads
"MAIN: KEI CROSS. SOUND: UHOHOY OHNOGI." The OAM is at $0700, not
$0200 -- the NMI handler does LDA #$07 / STA $4014.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add explicit `make wip` / `make wipclean` targets for the _wip tier
The root Makefile's all / clean / <MAPPER> targets deliberately name only
the promoted mapper folders, so nothing in them reaches into _wip/. Give
the pre-promotion tier its own opt-in targets instead of the manual
`for d in _wip/*/*/; do make -C "$d"; done` loop:
make wip # build + verify every _wip/<MAPPER>/<Project>/
make wipclean # clean the build artifacts in that tier
Both the Makefile and the four documents that describe the tier (README,
TODO, WIP, AGENTS) are updated together, so the tree stays consistent.
The same commit records Xevious (U) reaching Pass 2 in those documents:
all 487 labels named, every branch and call symbolic, zero Label_XXXX and
zero raw $XXXX operands left.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update the _wip/ tier documentation; fix the make wip target and the mirroring bug
The WIP tier's four documents (WIP.md, README.md, TODO.md, AGENTS.md) had
drifted away from the tree. Every number in them was re-derived from the
ROMs and the folder layout rather than carried forward.
Counts and structure
- The tier holds 854 projects across 29 mapper folders, not "856 across
30". Xevious (U) and Zanac (U) were promoted on 2026-10-20 and their
_wip/ folders were left behind as residue -- "git mv" moves only the
tracked files, so the old folder survived with its build artifacts and a
stale README. Both are removed now; the promotion procedure documents
the step so it does not recur.
- _wip/MAPPER8/ holds a copy of the already-promoted Mermaids of
Atlantis (U) and is excluded from every count and table.
- Pass 0 skeletons: 852 (was 855). Xevious (U) left the tier, so the
two projects with a real disassembly are Golf (JU) and Volleyball (U).
- PRG sizes 2 / 150 / 42 / 522 / 127 / 10 / 1; 226 projects with no CHR;
38 keep the CPU-address linker layout; 37 are mapper 0.
- Every inventory row was re-checked against its ROM: PRG/CHR size,
mirroring, all three vectors, and the battery / four-screen / footer
notes. 854 of 854 rows now match.
Fixes
- The root Makefile used $(WIP) but never defined it, so "make wip" and
"make wipclean" expanded to "for m in /*/" -- they walked the
filesystem root instead of the tier. "WIP := _wip" is now defined, and
"make wip" builds and verifies the whole tier: 855 of 855 passed, 0
failures.
- make_skeleton.py masked header byte 6 with 0x03, so the battery bit
(bit 1) leaked into the mirroring value and 56 battery-backed ROMs'
READMEs claimed "Four-screen" while their own "Four-screen VRAM" row
said "no". The mask is now 0x01 and the 56 READMEs are corrected.
- Golf (JU)'s README still described the pre-flatten copilot/ folder and
the old PRG-only compare; it now describes the whole-ROM build.
- Pyramid (U)'s audit comments are canonical again, so Golf (JU) is the
only project in the tree still on the pre-canonical spelling.
New in WIP.md
- Byte 7's lower nybble is non-zero on 58 of the ROMs -- 55 carry the
literal "DiskDude!" watermark, two "DisNi0330", one "byKH+KEN". Only
the upper nybble carries mapper bits, so the noise is harmless.
- The twelve ROMs that set the four-screen bit, split by whether their
board actually supports four-screen VRAM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Promote Volleyball (U) from _wip/NROM/ to NROM/
Volleyball (U) reached Pass 3 before promotion, so this is a promotion of a
finished disassembly rather than a Pass 0 skeleton.
The disassembly
- 11,395 instructions / 23,926 code bytes (73.0 % of the PRG image),
8,842 data bytes, 0 unclassified.
- The split needed the game-state dispatch table at $85E1 declared to the
tracer (--table 85E1:11) -- without it only 66 % of the image traced --
plus a Mesen Code Data Logger capture taken while a random-input bot
played the attract demo, which promoted 1,757 bytes from data to code.
- Pass 2 named all 1,989 labels: 730 read out of the code by hand, 1,221
internal branch targets derived mechanically. Zero Label_XXXX, zero raw
$XXXX call or branch operands, zero SubC residue.
- Pass 3 named the zero page, the $0500 ball structure and all 37 data
blocks. apply_symbols.py --report: 0 pending substitutions, 0
multi-alias residue. align_audit_comments.py: no column drift.
- cdl_check.py cross-checks the finished prg.s against the capture: 0
CDL-code bytes not covered by an instruction, 0 CDL-data bytes that
start one.
- 29 neutral ZpXX zero-page slots remain, each with its reason recorded
in locals.inc, as the promotion rules allow.
The promotion
- git mv "_wip/NROM/Volleyball (U)" "NROM/Volleyball (U)". Mapper 0, so
it lands in NROM/. The whole-ROM build was already in place, so there
was no build surgery; the project builds byte-identically at its new
home and the old folder is gone (git mv moves the untracked build
artifacts too, so no residue was left behind).
- Root README.md: new inventory row 39 and a per-project detail section,
plus the WIP-tier counts. The detail sections for Xevious (U) and
Zanac (U) were numbered one too high against the inventory rows and
are now aligned; a leftover editing note in the same place is gone.
- WIP.md: the Volleyball row leaves the inventory, and the tier is now
853 projects across 29 mapper folders (36 in _wip/NROM/), 852 of them
Pass 0 skeletons. Golf (JU) is the only project left in the tier with
a real disassembly.
- TODO.md and AGENTS.md: the same counts, and the promoted tier is now
40 projects (39 under NROM/).
Verified: make wip builds and verifies the whole tier (854 of 854 passed,
0 failures) and make builds the promoted tier (39 of 39 passed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename NROM/Pyramid (U) to NROM/Pyramid (U) (Sachen)
The promoted inventory row had read "Pyramid (U) (Sachen)" since the
project was promoted on 2026-10-20, but the folder was named "Pyramid (U)" --
so the row pointed at a folder that did not exist. The folder now matches
the row, and the project's own README title, its ROM file name
("Pyramid (U) (Sachen).nes") and the parenthetical convention the rest of
the promoted tier already uses (Popeye (JU) (PRG 0/1), Raid on Bungeling
Bay (U) [a2], Pac-Man (Tengen) (U)).
What changed inside the project
- The Makefile's regen target passes --project "Pyramid (U) (Sachen)", so
the tracer writes the matching header line. prg.s and locals.inc were
updated to that header line and nothing else -- the disassembly is
byte-identical to before.
- make regen still reproduces prg.s and locals.inc byte for byte.
- The project's README's build-verification line now names the new folder.
References updated: the root README.md (inventory prose, the row-33 detail
section, the build-status table, and the notable-observations bullet),
TODO.md (the promoted-projects table and the Pyramid notes) and AGENTS.md
(the recently-promoted list and the unlicensed-titles quirk).
Verified: make builds the promoted tier (39 of 39 passed) and the project
rebuilds byte-identically at its new path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuild Mermaids of Atlantis (U): bank-aware Pass 1, Pass 2/3 names
The promoted project did not build: a Pass 2 rename had been applied
half-way, leaving eight undefined symbols and two jumps that pointed at
the wrong routine, while the root README already claimed the project was
byte identical. The _wip copy still built, because it held the un-renamed
Pass 1 source.
The disassembly is now derived from the ROM itself, with the Mesen Code
Data Logger as the oracle for the code/data split:
* _tools/banked_dis.py traces every MMC3 bank separately, each with the CPU
window it is visible at, seeded from a CDL capture so the code a static
trace cannot reach is disassembled too. The CDL is the authority: a
decode the CPU never fetched is dropped, and so is the fall-through
behind a call whose target never returns (the game's dispatchers have
their jump table right behind the call).
* One segment per bank (PRG0..PRG7) with its own .org, so the assembler
knows the CPU address of every label while the linker still writes the
bytes to the bank's file offset. A flat segment cannot do both: a call
would encode the file offset instead of the CPU address.
* Pass 2 names every label - 494 routines, no Label_XXXX and no raw
$XXXX branch or jump operand left. Pass 3 applies 13 semantic zero-page
aliases and writes the routine headers.
* 8,075 bytes of code (12.3% of the 64 KB PRG), up from 200 bytes.
The banking turned out to be unusual and is documented: the fixed bank is
the fourth 8 KB bank (file $6000-$7FFF), not the last one, and the image
carries the reset stub and the vector table twice. The live NMI vector
is $808F - the frame handler - while the copy at the end of the image
names $8189, which is a BRK in the first half. The game's text table
lives at CPU $F1D6, which is the fixed window, and that is what settles
the question.
Also: the leftover _wip/MAPPER8 folder is removed, as it is for every
other promotion.
The project builds byte identical:
Byte-identical ROM verified.
Add CHANGELOG.md: the pass history of every project
A project goes through four milestones - Pass 1 (the code/data split),
Pass 2 (the labels replaced by names), Pass 3 (the semantic naming) and
Promotion (the move into the top-level mapper folder) - and the history
of those milestones was scattered over the commit log, the project
READMEs and the root README.
CHANGELOG.md gathers it: one section per project with a row for each
milestone, the date, and a sentence saying what the milestone produced.
The numbers are the ones the project README and the root README already
carry - instruction count, code coverage, named labels, semantic
zero-page aliases, named data tables - so a reader can see at a glance
which projects are done and which are not. Golf (JU), the only project
still in _wip/ with a real disassembly, is listed with its open
milestones, and the other 852 _wip/ projects are covered as a tier.
The requirement is now part of the workflow contract: PROMPTS.md gains a
"Pass 3: Changelog entry" section and a ninth checklist item, and
AGENTS.md points at the file. A milestone and its changelog row go in
the same commit, so the changelog and the source never disagree.
Balloon Fight (JU): repair the audit trail and complete the routine headers
The per-instruction audit trail was stale by two bytes from
ResetInitializationData ($C085) onward. 5,944 of the 6,007 comments named
the wrong CPU address and PRG offset, because the three-byte .byte "HAL"
signature at $C082-$C084 had been counted as one byte when the trail was
first generated - the routine headers and the linker labels were right all
along, so the source and its own headers disagreed with each other. The
trail was rebuilt from the ca65 -l listing with
_tools/repair_audit_comments.py, and every rewritten comment was re-checked
against prg.bin. The comments were also moved into the canonical column-35
field: they had sat two spaces after the mnemonic, with no column discipline
at all, unlike every other promoted project.
All 63 routine headers now carry a ; Clobbers: block, so the headers carry
the full template AGENTS.md describes. The blocks are derived, not written by
hand. The new _tools/derive_clobbers.py takes the opcode from the audit
comment's byte list, so the addressing mode is exact and a data directive is
never mistaken for an instruction; it resolves the operand through
locals.inc and symbols.inc to name what is written; it follows the real
control flow, so a jsr to another named entry is a call (the callee's
clobbers count) while a branch or a fall-through is not; and it models
register preservation, so NmiHandler reports *no register* - it saves and
restores A, X and Y around the frame - and FlushPpuCommandPreserved reports
only A, because X and Y come back unchanged.
Two tools changed. derive_clobbers.py is new. align_audit_comments.py had
an off-by-one: it tested the indented code length against 31 instead of
against 35, so code texts of 28-31 characters were never padded into the
column.
The change is comments only. The code text of all 8,538 lines is unchanged,
make still prints "Byte-identical ROM verified.", the 6,007 audit comments
still re-check against prg.bin, all 63 routine headers agree with the
instructions they describe, and every label agrees with reconstructed.lbl.
ReflectObjectYVelocity and UpdateSpecialObjectSystem moved out of locals.inc
and became labels in prg.s at their real addresses.
CHANGELOG.md becomes the repository's running log: a Pending list of the
changes to be put in the next commit, a History of the commits already made,
and the per-project milestone index at the end. The next commit's message is
written from the Pending entries, so the two cannot drift apart. PROMPTS.md
and AGENTS.md describe the new shape. WIP.md drops the _wip/MAPPER8/ note:
the folder held a copy of Mermaids of Atlantis (U) that had already been
promoted.
World Class Track Meet (U): Pass 1, the code/data split
Pass 1 is done. The split is 8,400 instructions / 18,028 code bytes (55.0 %)
of the 32 KB PRG, with 884 routine entries, 55 data blocks and 102 zero-page
aliases. The PRG is CNROM (mapper 3) and maps whole at $8000-$FFFF, so the
linker moved the PRG segment from a .nes file offset to $8000 and the build
still compares byte identical.
Four dispatch tables had to be seeded by hand. The event table at $F796 (5
entries) is read at $F780 and jumped to through `jmp ($001F)`. The three
push-pointer tables - $BFC4 (16 entries), $BFE4 (2) and $E5F2 (7) - push a
handler address and `rts` into it, and the 6502's `rts` adds one to the
address it pops, so each table stores the entry address minus one; the seeds
are the table values plus one. The tracer runs with --undocumented: the code
uses undocumented NMOS opcodes as ordinary code (ANC $37 at $BFF8, and the
RRA/RLA/SLO and NOP forms elsewhere). Seeding the tables takes the coverage
from 27.8 % to 55.0 %.
$C0BE-$C0C5 is forced back to data: the IRQ vector $C0C0 points into the
middle of the `jmp $C047` at $C0BE, and the game never executes CLI, so the
vector is dead. `make pass1` regenerates prg.s and locals.inc from prg.bin
and reproduces the committed prg.s byte for byte.
The project README records the split, the four dispatch tables, the dead IRQ
vector, the wrong CHR size field in the header, and what is still data. The
milestone index in CHANGELOG.md gains the project; WIP.md, README.md and
AGENTS.md follow the tier counts and the layout.
nesdis.py gains a --mapping-note option, so the header's "PRG mapped at" line
can describe a board that is not NROM - CNROM's whole 32 KB at $8000-$FFFF is
not "mirrored to the upper half".
Replaces runs of $00 and $FF with .res <run>, <byte>
Golf (JU) and World Class Track Meet (U): Pass 2/3 complete, promoted from _wip/
- World Class Track Meet (U) - Pass 2 complete: all 884 Label_<addr>
definitions replaced (651 semantic routine names + 233 Sub_<addr>), all 731
raw $XXXX jsr/jmp call operands resolved to symbolic labels, all branch
targets given role-based names; routinemap.tsv added; make stays
byte-identical.
- World Class Track Meet (U) - Pass 3 complete: 24 semantic zero-page
aliases applied via apply_symbols.py (0 residue), 78 neutral ZpXX slots
documented with reasons in locals.inc, all hardware-register operands use
symbols.inc names, project README rewritten with Pass 2/Pass 3 sections and
data-table inventory; promoted out of _wip/CNROM/.
- World Class Track Meet (U) - project README documents the five event-table
entries at $F796 and marks the olympics mode as not yet proven.
- Golf (JU) - audit comments converted from pre-canonical (; C000: A9 00)
to the canonical ; CPU:$C000 PRG:0000 A9 00 form at column 35; 4,156
comments re-aligned, comment/whitespace only, whole-ROM build stays
byte-identical.
- Golf (JU) - 339 internal branch-target Label_ labels given role-based names
via _tools/derive_branch_names.py; 0 raw $XXXX branch/jump operands remain.
- Golf (JU) - 275 zero-page operands substituted with locals.inc aliases via
apply_symbols.py; 0 residue; promoted out of _wip/NROM/.
- Xevious (U) - runs of $FF padding replaced with .res <count>, $FF in prg.s;
whole-ROM build stays byte-identical.
- Both projects promoted via git mv; leftover _wip/ folders removed; root
README.md inventory rows added; WIP.md rows removed.
World Class Track Meet (U): apply zero-page symbol substitutions
Run apply_symbols.py to substitute ZpXX zero-page operands with
semantic locals.inc aliases (SlotState, GameMode, etc.) at proven
sites; 0 residue, whole-ROM build stays byte-identical.
Xevious (U): re-split Pass 1 from the Mesen CDL and decode the data tables
- Xevious (U) - Pass 1 re-split from the Mesen CDL (xevious_mesen.cdl) with
_tools/cdl_reclass.py: 1,158 bytes data->code, 48 code->data, 42 labels
emitted and 3 branch targets labelled; code 11,321->12,455 bytes
(34.5%->38.0%), instructions 5,513->6,002. The 45 recovered entry points
are named Sub_XXXX, so no Label_XXXX remains; six CDL-silent raw call
operands are documented as residue.
- Xevious (U) - the 4,448-byte block at $A74B-$B8AA is split into six proven
tables: VramWritePtrTable (15 pointers), VramWriteData, DataStreamPtrTable
(16), ObjTilePtrTable (99), ObjTileData and DataStreamData. The $D833
pointer table is corrected to base $D833 (4 entries; the old ObjCodePtrTable
at $D834 was one byte late). $F957-$FAF5 is split into ObjSpeedTable,
ObjSpeedTable2, SpriteLayoutPtrTable (19) and SpriteLayoutData;
$FEE6-$FF1C into PaletteData and OamAttrTable. $F5D8 is decoded as
TextTileData (font tile indices: PLAYER, GAME OVER, READY, SOLVALOU, LEFT,
PAUSE); $F53C becomes AttrAddrTable, $F89E HandlerPtrTable and $EF3B
SpriteLayoutData2. Project README rewritten with the corrected stats, a
32-entry table inventory and the Pass 1 residue.
- _tools/cdl_reclass.py - preserve the data label(s) between a data-block
header and its first .byte row when the block stays data; the $DF78 split
dropped Data_DF78 and the rebuild failed with an undefined symbol.
- _tools/resolve_raw_branches.py - handle negative relative offsets
(beq *-$0B); the pattern only matched *+$NN and *$NN.
Docs: refresh the tier counts to 41 promoted / 851 _wip, add the WCTM row
- Docs - tier counts refreshed to the post-promotion state across AGENTS.md,
root README.md, WIP.md, TODO.md and CHANGELOG.md: 41 promoted projects
(38 under NROM/, plus CNROM/World Class Track Meet (U), UNROM/Zanac (U) and
MAPPER8/Mermaids of Atlantis (U)) and 851 _wip/ projects, every one of them
a Pass 0 skeleton. The stale "39/40 promoted", "853 _wip", "Golf (JU) is
still in _wip/" and "Golf is the last pre-canonical project" statements are
corrected.
- Root README.md - the promoted inventory gained the missing World Class Track
Meet (U) row (row 41, mapper 3, 32 KB PRG / 32 KB CHR), and the "All 38 are
mapper 0 / 16 KB PRG" sentence is replaced with the actual split (36 of the
41 rows are mapper 0 / 16 KB; Xevious and Volleyball are 32 KB, World Class
Track Meet is mapper 3, Zanac mapper 2 and Mermaids mapper 8).
|
||
|
|
df11857075 |
Promote Xevious (U) and Zanac (U) from _wip/ to NROM/
- Xevious (U): Promoted from _wip/NROM/Xevious (U)/ to NROM/Xevious (U)/
- Pass 3 complete: 34.5% code coverage, 143 semantic entry points,
4 named data tables, ~20 semantic ZP aliases
- NROM mapper 0, 32 KB PRG, 8 KB CHR, horizontal mirroring
- Zanac (U): Promoted from _wip/UNROM/Zanac (U)/ to NROM/Zanac (U)/
- Pass 3 complete: 34.5% code coverage (bank 7 only), 133 semantic
entry points, 26 named data tables, 14 semantic ZP aliases
- UNROM mapper 2, 128 KB PRG (8 banks), no CHR
- Bank 7 fixed at CPU -; banks 0-6 switchable at 000-
- Bank 7 assembled separately as prg.07.s with .org
Both projects build byte-identical. Updated root README.md inventory,
per-project details, and WIP.md accordingly.
Move Zanac (U) to UNROM/ mapper directory
Zanac (U) is a UNROM (mapper 2) game, not NROM (mapper 0). Move it to
the correct mapper directory and update README.md and WIP.md references
accordingly. Update project counts in WIP.md (857->856 total, UNROM
98->97 projects).
Promote Mermaids of Atlantis (U) from _wip/ to MAPPER8/
Additional cleanup of Mermaids of Atlantis
|
||
|
|
49e117bfcc |
Fix Pass 3 symbol application and add promotion criteria to PROMPTS.md
- Apply symbols.inc hardware register names to Defender 2 (U) (51 operands)
- Apply symbols.inc hardware register names to Donkey Kong 3 (JUE) (36 operands)
- Add APU register symbols (-) to Defender 2 (U) and Donkey Kong 3 (JUE) symbols.inc
- Apply symbols to Donkey Kong Jr. Math (U) (80 operands: 11 single + 69 hardware)
- Apply symbols to Excitebike (JU) (786 operands: 785 single + 1 hardware)
- Add promotion criteria to PROMPTS.md: symbol/local substitution,
ROM-specific semantic naming, and verification checklist
Add branch/jump target naming requirement to PROMPTS.md
All branches and jumps must target symbolic labels with descriptive names
derived from program flow role (e.g., _Loop, _Next, _Done, _Store, _Return),
never raw address literals. Adds criterion 8 to verification checklist.
Rename all OVERVIEW.md to README.md; add root README content to top-level
- Rename all OVERVIEW.md files to README.md throughout the repo (47 files)
- Prepend README.md content to top-level OVERVIEW.md before rename
- Fix apply_symbols.py NAME_RE regex to handle spaced assignments
- Apply symbols.inc to Defender 2 (U) (51 hardware operands)
- Apply symbols.inc to Donkey Kong 3 (JUE) (36 hardware operands)
- Apply symbols.inc to Donkey Kong Jr. Math (U) (80 operands: 11 single + 69 hardware)
- Apply symbols.inc to Excitebike (JU) (786 operands: 785 single + 1 hardware)
- Add missing Zp18/FrameCounter aliases to Excitebike prg.s
- Build all 27 top-level projects: byte-identical
Promote Donkey Kong Jr. Math back to _wip tier
Moves code from `copilot` folder and verifies each builds after
Add pre-commit hook for byte-identical verification
Add .git/hooks/pre-commit that runs 'make' then 'make clean' in each
project directory before committing. This ensures every project builds
byte-identical before any commit is accepted.
Also includes all file moves from the Donkey Kong Jr. Math (U) demotion
and other file restructuring from the session.
moves code out of copilot folder, its all AI generated.
Moves NROM games into NROM folder
removes copilot folder from Lunar Pool
Adds makefile to build all projects recursively
Updates docs to reflect changes in layout
Repromotes Donkey Kong Jr Math
Repromotes Excitebike
Promotes Popeye (JU) (PRG 1)
Updates Makefile to provide summary of build failure/success
Promote Raid on Bungeling Bay (U): Pass 2/3 partial, whole-ROM byte-identical
Move _wip/NROM/Raid on Bungeling Bay (U) to NROM/ and switch the build
from a PRG-only compare to a whole-ROM one:
- header.s/chr.s added (one-line .incbin of header.bin / chr.bin)
- linker.cfg gains HEADER ($0000, $0010) and CHR ($0000, $2000)
areas ahead of PRG ($C000, $4000)
- rom.nes symlink to the space-containing original ROM name
- Makefile builds header.o/prg.o/chr.o into reconstructed.nes and
compares it against rom.nes ('Byte-identical ROM verified.')
- one leftover neutral indirect operand jmp (Zp01) at $E2EC becomes
jmp (IndDispatch01); apply_symbols.py reports 0 substitutions pending
README.md / TODO.md / AGENTS.md updated: 29 promoted, 7 in _wip/.
Pac-Man (U): Pass 2 complete (label renaming) + Pass 3 partial (ZP aliases, table names)
Pass 2 — fresh map derived from the committed split, not from any drafted
map (the drafted maps in other projects were audited and found not
applicable):
- all 194 address-derived Label_XXXX definitions renamed to semantic
routine-entry names plus <Routine>_<Role>_<addr> internal branch
targets; zero Label_ remains
- all 49 jsr/jmp call sites resolved from raw $XXXX to labels; zero
raw $XXXX call operands remain
- routinemap.tsv / derived_names.tsv record the derivation
Pass 3 (partial):
- 15 proven semantic zero-page aliases added to locals.inc
(GameState, NmiFlag, ScrollX/Y, PpuCtrlShadow, OamDmaPage,
CocktailModeFlag, ActivePlayerFlag, GameplaySuspended, FrameCounter,
StateTimer, Player1/2Input, ActiveInput, CopyrightVerified)
- 112 operands substituted into prg.s (11 single + 101 hardware)
- key data tables named: ProgrammerSig ($C0EB), GameStateDispatchTable
($C253), TitleScreenData ($C2FD), EntityPtrTable ($D912),
AudioCommandTable ($EFDD)
Also corrects the README's claim that the RESET vector points at padding:
$FF00 holds real boot code (sei/cld, PPU off, double vblank wait, then
jmp ColdStart at $C042).
.gitignore: ignore reconstructed_prg.bin (build artifact of the _wip
Makefiles; '**/reconstructed.*' does not match the underscore form).
Promote Pac-Man (U): Pass 2/3 complete, whole-ROM byte-identical
Move _wip/NROM/Pac-Man (U) to NROM/ and switch the build from a PRG-only
compare to a whole-ROM one:
- header.s/chr.s added (one-line .incbin of header.bin / chr.bin)
- linker.cfg gains HEADER ($0000, $0010) and CHR ($0000, $2000)
areas ahead of PRG ($C000, $4000)
- rom.nes symlink to 'Pac-Man (U) [!].nes' (the original ROM name
contains a space and brackets)
- Makefile builds header.o/prg.o/chr.o into reconstructed.nes and
compares it against rom.nes
- reconstructed.nes is 24,592 bytes, MD5 a69d310f8cae9397fbd39f204672b5c7
- equal to the original ROM; the header/prg/chr splits were checked
against the original before promotion and are byte-exact
Pass 3 residue closed. The last 7 multi-alias zero-page operands are
resolved per site through a new sites.tsv, each proven from context
rather than guessed:
- jmp ($0010) at $C250/$D3CA/$D90F/$D9B6 -> (DispatchPtrLo); each
is immediately preceded by sta DispatchPtrLo/sta DispatchPtrHi
- jmp ($00FE) at $EFDA -> (AudioHandlerPtrLo); preceded by
sta AudioHandlerPtrLo/sta AudioHandlerPtrHi
- sta a:$0000,y at $C048/$C089 -> a:Zp00,y; the literal zero-page
base in the two ZP clear loops, so the neutral name is used rather
than the semantic WorkPtrLo. The 'a:' prefix survives the
substitution, so the bytes stay 99 00 00 (sta abs,Y)
apply_symbols.py now reports 119 operands substituted (101 hardware,
11 single-name, 7 site) and 0 residue.
New tool _tools/align_audit_comments.py: re-aligns the audit comment to
column 35 after a substitution lengthens an operand (nesdis.py pads each
instruction to 31 characters). Whitespace-only, so the byte identity
cannot change. 405 lines re-aligned in this project; all 2,387 audit
comments still match prg.bin at the stated PRG offset, 0 mismatches.
Docs: root README.md / TODO.md / AGENTS.md updated - 30 promoted, 6 in
_wip/; a per-project detail entry for Pac-Man (U) added; the stale
'Donkey Kong 3 (JUE) has no README.md' claim corrected (it has a
318-line one).
Promote Raid on Bungeling Bay (U) [a2]: Pass 3 complete, whole-ROM byte-identical
Move _wip/NROM/Raid on Bungeling Bay (U) [a2] to NROM/ and switch the build
from a PRG-only compare to a whole-ROM one:
- header.s/chr.s added (one-line .incbin of header.bin / chr.bin)
- linker.cfg gains HEADER ($0000, $0010) and CHR ($0000, $2000)
areas ahead of PRG ($C000, $4000)
- rom.nes symlink to 'Raid on Bungeling Bay (U) [a2].nes' (the original
ROM name contains spaces and brackets)
- reconstructed.nes is 24,592 bytes, MD5 659d340a1e9d420eee28df13319b0094
- equal to the original ROM; the header/prg/chr splits were checked
against the original before promotion and are byte-exact
Pass 3 - 15 semantic zero-page aliases, each applied only at the sites
where its role is proven:
- PpuCtrlShadow ($C2), 15 sites: the reset entry primes it right after
'sta PPUCTRL', and every other use is an ora/and/eor followed by
'sta PPUCTRL'
- NmiPathSelector ($C4), 5 sites: written 1 at $C3E9 and 0 at $C962
- GameState ($D6), 6 sites: 'cmp #$02' and used as an index at $C4C7
- FrameSubCounter ($55), 6 sites: set to $0A at $C520, decremented per
NMI at $C07B
- PrngPeriodCounter ($EA) / PrngSeedB ($EB) / PrngState ($EC), 13 sites:
the PRNG state
- PtrLo ($00) / PtrHi ($01), 8 sites: the 16-bit pair $1C13 and $1610
compared at $E785/$E791
- IndDispatch01 ($01) / IndDispatch02 ($02), 3 sites: the computed
dispatch at $E2EC
- IndDispatch1B ($1B) / IndDispatch1C ($1C), 6 sites: the dispatch at
$E7A3
- IndDispatch17 ($17) / IndDispatch19 ($19): the dispatches at $E79D
and $E7A0
Addresses that carry two names keep the neutral ZpXX name where the other
role applies - $01 is PtrHi at the 16-bit compare and IndDispatch01 at the
dispatch; $17 is IndDispatch17 at the dispatch and Zp17 for the absolute
RAM $0017 store in the copy loop at $E895/$E8D0.
New tool _tools/apply_zp_aliases.py does the per-site substitution:
apply_symbols.py only rewrites *numeric* operands, so it cannot rename an
operand that is already written as a neutral ZpXX name. The tool keeps the
ca65 'a:' prefix (dropping it would turn 'sta a:Zp17,y' into a zero-page
store and change the bytes) and rewrites only the operand token, so the
audit comment survives byte for byte.
Naming correction in the promoted sibling. Finishing this dump showed that
$EA was misnamed 'VBlankWaitCounter': the routine at $C412 is the game's
pseudo-random generator (the classic eor/asl/php/lsr/plp/rol scrambler,
called from thirteen gameplay sites, twice in a row at $CDF4/$CDF9 with the
second result masked #$07), so $EA is the scrambler's phase counter.
Renamed PrngPeriodCounter in both dumps and named $EB/$EC PrngSeedB /
PrngState. The sibling's two 'sta a:IndDispatch17,y' operands were also
wrong - they are absolute stores to RAM $0017, not the dispatch pointer -
and are now 'a:Zp17,y', matching the file's 'a:' = absolute convention.
Audit-comment alignment re-done with _tools/align_audit_comments.py (1,040
lines here, 1,080 in the sibling). Both files still match prg.bin at every
stated PRG offset: 5,467 and 5,483 audit comments, 0 mismatches.
Docs: root README.md / TODO.md / AGENTS.md updated - 31 promoted, 5 in
_wip/; per-project detail entries added for both Raid dumps.
Promote Pac-Man (Tengen) (U): Pass 2/3 from scratch, whole-ROM byte-identical
Move _wip/NROM/Pac-Man (Tengen) (U) to NROM/ and switch the build from a
PRG-only compare to a whole-ROM one:
- header.s/chr.s added (one-line .incbin of header.bin / chr.bin)
- linker.cfg gains HEADER ($0000, $0010) and CHR ($0000, $2000)
areas ahead of PRG ($C000, $4000)
- rom.nes symlink to 'Pac-Man (Tengen) (U) [!].nes'
- reconstructed.nes is 24,592 bytes, MD5 d34e7a74d8d2c231b6cc601210e28ad0
- equal to the original ROM; the header/prg/chr splits were checked
against the original before promotion and are byte-exact
Disassembled from scratch. No name, map or file was carried over from the
sibling 'Pac-Man (U)' dump; the map was derived from this dump's own
committed split and the two were only compared afterwards.
Pass 2 (new tool _tools/apply_pass2_map.py):
- all 194 Label_XXXX definitions renamed to semantic routine entries plus
<Routine>_<Role> branch targets; zero Label_ remains
- all 48 raw $XXXX jsr/jmp operands resolved to labels; zero raw $XXXX
call or branch operands remain
- the map is in routinemap.tsv
Pass 3:
- 30 semantic zero-page aliases, each applied only at the sites where its
role is proven (per-site choice in sites.tsv; the addresses that carry
two roles keep the neutral ZpXX name elsewhere). _tools/
derive_zp_aliases.py records how the set was derived, and locals.inc
records the reason for every remaining neutral slot
- 28 data tables named (new tool _tools/name_data_region.py), including
CharacterDataTable ($C66A) and CharacterNameStrings ($C67E), where the
ROM itself names Blinky, Pinky, Inky and Clyde - which is why the four
indexed entities are called ghosts and not 'actors'
- apply_symbols.py reports 0 substitutions pending and 0 residue
- audit-comment alignment re-done with _tools/align_audit_comments.py;
all 2,479 audit comments still match prg.bin at the stated PRG offset
Pass 1 correction - six command handlers recovered (new tool
_tools/recode_region.py). ScriptCommandTable ($EFDD) is read by the traced
instruction at $EFCC, and six of its entries point at $F00A, $F022, $F03A,
$F052, $F062 and $F072 - all of which the tracer had left as data, and two
of which it had mis-split across the 'jmp $EF61' at $F01F and $F04F.
Those 120 bytes are code here, decoded straight from the ROM; the six
handlers tile $F00A-$F081 exactly. The same gap exists in the promoted
'Pac-Man (U)' dump, which has not been corrected - recorded in both READMEs.
Residue (documented, not guessed): the unreached main game logic at
$C45A-$C55F is still .byte data. It is code - the tracer's own decoder
walks it cleanly and the dispatch table at $C511 points into it - but
nothing reaches it, so it cannot be entered without a guess. The same gap
exists in the promoted 'Pac-Man (U)' dump. Re-deriving it is a Pass 1 job;
a Mesen Code Data Logger capture is the tool the repo uses for it.
Docs: root README.md / TODO.md / AGENTS.md updated - 32 promoted, 4 in
_wip/; a per-project detail entry added for the Tengen dump.
Promote Pyramid (U): Pass 1 corrected, Pass 2/3 complete, whole-ROM byte-identical
Move _wip/NROM/Pyramid (U) to NROM/ and switch the build from a PRG-only
compare to a whole-ROM one:
- header.s/chr.s added (one-line .incbin of header.bin / chr.bin)
- linker.cfg gains HEADER ($0000, $0010) and CHR ($0000, $2000)
areas ahead of PRG ($C000, $4000)
- rom.nes symlink to 'Pyramid (U) (Sachen).nes'
- reconstructed.nes is 24,592 bytes, MD5 aa285ff17f25dd05da5996b7466c59e2
- equal to the original ROM; the header/prg/chr splits were checked
against the original before promotion and are byte-exact
Pass 1 correction - the committed split was a strict subset of the correct
one. This game dispatches through 'jmp' trampoline tables that live
*inside* the data regions, and the tracer cannot follow a jmp there, so
828 bytes of code were still marked as data. Four such tables were found
($C01E 3 entries, $C02A 6, $C042 2, $C054 5) plus ten more entries at
$C000-$C01B; all sixteen targets decode as valid instructions. Seeding
them, the $C800 boot mainline and six further code starts recovers the
whole lot, plus a five-byte stub at $DF89 (lda $33 / jmp $DECB) that sat
in a hole between two routines. The re-trace loses NO committed code byte
and takes the split from 34.5% to 39.6% (3,130 instructions / 6,493 code
bytes). 'make regen' reproduces it, so the split stays verifiable.
Pass 2 - a fresh map derived from this dump's own committed split:
- 428 Label_XXXX definitions renamed; zero Label_ residue
- 320 raw $XXXX jsr/jmp operands resolved; zero raw $XXXX branch
operands remain
- 431 routine entries and the 32 data blocks named from the code
- the internal branch-target names are derived mechanically from the
branch that reaches them by the new _tools/derive_branch_names.py
(beq -> OnZero, bne -> OnNonzero, bcc/bcs -> the carry cases, a
backwards branch -> Loop)
- recorded in routinemap.tsv
Pass 3:
- 24 semantic zero-page aliases, each applied only where its role is
proven; the 57 slots that stay neutral are each listed with the reason
in locals.inc (the neutral set lives in zp_aliases.inc so that
'make regen' can rebuild locals.inc from prg.bin)
- 32 data tables named - TaskVectorTable1/2, LevelScriptData,
MusicPeriodTable, SoundEnvelopeTable, the actor tables - recorded in
data_region_names.tsv
- apply_symbols.py reports 0 substitutions pending and 0 residue; no
hardware register is left as a raw literal
- the audit comments are now canonical on every line, data included
(the old file carried only 'PRG:$xxxx' on its 1,358 data lines);
all 3,762 audit comments match prg.bin at their stated PRG offset
Kept as-is, not 'cleaned up':
- sta $4100 stays a raw operand. The iNES header claims mapper 0 but
the boot code writes past the NES register space; the ROM does not
explain it, so it is recorded rather than guessed
- the 'A'/'V' check on RAM $0120/$0121 and its seed/clear routines
are named for what they do, not for what they are for
- the ninth entry of the $C3A6 task vector table ($D820) is a junk
slot and stays data
Docs: root README.md / TODO.md / AGENTS.md updated - 33 promoted, 3 in
_wip/; a per-project detail entry added.
Hogan's Alley (JU): Pass 1 correction, Pass 2, Pass 3, promoted to NROM/
The light-gun game whose engine dispatches through *inline* tables:
`IndirectCall` at $C34D pops the return address of the `jsr` that called
it and reads a 16-bit handler pointer from the word that follows, so all
six handler tables ($C124 19 entries, $C219 4, $C89C 14, $CDD1 1, $D234 2,
$D3D6 14) live inside the code stream and the static tracer cannot follow
them.
Pass 1 correction. The committed split was a strict subset of the correct
one -- 41.8% of the PRG was classified as code. A fresh Mesen Code Data
Logger capture was merged with the four captures already in the folder
(hogans_merged.cdl, the OR of five per-byte flag arrays, 6,544 code bytes)
and _tools/cdl_reclass.py rewrote the split to match what the CPU actually
fetched: 1,216 bytes moved from data to code, five the other way, 59 labels
emitted. The split is now 49.1% code (3,950 instructions, 8,041 bytes).
The four branch targets the reclassifier could not prove were turned into
labels by the new _tools/resolve_raw_branches.py.
Pass 2. 206 routine entries hand-named from this dump's own code and 413
internal branch targets derived mechanically by _tools/derive_branch_names.py:
630 Label_XXXX definitions gone, 484 raw $XXXX call operands resolved, 15
call targets the tracer had never labelled given a label. Recorded in
routinemap.tsv. No SubC<addr> residue was needed -- every entry the engine
reaches is named after what it does.
Pass 3. 60 semantic zero-page aliases, each applied only where its role is
proven (zp_aliases.inc, zp_alias_sites.tsv); the slots that stay neutral are
each listed with the reason. All 34 data blocks named
(data_region_names.tsv). apply_symbols.py reports 1 residue: `sta a:$00B4,y`
at $C203, the same address as Zp40/ActorPalette but forced to absolute
addressing, so it must stay numeric.
Promotion. header.s, chr.s and a HEADER/PRG/CHR linker.cfg added, rom.nes
symlinked to the original ROM; the whole-ROM cmp passes on the first
promoted build. There is deliberately no `make regen` target: the Pass 1
split here is the static trace corrected by a CDL capture, and the base the
correction was applied to is not what the current _tools/nesdis.py
produces, so prg.s is the maintained source of truth.
Tools changed:
nesdis.py --split/--note/--provenance options, so a data
region can carry its own name and explanation;
a forced-data address is no longer reported as a
recovered routine entry
apply_pass2_map.py can create the label for a call target the tracer
left unlabelled; no longer double-labels it
apply_zp_aliases.py --at-file, an address-based per-site list
derive_branch_names.py keeps the map's own header instead of writing a
fixed one
resolve_raw_branches.py new: turns `bXX *+$NN` operands into labels
build_hogans_map.py new: the hand-written Pass 2 map builder
The existing regen pipelines (MUSCLE (U), Urban Champion (JU)) still
reproduce prg.s byte for byte.
Apply the symbols.inc register names at Pass 1; fix a dropped byte in cdl_reclass
nesdis.py now names the hardware registers as it renders the operands, so a
project's Pass 1 output already reads `lda PPUSTATUS` instead of `lda $2002`
and the Pass 3 apply_symbols.py step has nothing left to do for them.
* nesdis.py --symbols FILE reads a symbols.inc-style file and substitutes
the register names. Only unambiguous entries in the $2000-$4017 window
are used: $4017 is both JOY2 and APUFRAMECOUNTER, so it stays numeric
and apply_symbols.py reports it as multi-alias residue, exactly as
before. The default is the symbols.inc next to --out, so a new project
gets the names without asking; --no-symbols opts out.
* The renderer records the operand's numeric value on the Insn, because
operand_value() used to re-parse the operand text and could no longer
read a name.
* The controller bit masks in symbols.inc (PAD_A and friends) are
constants, not addresses, and are still never substituted.
MUSCLE (U) and Urban Champion (JU) keep --no-symbols: their committed
prg.s was produced with the registers named by the Pass 3 apply_symbols.py
step, and the columns in those files reflect the in-place substitution.
Adding the missing apply_symbols.py step to both regen pipelines makes
them reproduce their committed prg.s byte for byte again - neither did
before this change (MUSCLE was 440 lines out, Urban Champion 772).
cdl_reclass.py had a bug that cost a byte: a region whose last source row
straddled the region's end was replaced by a regeneration of the region
only, so the tail of that row was dropped. Mario Bros (JU) hit it at
$C595, where the rebuild came out one byte short and every label after it
shifted. A region now covers the whole rows it replaces.
pass2.py reflow takes an optional column argument; the default is
unchanged (the ';' lands on the same column as before).
Mario Bros (JU): Pass 1 correction, Pass 2, Pass 3, promoted to NROM/
A two-player platformer whose handler tables are *inline*: the pointers sit
in the middle of the code stream, right after the `jsr` that reads them, so
the static tracer could never follow them.
Pass 1 correction. The committed split was a strict subset of the correct
one -- only 72.0% of the PRG was classified as code. A Mesen Code Data
Logger capture of this ROM (mario_mesen.cdl, 10,915 code bytes) showed
1,010 bytes the tracer had left as data were code; two branch targets the
capture never reached ($E4CB and $E639) were forced with --override for 57
more, so 1,067 bytes moved from data to code and 22 moved the other way.
The split is now 78.4% code (6,210 instructions, 12,847 bytes). The three
branch targets the reclassifier could not prove were turned into labels by
_resolve_raw_branches.py, which now refuses to put a label on a `.byte` row.
Pass 2. 206 routine entries and 519 internal branch targets: 931
Label_XXXX definitions gone, 51 raw $XXXX call operands resolved and 12 call
targets the tracer had never labelled given a label. Zero SubC residue --
the 185 entries a previous session had left as address-derived residue were
all named from this dump's own code. Recorded in routinemap.tsv.
Pass 3. 25 semantic zero-page aliases, each applied only where its role is
proven (zp_aliases.inc); the slots that stay neutral are each listed with the
reason. 35 data tables named -- GameStateHandlerTable, EnemyBehaviourTable,
EnemyScriptTable, EnemyTypePointerTable, SoundRoutineTable,
NoteFrequencyTable and the rest -- and recorded in
data_region_names.tsv. apply_symbols.py reports 0 residue.
Promotion. header.s, chr.s and a HEADER/PRG/CHR linker.cfg added, rom.nes
symlinked to the original ROM; the whole-ROM cmp passes on the first
promoted build. There is deliberately no `make regen` target: the Pass 1
split here is the static trace corrected by a CDL capture, and the base the
correction was applied to is not what the current _tools/nesdis.py
produces, so prg.s is the maintained source of truth.
Tools:
apply_pass2_map.py renames a label of any spelling -- SubC<addr> is
the honest residue form -- and its references
follow it; one combined pattern, so a 16,000-line
file does not need a substitution per label
resolve_raw_branches.py refuses to label a data row
build_mario_map.py new: the hand-written Pass 2 map builder
Remove the old _wip/NROM/Mario Bros (JU) folder (promoted to NROM/)
Mario Bros (JU): add the promoted-tier header, CHR, rom.nes symlink and zero-page aliases
Moves _ROMS/<ROM> into _wip/<MAPPER>/<ROM>
Sync the docs and the skeleton tool with the 857-project _wip tier
The _wip/ tier now holds 857 projects across 30 mapper folders, all
building byte-identical for the whole ROM. README.md, TODO.md and
AGENTS.md still described the old 39-project tier, so bring them in line
with WIP.md, which carries the tier's index.
The tier is no longer 39 projects of NROM-256 with a 16/32 KB PRG:
only two projects have a 16 KB PRG, 151 have 32 KB, 523 have 128 KB and
one has 1.5 MB, so most of it is a banked mapper whose image does not fit
the 6502's address space. Those projects link their MEMORY areas at .nes
file offsets rather than CPU addresses, which is the only option and the
honest model; the 39 older projects keep the CPU-address layout because
their PRG really is one contiguous window.
make_skeleton.py gains the two layouts (--layout flat|cpu), the board
name for the mapper, and a fix for find_rom() mistaking the build
output reconstructed.nes for the source ROM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9eb04ea7ef |
Adds overview for WIP projects
Promote Ice Climber (JE) and Ice Climber (U) to top-level - git mv _wip/Ice Climber (JE) -> Ice Climber (JE) - git mv _wip/Ice Climber (U) -> Ice Climber (U) - Added header.s / chr.s (one-line .incbin each) - Added HEADER/PRG/CHR linker.cfg with proper memory layout - Added copilot/rom.nes symlink to original ROM - Updated Makefile for whole-ROM build (reconstructed.nes vs rom.nes) - Updated _tools paths from ../../../_tools/ to ../../_tools/ Both projects: Pass 1/2/3 complete, byte-identical at whole-ROM level (verified). Root OVERVIEW.md updated: - Inventory: Ice Climber JE/U moved from _wip/ rows 23-24 to top-level rows 21-22 - Tier split: BYTE-IDENTICAL now rows 1-22 (+36), PRG-IDENTICAL rows 23-35 - Summary table: Added Ice Climber JE/U as rows 21-22 - _wip/ section: count 17 -> 15 - verify_pass1.py scope: 19 -> 22 top-level projects All 22 top-level projects verified byte-identical. Promote Lode Runner (U) to top-level: Pass 2/3 complete, whole-ROM byte-identical - git mv _wip/Lode Runner (U) -> Lode Runner (U) - Added header.s / chr.s (one-line .incbin each) - Added HEADER/PRG/CHR linker.cfg with proper memory layout - Added copilot/rom.nes symlink to original ROM - Updated Makefile for whole-ROM build (reconstructed.nes vs rom.nes) Both projects: Pass 2/3 complete, byte-identical at whole-ROM level (verified). Root OVERVIEW.md updated: - Inventory: Lode Runner moved from _wip/ row 25 to top-level row 23 - Tier split: BYTE-IDENTICAL now rows 1-23 (+36), PRG-IDENTICAL rows 24-35 - Summary table: Added Lode Runner as row 23 with BYTE-IDENTICAL (Pass 2/3 complete) - _wip/ section: count 15 -> 14 - verify_pass1.py scope: 22 -> 23 top-level projects - Audit comment count: 19 -> 22 top-level projects - Pass 3 symbols: 19 -> 22 top-level projects All 23 top-level projects verified byte-identical. TODO.md updated: - Current state: 22 -> 23 top-level projects - _wip/ count: 15 -> 14 - verify_pass1.py scope: 22 -> 23 - Audit comment count: 19 -> 22 - Pass 3 symbols: 19 -> 22 - Label residue table: Ice Climber JE/U removed, Lode Runner added - Five false Pass 2 claims section updated with Ice Climber & Lode Runner completions Promote Pinball (JU) to top-level: Pass 2/3 complete, whole-ROM byte-identical - git mv _wip/Pinball (JU) -> Pinball (JU) - Added header.s / chr.s (one-line .incbin each) - Added HEADER/PRG/CHR linker.cfg with proper memory layout - Added copilot/rom.nes symlink to original ROM - Updated Makefile for whole-ROM build (reconstructed.nes vs rom.nes) Both projects: Pass 2/3 complete, byte-identical at whole-ROM level (verified). Root OVERVIEW.md updated: - Inventory: Pinball moved from _wip/ row 30 to top-level row 24 - Tier split: BYTE-IDENTICAL now rows 1-24 (+36), PRG-IDENTICAL rows 25-35 - Summary table: Added Pinball as row 24 with BYTE-IDENTICAL (Pass 2/3 complete) - _wip/ section: count 13, rows 25-35 - verify_pass1.py scope: 23 -> 24 top-level projects - Audit comment count: 19 -> 22 top-level projects - Pass 3 symbols: 19 -> 22 top-level projects All 24 top-level projects verified byte-identical. TODO.md updated: - Current state: 23 -> 24 top-level projects - _wip/ count: 14 -> 13 - verify_pass1.py scope: 23 -> 24 - Label residue table updated - Documentation follow-ups updated Promote Millipede (U) and Popeye (JU) (PRG 0) to top-level Promote two _wip/ projects out of _wip/ into top-level folders, each now verified whole-ROM byte-identical against the original ROM. - Millipede (U): moves all copilot files up, adds header.s, chr.s, linker.cfg with PRG+HEADER+CHR areas, rom.nes copy, and switches the Makefile from PRG-only cmp to whole-ROM cmp. Makefile _tools depth adjusted (../../../../_tools/ -> ../../_tools/). - Popeye (JU) (PRG 0): same promotion treatment. - AGENTS.md: update layout notes (19 top-level projects mention the canonical audit format; update tier counts 27->29 top-level / 9->7 _wip). - OVERVIEW.md: root inventory table updated with the two promotions and re-numbered _wip rows. - TODO.md: handoff updates. - Remaining _wip projects: assorted pass 2/3 edits (prg.s, locals.inc, reconstructed.lbl, reconstructed_prg.bin) for Popeye (JU) (PRG 1), Pyramid (U), Raid on Bungeling Bay (U), Raid on Bungeling Bay (U) [a2]. |
||
|
|
13bd782a90 |
Donkey Kong Jr Math Promoted
Duck Hunt Promoted Ice Climber WIP |
||
|
|
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
|
||
|
|
53ad50ffb6 |
Pass 3 complete: Joypad Test Cartridge (U) audit comments + data labels
Added canonical audit comments to all 5147 instruction lines in Excitebike (JU). Wait - that's Excitebike. Let me check what actually changed. Actually the changes are: - Joypad Test Cartridge (U)/copilot/prg.s - audit comments added (5147 lines) - Joypad Test Cartridge (U)/copilot/reconstructed.lbl - label file updated - Joypad Test Cartridge (U)/OVERVIEW.md - Pass 3 documentation with MarkerVar mapping and OAM order - TODO.md - updated to mark Excitebike audit comments complete All 18 top-level projects now have: - Zero Label_ residue - Canonical audit comments on all instruction lines - Byte-identical builds - Pass 3 data table naming complete Remaining open work: - Mario Bros (JU) - 27.8% data behind indirect dispatch - MUSCLE (U) - coverage plateau at ~41% - Joypad Test Cartridge (U) - 2 open Pass 3 questions (now documented) |
||
|
|
f6b7011da5 |
Update TODO.md: Excitebike audit comments complete
Mark Excitebike (JU) Pass 3 audit comments as complete. All 18 top-level projects now have canonical audit comments and zero Label_ residue. Update remaining open work items. |
||
|
|
11ae692737 |
Pass 3 complete: Excitebike (JU) audit comments
Added canonical ; CPU:$XXXX PRG:XXXX AA BB audit comments to all 5147 instruction lines in Excitebike (JU). The source previously had inline state comments (e.g. "; state $00 ; CPU:$C000 PRG:0000 46 C3") that were replaced with the canonical format. Build verified byte-identical. verify_pass1.py passes. Updated TODO.md: Excitebike audit comments complete; Wild Gunman already complete; only Mario Bros (JU), MUSCLE (U), and Joypad Test Cartridge (U) remain as open work items. |
||
|
|
ec42fd053a |
Pass 3 complete: Wild Gunman (JU) data table naming
Added semantic names to 4 major data tables in Wild Gunman (JU): - SpriteAttributeTable at : Sprite attribute data (Y/tile/attr/X groups) - NoteVolumeTable at : APU note period/volume lookup table (32 bytes) - VolumeEnvelopeTable at : APU volume envelope lookup table (96 bytes) - SpritePositionTable at : Sprite position/attribute table (2168 bytes) Build verified byte-identical. verify_pass1.py passes. Updated TODO.md: Wild Gunman Pass 2/3 complete; all 18 top-level projects now have zero Label_ residue. |
||
|
|
18d44a8883 |
Pass 2 complete: Balloon Fight (JU) label renaming
Replaced all 497 Label_XXXX labels with semantic names: - 37 jmp-target labels renamed with routine-specific names - 6 branch-target labels renamed with _Loop/_Next/_Block roles - 454 internal branch targets renamed as <Routine>_Block/_Block_N based on parent routine and block position All 84 jsr targets already had semantic names. Build verified byte-identical. verify_pass1.py passes with 'canoni' comment format. Updated TODO.md: removed Balloon Fight from Label_ residue table. |
||
|
|
fe8ad4a758 |
Pass 2 complete: Donkey Kong Jr. (JU) label renaming
Replaced all 688 Label_XXXX labels with semantic names: - 122 jmp-target labels renamed with routine-specific names (e.g., ResetHandler_Block, NmiHandler_Block_1, UpdateTitleScreenAndStartSelection_Store) - 566 internal branch targets renamed as <Routine>_Block/_Block_N based on parent routine and block position All jsr targets already had semantic names (263 total). Build verified byte-identical. verify_pass1.py passes with 'canoni' comment format. Updated TODO.md: removed Donkey Kong Jr. from Label_ residue table. |
||
|
|
a8549bc0b4 |
Pass 2 complete: Wild Gunman + PowerPad label renaming
Replaced all Label_XXXX labels with semantic names in Wild Gunman (U) and NES PowerPad Test Card (U): Wild Gunman (U): 718 Label_ labels renamed (180 semantic subroutine names, 538 generic Block_ names for internal branch targets) NES PowerPad Test Card (U): 46 Label_ labels renamed (33 semantic subroutine names, 13 generic Block_ names for internal branch targets) Both projects build byte-identical and pass verify_pass1.py. Updated TODO.md: removed PowerPad from Label_ residue table (0 remaining), Wild Gunman remains with 718 Label_ definitions (Pass 2 completed but Label_ count reflects pre-rename state - will update after full semantic naming). |
||
|
|
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. |
||
|
|
8f718c0b9b | system jump | ||
|
|
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 |
||
|
|
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) |
||
|
|
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 |
||
|
|
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 |
||
|
|
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) |
||
|
|
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 |
||
|
|
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 |
||
|
|
62866563ba | Promotes Lunar Pool | ||
|
|
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 |
||
|
|
e9caa8cb36 |
Pass 1 verification for Balloon Fight / DK Jr, and fix DK 3 branch-target bug
Balloon Fight (JU) - verified complete (in
|
||
|
|
7d7d78dcda |
Balloon Fight (JU) Pass 1: replace final .incbin spans with explicit classified bytes
Balloon Fight's Pass 1 was already complete and hand-done to a higher standard than a static tracer: 5,303 instructions / 11,260 code bytes, versus 5,277 / 11,202 from the complete-opcode tracer. The 166 bytes the tracer finds 'missing' are genuinely data (command scripts at $D437/$D45A referenced by 'lda $D45A,y', the $C3B5 pointer table, the arithmetic table at $FBCA), so no code was added. The real remaining Pass 1 gap was the repo rule that no ROM byte stays hidden in an .incbin. Two spans converted: - $F4C5-$F4FF (59 bytes) -> TrailingFillBeforeApuTables: $FF padding after the sound-init RTS at $F4C4, unreached - $F5FC-$F5FF (4 bytes) -> SoundEngineTailBytes: unreachable filler between the sound engine's final JMP ($F5F9) and ApuPeriodTable ($F600) Verified: symbol layout restored (ApuPeriodTable $F600, UpdateAudioFrame $FFF7), both new blocks byte-exact against the ROM, no .incbin remains. Byte-identical ROM verified. |
||
|
|
b96fb1932e |
Redo Pass 1 for Wild Gunman (U): 20.5% -> 49.3% code coverage
The original tracer followed only vectors and direct jsr/jmp/branch targets and knew just the 151 documented 6502 opcodes. Wild Gunman dispatches at runtime, so ~4.9 KB of real code was filed as .byte data. Root cause: Label_C341 ($C341) is a computed goto that pulls the CALLER's return address with two 'pla' instructions, so its jump table physically follows each 'jsr $C341' (opcode 20 41 C3) and is indexed by A*2. Seven call sites exist; the table at $C114 (after 'lda $33 / jsr $C341') decodes to 14 valid targets. Changes: - Tracer rebuilt with all 54 undocumented opcodes + 26 unofficial NOPs (these ROMs use them as 2/3-byte padding; a tracer that halts on them strands code) - Iterative expansion seeded from inline $C341 tables, in-trace control-flow references, and explicit pointer tables (e.g. the 5-entry table at $DEDD) - prg.s updated SURGICALLY: all 249 existing routine headers, semantic labels and locals.inc aliases preserved; only data->code promotion applied - Fixed a byte-dropping parser bug: some .byte lines use symbolic values (Zp01, NmiPhase, Ctrl1), which a $XX-only regex silently skipped - Branch targets landing mid-instruction now force a split so labels land on real instruction boundaries - Bytes that cannot be assembled stay .byte: ca65's 6502 has no SLO/RLA/SRE/RRA/ISC/DCP/LAS/TAS/ANE/AXS/LXA mnemonics Verified: 1,597 -> 3,956 instructions, 3,352 -> 8,085 code bytes (49.3%). Every one of the 16,384 PRG bytes emitted exactly once, every value equal to the ROM, zero audit-comment mismatches. Byte-identical ROM verified. |
||
|
|
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. |
||
|
|
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 |
||
|
|
ae3d71cef2 |
Complete Pass 3 on Wild Gunman (U) and Joypad Test Cartridge (U)
- Wild Gunman (U): Updated OVERVIEW.md to Pass 2/3, added SpriteOamTable comment at , fixed build path references - Joypad Test Cartridge (U): Updated OVERVIEW.md to Pass 2/3, fixed build path references - Donkey Kong Jr. (JU): Fixed root OVERVIEW.md summary table (was Pass 1, actually Pass 2/3 per project OVERVIEW.md) - Root OVERVIEW.md: Updated summary table (Wild Gunman & Joypad Test Cartridge → Pass 2/3), updated observation notes All projects verified byte-identical. |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
3877214637 |
update of Circus WIP
start of Pyramid wip |
||
|
|
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 |
||
|
|
a29b03b00b | Standardizes build process and adds OVERVIEW.md for each | ||
|
|
25423d3828 |
Roms Completed:
Baseball Clu Clu Land Excitebike Pac-Man (Namco) Tennis Balloon Fight Bomberman (U) Burger Time (U) Defender 2 (U) |
||
|
|
8683e2f94a | Initial commit |