romdevtools 0.70.0 → 0.71.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 (33) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/examples/dreamcast/platformer/main.c +31 -0
  3. package/examples/dreamcast/puzzle/main.c +44 -0
  4. package/examples/dreamcast/racing/main.c +39 -0
  5. package/examples/dreamcast/shmup/main.c +50 -0
  6. package/examples/dreamcast/sports/main.c +39 -0
  7. package/package.json +3 -3
  8. package/src/cores/capabilities.js +13 -9
  9. package/src/host/LibretroHost.js +242 -23
  10. package/src/host/callbacks.js +68 -1
  11. package/src/host/cpu-state.js +32 -0
  12. package/src/host/dc-aica-state.js +67 -0
  13. package/src/mcp/tools/audio.js +1 -1
  14. package/src/mcp/tools/disasm.js +1 -1
  15. package/src/mcp/tools/index.js +1 -1
  16. package/src/mcp/tools/platform-docs.js +1 -1
  17. package/src/mcp/tools/platform-tools.js +9 -1
  18. package/src/mcp/tools/toolchain.js +115 -10
  19. package/src/platforms/dreamcast/MENTAL_MODEL.md +87 -0
  20. package/src/platforms/dreamcast/TROUBLESHOOTING.md +55 -0
  21. package/src/platforms/dreamcast/UPSTREAM_SOURCES.md +57 -0
  22. package/src/platforms/n64/MENTAL_MODEL.md +84 -0
  23. package/src/platforms/n64/TROUBLESHOOTING.md +60 -0
  24. package/src/platforms/n64/UPSTREAM_SOURCES.md +52 -0
  25. package/src/platforms/n64/lib/c/n64.c +181 -80
  26. package/src/platforms/ps1/MENTAL_MODEL.md +85 -0
  27. package/src/platforms/ps1/TROUBLESHOOTING.md +55 -0
  28. package/src/platforms/ps1/UPSTREAM_SOURCES.md +54 -0
  29. package/src/platforms/snes/TROUBLESHOOTING.md +10 -0
  30. package/src/toolchains/asar/asar.js +84 -14
  31. package/src/toolchains/mips-c/mips-c.js +35 -1
  32. package/src/toolchains/sh-c/lib/dc.h +65 -15
  33. package/src/toolchains/sh-c/sh-c.js +6 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,65 @@ 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.71.0 — 2026-06-28
