romdevtools 0.26.0 → 0.28.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 (144) hide show
  1. package/AGENTS.md +5 -3
  2. package/CHANGELOG.md +322 -3
  3. package/README.md +1 -1
  4. package/examples/README.md +1 -1
  5. package/examples/atari2600/templates/platformer.asm +18 -9
  6. package/examples/atari2600/templates/racing.asm +25 -4
  7. package/examples/atari2600/templates/shmup.asm +30 -5
  8. package/examples/atari2600/templates/sports.asm +41 -9
  9. package/examples/atari7800/templates/hello_sprite.c +8 -4
  10. package/examples/atari7800/templates/platformer.c +12 -8
  11. package/examples/atari7800/templates/puzzle.c +7 -4
  12. package/examples/atari7800/templates/racing.c +5 -2
  13. package/examples/atari7800/templates/shmup.c +8 -4
  14. package/examples/atari7800/templates/sports.c +6 -3
  15. package/examples/c64/templates/platformer.c +28 -24
  16. package/examples/c64/templates/puzzle.c +77 -16
  17. package/examples/c64/templates/racing.c +9 -0
  18. package/examples/c64/templates/shmup.c +13 -1
  19. package/examples/c64/templates/sports.c +9 -4
  20. package/examples/gb/templates/platformer.c +6 -2
  21. package/examples/gb/templates/puzzle.c +279 -101
  22. package/examples/gb/templates/racing.c +13 -1
  23. package/examples/gb/templates/shmup.c +13 -1
  24. package/examples/gb/templates/sports.c +9 -3
  25. package/examples/gba/templates/platformer.c +7 -13
  26. package/examples/gba/templates/puzzle.c +93 -15
  27. package/examples/gba/templates/racing.c +13 -1
  28. package/examples/gba/templates/shmup.c +13 -1
  29. package/examples/gba/templates/sports.c +17 -5
  30. package/examples/gbc/templates/platformer.c +6 -2
  31. package/examples/gbc/templates/puzzle.c +878 -178
  32. package/examples/gbc/templates/racing.c +13 -1
  33. package/examples/gbc/templates/shmup.c +13 -1
  34. package/examples/gbc/templates/sports.c +9 -3
  35. package/examples/genesis/templates/puzzle.c +76 -15
  36. package/examples/genesis/templates/racing.c +13 -1
  37. package/examples/genesis/templates/shmup_2p.c +13 -1
  38. package/examples/gg/templates/platformer.c +4 -0
  39. package/examples/gg/templates/puzzle.c +80 -14
  40. package/examples/gg/templates/racing.c +17 -1
  41. package/examples/gg/templates/shmup.c +17 -1
  42. package/examples/gg/templates/sports.c +4 -0
  43. package/examples/lynx/templates/platformer.c +25 -6
  44. package/examples/lynx/templates/puzzle.c +77 -14
  45. package/examples/lynx/templates/shmup.c +13 -1
  46. package/examples/lynx/templates/sports.c +5 -2
  47. package/examples/msx/platformer/main.c +2 -0
  48. package/examples/msx/puzzle/main.c +78 -15
  49. package/examples/msx/racing/main.c +1 -0
  50. package/examples/msx/shmup/main.c +1 -0
  51. package/examples/msx/sports/main.c +3 -2
  52. package/examples/nes/templates/platformer.c +11 -3
  53. package/examples/nes/templates/puzzle.c +81 -21
  54. package/examples/nes/templates/racing.c +15 -1
  55. package/examples/nes/templates/shmup.c +1 -0
  56. package/examples/nes/templates/sports.c +1 -0
  57. package/examples/pce/platformer/main.c +3 -1
  58. package/examples/pce/puzzle/main.c +78 -12
  59. package/examples/pce/racing/main.c +1 -0
  60. package/examples/pce/shmup/main.c +5 -4
  61. package/examples/pce/sports/main.c +4 -3
  62. package/examples/sms/templates/platformer.c +4 -0
  63. package/examples/sms/templates/puzzle.c +80 -14
  64. package/examples/sms/templates/racing.c +17 -1
  65. package/examples/sms/templates/shmup.c +17 -1
  66. package/examples/sms/templates/shmup_2p.c +17 -1
  67. package/examples/sms/templates/sports.c +4 -0
  68. package/examples/snes/templates/platformer.c +32 -15
  69. package/examples/snes/templates/puzzle.c +84 -16
  70. package/examples/snes/templates/racing.c +20 -1
  71. package/examples/snes/templates/shmup.c +20 -2
  72. package/examples/snes/templates/sports.c +7 -0
  73. package/package.json +12 -12
  74. package/src/cores/wasm/bluemsx_libretro.js +1 -1
  75. package/src/cores/wasm/bluemsx_libretro.wasm +0 -0
  76. package/src/cores/wasm/fceumm_libretro.js +1 -1
  77. package/src/cores/wasm/fceumm_libretro.wasm +0 -0
  78. package/src/cores/wasm/gambatte_libretro.js +1 -1
  79. package/src/cores/wasm/gambatte_libretro.wasm +0 -0
  80. package/src/cores/wasm/geargrafx_libretro.js +1 -1
  81. package/src/cores/wasm/geargrafx_libretro.wasm +0 -0
  82. package/src/cores/wasm/genesis_plus_gx_libretro.js +1 -1
  83. package/src/cores/wasm/genesis_plus_gx_libretro.wasm +0 -0
  84. package/src/cores/wasm/handy_libretro.js +1 -1
  85. package/src/cores/wasm/handy_libretro.wasm +0 -0
  86. package/src/cores/wasm/mgba_libretro.js +1 -1
  87. package/src/cores/wasm/mgba_libretro.wasm +0 -0
  88. package/src/cores/wasm/prosystem_libretro.js +1 -1
  89. package/src/cores/wasm/prosystem_libretro.wasm +0 -0
  90. package/src/cores/wasm/snes9x_libretro.js +1 -1
  91. package/src/cores/wasm/snes9x_libretro.wasm +0 -0
  92. package/src/cores/wasm/stella2014_libretro.js +1 -1
  93. package/src/cores/wasm/stella2014_libretro.wasm +0 -0
  94. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  95. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  96. package/src/host/LibretroHost.js +245 -10
  97. package/src/mcp/server.js +6 -0
  98. package/src/mcp/tools/disasm-rebuild.js +315 -65
  99. package/src/mcp/tools/disasm.js +149 -28
  100. package/src/mcp/tools/find-references.js +216 -51
  101. package/src/mcp/tools/frame.js +11 -4
  102. package/src/mcp/tools/index.js +15 -1
  103. package/src/mcp/tools/input.js +26 -3
  104. package/src/mcp/tools/memory.js +208 -39
  105. package/src/mcp/tools/playtest.js +56 -4
  106. package/src/mcp/tools/project.js +35 -9
  107. package/src/mcp/tools/toolchain.js +43 -10
  108. package/src/mcp/tools/watch-memory.js +172 -25
  109. package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +64 -17
  110. package/src/platforms/atari2600/MENTAL_MODEL.md +5 -1
  111. package/src/platforms/atari2600/TROUBLESHOOTING.md +40 -0
  112. package/src/platforms/atari7800/MENTAL_MODEL.md +27 -6
  113. package/src/platforms/gb/MENTAL_MODEL.md +16 -1
  114. package/src/platforms/gb/TROUBLESHOOTING.md +42 -0
  115. package/src/platforms/gb/lib/c/patch-header.js +7 -4
  116. package/src/platforms/gbc/MENTAL_MODEL.md +12 -0
  117. package/src/platforms/gbc/TROUBLESHOOTING.md +21 -0
  118. package/src/platforms/gbc/lib/c/font.h +43 -0
  119. package/src/platforms/gbc/lib/c/patch-header.js +7 -4
  120. package/src/platforms/genesis/MENTAL_MODEL.md +40 -6
  121. package/src/platforms/genesis/lib/c/genesis_sfx.c +37 -0
  122. package/src/platforms/genesis/lib/c/genesis_sfx.h +1 -0
  123. package/src/platforms/gg/TROUBLESHOOTING.md +13 -17
  124. package/src/platforms/gg/lib/c/gg_crt0.s +14 -2
  125. package/src/platforms/lynx/lib/c/lynx_sfx.c +38 -2
  126. package/src/platforms/lynx/lib/c/lynx_sfx.h +1 -0
  127. package/src/platforms/msx/MENTAL_MODEL.md +6 -0
  128. package/src/platforms/msx/TROUBLESHOOTING.md +21 -0
  129. package/src/platforms/msx/lib/c/msx_crt0.s +27 -0
  130. package/src/platforms/msx/lib/c/msx_hw.h +2 -0
  131. package/src/platforms/msx/lib/c/msx_vdp.c +45 -0
  132. package/src/platforms/nes/MENTAL_MODEL.md +10 -3
  133. package/src/platforms/nes/lib/c/nes_runtime.c +41 -0
  134. package/src/platforms/nes/lib/c/nes_runtime.h +2 -0
  135. package/src/platforms/pce/MENTAL_MODEL.md +9 -0
  136. package/src/platforms/pce/TROUBLESHOOTING.md +9 -0
  137. package/src/platforms/pce/lib/c/pce_hw.h +2 -1
  138. package/src/platforms/pce/lib/c/pce_sound.c +22 -0
  139. package/src/platforms/sms/MENTAL_MODEL.md +5 -0
  140. package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
  141. package/src/platforms/sms/lib/c/sms_crt0.s +14 -2
  142. package/src/platforms/snes/MENTAL_MODEL.md +5 -0
  143. package/src/playtest/playtest.js +73 -3
  144. package/src/toolchains/index.js +37 -8
