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