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.
Files changed (154) hide show
  1. package/AGENTS.md +51 -41
  2. package/CHANGELOG.md +46 -0
  3. package/README.md +3 -3
  4. package/examples/README.md +7 -7
  5. package/examples/atari2600/templates/platformer.asm +1225 -332
  6. package/examples/atari2600/templates/puzzle.asm +1056 -0
  7. package/examples/atari2600/templates/racing.asm +906 -275
  8. package/examples/atari2600/templates/shmup.asm +1031 -239
  9. package/examples/atari2600/templates/sports.asm +1135 -253
  10. package/examples/atari7800/templates/platformer.c +991 -156
  11. package/examples/atari7800/templates/puzzle.c +1091 -148
  12. package/examples/atari7800/templates/racing.c +952 -124
  13. package/examples/atari7800/templates/shmup.c +812 -134
  14. package/examples/atari7800/templates/sports.c +820 -184
  15. package/examples/c64/templates/platformer.c +879 -164
  16. package/examples/c64/templates/puzzle.c +855 -178
  17. package/examples/c64/templates/racing.c +873 -97
  18. package/examples/c64/templates/shmup.c +757 -161
  19. package/examples/c64/templates/sports.c +755 -100
  20. package/examples/gb/templates/platformer.c +841 -179
  21. package/examples/gb/templates/puzzle.c +986 -246
  22. package/examples/gb/templates/racing.c +754 -174
  23. package/examples/gb/templates/shmup.c +673 -175
  24. package/examples/gb/templates/sports.c +790 -159
  25. package/examples/gba/templates/platformer.c +626 -165
  26. package/examples/gba/templates/puzzle.c +519 -269
  27. package/examples/gba/templates/racing.c +511 -206
  28. package/examples/gba/templates/shmup.c +564 -179
  29. package/examples/gba/templates/sports.c +454 -174
  30. package/examples/gbc/templates/platformer.c +944 -180
  31. package/examples/gbc/templates/puzzle.c +363 -109
  32. package/examples/gbc/templates/racing.c +884 -180
  33. package/examples/gbc/templates/shmup.c +821 -185
  34. package/examples/gbc/templates/sports.c +870 -162
  35. package/examples/genesis/templates/platformer.c +747 -129
  36. package/examples/genesis/templates/puzzle.c +694 -261
  37. package/examples/genesis/templates/racing.c +726 -203
  38. package/examples/genesis/templates/shmup.c +535 -142
  39. package/examples/genesis/templates/sports.c +495 -158
  40. package/examples/gg/templates/platformer.c +880 -215
  41. package/examples/gg/templates/puzzle.c +875 -216
  42. package/examples/gg/templates/racing.c +915 -172
  43. package/examples/gg/templates/shmup.c +714 -191
  44. package/examples/gg/templates/sports.c +732 -129
  45. package/examples/lynx/templates/platformer.c +604 -69
  46. package/examples/lynx/templates/puzzle.c +498 -158
  47. package/examples/lynx/templates/racing.c +538 -102
  48. package/examples/lynx/templates/shmup.c +458 -131
  49. package/examples/lynx/templates/sports.c +496 -72
  50. package/examples/msx/platformer/main.c +649 -162
  51. package/examples/msx/puzzle/main.c +742 -240
  52. package/examples/msx/racing/main.c +669 -178
  53. package/examples/msx/shmup/main.c +460 -178
  54. package/examples/msx/sports/main.c +592 -126
  55. package/examples/nes/templates/platformer.c +589 -171
  56. package/examples/nes/templates/puzzle.c +563 -242
  57. package/examples/nes/templates/racing.c +502 -208
  58. package/examples/nes/templates/shmup.c +339 -145
  59. package/examples/nes/templates/sports.c +341 -183
  60. package/examples/pce/platformer/main.c +874 -205
  61. package/examples/pce/puzzle/main.c +802 -287
  62. package/examples/pce/racing/main.c +783 -208
  63. package/examples/pce/shmup/main.c +638 -212
  64. package/examples/pce/sports/main.c +586 -169
  65. package/examples/porting-across-platforms/README.md +1 -1
  66. package/examples/sms/templates/platformer.c +762 -177
  67. package/examples/sms/templates/puzzle.c +752 -212
  68. package/examples/sms/templates/racing.c +808 -145
  69. package/examples/sms/templates/shmup.c +599 -162
  70. package/examples/sms/templates/sports.c +630 -122
  71. package/examples/snes/templates/music_demo.c +7 -0
  72. package/examples/snes/templates/platformer-data.asm +123 -24
  73. package/examples/snes/templates/platformer-hdr.asm +57 -0
  74. package/examples/snes/templates/platformer.c +586 -165
  75. package/examples/snes/templates/puzzle-data.asm +116 -21
  76. package/examples/snes/templates/puzzle-hdr.asm +57 -0
  77. package/examples/snes/templates/puzzle.c +614 -235
  78. package/examples/snes/templates/racing-data.asm +390 -32
  79. package/examples/snes/templates/racing-hdr.asm +57 -0
  80. package/examples/snes/templates/racing.c +807 -196
  81. package/examples/snes/templates/shmup-data.asm +87 -29
  82. package/examples/snes/templates/shmup-hdr.asm +57 -0
  83. package/examples/snes/templates/shmup.c +459 -198
  84. package/examples/snes/templates/sports-data.asm +48 -2
  85. package/examples/snes/templates/sports-hdr.asm +57 -0
  86. package/examples/snes/templates/sports.c +414 -163
  87. package/package.json +1 -1
  88. package/src/host/LibretroHost.js +59 -1
  89. package/src/http/tool-registry.js +11 -11
  90. package/src/mcp/tools/cheats.js +2 -1
  91. package/src/mcp/tools/frame.js +3 -2
  92. package/src/mcp/tools/index.js +3 -3
  93. package/src/mcp/tools/input.js +5 -4
  94. package/src/mcp/tools/lifecycle.js +6 -4
  95. package/src/mcp/tools/platform-docs.js +1 -1
  96. package/src/mcp/tools/preview-tile.js +6 -2
  97. package/src/mcp/tools/project.js +1098 -130
  98. package/src/mcp/tools/rom-id.js +5 -1
  99. package/src/mcp/tools/run-until.js +4 -2
  100. package/src/mcp/tools/snippets.js +6 -6
  101. package/src/mcp/tools/sprite-pipeline.js +14 -2
  102. package/src/mcp/tools/state.js +2 -1
  103. package/src/mcp/tools/tile-inspect.js +8 -1
  104. package/src/mcp/tools/toolchain.js +12 -1
  105. package/src/mcp/tools/watch-memory.js +4 -3
  106. package/src/observer/bus.js +73 -0
  107. package/src/observer/livestream.html +4 -2
  108. package/src/observer/tool-wrap.js +17 -14
  109. package/src/platforms/atari7800/MENTAL_MODEL.md +5 -5
  110. package/src/platforms/atari7800/TROUBLESHOOTING.md +5 -5
  111. package/src/platforms/c64/MENTAL_MODEL.md +11 -4
  112. package/src/platforms/c64/TROUBLESHOOTING.md +13 -0
  113. package/src/platforms/gb/MENTAL_MODEL.md +3 -3
  114. package/src/platforms/gb/TROUBLESHOOTING.md +61 -8
  115. package/src/platforms/gb/lib/c/README.md +10 -11
  116. package/src/platforms/gb/lib/c/gb_crt0.s +27 -3
  117. package/src/platforms/gb/lib/c/patch-header.js +13 -3
  118. package/src/platforms/gba/MENTAL_MODEL.md +4 -4
  119. package/src/platforms/gba/TROUBLESHOOTING.md +3 -3
  120. package/src/platforms/gba/lib/c/gba_sfx.c +40 -0
  121. package/src/platforms/gba/lib/c/gba_sfx.h +10 -0
  122. package/src/platforms/gbc/MENTAL_MODEL.md +4 -4
  123. package/src/platforms/gbc/TROUBLESHOOTING.md +4 -4
  124. package/src/platforms/gbc/UPSTREAM_SOURCES.md +1 -1
  125. package/src/platforms/gbc/lib/c/README.md +10 -11
  126. package/src/platforms/gbc/lib/c/gb_crt0.s +26 -3
  127. package/src/platforms/gbc/lib/c/patch-header.js +13 -3
  128. package/src/platforms/genesis/MENTAL_MODEL.md +3 -3
  129. package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
  130. package/src/platforms/gg/MENTAL_MODEL.md +4 -4
  131. package/src/platforms/gg/TROUBLESHOOTING.md +3 -3
  132. package/src/platforms/gg/UPSTREAM_SOURCES.md +1 -1
  133. package/src/platforms/gg/lib/c/joypad_read.c +29 -0
  134. package/src/platforms/lynx/MENTAL_MODEL.md +1 -1
  135. package/src/platforms/lynx/TROUBLESHOOTING.md +3 -3
  136. package/src/platforms/msx/MENTAL_MODEL.md +5 -5
  137. package/src/platforms/msx/TROUBLESHOOTING.md +2 -2
  138. package/src/platforms/msx/lib/c/msx_hw.h +1 -0
  139. package/src/platforms/msx/lib/c/msx_vdp.c +25 -0
  140. package/src/platforms/nes/MENTAL_MODEL.md +2 -2
  141. package/src/platforms/nes/lib/c/nes_runtime.c +149 -34
  142. package/src/platforms/nes/lib/c/nes_runtime.h +34 -1
  143. package/src/platforms/pce/MENTAL_MODEL.md +5 -5
  144. package/src/platforms/pce/TROUBLESHOOTING.md +1 -1
  145. package/src/platforms/pce/lib/c/pce_hw.h +11 -0
  146. package/src/platforms/pce/lib/c/pce_video.c +32 -0
  147. package/src/platforms/sms/MENTAL_MODEL.md +6 -6
  148. package/src/platforms/snes/MENTAL_MODEL.md +2 -2
  149. package/src/platforms/snes/TROUBLESHOOTING.md +40 -1
  150. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +13 -8
  151. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.crt0.s +58 -5
  152. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +52 -3
  153. package/src/toolchains/cc65/presets/pce/rom32k.cfg +52 -0
  154. 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` — starter snippets per platform
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 — Scaffold a working project (the dumb-model-friendly path)
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. Use the high-level scaffolding tools and don't
149
- worry about ground truth:
150
-
151
- 1. **`scaffold({op:'project', platform, template, name, path})`** drops a
152
- complete, self-contained project tree on disk (main.c + the
153
- runtime files it needs + the vendored library source for
154
- 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
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
- 2. **`scaffold({op:'game', platform, genre})`** — same but picks a known-good
160
- genre scaffold (shmup / platformer / puzzle / sports / racing).
161
- 3. **`scaffold({op:'snippets', platform, mode})`** (mode `list`/`get`/`getAll`)
162
- / **`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
163
173
  vetted helper files (reset routine, read_pad, OAM DMA, palette
164
- upload, etc.) when building from a smaller starting point.
165
- `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
166
176
  without round-tripping bytes through your context — preferred
167
- when you're scaffolding into a project dir.
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 scaffold time). The FULL source of
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
- `scaffold({op:'copySnippets'})` to pull it in). Now it lands automatically
222
- 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
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. Use `scaffold({op:'game'})`, iterate.
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 scaffolded project (whether via `scaffold({op:'project'})` or `scaffold({op:'game'})`) is
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 scaffold time is `vendor/` —
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 `scaffold({op:'copySnippets'})` drops e.g. `read_joystick.asm` into
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 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
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 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):
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 `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.
284
294
 
