romdevtools 0.71.1 → 0.84.1

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 (105) hide show
  1. package/AGENTS.md +19 -16
  2. package/CHANGELOG.md +184 -0
  3. package/README.md +5 -5
  4. package/examples/README.md +1 -0
  5. package/examples/gametank/templates/gt_draw.h +91 -0
  6. package/examples/gametank/templates/gt_hud.h +89 -0
  7. package/examples/gametank/templates/gt_palette.h +59 -0
  8. package/examples/gametank/templates/gt_sound.h +90 -0
  9. package/examples/gametank/templates/gt_sprites.h +53 -0
  10. package/examples/gametank/templates/platformer.c +318 -0
  11. package/examples/gametank/templates/puzzle.c +295 -0
  12. package/examples/gametank/templates/racing.c +139 -0
  13. package/examples/gametank/templates/shmup.c +277 -0
  14. package/examples/gametank/templates/sports.c +119 -0
  15. package/package.json +24 -22
  16. package/src/cores/capabilities.js +24 -0
  17. package/src/cores/registry.js +7 -1
  18. package/src/host/LibretroHost.js +90 -17
  19. package/src/host/callbacks.js +20 -2
  20. package/src/host/cpu-state.js +17 -0
  21. package/src/host/gametank-acp-state.js +53 -0
  22. package/src/http/skill-doc.js +10 -6
  23. package/src/mcp/tools/audio.js +1 -1
  24. package/src/mcp/tools/cart-parts.js +76 -0
  25. package/src/mcp/tools/lifecycle.js +1 -1
  26. package/src/mcp/tools/platform-tools.js +9 -1
  27. package/src/platforms/gametank/lib/gt/audio/acp_image.bin +0 -0
  28. package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.c +55 -0
  29. package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.h +34 -0
  30. package/src/platforms/gametank/lib/gt/audio/audio_fw.asm +223 -0
  31. package/src/platforms/gametank/lib/gt/audio/audio_fw_blob.s +8 -0
  32. package/src/platforms/gametank/lib/gt/audio/instruments.c +79 -0
  33. package/src/platforms/gametank/lib/gt/audio/instruments.h +25 -0
  34. package/src/platforms/gametank/lib/gt/audio/music.c +370 -0
  35. package/src/platforms/gametank/lib/gt/audio/music.h +35 -0
  36. package/src/platforms/gametank/lib/gt/audio/note_numbers.h +132 -0
  37. package/src/platforms/gametank/lib/gt/audio/scaled_sines.raw +0 -0
  38. package/src/platforms/gametank/lib/gt/audio/sine_256_-63_63.bin +0 -0
  39. package/src/platforms/gametank/lib/gt/banking.c +29 -0
  40. package/src/platforms/gametank/lib/gt/banking.h +8 -0
  41. package/src/platforms/gametank/lib/gt/banking2.s +41 -0
  42. package/src/platforms/gametank/lib/gt/crt0.s +102 -0
  43. package/src/platforms/gametank/lib/gt/draw_logo.s +55 -0
  44. package/src/platforms/gametank/lib/gt/feature/persist/persist.c +103 -0
  45. package/src/platforms/gametank/lib/gt/feature/persist/persist.h +13 -0
  46. package/src/platforms/gametank/lib/gt/feature/random/random.c +40 -0
  47. package/src/platforms/gametank/lib/gt/feature/random/random.h +18 -0
  48. package/src/platforms/gametank/lib/gt/feature/text/text.c +111 -0
  49. package/src/platforms/gametank/lib/gt/feature/text/text.h +25 -0
  50. package/src/platforms/gametank/lib/gt/gametank.c +12 -0
  51. package/src/platforms/gametank/lib/gt/gametank.h +80 -0
  52. package/src/platforms/gametank/lib/gt/gametank_logo.inc +393 -0
  53. package/src/platforms/gametank/lib/gt/gen/assets/assets_index.h +9 -0
  54. package/src/platforms/gametank/lib/gt/gen/assets/sdk_default.h +5 -0
  55. package/src/platforms/gametank/lib/gt/gen/bank_nums.h +11 -0
  56. package/src/platforms/gametank/lib/gt/gen/modules_enabled.h +4 -0
  57. package/src/platforms/gametank/lib/gt/gen/modules_enabled.inc +4 -0
  58. package/src/platforms/gametank/lib/gt/gfx/draw_direct.c +132 -0
  59. package/src/platforms/gametank/lib/gt/gfx/draw_direct.h +75 -0
  60. package/src/platforms/gametank/lib/gt/gfx/draw_queue.c +177 -0
  61. package/src/platforms/gametank/lib/gt/gfx/draw_queue.h +35 -0
  62. package/src/platforms/gametank/lib/gt/gfx/draw_util.s +157 -0
  63. package/src/platforms/gametank/lib/gt/gfx/gfx_sys.c +43 -0
  64. package/src/platforms/gametank/lib/gt/gfx/gfx_sys.h +46 -0
  65. package/src/platforms/gametank/lib/gt/gfx/sprites.c +216 -0
  66. package/src/platforms/gametank/lib/gt/gfx/sprites.h +41 -0
  67. package/src/platforms/gametank/lib/gt/init.c +9 -0
  68. package/src/platforms/gametank/lib/gt/input.c +26 -0
  69. package/src/platforms/gametank/lib/gt/input.h +20 -0
  70. package/src/platforms/gametank/lib/gt/interrupt.s +67 -0
  71. package/src/platforms/gametank/lib/gt/vectors.s +14 -0
  72. package/src/platforms/gametank/lib/gt/wait.s +53 -0
  73. package/src/playtest/playtest.js +174 -26
  74. package/src/toolchains/arm-none-eabi-gcc/gcc.js +39 -188
  75. package/src/toolchains/asar/asar.js +10 -15
  76. package/src/toolchains/cc65/cc65.js +82 -92
  77. package/src/toolchains/cc65/da65.js +12 -17
  78. package/src/toolchains/cc65/preset-resolver.js +13 -2
  79. package/src/toolchains/cc65/presets/gametank/gametank.h +80 -0
  80. package/src/toolchains/cc65/presets/gametank/sdk.cfg +32 -0
  81. package/src/toolchains/cc65/presets/gametank/sdk.crt0.s +61 -0
  82. package/src/toolchains/cc65/presets/gametank/sdk.vectors.s +11 -0
  83. package/src/toolchains/cc65/presets/gametank/single-bank.cfg +28 -0
  84. package/src/toolchains/cc65/presets/gametank/single-bank.crt0.s +71 -0
  85. package/src/toolchains/cc65/presets/gametank/single-bank.vectors.s +12 -0
  86. package/src/toolchains/common/c-build.js +109 -0
  87. package/src/toolchains/common/gcc-toolchain.js +164 -0
  88. package/src/toolchains/common/wasm-tool.js +101 -0
  89. package/src/toolchains/dasm/dasm.js +12 -18
  90. package/src/toolchains/gba-c/gba-c.js +253 -305
  91. package/src/toolchains/genesis-c/genesis-c.js +196 -225
  92. package/src/toolchains/index.js +58 -4
  93. package/src/toolchains/m68k-elf-gcc/gcc.js +39 -202
  94. package/src/toolchains/mips-c/mips-c.js +68 -78
  95. package/src/toolchains/mips-elf-gcc/gcc.js +55 -118
  96. package/src/toolchains/rgbds/rgbds.js +7 -19
  97. package/src/toolchains/sdcc/sdcc.js +35 -26
  98. package/src/toolchains/sh-c/sh-c.js +44 -52
  99. package/src/toolchains/sh-elf-gcc/gcc.js +55 -110
  100. package/src/toolchains/sjasm/sjasm.js +10 -14
  101. package/src/toolchains/snes-c/snes-c.js +125 -150
  102. package/src/toolchains/tcc816/tcc816.js +10 -15
  103. package/src/toolchains/vasm68k/vasm68k.js +11 -16
  104. package/src/toolchains/wladx/wladx.js +5 -17
  105. package/src/toolchains/z80/binutils.js +5 -11
