romdevtools 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +56 -44
- package/CHANGELOG.md +355 -0
- package/README.md +4 -4
- package/examples/README.md +7 -7
- package/examples/atari2600/templates/platformer.asm +1227 -325
- package/examples/atari2600/templates/puzzle.asm +1056 -0
- package/examples/atari2600/templates/racing.asm +909 -257
- package/examples/atari2600/templates/shmup.asm +1035 -218
- package/examples/atari2600/templates/sports.asm +1143 -229
- package/examples/atari7800/templates/hello_sprite.c +8 -4
- package/examples/atari7800/templates/platformer.c +991 -152
- package/examples/atari7800/templates/puzzle.c +1091 -145
- package/examples/atari7800/templates/racing.c +949 -118
- package/examples/atari7800/templates/shmup.c +812 -130
- package/examples/atari7800/templates/sports.c +820 -181
- package/examples/c64/templates/platformer.c +876 -157
- package/examples/c64/templates/puzzle.c +881 -143
- package/examples/c64/templates/racing.c +873 -88
- package/examples/c64/templates/shmup.c +762 -154
- package/examples/c64/templates/sports.c +755 -95
- package/examples/gb/templates/platformer.c +841 -175
- package/examples/gb/templates/puzzle.c +1094 -176
- package/examples/gb/templates/racing.c +761 -169
- package/examples/gb/templates/shmup.c +679 -169
- package/examples/gb/templates/sports.c +790 -153
- package/examples/gba/templates/platformer.c +624 -169
- package/examples/gba/templates/puzzle.c +535 -207
- package/examples/gba/templates/racing.c +513 -196
- package/examples/gba/templates/shmup.c +565 -168
- package/examples/gba/templates/sports.c +454 -162
- package/examples/gbc/templates/platformer.c +944 -176
- package/examples/gbc/templates/puzzle.c +1131 -177
- package/examples/gbc/templates/racing.c +891 -175
- package/examples/gbc/templates/shmup.c +827 -179
- package/examples/gbc/templates/sports.c +870 -156
- package/examples/genesis/templates/platformer.c +747 -129
- package/examples/genesis/templates/puzzle.c +702 -208
- package/examples/genesis/templates/racing.c +728 -193
- package/examples/genesis/templates/shmup.c +535 -142
- package/examples/genesis/templates/shmup_2p.c +13 -1
- package/examples/genesis/templates/sports.c +495 -158
- package/examples/gg/templates/platformer.c +883 -214
- package/examples/gg/templates/puzzle.c +906 -181
- package/examples/gg/templates/racing.c +919 -160
- package/examples/gg/templates/shmup.c +716 -177
- package/examples/gg/templates/sports.c +735 -128
- package/examples/lynx/templates/platformer.c +604 -50
- package/examples/lynx/templates/puzzle.c +533 -130
- package/examples/lynx/templates/racing.c +538 -102
- package/examples/lynx/templates/shmup.c +461 -122
- package/examples/lynx/templates/sports.c +496 -69
- package/examples/msx/platformer/main.c +648 -159
- package/examples/msx/puzzle/main.c +750 -185
- package/examples/msx/racing/main.c +669 -177
- package/examples/msx/shmup/main.c +460 -177
- package/examples/msx/sports/main.c +591 -124
- package/examples/nes/templates/platformer.c +586 -160
- package/examples/nes/templates/puzzle.c +603 -222
- package/examples/nes/templates/racing.c +505 -197
- package/examples/nes/templates/shmup.c +339 -144
- package/examples/nes/templates/sports.c +341 -182
- package/examples/pce/platformer/main.c +875 -204
- package/examples/pce/puzzle/main.c +797 -216
- package/examples/pce/racing/main.c +782 -206
- package/examples/pce/shmup/main.c +638 -211
- package/examples/pce/sports/main.c +585 -167
- package/examples/porting-across-platforms/README.md +1 -1
- package/examples/sms/templates/platformer.c +765 -176
- package/examples/sms/templates/puzzle.c +783 -177
- package/examples/sms/templates/racing.c +812 -133
- package/examples/sms/templates/shmup.c +601 -148
- package/examples/sms/templates/shmup_2p.c +17 -1
- package/examples/sms/templates/sports.c +633 -121
- package/examples/snes/templates/music_demo.c +7 -0
- package/examples/snes/templates/platformer-data.asm +123 -24
- package/examples/snes/templates/platformer-hdr.asm +57 -0
- package/examples/snes/templates/platformer.c +587 -149
- package/examples/snes/templates/puzzle-data.asm +116 -21
- package/examples/snes/templates/puzzle-hdr.asm +57 -0
- package/examples/snes/templates/puzzle.c +632 -185
- package/examples/snes/templates/racing-data.asm +390 -32
- package/examples/snes/templates/racing-hdr.asm +57 -0
- package/examples/snes/templates/racing.c +807 -177
- package/examples/snes/templates/shmup-data.asm +87 -29
- package/examples/snes/templates/shmup-hdr.asm +57 -0
- package/examples/snes/templates/shmup.c +459 -180
- package/examples/snes/templates/sports-data.asm +48 -2
- package/examples/snes/templates/sports-hdr.asm +57 -0
- package/examples/snes/templates/sports.c +414 -156
- package/package.json +12 -12
- package/src/cores/wasm/bluemsx_libretro.js +1 -1
- package/src/cores/wasm/bluemsx_libretro.wasm +0 -0
- package/src/cores/wasm/fceumm_libretro.js +1 -1
- package/src/cores/wasm/fceumm_libretro.wasm +0 -0
- package/src/cores/wasm/gambatte_libretro.js +1 -1
- package/src/cores/wasm/gambatte_libretro.wasm +0 -0
- package/src/cores/wasm/geargrafx_libretro.js +1 -1
- package/src/cores/wasm/geargrafx_libretro.wasm +0 -0
- package/src/cores/wasm/genesis_plus_gx_libretro.js +1 -1
- package/src/cores/wasm/genesis_plus_gx_libretro.wasm +0 -0
- package/src/cores/wasm/handy_libretro.js +1 -1
- package/src/cores/wasm/handy_libretro.wasm +0 -0
- package/src/cores/wasm/mgba_libretro.js +1 -1
- package/src/cores/wasm/mgba_libretro.wasm +0 -0
- package/src/cores/wasm/prosystem_libretro.js +1 -1
- package/src/cores/wasm/prosystem_libretro.wasm +0 -0
- package/src/cores/wasm/snes9x_libretro.js +1 -1
- package/src/cores/wasm/snes9x_libretro.wasm +0 -0
- package/src/cores/wasm/stella2014_libretro.js +1 -1
- package/src/cores/wasm/stella2014_libretro.wasm +0 -0
- package/src/cores/wasm/vice_x64_libretro.js +1 -1
- package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
- package/src/host/LibretroHost.js +304 -11
- package/src/http/tool-registry.js +11 -11
- package/src/mcp/server.js +6 -0
- package/src/mcp/tools/cheats.js +2 -1
- package/src/mcp/tools/disasm-rebuild.js +315 -65
- package/src/mcp/tools/disasm.js +149 -28
- package/src/mcp/tools/find-references.js +216 -51
- package/src/mcp/tools/frame.js +14 -6
- package/src/mcp/tools/index.js +18 -4
- package/src/mcp/tools/input.js +31 -7
- package/src/mcp/tools/lifecycle.js +6 -4
- package/src/mcp/tools/memory.js +208 -39
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/playtest.js +56 -4
- package/src/mcp/tools/preview-tile.js +6 -2
- package/src/mcp/tools/project.js +1114 -120
- package/src/mcp/tools/rom-id.js +5 -1
- package/src/mcp/tools/run-until.js +4 -2
- package/src/mcp/tools/snippets.js +6 -6
- package/src/mcp/tools/sprite-pipeline.js +14 -2
- package/src/mcp/tools/state.js +2 -1
- package/src/mcp/tools/tile-inspect.js +8 -1
- package/src/mcp/tools/toolchain.js +55 -11
- package/src/mcp/tools/watch-memory.js +145 -27
- package/src/observer/bus.js +73 -0
- package/src/observer/livestream.html +4 -2
- package/src/observer/tool-wrap.js +17 -14
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +64 -17
- package/src/platforms/atari2600/MENTAL_MODEL.md +5 -1
- package/src/platforms/atari2600/TROUBLESHOOTING.md +40 -0
- package/src/platforms/atari7800/MENTAL_MODEL.md +32 -11
- package/src/platforms/atari7800/TROUBLESHOOTING.md +5 -5
- package/src/platforms/c64/MENTAL_MODEL.md +11 -4
- package/src/platforms/c64/TROUBLESHOOTING.md +13 -0
- package/src/platforms/gb/MENTAL_MODEL.md +19 -4
- package/src/platforms/gb/TROUBLESHOOTING.md +101 -6
- package/src/platforms/gb/lib/c/README.md +10 -11
- package/src/platforms/gb/lib/c/gb_crt0.s +27 -3
- package/src/platforms/gb/lib/c/patch-header.js +19 -6
- package/src/platforms/gba/MENTAL_MODEL.md +4 -4
- package/src/platforms/gba/TROUBLESHOOTING.md +3 -3
- package/src/platforms/gba/lib/c/gba_sfx.c +40 -0
- package/src/platforms/gba/lib/c/gba_sfx.h +10 -0
- package/src/platforms/gbc/MENTAL_MODEL.md +16 -4
- package/src/platforms/gbc/TROUBLESHOOTING.md +24 -3
- package/src/platforms/gbc/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gbc/lib/c/README.md +10 -11
- package/src/platforms/gbc/lib/c/font.h +43 -0
- package/src/platforms/gbc/lib/c/gb_crt0.s +26 -3
- package/src/platforms/gbc/lib/c/patch-header.js +19 -6
- package/src/platforms/genesis/MENTAL_MODEL.md +43 -9
- package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
- package/src/platforms/genesis/lib/c/genesis_sfx.c +37 -0
- package/src/platforms/genesis/lib/c/genesis_sfx.h +1 -0
- package/src/platforms/gg/MENTAL_MODEL.md +4 -4
- package/src/platforms/gg/TROUBLESHOOTING.md +14 -18
- package/src/platforms/gg/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gg/lib/c/gg_crt0.s +14 -2
- package/src/platforms/gg/lib/c/joypad_read.c +29 -0
- package/src/platforms/lynx/MENTAL_MODEL.md +1 -1
- package/src/platforms/lynx/TROUBLESHOOTING.md +3 -3
- package/src/platforms/lynx/lib/c/lynx_sfx.c +38 -2
- package/src/platforms/lynx/lib/c/lynx_sfx.h +1 -0
- package/src/platforms/msx/MENTAL_MODEL.md +11 -5
- package/src/platforms/msx/TROUBLESHOOTING.md +21 -0
- package/src/platforms/msx/lib/c/msx_crt0.s +27 -0
- package/src/platforms/msx/lib/c/msx_hw.h +3 -0
- package/src/platforms/msx/lib/c/msx_vdp.c +70 -0
- package/src/platforms/nes/MENTAL_MODEL.md +12 -5
- package/src/platforms/nes/lib/c/nes_runtime.c +190 -34
- package/src/platforms/nes/lib/c/nes_runtime.h +35 -0
- package/src/platforms/pce/MENTAL_MODEL.md +14 -5
- package/src/platforms/pce/TROUBLESHOOTING.md +9 -0
- package/src/platforms/pce/lib/c/pce_hw.h +13 -1
- package/src/platforms/pce/lib/c/pce_sound.c +22 -0
- package/src/platforms/pce/lib/c/pce_video.c +32 -0
- package/src/platforms/sms/MENTAL_MODEL.md +11 -6
- package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
- package/src/platforms/sms/lib/c/sms_crt0.s +14 -2
- package/src/platforms/snes/MENTAL_MODEL.md +7 -2
- package/src/platforms/snes/TROUBLESHOOTING.md +40 -1
- package/src/playtest/playtest.js +73 -3
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +13 -8
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.crt0.s +58 -5
- package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +52 -3
- package/src/toolchains/cc65/presets/pce/rom32k.cfg +52 -0
- package/src/toolchains/index.js +64 -19
|
@@ -102,6 +102,48 @@ export function isPlaytestRunning(sessionKey) {
|
|
|
102
102
|
return reconcileSession(sessionKey);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Human co-drive snapshot for one session: is a playtest window open, and has
|
|
107
|
+
* the HUMAN pressed anything (pad / keyboard / rewind-scrub) within the last
|
|
108
|
+
* ~2 s? Drives catalog({op:'status'}) and the co-drive warning attached to
|
|
109
|
+
* frame/input responses. Cheap, never throws; with no window everything is
|
|
110
|
+
* inactive. Frames are window ticks ≈ frames at 60fps real time.
|
|
111
|
+
* @param {string} sessionKey
|
|
112
|
+
* @returns {{windowOpen: boolean, humanInputActive: boolean, framesSinceHumanInput: number | null}}
|
|
113
|
+
*/
|
|
114
|
+
export function getPlaytestHumanStatus(sessionKey) {
|
|
115
|
+
if (!reconcileSession(sessionKey)) {
|
|
116
|
+
return { windowOpen: false, humanInputActive: false, framesSinceHumanInput: null };
|
|
117
|
+
}
|
|
118
|
+
const s = sessions.get(sessionKey);
|
|
119
|
+
return {
|
|
120
|
+
windowOpen: true,
|
|
121
|
+
humanInputActive: typeof s.humanInputActive === "function" ? !!s.humanInputActive() : false,
|
|
122
|
+
framesSinceHumanInput: typeof s.framesSinceHumanInput === "function" ? s.framesSinceHumanInput() : null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The warning attached to frame({op:'step'/'stepAndShot'}) and input(set/press/
|
|
128
|
+
* sequence/navigate) responses while a human is co-driving this session's
|
|
129
|
+
* playtest window. null when there's no window or the human hasn't pressed
|
|
130
|
+
* recently — so the field only appears when there's a REAL conflict.
|
|
131
|
+
* @param {string} sessionKey
|
|
132
|
+
* @returns {string | null}
|
|
133
|
+
*/
|
|
134
|
+
export function humanCoDriveWarning(sessionKey) {
|
|
135
|
+
const st = getPlaytestHumanStatus(sessionKey);
|
|
136
|
+
if (!st.windowOpen || !st.humanInputActive) return null;
|
|
137
|
+
const ago = st.framesSinceHumanInput != null ? `~${st.framesSinceHumanInput} frames ago` : "moments ago";
|
|
138
|
+
return (
|
|
139
|
+
`A playtest window is open and the HUMAN last pressed buttons ${ago} — you are co-driving the same ` +
|
|
140
|
+
"emulator. While they press, the window's input overwrites yours each tick (the human wins), and its " +
|
|
141
|
+
"real-time 60fps loop races your frame-stepping (non-deterministic results). Either host({op:'pause'}) " +
|
|
142
|
+
"while you inspect (the window keeps rendering, frozen), do deterministic work in a SECOND session " +
|
|
143
|
+
"(a different x-romdev-session header = a fully isolated emulator), or wait for the human to stop."
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
105
147
|
export function registerPlaytestTools(server, z, sessionKey) {
|
|
106
148
|
// op:'open' — open (or reuse) the SDL window for this session.
|
|
107
149
|
async function ptOpen({ scale = 3, title, aspect = "tv" }) {
|
|
@@ -283,8 +325,14 @@ export function registerPlaytestTools(server, z, sessionKey) {
|
|
|
283
325
|
// REPLACED by resetHost() on every runSource/loadMedia. Same object →
|
|
284
326
|
// screenshot() and the window agree. Different object → they've diverged.
|
|
285
327
|
const matches = !!windowHost && activeHost === windowHost;
|
|
328
|
+
const human = getPlaytestHumanStatus(sessionKey);
|
|
286
329
|
return jsonContent({
|
|
287
330
|
running: true,
|
|
331
|
+
// Is the human ACTIVELY playing right now (pressed within ~2 s)? While
|
|
332
|
+
// true, your input/setInput is overwritten each tick and real-time
|
|
333
|
+
// stepping races yours — pause, or use a second session.
|
|
334
|
+
humanInputActive: human.humanInputActive,
|
|
335
|
+
...(human.framesSinceHumanInput != null ? { framesSinceHumanInput: human.framesSinceHumanInput } : {}),
|
|
288
336
|
// What the HUMAN is looking at (the window's own host):
|
|
289
337
|
windowMediaPath: windowHost?.status?.mediaPath ?? null,
|
|
290
338
|
windowFrameCount: windowHost?.status?.frameCount ?? session.frameCount,
|
|
@@ -354,10 +402,14 @@ export function registerPlaytestTools(server, z, sessionKey) {
|
|
|
354
402
|
"eyes (boots, renders, the feature is visible) — a window on a black screen/crash just wastes their attention. " +
|
|
355
403
|
"BEST FOR diagnosing a USER-REPORTED bug: hand them the window, let them drive to the exact moment, then " +
|
|
356
404
|
"inspect the SAME live host in real time (memory/watch/sprites/state). Every other tool keeps working against " +
|
|
357
|
-
"that live host while the window is open. FOOTGUN — the window's loop
|
|
358
|
-
"
|
|
359
|
-
"
|
|
360
|
-
"
|
|
405
|
+
"that live host while the window is open. FOOTGUN — the window's loop steps the core in REAL TIME, and while " +
|
|
406
|
+
"the human is pressing (pad/keyboard) it writes their input each tick, overwriting yours — the human wins. " +
|
|
407
|
+
"(When the human is idle the window leaves your input({op:'set'}) alone, but its 60fps stepping still races " +
|
|
408
|
+
"your frame({op:'step'}).) You'll KNOW: frame/input responses carry `humanCoDriveWarning` while the human " +
|
|
409
|
+
"pressed within ~2s, and catalog({op:'status'})/playtest({op:'status'}) expose `humanInputActive`. To inspect " +
|
|
410
|
+
"a moving state freeze it first: host({op:'pause'}) → read → host({op:'resume'}); for deterministic stepping " +
|
|
411
|
+
"while the human plays, use a SECOND session (different x-romdev-session = fully isolated emulator). " +
|
|
412
|
+
"Requires @kmamal/sdl. `scale`/`title`/`aspect` shape the window.\n" +
|
|
361
413
|
"• op:'stop' — close THIS session's window (the host stays loaded; other agents' windows unaffected).\n" +
|
|
362
414
|
"• op:'status' — is a window open, what ROM/frame it shows, and `activeHostMatchesWindow` (false = a build/" +
|
|
363
415
|
"loadMedia swapped the active host, so frame({op:'screenshot'}) no longer shows what the human sees — use op:'framebuffer').\n" +
|
|
@@ -409,7 +409,10 @@ export async function previewTileArtCore(args) {
|
|
|
409
409
|
|
|
410
410
|
if (outputPath) {
|
|
411
411
|
await writeFile(outputPath, png);
|
|
412
|
-
|
|
412
|
+
// Livestream sideband: the human sees the rendered sheet even though the
|
|
413
|
+
// agent only gets the path.
|
|
414
|
+
return { ...result, outputPath, note: `${png.length} bytes of PNG written to ${outputPath}.`,
|
|
415
|
+
_observerImages: [{ kind: "image", mimeType: "image/png", base64: png.toString("base64") }] };
|
|
413
416
|
}
|
|
414
417
|
return { ...result, pngBase64: png.toString("base64") };
|
|
415
418
|
}
|
|
@@ -467,7 +470,8 @@ async function previewMsxScreen2(args, d) {
|
|
|
467
470
|
};
|
|
468
471
|
if (outputPath) {
|
|
469
472
|
await writeFile(outputPath, buf);
|
|
470
|
-
return { ...result, outputPath
|
|
473
|
+
return { ...result, outputPath,
|
|
474
|
+
_observerImages: [{ kind: "image", mimeType: "image/png", base64: buf.toString("base64") }] };
|
|
471
475
|
}
|
|
472
476
|
return { ...result, pngBase64: buf.toString("base64") };
|
|
473
477
|
}
|