@@ -7,6 +7,7 @@
7
7
  */
8
8
  #include "sms_hw.h"
9
9
  #include "sms_sfx.h"
10
+ #include "sms_music.h"
10
11
  #include <stdint.h>
11
12
 
12
13
  extern void sms_vdp_init(void);
@@ -138,27 +139,89 @@ static uint8_t collides(int8_t col, int8_t row) {
138
139
  return 0;
139
140
  }
140
141
 
142
+ /* ── match / clear / gravity core (ported from the GBC reference puzzle).
143
+ * The old scan was horizontal-only AND cleared cells mid-scan, so vertical
144
+ * and diagonal runs never cleared, 4+ runs half-cleared, and nothing ever
145
+ * fell afterwards ("rows don't shift down"). This marks every 3+ run in all
146
+ * 4 directions, clears them, applies per-column gravity, and loops so
147
+ * cascades chain (score scales with chain depth). */
148
+ static uint8_t matched[ROWS][COLS];
149
+ static const int8_t DIRS4[4][2] = { {0,1}, {1,0}, {1,1}, {1,-1} };
150
+
151
+ static uint8_t mark_and_count(void) {
152
+ uint8_t r, c, d, len, k, cnt;
153
+ uint8_t col;
154
+ int8_t dr, dc;
155
+ int sr, sc;
156
+ cnt = 0;
157
+ for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) matched[r][c] = 0;
158
+ for (r = 0; r < ROWS; r++) {
159
+ for (c = 0; c < COLS; c++) {
160
+ col = grid[r][c];
161
+ if (col == 0) continue;
162
+ for (d = 0; d < 4; d++) {
163
+ dr = DIRS4[d][0]; dc = DIRS4[d][1];
164
+ sr = (int)r - dr; sc = (int)c - dc;
165
+ if (sr >= 0 && sr < ROWS && sc >= 0 && sc < COLS
166
+ && grid[sr][sc] == col) continue; /* not the run's start */
167
+ len = 1;
168
+ sr = (int)r + dr; sc = (int)c + dc;
169
+ while (sr >= 0 && sr < ROWS && sc >= 0 && sc < COLS
170
+ && grid[sr][sc] == col) { len++; sr += dr; sc += dc; }
171
+ if (len >= 3) {
172
+ sr = r; sc = c;
173
+ for (k = 0; k < len; k++) {
174
+ if (!matched[sr][sc]) { matched[sr][sc] = 1; cnt++; }
175
+ sr += dr; sc += dc;
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ return cnt;
182
+ }
183
+
184
+ /* collapse each column so survivors rest on the floor (in place: walk
185
+ * from the bottom, copying gems down to a write cursor, then zero above) */
186
+ static void apply_gravity(void) {
187
+ uint8_t c;
188
+ int r, w;
189
+ for (c = 0; c < COLS; c++) {
190
+ w = ROWS - 1;
191
+ for (r = ROWS - 1; r >= 0; r--) {
192
+ if (grid[r][c] != 0) { grid[w][c] = grid[r][c]; w--; }
193
+ }
194
+ for (; w >= 0; w--) grid[w][c] = 0;
195
+ }
196
+ }
197
+
198
+ static void resolve_board(void) {
199
+ uint8_t n, r, c, chain;
200
+ unsigned int amt;
201
+ chain = 0;
202
+ while (1) {
203
+ n = mark_and_count();
204
+ if (n == 0) break;
205
+ chain++;
206
+ for (r = 0; r < ROWS; r++)
207
+ for (c = 0; c < COLS; c++)
208
+ if (matched[r][c]) grid[r][c] = 0;
209
+ amt = (unsigned int)n * 10u;
210
+ if (chain > 1) amt = amt * chain;
211
+ if (score < 65500u) score = (uint16_t)(score + amt);
212
+ sfx_tone(0, 200, 10); /* clear chime */
213
+ apply_gravity();
214
+ }
215
+ }
216
+
141
217
  static void lock_piece(void) {
142
218
  uint8_t i;
143
219
  int8_t r;
144
- int8_t c;
145
- uint8_t a, b, d;
146
220
  for (i = 0; i < 3; i++) {
147
221
  r = (int8_t)(piece_y + i);
148
222
  if (r >= 0 && r < ROWS) grid[r][piece_x] = piece[i];
149
223
  }
150
- for (i = 0; i < 3; i++) {
151
- r = (int8_t)(piece_y + i);
152
- if (r < 0 || r >= ROWS) continue;
153
- for (c = 0; c <= COLS - 3; c++) {
154
- a = grid[r][c]; b = grid[r][c + 1]; d = grid[r][c + 2];
155
- if (a != 0 && a == b && b == d) {
156
- grid[r][c] = 0; grid[r][c + 1] = 0; grid[r][c + 2] = 0;
157
- if (score < 65500u) score = (uint16_t)(score + 30);
158
- sfx_tone(0, 200, 10); /* triple-clear chime */
159
- }
160
- }
161
- }
224
+ resolve_board();
162
225
  draw_grid();
163
226
  }
164
227
 
@@ -191,12 +254,15 @@ void main(void) {
191
254
  draw_grid();
192
255
 
193
256
  sfx_init();
257
+ music_init();
258
+ music_play(0); /* continuous background music ("no sound" was the playtest verdict) */
194
259
  sms_vdp_display_on();
195
260
 
196
261
  do {
197
262
  uint8_t pad, fall_rate, t;
198
263
  sms_vblank_wait();
199
264
  sfx_update();
265
+ music_update();
200
266
  draw_piece(1);
201
267
 
202
268
  pad = sms_joypad_read();
@@ -12,6 +12,7 @@
12
12
  */
13
13
  #include "sms_hw.h"
14
14
  #include "sms_sfx.h"
15
+ #include "sms_music.h"
15
16
  #include <stdint.h>
16
17
 
17
18
  extern void sms_vdp_init(void);
@@ -116,11 +117,23 @@ static void reset_run(void) {
116
117
  game_over_timer = 0;
117
118
  }
118
119
 
120
+ /* Galois LFSR (taps $B8), period 255 -- real per-spawn randomness.
121
+ * The old code derived the spawn column from spawn_timer, but the caller
122
+ * resets spawn_timer just before calling here, so it was CONSTANT and
123
+ * every enemy spawned in the same left column/lane. */
124
+ static uint8_t rng_state = 0xA5;
125
+ static uint8_t rand8(void) {
126
+ uint8_t lsb = (uint8_t)(rng_state & 1);
127
+ rng_state >>= 1;
128
+ if (lsb) rng_state ^= 0xB8;
129
+ return rng_state;
130
+ }
131
+
119
132
  static void spawn_obstacle(void) {
120
133
  uint8_t i;
121
134
  for (i = 0; i < MAX_OBSTACLES; i++) {
122
135
  if (!obstacles[i].alive) {
123
- obstacles[i].x = lane_x[(spawn_timer * 13) % 3];
136
+ obstacles[i].x = lane_x[rand8() % 3];
124
137
  obstacles[i].y = 0;
125
138
  obstacles[i].alive = 1;
126
139
  return;
@@ -137,6 +150,8 @@ void main(void) {
137
150
  draw_track();
138
151
  sms_sprite_init();
139
152
  sfx_init();
153
+ music_init();
154
+ music_play(0); /* continuous background music ("no sound" was the playtest verdict) */
140
155
  sms_vdp_display_on();
141
156
 
142
157
  reset_run();
@@ -148,6 +163,7 @@ void main(void) {
148
163
  int16_t step;
149
164
  sms_vblank_wait();
150
165
  sfx_update();
166
+ music_update();
151
167
 
152
168
  /* Stage SAT. */
153
169
  slot = 0;
@@ -8,6 +8,7 @@
8
8
  */
9
9
  #include "sms_hw.h"
10
10
  #include "sms_sfx.h"
11
+ #include "sms_music.h"
11
12
  #include <stdint.h>
12
13
 
13
14
  extern void sms_vdp_init(void);
@@ -119,11 +120,23 @@ static void fire(void) {
119
120
  }
120
121
  }
121
122
 
123
+ /* Galois LFSR (taps $B8), period 255 -- real per-spawn randomness.
124
+ * The old code derived the spawn column from spawn_timer, but the caller
125
+ * resets spawn_timer just before calling here, so it was CONSTANT and
126
+ * every enemy spawned in the same left column/lane. */
127
+ static uint8_t rng_state = 0xA5;
128
+ static uint8_t rand8(void) {
129
+ uint8_t lsb = (uint8_t)(rng_state & 1);
130
+ rng_state >>= 1;
131
+ if (lsb) rng_state ^= 0xB8;
132
+ return rng_state;
133
+ }
134
+
122
135
  static void spawn(void) {
123
136
  uint8_t i;
124
137
  for (i = 0; i < MAX_ENEMIES; i++) {
125
138
  if (!enemies[i].alive) {
126
- enemies[i].x = (uint8_t)(((spawn_timer * 37) & 0xFF) % (256 - 16) + 8);
139
+ enemies[i].x = (uint8_t)(rand8() % (256 - 16) + 8);
127
140
  enemies[i].y = 0;
128
141
  enemies[i].alive = 1;
129
142
  return;
@@ -151,6 +164,8 @@ void main(void) {
151
164
 
152
165
  sms_sprite_init();
153
166
  sfx_init();
167
+ music_init();
168
+ music_play(0); /* continuous background music ("no sound" was the playtest verdict) */
154
169
  sms_vdp_display_on();
155
170
 
156
171
  do {
@@ -158,6 +173,7 @@ void main(void) {
158
173
  uint8_t i, j;
159
174
  sms_vblank_wait();
160
175
  sfx_update();
176
+ music_update();
161
177
 
162
178
  /* Stage SAT for the new frame. */
163
179
  sms_sprite_set(0, player.x, player.y, T_SHIP);
@@ -18,6 +18,7 @@
18
18
  */
19
19
  #include "sms_hw.h"
20
20
  #include "sms_sfx.h"
21
+ #include "sms_music.h"
21
22
  #include <stdint.h>
22
23
 
23
24
  extern void sms_vdp_init(void);
@@ -116,11 +117,23 @@ static void fire(Obj *ship, Obj *pool) {
116
117
  }
117
118
  }
118
119
 
120
+ /* Galois LFSR (taps $B8), period 255 -- real per-spawn randomness.
121
+ * The old code derived the spawn column from spawn_timer, but the caller
122
+ * resets spawn_timer just before calling here, so it was CONSTANT and
123
+ * every enemy spawned in the same left column/lane. */
124
+ static uint8_t rng_state = 0xA5;
125
+ static uint8_t rand8(void) {
126
+ uint8_t lsb = (uint8_t)(rng_state & 1);
127
+ rng_state >>= 1;
128
+ if (lsb) rng_state ^= 0xB8;
129
+ return rng_state;
130
+ }
131
+
119
132
  static void spawn(void) {
120
133
  uint8_t i;
121
134
  for (i = 0; i < MAX_ENEMIES; i++) {
122
135
  if (!enemies[i].alive) {
123
- enemies[i].x = (uint8_t)(((spawn_timer * 37) & 0xFF) % (256 - 16) + 8);
136
+ enemies[i].x = (uint8_t)(rand8() % (256 - 16) + 8);
124
137
  enemies[i].y = 0;
125
138
  enemies[i].alive = 1;
126
139
  return;
@@ -149,6 +162,8 @@ void main(void) {
149
162
 
150
163
  sms_sprite_init();
151
164
  sfx_init();
165
+ music_init();
166
+ music_play(0); /* continuous background music ("no sound" was the playtest verdict) */
152
167
  sms_vdp_display_on();
153
168
 
154
169
  do {
@@ -156,6 +171,7 @@ void main(void) {
156
171
  uint8_t i, j;
157
172
  sms_vblank_wait();
158
173
  sfx_update();
174
+ music_update();
159
175
 
160
176
  /* Stage SAT first. */
161
177
  sms_sprite_set(0, p1.x, p1.y, T_SHIP_P1);
@@ -14,6 +14,7 @@
14
14
  */
15
15
  #include "sms_hw.h"
16
16
  #include "sms_sfx.h"
17
+ #include "sms_music.h"
17
18
  #include <stdint.h>
18
19
 
19
20
  extern void sms_vdp_init(void);
@@ -116,6 +117,8 @@ void main(void) {
116
117
  draw_court();
117
118
  sms_sprite_init();
118
119
  sfx_init();
120
+ music_init();
121
+ music_play(0); /* continuous background music ("no sound" was the playtest verdict) */
119
122
  sms_vdp_display_on();
120
123
 
121
124
  reset_match();
@@ -126,6 +129,7 @@ void main(void) {
126
129
  int16_t target;
127
130
  sms_vblank_wait();
128
131
  sfx_update();
132
+ music_update();
129
133
 
130
134
  /* Stage SAT first — uploaded at vblank. */
131
135
  slot = 0;
@@ -35,20 +35,21 @@ static u16 bg_map[32 * 32];
35
35
 
36
36
  typedef struct { s16 x, y, w, h; } Rect;
37
37
 
38
- #define WORLD_W 512
38
+ #define WORLD_W 256 /* = the 32x32 console map width, so platforms are drawable */
39
39
  #define SCREEN_W 256
40
40
 
41
- /* Platforms in WORLD coords, spread across the 512-px world. */
41
+ /* Platforms in WORLD coords. The world is 256 px exactly the 32x32
42
+ * console map — so every platform can be DRAWN on the text layer and
43
+ * scrolls 1:1 with the physics. (The old 512-px world only existed as
44
+ * invisible collision rects: the 32-col map can't show cols 32-63, so
45
+ * the player appeared to stand and jump on nothing.) */
42
46
  static const Rect platforms[] = {
43
- { 0, 200, 512, 24 }, /* floor spans the world */
44
- { 30, 160, 56, 8 },
45
- { 110, 140, 64, 8 },
46
- { 190, 110, 48, 8 },
47
- { 60, 100, 32, 8 },
48
- { 300, 150, 64, 8 },
49
- { 400, 120, 56, 8 },
50
- { 360, 84, 48, 8 },
51
- { 460, 168, 48, 8 },
47
+ { 0, 200, 256, 24 }, /* floor spans the world */
48
+ { 24, 168, 56, 8 },
49
+ { 104, 144, 64, 8 },
50
+ { 184, 112, 48, 8 },
51
+ { 48, 96, 40, 8 },
52
+ { 208, 72, 40, 8 },
52
53
  };
53
54
  #define N_PLATFORMS (sizeof(platforms) / sizeof(platforms[0]))
54
55
 
@@ -93,6 +94,13 @@ int main(void) {
93
94
  * palette in block 0 (HUD text stays legible). */
94
95
  bgInitTileSet(1, (u8 *)&tilbg, (u8 *)&palbg, 1,
95
96
  32, 32, BG_16COLORS, 0x2000);
97
+
98
+ /* Per-genre backdrop tint — every SNES scaffold used to ship the same
99
+ * blue checkered wallpaper ('no variety'). Recolor the wallpaper's
100
+ * CGRAM entries (block 1 = entries 16+) to a sky blue scheme. */
101
+ setPaletteColor(0, RGB5(10,18,28));
102
+ setPaletteColor(17, RGB5(13,21,29));
103
+ setPaletteColor(18, RGB5(8,15,26));
96
104
  for (i = 0; i < 32 * 32; i++) bg_map[i] = 0x0400;
97
105
  bgInitMapSet(1, (u8 *)bg_map, sizeof(bg_map), SC_32x32, 0x4000);
98
106
  bgSetEnable(1);
@@ -103,10 +111,19 @@ int main(void) {
103
111
  consoleDrawText(2, 1, "D-PAD MOVE A JUMP");
104
112
  /* Column markers across the BG so the hardware scroll is visible as
105
113
  * you move (the console map is 32 cells / 256 px and wraps). */
106
- consoleDrawText( 1, 12, "|0");
107
- consoleDrawText( 9, 12, "|64");
108
- consoleDrawText(17, 12, "|128");
109
- consoleDrawText(25, 12, "|192");
114
+ /* Draw every platform as a row of '=' on the (scrolling) text layer
115
+ * so the player can SEE what they're standing on. */
116
+ {
117
+ char buf[33];
118
+ u16 pi, k, cols;
119
+ for (pi = 0; pi < N_PLATFORMS; pi++) {
120
+ cols = platforms[pi].w / 8;
121
+ if (cols > 32) cols = 32;
122
+ for (k = 0; k < cols; k++) buf[k] = '=';
123
+ buf[cols] = 0;
124
+ consoleDrawText(platforms[pi].x / 8, platforms[pi].y / 8, buf);
125
+ }
126
+ }
110
127
 
111
128
  oamSet(0, 32, 100, 3, 0, 0, 0, 0);
112
129
  /* Screen ON first, THEN sound. sfx_init() must run AFTER setScreenOn()
@@ -103,28 +103,89 @@ static u8 collides(s16 col, s16 row) {
103
103
  return 0;
104
104
  }
105
105
 
106
+ /* ── match / clear / gravity core (ported from the GBC reference puzzle).
107
+ * The old scan was horizontal-only AND cleared cells mid-scan, so vertical
108
+ * and diagonal runs never cleared, 4+ runs half-cleared, and nothing ever
109
+ * fell afterwards ("rows don't shift down"). This marks every 3+ run in all
110
+ * 4 directions, clears them, applies per-column gravity, and loops so
111
+ * cascades chain (score scales with chain depth). */
112
+ static u8 matched[ROWS][COLS];
113
+ static const s8 DIRS4[4][2] = { {0,1}, {1,0}, {1,1}, {1,-1} };
114
+
115
+ static u8 mark_and_count(void) {
116
+ u8 r, c, d, len, k, cnt;
117
+ u8 col;
118
+ s8 dr, dc;
119
+ int sr, sc;
120
+ cnt = 0;
121
+ for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) matched[r][c] = 0;
122
+ for (r = 0; r < ROWS; r++) {
123
+ for (c = 0; c < COLS; c++) {
124
+ col = grid[r][c];
125
+ if (col == 0) continue;
126
+ for (d = 0; d < 4; d++) {
127
+ dr = DIRS4[d][0]; dc = DIRS4[d][1];
128
+ sr = (int)r - dr; sc = (int)c - dc;
129
+ if (sr >= 0 && sr < ROWS && sc >= 0 && sc < COLS
130
+ && grid[sr][sc] == col) continue; /* not the run's start */
131
+ len = 1;
132
+ sr = (int)r + dr; sc = (int)c + dc;
133
+ while (sr >= 0 && sr < ROWS && sc >= 0 && sc < COLS
134
+ && grid[sr][sc] == col) { len++; sr += dr; sc += dc; }
135
+ if (len >= 3) {
136
+ sr = r; sc = c;
137
+ for (k = 0; k < len; k++) {
138
+ if (!matched[sr][sc]) { matched[sr][sc] = 1; cnt++; }
139
+ sr += dr; sc += dc;
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ return cnt;
146
+ }
147
+
148
+ /* collapse each column so survivors rest on the floor (in place: walk
149
+ * from the bottom, copying gems down to a write cursor, then zero above) */
150
+ static void apply_gravity(void) {
151
+ u8 c;
152
+ int r, w;
153
+ for (c = 0; c < COLS; c++) {
154
+ w = ROWS - 1;
155
+ for (r = ROWS - 1; r >= 0; r--) {
156
+ if (grid[r][c] != 0) { grid[w][c] = grid[r][c]; w--; }
157
+ }
158
+ for (; w >= 0; w--) grid[w][c] = 0;
159
+ }
160
+ }
161
+
162
+ static void resolve_board(void) {
163
+ u8 n, r, c, chain;
164
+ unsigned int amt;
165
+ chain = 0;
166
+ while (1) {
167
+ n = mark_and_count();
168
+ if (n == 0) break;
169
+ chain++;
170
+ for (r = 0; r < ROWS; r++)
171
+ for (c = 0; c < COLS; c++)
172
+ if (matched[r][c]) grid[r][c] = 0;
173
+ amt = (unsigned int)n * 10u;
174
+ if (chain > 1) amt = amt * chain;
175
+ if (score < 65500) score += amt;
176
+ sfx_play(2); /* clear chime */
177
+ apply_gravity();
178
+ }
179
+ }
180
+
106
181
  static void lock_piece(void) {
107
182
  u16 i;
108
- s16 r, c;
109
- u8 a, b, d;
183
+ s16 r;
110
184
  for (i = 0; i < 3; i++) {
111
185
  r = piece_y + i;
112
186
  if (r >= 0 && r < ROWS) grid[r][piece_x] = piece[i];
113
187
  }
114
- for (i = 0; i < 3; i++) {
115
- r = piece_y + i;
116
- if (r < 0 || r >= ROWS) continue;
117
- for (c = 0; c <= COLS - 3; c++) {
118
- a = grid[r][c]; b = grid[r][c + 1]; d = grid[r][c + 2];
119
- if (a != 0 && a == b && b == d) {
120
- grid[r][c] = 0;
121
- grid[r][c + 1] = 0;
122
- grid[r][c + 2] = 0;
123
- if (score < 65500) score += 30;
124
- sfx_play(2); /* triple-clear chime */
125
- }
126
- }
127
- }
188
+ resolve_board();
128
189
  draw_grid();
129
190
  }
130
191
 
@@ -161,6 +222,13 @@ int main(void) {
161
222
  * palette in block 0 (HUD/grid text stays legible). */
162
223
  bgInitTileSet(1, (u8 *)&tilbg, (u8 *)&palbg, 1,
163
224
  32, 32, BG_16COLORS, 0x2000);
225
+
226
+ /* Per-genre backdrop tint — every SNES scaffold used to ship the same
227
+ * blue checkered wallpaper ('no variety'). Recolor the wallpaper's
228
+ * CGRAM entries (block 1 = entries 16+) to a deep violet scheme. */
229
+ setPaletteColor(0, RGB5(4,2,8));
230
+ setPaletteColor(17, RGB5(9,5,15));
231
+ setPaletteColor(18, RGB5(6,3,11));
164
232
  for (i = 0; i < 32 * 32; i++) bg_map[i] = 0x0400;
165
233
  bgInitMapSet(1, (u8 *)bg_map, sizeof(bg_map), SC_32x32, 0x4000);
166
234
  bgSetEnable(1);
@@ -61,11 +61,23 @@ static void reset_run(void) {
61
61
  game_over_timer = 0;
62
62
  }
63
63
 
64
+ /* Galois LFSR (taps $B8), period 255 -- real per-spawn randomness.
65
+ * The old code derived the spawn column from spawn_timer, but the caller
66
+ * resets spawn_timer just before calling here, so it was CONSTANT and
67
+ * every enemy spawned in the same left column/lane. */
68
+ static u8 rng_state = 0xA5;
69
+ static u8 rand8(void) {
70
+ u8 lsb = (u8)(rng_state & 1);
71
+ rng_state >>= 1;
72
+ if (lsb) rng_state ^= 0xB8;
73
+ return rng_state;
74
+ }
75
+
64
76
  static void spawn_obstacle(void) {
65
77
  u8 i;
66
78
  for (i = 0; i < MAX_OBSTACLES; i++) {
67
79
  if (!obstacles[i].alive) {
68
- obstacles[i].x = lane_x[(spawn_timer * 13) % 3];
80
+ obstacles[i].x = lane_x[rand8() % 3];
69
81
  obstacles[i].y = 0;
70
82
  obstacles[i].alive = 1;
71
83
  return;
@@ -128,6 +140,13 @@ int main(void) {
128
140
  * palette in block 0 (HUD/road text stays legible). */
129
141
  bgInitTileSet(1, (u8 *)&tilbg, (u8 *)&palbg, 1,
130
142
  32, 32, BG_16COLORS, 0x2000);
143
+
144
+ /* Per-genre backdrop tint — every SNES scaffold used to ship the same
145
+ * blue checkered wallpaper ('no variety'). Recolor the wallpaper's
146
+ * CGRAM entries (block 1 = entries 16+) to a asphalt grey scheme. */
147
+ setPaletteColor(0, RGB5(4,4,5));
148
+ setPaletteColor(17, RGB5(8,8,9));
149
+ setPaletteColor(18, RGB5(6,6,7));
131
150
  for (bi = 0; bi < 32 * 32; bi++) bg_map[bi] = 0x0400;
132
151
  bgInitMapSet(1, (u8 *)bg_map, sizeof(bg_map), SC_32x32, 0x4000);
133
152
  bgSetEnable(1);
@@ -100,11 +100,23 @@ static void stage_frame(void) {
100
100
  }
101
101
  }
102
102
 
103
+ /* Galois LFSR (taps $B8), period 255 -- real per-spawn randomness.
104
+ * The old code derived the spawn column from spawn_timer, but the caller
105
+ * resets spawn_timer just before calling here, so it was CONSTANT and
106
+ * every enemy spawned in the same left column/lane. */
107
+ static u8 rng_state = 0xA5;
108
+ static u8 rand8(void) {
109
+ u8 lsb = (u8)(rng_state & 1);
110
+ rng_state >>= 1;
111
+ if (lsb) rng_state ^= 0xB8;
112
+ return rng_state;
113
+ }
114
+
103
115
  static void spawn(void) {
104
116
  u16 i;
105
117
  for (i = 0; i < MAX_ENEMIES; i++) {
106
118
  if (!enemies[i].alive) {
107
- enemies[i].x = ((spawn_timer * 37) & 0xFF) % (256 - 16) + 4;
119
+ enemies[i].x = rand8() % (256 - 16) + 4;
108
120
  enemies[i].y = 0;
109
121
  enemies[i].alive = 1;
110
122
  return;
@@ -137,6 +149,13 @@ int main(void) {
137
149
  bgInitTileSet(1, (u8 *)&tilbg, (u8 *)&palbg,
138
150
  1, /* load palbg into CGRAM palette block 1 */
139
151
  32, 32, BG_16COLORS, 0x2000);
152
+
153
+ /* Per-genre backdrop tint — every SNES scaffold used to ship the same
154
+ * blue checkered wallpaper ('no variety'). Recolor the wallpaper's
155
+ * CGRAM entries (block 1 = entries 16+) to a near-black space scheme. */
156
+ setPaletteColor(0, RGB5(0,0,3));
157
+ setPaletteColor(17, RGB5(3,3,8));
158
+ setPaletteColor(18, RGB5(1,1,5));
140
159
  /* Every map entry: tile 0, palette block 1 (bits 10-12 = 1 → 0x0400),
141
160
  * so the wallpaper uses palbg and leaves the console font palette
142
161
  * (block 0) untouched — HUD text stays white/legible. */
@@ -145,7 +164,6 @@ int main(void) {
145
164
  bgSetEnable(1);
146
165
 
147
166
  bgSetDisable(2);
148
- setPaletteColor(0, RGB5(0, 0, 6)); /* dark-blue backdrop (CGRAM 0) */
149
167
 
150
168
  /* 3 sprite tiles (ship/bullet/enemy) × 32 bytes = 96 bytes. */
151
169
  oamInitGfxSet(&tilsprite, 96, &palsprite, 32, 0,
@@ -109,6 +109,13 @@ int main(void) {
109
109
  * palette in block 0 (HUD/court text stays legible). */
110
110
  bgInitTileSet(1, (u8 *)&tilbg, (u8 *)&palbg, 1,
111
111
  32, 32, BG_16COLORS, 0x2000);
112
+
113
+ /* Per-genre backdrop tint — every SNES scaffold used to ship the same
114
+ * blue checkered wallpaper ('no variety'). Recolor the wallpaper's
115
+ * CGRAM entries (block 1 = entries 16+) to a court green scheme. */
116
+ setPaletteColor(0, RGB5(2,9,4));
117
+ setPaletteColor(17, RGB5(5,14,7));
118
+ setPaletteColor(18, RGB5(3,11,5));
112
119
  for (i = 0; i < 32 * 32; i++) bg_map[i] = 0x0400;
113
120
  bgInitMapSet(1, (u8 *)bg_map, sizeof(bg_map), SC_32x32, 0x4000);
114
121
  bgSetEnable(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "romdevtools",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "Tool server giving coding agents full control of homebrew ROM development AND reverse-engineering/romhacking across 14 retro platforms (NES, SNES, GB, Genesis, Atari, C64, PC Engine, MSX, ...) 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",
@@ -50,19 +50,19 @@
50
50
  "fuse.js": "^7.4.1",
51
51
  "omggif": "^1.0.10",
52
52
  "pngjs": "^7.0.0",
53
- "romdev-core-bluemsx": "0.4.0",
54
- "romdev-core-fceumm": "0.8.0",
55
- "romdev-core-gambatte": "0.7.0",
56
- "romdev-core-geargrafx": "0.5.0",
57
- "romdev-core-gpgx": "0.10.0",
58
- "romdev-core-handy": "0.5.0",
59
- "romdev-core-prosystem": "0.6.0",
60
- "romdev-core-vice": "0.7.0",
53
+ "romdev-core-bluemsx": "0.5.0",
54
+ "romdev-core-fceumm": "0.9.0",
55
+ "romdev-core-gambatte": "0.8.0",
56
+ "romdev-core-geargrafx": "0.6.0",
57
+ "romdev-core-gpgx": "0.11.0",
58
+ "romdev-core-handy": "0.6.0",
59
+ "romdev-core-prosystem": "0.7.0",
60
+ "romdev-core-vice": "0.8.0",
61
61
  "romdev-famitone": "0.1.0",
62
62
  "romdev-maxmod": "0.1.0",
63
- "romdev-platform-atari2600": "0.6.0",
64
- "romdev-platform-gba": "0.6.0",
65
- "romdev-platform-snes": "0.6.0",
63
+ "romdev-platform-atari2600": "0.7.0",
64
+ "romdev-platform-gba": "0.7.0",
65
+ "romdev-platform-snes": "0.7.0",
66
66
  "romdev-toolchain-cc65": "0.1.1",
67
67
  "romdev-toolchain-m68k-gcc": "0.2.0",
68
68
  "romdev-toolchain-rgbds": "0.1.0",