romdevtools 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +56 -44
- package/CHANGELOG.md +355 -0
- package/README.md +4 -4
- package/examples/README.md +7 -7
- package/examples/atari2600/templates/platformer.asm +1227 -325
- package/examples/atari2600/templates/puzzle.asm +1056 -0
- package/examples/atari2600/templates/racing.asm +909 -257
- package/examples/atari2600/templates/shmup.asm +1035 -218
- package/examples/atari2600/templates/sports.asm +1143 -229
- package/examples/atari7800/templates/hello_sprite.c +8 -4
- package/examples/atari7800/templates/platformer.c +991 -152
- package/examples/atari7800/templates/puzzle.c +1091 -145
- package/examples/atari7800/templates/racing.c +949 -118
- package/examples/atari7800/templates/shmup.c +812 -130
- package/examples/atari7800/templates/sports.c +820 -181
- package/examples/c64/templates/platformer.c +876 -157
- package/examples/c64/templates/puzzle.c +881 -143
- package/examples/c64/templates/racing.c +873 -88
- package/examples/c64/templates/shmup.c +762 -154
- package/examples/c64/templates/sports.c +755 -95
- package/examples/gb/templates/platformer.c +841 -175
- package/examples/gb/templates/puzzle.c +1094 -176
- package/examples/gb/templates/racing.c +761 -169
- package/examples/gb/templates/shmup.c +679 -169
- package/examples/gb/templates/sports.c +790 -153
- package/examples/gba/templates/platformer.c +624 -169
- package/examples/gba/templates/puzzle.c +535 -207
- package/examples/gba/templates/racing.c +513 -196
- package/examples/gba/templates/shmup.c +565 -168
- package/examples/gba/templates/sports.c +454 -162
- package/examples/gbc/templates/platformer.c +944 -176
- package/examples/gbc/templates/puzzle.c +1131 -177
- package/examples/gbc/templates/racing.c +891 -175
- package/examples/gbc/templates/shmup.c +827 -179
- package/examples/gbc/templates/sports.c +870 -156
- package/examples/genesis/templates/platformer.c +747 -129
- package/examples/genesis/templates/puzzle.c +702 -208
- package/examples/genesis/templates/racing.c +728 -193
- package/examples/genesis/templates/shmup.c +535 -142
- package/examples/genesis/templates/shmup_2p.c +13 -1
- package/examples/genesis/templates/sports.c +495 -158
- package/examples/gg/templates/platformer.c +883 -214
- package/examples/gg/templates/puzzle.c +906 -181
- package/examples/gg/templates/racing.c +919 -160
- package/examples/gg/templates/shmup.c +716 -177
- package/examples/gg/templates/sports.c +735 -128
- package/examples/lynx/templates/platformer.c +604 -50
- package/examples/lynx/templates/puzzle.c +533 -130
- package/examples/lynx/templates/racing.c +538 -102
- package/examples/lynx/templates/shmup.c +461 -122
- package/examples/lynx/templates/sports.c +496 -69
- package/examples/msx/platformer/main.c +648 -159
- package/examples/msx/puzzle/main.c +750 -185
- package/examples/msx/racing/main.c +669 -177
- package/examples/msx/shmup/main.c +460 -177
- package/examples/msx/sports/main.c +591 -124
- package/examples/nes/templates/platformer.c +586 -160
- package/examples/nes/templates/puzzle.c +603 -222
- package/examples/nes/templates/racing.c +505 -197
- package/examples/nes/templates/shmup.c +339 -144
- package/examples/nes/templates/sports.c +341 -182
- package/examples/pce/platformer/main.c +875 -204
- package/examples/pce/puzzle/main.c +797 -216
- package/examples/pce/racing/main.c +782 -206
- package/examples/pce/shmup/main.c +638 -211
- package/examples/pce/sports/main.c +585 -167
- package/examples/porting-across-platforms/README.md +1 -1
- package/examples/sms/templates/platformer.c +765 -176
- package/examples/sms/templates/puzzle.c +783 -177
- package/examples/sms/templates/racing.c +812 -133
- package/examples/sms/templates/shmup.c +601 -148
- package/examples/sms/templates/shmup_2p.c +17 -1
- package/examples/sms/templates/sports.c +633 -121
- package/examples/snes/templates/music_demo.c +7 -0
- package/examples/snes/templates/platformer-data.asm +123 -24
- package/examples/snes/templates/platformer-hdr.asm +57 -0
- package/examples/snes/templates/platformer.c +587 -149
- package/examples/snes/templates/puzzle-data.asm +116 -21
- package/examples/snes/templates/puzzle-hdr.asm +57 -0
- package/examples/snes/templates/puzzle.c +632 -185
- package/examples/snes/templates/racing-data.asm +390 -32
- package/examples/snes/templates/racing-hdr.asm +57 -0
- package/examples/snes/templates/racing.c +807 -177
- package/examples/snes/templates/shmup-data.asm +87 -29
- package/examples/snes/templates/shmup-hdr.asm +57 -0
- package/examples/snes/templates/shmup.c +459 -180
- package/examples/snes/templates/sports-data.asm +48 -2
- package/examples/snes/templates/sports-hdr.asm +57 -0
- package/examples/snes/templates/sports.c +414 -156
- package/package.json +12 -12
- package/src/cores/wasm/bluemsx_libretro.js +1 -1
- package/src/cores/wasm/bluemsx_libretro.wasm +0 -0
- package/src/cores/wasm/fceumm_libretro.js +1 -1
- package/src/cores/wasm/fceumm_libretro.wasm +0 -0
- package/src/cores/wasm/gambatte_libretro.js +1 -1
- package/src/cores/wasm/gambatte_libretro.wasm +0 -0
- package/src/cores/wasm/geargrafx_libretro.js +1 -1
- package/src/cores/wasm/geargrafx_libretro.wasm +0 -0
- package/src/cores/wasm/genesis_plus_gx_libretro.js +1 -1
- package/src/cores/wasm/genesis_plus_gx_libretro.wasm +0 -0
- package/src/cores/wasm/handy_libretro.js +1 -1
- package/src/cores/wasm/handy_libretro.wasm +0 -0
- package/src/cores/wasm/mgba_libretro.js +1 -1
- package/src/cores/wasm/mgba_libretro.wasm +0 -0
- package/src/cores/wasm/prosystem_libretro.js +1 -1
- package/src/cores/wasm/prosystem_libretro.wasm +0 -0
- package/src/cores/wasm/snes9x_libretro.js +1 -1
- package/src/cores/wasm/snes9x_libretro.wasm +0 -0
- package/src/cores/wasm/stella2014_libretro.js +1 -1
- package/src/cores/wasm/stella2014_libretro.wasm +0 -0
- package/src/cores/wasm/vice_x64_libretro.js +1 -1
- package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
- package/src/host/LibretroHost.js +304 -11
- package/src/http/tool-registry.js +11 -11
- package/src/mcp/server.js +6 -0
- package/src/mcp/tools/cheats.js +2 -1
- package/src/mcp/tools/disasm-rebuild.js +315 -65
- package/src/mcp/tools/disasm.js +149 -28
- package/src/mcp/tools/find-references.js +216 -51
- package/src/mcp/tools/frame.js +14 -6
- package/src/mcp/tools/index.js +18 -4
- package/src/mcp/tools/input.js +31 -7
- package/src/mcp/tools/lifecycle.js +6 -4
- package/src/mcp/tools/memory.js +208 -39
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/playtest.js +56 -4
- package/src/mcp/tools/preview-tile.js +6 -2
- package/src/mcp/tools/project.js +1114 -120
- package/src/mcp/tools/rom-id.js +5 -1
- package/src/mcp/tools/run-until.js +4 -2
- package/src/mcp/tools/snippets.js +6 -6
- package/src/mcp/tools/sprite-pipeline.js +14 -2
- package/src/mcp/tools/state.js +2 -1
- package/src/mcp/tools/tile-inspect.js +8 -1
- package/src/mcp/tools/toolchain.js +55 -11
- package/src/mcp/tools/watch-memory.js +145 -27
- package/src/observer/bus.js +73 -0
- package/src/observer/livestream.html +4 -2
- package/src/observer/tool-wrap.js +17 -14
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +64 -17
- package/src/platforms/atari2600/MENTAL_MODEL.md +5 -1
- package/src/platforms/atari2600/TROUBLESHOOTING.md +40 -0
- package/src/platforms/atari7800/MENTAL_MODEL.md +32 -11
- package/src/platforms/atari7800/TROUBLESHOOTING.md +5 -5
- package/src/platforms/c64/MENTAL_MODEL.md +11 -4
- package/src/platforms/c64/TROUBLESHOOTING.md +13 -0
- package/src/platforms/gb/MENTAL_MODEL.md +19 -4
- package/src/platforms/gb/TROUBLESHOOTING.md +101 -6
- package/src/platforms/gb/lib/c/README.md +10 -11
- package/src/platforms/gb/lib/c/gb_crt0.s +27 -3
- package/src/platforms/gb/lib/c/patch-header.js +19 -6
- package/src/platforms/gba/MENTAL_MODEL.md +4 -4
- package/src/platforms/gba/TROUBLESHOOTING.md +3 -3
- package/src/platforms/gba/lib/c/gba_sfx.c +40 -0
- package/src/platforms/gba/lib/c/gba_sfx.h +10 -0
- package/src/platforms/gbc/MENTAL_MODEL.md +16 -4
- package/src/platforms/gbc/TROUBLESHOOTING.md +24 -3
- package/src/platforms/gbc/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gbc/lib/c/README.md +10 -11
- package/src/platforms/gbc/lib/c/font.h +43 -0
- package/src/platforms/gbc/lib/c/gb_crt0.s +26 -3
- package/src/platforms/gbc/lib/c/patch-header.js +19 -6
- package/src/platforms/genesis/MENTAL_MODEL.md +43 -9
- package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
- package/src/platforms/genesis/lib/c/genesis_sfx.c +37 -0
- package/src/platforms/genesis/lib/c/genesis_sfx.h +1 -0
- package/src/platforms/gg/MENTAL_MODEL.md +4 -4
- package/src/platforms/gg/TROUBLESHOOTING.md +14 -18
- package/src/platforms/gg/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gg/lib/c/gg_crt0.s +14 -2
- package/src/platforms/gg/lib/c/joypad_read.c +29 -0
- package/src/platforms/lynx/MENTAL_MODEL.md +1 -1
- package/src/platforms/lynx/TROUBLESHOOTING.md +3 -3
- package/src/platforms/lynx/lib/c/lynx_sfx.c +38 -2
- package/src/platforms/lynx/lib/c/lynx_sfx.h +1 -0
- package/src/platforms/msx/MENTAL_MODEL.md +11 -5
- package/src/platforms/msx/TROUBLESHOOTING.md +21 -0
- package/src/platforms/msx/lib/c/msx_crt0.s +27 -0
- package/src/platforms/msx/lib/c/msx_hw.h +3 -0
- package/src/platforms/msx/lib/c/msx_vdp.c +70 -0
- package/src/platforms/nes/MENTAL_MODEL.md +12 -5
- package/src/platforms/nes/lib/c/nes_runtime.c +190 -34
- package/src/platforms/nes/lib/c/nes_runtime.h +35 -0
- package/src/platforms/pce/MENTAL_MODEL.md +14 -5
- package/src/platforms/pce/TROUBLESHOOTING.md +9 -0
- package/src/platforms/pce/lib/c/pce_hw.h +13 -1
- package/src/platforms/pce/lib/c/pce_sound.c +22 -0
- package/src/platforms/pce/lib/c/pce_video.c +32 -0
- package/src/platforms/sms/MENTAL_MODEL.md +11 -6
- package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
- package/src/platforms/sms/lib/c/sms_crt0.s +14 -2
- package/src/platforms/snes/MENTAL_MODEL.md +7 -2
- package/src/platforms/snes/TROUBLESHOOTING.md +40 -1
- package/src/playtest/playtest.js +73 -3
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +13 -8
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.crt0.s +58 -5
- package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +52 -3
- package/src/toolchains/cc65/presets/pce/rom32k.cfg +52 -0
- package/src/toolchains/index.js +64 -19
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,361 @@ All notable changes to `romdevtools`. Dates are release dates.
|
|
|
4
4
|
(Published as `romdev-mcp` through 0.11.0; renamed to `romdevtools` in 0.13.0 —
|
|
5
5
|
the `romdev-mcp` bin is kept as an alias.)
|
|
6
6
|
|
|
7
|
+
## 0.29.0 — 2026-06-11
|
|
8
|
+
|
|
9
|
+
### Examples — the complete-game library, finished & made honest
|
|
10
|
+
- **The 14×5 grid is complete (70 games).** Every platform now ships all five
|
|
11
|
+
canonical genres. The last gap, the Atari 2600 puzzle, ships as **TILE TWINS**
|
|
12
|
+
— a memory match-pairs game (a real puzzle: a static, turn-based board drawn
|
|
13
|
+
as full-width COLUPF bands, the honest TIA fit — not a colored match-3 grid the
|
|
14
|
+
TIA can't render). Forkable via `examples({op:'fork', example:'atari2600/puzzle'})`.
|
|
15
|
+
- **C64 games now SAVE for real — 1541 disk save.** The honest C64 medium is the
|
|
16
|
+
floppy (no battery SRAM). All 5 C64 games write a hi-score/record to a SEQ file
|
|
17
|
+
on drive 8 via the KERNAL; run from a `.d64` it commits to the live disk and
|
|
18
|
+
`state({op:'exportDisk'})` captures it (reload restores it). As a bare `.prg`
|
|
19
|
+
there's no mounted disk, so it's an honest in-session no-op. (Replaces the old
|
|
20
|
+
gated no-op seams — the VICE core already supported writable-disk write-back.)
|
|
21
|
+
- **C64 two-player now works.** The VICE core drove only one control port per
|
|
22
|
+
RetroPad; host port-1 (P2) input never reached the game. Now both standard
|
|
23
|
+
control ports are live (host port 0 = P1, host port 1 = P2) — verified by
|
|
24
|
+
driving both paddles independently in the versus games.
|
|
25
|
+
- **PCE save claim corrected (honesty).** A bare HuCard cannot save — BRAM is
|
|
26
|
+
peripheral-only (CD-ROM² / Tennokoe Bank / Memory Base 128) on real hardware.
|
|
27
|
+
The 5 PCE games no longer claim a battery save; they keep an honest in-session
|
|
28
|
+
hi-score, with the BRAM mapping documented in-file as the real-hardware path.
|
|
29
|
+
- **Honest framing.** The examples are described as SCAFFOLDING, not showcases:
|
|
30
|
+
the gameplay is intentionally thin — their value is the working boot sequence,
|
|
31
|
+
APIs, and syntax to fork from. Superlatives dropped from the `examples` tool
|
|
32
|
+
doctrine and the generated project README.
|
|
33
|
+
|
|
34
|
+
### Added — livestream frame coverage: see what the agent is doing
|
|
35
|
+
- **Most state-changing tools now emit the post-call frame to /livestream**,
|
|
36
|
+
at zero cost to the agent (deferred PNG encode after the response goes
|
|
37
|
+
out): `frame({op:'step'})`, `input` set/press/sequence/navigate,
|
|
38
|
+
`state({op:'load'})`, `loadMedia`, `host({op:'reset'})` (soft + hard),
|
|
39
|
+
`runUntil`, `cheats({op:'apply'})`, and `cpu({op:'call'})` — joining the
|
|
40
|
+
breakpoint/watch hits, verify, and stepInstruction that already emitted.
|
|
41
|
+
- **Throttled to one frame per 2s per (session, tool)**, trailing-edge: a
|
|
42
|
+
frame-step loop can't flood the stream and its LAST frame always lands;
|
|
43
|
+
different tools back-to-back all show; multiple agents on one server
|
|
44
|
+
never throttle each other.
|
|
45
|
+
- **`call_frame` events carry a caption** (`step ×30`, `press start`,
|
|
46
|
+
`state load boss`, `loaded game.nes`) and the livestream UI shows it on
|
|
47
|
+
the image card — the stream reads as a narrative.
|
|
48
|
+
- **To-disk renders now reach the stream too**: `tiles({op:'preview'})`,
|
|
49
|
+
`extractSpriteSheet`-style file renders, and `encodeArt` quantize/crop
|
|
50
|
+
attach the PNG as an observer sideband when routed to `outputPath` — the
|
|
51
|
+
human sees the render even though the agent only gets a path.
|
|
52
|
+
|
|
53
|
+
## 0.28.0
|
|
54
|
+
|
|
55
|
+
The reverse-engineering release: the three RE primitives — break-instant
|
|
56
|
+
`registersAtHit`, interference-free `pure` CPU calls, and the
|
|
57
|
+
`watch({on:'copy'})` graphics source-trace — now work on ALL 14 platforms
|
|
58
|
+
(every emulator core rebuilt; upstream pins unchanged, everything carried by
|
|
59
|
+
the patches in `scripts/patches/`). Plus the full scaffold overhaul from real
|
|
60
|
+
RetroDECK playtesting, banked-cart parity for disasm/rebuild, the value-search
|
|
61
|
+
upgrades, and the playtest co-drive detection. Details per section below.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
### Added — pure calls + the generic copy trace on ALL 14 platforms (primitives #2 and #3)
|
|
65
|
+
The other two primitives from the all-platforms RE proposal, completing the
|
|
66
|
+
set (registersAtHit was #1):
|
|
67
|
+
- **`cpu({op:'call', pure:true})` works everywhere.** The guarantee is the
|
|
68
|
+
same on every platform — the game's own NMI/IRQ/VBlank logic CANNOT run
|
|
69
|
+
during the call and stomp the routine's output buffer — with the mechanism
|
|
70
|
+
reported as `pureMode`: Genesis/SMS/GG step ONLY the CPU (`'cpu-only'`,
|
|
71
|
+
the gpgx separable-loop path); every other core suppresses interrupt
|
|
72
|
+
DELIVERY for the duration (`'irq-blocked'` via a new `romdev_irqblock_set`
|
|
73
|
+
export — pending lines stay pending, video/timers advance harmlessly, no
|
|
74
|
+
game handler executes); the 2600's 6507 has no interrupt lines at all
|
|
75
|
+
(`'no-interrupts'`). Proven live on NES: NMI delivery verified firing,
|
|
76
|
+
then silent under the block, then a planted routine pure-called
|
|
77
|
+
end-to-end with its write landing.
|
|
78
|
+
- **`watch({on:'copy'})` — the generic "where does this graphic come
|
|
79
|
+
from?".** Logs every write landing in a VRAM/dest address window with the
|
|
80
|
+
EXECUTING instruction's PC. Port-based video memory is hooked INSIDE the
|
|
81
|
+
cores — NES $2007, SNES $2118/19 (BOTH CPU port writes and the DMA path —
|
|
82
|
+
the PC is the DMA-triggering instruction), PCE VWR, MSX VDP data port,
|
|
83
|
+
SMS/GG/Genesis VDP data port (the CPU-port complement of the Genesis DMA
|
|
84
|
+
watch). Direct-mapped platforms (GB/GBC, GBA, C64, Lynx, 7800) route
|
|
85
|
+
through the CPU-address range log automatically. Follow a hit with
|
|
86
|
+
breakpoint({on:'pc', address: pc}) for registersAtHit at the uploader.
|
|
87
|
+
- Cores rebuilt again (same pins; the scripts/patches/ diffs carry
|
|
88
|
+
everything — all 11 verified to apply clean to pristine checkouts).
|
|
89
|
+
- `test/pure-copy-primitives.test.js`: the 13-core irq-block/run-pure
|
|
90
|
+
feature matrix, NES NMI-delivery proof + end-to-end pure call, MSX
|
|
91
|
+
block-safety, and copy traces on NES (port), SNES (port+DMA), GB (mapped).
|
|
92
|
+
|
|
93
|
+
### Fixed/Added — registersAtHit + freeze-after-hit on ALL 14 platforms (every core rebuilt)
|
|
94
|
+
The gpgx round's break-instant fixes, extended to every other core — the same
|
|
95
|
+
three guarantees now hold across the whole platform matrix:
|
|
96
|
+
- **`registersAtHit` everywhere** — every breakpoint hit (pc-break, watchdog,
|
|
97
|
+
write-watch, read-watch) on every platform freezes the FULL register file at
|
|
98
|
+
the hit instant inside the core hook, exported via `romdev_regsnap_get` and
|
|
99
|
+
surfaced in the breakpoint hit response. Per-CPU register sets: 6502 family
|
|
100
|
+
(NES/2600/7800/C64/Lynx/PCE) A/X/Y/P/S/PC; 65816 (SNES) +DB/D; sm83 (GB/GBC)
|
|
101
|
+
A/F/B/C/D/E/H/L/SP; Z80 (SMS/GG/MSX) +IX/IY; m68k (Genesis) D0-7/A0-7/SR;
|
|
102
|
+
ARM7 (GBA) r0-r15/CPSR. NES previously snapshotted pc-breaks only — its
|
|
103
|
+
write/read hits now snapshot too.
|
|
104
|
+
- **Freeze-after-hit everywhere** — once a hit fires, the CPU run loop stays
|
|
105
|
+
frozen (across re-entries and frames) until the host clears the hit, so even
|
|
106
|
+
live register reads agree with the snapshot. Previously each core resumed on
|
|
107
|
+
the next loop re-entry and the registers drifted.
|
|
108
|
+
- **Executing-instruction PC everywhere** — write/read watchpoints and range
|
|
109
|
+
logs report the EXECUTING instruction's first byte, latched at dispatch
|
|
110
|
+
(sm83/Z80/65816/6502 PCs advance past operands mid-instruction — the same
|
|
111
|
+
off-by-one class the gpgx round fixed for m68k; GBA reports the pipeline PC,
|
|
112
|
+
matching its breakpoint-address convention).
|
|
113
|
+
- Cores rebuilt: fceumm, snes9x, gambatte, mGBA, handy, vice, stella2014,
|
|
114
|
+
prosystem, geargrafx, bluemsx (pins unchanged; the romdev patches in
|
|
115
|
+
scripts/patches/ carry all of it — the whole stack reproduces from a clean
|
|
116
|
+
clone). `cpu({op:'call', pure:true})` remains gpgx-only (the other systems'
|
|
117
|
+
CPU/video loops are not separable without deeper core surgery); their calls
|
|
118
|
+
carry the ⚠ frame-logic caveat instead.
|
|
119
|
+
- `test/regsnap-all-cores.test.js`: live single-step snapshot + freeze proof
|
|
120
|
+
on 10 platforms (plus the existing gpgx suite for Genesis/SMS/GG).
|
|
121
|
+
|
|
122
|
+
### Fixed/Added — gpgx core round (the NBA-Jam-both-consoles feedback): break-instant truth on Genesis/SMS/GG
|
|
123
|
+
The first core rebuild in this release (gpgx only; pins unchanged, patch extended).
|
|
124
|
+
- **`registersAtHit` on Genesis/SMS/GG** — `breakpoint({on:'pc'|'write'|'read'})`
|
|
125
|
+
hits now carry the FULL register file (m68k d0-d7/a0-a7/pc/sr/sp; z80
|
|
126
|
+
a/f/b/c/d/e/h/l/ix/iy/pc/sp) frozen by the core AT the hit instant. gpgx
|
|
127
|
+
schedules CPUs per scanline, so the live register file used to drift
|
|
128
|
+
hundreds of instructions past a hit before the host could read it — the
|
|
129
|
+
"wrong-pointer chases" that cost a real RE session ~2h. On a pc-break the
|
|
130
|
+
CPU now also stays FROZEN for the remainder of the frame (and across
|
|
131
|
+
frames until the hit is cleared), so even live reads agree.
|
|
132
|
+
- **Write/read watchpoint PC is the EXECUTING instruction** — the hooks now
|
|
133
|
+
record the instruction's first-byte address latched at dispatch, not the
|
|
134
|
+
post-prefetch PC (the orb-at-$2A7216-reported-as-$2A721C off-by-one).
|
|
135
|
+
`breakpoint({on:'write'})` also renames `value`→`valueByte` (it's the one
|
|
136
|
+
byte that landed, not the operand) and explains its `hits` semantics.
|
|
137
|
+
- **`cpu({op:'call', pure:true})`** — steps ONLY the active CPU (new
|
|
138
|
+
`romdev_run_pure` export): no VDP line processing, no co-CPU, no interrupts
|
|
139
|
+
raised — so the game's own VBlank logic can NOT run "concurrently" and
|
|
140
|
+
stomp the driven routine's output buffer (a real session diffed a correct
|
|
141
|
+
codec reimplementation against that poisoned output for ~1.5h). Non-pure
|
|
142
|
+
calls that spanned frames now carry a loud ⚠ caveat naming the risk and
|
|
143
|
+
the fix.
|
|
144
|
+
- **Genesis `system_ram` normalized to CPU byte order** — gpgx stores 68k
|
|
145
|
+
work RAM host-LE word-swapped (`work_ram[A^1]`); the raw layout leaked
|
|
146
|
+
through every byte-granular tool. Self-consistent within search→write
|
|
147
|
+
loops (which is why it hid — even a test had the swapped bytes baked in as
|
|
148
|
+
the expected value), but off-by-XOR-1 the moment an offset crossed to/from
|
|
149
|
+
disassembly addresses or cheat-DB maps. Offset X now IS the byte the 68k
|
|
150
|
+
sees at $FF0000+X; words read big-endian as documented. This also fixes
|
|
151
|
+
`cpu({op:'call'})` sentinel pushes / `presetMemory` writes for any non-zero
|
|
152
|
+
sentinel address (the default $0 sentinel was swap-invariant, hiding it).
|
|
153
|
+
- breakpoint hit responses normalize `hits` (a watchdog stop no longer
|
|
154
|
+
reports the contradictory `hit:true, hits:0`).
|
|
155
|
+
- Docs: the held-input menu trick (when a `pressDuring` schedule never
|
|
156
|
+
registers on a menu screen, hold via `input({op:'set'})` and omit
|
|
157
|
+
pressDuring — runs inherit held input) is now in the breakpoint/watch tool
|
|
158
|
+
docs; the server banner prints a one-line headless note when no display is
|
|
159
|
+
available (so an agent knows before promising a playtest window).
|
|
160
|
+
- `test/gpgx-registers-at-hit.test.js`: live-core coverage for all of it,
|
|
161
|
+
including a per-platform Genesis memory-read smoke (the earlier
|
|
162
|
+
"info is not defined" regression was invisible to a fake-host-only suite).
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
### Fixed/Added — value-search upgrades (from the locate-value skill review)
|
|
166
|
+
- **Relative compares work as the FIRST `searchNext`.** `op:'search'` now
|
|
167
|
+
baselines every candidate at seed time, so `compare:'inc'/'dec'/'changed'/
|
|
168
|
+
'unchanged'` no longer silently return 0 candidates on the first narrow
|
|
169
|
+
(the footgun a real session burned rounds on and a skill had to document —
|
|
170
|
+
the "do one eq round first" workaround is obsolete).
|
|
171
|
+
- **Representation-aware search** — `memory({op:'search', as:'bcd'|'digits'})`
|
|
172
|
+
for the stored≠displayed cases: `'bcd'` matches packed-BCD values (2 decimal
|
|
173
|
+
digits/byte, region endianness — classic NES scores); `'digits'` matches one
|
|
174
|
+
byte per ON-SCREEN digit at ANY constant tile base (HUD digit/tile-index
|
|
175
|
+
buffers; the base is auto-detected per candidate and reported; single-digit
|
|
176
|
+
seeds only accept base 0/0x30 to avoid matching everything). `searchNext`
|
|
177
|
+
narrows in the seed's representation automatically, including numeric
|
|
178
|
+
`inc`/`dec` on decoded values. Works on all platforms/regions (endianness
|
|
179
|
+
per region, big-endian m68k included).
|
|
180
|
+
- **search/searchNext response notes fixed** — they recommended the dead
|
|
181
|
+
`searchValue` name and a `writeMemory({bytes})` form that op:'write'
|
|
182
|
+
REJECTS; now they name the live ops with a `hex` payload, mention the
|
|
183
|
+
scene-changed-mid-step empty-round trap, and point input-driven values at
|
|
184
|
+
`diffRuns`. Same stale-name fix in two `watch` tool notes.
|
|
185
|
+
- `test/search-representations.test.js` covers all of it.
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
### Added — banked-cart parity across ALL platforms (per-bank references + rebuild glue)
|
|
189
|
+
The 0.27.0 feedback round fixed per-bank reference scanning and one-call banked
|
|
190
|
+
rebuild glue for NES only. Every other banked-cart platform now gets the same
|
|
191
|
+
treatment:
|
|
192
|
+
- **`disasm({target:'references'})` scans EVERY bank on every banked format** —
|
|
193
|
+
SNES multi-bank LoROM (was: only the first 32KB bank), GB/GBC MBC and SMS/GG
|
|
194
|
+
Sega-mapper and MSX megaROM (was: only the first 32KB), Atari 2600 F8/F6/F4
|
|
195
|
+
(was: only the boot bank), Atari 7800 (was: only the top 16KB — flat carts now
|
|
196
|
+
scan the WHOLE image, SuperGame carts per-bank), and >32KB HuCards (was: a
|
|
197
|
+
wrapped, garbage start address). Non-NES refs carry a `romBank` tag (NES keeps
|
|
198
|
+
`prgBank`). Very large carts scan the first 64 banks and SAY SO in `notes`.
|
|
199
|
+
- **`disasm({target:'project'})` splits every banked format per-bank** so
|
|
200
|
+
instructions never straddle a bank edge: Sega-mapper SMS/GG (16KB banks),
|
|
201
|
+
MSX megaROMs (16KB banks + the "AB" header as its own data region), banked
|
|
202
|
+
2600 (4KB banks), 7800 SuperGame (16KB banks + the .a78 header split out),
|
|
203
|
+
>32KB HuCards (8KB pages + optional copier header split out).
|
|
204
|
+
- **Atari 7800 SuperGame and PC Engine HuCards (flat AND banked) get one-call
|
|
205
|
+
byte-identical `build()` rebuilds** — their asm toolchain is cc65/ca65, the
|
|
206
|
+
same match that made NES one-call. NES-style glue: HEADER segment carrying
|
|
207
|
+
the original header bytes, per-bank segment wrappers, generated multi-bank
|
|
208
|
+
`.cfg` via `linkerConfigPath`. **PCE was previously the one honestly-LOSSY
|
|
209
|
+
case** (planRegions trimmed real $FF padding and didn't strip copier
|
|
210
|
+
headers) — both fixed, `verifiable:true` now.
|
|
211
|
+
- **SMS/GG, MSX, and 2600 banked carts get per-bank native rebuild recipes**
|
|
212
|
+
(their `build()` is SDCC/DASM — can't consume the disasm syntax): per-bank
|
|
213
|
+
wrappers + cfg blobs (2600), bank-by-bank `as`/`objcopy`/`dd`/`cat` recipes
|
|
214
|
+
in `BUILD.md` (SMS/GG/MSX), all byte-exact.
|
|
215
|
+
- Proven by `test/banked-parity.test.js`: synthetic banked carts on 7 platforms;
|
|
216
|
+
byte-identical one-call rebuilds verified end-to-end for 7800 SuperGame,
|
|
217
|
+
banked PCE, and flat-PCE-with-real-padding.
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
### Fixed — scaffold overhaul from real RetroDECK/Bazzite playtesting (all 14 platforms)
|
|
221
|
+
A full human playtest of every genre scaffold on real hardware surfaced clusters
|
|
222
|
+
of repeated logic errors. The big ones:
|
|
223
|
+
- **SMS/GG: every `build({output:'project'})` ROM black-screened** — the project
|
|
224
|
+
recipe skipped the dir's `*_crt0.s` believing `buildForPlatform` auto-injects
|
|
225
|
+
the bundled crt0 (it doesn't; only the rom/run handlers do), so SDCC's stock
|
|
226
|
+
z80 crt0 linked instead and `main()` never ran. Also: the bundled crt0's reset
|
|
227
|
+
block was 9 bytes (overflowed into the `.org 0x0008` RST slot, corrupting
|
|
228
|
+
`jp gsinit`), `_CODE` linked at `$0000` ON TOP of the vector table, and `.gg`
|
|
229
|
+
ROMs got an SMS region nibble (`$4C`) that flips Genesis-Plus-GX into SMS-compat
|
|
230
|
+
mode. Project builds now route/fall back to the bundled crt0, `_CODE` sits at
|
|
231
|
+
`$0100`, GG ROMs get region `$7C`, ROMs pad to 32KB before the TMR SEGA header,
|
|
232
|
+
and a regression test pins the boot byte + header. The SMS scaffold now ships
|
|
233
|
+
`sms_crt0.s` like GG/MSX.
|
|
234
|
+
- **"All enemies spawn on the left"** (18 shmup/racing templates): spawn X/lane
|
|
235
|
+
came from `spawn_timer`, which the caller resets to 0 immediately before
|
|
236
|
+
`spawn()` — a constant. Each template now has a Galois-LFSR `rand8()`.
|
|
237
|
+
- **Puzzle genre**: the gbc template is now the polished falling-jewel reference
|
|
238
|
+
game (4-direction matches, gravity + cascade chains, magic piece, SFX + music,
|
|
239
|
+
collect/flush vblank rendering, dataLoc `$C200` via the gb/gbc project recipe);
|
|
240
|
+
the DMG gb template is rebuilt around the same core; and the
|
|
241
|
+
mark/clear/gravity/cascade core is ported to all 10 other platforms (PCE: H+V
|
|
242
|
+
in its 8KB boot bank). Replaces a horizontal-only scan that missed vertical/
|
|
243
|
+
diagonal matches, half-cleared 4+ runs, and never dropped survivors.
|
|
244
|
+
- **Atari 2600**: SWCHA ASL carry-chains clobbered A between shifts (pressing
|
|
245
|
+
RIGHT also "pressed" LEFT — the stuck-to-the-left-edge bug) in three templates;
|
|
246
|
+
the platformer's terminal-velocity clamp caught POSITIVE velocities (unsigned
|
|
247
|
+
CMP), killing every jump within one frame; sports' paddle axis was inverted vs
|
|
248
|
+
the kernel's Y convention and RESBL was never strobed (the ball NEVER moved
|
|
249
|
+
horizontally — per-frame div-15 + HMBL positioning added); racing re-randomizes
|
|
250
|
+
both lanes on crash; shmup aliens reaching the cannon reset the wave.
|
|
251
|
+
- **Atari 7800**: the SWCHA joystick bit defines were exactly REVERSED on every
|
|
252
|
+
template (up/down steered left/right; sports' left/right moved the paddle
|
|
253
|
+
vertically). Plus speed tuning (platformer movement + jump, puzzle fall rate,
|
|
254
|
+
sports serve).
|
|
255
|
+
- **Platformers**: GBA fell through every platform (the `blocked_below` gate
|
|
256
|
+
only matched a 1px window at 20px/frame fall speeds); SNES platforms are now
|
|
257
|
+
visibly drawn on the scrolled text layer (were invisible collision rects);
|
|
258
|
+
Lynx landing uses a crossing test (exact-equality check tunnelled); C64
|
|
259
|
+
`render_view` rewritten ~20x faster (a per-CELL platform scan + 16-bit modulo
|
|
260
|
+
cost ~2s per 8px scroll step at 1MHz — froze the game and ate jump presses);
|
|
261
|
+
NES player is red (was sky-blue on sky-blue) and moves 2px/frame; GB/GBC jump
|
|
262
|
+
height tamed.
|
|
263
|
+
- **GBA sports "never starts"**: `tte_printf` (broken in this libtonc — the
|
|
264
|
+
documented GBA-1 issue) ran every frame and crashed with an undefined-
|
|
265
|
+
instruction exception on iteration 1. Replaced with the `tte_write` digit path
|
|
266
|
+
the other templates already use.
|
|
267
|
+
- **SNES**: each genre now gets a distinct backdrop tint (every scaffold shipped
|
|
268
|
+
the same blue checkered wallpaper).
|
|
269
|
+
- **Sound everywhere**: every scaffold now has a continuous background-music
|
|
270
|
+
loop plus audible SFX, verified per platform by recording + RMS analysis.
|
|
271
|
+
Genesis/Lynx tick a melody inside `sfx_update()` (no template wiring; Lynx
|
|
272
|
+
voices 64→100), NES adds a triangle-channel melody to `nes_runtime`, PCE a
|
|
273
|
+
ch5 melody with corrected volume (the 5-bit field is ~-1.5dB/step from 31 —
|
|
274
|
+
the old 13 was -27dB, near-silence; the shmup SFX are maxed), and the SMS/GG
|
|
275
|
+
3-voice tracker that already shipped is now actually STARTED by all 11
|
|
276
|
+
templates. **MSX root cause**: `msx_crt0.s` had the same `_INITIALIZER`-in-RAM
|
|
277
|
+
bug fixed for SMS/GG (every `static x = N` booted 0) plus a BIOS-KEYINT
|
|
278
|
+
PSGADDR-latch race (PSG writes now DI/EI-guarded) — both fixed; this likely
|
|
279
|
+
also explains the reported MSX sprite flakiness.
|
|
280
|
+
- **GB/GBC sports scanline tear**: the OAM DMA now fires at the vblank leading
|
|
281
|
+
edge (45 staged `oam_set` calls used to push it a third of the frame into
|
|
282
|
+
active display — the "horizontal line a 3rd of the way down" glitch).
|
|
283
|
+
- Misc per-genre polish: PCE gameplay speeds, C64 racing clears the BASIC
|
|
284
|
+
startup text, C64 sports court widened to the 9-bit sprite range, MSX/Lynx
|
|
285
|
+
sports contrast, GBA puzzle well border.
|
|
286
|
+
- **Verification**: all 69 existing platform×genre scaffolds were swept —
|
|
287
|
+
scaffold → project build → boot → render-health green, all 14 platforms
|
|
288
|
+
respond to input, and each platform's audio was captured and RMS-checked.
|
|
289
|
+
(Atari 2600 has no puzzle genre by design.)
|
|
290
|
+
|
|
291
|
+
### Fixed/Added — the 0.27.0 Zanac RE feedback round (banked-NES rebuilds, A/B diff, token cuts)
|
|
292
|
+
- **Banked NES `disasm({target:'project'})` now emits COMPLETE, working rebuild
|
|
293
|
+
glue** (the headline ask): a `HEADER` segment with the original 16 iNES bytes,
|
|
294
|
+
a per-bank `PRGn` segment wrapper for every bank, a multi-bank `nes_rebuild.cfg`
|
|
295
|
+
(switchable banks at `$8000`, fixed top bank at `$C000`, CHR wired when
|
|
296
|
+
present), and a `rebuild.json` `build()` call referencing all of it. Proven
|
|
297
|
+
byte-identical on a synthetic 4-bank mapper-2 ROM fed straight back to
|
|
298
|
+
`build()` — what previously took an hour of hand-written segments + cfg is
|
|
299
|
+
now zero glue. (NROM keeps the existing proven `inesHeader` one-call path.)
|
|
300
|
+
- **`build({linkerConfigPath})`** reads the `.cfg` from disk so a large
|
|
301
|
+
multi-bank config never streams through context (and `rebuild.json` uses it).
|
|
302
|
+
- **`disasm({target:'references'})` scans every PRG bank on banked NES** —
|
|
303
|
+
the old flat-blob-at-`$8000` disassembly returned `refsFound:0` on >32KB
|
|
304
|
+
ROMs. Refs now carry a `prgBank` tag, and `#$nn` immediates no longer count
|
|
305
|
+
as references (they're values, not addresses).
|
|
306
|
+
- **`memory({op:'diffRuns'})`** — the A/B input-diff primitive: runs the same
|
|
307
|
+
start state twice under two different held inputs (savestate restore in
|
|
308
|
+
between) and returns only the divergent bytes, with run-A/run-B values for
|
|
309
|
+
small clusters. Replaces the save/run/dump/restore/run/dump/python-diff loop
|
|
310
|
+
(~6 calls + a 4KB context hit) with one call; live-verified isolating an NES
|
|
311
|
+
player-X byte.
|
|
312
|
+
- **`memory({op:'read'/'readCart', outputPath, echo:false})`** returns just
|
|
313
|
+
`{path, bytes}` — no more ~4KB hex echo on a 2KB dump that was explicitly
|
|
314
|
+
routed to disk.
|
|
315
|
+
- **`memory({op:'diff'})`**: summary clusters ≤8 bytes now include
|
|
316
|
+
`before`/`after` hex (no more falling back to `view:'raw'` for the values),
|
|
317
|
+
and `minDelta` filters RNG/counter wiggle.
|
|
318
|
+
- **`input({op:'press'})` guarantees a released→pressed edge** (one released
|
|
319
|
+
frame first), so edge-triggered handlers (START pause) can't miss the press
|
|
320
|
+
when the button was already held.
|
|
321
|
+
- **`breakpoint({on:'pc'})` misses now diagnose**: report `pcNow`, stop
|
|
322
|
+
suggesting `pressDuring` when input WAS supplied (wrong-address is then the
|
|
323
|
+
likely story), and point at `watch({on:'pc'})` coverage tracing.
|
|
324
|
+
|
|
325
|
+
### Added — human co-drive detection: agents now KNOW when a human is playing in the playtest window
|
|
326
|
+
The long-standing confusion ("they get confused when I try to play while they're
|
|
327
|
+
coding") had a real mechanism: the playtest window shares the session's ONE
|
|
328
|
+
emulator host with the agent, and its 60fps tick wrote the human's pad state —
|
|
329
|
+
including all-zeros when nobody was pressing — over the agent's `input({op:'set'})`
|
|
330
|
+
every frame. The agent had no signal a human was co-driving and no warning that
|
|
331
|
+
its input was being clobbered. Now:
|
|
332
|
+
- **The window only writes input while the human is actually pressing** (pad,
|
|
333
|
+
keyboard, or rewind-scrub), plus one release write after they let go. An idle
|
|
334
|
+
window no longer silently clobbers the agent's held input. The human still wins
|
|
335
|
+
the instant they press.
|
|
336
|
+
- **The window tracks human activity** ("pressed within the last ~2 s" ≈ 120
|
|
337
|
+
ticks) and exposes it: `catalog({op:'status'})` reports `playtestWindowOpen` +
|
|
338
|
+
`humanInputActive` (+ `framesSinceHumanInput`), and `playtest({op:'status'})`
|
|
339
|
+
reports the same.
|
|
340
|
+
- **`frame({op:'step'/'stepAndShot'})` and `input(set/press/sequence/navigate)`
|
|
341
|
+
responses carry a `humanCoDriveWarning`** while the human is actively playing,
|
|
342
|
+
telling the agent the conflict is happening NOW and pointing at the escape
|
|
343
|
+
hatches: `host({op:'pause'})` to inspect frozen, or a second session
|
|
344
|
+
(different `x-romdev-session` = fully isolated emulator) for deterministic work.
|
|
345
|
+
- The playtest tool's FOOTGUN doc now describes the real contract (real-time
|
|
346
|
+
stepping always races; input only clobbered while the human presses).
|
|
347
|
+
|
|
348
|
+
### Changed — `screenshot` scale docs: native is the accurate default, upscale adds no detail
|
|
349
|
+
The `scale` param's docs oversold integer UPscaling as making tiny handheld shots
|
|
350
|
+
"legible." That was misleading: nearest-neighbor upscale just duplicates pixels —
|
|
351
|
+
it adds **no information** the native frame doesn't already have, costs more image
|
|
352
|
+
tokens, and since VLM vision encoders resize every input to their own fixed
|
|
353
|
+
resolution it may not change what the model sees (and can slightly degrade it via a
|
|
354
|
+
bicubic downscale of stretched pixels). Reworded the param + tool description to
|
|
355
|
+
lead with **native (`scale:1`, the default) = perfect pixels = the accurate
|
|
356
|
+
representation**, keep the genuinely-useful DOWNscale (`<1`, fewer tokens for
|
|
357
|
+
"did it change?" checks), and frame upscale honestly as a last resort for clients
|
|
358
|
+
that can't zoom a small image. (No behavior change — `scale` was already opt-in and
|
|
359
|
+
defaulted to native; this is the docs telling the truth about it.)
|
|
360
|
+
(Committed during the 0.27.0 cycle but AFTER 0.27.0 published — ships in 0.28.0.)
|
|
361
|
+
|
|
7
362
|
## 0.27.0
|
|
8
363
|
|
|
9
364
|
### Added — `breakpoint(on:'pc', captureMemory:[…])` reads named RAM at the hit
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ That's it — one command starts the local romdev **tool server** (no global ins
|
|
|
12
12
|
- **Agent Skill** — `GET /skills/romdev/SKILL.md` (the [Agent Skills](https://agentskills.io) standard; save it to your skills dir as `skills/romdev/SKILL.md`; ~100 tokens until invoked).
|
|
13
13
|
- **MCP** — it's also a [Model Context Protocol](https://modelcontextprotocol.io/) server at `/mcp` for clients that want it.
|
|
14
14
|
|
|
15
|
-
This package contains all the JavaScript — the tool surface, the WASM emulator host, the per-platform
|
|
15
|
+
This package contains all the JavaScript — the tool surface, the WASM emulator host, the per-platform example games, runtime/library source, and debug helpers — but **no emulator or compiler WASM itself.** Those ship in the `romdev-*` binary packages it depends on, loaded on demand the first time you build or run a given platform.
|
|
16
16
|
|
|
17
17
|
> For the full project — what romdev is, the supported-platform matrix, how the pieces fit together, and how to develop on it — see the [repository README](https://github.com/monteslu/romdev#readme).
|
|
18
18
|
|
|
@@ -21,8 +21,8 @@ This package contains all the JavaScript — the tool surface, the WASM emulator
|
|
|
21
21
|
- **`bin`**
|
|
22
22
|
- `romdevtools` → the tool server (`src/mcp/server.js`). Serves the HTTP tool routes, `/documentation`, `/skills/romdev/SKILL.md`, and an MCP endpoint on `http://127.0.0.1:7331` by default (`PORT` / `HOST` to override). `romdev-mcp` is kept as an alias of the same command.
|
|
23
23
|
- `romdevtools-cli` → a smoke/utility CLI, incl. `romdevtools-cli play <rom>` (SDL window, hot-plug controllers).
|
|
24
|
-
- **`src/`** — the server, MCP tools, WASM host, core/toolchain resolvers, per-platform memory interpretation, and bundled library/runtime source (cc65 libs, PVSnesLib, SGDK, libtonc/libgba, hUGEDriver, …) that
|
|
25
|
-
- **`examples/`** — per-platform
|
|
24
|
+
- **`src/`** — the server, MCP tools, WASM host, core/toolchain resolvers, per-platform memory interpretation, and bundled library/runtime source (cc65 libs, PVSnesLib, SGDK, libtonc/libgba, hUGEDriver, …) that forked projects link against.
|
|
25
|
+
- **`examples/`** — per-platform example games (complete, working, forkable) and minimal references.
|
|
26
26
|
|
|
27
27
|
## Dependencies
|
|
28
28
|
|
|
@@ -45,7 +45,7 @@ claude mcp add --transport http romdev http://127.0.0.1:7331/mcp
|
|
|
45
45
|
|
|
46
46
|
It's a standard **streamable-HTTP** MCP server at `http://127.0.0.1:7331/mcp`. For opencode, Codex CLI, and other clients, see **[Connect](https://github.com/monteslu/romdev#connect)** in the repository README. An optional human observer (live tool-call view) is at `/livestream`.
|
|
47
47
|
|
|
48
|
-
Agents: the server delivers [`AGENTS.md`](./AGENTS.md) as connection-time instructions — the workflow guide for the full tool surface. Or just connect your agent and call `catalog({op:'categories'})` to explore the tools live
|
|
48
|
+
Agents: the server delivers [`AGENTS.md`](./AGENTS.md) as connection-time instructions — the workflow guide for the full tool surface. Or just connect your agent and call `catalog({op:'categories'})` to explore the tools live, and `catalog({op:'status'})` for the running version + session snapshot.
|
|
49
49
|
|
|
50
50
|
## Prefer not to use MCP? Use HTTP or a Skill
|
|
51
51
|
|
package/examples/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Working example games — one buildable, rendering starting point per platform×genre (plus minimal references) for every platform romdev's bundled toolchains can build. These are **scaffolding, not showcases**: the gameplay is intentionally thin (treat it as placeholder and reshape it). Their value is that each already carries the platform's boot sequence, hardware init, APIs, and syntax wired up and WORKING, so you change 2 things while 13 keep working instead of getting 15 right from a blank file. **Never start from a blank file: fork the example whose CORE LOOP is nearest your game (even for a very different game) with `examples({op:'fork', example:'<platform>/<name>', name, path})`, then modify one thing at a time, re-running `build({output:'run'})` after each.** Read OTHER examples with `examples({op:'show'})` for techniques to graft. Rationale: retro bring-up is a long chain of fragile hardware init with zero partial credit; a working game is a regression oracle.
|
|
4
4
|
|
|
5
5
|
Each example fits the convention:
|
|
6
6
|
- A `main.c` or `main.asm`/`main.s` as the entry point.
|
|
@@ -14,18 +14,18 @@ Each example fits the convention:
|
|
|
14
14
|
| nes (full game) | `nes/space-shooter/` | cc65 | Canonical complete-game reference — a generic fixed-shooter showing "NES-shaped C": bit-packed alien state, 5-col formation for the 8-sprite/scanline limit, correct OAM-staging order, shields/HUD as BG tiles, CHR-RAM upload, APU SFX. Build with `linkerConfig: "chr-ram-runtime"` + `nes_runtime.c`. See its README. |
|
|
15
15
|
| c64 | `c64/main.c` | cc65 | Direct VIC-II + screen-RAM demo: paints background, writes screen codes for "HELLO ROM-DEV-MCP" into screen RAM with white-on-blue color, cycles the border color. C89 (cc65) — no mixed decl/code. |
|
|
16
16
|
| atari2600 | `atari2600/main.asm` | dasm | Standard NTSC kernel (3 vsync + 37 vblank + 192 visible + 30 overscan) with a single GRP0 sprite, joystick-driven movement, and the vector table at $FFFA. |
|
|
17
|
-
| atari2600 (gallery shooter) | `atari2600/templates/mini_invaders.asm` | dasm | Fixed-shooter 2600 game using the RIGHT TIA objects (not playfield "barcode" bars): P0 double-width cannon, P1 + NUSIZ1=%011 = a row of 3 hardware-replicated invaders, M0 = shot. Aliens march + drop at edges; button fires. The honest 2600-idiomatic genre layout. `
|
|
17
|
+
| atari2600 (gallery shooter) | `atari2600/templates/mini_invaders.asm` | dasm | Fixed-shooter 2600 game using the RIGHT TIA objects (not playfield "barcode" bars): P0 double-width cannon, P1 + NUSIZ1=%011 = a row of 3 hardware-replicated invaders, M0 = shot. Aliens march + drop at edges; button fires. The honest 2600-idiomatic genre layout. `examples({op:'fork', example:"atari2600/mini_invaders", name, path})`. |
|
|
18
18
|
| atari7800 | `atari7800/main.c` | cc65 | Minimal MARIA bring-up: DLL pointing at a single-zone DL, palette load, CHARBASE = 0. cc65 can't constant-fold pointer→int for static initializers, so the DL/DLL addresses are patched in at runtime in `main()`. |
|
|
19
19
|
| lynx | `lynx/main.c` | cc65 | |
|
|
20
20
|
| snes | `snes/main.asm` | asar | |
|
|
21
21
|
| genesis | `genesis/main.s` | vasm68k | |
|
|
22
22
|
| gb | `gb/main.c` (default) or `gb/main.asm` (`language:"asm"`) | sdcc sm83 port (C, default) / rgbds (asm) | C example cycles the BG palette every 32 frames. Asm example shows yellow 'H' on light BG, scrollable with A. SDCC GB hardware-register headers under `src/platforms/gb/lib/c/gb_hardware.h`. |
|
|
23
23
|
| gbc | `gbc/main.asm` | rgbds (asm) / sdcc sm83 (C) | The bundled example is an asm CGB-color demo (yellow 'H' on a true-blue BG, only possible on GBC). C is also supported via SDCC sm83 — same as GB. |
|
|
24
|
-
| sms | `sms/main.c` (or `sms/templates/*.c`) | sdcc | Pair with `src/platforms/sms/lib/c/sms_crt0.s` (passed via `crt0` arg) — boots into a real cartridge with vector table + SP=$DFF0 + IM 1. Yellow 'H' on blue, scrollable with P1-B1. The
|
|
25
|
-
| gg | `gg/templates/default.c` (or any other
|
|
26
|
-
| gba | `gba/templates/*.c` | arm-none-eabi-gcc | Default runtime = **libtonc** (`#include <tonc.h>`). 9
|
|
27
|
-
| pce | `pce/<template>/main.c` | cc65 (HuC6280) | HuCard homebrew, no BIOS. Ships a direct-register VDC/PSG helper lib (`pce.h` + `pce.lib`) — cc65 has no PCE sprite/sound library.
|
|
28
|
-
| msx | `msx/<template>/main.c` | sdcc (z80) | Boots cartridge homebrew on the open C-BIOS (no proprietary ROM). Ships an AY-3-8910 + TMS9918/V9938 VDP helper lib (`msx_hw.h` + `msx_vdp.c`).
|
|
24
|
+
| sms | `sms/main.c` (or `sms/templates/*.c`) | sdcc | Pair with `src/platforms/sms/lib/c/sms_crt0.s` (passed via `crt0` arg) — boots into a real cartridge with vector table + SP=$DFF0 + IM 1. Yellow 'H' on blue, scrollable with P1-B1. The 10 examples under `sms/templates/` (default, hello_sprite, tile_engine, shmup, shmup_2p, platformer, puzzle, sports, racing, music_demo) all use this crt0 — `examples({op:'fork'})` copies it in automatically. |
|
|
25
|
+
| gg | `gg/templates/default.c` (or any other example) | sdcc | R53: GG now ships `src/platforms/gg/lib/c/gg_crt0.s` (byte-identical to SMS's). Real visible-and-runnable default: VDP Mode 4 init + palette + yellow 'H' centered in the 160×144 visible viewport + B1 scroll loop. The 9 examples (default, hello_sprite, tile_engine, shmup, platformer, puzzle, sports, racing, music_demo) all link the GG runtime + crt0 via `examples({op:'fork', example:"gg/<name>", name, path})`. |
|
|
26
|
+
| gba | `gba/templates/*.c` | arm-none-eabi-gcc | Default runtime = **libtonc** (`#include <tonc.h>`). 9 examples incl. `tonc_hello`, `tonc_hello_sprite`, the 5 genre games, and `maxmod_demo` (music). Pass `runtime:"libgba"` for the devkitPro API, `runtime:"none"` for bare newlib. **Always call `irq_init(NULL); irq_add(II_VBLANK, NULL);` before `VBlankIntrWait()`** — otherwise the BIOS halts forever. |
|
|
27
|
+
| pce | `pce/<template>/main.c` | cc65 (HuC6280) | HuCard homebrew, no BIOS. Ships a direct-register VDC/PSG helper lib (`pce.h` + `pce.lib`) — cc65 has no PCE sprite/sound library. Examples: `sprite_move`, `catch_game`, `music_sfx`, plus the 5 genre games (shmup/platformer/puzzle/sports/racing). **`#include <stdint.h>`** for int8/16/32_t — `pce.h` only typedefs u8/u16. The genre games fill the BAT (32×32 virtual screen); the platformer smooth-scrolls via the VDC BXR register. |
|
|
28
|
+
| msx | `msx/<template>/main.c` | sdcc (z80) | Boots cartridge homebrew on the open C-BIOS (no proprietary ROM). Ships an AY-3-8910 + TMS9918/V9938 VDP helper lib (`msx_hw.h` + `msx_vdp.c`). Examples: `sprite_move`, `catch_game`, `music_sfx`, plus the 5 genre games. The bundled `msx_crt0.s` (applied by the dir-build recipe automatically) emits the `"AB"` cartridge header at $4000 + INIT pointer — **C-BIOS shows its logo for ~2-3 s, then CALLs INIT**, so run ≥240 frames before screenshotting. The platformer column-streams the SCREEN 2 name table for a tile-by-tile scroll. |
|
|
29
29
|
|
|
30
30
|
## Guides
|
|
31
31
|
|