8
+
9
+ ### N64 / PS1 / Dreamcast reach full parity: cpuState + audioDebug + GPU-rendering helper libs + 5 examples each
10
+
11
+ The three next-gen 3D platforms are now first-class alongside the original 14.
12
+
13
+ **Live debugging (cpuState + audioDebug).** PS1 (beetle_psx_hw) and Dreamcast (flycast)
14
+ gained the romdev debug-reader exports, so `cpu({op:'read'})` and
15
+ `audioDebug({op:'inspect'})` light up (N64 already had them):
16
+ - **PS1** — `cpu({op:'read'})` returns the live R3000A register file; `audioDebug({chip:'spu'})`
17
+ decodes the SPU's 24 voices + main volume (read from the raw register mirror, not the
18
+ sweep-quantized `SPU_Read`).
19
+ - **Dreamcast** — `cpu({op:'read'})` returns the SH-4 registers (r0–r15, pc, pr, gbr, vbr,
20
+ sr + decoded flags, mac, fpul); `audioDebug({chip:'aica'})` decodes the AICA's 64
21
+ PCM/ADPCM channels + master volume. Full host plumbing + decoders added.
22
+ - The capability manifest now reports cpuState/audioDebug `true` for all three (verified by
23
+ probing real core exports; `breakpoint`/`watch` remain pending on PS1/DC).
24
+
25
+ **GPU-rendering helper libs (no software framebuffers).** Every 3D platform's helper now
26
+ draws through the GPU — a software rasterizer would be black on the GL cores and <1fps:
27
+ - **N64** — the `n64.h` helper was rewritten to emit a **GBI (F3DEX2) display list** that
28
+ glide64 HLEs onto the GPU (clear/rect as fill-rectangles, triangles scan-converted into
29
+ GPU fill-rect spans), instead of poking pixels into RDRAM (which rendered black on
30
+ glide64). No Nintendo microcode shipped — the OSTask CRC-bait trick selects F3DEX2.
31
+ - **PS1** — already correct (GP0 GPU primitives on beetle_psx_hw).
32
+ - **Dreamcast** — `dc.h` now sets **480i interlace** in `dc_video_init` (240p only showed
33
+ the top 240 of 480 lines), and added a Maple-DMA `dc_pad()` / `dc_pressed()`.
34
+ - `#include "n64.h"` / `#include "psx.h"` now auto-bundle (parity with `dc.h`).
35
+
36
+ **5 genre examples per platform.** N64, PS1, and Dreamcast each ship
37
+ shmup / platformer / puzzle / racing / sports examples — all verified building + rendering
38
+ on the GPU.
39
+
40
+ **Docs.** MENTAL_MODEL + TROUBLESHOOTING for N64, PS1, and Dreamcast (the render model,
41
+ boot story, debug surface, and what's N/A and why).
42
+
43
+ ### asar (SNES) build-tool fixes
44
+
45
+ From real commercial-disassembly feedback:
46
+ - **`build({output:'project'|'run'})` honors `options` + a new `defines` map** (e.g. asar
47
+ `--define`) — they were silently dropped.
48
+ - **New `entry` param** so a project whose top file isn't `main.*` (e.g. an existing
49
+ `smw.asm` disassembly) builds without injecting glue.
50
+ - **Subdirectory assets are staged recursively** (a flat `readdir` missed nested
51
+ `incbin`/`incsrc` targets).
52
+ - **A clean asar error is no longer mislabeled** `[worker] Abort in WASM` (it exits errors
53
+ via a C++-exception path; the misleading heap-pointer line is stripped when real
54
+ diagnostics are present).
55
+ - **The LoROM-header bankcross preflight no longer false-positives** on banks $01+ (the
56
+ header is only in bank $00) and honors `check bankcross off`.
57
+ - **A readfile/filesize advisory** warns when a source reads many distinct files that way
58
+ (the asar-WASM resource limit), pointing at the pre-convert-to-`.bin` + `incbin`
59
+ workaround.
60
+
61
+ ### Toolchain
62
+
63
+ - **sh-c (Dreamcast) defaults to `-O1`** — the sh-elf `cc1.wasm` has an `-O2`-only pass
64
+ that aborts on common control flow; `-O1` dodges it. A user-supplied `-O<level>` wins.
65
+
7
66
  ## 0.70.0 — 2026-06-26
8
67
 
9
68
  ### Dreamcast is ship-ready: verified build → run → screenshot, with a renderable example
