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/AGENTS.md
CHANGED
|
@@ -4,7 +4,7 @@ This is romdev's GENERIC orientation — read it once. The platform-specific det
|
|
|
4
4
|
|
|
5
5
|
## What this server does
|
|
6
6
|
|
|
7
|
-
Drives the full homebrew ROM dev loop for 14 retro game platforms (NES, SNES, Game Boy, Game Boy Color, Game Boy Advance, Genesis, Sega Master System, Game Gear, Atari 2600/7800, Atari Lynx, Commodore 64, PC Engine / TurboGrafx-16, and MSX / MSX2). Build → run → screenshot → inspect → patch → iterate. Also a strong reverse-engineering kit: disassemble existing ROMs into byte-exact rebuildable projects (`disasm({target:'project'})`/`disasm({target:'references'})` — the workhorse for any structural hack), find a value's address with the Cheat-Engine search loop (`memory({op:'search'})`/`memory({op:'searchNext'})`), find the EXACT instruction that wrote a RAM byte (`breakpoint({on:'write'})`, a core-level write watchpoint), confirm a patch is live in the running image (`memory({op:'readCart'})`), tell whether a "found table" is really ASCII (`memory({op:'classify'})`), trace
|
|
7
|
+
Drives the full homebrew ROM dev loop for 14 retro game platforms (NES, SNES, Game Boy, Game Boy Color, Game Boy Advance, Genesis, Sega Master System, Game Gear, Atari 2600/7800, Atari Lynx, Commodore 64, PC Engine / TurboGrafx-16, and MSX / MSX2). Build → run → screenshot → inspect → patch → iterate. Also a strong reverse-engineering kit: disassemble existing ROMs into byte-exact rebuildable projects (`disasm({target:'project'})`/`disasm({target:'references'})` — the workhorse for any structural hack), find a value's address with the Cheat-Engine search loop (`memory({op:'search'})`/`memory({op:'searchNext'})`), find the EXACT instruction that wrote a RAM byte (`breakpoint({on:'write'})`, a core-level write watchpoint), confirm a patch is live in the running image (`memory({op:'readCart'})`), tell whether a "found table" is really ASCII (`memory({op:'classify'})`), trace where an on-screen graphic comes from (`watch({on:'copy'})` on all 14 — writer PC per VRAM write; `watch({on:'dma'})` for Genesis DMA sources), drive menus by screen-change (`navigate`), and look up cheats (`cheats({op:'lookup'})`/`cheats({op:'search'})`: a free, crowd-sourced labeled RAM/code map for known ROMs), apply + create cheats, convert assets, study patterns from real games. **Doing a romhack? Start with `platform({op:'doc', platform:'romhacking', name:'playbook'})`** — the decision tree that wires all of the above together. Bundled WASM toolchains and emulator cores — no system dependencies, no installs.
|
|
8
8
|
|
|
9
9
|
You drive the work. The human is a director — they may want a game, a ROM disassembly, a tool-assisted reverse-engineering session, or anything else this server can do.
|
|
10
10
|
|
|
@@ -40,6 +40,8 @@ A couple of optional features load a native Node addon (most notably the `playte
|
|
|
40
40
|
|
|
41
41
|
If a human is sitting next to you during this session — and that's most sessions in practice — open the playtest window as soon as your first build succeeds. `playtest()` opens a native SDL window that runs your ROM live and accepts USB gamepads (hot-plugged controllers are picked up automatically). It returns **immediately** — the render loop runs in the background, so you keep calling other tools while the human plays. Every other MCP tool keeps working against that same running ROM, and **`build({output:'run'})`/`loadMedia` rebuilds update the window in place** — the window follows your latest build, no relaunch and no crash on rebuild. A human sitting next to you should be **playing the game** while you iterate, not watching screenshots scroll past.
|
|
42
42
|
|
|
43
|
+
**Co-driving is detected for you.** While the human is actively pressing (pad or keyboard), the window's input wins over yours and its real-time loop races your frame-stepping — and you'll KNOW: `frame`/`input` responses carry a `humanCoDriveWarning` while they pressed within the last ~2s, and `catalog({op:'status'})` / `playtest({op:'status'})` expose `humanInputActive`. When the human is idle the window leaves your `input({op:'set'})` alone. For deterministic stepping while they play, either `host({op:'pause'})` (the window keeps rendering, frozen) or use a SECOND session (a different `x-romdev-session` header = a fully isolated emulator).
|
|
44
|
+
|
|
43
45
|
```
|
|
44
46
|
playtest() // opens the SDL window (returns immediately). op:'open' is the default;
|
|
45
47
|
// playtest({op:'stop'|'status'|'framebuffer'}) close / check / capture-what-the-human-sees
|
|
@@ -63,10 +65,10 @@ Skip playtest only when there's clearly no human in the loop: CI runs, automated
|
|
|
63
65
|
- `run` — load ROMs, step frames, screenshot (works for existing ROMs you didn't compile)
|
|
64
66
|
- `input` — drive controllers, look up hardware bit layouts. `navigate` walks menus by advancing on SCREEN CHANGE (not fixed frames) and reports whether each press was consumed — the fast, reliable way to script a UI.
|
|
65
67
|
- `state` — savestates and forensic state inspection (`state({op:'save'})`, `state({op:'load'})`, `state({op:'export'})` a slot to disk without touching the live host, `state({op:'list'})`, `state({op:'dump'})`)
|
|
66
|
-
- `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"). **`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); `state({op:'diff'})` is the coarse whole-machine version.
|
|
67
|
-
- `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), **`watch({on:'dma', precision:'sampled'})`** (Genesis: which ROM offset a VRAM graphic was DMA'd from), **`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), `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)
|
|
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
|
+
- `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)
|
|
68
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'})`)
|
|
69
|
-
- `project` —
|
|
71
|
+
- `project` — the example-game library (`examples`: list / fork / show, plus the legacy snippet ops)
|
|
70
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
|
|
71
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
|
|
72
74
|
|
|
@@ -140,29 +142,39 @@ Ergonomic exceptions:
|
|
|
140
142
|
|
|
141
143
|
Two parallel paths depending on what you need:
|
|
142
144
|
|
|
143
|
-
### Path A —
|
|
145
|
+
### Path A — Fork a working example game (the dumb-model-friendly path)
|
|
144
146
|
|
|
145
147
|
Most agent sessions start here. You want a working ROM, not a
|
|
146
|
-
research project.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
153
163
|
(`files`) + a `vendorFileCount`; pass `verbose:true` for the full manifest.
|
|
154
164
|
Build the whole dir in one call with `build({output:'project', path,
|
|
155
165
|
outputPath})` (toolchain/crt0/linker inferred — no manifest); the bundled
|
|
156
166
|
examples ARE the reference implementation.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
161
173
|
vetted helper files (reset routine, read_pad, OAM DMA, palette
|
|
162
|
-
upload, etc.)
|
|
163
|
-
`
|
|
174
|
+
upload, etc.) as one-off references.
|
|
175
|
+
`examples({op:'copySnippets'})` writes the files to disk in one call
|
|
164
176
|
without round-tripping bytes through your context — preferred
|
|
165
|
-
when you're
|
|
177
|
+
when you're copying into a project dir.
|
|
166
178
|
|
|
167
179
|
Reminder (it's the second rule up top): **read your platform's
|
|
168
180
|
`platform({op:'doc', platform, name:'mental_model'})` BEFORE you write code for
|
|
@@ -186,7 +198,7 @@ order:
|
|
|
186
198
|
All ~50-200 lines, fully readable. Read these when an API call
|
|
187
199
|
isn't doing what you expect.
|
|
188
200
|
3. **Your own project's `vendor/` library source** (R58b — auto-
|
|
189
|
-
copied into every project at
|
|
201
|
+
copied into every project at fork time). The FULL source of
|
|
190
202
|
every library your ROM links against — `vendor/cc65/libsrc/<p>/`
|
|
191
203
|
for cc65 platforms, `vendor/libtonc/src/` + `vendor/libgba/src/`
|
|
192
204
|
for GBA, `vendor/pvsneslib/source/` for SNES, `vendor/sgdk/src/`
|
|
@@ -216,8 +228,8 @@ into "what the linked code actually does."
|
|
|
216
228
|
|
|
217
229
|
The "vendor/" library source in your project is new in R58b (it was
|
|
218
230
|
previously in the install only; you'd have to call
|
|
219
|
-
`
|
|
220
|
-
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
|
|
221
233
|
rounds partly because cc65's TGI driver source wasn't visible;
|
|
222
234
|
post-R58b you can `grep -rn bar_c vendor/cc65/libsrc/lynx/` from
|
|
223
235
|
inside your project directory and read the actual blitter code.
|
|
@@ -230,7 +242,7 @@ disagree with behavior, trust the library source over the example.
|
|
|
230
242
|
|
|
231
243
|
### Which path to use
|
|
232
244
|
|
|
233
|
-
- **Just need a working game** → Path A.
|
|
245
|
+
- **Just need a working game** → Path A. Fork the nearest example with `examples({op:'fork'})`, iterate.
|
|
234
246
|
- **Hit a bug or unexpected behavior** → switch to Path B.
|
|
235
247
|
- **Don't know which** → start in Path A; if iterations fail to
|
|
236
248
|
converge after 2-3 attempts, you're hitting something path A
|
|
@@ -238,7 +250,7 @@ disagree with behavior, trust the library source over the example.
|
|
|
238
250
|
|
|
239
251
|
### Where files land in your project tree
|
|
240
252
|
|
|
241
|
-
A
|
|
253
|
+
A forked project (`examples({op:'fork'})`) is
|
|
242
254
|
**FLAT** for everything you author. `main.c` / `main.asm`, your
|
|
243
255
|
helper modules (e.g. `gb_runtime.c`, `nes_runtime.c`,
|
|
244
256
|
`atari7800_sfx.c`, `vcs_constants.h`), the platform crt0 + linker
|
|
@@ -247,13 +259,13 @@ config — all sit at the project root, next to each other. Asm
|
|
|
247
259
|
without `-I` flags because dasm / cc65 / sdcc all default to the
|
|
248
260
|
current directory.
|
|
249
261
|
|
|
250
|
-
The **only** subdir you'll see at
|
|
262
|
+
The **only** subdir you'll see at fork time is `vendor/` —
|
|
251
263
|
that's the read-only library source tree (cc65 libsrc, libtonc /
|
|
252
264
|
libgba src, PVSnesLib source, SGDK src) auto-bundled by R58b so
|
|
253
265
|
you can `grep -rn vendor/` when debugging. Don't put your own
|
|
254
266
|
source under `vendor/`.
|
|
255
267
|
|
|
256
|
-
So when `
|
|
268
|
+
So when `examples({op:'copySnippets'})` drops e.g. `read_joystick.asm` into
|
|
257
269
|
your project dir, it lands at `./read_joystick.asm` (alongside
|
|
258
270
|
`main.asm`), NOT under `./include/` or `./lib/`. Every platform
|
|
259
271
|
follows the same flat layout.
|
|
@@ -261,7 +273,7 @@ follows the same flat layout.
|
|
|
261
273
|
Because the layout is flat, **the simplest loop is `build({output:'run', path, platform})`
|
|
262
274
|
(build + load + run + screenshot in one call) or `build({output:'project', path, platform})`
|
|
263
275
|
(build the dir to a ROM) — no per-iteration file manifest, on EVERY platform.** Point it at
|
|
264
|
-
a
|
|
276
|
+
a forked project directory and it does the right per-platform thing automatically: finds the
|
|
265
277
|
entry (`main.c` for C / SGDK Genesis / GBA / cc65-C / SDCC-C, or `main.s` / `main.asm` for
|
|
266
278
|
asm), routes the platform's crt0 correctly (e.g. GB/GBC `gb_crt0.s` via the cart-header path,
|
|
267
279
|
not as a plain source — so no `gsinit` collision), applies the right linker preset
|
|
@@ -276,11 +288,11 @@ with explicit `sources` only when the files aren't on disk, e.g. generated in-co
|
|
|
276
288
|
|
|
277
289
|
## Supported platforms
|
|
278
290
|
|
|
279
|
-
**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):
|
|
280
292
|
|
|
281
|
-
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.
|
|
282
294
|
|
|
283
|
-
**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.
|
|
284
296
|
|
|
285
297
|
**Delisted** (toolchain works but core-side issue blocks the run loop): Atari 5200 (atari800 BIOS-load path), ZX Spectrum (fuse tape-load path).
|
|
286
298
|
|
|
@@ -345,7 +357,7 @@ The deep per-platform inspectors + the exact memory-region names, core quirks, a
|
|
|
345
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.)
|
|
346
358
|
- **PC Engine** — generic shapes + the core's native regions only so far (no custom-inspector treatment yet).
|
|
347
359
|
|
|
348
|
-
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.
|
|
349
361
|
|
|
350
362
|
## ROMs are finalized for real hardware automatically
|
|
351
363
|
|
|
@@ -604,15 +616,17 @@ OAM format: bytes per sprite are `[y, tileIndex, attributes, x]`.
|
|
|
604
616
|
|
|
605
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.
|
|
606
618
|
|
|
607
|
-
##
|
|
619
|
+
## Starting a project: fork an example game
|
|
608
620
|
|
|
609
|
-
|
|
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.
|
|
628
|
+
|
|
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.
|
|
616
630
|
|
|
617
631
|
Then iterate with `build({output:'run'})` against the source you read from `path/main.*`.
|
|
618
632
|
|
|
@@ -692,15 +706,13 @@ Two tools that save real time and frustration:
|
|
|
692
706
|
|
|
693
707
|
## Starter snippets
|
|
694
708
|
|
|
695
|
-
`
|
|
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.)
|
|
696
710
|
|
|
697
711
|
**Three ways to actually use them:**
|
|
698
712
|
|
|
699
|
-
- `
|
|
700
|
-
- `
|
|
701
|
-
- **`
|
|
702
|
-
|
|
703
|
-
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).
|
|
704
716
|
|
|
705
717
|
## Don't burn your own context with binary data
|
|
706
718
|
|
|
@@ -805,8 +817,8 @@ them for YOUR platform before you build.** By symptom:
|
|
|
805
817
|
8-sprites-per-scanline limit, SAT `$D0` terminator, R6 sprite-tile-base
|
|
806
818
|
($2000 vs $0000), GG OAM hardware-vs-visible coords → sms/gg `MENTAL_MODEL`.
|
|
807
819
|
|
|
808
|
-
Turnkey NES/GB/GBC projects (`
|
|
809
|
-
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
|
|
810
822
|
project dir and auto-fix the cart header at build — iterate the whole dir with
|
|
811
823
|
`build({output:'run', path, platform})`. Details in those platforms' MENTAL_MODELs.
|
|
812
824
|
|