285
- **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.
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 `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.
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
- ## Project scaffolding
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
- Three shapes, pick the one that matches what you're doing:
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', 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.
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:'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[]`.
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
- - **`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 wired — fill 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.
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
- `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.)
698
710
 
699
711
  **Three ways to actually use them:**
700
712
 
701
- - `scaffold({op:'snippets', platform, mode:'get', name})` — one snippet's contents, returned as a string.
702
- - `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).
703
- - **`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).
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 (`scaffold({op:'project'})`) copy every runtime file
811
- 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
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 scaffolds, 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.
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 scaffolded projects link against.
25
- - **`examples/`** — per-platform starter projects and genre scaffolds.
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
 
@@ -1,6 +1,6 @@
1
1
  # Examples
2
2
 
3
- Minimal "hello" projects for every platform romdev's bundled toolchains can build. Each one is the simplest thing that's still a real ROM an agent forks from these instead of starting from a blank file.
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. `scaffold({op:'project', platform:"atari2600", template:"mini_invaders"})`. |
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 templates under `sms/templates/` (default, hello_sprite, tile_engine, shmup, shmup_2p, platformer, puzzle, sports, racing, music_demo) all use this crt0 — `scaffold({op:'project'})` copies it in automatically. |
25
- | gg | `gg/templates/default.c` (or any other template) | 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 templates (default, hello_sprite, tile_engine, shmup, platformer, puzzle, sports, racing, music_demo) all link the GG runtime + crt0 via `scaffold({op:'project', platform:"gg"})`. |
26
- | gba | `gba/templates/*.c` | arm-none-eabi-gcc | Default runtime = **libtonc** (`#include <tonc.h>`). 9 scaffolds incl. `tonc_hello`, `tonc_hello_sprite`, the 5 genre scaffolds, 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. Templates: `sprite_move`, `catch_game`, `music_sfx`, plus the 5 genre scaffolds (shmup/platformer/puzzle/sports/racing). **`#include <stdint.h>`** for int8/16/32_t — `pce.h` only typedefs u8/u16. Genre scaffolds 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`). Templates: `sprite_move`, `catch_game`, `music_sfx`, plus the 5 genre scaffolds. 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. |
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