@@ -0,0 +1,31 @@
1
+ /* Dreamcast PLATFORMER demo — a side-scrolling scene: a hero hops across platforms
2
+ * while the camera scrolls. Self-animating (no input). Renders on the PowerVR2 via
3
+ * the framebuffer path. */
4
+ #include "dc.h"
5
+
6
+ void main(void){
7
+ int frame = 0;
8
+ dc_video_init();
9
+ for (;;){
10
+ int scroll = (frame * 2) % (DC_W);
11
+ int t = frame % 90;
12
+ int jump = t < 45 ? t : (90 - t); /* 0..45..0 hop */
13
+ int hy = DC_H - 140 - jump * 2;
14
+ int hx = DC_W/2 - 16;
15
+
16
+ dc_clear(dc_rgb(80, 150, 230)); /* sky */
17
+ /* parallax hills */
18
+ { int i; for (i = -1; i < 6; i++){ int x = i*140 - (scroll/2); dc_rect(x, DC_H-200, 120, 200, dc_rgb(60, 170, 90)); } }
19
+ /* ground */
20
+ dc_rect(0, DC_H-60, DC_W, 60, dc_rgb(120, 80, 40));
21
+ /* floating platforms (scrolling) */
22
+ { int i; for (i = -1; i < 6; i++){ int x = i*180 - (scroll % 180); dc_rect(x, DC_H-160, 90, 18, dc_rgb(150, 110, 60)); } }
23
+ /* hero (head + body) */
24
+ dc_rect(hx, hy, 32, 32, dc_rgb(240, 90, 70));
25
+ dc_rect(hx+6, hy+6, 6, 6, dc_rgb(255,255,255)); /* eye */
26
+ dc_rect(hx, hy+32, 32, 20, dc_rgb(40, 60, 200)); /* legs */
27
+ /* a coin that bobs */
28
+ dc_rect(DC_W/2 + 120 - (scroll%240), DC_H-220 + (jump), 18, 18, dc_rgb(255, 220, 60));
29
+ frame++;
30
+ }
31
+ }
@@ -0,0 +1,44 @@
1
+ /* Dreamcast PUZZLE demo — a falling-block (Tetris-like) board that fills + clears
2
+ * rows on a loop. Self-animating (no input). Renders on the PowerVR2 framebuffer. */
3
+ #include "dc.h"
4
+
5
+ #define COLS 10
6
+ #define ROWS 16
7
+ #define CELL 24
8
+
9
+ static unsigned char board[ROWS][COLS];
10
+ static unsigned int rng = 0xACE1u;
11
+ static unsigned int rnd(void){ rng ^= rng << 7; rng ^= rng >> 9; rng ^= rng << 8; return rng; }
12
+
13
+ static const u16 PAL[7] = { 0, 0xF800, 0x07E0, 0x001F, 0xFFE0, 0xF81F, 0x07FF };
14
+
15
+ void main(void){
16
+ int frame = 0, px = COLS/2, py = 0, col = 1, i, r, c;
17
+ dc_video_init();
18
+ for (;;){
19
+ int ox = (DC_W - COLS*CELL)/2, oy = (DC_H - ROWS*CELL)/2;
20
+
21
+ if ((frame % 8) == 0){
22
+ /* drop the active block one row; lock + respawn at the bottom */
23
+ if (py+1 >= ROWS || board[py+1][px]){
24
+ board[py][px] = col;
25
+ /* clear full-ish rows occasionally to keep it moving */
26
+ for (r = 0; r < ROWS; r++){ int full=1; for (c=0;c<COLS;c++) if(!board[r][c]) full=0;
27
+ if (full){ for(c=0;c<COLS;c++) board[r][c]=0; } }
28
+ py = 0; px = rnd()%COLS; col = 1 + rnd()%6;
29
+ if (board[0][px]){ for(r=0;r<ROWS;r++)for(c=0;c<COLS;c++) board[r][c]=0; } /* reset if buried */
30
+ } else py++;
31
+ }
32
+ dc_clear(dc_rgb(18, 18, 30));
33
+ /* board frame */
34
+ dc_rect(ox-4, oy-4, COLS*CELL+8, ROWS*CELL+8, dc_rgb(90,90,120));
35
+ dc_rect(ox, oy, COLS*CELL, ROWS*CELL, dc_rgb(10,10,18));
36
+ /* settled cells */
37
+ for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) if (board[r][c])
38
+ dc_rect(ox + c*CELL+1, oy + r*CELL+1, CELL-2, CELL-2, PAL[board[r][c]]);
39
+ /* active block */
40
+ dc_rect(ox + px*CELL+1, oy + py*CELL+1, CELL-2, CELL-2, PAL[col]);
41
+ frame++;
42
+ (void)i;
43
+ }
44
+ }
@@ -0,0 +1,39 @@
1
+ /* Dreamcast RACING demo — a pseudo-3D road that curves + scrolls toward a horizon,
2
+ * with a car at the bottom that drifts side to side. Self-animating (no input).
3
+ * Renders on the PowerVR2 framebuffer. */
4
+ #include "dc.h"
5
+
6
+ /* tiny fixed sine via a 16-entry quarter table */
7
+ static const int SIN16[17] = {0,49,97,142,181,213,236,251,256,251,236,213,181,142,97,49,0};
8
+ static int isin(int a){ a &= 63; if(a<16) return SIN16[a]; if(a<32) return SIN16[32-a]; if(a<48) return -SIN16[a-32]; return -SIN16[64-a]; }
9
+
10
+ void main(void){
11
+ int frame = 0, y;
12
+ dc_video_init();
13
+ for (;;){
14
+ int horizon = DC_H/3;
15
+ dc_clear(dc_rgb(60, 140, 230)); /* sky */
16
+ dc_rect(0, horizon, DC_W, DC_H-horizon, dc_rgb(40, 120, 50)); /* grass */
17
+
18
+ /* road: for each scanline below the horizon, a trapezoid widening to the
19
+ bottom, shifted by a curve that scrolls. */
20
+ for (y = horizon; y < DC_H; y++){
21
+ int depth = y - horizon;
22
+ int half = depth * (DC_W/2 - 20) / (DC_H - horizon) + 12;
23
+ int curve = isin((frame/2 + depth/4)) * depth / 256;
24
+ int cx = DC_W/2 + curve;
25
+ /* road stripe alternation for motion */
26
+ u16 rc = (((depth + frame) / 12) & 1) ? dc_rgb(70,70,75) : dc_rgb(90,90,95);
27
+ dc_rect(cx - half, y, half*2, 1, rc);
28
+ /* center dashes */
29
+ if ((((depth + frame*2)/16) & 1)) dc_rect(cx-2, y, 4, 1, dc_rgb(240,240,120));
30
+ }
31
+ /* the car (drifts L/R) */
32
+ { int carx = DC_W/2 + isin(frame/3) * 60 / 256 - 24;
33
+ dc_rect(carx, DC_H-70, 48, 40, dc_rgb(220, 40, 40));
34
+ dc_rect(carx+6, DC_H-64, 36, 14, dc_rgb(40,40,60)); /* windshield */
35
+ dc_rect(carx-4, DC_H-44, 10, 18, dc_rgb(20,20,20)); /* wheels */
36
+ dc_rect(carx+42, DC_H-44, 10, 18, dc_rgb(20,20,20)); }
37
+ frame++;
38
+ }
39
+ }
@@ -0,0 +1,50 @@
1
+ /* Dreamcast SHMUP demo — a vertical shooter scene rendered on the PowerVR2 via the
2
+ * framebuffer path. The ship weaves, bullets stream up, enemies descend + recycle.
3
+ * Self-animating (no input) — proves program-controlled graphics render on the GPU.
4
+ * (DC input via dc_pad() reads the resting controller state; full press wiring is a
5
+ * follow-up — see dc.h.) */
6
+ #include "dc.h"
7
+
8
+ #define NB 12
9
+ #define NE 6
10
+
11
+ static int frame;
12
+ static unsigned int rng = 0x1234567u;
13
+ static unsigned int rnd(void){ rng ^= rng << 13; rng ^= rng >> 17; rng ^= rng << 5; return rng; }
14
+
15
+ static int bx[NB], by[NB], ba[NB];
16
+ static int ex[NE], ey[NE];
17
+
18
+ void main(void){
19
+ int i;
20
+ dc_video_init();
21
+ for (i = 0; i < NE; i++){ ex[i] = 60 + i*90; ey[i] = -(int)(rnd()%300); }
22
+ for (;;){
23
+ int shipx = DC_W/2 + (int)(80.0 * ((frame % 120) < 60 ? (frame%60) : (60-(frame%60))) / 60.0 - 40);
24
+ int shipy = DC_H - 80;
25
+
26
+ /* spawn a bullet every 8 frames */
27
+ if ((frame & 7) == 0){
28
+ for (i = 0; i < NB; i++) if (!ba[i]){ ba[i]=1; bx[i]=shipx; by[i]=shipy; break; }
29
+ }
30
+ dc_clear(dc_rgb(8, 10, 28));
31
+ /* starfield */
32
+ for (i = 0; i < 24; i++){
33
+ int sx = (int)((rnd()%DC_W));
34
+ int sy = (int)((frame*2 + i*53) % DC_H);
35
+ dc_rect(sx, sy, 2, 2, dc_rgb(120,120,160));
36
+ }
37
+ /* enemies */
38
+ for (i = 0; i < NE; i++){
39
+ ey[i] += 2;
40
+ if (ey[i] > DC_H){ ey[i] = -(int)(rnd()%200); ex[i] = (int)(rnd()%(DC_W-40)); }
41
+ if (ey[i] >= 0) dc_rect(ex[i], ey[i], 36, 28, dc_rgb(220, 60, 60));
42
+ }
43
+ /* bullets */
44
+ for (i = 0; i < NB; i++) if (ba[i]){ by[i] -= 8; if (by[i] < 0) ba[i]=0; else dc_rect(bx[i]-2, by[i], 4, 12, dc_rgb(255,240,80)); }
45
+ /* player ship (a little arrow of rects) */
46
+ dc_rect(shipx-4, shipy, 8, 24, dc_rgb(80, 200, 255));
47
+ dc_rect(shipx-16, shipy+14, 32, 8, dc_rgb(60, 160, 230));
48
+ frame++;
49
+ }
50
+ }
@@ -0,0 +1,39 @@
1
+ /* Dreamcast SPORTS demo — a top-down pong/air-hockey rally: two paddles track a
2
+ * bouncing ball with a scoreboard. Self-animating (the CPU "AI" plays both sides).
3
+ * Renders on the PowerVR2 framebuffer. */
4
+ #include "dc.h"
5
+
6
+ void main(void){
7
+ int frame = 0;
8
+ int bx = DC_W/2, by = DC_H/2, vx = 5, vy = 3;
9
+ int p0 = DC_H/2, p1 = DC_H/2; /* paddle centers (y) */
10
+ int s0 = 0, s1 = 0, i;
11
+ dc_video_init();
12
+ for (;;){
13
+ /* ball physics */
14
+ bx += vx; by += vy;
15
+ if (by < 12 || by > DC_H-12) vy = -vy;
16
+ /* paddles track the ball (simple AI) */
17
+ if (p0 < by) p0 += 4; else p0 -= 4;
18
+ if (p1 < by) p1 += 4; else p1 -= 4;
19
+ /* bounce off paddles / score */
20
+ if (bx < 40){ if (by > p0-50 && by < p0+50) vx = -vx; else { s1++; bx=DC_W/2; by=DC_H/2; vx=5; } }
21
+ if (bx > DC_W-40){ if (by > p1-50 && by < p1+50) vx = -vx; else { s0++; bx=DC_W/2; by=DC_H/2; vx=-5; } }
22
+
23
+ dc_clear(dc_rgb(15, 60, 30)); /* court green */
24
+ /* center line (dashed) */
25
+ for (i = 0; i < DC_H; i += 32) dc_rect(DC_W/2-2, i+8, 4, 16, dc_rgb(200,200,200));
26
+ /* top/bottom rails */
27
+ dc_rect(0, 0, DC_W, 8, dc_rgb(220,220,220));
28
+ dc_rect(0, DC_H-8, DC_W, 8, dc_rgb(220,220,220));
29
+ /* paddles */
30
+ dc_rect(24, p0-50, 14, 100, dc_rgb(80, 160, 255));
31
+ dc_rect(DC_W-38, p1-50, 14, 100, dc_rgb(255, 160, 80));
32
+ /* ball */
33
+ dc_rect(bx-8, by-8, 16, 16, dc_rgb(255, 240, 80));
34
+ /* scoreboard (score as stacked pips) */
35
+ for (i = 0; i < s0 && i < 9; i++) dc_rect(DC_W/2 - 60 - i*10, 16, 8, 8, dc_rgb(80,160,255));
36
+ for (i = 0; i < s1 && i < 9; i++) dc_rect(DC_W/2 + 56 + i*10, 16, 8, 8, dc_rgb(255,160,80));
37
+ frame++; (void)frame;
38
+ }
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "romdevtools",
3
- "version": "0.70.0",
3
+ "version": "0.71.0",
4
4
  "description": "Tool server giving coding agents full control of homebrew ROM development AND reverse-engineering/romhacking across 17 retro platforms (NES, SNES, GB, Genesis, Atari, C64, PC Engine, MSX, PlayStation, N64, Dreamcast, ...) via WASM toolchains + emulator cores. Use over plain HTTP, as an Agent Skill, or as an MCP server.",
5
5
  "type": "module",
6
6
  "main": "src/mcp/server.js",
@@ -58,9 +58,9 @@
58
58
  "romdev-core-geargrafx": "0.7.0",
59
59
  "romdev-core-gpgx": "0.12.0",
60
60
  "romdev-core-handy": "0.7.0",
61
- "romdev-core-flycast": "0.1.0",
61
+ "romdev-core-flycast": "0.1.1",
62
62
  "romdev-core-parallel-n64": "0.1.0",
63
- "romdev-core-beetle-psx-hw": "0.1.0",
63
+ "romdev-core-beetle-psx-hw": "0.1.1",
64
64
  "romdev-core-prosystem": "0.8.0",
65
65
  "romdev-core-vice": "0.9.0",
66
66
  "romdev-famitone": "0.1.0",
@@ -224,14 +224,15 @@ export const CAPABILITIES = {
224
224
  // beetle_psx_hw: the GPU renders on the REAL GPU via the GLES3/WebGL2 hardware
225
225
  // renderer through native-gles (like glide64-N64 + Flycast-DC), with OpenBIOS
226
226
  // EMBEDDED (PCSX-Redux, MIT, region-free) — no Sony firmware to ship, no BIOS file.
227
- // run/screenshot + cheats live; disasm + decompile work (MIPS Capstone + the
228
- // MIPS:LE:32 SLEIGH spec). cpuState/audioDebug are OFF: those need beetle-side
229
- // romdev_mips_regs_get/romdev_spu_get exports (a future core patch the old
230
- // pcsx_rearmed-software build had them, but it has no WebGL2 GPU path). build needs
231
- // a PS1 toolchain (PSn00bSDK, not yet).
227
+ // run/screenshot + cheats + cpuState + audioDebug live; disasm + decompile work
228
+ // (MIPS Capstone + the MIPS:LE:32 SLEIGH spec). cpuState (R3000A GPR_full/BACKED_PC)
229
+ // + audioDebug (SPU $1F801C00 register block) come from beetle-side
230
+ // romdev_mips_regs_get/romdev_spu_get exports patched into cpu.c (see
231
+ // scripts/patches/romdev-snippets/beetle-psx-regsnap.c). build needs a PS1
232
+ // toolchain (PSn00bSDK, not yet).
232
233
  build: true, run: true, screenshot: true,
233
234
  inspectSprites: false, inspectPalette: false, inspectBackground: false,
234
- renderingContext: false, cpuState: false, audioDebug: false,
235
+ renderingContext: false, cpuState: true, audioDebug: true,
235
236
  cart: false, disasm: true, decompile: true,
236
237
  },
237
238
  },
