romdevtools 0.27.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 +309 -0
- 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 +141 -24
- 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
|
@@ -27,10 +27,14 @@
|
|
|
27
27
|
#define SWCHA (*(volatile uint8_t*)0x280)
|
|
28
28
|
|
|
29
29
|
/* SWCHA bit pattern (port A, active LOW — invert before testing) */
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
/* SWCHA P0 nibble, active-low after the ~SWCHA invert. The bit order is
|
|
31
|
+
* Right/Left/Down/Up from bit7 down — the OLD defines here were exactly
|
|
32
|
+
* REVERSED (UP=0x80 etc.), which made up/down move the sprite left/right
|
|
33
|
+
* on every 7800 scaffold. */
|
|
34
|
+
#define JOY_RIGHT 0x80
|
|
35
|
+
#define JOY_LEFT 0x40
|
|
36
|
+
#define JOY_DOWN 0x20
|
|
37
|
+
#define JOY_UP 0x10
|
|
34
38
|
|
|
35
39
|
/* 16-pixel-wide ball (= 4 bytes in 160A mode), 8 rows tall. */
|
|
36
40
|
static const uint8_t sprite_row0[4] = { 0x05, 0x55, 0x55, 0x50 };
|
|
@@ -25,10 +25,14 @@
|
|
|
25
25
|
#define SWCHA (*(volatile uint8_t*)0x280)
|
|
26
26
|
#define INPT4 (*(volatile uint8_t*)0x0C)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
/* SWCHA P0 nibble, active-low after the ~SWCHA invert. The bit order is
|
|
29
|
+
* Right/Left/Down/Up from bit7 down — the OLD defines here were exactly
|
|
30
|
+
* REVERSED (UP=0x80 etc.), which made up/down move the sprite left/right
|
|
31
|
+
* on every 7800 scaffold. */
|
|
32
|
+
#define JOY_RIGHT 0x80
|
|
33
|
+
#define JOY_LEFT 0x40
|
|
34
|
+
#define JOY_DOWN 0x20
|
|
35
|
+
#define JOY_UP 0x10
|
|
32
36
|
|
|
33
37
|
/* 16-pixel-wide (= 4 bytes in 160A), 8 rows tall player ball. */
|
|
34
38
|
static const uint8_t player_row0[4] = { 0x05, 0x55, 0x55, 0x50 };
|
|
@@ -126,10 +130,10 @@ static void vblank_wait(void) {
|
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
/* Physics in 4.4 fixed point — 16 = 1 px, allows half-pixel velocity. */
|
|
129
|
-
#define GRAVITY
|
|
130
|
-
#define MOVE_PX 1
|
|
131
|
-
#define JUMP_VEL (-48
|
|
132
|
-
#define MAXFALL
|
|
133
|
+
#define GRAVITY 6
|
|
134
|
+
#define MOVE_PX 2 /* 1 px/frame read as 'doesn't move' — 2 is snappy */
|
|
135
|
+
#define JUMP_VEL (-80) /* was -48: a 10px hop over ~12 frames read as 'jumps very slowly' — this is ~27px in the same time */
|
|
136
|
+
#define MAXFALL 64
|
|
133
137
|
#define GROUND_Y 200 /* DLL index of the ground line */
|
|
134
138
|
|
|
135
139
|
void main(void) {
|
|
@@ -31,8 +31,11 @@
|
|
|
31
31
|
#define SWCHA (*(volatile uint8_t*)0x280)
|
|
32
32
|
#define INPT4 (*(volatile uint8_t*)0x0C)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
/* SWCHA bit order is Right(0x80)/Left(0x40)/Down(0x20)/Up(0x10) — the
|
|
35
|
+
* old 0x20/0x10 masks here were the DOWN/UP bits, so the stick's
|
|
36
|
+
* vertical axis steered horizontally. */
|
|
37
|
+
#define JOY_LEFT 0x40
|
|
38
|
+
#define JOY_RIGHT 0x80
|
|
36
39
|
|
|
37
40
|
#define COLS 8
|
|
38
41
|
#define CELL_W_PIX 8
|
|
@@ -181,11 +184,11 @@ void main(void) {
|
|
|
181
184
|
if (pad & JOY_RIGHT && piece_x_col < COLS - 1) { piece_x_col++; set_x((uint8_t)(60 + piece_x_col * CELL_W_PIX)); }
|
|
182
185
|
|
|
183
186
|
btn = (INPT4 & 0x80) ? 0 : 1;
|
|
184
|
-
if (btn && !prev_btn) { fall_timer =
|
|
187
|
+
if (btn && !prev_btn) { fall_timer = 18; sfx_tone(0, 4, 4); }
|
|
185
188
|
prev_btn = btn;
|
|
186
189
|
|
|
187
190
|
fall_timer++;
|
|
188
|
-
if (fall_timer >=
|
|
191
|
+
if (fall_timer >= 18) { /* was 30 — 'moving down very slowly' */
|
|
189
192
|
fall_timer = 0;
|
|
190
193
|
piece_y++;
|
|
191
194
|
if (piece_y >= BOT_Y) {
|
|
@@ -27,8 +27,11 @@
|
|
|
27
27
|
#define CTRL (*(volatile uint8_t*)0x3C)
|
|
28
28
|
#define SWCHA (*(volatile uint8_t*)0x280)
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/* SWCHA bit order is Right(0x80)/Left(0x40)/Down(0x20)/Up(0x10) — the
|
|
31
|
+
* old 0x20/0x10 masks here were the DOWN/UP bits, so the stick's
|
|
32
|
+
* vertical axis steered horizontally. */
|
|
33
|
+
#define JOY_LEFT 0x40
|
|
34
|
+
#define JOY_RIGHT 0x80
|
|
32
35
|
|
|
33
36
|
/* 16-pixel-wide (= 4 bytes in 160A) × 8 row car sprite. */
|
|
34
37
|
static const uint8_t car_row0[4] = { 0x05, 0x55, 0x55, 0x50 };
|
|
@@ -31,10 +31,14 @@
|
|
|
31
31
|
#define SWCHA (*(volatile uint8_t*)0x280)
|
|
32
32
|
#define INPT4 (*(volatile uint8_t*)0x0C)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
/* SWCHA P0 nibble, active-low after the ~SWCHA invert. The bit order is
|
|
35
|
+
* Right/Left/Down/Up from bit7 down — the OLD defines here were exactly
|
|
36
|
+
* REVERSED (UP=0x80 etc.), which made up/down move the sprite left/right
|
|
37
|
+
* on every 7800 scaffold. */
|
|
38
|
+
#define JOY_RIGHT 0x80
|
|
39
|
+
#define JOY_LEFT 0x40
|
|
40
|
+
#define JOY_DOWN 0x20
|
|
41
|
+
#define JOY_UP 0x10
|
|
38
42
|
|
|
39
43
|
/* Ship sprite — 16 px wide × 8 rows. */
|
|
40
44
|
static const uint8_t ship_row0[4] = { 0x00, 0x05, 0x50, 0x00 };
|
|
@@ -31,8 +31,11 @@
|
|
|
31
31
|
#define CTRL (*(volatile uint8_t*)0x3C)
|
|
32
32
|
#define SWCHA (*(volatile uint8_t*)0x280)
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
/* SWCHA bit order is Right(0x80)/Left(0x40)/Down(0x20)/Up(0x10) — the
|
|
35
|
+
* old 0x80/0x40 masks were the RIGHT/LEFT bits, so the stick's
|
|
36
|
+
* horizontal axis moved the paddle vertically. */
|
|
37
|
+
#define P1_UP 0x10
|
|
38
|
+
#define P1_DOWN 0x20
|
|
36
39
|
#define P2_UP 0x08
|
|
37
40
|
#define P2_DOWN 0x04
|
|
38
41
|
|
|
@@ -169,7 +172,7 @@ static void serve_ball(uint8_t to_left) {
|
|
|
169
172
|
bx = 76;
|
|
170
173
|
by = 120;
|
|
171
174
|
bdx = to_left ? -2 : 2;
|
|
172
|
-
bdy = 1
|
|
175
|
+
bdy = 2; /* was 1 — the rally felt 'very slow' */
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
void main(void) {
|
|
@@ -62,17 +62,6 @@ static const uint8_t platforms[][3] = {
|
|
|
62
62
|
};
|
|
63
63
|
#define N_PLATFORMS (sizeof(platforms) / sizeof(platforms[0]))
|
|
64
64
|
|
|
65
|
-
/* Is world char-cell (col,row) a platform block? */
|
|
66
|
-
static uint8_t world_is_wall(uint8_t col, uint8_t row) {
|
|
67
|
-
uint8_t i;
|
|
68
|
-
for (i = 0; i < N_PLATFORMS; i++) {
|
|
69
|
-
if (row == platforms[i][2]
|
|
70
|
-
&& col >= platforms[i][0]
|
|
71
|
-
&& col < platforms[i][1]) return 1;
|
|
72
|
-
}
|
|
73
|
-
return 0;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
65
|
static void wait_vblank(void) {
|
|
77
66
|
while (PEEK(VIC_RASTER) < 250) { }
|
|
78
67
|
while (PEEK(VIC_RASTER) >= 250) { }
|
|
@@ -88,33 +77,48 @@ static void copy_sprite(uint8_t slot, const uint8_t *data) {
|
|
|
88
77
|
* starting at world column `coarseCol`. Called once per coarse-scroll step
|
|
89
78
|
* (every 8 px of camera movement) — NOT every frame. */
|
|
90
79
|
static void render_view(uint8_t coarseCol) {
|
|
91
|
-
uint8_t sc, r;
|
|
92
|
-
uint16_t wc;
|
|
80
|
+
uint8_t sc, r, i, c8;
|
|
81
|
+
uint16_t wc, off;
|
|
82
|
+
uint8_t wallrow[VIS_ROWS];
|
|
83
|
+
/* PERFORMANCE IS LOAD-BEARING HERE. The old version re-scanned the
|
|
84
|
+
* 10-entry platform table for EVERY CELL and computed a 16-bit modulo
|
|
85
|
+
* (a cc65 library call) per sky cell — ~2 SECONDS per re-render at
|
|
86
|
+
* 1 MHz, re-run on every 8-px coarse step. The game spent nearly all
|
|
87
|
+
* its time in here: scrolling froze, jump presses were eaten between
|
|
88
|
+
* the multi-second loop passes, and the sprite setup after the first
|
|
89
|
+
* renders didn't run for hundreds of frames. This version flags each
|
|
90
|
+
* column's platform rows ONCE (40 table scans total, not 1000) and
|
|
91
|
+
* uses mask arithmetic for the sky texture. ~20x faster. */
|
|
93
92
|
for (sc = 0; sc < VIS_COLS; sc++) {
|
|
94
93
|
wc = (uint16_t)coarseCol + sc;
|
|
94
|
+
c8 = (uint8_t)wc;
|
|
95
|
+
for (r = 0; r < VIS_ROWS; r++) wallrow[r] = 0;
|
|
96
|
+
if (wc < WORLD_COLS) {
|
|
97
|
+
for (i = 0; i < N_PLATFORMS; i++) {
|
|
98
|
+
if (c8 >= platforms[i][0] && c8 < platforms[i][1])
|
|
99
|
+
wallrow[platforms[i][2]] = 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
off = sc;
|
|
95
103
|
for (r = 0; r < VIS_ROWS; r++) {
|
|
96
|
-
|
|
97
|
-
if (wc < WORLD_COLS && world_is_wall((uint8_t)wc, r)) {
|
|
104
|
+
if (wallrow[r]) {
|
|
98
105
|
SCREEN[off] = 0xA0; /* reverse-space solid block */
|
|
99
106
|
COLORS[off] = 0x0C; /* mid grey platform */
|
|
100
107
|
} else if (r >= 22) {
|
|
101
|
-
/*
|
|
102
|
-
* band reads as solid terrain, not void. */
|
|
108
|
+
/* dithered earth below the floor row */
|
|
103
109
|
SCREEN[off] = 0xA0;
|
|
104
|
-
COLORS[off] = ((
|
|
110
|
+
COLORS[off] = ((c8 ^ r) & 1) ? 0x09 : 0x08; /* brown / orange */
|
|
105
111
|
} else {
|
|
106
|
-
/*
|
|
107
|
-
|
|
108
|
-
* coarse lattice add detail; reverse-space everywhere else gives a
|
|
109
|
-
* filled (non-blank) sky band that scrolls with the world. */
|
|
110
|
-
if (((wc * 3u + r * 7u) % 23u) == 0u) {
|
|
112
|
+
/* textured sky: sparse '.' stars on a cheap AND-mask lattice */
|
|
113
|
+
if (((uint8_t)(c8 + (r << 2)) & 15) == 0) {
|
|
111
114
|
SCREEN[off] = 0x2E; /* '.' distant detail */
|
|
112
115
|
COLORS[off] = 0x01; /* white */
|
|
113
116
|
} else {
|
|
114
117
|
SCREEN[off] = 0xA0; /* solid block sky */
|
|
115
|
-
COLORS[off] = ((
|
|
118
|
+
COLORS[off] = ((c8 ^ (r >> 1)) & 1) ? 0x06 : 0x0E; /* blue / light blue */
|
|
116
119
|
}
|
|
117
120
|
}
|
|
121
|
+
off += 40;
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
}
|
|
@@ -133,28 +133,89 @@ static void new_piece(void) {
|
|
|
133
133
|
piece_y = -3;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/* ── match / clear / gravity core (ported from the GBC reference puzzle).
|
|
137
|
+
* The old scan was horizontal-only AND cleared cells mid-scan, so vertical
|
|
138
|
+
* and diagonal runs never cleared, 4+ runs half-cleared, and nothing ever
|
|
139
|
+
* fell afterwards ("rows don't shift down"). This marks every 3+ run in all
|
|
140
|
+
* 4 directions, clears them, applies per-column gravity, and loops so
|
|
141
|
+
* cascades chain (score scales with chain depth). */
|
|
142
|
+
static uint8_t matched[ROWS][COLS];
|
|
143
|
+
static const int8_t DIRS4[4][2] = { {0,1}, {1,0}, {1,1}, {1,-1} };
|
|
144
|
+
|
|
145
|
+
static uint8_t mark_and_count(void) {
|
|
146
|
+
uint8_t r, c, d, len, k, cnt;
|
|
147
|
+
uint8_t col;
|
|
148
|
+
int8_t dr, dc;
|
|
149
|
+
int sr, sc;
|
|
150
|
+
cnt = 0;
|
|
151
|
+
for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) matched[r][c] = 0;
|
|
152
|
+
for (r = 0; r < ROWS; r++) {
|
|
153
|
+
for (c = 0; c < COLS; c++) {
|
|
154
|
+
col = grid[r][c];
|
|
155
|
+
if (col == 0) continue;
|
|
156
|
+
for (d = 0; d < 4; d++) {
|
|
157
|
+
dr = DIRS4[d][0]; dc = DIRS4[d][1];
|
|
158
|
+
sr = (int)r - dr; sc = (int)c - dc;
|
|
159
|
+
if (sr >= 0 && sr < ROWS && sc >= 0 && sc < COLS
|
|
160
|
+
&& grid[sr][sc] == col) continue; /* not the run's start */
|
|
161
|
+
len = 1;
|
|
162
|
+
sr = (int)r + dr; sc = (int)c + dc;
|
|
163
|
+
while (sr >= 0 && sr < ROWS && sc >= 0 && sc < COLS
|
|
164
|
+
&& grid[sr][sc] == col) { len++; sr += dr; sc += dc; }
|
|
165
|
+
if (len >= 3) {
|
|
166
|
+
sr = r; sc = c;
|
|
167
|
+
for (k = 0; k < len; k++) {
|
|
168
|
+
if (!matched[sr][sc]) { matched[sr][sc] = 1; cnt++; }
|
|
169
|
+
sr += dr; sc += dc;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return cnt;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* collapse each column so survivors rest on the floor (in place: walk
|
|
179
|
+
* from the bottom, copying gems down to a write cursor, then zero above) */
|
|
180
|
+
static void apply_gravity(void) {
|
|
181
|
+
uint8_t c;
|
|
182
|
+
int r, w;
|
|
183
|
+
for (c = 0; c < COLS; c++) {
|
|
184
|
+
w = ROWS - 1;
|
|
185
|
+
for (r = ROWS - 1; r >= 0; r--) {
|
|
186
|
+
if (grid[r][c] != 0) { grid[w][c] = grid[r][c]; w--; }
|
|
187
|
+
}
|
|
188
|
+
for (; w >= 0; w--) grid[w][c] = 0;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static void resolve_board(void) {
|
|
193
|
+
uint8_t n, r, c, chain;
|
|
194
|
+
unsigned int amt;
|
|
195
|
+
chain = 0;
|
|
196
|
+
while (1) {
|
|
197
|
+
n = mark_and_count();
|
|
198
|
+
if (n == 0) break;
|
|
199
|
+
chain++;
|
|
200
|
+
for (r = 0; r < ROWS; r++)
|
|
201
|
+
for (c = 0; c < COLS; c++)
|
|
202
|
+
if (matched[r][c]) grid[r][c] = 0;
|
|
203
|
+
amt = (unsigned int)n * 10u;
|
|
204
|
+
if (chain > 1) amt = amt * chain;
|
|
205
|
+
if (score < 65500u) score += amt;
|
|
206
|
+
sfx_tone(0, 0x80, 0x10, 12); /* clear chime */
|
|
207
|
+
apply_gravity();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
136
211
|
static void lock_piece(void) {
|
|
137
|
-
uint8_t i
|
|
212
|
+
uint8_t i;
|
|
138
213
|
int8_t r;
|
|
139
|
-
uint8_t a, b, d;
|
|
140
214
|
for (i = 0; i < 3; i++) {
|
|
141
215
|
r = (int8_t)(piece_y + i);
|
|
142
216
|
if (r >= 0 && r < ROWS) grid[r][piece_x] = piece[i];
|
|
143
217
|
}
|
|
144
|
-
|
|
145
|
-
r = (int8_t)(piece_y + i);
|
|
146
|
-
if (r < 0 || r >= ROWS) continue;
|
|
147
|
-
for (c = 0; c <= COLS - 3; c++) {
|
|
148
|
-
a = grid[r][c]; b = grid[r][c+1]; d = grid[r][c+2];
|
|
149
|
-
if (a != 0 && a == b && b == d) {
|
|
150
|
-
grid[r][c] = 0;
|
|
151
|
-
grid[r][c+1] = 0;
|
|
152
|
-
grid[r][c+2] = 0;
|
|
153
|
-
if (score < 65500u) score += 30;
|
|
154
|
-
sfx_tone(0, 0x80, 0x10, 12);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
218
|
+
resolve_board();
|
|
158
219
|
draw_grid();
|
|
159
220
|
}
|
|
160
221
|
|
|
@@ -82,6 +82,15 @@ void main(void) {
|
|
|
82
82
|
POKE(VIC_BORDER, 0x00);
|
|
83
83
|
POKE(VIC_BG0, 0x09); /* brown road */
|
|
84
84
|
|
|
85
|
+
/* Clear screen RAM: a .prg starts over the BASIC screen, so the
|
|
86
|
+
* KERNAL's startup text (the leftover the playtest saw at the top)
|
|
87
|
+
* stays visible until someone wipes it. */
|
|
88
|
+
{
|
|
89
|
+
uint16_t k;
|
|
90
|
+
volatile uint8_t *scr = (volatile uint8_t*)0x0400;
|
|
91
|
+
for (k = 0; k < 1000; k++) scr[k] = 0x20;
|
|
92
|
+
}
|
|
93
|
+
|
|
85
94
|
player.x = LANE1_X; player.y = 220; player.alive = 1;
|
|
86
95
|
for (i = 0; i < MAX_OBS; i++) obstacles[i].alive = 0;
|
|
87
96
|
spawn_timer = 0;
|
|
@@ -91,11 +91,23 @@ static void fire(void) {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/* Galois LFSR (taps $B8), period 255 -- real per-spawn randomness.
|
|
95
|
+
* The old code derived the spawn column from spawn_timer, but the caller
|
|
96
|
+
* resets spawn_timer just before calling here, so it was CONSTANT and
|
|
97
|
+
* every enemy spawned in the same left column/lane. */
|
|
98
|
+
static uint8_t rng_state = 0xA5;
|
|
99
|
+
static uint8_t rand8(void) {
|
|
100
|
+
uint8_t lsb = (uint8_t)(rng_state & 1);
|
|
101
|
+
rng_state >>= 1;
|
|
102
|
+
if (lsb) rng_state ^= 0xB8;
|
|
103
|
+
return rng_state;
|
|
104
|
+
}
|
|
105
|
+
|
|
94
106
|
static void spawn(void) {
|
|
95
107
|
uint8_t i;
|
|
96
108
|
for (i = 0; i < MAX_ENEMIES; i++) {
|
|
97
109
|
if (!enemies[i].alive) {
|
|
98
|
-
enemies[i].x = (uint8_t)(
|
|
110
|
+
enemies[i].x = (uint8_t)(24 + (rand8() % 232));
|
|
99
111
|
enemies[i].y = 30;
|
|
100
112
|
enemies[i].alive = 1;
|
|
101
113
|
return;
|
|
@@ -96,7 +96,11 @@ void main(void) {
|
|
|
96
96
|
|
|
97
97
|
/* P1 paddle on left, P2 on right. */
|
|
98
98
|
POKE(VIC_SPRITE_X(0), 30);
|
|
99
|
-
|
|
99
|
+
/* P2 at X=310 — the right side of the REAL court. The old 240 sat only
|
|
100
|
+
* ~2/3 across the screen ("computer player too close to center"): C64
|
|
101
|
+
* sprite X is 9-bit, the extra bit lives in $D010, and the court design
|
|
102
|
+
* had been squeezed into 8 bits. */
|
|
103
|
+
POKE(VIC_SPRITE_X(1), 310 - 256);
|
|
100
104
|
|
|
101
105
|
sfx_init();
|
|
102
106
|
POKE(VIC_SPR_ENA, 0x07);
|
|
@@ -122,16 +126,17 @@ void main(void) {
|
|
|
122
126
|
bdx = -bdx; sfx_tone(0, 0x40, 0x20, 3);
|
|
123
127
|
}
|
|
124
128
|
/* Paddle 2 collision */
|
|
125
|
-
if (bdx > 0 && bx >
|
|
129
|
+
if (bdx > 0 && bx > 296 && bx < 314 && by > p2y - 8 && by < p2y + 22) {
|
|
126
130
|
bdx = -bdx; sfx_tone(0, 0x40, 0x20, 3);
|
|
127
131
|
}
|
|
128
132
|
/* Score */
|
|
129
|
-
if (bx < 5) { p2_score++; if (p2_score > 9) p2_score = 0; sfx_noise(20); bx =
|
|
130
|
-
if (bx >
|
|
133
|
+
if (bx < 5) { p2_score++; if (p2_score > 9) p2_score = 0; sfx_noise(20); bx = 170; by = 130; bdx = 2; }
|
|
134
|
+
if (bx > 330) { p1_score++; if (p1_score > 9) p1_score = 0; sfx_tone(0, 0x80, 0x10, 16); bx = 170; by = 130; bdx = -2; }
|
|
131
135
|
|
|
132
136
|
POKE(VIC_SPRITE_Y(0), p1y);
|
|
133
137
|
POKE(VIC_SPRITE_Y(1), p2y);
|
|
134
138
|
POKE(VIC_SPRITE_X(2), (uint8_t)bx);
|
|
139
|
+
POKE(VIC_SPRITES_X8, (uint8_t)(0x02 | ((bx > 255) ? 0x04 : 0x00)));
|
|
135
140
|
POKE(VIC_SPRITE_Y(2), (uint8_t)by);
|
|
136
141
|
}
|
|
137
142
|
}
|
|
@@ -116,10 +116,11 @@ void main(void) {
|
|
|
116
116
|
const Rect *p;
|
|
117
117
|
const int16_t GRAVITY = 10;
|
|
118
118
|
const int16_t MOVE = 20;
|
|
119
|
-
const int16_t JUMP = -180
|
|
119
|
+
const int16_t JUMP = -140; /* was -180: ~100px peak (most of the screen) — 'jumps a little too high' */
|
|
120
120
|
const int16_t MAXFALL = 280;
|
|
121
121
|
|
|
122
122
|
lcd_init_default();
|
|
123
|
+
sound_init();
|
|
123
124
|
enable_vblank_irq(); /* MANDATORY: HALT-driven wait_vblank. Without this,
|
|
124
125
|
* busy-poll wait_vblank runs ~1/30 speed on the WASM
|
|
125
126
|
* emulator and the game loop appears to hang. */
|
|
@@ -169,7 +170,10 @@ void main(void) {
|
|
|
169
170
|
if (pad & PAD_RIGHT) vx = MOVE;
|
|
170
171
|
|
|
171
172
|
grounded = on_platform(ipx, ipy);
|
|
172
|
-
if ((pad & PAD_A) && !(prev & PAD_A) && grounded)
|
|
173
|
+
if ((pad & PAD_A) && !(prev & PAD_A) && grounded) {
|
|
174
|
+
vy = JUMP;
|
|
175
|
+
sound_play_tone(1, 1750, 8); /* jump blip (ch2 square) */
|
|
176
|
+
}
|
|
173
177
|
prev = pad;
|
|
174
178
|
|
|
175
179
|
vy += GRAVITY;
|