@@ -0,0 +1,277 @@
1
+ /* ── shmup.c — GameTank vertical shooter (complete example game) ─────────────
2
+ *
3
+ * A COMPLETE, working game on the bundled GameTank SDK draw-queue runtime: an
4
+ * arrowhead fighter you fly along the bottom, enemy raiders that fall from the
5
+ * top, gold missiles you fire up, explosions on a kill, a score/lives HUD, SFX,
6
+ * and a title → play → game-over → restart loop. Drawn with REAL pixel-art
7
+ * sprites loaded into graphics RAM at runtime (gt_sprites.h) and blitted with
8
+ * per-pixel color + transparency.
9
+ *
10
+ * FORK THIS. Markers:
11
+ * HARDWARE IDIOM (load-bearing) — the GameTank draws via a blitter DRAW QUEUE
12
+ * (gt_draw.h): enqueue rects/sprites each frame, then gt_present() drains the
13
+ * queue, waits a vblank, and flips the double buffer. Sprites live in GRAM
14
+ * (gt_load_sprite writes the pixels once at init). Don't fire blits by hand.
15
+ * GAME LOGIC (clay) — sprite art, speeds, spawn timing, scoring: tune freely.
16
+ *
17
+ * SCREEN: 128x128. PLAYERS: 1. CONTROLS: D-pad move, A or START to fire/confirm.
18
+ */
19
+ #include "gametank.h"
20
+ #include "draw_queue.h"
21
+ #include "input.h"
22
+ #include "gt_palette.h"
23
+ #include "gt_draw.h"
24
+ #include "gt_sprites.h"
25
+ #include "gt_hud.h"
26
+ #include "gt_sound.h"
27
+
28
+ /* ── palette ── */
29
+ #define C_SPACE GT_DKBLUE
30
+ #define C_HUDBAR GT_NAVY
31
+ #define C_HUD GT_WHITE
32
+ #define C_LIFE GT_GREEN
33
+
34
+ /* ── sprite art (palette-index bytes; 0 = transparent). short aliases for art. ── */
35
+ #define C GT_CYAN
36
+ #define W GT_WHITE
37
+ #define T GT_TEAL
38
+ #define G GT_GOLD
39
+ #define O GT_ORANGE
40
+ #define M GT_MAGENTA
41
+ #define U GT_PURPLE
42
+ #define R GT_RED
43
+ #define Y GT_YELLOW
44
+
45
+ #define SHIP_W 16
46
+ #define SHIP_H 16
47
+ static const unsigned char ART_SHIP[SHIP_W * SHIP_H] = {
48
+ 0,0,0,0,0,0,0,C,C,0,0,0,0,0,0,0,
49
+ 0,0,0,0,0,0,0,C,C,0,0,0,0,0,0,0,
50
+ 0,0,0,0,0,0,C,W,W,C,0,0,0,0,0,0,
51
+ 0,0,0,0,0,0,C,W,W,C,0,0,0,0,0,0,
52
+ 0,0,0,0,0,C,C,W,W,C,C,0,0,0,0,0,
53
+ 0,0,0,0,C,C,T,T,T,T,C,C,0,0,0,0,
54
+ 0,0,0,C,C,T,T,T,T,T,T,C,C,0,0,0,
55
+ 0,0,C,C,T,T,T,W,W,T,T,T,C,C,0,0,
56
+ 0,C,C,T,T,T,T,W,W,T,T,T,T,C,C,0,
57
+ C,C,T,T,T,T,T,T,T,T,T,T,T,T,C,C,
58
+ C,C,C,T,T,T,T,T,T,T,T,T,T,C,C,C,
59
+ C,0,C,C,T,T,T,T,T,T,T,T,C,C,0,C,
60
+ 0,0,0,C,C,0,T,T,T,T,0,C,C,0,0,0,
61
+ 0,0,0,0,0,0,G,G,G,G,0,0,0,0,0,0,
62
+ 0,0,0,0,0,G,O,G,G,O,G,0,0,0,0,0,
63
+ 0,0,0,0,0,0,O,0,0,O,0,0,0,0,0,0,
64
+ };
65
+
66
+ #define ENEMY_W 16
67
+ #define ENEMY_H 16
68
+ static const unsigned char ART_ENEMY[ENEMY_W * ENEMY_H] = {
69
+ 0,0,0,0,0,0,R,R,R,R,0,0,0,0,0,0,
70
+ 0,0,0,0,0,R,R,M,M,R,R,0,0,0,0,0,
71
+ 0,0,0,0,R,R,M,M,M,M,R,R,0,0,0,0,
72
+ 0,0,0,R,R,M,M,U,U,M,M,R,R,0,0,0,
73
+ 0,0,R,R,M,M,U,W,W,U,M,M,R,R,0,0,
74
+ 0,R,R,M,M,U,W,Y,Y,W,U,M,M,R,R,0,
75
+ R,R,M,M,U,W,Y,R,R,Y,W,U,M,M,R,R,
76
+ R,M,M,U,U,W,Y,R,R,Y,W,U,U,M,M,R,
77
+ R,M,M,U,W,W,Y,Y,Y,Y,W,W,U,M,M,R,
78
+ 0,R,M,M,U,U,W,W,W,W,U,U,M,M,R,0,
79
+ 0,R,R,M,M,M,U,U,U,U,M,M,M,R,R,0,
80
+ 0,0,R,R,M,M,M,M,M,M,M,M,R,R,0,0,
81
+ 0,0,0,R,R,R,M,M,M,M,R,R,R,0,0,0,
82
+ 0,0,R,R,0,R,R,M,M,R,R,0,R,R,0,0,
83
+ 0,R,R,0,0,0,R,R,R,R,0,0,0,R,R,0,
84
+ R,R,0,0,0,0,0,R,R,0,0,0,0,0,R,R,
85
+ };
86
+
87
+ #define SHOT_W 6
88
+ #define SHOT_H 10
89
+ static const unsigned char ART_SHOT[SHOT_W * SHOT_H] = {
90
+ 0,0,W,W,0,0,
91
+ 0,G,W,W,G,0,
92
+ 0,G,W,W,G,0,
93
+ G,G,W,W,G,G,
94
+ G,G,W,W,G,G,
95
+ G,G,G,G,G,G,
96
+ 0,G,G,G,G,0,
97
+ 0,O,O,O,O,0,
98
+ 0,0,O,O,0,0,
99
+ 0,0,Y,Y,0,0,
100
+ };
101
+
102
+ #define BOOM_W 16
103
+ #define BOOM_H 16
104
+ static const unsigned char ART_BOOM[BOOM_W * BOOM_H] = {
105
+ 0,0,0,Y,0,0,0,R,R,0,0,0,Y,0,0,0,
106
+ 0,0,0,0,O,0,Y,O,O,Y,0,O,0,0,0,0,
107
+ 0,Y,0,0,0,O,O,Y,Y,O,O,0,0,0,Y,0,
108
+ 0,0,0,O,O,Y,Y,W,W,Y,Y,O,O,0,0,0,
109
+ Y,0,O,O,Y,W,W,W,W,W,W,Y,O,O,0,Y,
110
+ 0,0,O,Y,W,W,Y,O,O,Y,W,W,Y,O,0,0,
111
+ 0,O,Y,W,Y,O,O,R,R,O,O,Y,W,Y,O,0,
112
+ R,O,O,W,O,O,R,R,R,R,O,O,W,O,O,R,
113
+ R,O,O,W,O,O,R,R,R,R,O,O,W,O,O,R,
114
+ 0,O,Y,W,Y,O,O,R,R,O,O,Y,W,Y,O,0,
115
+ 0,0,O,Y,W,W,Y,O,O,Y,W,W,Y,O,0,0,
116
+ Y,0,O,O,Y,W,W,W,W,W,W,Y,O,O,0,Y,
117
+ 0,0,0,O,O,Y,Y,W,W,Y,Y,O,O,0,0,0,
118
+ 0,Y,0,0,0,O,O,Y,Y,O,O,0,0,0,Y,0,
119
+ 0,0,0,0,O,0,Y,O,O,Y,0,O,0,0,0,0,
120
+ 0,0,0,Y,0,0,0,R,R,0,0,0,Y,0,0,0,
121
+ };
122
+
123
+ #undef C
124
+ #undef W
125
+ #undef T
126
+ #undef G
127
+ #undef O
128
+ #undef M
129
+ #undef U
130
+ #undef R
131
+ #undef Y
132
+
133
+ /* Each sprite gets its own GRAM row band (gy step 16) so a 16-wide sprite can't
134
+ * read into the next one's columns. */
135
+ static GtSprite SPR_SHIP, SPR_ENEMY, SPR_SHOT, SPR_BOOM;
136
+ static void load_art(void) {
137
+ SPR_SHIP.gx = 0; SPR_SHIP.gy = 0; SPR_SHIP.w = SHIP_W; SPR_SHIP.h = SHIP_H;
138
+ SPR_ENEMY.gx = 0; SPR_ENEMY.gy = 16; SPR_ENEMY.w = ENEMY_W; SPR_ENEMY.h = ENEMY_H;
139
+ SPR_SHOT.gx = 0; SPR_SHOT.gy = 32; SPR_SHOT.w = SHOT_W; SPR_SHOT.h = SHOT_H;
140
+ SPR_BOOM.gx = 0; SPR_BOOM.gy = 48; SPR_BOOM.w = BOOM_W; SPR_BOOM.h = BOOM_H;
141
+ gt_load_sprite(ART_SHIP, SPR_SHIP.gx, SPR_SHIP.gy, SHIP_W, SHIP_H);
142
+ gt_load_sprite(ART_ENEMY, SPR_ENEMY.gx, SPR_ENEMY.gy, ENEMY_W, ENEMY_H);
143
+ gt_load_sprite(ART_SHOT, SPR_SHOT.gx, SPR_SHOT.gy, SHOT_W, SHOT_H);
144
+ gt_load_sprite(ART_BOOM, SPR_BOOM.gx, SPR_BOOM.gy, BOOM_W, BOOM_H);
145
+ }
146
+
147
+ /* ── game state ── */
148
+ #define N_ENEMY 4
149
+ #define N_SHOT 3
150
+ #define N_BOOM 4
151
+
152
+ static unsigned char ship_x;
153
+ static unsigned char ex[N_ENEMY], ey[N_ENEMY], e_on[N_ENEMY]; /* enemies: x, y, active */
154
+ static unsigned char sx[N_SHOT], sy[N_SHOT], s_on[N_SHOT]; /* shots: x, y, active */
155
+ static unsigned char bx[N_BOOM], by[N_BOOM], bt[N_BOOM]; /* explosions: x, y, timer */
156
+ static unsigned char fire_cool, spawn_t;
157
+ static unsigned int score;
158
+ static unsigned char lives;
159
+
160
+ static void reset_game(void) {
161
+ unsigned char i;
162
+ ship_x = 56; score = 0; lives = 3; fire_cool = 0; spawn_t = 0;
163
+ for (i = 0; i < N_ENEMY; i++) e_on[i] = 0;
164
+ for (i = 0; i < N_SHOT; i++) s_on[i] = 0;
165
+ for (i = 0; i < N_BOOM; i++) bt[i] = 0;
166
+ }
167
+
168
+ /* spawn an explosion at (x,y). */
169
+ static void make_boom(unsigned char x, unsigned char y) {
170
+ unsigned char i;
171
+ for (i = 0; i < N_BOOM; i++) {
172
+ if (!bt[i]) { bx[i] = x; by[i] = y; bt[i] = 10; return; }
173
+ }
174
+ }
175
+
176
+ /* spawn one enemy at the top in a free slot. */
177
+ static void spawn_enemy(void) {
178
+ unsigned char i;
179
+ for (i = 0; i < N_ENEMY; i++) {
180
+ if (!e_on[i]) {
181
+ ex[i] = 8 + (unsigned char)(rnd8() % 100);
182
+ ey[i] = 0;
183
+ e_on[i] = 1;
184
+ return;
185
+ }
186
+ }
187
+ }
188
+
189
+ /* boxes-overlap test (sprites are ~16 wide). */
190
+ static unsigned char near16(unsigned char ax, unsigned char ay, unsigned char bx, unsigned char by) {
191
+ unsigned char dx = ax > bx ? ax - bx : bx - ax;
192
+ unsigned char dy = ay > by ? ay - by : by - ay;
193
+ return dx < 14 && dy < 14;
194
+ }
195
+
196
+ void main(void) {
197
+ unsigned char i, j;
198
+ load_art();
199
+ reset_game();
200
+
201
+ for (;;) {
202
+ /* ── input ── */
203
+ if ((player1_buttons & INPUT_MASK_LEFT) && ship_x > 2) ship_x -= 2;
204
+ if ((player1_buttons & INPUT_MASK_RIGHT) && ship_x < 110) ship_x += 2;
205
+ if (fire_cool) fire_cool--;
206
+ if ((player1_buttons & (INPUT_MASK_A | INPUT_MASK_START)) && !fire_cool) {
207
+ for (i = 0; i < N_SHOT; i++) {
208
+ if (!s_on[i]) { s_on[i] = 1; sx[i] = ship_x + 5; sy[i] = 100; fire_cool = 10; gt_sfx(GT_SFX_SHOOT); break; }
209
+ }
210
+ }
211
+
212
+ /* ── shots move up ── */
213
+ for (i = 0; i < N_SHOT; i++) {
214
+ if (s_on[i]) {
215
+ if (sy[i] < 6) s_on[i] = 0;
216
+ else sy[i] -= 6;
217
+ }
218
+ }
219
+
220
+ /* ── spawn enemies on a timer ── */
221
+ spawn_t++;
222
+ if (spawn_t >= 48) { spawn_t = 0; spawn_enemy(); }
223
+
224
+ /* ── enemies fall ── */
225
+ for (i = 0; i < N_ENEMY; i++) {
226
+ if (e_on[i]) {
227
+ ey[i]++;
228
+ if (ey[i] > 120) e_on[i] = 0; /* off the bottom: just despawn */
229
+ }
230
+ }
231
+
232
+ /* ── shot vs enemy ── */
233
+ for (i = 0; i < N_ENEMY; i++) {
234
+ if (e_on[i]) {
235
+ for (j = 0; j < N_SHOT; j++) {
236
+ if (s_on[j] && near16(ex[i] + 8, ey[i] + 8, sx[j] + 3, sy[j] + 4)) {
237
+ make_boom(ex[i], ey[i]);
238
+ e_on[i] = 0; s_on[j] = 0; score += 10; gt_sfx(GT_SFX_EXPLODE);
239
+ break;
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ /* ── enemy vs ship ── */
246
+ for (i = 0; i < N_ENEMY; i++) {
247
+ if (e_on[i] && near16(ex[i] + 8, ey[i] + 8, ship_x + 8, 116)) {
248
+ make_boom(ex[i], ey[i]);
249
+ e_on[i] = 0;
250
+ gt_sfx(GT_SFX_EXPLODE);
251
+ if (lives) lives--;
252
+ }
253
+ }
254
+
255
+ /* ── age explosions ── */
256
+ for (i = 0; i < N_BOOM; i++) if (bt[i]) bt[i]--;
257
+
258
+ /* ── draw (scalar copies: indexing inside the gt_blit macro is fragile in cc65) ── */
259
+ gt_clear(C_SPACE);
260
+ for (i = 0; i < N_ENEMY; i++) if (e_on[i]) { unsigned char dx = ex[i], dy = ey[i]; gt_blit(dx, dy, SPR_ENEMY); }
261
+ for (i = 0; i < N_SHOT; i++) if (s_on[i]) { unsigned char dx = sx[i], dy = sy[i]; gt_blit(dx, dy, SPR_SHOT); }
262
+ for (i = 0; i < N_BOOM; i++) if (bt[i]) { unsigned char dx = bx[i], dy = by[i]; gt_blit(dx, dy, SPR_BOOM); }
263
+ gt_blit(ship_x, 108, SPR_SHIP);
264
+ /* HUD: bar in the play area (y>=7), text inside it. */
265
+ queue_draw_box(1, 7, 126, 7, C_HUDBAR);
266
+ hud_number(score, 30, 8, 2, C_HUD);
267
+ hud_pips(lives, 96, 8, 4, C_LIFE);
268
+ /* Re-clean the top BORDER (rows 0-6) LAST. Blitting sprites leaves a stray light
269
+ * line on the framebuffer's top scanline; only the border-clear path can rewrite
270
+ * rows 0-6, and doing it AFTER the sprite blits removes the flickering 1px seam. */
271
+ queue_clear_border(C_HUDBAR);
272
+ gt_present();
273
+ gt_sfx_tick();
274
+
275
+ update_inputs();
276
+ }
277
+ }
@@ -0,0 +1,119 @@
1
+ /* ── sports.c — GameTank 2-player paddle game (complete example) ─────────────
2
+ *
3
+ * A COMPLETE, working game on the bundled GameTank SDK draw-queue runtime — and
4
+ * a genuine 2-PLAYER one (the GameTank has two gamepad ports, unlike the
5
+ * handhelds). Two paddles, a bouncing ball that speeds up on each hit, first to
6
+ * 7 wins. Title → play → match-point → restart. The framebuffer makes a clean
7
+ * paddle game trivial — paddles + ball are just rects redrawn each frame.
8
+ *
9
+ * FORK THIS. Markers:
10
+ * HARDWARE IDIOM (load-bearing) — paddles/ball/court = rects redrawn each
11
+ * frame; the ball's sub-pixel motion is integer x/y with a velocity. No
12
+ * sprites, no flicker.
13
+ * GAME LOGIC (clay) — paddle speed, ball speed-up, win score: tune freely.
14
+ *
15
+ * SFX (gt_sound.h, ACP synth): a blip on wall bounces, a "pong" on paddle hits,
16
+ * and a ding on each point scored. Box-drawn HUD (gt_hud.h).
17
+ * SCREEN: 128x128. PLAYERS: 2 (P1 left pad = $2008, P2 right pad = $2009).
18
+ */
19
+ #include "gametank.h"
20
+ #include "draw_queue.h"
21
+ #include "input.h"
22
+ #include "gt_palette.h"
23
+ #include "gt_draw.h"
24
+ #include "gt_hud.h"
25
+ #include "gt_sound.h"
26
+
27
+ #define C_COURT GT_DKGRN /* court: dark green */
28
+ #define C_NET GT_WHITE
29
+ #define C_P1 GT_CYAN /* left paddle */
30
+ #define C_P2 GT_GOLD /* right paddle */
31
+ #define C_BALL GT_WHITE
32
+ #define C_HUD GT_WHITE
33
+
34
+ #define PAD_H 26
35
+ #define PAD_W 5
36
+ #define WIN 7
37
+
38
+ static unsigned char p1y, p2y; /* paddle tops */
39
+ static signed char bx, by; /* ball pos (signed for clamp math) */
40
+ static signed char vx, vy; /* ball velocity */
41
+ static unsigned char s1, s2;
42
+
43
+ static void serve(unsigned char toLeft) {
44
+ bx = 62; by = 60;
45
+ vx = toLeft ? -2 : 2; vy = (by & 1) ? 1 : -1;
46
+ }
47
+ static void reset_game(void) { p1y = 51; p2y = 51; s1 = 0; s2 = 0; serve(1); }
48
+
49
+ static unsigned char title_or_win(unsigned char win) { /* win: 0 title, 1 P1, 2 P2 */
50
+ gt_start_reset();
51
+ while (1) {
52
+ gt_clear(C_COURT);
53
+ if (!win) {
54
+ gt_rect(28, 36, 72, 10, C_BALL);
55
+ gt_rect(20, 64, PAD_W, PAD_H, C_P1);
56
+ gt_rect(103, 64, PAD_W, PAD_H, C_P2);
57
+ gt_rect(62, 72, 5, 5, C_BALL);
58
+ } else {
59
+ gt_rect(30, 52, 68, 24, C_NET);
60
+ hud_digit(win, 60, 56, 4, win == 1 ? C_P1 : C_P2); /* "1" or "2" wins */
61
+ }
62
+ gt_present(); gt_music_tick();
63
+ update_inputs();
64
+ if (gt_start_pressed()) return 1;
65
+ }
66
+ }
67
+
68
+ void main(void) {
69
+ unsigned char i;
70
+ reset_game();
71
+
72
+ for (;;) {
73
+ title_or_win(0);
74
+ reset_game();
75
+
76
+ for (;;) {
77
+ update_inputs();
78
+
79
+ /* P1 (left pad) + P2 (right pad) — update_inputs fills player1/2_buttons */
80
+ if ((player1_buttons & INPUT_MASK_UP) && p1y > 2) p1y -= 3;
81
+ if ((player1_buttons & INPUT_MASK_DOWN) && p1y < 127 - PAD_H) p1y += 3;
82
+ if ((player2_buttons & INPUT_MASK_UP) && p2y > 2) p2y -= 3;
83
+ if ((player2_buttons & INPUT_MASK_DOWN) && p2y < 127 - PAD_H) p2y += 3;
84
+
85
+ /* ball move */
86
+ bx += vx; by += vy;
87
+ if (by <= 2) { by = 2; vy = -vy; gt_sfx(GT_SFX_SHOOT); } /* wall blip */
88
+ if (by >= 122) { by = 122; vy = -vy; gt_sfx(GT_SFX_SHOOT); }
89
+
90
+ /* paddle collisions (speed up on hit) — the satisfying "pong" */
91
+ if (vx < 0 && bx <= 25 && bx >= 18 && by + 5 >= (signed char)p1y && by <= (signed char)(p1y + PAD_H)) {
92
+ bx = 25; vx = (signed char)(-vx + 1); vy += (signed char)((by - (signed char)(p1y + PAD_H / 2)) >> 3);
93
+ gt_sfx(GT_SFX_HIT);
94
+ }
95
+ if (vx > 0 && bx >= 98 && bx <= 105 && by + 5 >= (signed char)p2y && by <= (signed char)(p2y + PAD_H)) {
96
+ bx = 98; vx = (signed char)(-vx - 1); vy += (signed char)((by - (signed char)(p2y + PAD_H / 2)) >> 3);
97
+ gt_sfx(GT_SFX_HIT);
98
+ }
99
+ if (vx > 4) vx = 4; if (vx < -4) vx = -4;
100
+
101
+ /* score — a clear ding for the point */
102
+ if (bx < 0) { s2++; gt_sfx(GT_SFX_COIN); if (s2 >= WIN) goto win2; serve(0); }
103
+ if (bx > 126) { s1++; gt_sfx(GT_SFX_COIN); if (s1 >= WIN) goto win1; serve(1); }
104
+
105
+ /* ── HARDWARE IDIOM: court + net + paddles + ball, all rects ── */
106
+ gt_clear(C_COURT);
107
+ for (i = 4; i < 124; i += 10) gt_rect(63, i, 2, 5, C_NET); /* dashed net */
108
+ gt_rect(20, p1y, PAD_W, PAD_H, C_P1);
109
+ gt_rect(103, p2y, PAD_W, PAD_H, C_P2);
110
+ gt_rect((unsigned char)bx, (unsigned char)by, 5, 5, C_BALL);
111
+ hud_number(s1, 44, 4, 2, C_P1);
112
+ hud_number(s2, 84, 4, 2, C_P2);
113
+
114
+ gt_present(); gt_music_tick();
115
+ }
116
+ win1: title_or_win(1); continue;
117
+ win2: title_or_win(2); continue;
118
+ }
119
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "romdevtools",
3
- "version": "0.71.1",
3
+ "version": "0.84.1",
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",
@@ -52,29 +52,31 @@
52
52
  "pngjs": "^7.0.0",
53
53
  "romdev-analysis": "0.1.0",
54
54
  "romdev-analysis-decompiler": "0.1.0",
55
- "romdev-core-bluemsx": "0.6.0",
56
- "romdev-core-fceumm": "0.10.0",
57
- "romdev-core-gambatte": "0.9.0",
58
- "romdev-core-geargrafx": "0.7.0",
59
- "romdev-core-gpgx": "0.12.0",
60
- "romdev-core-handy": "0.7.0",
61
- "romdev-core-flycast": "0.1.1",
62
- "romdev-core-parallel-n64": "0.1.0",
63
- "romdev-core-beetle-psx-hw": "0.1.1",
64
- "romdev-core-prosystem": "0.8.0",
65
- "romdev-core-vice": "0.9.0",
55
+ "romdev-audio-resampler": "0.1.0",
56
+ "romdev-core-beetle-psx-hw": "0.2.0",
57
+ "romdev-core-bluemsx": "0.7.0",
58
+ "romdev-core-fceumm": "0.11.0",
59
+ "romdev-core-flycast": "0.2.0",
60
+ "romdev-core-gambatte": "0.10.0",
61
+ "romdev-core-gametank": "0.1.0",
62
+ "romdev-core-geargrafx": "0.8.0",
63
+ "romdev-core-gpgx": "0.13.0",
64
+ "romdev-core-handy": "0.8.0",
65
+ "romdev-core-parallel-n64": "0.2.0",
66
+ "romdev-core-prosystem": "0.9.0",
67
+ "romdev-core-vice": "0.10.0",
66
68
  "romdev-famitone": "0.1.0",
67
69
  "romdev-maxmod": "0.1.0",
68
- "romdev-platform-atari2600": "0.8.0",
69
- "romdev-platform-gba": "0.8.0",
70
- "romdev-platform-snes": "0.8.0",
71
- "romdev-toolchain-cc65": "0.1.1",
72
- "romdev-toolchain-m68k-gcc": "0.2.0",
73
- "romdev-toolchain-mips-gcc": "0.1.0",
74
- "romdev-toolchain-rgbds": "0.1.0",
75
- "romdev-toolchain-sdcc": "0.2.0",
76
- "romdev-toolchain-sh-gcc": "0.1.0",
77
- "romdev-toolchain-vasm": "0.1.0",
70
+ "romdev-platform-atari2600": "0.9.0",
71
+ "romdev-platform-gba": "0.9.0",
72
+ "romdev-platform-snes": "0.9.0",
73
+ "romdev-toolchain-cc65": "0.1.2",
74
+ "romdev-toolchain-m68k-gcc": "0.2.1",
75
+ "romdev-toolchain-mips-gcc": "0.1.1",
76
+ "romdev-toolchain-rgbds": "0.1.1",
77
+ "romdev-toolchain-sdcc": "0.2.1",
78
+ "romdev-toolchain-sh-gcc": "0.1.1",
79
+ "romdev-toolchain-vasm": "0.1.1",
78
80
  "romdev-xgm2": "0.1.0",
79
81
  "romdev_game_codes": "0.1.0",
80
82
  "socket.io": "^4.8.3",
@@ -174,6 +174,30 @@ export const CAPABILITIES = {
174
174
  cart: false, disasm: true, decompile: true,
175
175
  },
176
176
  },
177
+ gametank: {
178
+ // Clyde Shaffer's open W65C02S console: a 128x128 framebuffer drawn by a
179
+ // hardware blitter (no tilemap / no fixed OAM), + a second 65C02 audio
180
+ // coprocessor. Closest cousin is the Lynx (also 65C02 + blitter). The core
181
+ // is patched with the romdev_* debug hooks (6502 regsnap + MemoryWrite/Read
182
+ // watchpoints + the mos6502 dispatch freeze) — so cpuState + write/read
183
+ // watchpoints + pc-break + watchdog + coverage are LIVE, alongside build/run/
184
+ // screenshot/disasm/decompile. inspectSprites is N/A (the blitter has no OAM,
185
+ // like Dreamcast); inspectBackground N/A (framebuffer, not a tilemap).
186
+ cpuFamily: "6502", decompileQuality: "rough",
187
+ cpus: { main: "6502", secondary: ["acp-65c02"] }, // ACP = the audio coprocessor (2nd 65C02)
188
+ // audioDebug(chip:'acp') reports the ACP's STATE (DAC output, IRQ/sample rate,
189
+ // run/mute, audio-CPU PC) via the core's romdev_acp_get export — it's a second
190
+ // 65C02 driving a DAC, not a fixed-register synth.
191
+ audioChips: ["acp"],
192
+ memoryRegions: [...GENERIC_REGIONS],
193
+ renderingKind: "framebuffer", introspection: "shallow",
194
+ ops: {
195
+ build: true, run: true, screenshot: true,
196
+ inspectSprites: false, inspectPalette: true, inspectBackground: false,
197
+ renderingContext: true, cpuState: true, audioDebug: true,
198
+ cart: true, disasm: true, decompile: true,
199
+ },
200
+ },
177
201
  pce: {
178
202
  cpuFamily: "huc6280", decompileQuality: "medium",
179
203
  cpus: { main: "", secondary: [] }, // getCPUState main NOT wired for pce
@@ -53,6 +53,7 @@ export const CORES = {
53
53
  c64: { platform: "c64", coreName: "vice_x64", pkg: "romdev-core-vice", displayName: "Commodore 64 (VICE x64)" },
54
54
  pce: { platform: "pce", coreName: "geargrafx", pkg: "romdev-core-geargrafx", displayName: "PC Engine / TurboGrafx-16 (Geargrafx)", aka: "turbografx,tg16,pcengine" },
55
55
  msx: { platform: "msx", coreName: "bluemsx", pkg: "romdev-core-bluemsx", displayName: "MSX / MSX2 (blueMSX)", aka: "msx2" },
56
+ gametank: { platform: "gametank", coreName: "gametank", pkg: "romdev-core-gametank", displayName: "GameTank (Clyde Shaffer)", aka: "gtr" },
56
57
  // 32-bit MIPS tier. These cores HW-render (GL): the host lazy-loads the OPTIONAL
57
58
  // webgl-node bridge only when one of these boots (hwRender:true). The other 14 are
58
59
  // software-rendered and never touch GL, so a headless user without the GPU module
@@ -69,7 +70,12 @@ export const CORES = {
69
70
  // Flycast = full Dreamcast emulator, GLES3/WebGL2 HW-render (PowerVR2 is GPU-first,
70
71
  // no software framebuffer path) → driven through the native-gles/webgl-node bridge
71
72
  // like the GL N64 build. HLE BIOS (reios) on by default — no firmware to ship.
72
- dreamcast: { platform: "dreamcast", coreName: "flycast", pkg: "romdev-core-flycast", displayName: "Sega Dreamcast (Flycast)", aka: "dc", hwRender: true },
73
+ // noderawfs: the flycast WASM is built with -s NODERAWFS=1, so its filesystem IS
74
+ // Node's real fs — libchdr fopens/seeks the disc image off DISK on demand instead
75
+ // of the host loading the whole (up to ~1GB) CHD into the WASM heap (which OOM'd a
76
+ // 1GB max-heap on big discs like Sonic Adventure). The host passes the REAL path
77
+ // and skips the malloc+FS.writeFile for these cores. See LibretroHost.loadMedia.
78
+ dreamcast: { platform: "dreamcast", coreName: "flycast", pkg: "romdev-core-flycast", displayName: "Sega Dreamcast (Flycast)", aka: "dc", hwRender: true, noderawfs: true },
73
79
  };
74
80
 
75
81
  /** Try to get {jsPath,wasmPath} for a core from its binary package. */