romdevtools 0.28.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 +51 -41
- package/CHANGELOG.md +46 -0
- package/README.md +3 -3
- package/examples/README.md +7 -7
- package/examples/atari2600/templates/platformer.asm +1225 -332
- package/examples/atari2600/templates/puzzle.asm +1056 -0
- package/examples/atari2600/templates/racing.asm +906 -275
- package/examples/atari2600/templates/shmup.asm +1031 -239
- package/examples/atari2600/templates/sports.asm +1135 -253
- package/examples/atari7800/templates/platformer.c +991 -156
- package/examples/atari7800/templates/puzzle.c +1091 -148
- package/examples/atari7800/templates/racing.c +952 -124
- package/examples/atari7800/templates/shmup.c +812 -134
- package/examples/atari7800/templates/sports.c +820 -184
- package/examples/c64/templates/platformer.c +879 -164
- package/examples/c64/templates/puzzle.c +855 -178
- package/examples/c64/templates/racing.c +873 -97
- package/examples/c64/templates/shmup.c +757 -161
- package/examples/c64/templates/sports.c +755 -100
- package/examples/gb/templates/platformer.c +841 -179
- package/examples/gb/templates/puzzle.c +986 -246
- package/examples/gb/templates/racing.c +754 -174
- package/examples/gb/templates/shmup.c +673 -175
- package/examples/gb/templates/sports.c +790 -159
- package/examples/gba/templates/platformer.c +626 -165
- package/examples/gba/templates/puzzle.c +519 -269
- package/examples/gba/templates/racing.c +511 -206
- package/examples/gba/templates/shmup.c +564 -179
- package/examples/gba/templates/sports.c +454 -174
- package/examples/gbc/templates/platformer.c +944 -180
- package/examples/gbc/templates/puzzle.c +363 -109
- package/examples/gbc/templates/racing.c +884 -180
- package/examples/gbc/templates/shmup.c +821 -185
- package/examples/gbc/templates/sports.c +870 -162
- package/examples/genesis/templates/platformer.c +747 -129
- package/examples/genesis/templates/puzzle.c +694 -261
- package/examples/genesis/templates/racing.c +726 -203
- package/examples/genesis/templates/shmup.c +535 -142
- package/examples/genesis/templates/sports.c +495 -158
- package/examples/gg/templates/platformer.c +880 -215
- package/examples/gg/templates/puzzle.c +875 -216
- package/examples/gg/templates/racing.c +915 -172
- package/examples/gg/templates/shmup.c +714 -191
- package/examples/gg/templates/sports.c +732 -129
- package/examples/lynx/templates/platformer.c +604 -69
- package/examples/lynx/templates/puzzle.c +498 -158
- package/examples/lynx/templates/racing.c +538 -102
- package/examples/lynx/templates/shmup.c +458 -131
- package/examples/lynx/templates/sports.c +496 -72
- package/examples/msx/platformer/main.c +649 -162
- package/examples/msx/puzzle/main.c +742 -240
- package/examples/msx/racing/main.c +669 -178
- package/examples/msx/shmup/main.c +460 -178
- package/examples/msx/sports/main.c +592 -126
- package/examples/nes/templates/platformer.c +589 -171
- package/examples/nes/templates/puzzle.c +563 -242
- package/examples/nes/templates/racing.c +502 -208
- package/examples/nes/templates/shmup.c +339 -145
- package/examples/nes/templates/sports.c +341 -183
- package/examples/pce/platformer/main.c +874 -205
- package/examples/pce/puzzle/main.c +802 -287
- package/examples/pce/racing/main.c +783 -208
- package/examples/pce/shmup/main.c +638 -212
- package/examples/pce/sports/main.c +586 -169
- package/examples/porting-across-platforms/README.md +1 -1
- package/examples/sms/templates/platformer.c +762 -177
- package/examples/sms/templates/puzzle.c +752 -212
- package/examples/sms/templates/racing.c +808 -145
- package/examples/sms/templates/shmup.c +599 -162
- package/examples/sms/templates/sports.c +630 -122
- 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 +586 -165
- package/examples/snes/templates/puzzle-data.asm +116 -21
- package/examples/snes/templates/puzzle-hdr.asm +57 -0
- package/examples/snes/templates/puzzle.c +614 -235
- 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 -196
- 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 -198
- 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 -163
- package/package.json +1 -1
- package/src/host/LibretroHost.js +59 -1
- package/src/http/tool-registry.js +11 -11
- package/src/mcp/tools/cheats.js +2 -1
- package/src/mcp/tools/frame.js +3 -2
- package/src/mcp/tools/index.js +3 -3
- package/src/mcp/tools/input.js +5 -4
- package/src/mcp/tools/lifecycle.js +6 -4
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/preview-tile.js +6 -2
- package/src/mcp/tools/project.js +1098 -130
- 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 +12 -1
- package/src/mcp/tools/watch-memory.js +4 -3
- 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/atari7800/MENTAL_MODEL.md +5 -5
- 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 +3 -3
- package/src/platforms/gb/TROUBLESHOOTING.md +61 -8
- 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 +13 -3
- 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 +4 -4
- package/src/platforms/gbc/TROUBLESHOOTING.md +4 -4
- 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/gb_crt0.s +26 -3
- package/src/platforms/gbc/lib/c/patch-header.js +13 -3
- package/src/platforms/genesis/MENTAL_MODEL.md +3 -3
- package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
- package/src/platforms/gg/MENTAL_MODEL.md +4 -4
- package/src/platforms/gg/TROUBLESHOOTING.md +3 -3
- package/src/platforms/gg/UPSTREAM_SOURCES.md +1 -1
- 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/msx/MENTAL_MODEL.md +5 -5
- package/src/platforms/msx/TROUBLESHOOTING.md +2 -2
- package/src/platforms/msx/lib/c/msx_hw.h +1 -0
- package/src/platforms/msx/lib/c/msx_vdp.c +25 -0
- package/src/platforms/nes/MENTAL_MODEL.md +2 -2
- package/src/platforms/nes/lib/c/nes_runtime.c +149 -34
- package/src/platforms/nes/lib/c/nes_runtime.h +34 -1
- package/src/platforms/pce/MENTAL_MODEL.md +5 -5
- package/src/platforms/pce/TROUBLESHOOTING.md +1 -1
- package/src/platforms/pce/lib/c/pce_hw.h +11 -0
- package/src/platforms/pce/lib/c/pce_video.c +32 -0
- package/src/platforms/sms/MENTAL_MODEL.md +6 -6
- package/src/platforms/snes/MENTAL_MODEL.md +2 -2
- package/src/platforms/snes/TROUBLESHOOTING.md +40 -1
- 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 +27 -11
package/AGENTS.md
CHANGED
|
@@ -68,7 +68,7 @@ Skip playtest only when there's clearly no human in the loop: CI runs, automated
|
|
|
68
68
|
- `memory` — read/write VRAM/OAM/CGRAM/ARAM and other regions (all 14 platforms). `memory({op:'read'})` takes `offsets:[…]` to batch scattered reads in one call. **`memory({op:'search'})`/`memory({op:'searchNext'})`** = the Cheat-Engine value-search loop ("find the address of X, narrow as X changes") — relative compares (`inc`/`dec`/`changed`) work as the FIRST narrow (baselines recorded at seed), and `as:'bcd'`/`as:'digits'` search packed-BCD scores and digit-per-byte HUD buffers (any constant tile base) when stored ≠ displayed. **`memory({op:'readCart'})`** reads the loaded cart image to confirm a patch is live. **`memory({op:'classify'})`** says whether bytes look like ASCII/code/tile-data (kills the "found table that's really a string" trap). `memory({op:'snapshot'})` + `memory({op:'diff'})` answer "which bytes changed across this event?" (diff defaults to a clustered summary with stride detection; small clusters carry before/after hex, `minDelta` filters churn); **`memory({op:'diffRuns', portsA, portsB?})`** answers "which byte does this INPUT drive?" in one call (same start state run twice under two inputs, only the divergent bytes return); `state({op:'diff'})` is the coarse whole-machine version. Reads routed to disk take `echo:false` to skip the inline hex.
|
|
69
69
|
- `debug` — **`frame({op:'verify'})`** (NO-VISION render-health: one call answers "is the game actually rendering / alive?" on all 14 — fuses a framebuffer pixel scan with the per-platform render-enable/NMI decode; `{verified:true|false|null, issues[], pixels, render}`, frame-0-guarded so it never cries wolf on boot), `sprites({op:'inspect'})`, `palette({source:'live'})`, `cpu({op:'read'})` (all 14), `audioDebug({op:'inspect'})` (the 12 systems with a sound chip — all but Atari 2600/7800; pass `frames:N` to TRACE a per-channel note-timeline for headless melody asserts), `background({view:'renderState'})`, `breakpoint({on:'write'})` (write watchpoint, all 14; EVERY hit on EVERY platform carries `registersAtHit` — the register file frozen at the hit instant, the only honest read since live regs drift after a hit — and the CPU stays frozen until the hit is cleared), **`watch({on:'dma', precision:'sampled'})`** (Genesis: which ROM offset a VRAM graphic was DMA'd from), **`watch({on:'copy'})`** (ALL 14: every write landing in a VRAM window logged with the EXECUTING instruction's PC — the generic 'which routine uploads this graphic?'; port-based video memory hooked in-core incl. the SNES DMA path, CPU-mapped VRAM via the range log), **`disasm({target:'bytes'|'rom'|'references'|'project'})`** (ALL 14 — native binutils objdump per CPU, incl. GBA ARM7/Thumb; the byte-exact `disasm({target:'project'})` reassembles through native as/ld/objcopy; banked carts — NES mappers, SNES LoROM, GB MBC, Sega mapper, MSX megaROM, 2600 F8/F6/F4, 7800 SuperGame, >32KB HuCards — are split and reference-scanned PER BANK, refs tagged `prgBank`/`romBank`), `symbols({op})` lookup, `background({view:'rendered'})`, plus **`cheats({op})`** (`cheats({op:'lookup'})` = a free labeled RAM/code map for known ROMs, `cheats({op:'search'})` to fuzzy-find a game by name, `cheats({op:'apply'})`/`cheats({op:'clear'})` non-destructively, `cheats({op:'make'})` to create codes)
|
|
70
70
|
- `assets` — convert PNGs to tiles (`encodeArt`/`importArt`), WAVs to BRR, identify ROMs (`cart({op:'identify'})`), plus the hacking toolkit (`romPatch({op})` — write/writeMany/spliceCHR/relocate/makeStored/findFree/findPointer/diff, `assembleSnippet`, `cart({op:'extract'})`, `cart({op:'wrap'})`)
|
|
71
|
-
- `project` —
|
|
71
|
+
- `project` — the example-game library (`examples`: list / fork / show, plus the legacy snippet ops)
|
|
72
72
|
- `show` — `playtest({op})`: `op:'open'` opens the live SDL window for a human, `op:'stop'` closes it, `op:'status'` reports liveness, `op:'framebuffer'` captures exactly what the human's window shows
|
|
73
73
|
- `advanced` — `runUntil`, **`watch({on:'mem'|'range'|'pc'})`** (LOG-ALL tracing; `range`/`pc` take **`fromState`**/`fromStatePath` to trace from a restored savestate moment), **`breakpoint({on:'write'})`** (the EXACT instruction that wrote a byte, via a core watchpoint — fixes the frame-sampled-PC problem; `precision:'sampled'` is the cheap frame-PC version; on a `pressDuring` run pass **`abortIf:[{region,offset,label}]`** to stop early if the driven scenario derails — a guard byte changing returns `{aborted, abortedBy, before, after}` instead of burning all `maxFrames` on a meaningless `found:false`), **`breakpoint({on:'pc'})`** (execution breakpoint — freeze the CPU AT an instruction and read its registers), **`breakpoint({on:'read'})`** (the EXACT instruction that read a byte), **`frame({op:'stepInstruction'})`** (CPU single-step) — all 14 platforms; input recording
|
|
74
74
|
|
|
@@ -142,29 +142,39 @@ Ergonomic exceptions:
|
|
|
142
142
|
|
|
143
143
|
Two parallel paths depending on what you need:
|
|
144
144
|
|
|
145
|
-
### Path A —
|
|
145
|
+
### Path A — Fork a working example game (the dumb-model-friendly path)
|
|
146
146
|
|
|
147
147
|
Most agent sessions start here. You want a working ROM, not a
|
|
148
|
-
research project.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
research project. **Never start from a blank file — fork the example
|
|
149
|
+
whose CORE LOOP is nearest your game (even for a very different game),
|
|
150
|
+
then modify one thing at a time, re-running `build({output:'run'})`
|
|
151
|
+
after each change.** Retro bring-up is a long chain of fragile
|
|
152
|
+
hardware init with zero partial credit; a working game is a
|
|
153
|
+
regression oracle.
|
|
154
|
+
|
|
155
|
+
1. **`examples({op:'list', platform?})`** — the mechanics map of the
|
|
156
|
+
complete working example games (kind `game` vs minimal `reference`).
|
|
157
|
+
Pick the one whose core loop is nearest your game.
|
|
158
|
+
2. **`examples({op:'fork', example:"<platform>/<name>", name, path})`** —
|
|
159
|
+
copies that example into a NEW project dir as YOUR game (sources +
|
|
160
|
+
every runtime file + crt0 + linker cfg + vendored library source +
|
|
161
|
+
README + .gitignore), renamed throughout. It builds and runs before
|
|
162
|
+
you change a line. The response lists only the files you EDIT
|
|
155
163
|
(`files`) + a `vendorFileCount`; pass `verbose:true` for the full manifest.
|
|
156
164
|
Build the whole dir in one call with `build({output:'project', path,
|
|
157
165
|
outputPath})` (toolchain/crt0/linker inferred — no manifest); the bundled
|
|
158
166
|
examples ARE the reference implementation.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
3. **`examples({op:'show', example, file?, technique?})`** — read a
|
|
168
|
+
DONOR example without forking it; `technique` extracts one marked
|
|
169
|
+
HARDWARE IDIOM block (with its dependency header) to graft into
|
|
170
|
+
your game instead of rewriting it.
|
|
171
|
+
4. **`examples({op:'snippets', platform, mode})`** (mode `list`/`get`/`getAll`)
|
|
172
|
+
/ **`examples({op:'copySnippets', platform, destinationDir})`** — fetch
|
|
163
173
|
vetted helper files (reset routine, read_pad, OAM DMA, palette
|
|
164
|
-
upload, etc.)
|
|
165
|
-
`
|
|
174
|
+
upload, etc.) as one-off references.
|
|
175
|
+
`examples({op:'copySnippets'})` writes the files to disk in one call
|
|
166
176
|
without round-tripping bytes through your context — preferred
|
|
167
|
-
when you're
|
|
177
|
+
when you're copying into a project dir.
|
|
168
178
|
|
|
169
179
|
Reminder (it's the second rule up top): **read your platform's
|
|
170
180
|
`platform({op:'doc', platform, name:'mental_model'})` BEFORE you write code for
|
|
@@ -188,7 +198,7 @@ order:
|
|
|
188
198
|
All ~50-200 lines, fully readable. Read these when an API call
|
|
189
199
|
isn't doing what you expect.
|
|
190
200
|
3. **Your own project's `vendor/` library source** (R58b — auto-
|
|
191
|
-
copied into every project at
|
|
201
|
+
copied into every project at fork time). The FULL source of
|
|
192
202
|
every library your ROM links against — `vendor/cc65/libsrc/<p>/`
|
|
193
203
|
for cc65 platforms, `vendor/libtonc/src/` + `vendor/libgba/src/`
|
|
194
204
|
for GBA, `vendor/pvsneslib/source/` for SNES, `vendor/sgdk/src/`
|
|
@@ -218,8 +228,8 @@ into "what the linked code actually does."
|
|
|
218
228
|
|
|
219
229
|
The "vendor/" library source in your project is new in R58b (it was
|
|
220
230
|
previously in the install only; you'd have to call
|
|
221
|
-
`
|
|
222
|
-
when you `
|
|
231
|
+
`examples({op:'copySnippets'})` to pull it in). Now it lands automatically
|
|
232
|
+
when you `examples({op:'fork'})`. Round 30/31 Lynx wedges took 5 friction
|
|
223
233
|
rounds partly because cc65's TGI driver source wasn't visible;
|
|
224
234
|
post-R58b you can `grep -rn bar_c vendor/cc65/libsrc/lynx/` from
|
|
225
235
|
inside your project directory and read the actual blitter code.
|
|
@@ -232,7 +242,7 @@ disagree with behavior, trust the library source over the example.
|
|
|
232
242
|
|
|
233
243
|
### Which path to use
|
|
234
244
|
|
|
235
|
-
- **Just need a working game** → Path A.
|
|
245
|
+
- **Just need a working game** → Path A. Fork the nearest example with `examples({op:'fork'})`, iterate.
|
|
236
246
|
- **Hit a bug or unexpected behavior** → switch to Path B.
|
|
237
247
|
- **Don't know which** → start in Path A; if iterations fail to
|
|
238
248
|
converge after 2-3 attempts, you're hitting something path A
|
|
@@ -240,7 +250,7 @@ disagree with behavior, trust the library source over the example.
|
|
|
240
250
|
|
|
241
251
|
### Where files land in your project tree
|
|
242
252
|
|
|
243
|
-
A
|
|
253
|
+
A forked project (`examples({op:'fork'})`) is
|
|
244
254
|
**FLAT** for everything you author. `main.c` / `main.asm`, your
|
|
245
255
|
helper modules (e.g. `gb_runtime.c`, `nes_runtime.c`,
|
|
246
256
|
`atari7800_sfx.c`, `vcs_constants.h`), the platform crt0 + linker
|
|
@@ -249,13 +259,13 @@ config — all sit at the project root, next to each other. Asm
|
|
|
249
259
|
without `-I` flags because dasm / cc65 / sdcc all default to the
|
|
250
260
|
current directory.
|
|
251
261
|
|
|
252
|
-
The **only** subdir you'll see at
|
|
262
|
+
The **only** subdir you'll see at fork time is `vendor/` —
|
|
253
263
|
that's the read-only library source tree (cc65 libsrc, libtonc /
|
|
254
264
|
libgba src, PVSnesLib source, SGDK src) auto-bundled by R58b so
|
|
255
265
|
you can `grep -rn vendor/` when debugging. Don't put your own
|
|
256
266
|
source under `vendor/`.
|
|
257
267
|
|
|
258
|
-
So when `
|
|
268
|
+
So when `examples({op:'copySnippets'})` drops e.g. `read_joystick.asm` into
|
|
259
269
|
your project dir, it lands at `./read_joystick.asm` (alongside
|
|
260
270
|
`main.asm`), NOT under `./include/` or `./lib/`. Every platform
|
|
261
271
|
follows the same flat layout.
|
|
@@ -263,7 +273,7 @@ follows the same flat layout.
|
|
|
263
273
|
Because the layout is flat, **the simplest loop is `build({output:'run', path, platform})`
|
|
264
274
|
(build + load + run + screenshot in one call) or `build({output:'project', path, platform})`
|
|
265
275
|
(build the dir to a ROM) — no per-iteration file manifest, on EVERY platform.** Point it at
|
|
266
|
-
a
|
|
276
|
+
a forked project directory and it does the right per-platform thing automatically: finds the
|
|
267
277
|
entry (`main.c` for C / SGDK Genesis / GBA / cc65-C / SDCC-C, or `main.s` / `main.asm` for
|
|
268
278
|
asm), routes the platform's crt0 correctly (e.g. GB/GBC `gb_crt0.s` via the cart-header path,
|
|
269
279
|
not as a plain source — so no `gsinit` collision), applies the right linker preset
|
|
@@ -278,11 +288,11 @@ with explicit `sources` only when the files aren't on disk, e.g. generated in-co
|
|
|
278
288
|
|
|
279
289
|
## Supported platforms
|
|
280
290
|
|
|
281
|
-
**14 tier-1 platforms** (build + run + screenshot + inspect + genre
|
|
291
|
+
**14 tier-1 platforms** (build + run + screenshot + inspect + genre example games + sound + music + per-platform MENTAL_MODEL.md + TROUBLESHOOTING.md):
|
|
282
292
|
|
|
283
|
-
NES, Game Boy, Game Boy Color, SNES, Genesis, Game Boy Advance, SMS, Game Gear, C64, Atari 7800, Lynx, PC Engine, MSX — all with the full `
|
|
293
|
+
NES, Game Boy, Game Boy Color, SNES, Genesis, Game Boy Advance, SMS, Game Gear, C64, Atari 7800, Lynx, PC Engine, MSX — all with the full set of forkable genre example games (`examples({op:'fork', example:'<platform>/shmup|platformer|puzzle|sports|racing', name, path})`). The Atari 2600 is also tier-1 but ships **4** of those genres (no `puzzle` — the TIA has no tilemap to draw a match-3 board). The `platformer` example side-scrolls (hardware camera + per-platform column streaming) on every tier-1 platform except NES and the Atari 2600, which are single-screen (neither has hardware background scroll). Every tier-1 platform also ships a music demo using the platform's de-facto music engine — `music_demo` for most: FamiTone2 (NES), hUGEDriver (GB/GBC), SPC700 driver (SNES), XGM2 via SGDK (Genesis), maxmod + .xm soundbank (GBA), PSG trackers (SMS/GG), SID sequencer (C64), `lynx_snd_play` (Lynx), 2-voice TIA (Atari 2600/7800); PC Engine and MSX ship theirs as `music_sfx` (HuC6280 PSG; AY-3-8910 PSG). PC Engine and MSX additionally ship a hardware helper library plus `sprite_move` / `catch_game` example projects alongside the genre examples.
|
|
284
294
|
|
|
285
|
-
**Bring-up only** (build pipeline works, single `default`
|
|
295
|
+
**Bring-up only** (build pipeline works, single `default` example, no genre example games or sound/music wrappers yet): ColecoVision. Uses SDCC z80 same as SMS/GG/MSX — the genre examples are queued.
|
|
286
296
|
|
|
287
297
|
**Delisted** (toolchain works but core-side issue blocks the run loop): Atari 5200 (atari800 BIOS-load path), ZX Spectrum (fuse tape-load path).
|
|
288
298
|
|
|
@@ -347,7 +357,7 @@ The deep per-platform inspectors + the exact memory-region names, core quirks, a
|
|
|
347
357
|
- **MSX** — VDP/PSG inspection or AY8910 `audioDebug`. (ColecoVision is bring-up-only: standard `system_ram`/`save_ram`/`video_ram`, no custom inspectors — extend by patching its core per the snes9x/gpgx pattern.)
|
|
348
358
|
- **PC Engine** — generic shapes + the core's native regions only so far (no custom-inspector treatment yet).
|
|
349
359
|
|
|
350
|
-
Starter snippets per platform live under `src/platforms/<platform>/lib/`. Discover via `
|
|
360
|
+
Starter snippets per platform live under `src/platforms/<platform>/lib/`. Discover via `examples({op:'snippets', platform})` (default `mode:'list'`), fetch one via `examples({op:'snippets', platform, mode:'get', snippetName})`. SNES + NES + Genesis + SMS + Game Boy + Atari 2600 + Atari 7800 have substantial snippet libraries; others are minimal.
|
|
351
361
|
|
|
352
362
|
## ROMs are finalized for real hardware automatically
|
|
353
363
|
|
|
@@ -606,15 +616,17 @@ OAM format: bytes per sprite are `[y, tileIndex, attributes, x]`.
|
|
|
606
616
|
|
|
607
617
|
`state({op:'load'})` removes any active cheats (a save-state blob doesn't carry frontend cheat state) and reports `cheatsCleared`. `host({op:'reset'})` resets the frame counter + core state (and clears cheats) but keeps the loaded ROM.
|
|
608
618
|
|
|
609
|
-
##
|
|
619
|
+
## Starting a project: fork an example game
|
|
620
|
+
|
|
621
|
+
**Never start from a blank file — fork the example whose CORE LOOP is nearest your game (even for a very different game), 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.
|
|
610
622
|
|
|
611
|
-
|
|
623
|
+
- **`examples({op:'list', platform?})`** — the mechanics map of the example library: every example with its kind (`game` = complete working game, `reference` = minimal demo like `default` / `hello_sprite` / `tile_engine`), mechanics inventory, hardware techniques demonstrated, players, SRAM. Pick the example whose core loop is nearest your game.
|
|
612
624
|
|
|
613
|
-
- **`
|
|
625
|
+
- **`examples({op:'fork', example:"<platform>/<name>", name, path, title?, overwrite?})`** — copies that example into a NEW project dir as YOUR game: `main.{c,asm,s}` + every runtime file it depends on (headers, crt0, linker .cfg) + README + `.gitignore`, renamed throughout. Self-contained: take it elsewhere and rebuild with stock cc65/sdcc, no romdev install needed. (You can also pass `platform` + `template` instead of `example`.) **Then build it in ONE call: `build({output:'run', path:<that dir>, platform})`** — the dir build applies the platform's recipe (crt0/linker-preset/runtime/intermediate-skip) automatically, so you never hand-wire `crt0Path`/`codeLoc`/`linkerConfig`. This fork→build path is verified to build + render on every platform/example.
|
|
614
626
|
|
|
615
|
-
-
|
|
627
|
+
- The **genre example games** (`shmup` / `platformer` / `puzzle` / `sports` / `racing`) are the usual fork targets — complete working ROMs with state machine + sprite allocation + sound wired. Available on **all 14 tier-1 platforms** (NES, GB, GBC, SNES, Genesis, SMS, GG, C64, GBA, Lynx, Atari 7800, PC Engine, MSX — full 5 each; Atari 2600 — 4, no `puzzle` since the TIA has no tilemap for a match-3 board). Availability is derived from the registered examples (not a hardcoded list), so the error message for an unsupported (platform, name) pair always names the current set; e.g. `atari2600/puzzle` is rejected and the error lists the examples it *does* have. ColecoVision (bring-up only) has no genre examples. No example matches your genre exactly? Fork the NEAREST core loop and reshape it — `examples({op:'list'})` returns the genre→nearest-fork guidance. **Want a side-scroller? Fork `<platform>/platformer`** — on every platform EXCEPT NES and the Atari 2600 it already side-scrolls: a hardware camera follows the player (SCX/$D016/R8/BXR/BG?HOFS/REG_BG?HOFS/bgSetScroll depending on platform), with software tile-column streaming where the world is wider than one nametable/plane. NES and the Atari 2600 are single-screen (no hardware background scroll — platforms drawn as sprites/playfield); to make NES scroll, draw platforms into the background nametables + `ppu_scroll(camX,0)` (it flips the PPUCTRL nametable-select bit past 256 px) + stream columns past 512 px. Each platformer's `describe` text gives the per-platform specifics; the scroll-register details live in the platform's MENTAL_MODEL.md "Horizontal scrolling" section.
|
|
616
628
|
|
|
617
|
-
- **`
|
|
629
|
+
- **`examples({op:'show', example, file?, technique?})`** — read a donor example WITHOUT forking it: a whole file, or one marked HARDWARE IDIOM block (`technique`) with the dependency header that says what the block needs to survive a transplant. Fork for the core loop; show OTHER examples for techniques to graft.
|
|
618
630
|
|
|
619
631
|
Then iterate with `build({output:'run'})` against the source you read from `path/main.*`.
|
|
620
632
|
|
|
@@ -694,15 +706,13 @@ Two tools that save real time and frustration:
|
|
|
694
706
|
|
|
695
707
|
## Starter snippets
|
|
696
708
|
|
|
697
|
-
`
|
|
709
|
+
`examples({op:'snippets', platform})` (default `mode:'list'`) and `examples({op:'snippets', platform, mode:'get', snippetName})` give you vetted boilerplate — reset routine, `read_pad`, OAM DMA, palette upload, nametable clear. Each snippet's comments encode foot-guns prior agent sessions already hit. Always check what's available for your platform before writing platform-specific boilerplate from scratch. NES, SNES, SMS, GG, GB/GBC, Genesis, GBA, C64, Atari 7800 all have substantial snippet libraries. (Prefer forking + grafting from the real example games; snippets remain for one-off references.)
|
|
698
710
|
|
|
699
711
|
**Three ways to actually use them:**
|
|
700
712
|
|
|
701
|
-
- `
|
|
702
|
-
- `
|
|
703
|
-
- **`
|
|
704
|
-
|
|
705
|
-
Or skip the separate call entirely: `scaffold({op:'project', withSnippets: true})` does the same thing as a one-shot.
|
|
713
|
+
- `examples({op:'snippets', platform, mode:'get', snippetName})` — one snippet's contents, returned as a string.
|
|
714
|
+
- `examples({op:'snippets', platform, mode:'getAll', language?})` — every snippet joined into one string. Useful for **reading**; the giant blob lands in your context (or pass `outputPath` to write it to disk instead).
|
|
715
|
+
- **`examples({op:'copySnippets', platform, destinationDir, language?, include?})`** — writes every snippet (or a filtered subset) straight to disk. **Bytes never pass through your context.** Use this when you're copying into a project dir. Flattens `lib/<lang>/foo.c` → `<destinationDir>/foo.c`. Optional `include: ["vdp_init", "joypad_read"]` whitelist for cherry-picking. Default `overwrite: true` (vetted boilerplate is meant to be regenerated).
|
|
706
716
|
|
|
707
717
|
## Don't burn your own context with binary data
|
|
708
718
|
|
|
@@ -807,8 +817,8 @@ them for YOUR platform before you build.** By symptom:
|
|
|
807
817
|
8-sprites-per-scanline limit, SAT `$D0` terminator, R6 sprite-tile-base
|
|
808
818
|
($2000 vs $0000), GG OAM hardware-vs-visible coords → sms/gg `MENTAL_MODEL`.
|
|
809
819
|
|
|
810
|
-
Turnkey NES/GB/GBC projects (`
|
|
811
|
-
the
|
|
820
|
+
Turnkey NES/GB/GBC projects (`examples({op:'fork'})`) copy every runtime file
|
|
821
|
+
the example needs (`*_runtime.{h,c}`, `gb_hardware.h`, crt0, linker cfg) into the
|
|
812
822
|
project dir and auto-fix the cart header at build — iterate the whole dir with
|
|
813
823
|
`build({output:'run', path, platform})`. Details in those platforms' MENTAL_MODELs.
|
|
814
824
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,52 @@ 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
|
+
|
|
7
53
|
## 0.28.0
|
|
8
54
|
|
|
9
55
|
The reverse-engineering release: the three RE primitives — break-instant
|
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
|
|
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 10
|
|
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
|
|