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
|
@@ -62,10 +62,13 @@ void main(void) {
|
|
|
62
62
|
tgi_line(70, 40, 70, 60);
|
|
63
63
|
tgi_line(90, 40, 90, 60);
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
/* Playtest: "needs better contrast" — yellow paddles + white ball pop
|
|
66
|
+
* against the green court far better than white-on-lightgreen +
|
|
67
|
+
* yellow-on-green did. */
|
|
68
|
+
tgi_setcolor(COLOR_YELLOW);
|
|
66
69
|
tgi_bar(PADDLE_X1, (unsigned)p1y, PADDLE_X1 + PADDLE_W - 1, (unsigned)(p1y + PADDLE_H - 1));
|
|
67
70
|
tgi_bar(PADDLE_X2, (unsigned)p2y, PADDLE_X2 + PADDLE_W - 1, (unsigned)(p2y + PADDLE_H - 1));
|
|
68
|
-
tgi_setcolor(
|
|
71
|
+
tgi_setcolor(COLOR_WHITE);
|
|
69
72
|
tgi_bar((unsigned)bx, (unsigned)by, (unsigned)(bx + BALL_SIZE - 1), (unsigned)(by + BALL_SIZE - 1));
|
|
70
73
|
tgi_updatedisplay();
|
|
71
74
|
sfx_update();
|
|
@@ -144,27 +144,89 @@ static uint8_t collides(int8_t col, int8_t row) {
|
|
|
144
144
|
return 0;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
/* ── match / clear / gravity core (ported from the GBC reference puzzle).
|
|
148
|
+
* The old scan was horizontal-only AND cleared cells mid-scan, so vertical
|
|
149
|
+
* and diagonal runs never cleared, 4+ runs half-cleared, and nothing ever
|
|
150
|
+
* fell afterwards ("rows don't shift down"). This marks every 3+ run in all
|
|
151
|
+
* 4 directions, clears them, applies per-column gravity, and loops so
|
|
152
|
+
* cascades chain (score scales with chain depth). */
|
|
153
|
+
static uint8_t matched[ROWS][COLS];
|
|
154
|
+
static const int8_t DIRS4[4][2] = { {0,1}, {1,0}, {1,1}, {1,-1} };
|
|
155
|
+
|
|
156
|
+
static uint8_t mark_and_count(void) {
|
|
157
|
+
uint8_t r, c, d, len, k, cnt;
|
|
158
|
+
uint8_t col;
|
|
159
|
+
int8_t dr, dc;
|
|
160
|
+
int sr, sc;
|
|
161
|
+
cnt = 0;
|
|
162
|
+
for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) matched[r][c] = 0;
|
|
163
|
+
for (r = 0; r < ROWS; r++) {
|
|
164
|
+
for (c = 0; c < COLS; c++) {
|
|
165
|
+
col = grid[r][c];
|
|
166
|
+
if (col == 0) continue;
|
|
167
|
+
for (d = 0; d < 4; d++) {
|
|
168
|
+
dr = DIRS4[d][0]; dc = DIRS4[d][1];
|
|
169
|
+
sr = (int)r - dr; sc = (int)c - dc;
|
|
170
|
+
if (sr >= 0 && sr < ROWS && sc >= 0 && sc < COLS
|
|
171
|
+
&& grid[sr][sc] == col) continue; /* not the run's start */
|
|
172
|
+
len = 1;
|
|
173
|
+
sr = (int)r + dr; sc = (int)c + dc;
|
|
174
|
+
while (sr >= 0 && sr < ROWS && sc >= 0 && sc < COLS
|
|
175
|
+
&& grid[sr][sc] == col) { len++; sr += dr; sc += dc; }
|
|
176
|
+
if (len >= 3) {
|
|
177
|
+
sr = r; sc = c;
|
|
178
|
+
for (k = 0; k < len; k++) {
|
|
179
|
+
if (!matched[sr][sc]) { matched[sr][sc] = 1; cnt++; }
|
|
180
|
+
sr += dr; sc += dc;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return cnt;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* collapse each column so survivors rest on the floor (in place: walk
|
|
190
|
+
* from the bottom, copying gems down to a write cursor, then zero above) */
|
|
191
|
+
static void apply_gravity(void) {
|
|
192
|
+
uint8_t c;
|
|
193
|
+
int r, w;
|
|
194
|
+
for (c = 0; c < COLS; c++) {
|
|
195
|
+
w = ROWS - 1;
|
|
196
|
+
for (r = ROWS - 1; r >= 0; r--) {
|
|
197
|
+
if (grid[r][c] != 0) { grid[w][c] = grid[r][c]; w--; }
|
|
198
|
+
}
|
|
199
|
+
for (; w >= 0; w--) grid[w][c] = 0;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static void resolve_board(void) {
|
|
204
|
+
uint8_t n, r, c, chain;
|
|
205
|
+
unsigned int amt;
|
|
206
|
+
chain = 0;
|
|
207
|
+
while (1) {
|
|
208
|
+
n = mark_and_count();
|
|
209
|
+
if (n == 0) break;
|
|
210
|
+
chain++;
|
|
211
|
+
for (r = 0; r < ROWS; r++)
|
|
212
|
+
for (c = 0; c < COLS; c++)
|
|
213
|
+
if (matched[r][c]) grid[r][c] = 0;
|
|
214
|
+
amt = (unsigned int)n * 10u;
|
|
215
|
+
if (chain > 1) amt = amt * chain;
|
|
216
|
+
if (score < 999) score += n;
|
|
217
|
+
msx_psg_tone(0, 0x180, 13); blip = 8; /* clear chime */
|
|
218
|
+
apply_gravity();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
147
222
|
static void lock_piece(void) {
|
|
148
223
|
uint8_t i;
|
|
149
|
-
int8_t r
|
|
150
|
-
uint8_t a, b, d;
|
|
224
|
+
int8_t r;
|
|
151
225
|
for (i = 0; i < 3; i++) {
|
|
152
226
|
r = (int8_t)(piece_y + i);
|
|
153
227
|
if (r >= 0 && r < ROWS) grid[r][piece_x] = piece[i];
|
|
154
228
|
}
|
|
155
|
-
|
|
156
|
-
r = (int8_t)(piece_y + i);
|
|
157
|
-
if (r < 0 || r >= ROWS) continue;
|
|
158
|
-
for (c = 0; c <= COLS - 3; c++) {
|
|
159
|
-
a = grid[r][c]; b = grid[r][c + 1]; d = grid[r][c + 2];
|
|
160
|
-
if (a != 0 && a == b && b == d) {
|
|
161
|
-
grid[r][c] = 0; grid[r][c + 1] = 0; grid[r][c + 2] = 0;
|
|
162
|
-
if (score < 999) score += 3;
|
|
163
|
-
msx_psg_tone(0, 0x180, 13);
|
|
164
|
-
blip = 8;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
229
|
+
resolve_board();
|
|
168
230
|
draw_grid();
|
|
169
231
|
}
|
|
170
232
|
|
|
@@ -202,6 +264,7 @@ void main(void) {
|
|
|
202
264
|
|
|
203
265
|
for (;;) {
|
|
204
266
|
vsync();
|
|
267
|
+
msx_music_tick();
|
|
205
268
|
draw_piece(1);
|
|
206
269
|
|
|
207
270
|
dir = msx_read_joystick(1);
|
|
@@ -43,7 +43,7 @@ static const uint8_t TILE_LINE[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
|
|
|
43
43
|
static const uint8_t TILE_NET[8] = {0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00};
|
|
44
44
|
|
|
45
45
|
/* colour bytes (hi fg, lo bg). 3=green(dark), 12=green(light), 15=white */
|
|
46
|
-
#define COL_FIELD
|
|
46
|
+
#define COL_FIELD 0xC1 /* light-green-on-black field (was 0x21 medium green — muddy per the contrast playtest note) */
|
|
47
47
|
#define COL_LINE 0xF1 /* white line on black */
|
|
48
48
|
#define COL_NET 0xF2 /* white net dashes on dark-green field */
|
|
49
49
|
|
|
@@ -118,6 +118,7 @@ void main(void) {
|
|
|
118
118
|
|
|
119
119
|
for (;;) {
|
|
120
120
|
vsync();
|
|
121
|
+
msx_music_tick();
|
|
121
122
|
|
|
122
123
|
/* push sprites: left paddle (3 cells), right paddle (3), ball */
|
|
123
124
|
slot = 0;
|
|
@@ -125,7 +126,7 @@ void main(void) {
|
|
|
125
126
|
msx_set_sprite(slot++, PADDLE_X1, (uint8_t)(p1y + i * 8), 0, COL_SPR);
|
|
126
127
|
for (i = 0; i < PADDLE_H / 8; i++)
|
|
127
128
|
msx_set_sprite(slot++, PADDLE_X2, (uint8_t)(p2y + i * 8), 0, COL_SPR);
|
|
128
|
-
msx_set_sprite(slot++, (uint8_t)bx, (uint8_t)by, 0,
|
|
129
|
+
msx_set_sprite(slot++, (uint8_t)bx, (uint8_t)by, 0, 11); /* light-yellow ball — distinct from the white paddles (contrast playtest note) */
|
|
129
130
|
|
|
130
131
|
p1 = msx_read_joystick(1);
|
|
131
132
|
p2 = msx_read_joystick(2);
|
|
@@ -82,7 +82,9 @@ static const uint8_t palette[32] = {
|
|
|
82
82
|
* Keep it equal to the BG backdrop (sky blue) or the sky renders as
|
|
83
83
|
* whatever colour-0 you put here, not the BG[0] above. (Sprite colour 0 is
|
|
84
84
|
* transparent regardless, so this never affects how sprites draw.) */
|
|
85
|
-
0x21,
|
|
85
|
+
0x21, 0x16, 0x30, 0x27, /* sp0: player — RED + white/orange trim. (Was light
|
|
86
|
+
* blues — nearly invisible against the sky-blue
|
|
87
|
+
* backdrop. Colour 0 stays the backdrop mirror.) */
|
|
86
88
|
0x0F, 0x18, 0x28, 0x38, /* sp1: platforms — green */
|
|
87
89
|
0x0F, 0x16, 0x06, 0x36,
|
|
88
90
|
0x0F, 0x2A, 0x1A, 0x0A,
|
|
@@ -113,13 +115,18 @@ static uint8_t on_ground = 0;
|
|
|
113
115
|
|
|
114
116
|
#define GRAVITY_Q44 1 /* +1/16 px per frame per frame */
|
|
115
117
|
#define JUMP_VEL_Q44 (-40) /* initial vy → peak ~5 tile jump */
|
|
116
|
-
#define MOVE_SPEED
|
|
118
|
+
#define MOVE_SPEED 2 /* px/frame — 1 read as 'moves slowly' in playtesting */
|
|
117
119
|
|
|
118
120
|
/* AABB: player rect vs platform top edge (treat platform as 8 px tall). */
|
|
119
121
|
static uint8_t landed_on(uint8_t pl_idx, uint8_t player_y) {
|
|
120
122
|
const Platform *p = &platforms[pl_idx];
|
|
121
123
|
uint8_t feet_y = player_y + 7;
|
|
122
|
-
|
|
124
|
+
/* Window starts ONE PIXEL above the top edge: the standing snap puts the
|
|
125
|
+
* feet at p->y - 1, and gravity's sub-pixel trickle doesn't move the
|
|
126
|
+
* integer Y every frame — with the old `feet_y < p->y` cutoff the player
|
|
127
|
+
* "stood" with on_ground=0 most frames, so A-press jumps only registered
|
|
128
|
+
* on lucky frames and the idle/jump sprite flickered every frame. */
|
|
129
|
+
if (feet_y + 1 < p->y || feet_y > p->y + 4) return 0; /* not at top edge */
|
|
123
130
|
if (px + 7 < p->x) return 0;
|
|
124
131
|
if (px > p->x + (p->w << 3) - 1) return 0;
|
|
125
132
|
return 1;
|
|
@@ -190,6 +197,7 @@ void main(void) {
|
|
|
190
197
|
ppu_wait_nmi();
|
|
191
198
|
|
|
192
199
|
/* ── Input ──────────────────────────────────────────────── */
|
|
200
|
+
sound_music_tick();
|
|
193
201
|
pad = pad_poll(0);
|
|
194
202
|
if ((pad & PAD_LEFT) && px > 8) px -= MOVE_SPEED;
|
|
195
203
|
if ((pad & PAD_RIGHT) && px < 240) px += MOVE_SPEED;
|
|
@@ -132,8 +132,86 @@ static void spawn_piece(void) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/* Lock current piece into the grid + check for match-3 horizontals. */
|
|
135
|
+
/* ── match / clear / gravity core (ported from the GBC reference puzzle).
|
|
136
|
+
* The old scan was horizontal-only AND cleared cells mid-scan, so vertical
|
|
137
|
+
* and diagonal runs never cleared, 4+ runs half-cleared, and nothing ever
|
|
138
|
+
* fell afterwards ("rows don't shift down"). This marks every 3+ run in all
|
|
139
|
+
* 4 directions, clears them, applies per-column gravity, and loops so
|
|
140
|
+
* cascades chain (score scales with chain depth). */
|
|
141
|
+
/* matched[] lives at $0500 — OUTSIDE the linker's RAM area ($0300-$04FF,
|
|
142
|
+
* which grid+runtime statics nearly fill; 72 more BSS bytes overflow it).
|
|
143
|
+
* $0500-$07FF is real, unused NES work RAM (hw stack is $0100, shadow OAM
|
|
144
|
+
* $0200), so an absolute pointer there is free. */
|
|
145
|
+
#define matched ((uint8_t (*)[GRID_W])0x0500)
|
|
146
|
+
static const int8_t DIRS4[4][2] = { {0,1}, {1,0}, {1,1}, {1,-1} };
|
|
147
|
+
|
|
148
|
+
static uint8_t mark_and_count(void) {
|
|
149
|
+
uint8_t r, c, d, len, k, cnt;
|
|
150
|
+
uint8_t col;
|
|
151
|
+
int8_t dr, dc;
|
|
152
|
+
int sr, sc;
|
|
153
|
+
cnt = 0;
|
|
154
|
+
for (r = 0; r < GRID_H; r++) for (c = 0; c < GRID_W; c++) matched[r][c] = 0;
|
|
155
|
+
for (r = 0; r < GRID_H; r++) {
|
|
156
|
+
for (c = 0; c < GRID_W; c++) {
|
|
157
|
+
col = grid[r][c];
|
|
158
|
+
if (col == EMPTY) continue;
|
|
159
|
+
for (d = 0; d < 4; d++) {
|
|
160
|
+
dr = DIRS4[d][0]; dc = DIRS4[d][1];
|
|
161
|
+
sr = (int)r - dr; sc = (int)c - dc;
|
|
162
|
+
if (sr >= 0 && sr < GRID_H && sc >= 0 && sc < GRID_W
|
|
163
|
+
&& grid[sr][sc] == col) continue; /* not the run's start */
|
|
164
|
+
len = 1;
|
|
165
|
+
sr = (int)r + dr; sc = (int)c + dc;
|
|
166
|
+
while (sr >= 0 && sr < GRID_H && sc >= 0 && sc < GRID_W
|
|
167
|
+
&& grid[sr][sc] == col) { len++; sr += dr; sc += dc; }
|
|
168
|
+
if (len >= 3) {
|
|
169
|
+
sr = r; sc = c;
|
|
170
|
+
for (k = 0; k < len; k++) {
|
|
171
|
+
if (!matched[sr][sc]) { matched[sr][sc] = 1; cnt++; }
|
|
172
|
+
sr += dr; sc += dc;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return cnt;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* collapse each column so survivors rest on the floor (in place: walk
|
|
182
|
+
* from the bottom, copying gems down to a write cursor, then zero above) */
|
|
183
|
+
static void apply_gravity(void) {
|
|
184
|
+
uint8_t c;
|
|
185
|
+
int r, w;
|
|
186
|
+
for (c = 0; c < GRID_W; c++) {
|
|
187
|
+
w = GRID_H - 1;
|
|
188
|
+
for (r = GRID_H - 1; r >= 0; r--) {
|
|
189
|
+
if (grid[r][c] != EMPTY) { grid[w][c] = grid[r][c]; w--; }
|
|
190
|
+
}
|
|
191
|
+
for (; w >= 0; w--) grid[w][c] = EMPTY;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static void resolve_board(void) {
|
|
196
|
+
uint8_t n, r, c, chain;
|
|
197
|
+
unsigned int amt;
|
|
198
|
+
chain = 0;
|
|
199
|
+
while (1) {
|
|
200
|
+
n = mark_and_count();
|
|
201
|
+
if (n == 0) break;
|
|
202
|
+
chain++;
|
|
203
|
+
for (r = 0; r < GRID_H; r++)
|
|
204
|
+
for (c = 0; c < GRID_W; c++)
|
|
205
|
+
if (matched[r][c]) grid[r][c] = EMPTY;
|
|
206
|
+
amt = (unsigned int)n * 10u;
|
|
207
|
+
if (chain > 1) amt = amt * chain;
|
|
208
|
+
(void)amt;
|
|
209
|
+
sound_play_tone(0, 0xC0, 6, 4); /* clear chime */
|
|
210
|
+
apply_gravity();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
135
214
|
static void lock_piece(void) {
|
|
136
|
-
uint8_t r, c, run, run_col;
|
|
137
215
|
int8_t i;
|
|
138
216
|
/* Drop the 3 cells into the grid. */
|
|
139
217
|
for (i = 0; i < 3; i++) {
|
|
@@ -142,26 +220,7 @@ static void lock_piece(void) {
|
|
|
142
220
|
grid[y][piece_x] = piece_col[i];
|
|
143
221
|
}
|
|
144
222
|
}
|
|
145
|
-
|
|
146
|
-
for (r = 0; r < GRID_H; r++) {
|
|
147
|
-
run = 1;
|
|
148
|
-
run_col = grid[r][0];
|
|
149
|
-
for (c = 1; c < GRID_W; c++) {
|
|
150
|
-
if (grid[r][c] == run_col && run_col != EMPTY) {
|
|
151
|
-
++run;
|
|
152
|
-
if (run >= 3) {
|
|
153
|
-
/* Found a triple ending at c — clear back. */
|
|
154
|
-
grid[r][c] = EMPTY;
|
|
155
|
-
grid[r][c - 1] = EMPTY;
|
|
156
|
-
grid[r][c - 2] = EMPTY;
|
|
157
|
-
sound_play_tone(0, 0x100 - (c << 4), 6, 4);
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
run = 1;
|
|
161
|
-
run_col = grid[r][c];
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
223
|
+
resolve_board();
|
|
165
224
|
}
|
|
166
225
|
|
|
167
226
|
/* Can the piece occupy (x, y..y+2) given the current grid? */
|
|
@@ -258,6 +317,7 @@ void main(void) {
|
|
|
258
317
|
ppu_wait_nmi();
|
|
259
318
|
|
|
260
319
|
/* ── Input ──────────────────────────────────────────────── */
|
|
320
|
+
sound_music_tick();
|
|
261
321
|
pad = pad_poll(0);
|
|
262
322
|
if ((pad & PAD_LEFT) && !(prev_pad & PAD_LEFT) && can_place(piece_x - 1, piece_y)) --piece_x;
|
|
263
323
|
if ((pad & PAD_RIGHT) && !(prev_pad & PAD_RIGHT) && can_place(piece_x + 1, piece_y)) ++piece_x;
|
|
@@ -176,11 +176,23 @@ static void reset_run(void) {
|
|
|
176
176
|
game_over_timer = 0;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
/* Galois LFSR (taps $B8), period 255 -- real per-spawn randomness.
|
|
180
|
+
* The old code derived the spawn column from spawn_timer, but the caller
|
|
181
|
+
* resets spawn_timer just before calling here, so it was CONSTANT and
|
|
182
|
+
* every enemy spawned in the same left column/lane. */
|
|
183
|
+
static uint8_t rng_state = 0xA5;
|
|
184
|
+
static uint8_t rand8(void) {
|
|
185
|
+
uint8_t lsb = (uint8_t)(rng_state & 1);
|
|
186
|
+
rng_state >>= 1;
|
|
187
|
+
if (lsb) rng_state ^= 0xB8;
|
|
188
|
+
return rng_state;
|
|
189
|
+
}
|
|
190
|
+
|
|
179
191
|
static void spawn_obstacle(void) {
|
|
180
192
|
uint8_t i;
|
|
181
193
|
for (i = 0; i < MAX_OBSTACLES; i++) {
|
|
182
194
|
if (!obstacles[i].alive) {
|
|
183
|
-
obstacles[i].x = lane_x[(
|
|
195
|
+
obstacles[i].x = lane_x[rand8() % 3];
|
|
184
196
|
obstacles[i].y = 0;
|
|
185
197
|
obstacles[i].alive = 1;
|
|
186
198
|
return;
|
|
@@ -240,6 +252,8 @@ void main(void) {
|
|
|
240
252
|
|
|
241
253
|
ppu_wait_nmi();
|
|
242
254
|
|
|
255
|
+
sound_music_tick();
|
|
256
|
+
|
|
243
257
|
p1 = pad_poll(0);
|
|
244
258
|
|
|
245
259
|
if (game_over_timer > 0) {
|
|
@@ -215,6 +215,7 @@ void main(void) {
|
|
|
215
215
|
ppu_wait_nmi();
|
|
216
216
|
|
|
217
217
|
/* ── Input ───────────────────────────────────────────────── */
|
|
218
|
+
sound_music_tick();
|
|
218
219
|
pad = pad_poll(0);
|
|
219
220
|
if ((pad & PAD_LEFT) && ship_x > 8) --ship_x;
|
|
220
221
|
if ((pad & PAD_RIGHT) && ship_x < 240) ++ship_x;
|
|
@@ -167,7 +167,7 @@ static u8 on_platform(int16_t ipx, int16_t ipy) {
|
|
|
167
167
|
|
|
168
168
|
void main(void) {
|
|
169
169
|
const int16_t GRAVITY = 10;
|
|
170
|
-
const int16_t MOVE = 22
|
|
170
|
+
const int16_t MOVE = 34; /* was 22 — playtest: 'slow overall' */
|
|
171
171
|
const int16_t JUMP = -200;
|
|
172
172
|
const int16_t MAXFALL = 300;
|
|
173
173
|
|
|
@@ -210,6 +210,8 @@ void main(void) {
|
|
|
210
210
|
const Rect *p;
|
|
211
211
|
|
|
212
212
|
waitvsync();
|
|
213
|
+
|
|
214
|
+
psg_music_tick();
|
|
213
215
|
pad = pce_joy_read();
|
|
214
216
|
|
|
215
217
|
ipx = px >> 4;
|
|
@@ -198,20 +198,85 @@ static void draw_piece(u8 clear) {
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
201
|
+
/* ── match / clear / gravity core (ported from the GBC reference puzzle).
|
|
202
|
+
* The old scan was horizontal-only AND cleared cells mid-scan, so vertical
|
|
203
|
+
* and diagonal runs never cleared, 4+ runs half-cleared, and nothing ever
|
|
204
|
+
* fell afterwards ("rows don't shift down"). This marks every 3+ run in all
|
|
205
|
+
* 4 directions, clears them, applies per-column gravity, and loops so
|
|
206
|
+
* cascades chain (score scales with chain depth). */
|
|
207
|
+
static u8 matched[ROWS][COLS];
|
|
208
|
+
/* H + V only on PCE — the stock cc65 pce.cfg boot bank is 8KB and the
|
|
209
|
+
* two diagonal passes don't fit; add them back if you free up ROM. */
|
|
210
|
+
static const int8_t DIRS4[2][2] = { {0,1}, {1,0} };
|
|
211
|
+
|
|
212
|
+
static u8 mark_and_count(void) {
|
|
213
|
+
u8 r, c, d, len, k, cnt;
|
|
214
|
+
u8 col;
|
|
215
|
+
int8_t dr, dc;
|
|
216
|
+
int sr, sc;
|
|
217
|
+
cnt = 0;
|
|
218
|
+
for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) matched[r][c] = 0;
|
|
219
|
+
for (r = 0; r < ROWS; r++) {
|
|
220
|
+
for (c = 0; c < COLS; c++) {
|
|
221
|
+
col = grid[r][c];
|
|
222
|
+
if (col == 0) continue;
|
|
223
|
+
for (d = 0; d < 2; d++) {
|
|
224
|
+
dr = DIRS4[d][0]; dc = DIRS4[d][1];
|
|
225
|
+
/* (no run-start check: a mid-run scan only re-marks already-
|
|
226
|
+
* marked cells, so skipping the predecessor test is pure
|
|
227
|
+
* code-size savings on the 8KB PCE boot bank) */
|
|
228
|
+
len = 1;
|
|
229
|
+
sr = (int)r + dr; sc = (int)c + dc;
|
|
230
|
+
while (sr >= 0 && sr < ROWS && sc >= 0 && sc < COLS
|
|
231
|
+
&& grid[sr][sc] == col) { len++; sr += dr; sc += dc; }
|
|
232
|
+
if (len >= 3) {
|
|
233
|
+
sr = r; sc = c;
|
|
234
|
+
for (k = 0; k < len; k++) {
|
|
235
|
+
if (!matched[sr][sc]) { matched[sr][sc] = 1; cnt++; }
|
|
236
|
+
sr += dr; sc += dc;
|
|
237
|
+
}
|
|
213
238
|
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return cnt;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* collapse each column so survivors rest on the floor (in place: walk
|
|
246
|
+
* from the bottom, copying gems down to a write cursor, then zero above) */
|
|
247
|
+
static void apply_gravity(void) {
|
|
248
|
+
u8 c;
|
|
249
|
+
int r, w;
|
|
250
|
+
for (c = 0; c < COLS; c++) {
|
|
251
|
+
w = ROWS - 1;
|
|
252
|
+
for (r = ROWS - 1; r >= 0; r--) {
|
|
253
|
+
if (grid[r][c] != 0) { grid[w][c] = grid[r][c]; w--; }
|
|
214
254
|
}
|
|
255
|
+
for (; w >= 0; w--) grid[w][c] = 0;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static void resolve_board(void) {
|
|
260
|
+
u8 n, r, c, chain;
|
|
261
|
+
unsigned int amt;
|
|
262
|
+
chain = 0;
|
|
263
|
+
while (1) {
|
|
264
|
+
n = mark_and_count();
|
|
265
|
+
if (n == 0) break;
|
|
266
|
+
chain++;
|
|
267
|
+
for (r = 0; r < ROWS; r++)
|
|
268
|
+
for (c = 0; c < COLS; c++)
|
|
269
|
+
if (matched[r][c]) grid[r][c] = 0;
|
|
270
|
+
amt = (unsigned int)n * 10u;
|
|
271
|
+
if (chain > 1) amt = amt * chain;
|
|
272
|
+
if (score < 9999) score += amt;
|
|
273
|
+
psg_tone(0, 0x180, 24); /* clear chime */
|
|
274
|
+
apply_gravity();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
static void clear_triples(void) {
|
|
279
|
+
resolve_board();
|
|
215
280
|
}
|
|
216
281
|
|
|
217
282
|
static void lock_piece(void) {
|
|
@@ -262,6 +327,7 @@ void main(void) {
|
|
|
262
327
|
for (;;) {
|
|
263
328
|
u8 fall_rate;
|
|
264
329
|
waitvsync();
|
|
330
|
+
psg_music_tick();
|
|
265
331
|
|
|
266
332
|
draw_piece(1); /* erase old piece footprint */
|
|
267
333
|
|
|
@@ -225,7 +225,7 @@ static void fire(void) {
|
|
|
225
225
|
bullets[i].x = player.x;
|
|
226
226
|
bullets[i].y = (u16)(player.y - 10);
|
|
227
227
|
bullets[i].alive = 1;
|
|
228
|
-
psg_tone(2, 0x180,
|
|
228
|
+
psg_tone(2, 0x180, 31); /* max vol — playtest said too quiet */
|
|
229
229
|
sfx_timer = 4;
|
|
230
230
|
return;
|
|
231
231
|
}
|
|
@@ -280,11 +280,12 @@ void main(void) {
|
|
|
280
280
|
|
|
281
281
|
for (;;) {
|
|
282
282
|
waitvsync();
|
|
283
|
+
psg_music_tick();
|
|
283
284
|
pad = pce_joy_read();
|
|
284
285
|
|
|
285
286
|
/* move ship */
|
|
286
|
-
if ((pad & PCE_JOY_LEFT) && player.x > 2) player.x -=
|
|
287
|
-
if ((pad & PCE_JOY_RIGHT) && player.x < 238) player.x +=
|
|
287
|
+
if ((pad & PCE_JOY_LEFT) && player.x > 2) player.x -= 4; /* playtest: 'slow overall' */
|
|
288
|
+
if ((pad & PCE_JOY_RIGHT) && player.x < 238) player.x += 4;
|
|
288
289
|
if ((pad & PCE_JOY_UP) && player.y > 8) player.y -= 3;
|
|
289
290
|
if ((pad & PCE_JOY_DOWN) && player.y < 208) player.y += 3;
|
|
290
291
|
if ((pad & PCE_JOY_I) && !(prev_pad & PCE_JOY_I)) fire();
|
|
@@ -318,7 +319,7 @@ void main(void) {
|
|
|
318
319
|
enemies[j].alive = 0;
|
|
319
320
|
if (score < 9999) score += 10;
|
|
320
321
|
draw_score();
|
|
321
|
-
psg_tone(3, 0x040,
|
|
322
|
+
psg_tone(3, 0x040, 31);
|
|
322
323
|
sfx_timer = 6;
|
|
323
324
|
break;
|
|
324
325
|
}
|
|
@@ -159,7 +159,7 @@ static void draw_scores(void) {
|
|
|
159
159
|
static void serve_ball(u8 to_left) {
|
|
160
160
|
bx = 120; by = 110;
|
|
161
161
|
bdx = to_left ? -2 : 2;
|
|
162
|
-
bdy = ((score_p1 + score_p2) & 1) ? -
|
|
162
|
+
bdy = ((score_p1 + score_p2) & 1) ? -2 : 2; /* was ±1 — rally felt slow */
|
|
163
163
|
serve_timer = 40;
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -199,6 +199,7 @@ void main(void) {
|
|
|
199
199
|
u8 slot;
|
|
200
200
|
int16_t target;
|
|
201
201
|
waitvsync();
|
|
202
|
+
psg_music_tick();
|
|
202
203
|
|
|
203
204
|
/* stage sprites: P1 paddle (3 segs), P2 paddle (3 segs), ball */
|
|
204
205
|
slot = 0;
|
|
@@ -212,8 +213,8 @@ void main(void) {
|
|
|
212
213
|
pad = pce_joy_read();
|
|
213
214
|
|
|
214
215
|
/* P1 control */
|
|
215
|
-
if ((pad & PCE_JOY_UP) && p1y > COURT_TOP) p1y -=
|
|
216
|
-
if ((pad & PCE_JOY_DOWN) && p1y < COURT_BOT - PADDLE_H) p1y +=
|
|
216
|
+
if ((pad & PCE_JOY_UP) && p1y > COURT_TOP) p1y -= 4; /* playtest: 'slow overall' */
|
|
217
|
+
if ((pad & PCE_JOY_DOWN) && p1y < COURT_BOT - PADDLE_H) p1y += 4;
|
|
217
218
|
|
|
218
219
|
/* P2 chase-AI */
|
|
219
220
|
target = (int16_t)(by - PADDLE_H / 2 + BALL_SIZE / 2);
|
|
@@ -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);
|
|
@@ -138,6 +139,8 @@ void main(void) {
|
|
|
138
139
|
|
|
139
140
|
sms_sprite_init();
|
|
140
141
|
sfx_init();
|
|
142
|
+
music_init();
|
|
143
|
+
music_play(0); /* continuous background music ("no sound" was the playtest verdict) */
|
|
141
144
|
sms_sprite_set(0, (uint8_t)(px >> 4), (uint8_t)(py >> 4), 0);
|
|
142
145
|
sms_sat_upload();
|
|
143
146
|
sms_vdp_display_on();
|
|
@@ -151,6 +154,7 @@ void main(void) {
|
|
|
151
154
|
const Rect *p;
|
|
152
155
|
sms_vblank_wait();
|
|
153
156
|
sfx_update();
|
|
157
|
+
music_update();
|
|
154
158
|
|
|
155
159
|
ipx = px >> 4;
|
|
156
160
|
ipy = py >> 4;
|