@@ -264,11 +265,14 @@ export const CAPABILITIES = {
264
265
  // code (run + memory introspection), and the PowerVR2 present-path works — flycast
265
266
  // renders to the GL FBO and the host reads it back (verified: a framebuffer-writing
266
267
  // program shows ~727k captured pixels). `build` lands with the sh-elf-gcc WASM
267
- // toolchain. The 3D renderer has no tile/sprite inspectors (N/A by hw).
268
- // disasm/decompile = SH-4 analysis slice (rizin `sh` + Ghidra SuperH4 SLEIGH).
268
+ // toolchain. cpuState (SH-4 Sh4cntx regs) + audioDebug (AICA 64-channel register
269
+ // window) come from romdev_sh4_regs_get/romdev_aica_get patched into the flycast
270
+ // libretro entry (see scripts/patches/romdev-snippets/flycast-debug.c). The 3D
271
+ // renderer has no tile/sprite inspectors (N/A by hw). disasm/decompile = SH-4
272
+ // analysis slice (rizin `sh` + Ghidra SuperH4 SLEIGH).
269
273
  build: true, run: true, screenshot: true,
270
274
  inspectSprites: false, inspectPalette: false, inspectBackground: false,
271
- renderingContext: false, cpuState: false, audioDebug: false,
275
+ renderingContext: false, cpuState: true, audioDebug: true,
272
276
  cart: false, disasm: true, decompile: true,
273
277
  },
274
278
  },