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.
Files changed (199) hide show
  1. package/AGENTS.md +56 -44
  2. package/CHANGELOG.md +355 -0
  3. package/README.md +4 -4
  4. package/examples/README.md +7 -7
  5. package/examples/atari2600/templates/platformer.asm +1227 -325
  6. package/examples/atari2600/templates/puzzle.asm +1056 -0
  7. package/examples/atari2600/templates/racing.asm +909 -257
  8. package/examples/atari2600/templates/shmup.asm +1035 -218
  9. package/examples/atari2600/templates/sports.asm +1143 -229
  10. package/examples/atari7800/templates/hello_sprite.c +8 -4
  11. package/examples/atari7800/templates/platformer.c +991 -152
  12. package/examples/atari7800/templates/puzzle.c +1091 -145
  13. package/examples/atari7800/templates/racing.c +949 -118
  14. package/examples/atari7800/templates/shmup.c +812 -130
  15. package/examples/atari7800/templates/sports.c +820 -181
  16. package/examples/c64/templates/platformer.c +876 -157
  17. package/examples/c64/templates/puzzle.c +881 -143
  18. package/examples/c64/templates/racing.c +873 -88
  19. package/examples/c64/templates/shmup.c +762 -154
  20. package/examples/c64/templates/sports.c +755 -95
  21. package/examples/gb/templates/platformer.c +841 -175
  22. package/examples/gb/templates/puzzle.c +1094 -176
  23. package/examples/gb/templates/racing.c +761 -169
  24. package/examples/gb/templates/shmup.c +679 -169
  25. package/examples/gb/templates/sports.c +790 -153
  26. package/examples/gba/templates/platformer.c +624 -169
  27. package/examples/gba/templates/puzzle.c +535 -207
  28. package/examples/gba/templates/racing.c +513 -196
  29. package/examples/gba/templates/shmup.c +565 -168
  30. package/examples/gba/templates/sports.c +454 -162
  31. package/examples/gbc/templates/platformer.c +944 -176
  32. package/examples/gbc/templates/puzzle.c +1131 -177
  33. package/examples/gbc/templates/racing.c +891 -175
  34. package/examples/gbc/templates/shmup.c +827 -179
  35. package/examples/gbc/templates/sports.c +870 -156
  36. package/examples/genesis/templates/platformer.c +747 -129
  37. package/examples/genesis/templates/puzzle.c +702 -208
  38. package/examples/genesis/templates/racing.c +728 -193
  39. package/examples/genesis/templates/shmup.c +535 -142
  40. package/examples/genesis/templates/shmup_2p.c +13 -1
  41. package/examples/genesis/templates/sports.c +495 -158
  42. package/examples/gg/templates/platformer.c +883 -214
  43. package/examples/gg/templates/puzzle.c +906 -181
  44. package/examples/gg/templates/racing.c +919 -160
  45. package/examples/gg/templates/shmup.c +716 -177
  46. package/examples/gg/templates/sports.c +735 -128
  47. package/examples/lynx/templates/platformer.c +604 -50
  48. package/examples/lynx/templates/puzzle.c +533 -130
  49. package/examples/lynx/templates/racing.c +538 -102
  50. package/examples/lynx/templates/shmup.c +461 -122
  51. package/examples/lynx/templates/sports.c +496 -69
  52. package/examples/msx/platformer/main.c +648 -159
  53. package/examples/msx/puzzle/main.c +750 -185
  54. package/examples/msx/racing/main.c +669 -177
  55. package/examples/msx/shmup/main.c +460 -177
  56. package/examples/msx/sports/main.c +591 -124
  57. package/examples/nes/templates/platformer.c +586 -160
  58. package/examples/nes/templates/puzzle.c +603 -222
  59. package/examples/nes/templates/racing.c +505 -197
  60. package/examples/nes/templates/shmup.c +339 -144
  61. package/examples/nes/templates/sports.c +341 -182
  62. package/examples/pce/platformer/main.c +875 -204
  63. package/examples/pce/puzzle/main.c +797 -216
  64. package/examples/pce/racing/main.c +782 -206
  65. package/examples/pce/shmup/main.c +638 -211
  66. package/examples/pce/sports/main.c +585 -167
  67. package/examples/porting-across-platforms/README.md +1 -1
  68. package/examples/sms/templates/platformer.c +765 -176
  69. package/examples/sms/templates/puzzle.c +783 -177
  70. package/examples/sms/templates/racing.c +812 -133
  71. package/examples/sms/templates/shmup.c +601 -148
  72. package/examples/sms/templates/shmup_2p.c +17 -1
  73. package/examples/sms/templates/sports.c +633 -121
  74. package/examples/snes/templates/music_demo.c +7 -0
  75. package/examples/snes/templates/platformer-data.asm +123 -24
  76. package/examples/snes/templates/platformer-hdr.asm +57 -0
  77. package/examples/snes/templates/platformer.c +587 -149
  78. package/examples/snes/templates/puzzle-data.asm +116 -21
  79. package/examples/snes/templates/puzzle-hdr.asm +57 -0
  80. package/examples/snes/templates/puzzle.c +632 -185
  81. package/examples/snes/templates/racing-data.asm +390 -32
  82. package/examples/snes/templates/racing-hdr.asm +57 -0
  83. package/examples/snes/templates/racing.c +807 -177
  84. package/examples/snes/templates/shmup-data.asm +87 -29
  85. package/examples/snes/templates/shmup-hdr.asm +57 -0
  86. package/examples/snes/templates/shmup.c +459 -180
  87. package/examples/snes/templates/sports-data.asm +48 -2
  88. package/examples/snes/templates/sports-hdr.asm +57 -0
  89. package/examples/snes/templates/sports.c +414 -156
  90. package/package.json +12 -12
  91. package/src/cores/wasm/bluemsx_libretro.js +1 -1
  92. package/src/cores/wasm/bluemsx_libretro.wasm +0 -0
  93. package/src/cores/wasm/fceumm_libretro.js +1 -1
  94. package/src/cores/wasm/fceumm_libretro.wasm +0 -0
  95. package/src/cores/wasm/gambatte_libretro.js +1 -1
  96. package/src/cores/wasm/gambatte_libretro.wasm +0 -0
  97. package/src/cores/wasm/geargrafx_libretro.js +1 -1
  98. package/src/cores/wasm/geargrafx_libretro.wasm +0 -0
  99. package/src/cores/wasm/genesis_plus_gx_libretro.js +1 -1
  100. package/src/cores/wasm/genesis_plus_gx_libretro.wasm +0 -0
  101. package/src/cores/wasm/handy_libretro.js +1 -1
  102. package/src/cores/wasm/handy_libretro.wasm +0 -0
  103. package/src/cores/wasm/mgba_libretro.js +1 -1
  104. package/src/cores/wasm/mgba_libretro.wasm +0 -0
  105. package/src/cores/wasm/prosystem_libretro.js +1 -1
  106. package/src/cores/wasm/prosystem_libretro.wasm +0 -0
  107. package/src/cores/wasm/snes9x_libretro.js +1 -1
  108. package/src/cores/wasm/snes9x_libretro.wasm +0 -0
  109. package/src/cores/wasm/stella2014_libretro.js +1 -1
  110. package/src/cores/wasm/stella2014_libretro.wasm +0 -0
  111. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  112. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  113. package/src/host/LibretroHost.js +304 -11
  114. package/src/http/tool-registry.js +11 -11
  115. package/src/mcp/server.js +6 -0
  116. package/src/mcp/tools/cheats.js +2 -1
  117. package/src/mcp/tools/disasm-rebuild.js +315 -65
  118. package/src/mcp/tools/disasm.js +149 -28
  119. package/src/mcp/tools/find-references.js +216 -51
  120. package/src/mcp/tools/frame.js +14 -6
  121. package/src/mcp/tools/index.js +18 -4
  122. package/src/mcp/tools/input.js +31 -7
  123. package/src/mcp/tools/lifecycle.js +6 -4
  124. package/src/mcp/tools/memory.js +208 -39
  125. package/src/mcp/tools/platform-docs.js +1 -1
  126. package/src/mcp/tools/playtest.js +56 -4
  127. package/src/mcp/tools/preview-tile.js +6 -2
  128. package/src/mcp/tools/project.js +1114 -120
  129. package/src/mcp/tools/rom-id.js +5 -1
  130. package/src/mcp/tools/run-until.js +4 -2
  131. package/src/mcp/tools/snippets.js +6 -6
  132. package/src/mcp/tools/sprite-pipeline.js +14 -2
  133. package/src/mcp/tools/state.js +2 -1
  134. package/src/mcp/tools/tile-inspect.js +8 -1
  135. package/src/mcp/tools/toolchain.js +55 -11
  136. package/src/mcp/tools/watch-memory.js +145 -27
  137. package/src/observer/bus.js +73 -0
  138. package/src/observer/livestream.html +4 -2
  139. package/src/observer/tool-wrap.js +17 -14
  140. package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +64 -17
  141. package/src/platforms/atari2600/MENTAL_MODEL.md +5 -1
  142. package/src/platforms/atari2600/TROUBLESHOOTING.md +40 -0
  143. package/src/platforms/atari7800/MENTAL_MODEL.md +32 -11
  144. package/src/platforms/atari7800/TROUBLESHOOTING.md +5 -5
  145. package/src/platforms/c64/MENTAL_MODEL.md +11 -4
  146. package/src/platforms/c64/TROUBLESHOOTING.md +13 -0
  147. package/src/platforms/gb/MENTAL_MODEL.md +19 -4
  148. package/src/platforms/gb/TROUBLESHOOTING.md +101 -6
  149. package/src/platforms/gb/lib/c/README.md +10 -11
  150. package/src/platforms/gb/lib/c/gb_crt0.s +27 -3
  151. package/src/platforms/gb/lib/c/patch-header.js +19 -6
  152. package/src/platforms/gba/MENTAL_MODEL.md +4 -4
  153. package/src/platforms/gba/TROUBLESHOOTING.md +3 -3
  154. package/src/platforms/gba/lib/c/gba_sfx.c +40 -0
  155. package/src/platforms/gba/lib/c/gba_sfx.h +10 -0
  156. package/src/platforms/gbc/MENTAL_MODEL.md +16 -4
  157. package/src/platforms/gbc/TROUBLESHOOTING.md +24 -3
  158. package/src/platforms/gbc/UPSTREAM_SOURCES.md +1 -1
  159. package/src/platforms/gbc/lib/c/README.md +10 -11
  160. package/src/platforms/gbc/lib/c/font.h +43 -0
  161. package/src/platforms/gbc/lib/c/gb_crt0.s +26 -3
  162. package/src/platforms/gbc/lib/c/patch-header.js +19 -6
  163. package/src/platforms/genesis/MENTAL_MODEL.md +43 -9
  164. package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
  165. package/src/platforms/genesis/lib/c/genesis_sfx.c +37 -0
  166. package/src/platforms/genesis/lib/c/genesis_sfx.h +1 -0
  167. package/src/platforms/gg/MENTAL_MODEL.md +4 -4
  168. package/src/platforms/gg/TROUBLESHOOTING.md +14 -18
  169. package/src/platforms/gg/UPSTREAM_SOURCES.md +1 -1
  170. package/src/platforms/gg/lib/c/gg_crt0.s +14 -2
  171. package/src/platforms/gg/lib/c/joypad_read.c +29 -0
  172. package/src/platforms/lynx/MENTAL_MODEL.md +1 -1
  173. package/src/platforms/lynx/TROUBLESHOOTING.md +3 -3
  174. package/src/platforms/lynx/lib/c/lynx_sfx.c +38 -2
  175. package/src/platforms/lynx/lib/c/lynx_sfx.h +1 -0
  176. package/src/platforms/msx/MENTAL_MODEL.md +11 -5
  177. package/src/platforms/msx/TROUBLESHOOTING.md +21 -0
  178. package/src/platforms/msx/lib/c/msx_crt0.s +27 -0
  179. package/src/platforms/msx/lib/c/msx_hw.h +3 -0
  180. package/src/platforms/msx/lib/c/msx_vdp.c +70 -0
  181. package/src/platforms/nes/MENTAL_MODEL.md +12 -5
  182. package/src/platforms/nes/lib/c/nes_runtime.c +190 -34
  183. package/src/platforms/nes/lib/c/nes_runtime.h +35 -0
  184. package/src/platforms/pce/MENTAL_MODEL.md +14 -5
  185. package/src/platforms/pce/TROUBLESHOOTING.md +9 -0
  186. package/src/platforms/pce/lib/c/pce_hw.h +13 -1
  187. package/src/platforms/pce/lib/c/pce_sound.c +22 -0
  188. package/src/platforms/pce/lib/c/pce_video.c +32 -0
  189. package/src/platforms/sms/MENTAL_MODEL.md +11 -6
  190. package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
  191. package/src/platforms/sms/lib/c/sms_crt0.s +14 -2
  192. package/src/platforms/snes/MENTAL_MODEL.md +7 -2
  193. package/src/platforms/snes/TROUBLESHOOTING.md +40 -1
  194. package/src/playtest/playtest.js +73 -3
  195. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +13 -8
  196. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.crt0.s +58 -5
  197. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +52 -3
  198. package/src/toolchains/cc65/presets/pce/rom32k.cfg +52 -0
  199. 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 which ROM offset a Genesis graphic was DMA'd from (`watch({on:'dma', precision:'sampled'})`), 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.
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` — starter snippets per platform
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 — Scaffold a working project (the dumb-model-friendly path)
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. Use the high-level scaffolding tools and don't
147
- worry about ground truth:
148
-
149
- 1. **`scaffold({op:'project', platform, template, name, path})`** drops a
150
- complete, self-contained project tree on disk (main.c + the
151
- runtime files it needs + the vendored library source for
152
- reference + README + .gitignore). The response lists only the files you EDIT
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
- 2. **`scaffold({op:'game', platform, genre})`** — same but picks a known-good
158
- genre scaffold (shmup / platformer / puzzle / sports / racing).
159
- 3. **`scaffold({op:'snippets', platform, mode})`** (mode `list`/`get`/`getAll`)
160
- / **`scaffold({op:'copySnippets', platform, destinationDir})`** fetch
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.) when building from a smaller starting point.
163
- `scaffold({op:'copySnippets'})` writes the files to disk in one call
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 scaffolding into a project dir.
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 scaffold time). The FULL source of
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
- `scaffold({op:'copySnippets'})` to pull it in). Now it lands automatically
220
- when you `scaffold({op:'project'})`. Round 30/31 Lynx wedges took 5 friction
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. Use `scaffold({op:'game'})`, iterate.
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 scaffolded project (whether via `scaffold({op:'project'})` or `scaffold({op:'game'})`) is
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 scaffold time is `vendor/` —
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 `scaffold({op:'copySnippets'})` drops e.g. `read_joystick.asm` into
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 scaffolded directory and it does the right per-platform thing automatically: finds the
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 scaffolds + sound + music + per-platform MENTAL_MODEL.md + TROUBLESHOOTING.md):
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 `scaffold({op:'game', genre: shmup|platformer|puzzle|sports|racing})` set. 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` scaffold 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 scaffolds.
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` template, no genre scaffolds or sound/music wrappers yet): ColecoVision. Uses SDCC z80 same as SMS/GG/MSX — the genre scaffolds are queued.
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 `scaffold({op:'snippets', platform})` (default `mode:'list'`), fetch one via `scaffold({op:'snippets', platform, mode:'get', name})`. SNES + NES + Genesis + SMS + Game Boy + Atari 2600 + Atari 7800 have substantial snippet libraries; others are minimal.
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
- ## Project scaffolding
619
+ ## Starting a project: fork an example game
608
620
 
609
- Three shapes, pick the one that matches what you're doing:
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
- - **`scaffold({op:'project', platform, name, path, template?})`** — writes a starter directory: `main.{c,asm,s}` (from `examples/<platform>/templates/`) + every runtime file the template depends on (headers, crt0, linker .cfg) + README + `.gitignore`. Self-contained: take it elsewhere and rebuild with stock cc65/sdcc, no romdev install needed. Defaults to `template:"default"` (smallest visible-and-runnable program); most tier-1 platforms also have `hello_sprite` + `tile_engine` + the 5 genre templates. **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 scaffold→build path is verified to build + render on every platform/template.
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
- - **`scaffold({op:'project', ..., withSnippets: true})`** — same as above, **plus** drops every vetted starter snippet for the platform alongside main.c. Use when you want "main.c + every helper file ready to edit" in one shot, without picking a genre. Snippets that overlap with the template's runtime are skipped (no double-writes). Response includes `snippetsCopied: string[]`.
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
- - **`scaffold({op:'game', platform, genre})`** genre-shaped scaffold (`shmup` / `platformer` / `puzzle` / `sports` / `racing`). Higher-level than `scaffold({op:'project'})` picks the right template + runtime + crt0 + linker config for the genre. 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 templates (not a hardcoded list), so the error message for an unsupported (platform, genre) pair always names the current set; e.g. `atari2600` + `puzzle` is rejected and the error lists the genres it *does* have. ColecoVision (bring-up only) has no genre scaffolds and is rejected wholesale. Ships a complete working ROM with state machine + sprite allocation + sound wiredfill in gameplay logic on top. **Want a side-scroller? Use `genre:"platformer"`** — and on every platform EXCEPT NES and the Atari 2600 the scaffold 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.
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
- `scaffold({op:'snippets', platform})` (default `mode:'list'`) and `scaffold({op:'snippets', platform, mode:'get', name})` 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.
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
- - `scaffold({op:'snippets', platform, mode:'get', name})` — one snippet's contents, returned as a string.
700
- - `scaffold({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).
701
- - **`scaffold({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 scaffolding 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).
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 (`scaffold({op:'project'})`) copy every runtime file
809
- the template needs (`*_runtime.{h,c}`, `gb_hardware.h`, crt0, linker cfg) into 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