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.
- package/AGENTS.md +5 -3
- package/CHANGELOG.md +322 -3
- package/README.md +1 -1
- package/examples/README.md +1 -1
- package/examples/atari2600/templates/platformer.asm +18 -9
- package/examples/atari2600/templates/racing.asm +25 -4
- package/examples/atari2600/templates/shmup.asm +30 -5
- package/examples/atari2600/templates/sports.asm +41 -9
- package/examples/atari7800/templates/hello_sprite.c +8 -4
- package/examples/atari7800/templates/platformer.c +12 -8
- package/examples/atari7800/templates/puzzle.c +7 -4
- package/examples/atari7800/templates/racing.c +5 -2
- package/examples/atari7800/templates/shmup.c +8 -4
- package/examples/atari7800/templates/sports.c +6 -3
- package/examples/c64/templates/platformer.c +28 -24
- package/examples/c64/templates/puzzle.c +77 -16
- package/examples/c64/templates/racing.c +9 -0
- package/examples/c64/templates/shmup.c +13 -1
- package/examples/c64/templates/sports.c +9 -4
- package/examples/gb/templates/platformer.c +6 -2
- package/examples/gb/templates/puzzle.c +279 -101
- package/examples/gb/templates/racing.c +13 -1
- package/examples/gb/templates/shmup.c +13 -1
- package/examples/gb/templates/sports.c +9 -3
- package/examples/gba/templates/platformer.c +7 -13
- package/examples/gba/templates/puzzle.c +93 -15
- package/examples/gba/templates/racing.c +13 -1
- package/examples/gba/templates/shmup.c +13 -1
- package/examples/gba/templates/sports.c +17 -5
- package/examples/gbc/templates/platformer.c +6 -2
- package/examples/gbc/templates/puzzle.c +878 -178
- package/examples/gbc/templates/racing.c +13 -1
- package/examples/gbc/templates/shmup.c +13 -1
- package/examples/gbc/templates/sports.c +9 -3
- package/examples/genesis/templates/puzzle.c +76 -15
- package/examples/genesis/templates/racing.c +13 -1
- package/examples/genesis/templates/shmup_2p.c +13 -1
- package/examples/gg/templates/platformer.c +4 -0
- package/examples/gg/templates/puzzle.c +80 -14
- package/examples/gg/templates/racing.c +17 -1
- package/examples/gg/templates/shmup.c +17 -1
- package/examples/gg/templates/sports.c +4 -0
- package/examples/lynx/templates/platformer.c +25 -6
- package/examples/lynx/templates/puzzle.c +77 -14
- package/examples/lynx/templates/shmup.c +13 -1
- package/examples/lynx/templates/sports.c +5 -2
- package/examples/msx/platformer/main.c +2 -0
- package/examples/msx/puzzle/main.c +78 -15
- package/examples/msx/racing/main.c +1 -0
- package/examples/msx/shmup/main.c +1 -0
- package/examples/msx/sports/main.c +3 -2
- package/examples/nes/templates/platformer.c +11 -3
- package/examples/nes/templates/puzzle.c +81 -21
- package/examples/nes/templates/racing.c +15 -1
- package/examples/nes/templates/shmup.c +1 -0
- package/examples/nes/templates/sports.c +1 -0
- package/examples/pce/platformer/main.c +3 -1
- package/examples/pce/puzzle/main.c +78 -12
- package/examples/pce/racing/main.c +1 -0
- package/examples/pce/shmup/main.c +5 -4
- package/examples/pce/sports/main.c +4 -3
- package/examples/sms/templates/platformer.c +4 -0
- package/examples/sms/templates/puzzle.c +80 -14
- package/examples/sms/templates/racing.c +17 -1
- package/examples/sms/templates/shmup.c +17 -1
- package/examples/sms/templates/shmup_2p.c +17 -1
- package/examples/sms/templates/sports.c +4 -0
- package/examples/snes/templates/platformer.c +32 -15
- package/examples/snes/templates/puzzle.c +84 -16
- package/examples/snes/templates/racing.c +20 -1
- package/examples/snes/templates/shmup.c +20 -2
- package/examples/snes/templates/sports.c +7 -0
- 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 +245 -10
- package/src/mcp/server.js +6 -0
- 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 +11 -4
- package/src/mcp/tools/index.js +15 -1
- package/src/mcp/tools/input.js +26 -3
- package/src/mcp/tools/memory.js +208 -39
- package/src/mcp/tools/playtest.js +56 -4
- package/src/mcp/tools/project.js +35 -9
- package/src/mcp/tools/toolchain.js +43 -10
- package/src/mcp/tools/watch-memory.js +172 -25
- 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 +27 -6
- package/src/platforms/gb/MENTAL_MODEL.md +16 -1
- package/src/platforms/gb/TROUBLESHOOTING.md +42 -0
- package/src/platforms/gb/lib/c/patch-header.js +7 -4
- package/src/platforms/gbc/MENTAL_MODEL.md +12 -0
- package/src/platforms/gbc/TROUBLESHOOTING.md +21 -0
- package/src/platforms/gbc/lib/c/font.h +43 -0
- package/src/platforms/gbc/lib/c/patch-header.js +7 -4
- package/src/platforms/genesis/MENTAL_MODEL.md +40 -6
- 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/TROUBLESHOOTING.md +13 -17
- package/src/platforms/gg/lib/c/gg_crt0.s +14 -2
- 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 +6 -0
- 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 +2 -0
- package/src/platforms/msx/lib/c/msx_vdp.c +45 -0
- package/src/platforms/nes/MENTAL_MODEL.md +10 -3
- package/src/platforms/nes/lib/c/nes_runtime.c +41 -0
- package/src/platforms/nes/lib/c/nes_runtime.h +2 -0
- package/src/platforms/pce/MENTAL_MODEL.md +9 -0
- package/src/platforms/pce/TROUBLESHOOTING.md +9 -0
- package/src/platforms/pce/lib/c/pce_hw.h +2 -1
- package/src/platforms/pce/lib/c/pce_sound.c +22 -0
- package/src/platforms/sms/MENTAL_MODEL.md +5 -0
- 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 +5 -0
- package/src/playtest/playtest.js +73 -3
- 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
|
-
|
|
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[(
|
|
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)((
|
|
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)((
|
|
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
|
|
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
|
|
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,
|
|
44
|
-
{
|
|
45
|
-
{
|
|
46
|
-
{
|
|
47
|
-
{
|
|
48
|
-
{
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
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
|
-
|
|
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[(
|
|
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 = (
|
|
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.
|
|
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.
|
|
54
|
-
"romdev-core-fceumm": "0.
|
|
55
|
-
"romdev-core-gambatte": "0.
|
|
56
|
-
"romdev-core-geargrafx": "0.
|
|
57
|
-
"romdev-core-gpgx": "0.
|
|
58
|
-
"romdev-core-handy": "0.
|
|
59
|
-
"romdev-core-prosystem": "0.
|
|
60
|
-
"romdev-core-vice": "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.
|
|
64
|
-
"romdev-platform-gba": "0.
|
|
65
|
-
"romdev-platform-snes": "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",
|