romdevtools 0.15.0 → 0.21.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 +61 -13
- package/CHANGELOG.md +289 -0
- package/README.md +1 -1
- package/examples/README.md +2 -0
- package/examples/atari2600/templates/platformer.asm +460 -0
- package/examples/atari2600/templates/racing.asm +463 -0
- package/examples/atari2600/templates/shmup.asm +386 -0
- package/examples/atari2600/templates/sports.asm +362 -0
- package/examples/atari7800/templates/default.c +49 -5
- package/examples/atari7800/templates/platformer.c +43 -4
- package/examples/atari7800/templates/puzzle.c +39 -4
- package/examples/atari7800/templates/racing.c +39 -4
- package/examples/atari7800/templates/shmup.c +40 -2
- package/examples/atari7800/templates/sports.c +36 -5
- package/examples/c64/templates/platformer.c +19 -5
- package/examples/c64/templates/puzzle.c +32 -2
- package/examples/c64/templates/shmup.c +28 -2
- package/examples/c64/templates/sports.c +30 -2
- package/examples/gb/templates/default.c +110 -16
- package/examples/gb/templates/platformer.c +25 -4
- package/examples/gb/templates/puzzle.c +32 -2
- package/examples/gb/templates/racing.c +72 -8
- package/examples/gb/templates/shmup.c +38 -1
- package/examples/gb/templates/sports.c +48 -1
- package/examples/gba/templates/gba_hello.c +29 -11
- package/examples/gba/templates/puzzle.c +15 -3
- package/examples/gba/templates/racing.c +65 -3
- package/examples/gba/templates/shmup.c +41 -4
- package/examples/gba/templates/sports.c +36 -2
- package/examples/gba/templates/tonc_hello.c +41 -5
- package/examples/gbc/templates/default.c +103 -26
- package/examples/gbc/templates/platformer.c +25 -4
- package/examples/gbc/templates/puzzle.c +32 -2
- package/examples/gbc/templates/racing.c +85 -19
- package/examples/gbc/templates/shmup.c +34 -1
- package/examples/gbc/templates/sports.c +45 -1
- package/examples/genesis/templates/puzzle.c +37 -3
- package/examples/genesis/templates/racing.c +44 -11
- package/examples/genesis/templates/sgdk_hello.c +34 -1
- package/examples/genesis/templates/shmup.c +31 -1
- package/examples/gg/templates/default.c +56 -18
- package/examples/gg/templates/platformer.c +18 -12
- package/examples/gg/templates/puzzle.c +38 -7
- package/examples/gg/templates/racing.c +51 -5
- package/examples/gg/templates/shmup.c +47 -3
- package/examples/gg/templates/sports.c +46 -3
- package/examples/lynx/templates/default.c +39 -8
- package/examples/lynx/templates/puzzle.c +28 -1
- package/examples/lynx/templates/racing.c +34 -7
- package/examples/lynx/templates/shmup.c +42 -3
- package/examples/lynx/templates/sports.c +29 -2
- package/examples/msx/platformer/main.c +213 -0
- package/examples/msx/puzzle/main.c +250 -0
- package/examples/msx/racing/main.c +249 -0
- package/examples/msx/shmup/main.c +288 -0
- package/examples/msx/sports/main.c +182 -0
- package/examples/nes/templates/default.c +67 -19
- package/examples/nes/templates/platformer.c +65 -6
- package/examples/nes/templates/puzzle.c +67 -6
- package/examples/nes/templates/racing.c +45 -13
- package/examples/nes/templates/shmup.c +51 -2
- package/examples/nes/templates/sports.c +51 -6
- package/examples/pce/platformer/main.c +283 -0
- package/examples/pce/puzzle/main.c +304 -0
- package/examples/pce/racing/main.c +304 -0
- package/examples/pce/shmup/main.c +346 -0
- package/examples/pce/sports/main.c +254 -0
- package/examples/sms/main.c +35 -6
- package/examples/sms/templates/puzzle.c +34 -5
- package/examples/sms/templates/racing.c +39 -2
- package/examples/sms/templates/shmup.c +41 -2
- package/examples/sms/templates/sports.c +43 -2
- package/examples/snes/templates/default.c +50 -28
- package/examples/snes/templates/platformer-data.asm +22 -0
- package/examples/snes/templates/platformer.c +16 -1
- package/examples/snes/templates/puzzle-data.asm +22 -0
- package/examples/snes/templates/puzzle.c +17 -1
- package/examples/snes/templates/racing-data.asm +22 -0
- package/examples/snes/templates/racing.c +17 -1
- package/examples/snes/templates/shmup-data.asm +22 -0
- package/examples/snes/templates/shmup.c +20 -1
- package/examples/snes/templates/sports-data.asm +22 -0
- package/examples/snes/templates/sports.c +16 -1
- package/package.json +1 -1
- 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 +122 -1
- package/src/host/callbacks.js +9 -1
- package/src/host/types.js +15 -8
- package/src/http/skill-doc.js +1 -1
- package/src/http/tool-registry.js +27 -2
- package/src/mcp/tools/cart-parts.js +75 -3
- package/src/mcp/tools/disasm-rebuild.js +507 -0
- package/src/mcp/tools/disasm.js +95 -6
- package/src/mcp/tools/frame.js +168 -3
- package/src/mcp/tools/index.js +4 -4
- package/src/mcp/tools/lifecycle.js +4 -2
- package/src/mcp/tools/project.js +54 -9
- package/src/mcp/tools/state.js +201 -14
- package/src/mcp/tools/toolchain.js +89 -4
- package/src/mcp/tools/watch-memory.js +125 -14
- package/src/platforms/c64/MENTAL_MODEL.md +45 -1
- package/src/platforms/c64/d64.js +281 -0
- package/src/platforms/gb/MENTAL_MODEL.md +10 -0
- package/src/platforms/msx/MENTAL_MODEL.md +10 -6
- package/src/platforms/nes/MENTAL_MODEL.md +63 -2
- package/src/platforms/pce/MENTAL_MODEL.md +9 -4
- package/src/platforms/pce/lib/c/pce_video.c +1 -1
- package/src/rom-id/identifier.js +15 -0
- package/src/toolchains/cc65/cc65.js +8 -1
- package/src/toolchains/cc65/ines.js +145 -0
- package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
- package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
- package/src/toolchains/common/reassemble.js +10 -2
- package/src/toolchains/gba-c/gba-c.js +6 -1
- package/src/toolchains/genesis-c/genesis-c.js +10 -2
- package/src/toolchains/parse-errors.js +67 -5
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PC Engine "puzzle" — a match-3 falling-block scaffold.
|
|
3
|
+
*
|
|
4
|
+
* A 1x3 column of coloured blocks falls into a 6-wide x 12-tall well drawn with
|
|
5
|
+
* background tiles. LEFT/RIGHT slide the piece, button I rotates the three
|
|
6
|
+
* colours, DOWN soft-drops, button II hard-drops. When a piece locks, any
|
|
7
|
+
* horizontal run of three same-colour cells clears and scores. Mirrors the
|
|
8
|
+
* NES/Genesis/SNES/GB/SMS puzzle scaffolds, translated to the PCE helper API.
|
|
9
|
+
*
|
|
10
|
+
* The whole field is drawn from BG tiles (no sprites needed) — a grey wall
|
|
11
|
+
* frame around a dim field interior, with R/G/B block tiles for the cells, so
|
|
12
|
+
* the screen is clearly a populated playfield (clears the verify gate).
|
|
13
|
+
*
|
|
14
|
+
* PCE notes (see pce_hw.h / MENTAL_MODEL.md):
|
|
15
|
+
* - bg_enable() turns on the BG plane + the VBlank IRQ (waitvsync needs it).
|
|
16
|
+
* - .bss must be non-empty (pce_video.c's _pce_keep[] covers it).
|
|
17
|
+
*
|
|
18
|
+
* cc65 is C89 — declare locals at the top of a block.
|
|
19
|
+
*/
|
|
20
|
+
#include <pce.h>
|
|
21
|
+
#include <stdint.h> /* int8_t for signed grid coordinates */
|
|
22
|
+
#include "pce_hw.h"
|
|
23
|
+
|
|
24
|
+
/* ---- VRAM layout (word addresses) --------------------------------------- */
|
|
25
|
+
#define BAT_VRAM 0x0000
|
|
26
|
+
#define BG_VRAM 0x1000 /* cabinet background (dotted, colour 6/7) */
|
|
27
|
+
#define RED_VRAM 0x1010 /* block colour 1 */
|
|
28
|
+
#define GRN_VRAM 0x1020 /* block colour 2 */
|
|
29
|
+
#define BLU_VRAM 0x1030 /* block colour 3 */
|
|
30
|
+
#define WALL_VRAM 0x1040 /* well border (colour 4) */
|
|
31
|
+
#define FIELD_VRAM 0x1050 /* dim empty field (colour 5) */
|
|
32
|
+
|
|
33
|
+
#define BAT_ENTRY(pal, vram) ((u16)(((pal) << 12) | ((vram) >> 4)))
|
|
34
|
+
|
|
35
|
+
#define COLS 6
|
|
36
|
+
#define ROWS 12
|
|
37
|
+
#define GRID_COL0 13 /* well's left BAT column (centres the 6-wide field) */
|
|
38
|
+
#define GRID_ROW0 4 /* well's top BAT row */
|
|
39
|
+
|
|
40
|
+
/* ---- state -------------------------------------------------------------- */
|
|
41
|
+
static u8 grid[ROWS][COLS]; /* 0 = empty, 1..3 = colour */
|
|
42
|
+
static u8 piece[3]; /* three stacked colours */
|
|
43
|
+
static int8_t piece_x; /* column 0..COLS-1 */
|
|
44
|
+
static int8_t piece_y; /* row of top cell (can be negative) */
|
|
45
|
+
static u8 fall_timer;
|
|
46
|
+
static u16 score;
|
|
47
|
+
static u16 rng;
|
|
48
|
+
static u8 pad, prev_pad;
|
|
49
|
+
static u16 tile_buf[16];
|
|
50
|
+
|
|
51
|
+
static void make_solid_tile(u16 *t, u8 ci) {
|
|
52
|
+
u8 r;
|
|
53
|
+
u8 p0 = (ci & 1) ? 0xFF : 0x00;
|
|
54
|
+
u8 p1 = (ci & 2) ? 0xFF : 0x00;
|
|
55
|
+
u8 p2 = (ci & 4) ? 0xFF : 0x00;
|
|
56
|
+
u8 p3 = (ci & 8) ? 0xFF : 0x00;
|
|
57
|
+
for (r = 0; r < 8; ++r) {
|
|
58
|
+
t[r] = (u16)(p0 | (p1 << 8));
|
|
59
|
+
t[r + 8] = (u16)(p2 | (p3 << 8));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* A block tile: a solid `ci`-colour body with a 1px `frame`-colour border on
|
|
64
|
+
* all four edges, so adjacent same-colour blocks still read as distinct cells.
|
|
65
|
+
* For each of the 8 rows we pick a per-plane mask: border rows (0,7) are all
|
|
66
|
+
* `frame`; interior rows are `ci` body with the left/right edge pixels framed. */
|
|
67
|
+
static void make_block_tile(u16 *t, u8 ci, u8 frame) {
|
|
68
|
+
u8 r;
|
|
69
|
+
for (r = 0; r < 8; ++r) {
|
|
70
|
+
u8 edge_row = (r == 0 || r == 7);
|
|
71
|
+
/* body colour planes (fill the whole row) */
|
|
72
|
+
u8 b0 = (ci & 1) ? 0xFF : 0x00, b1 = (ci & 2) ? 0xFF : 0x00;
|
|
73
|
+
u8 b2 = (ci & 4) ? 0xFF : 0x00, b3 = (ci & 8) ? 0xFF : 0x00;
|
|
74
|
+
/* frame colour planes */
|
|
75
|
+
u8 f0 = (frame & 1) ? 0xFF : 0x00, f1 = (frame & 2) ? 0xFF : 0x00;
|
|
76
|
+
u8 f2 = (frame & 4) ? 0xFF : 0x00, f3 = (frame & 8) ? 0xFF : 0x00;
|
|
77
|
+
u8 p0, p1, p2, p3;
|
|
78
|
+
if (edge_row) {
|
|
79
|
+
p0 = f0; p1 = f1; p2 = f2; p3 = f3; /* whole row framed */
|
|
80
|
+
} else {
|
|
81
|
+
/* body fill, but pixels 0 and 7 (mask 0x81) use the frame colour */
|
|
82
|
+
p0 = (u8)((b0 & 0x7E) | (f0 & 0x81));
|
|
83
|
+
p1 = (u8)((b1 & 0x7E) | (f1 & 0x81));
|
|
84
|
+
p2 = (u8)((b2 & 0x7E) | (f2 & 0x81));
|
|
85
|
+
p3 = (u8)((b3 & 0x7E) | (f3 & 0x81));
|
|
86
|
+
}
|
|
87
|
+
t[r] = (u16)(p0 | (p1 << 8));
|
|
88
|
+
t[r + 8] = (u16)(p2 | (p3 << 8));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Cabinet background tile: every pixel is colour 6, with a colour-7 dot on a
|
|
93
|
+
* sparse lattice, so the whole screen reads as an intentional textured backdrop
|
|
94
|
+
* rather than the flat hardware backdrop. Colour 6 = planes 1+2; colour 7 adds
|
|
95
|
+
* plane 0 (so dots = planes 0+1+2). Build per-plane row bytes:
|
|
96
|
+
* plane0 (low byte words 0..7) = dot mask (only dot pixels)
|
|
97
|
+
* plane1 (high byte words 0..7) = 0xFF (colour 6 base, all pixels)
|
|
98
|
+
* plane2 (low byte words 8..15) = 0xFF (colour 6 base, all pixels)
|
|
99
|
+
* plane3 (high byte words 8..15)= 0 */
|
|
100
|
+
static void make_dots_tile(u16 *t) {
|
|
101
|
+
u8 r;
|
|
102
|
+
for (r = 0; r < 8; ++r) {
|
|
103
|
+
u8 dot = ((r & 3) == 0) ? 0x22 : 0x00; /* dot columns every 4 px */
|
|
104
|
+
t[r] = (u16)(dot | 0xFF00u); /* plane0=dots, plane1=base */
|
|
105
|
+
t[r + 8] = (u16)0x00FFu; /* plane2=base, plane3=0 */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static void upload_art(void) {
|
|
110
|
+
make_dots_tile(tile_buf); load_tiles(BG_VRAM, tile_buf, 16);
|
|
111
|
+
make_block_tile(tile_buf, 1, 6); load_tiles(RED_VRAM, tile_buf, 16);
|
|
112
|
+
make_block_tile(tile_buf, 2, 6); load_tiles(GRN_VRAM, tile_buf, 16);
|
|
113
|
+
make_block_tile(tile_buf, 3, 6); load_tiles(BLU_VRAM, tile_buf, 16);
|
|
114
|
+
make_solid_tile(tile_buf, 4); load_tiles(WALL_VRAM, tile_buf, 16);
|
|
115
|
+
make_solid_tile(tile_buf, 5); load_tiles(FIELD_VRAM, tile_buf, 16);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static u16 vram_for(u8 cell) {
|
|
119
|
+
if (cell == 1) return RED_VRAM;
|
|
120
|
+
if (cell == 2) return GRN_VRAM;
|
|
121
|
+
if (cell == 3) return BLU_VRAM;
|
|
122
|
+
return FIELD_VRAM; /* empty -> dim field interior */
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static void put_cell(u8 batCol, u8 batRow, u16 vram) {
|
|
126
|
+
u16 e = BAT_ENTRY(0, vram);
|
|
127
|
+
vram_set_write_addr((u16)(BAT_VRAM + batRow * 32 + batCol));
|
|
128
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
129
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* fill the whole BAT with the cabinet background tile */
|
|
133
|
+
static void clear_bat(void) {
|
|
134
|
+
u8 r, c;
|
|
135
|
+
u16 e = BAT_ENTRY(0, BG_VRAM);
|
|
136
|
+
for (r = 0; r < 32; ++r) {
|
|
137
|
+
vram_set_write_addr((u16)(BAT_VRAM + r * 32));
|
|
138
|
+
for (c = 0; c < 32; ++c) {
|
|
139
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
140
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* draw the well frame + dim interior */
|
|
146
|
+
static void draw_well(void) {
|
|
147
|
+
int8_t r, c;
|
|
148
|
+
for (r = -1; r <= ROWS; ++r) {
|
|
149
|
+
for (c = -1; c <= COLS; ++c) {
|
|
150
|
+
u16 vram = (r == -1 || r == ROWS || c == -1 || c == COLS)
|
|
151
|
+
? WALL_VRAM : FIELD_VRAM;
|
|
152
|
+
put_cell((u8)(GRID_COL0 + c), (u8)(GRID_ROW0 + r), vram);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static void draw_grid(void) {
|
|
158
|
+
u8 r, c;
|
|
159
|
+
for (r = 0; r < ROWS; ++r)
|
|
160
|
+
for (c = 0; c < COLS; ++c)
|
|
161
|
+
put_cell((u8)(GRID_COL0 + c), (u8)(GRID_ROW0 + r), vram_for(grid[r][c]));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static u16 next_rand(void) {
|
|
165
|
+
rng = (u16)(rng * 25173u + 13849u);
|
|
166
|
+
return rng;
|
|
167
|
+
}
|
|
168
|
+
static u8 rand_color(void) { return (u8)(1 + (next_rand() >> 8) % 3); }
|
|
169
|
+
|
|
170
|
+
static void new_piece(void) {
|
|
171
|
+
piece[0] = rand_color();
|
|
172
|
+
piece[1] = rand_color();
|
|
173
|
+
piece[2] = rand_color();
|
|
174
|
+
piece_x = COLS / 2 - 1;
|
|
175
|
+
piece_y = -3;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static u8 collides(int8_t col, int8_t row) {
|
|
179
|
+
u8 i;
|
|
180
|
+
int8_t r;
|
|
181
|
+
if (col < 0 || col >= COLS) return 1;
|
|
182
|
+
for (i = 0; i < 3; ++i) {
|
|
183
|
+
r = (int8_t)(row + i);
|
|
184
|
+
if (r >= ROWS) return 1;
|
|
185
|
+
if (r >= 0 && grid[r][col] != 0) return 1;
|
|
186
|
+
}
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
static void draw_piece(u8 clear) {
|
|
191
|
+
u8 i;
|
|
192
|
+
for (i = 0; i < 3; ++i) {
|
|
193
|
+
int8_t r = (int8_t)(piece_y + i);
|
|
194
|
+
u8 v;
|
|
195
|
+
if (r < 0 || r >= ROWS) continue;
|
|
196
|
+
v = clear ? grid[r][piece_x] : piece[i];
|
|
197
|
+
put_cell((u8)(GRID_COL0 + piece_x), (u8)(GRID_ROW0 + r), vram_for(v));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static void clear_triples(void) {
|
|
202
|
+
u8 r;
|
|
203
|
+
int8_t c;
|
|
204
|
+
u8 a, b, d;
|
|
205
|
+
for (r = 0; r < ROWS; ++r) {
|
|
206
|
+
for (c = 0; c <= COLS - 3; ++c) {
|
|
207
|
+
a = grid[r][c]; b = grid[r][c + 1]; d = grid[r][c + 2];
|
|
208
|
+
if (a != 0 && a == b && b == d) {
|
|
209
|
+
grid[r][c] = 0; grid[r][c + 1] = 0; grid[r][c + 2] = 0;
|
|
210
|
+
if (score < 9999) score += 30;
|
|
211
|
+
psg_tone(0, 0x180, 24);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static void lock_piece(void) {
|
|
218
|
+
u8 i;
|
|
219
|
+
int8_t r;
|
|
220
|
+
for (i = 0; i < 3; ++i) {
|
|
221
|
+
r = (int8_t)(piece_y + i);
|
|
222
|
+
if (r >= 0 && r < ROWS) grid[r][piece_x] = piece[i];
|
|
223
|
+
}
|
|
224
|
+
clear_triples();
|
|
225
|
+
draw_grid();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
void main(void) {
|
|
229
|
+
u8 r, c;
|
|
230
|
+
u8 sfx_timer;
|
|
231
|
+
|
|
232
|
+
_pce_keep[0] = 0;
|
|
233
|
+
|
|
234
|
+
/* palette: BG sub-pal 0 holds field/wall + R/G/B blocks + frame */
|
|
235
|
+
vce_set_color(0, PCE_RGB(0, 0, 1)); /* backdrop navy */
|
|
236
|
+
vce_set_color(1, PCE_RGB(7, 1, 1)); /* c1 red block */
|
|
237
|
+
vce_set_color(2, PCE_RGB(1, 6, 1)); /* c2 green block */
|
|
238
|
+
vce_set_color(3, PCE_RGB(2, 3, 7)); /* c3 blue block */
|
|
239
|
+
vce_set_color(4, PCE_RGB(5, 5, 5)); /* c4 wall grey */
|
|
240
|
+
vce_set_color(5, PCE_RGB(1, 2, 4)); /* c5 field blue (clearly *
|
|
241
|
+
* distinct from backdrop) */
|
|
242
|
+
vce_set_color(6, PCE_RGB(1, 0, 2)); /* c6 cabinet purple base + *
|
|
243
|
+
* block frame */
|
|
244
|
+
vce_set_color(7, PCE_RGB(2, 1, 4)); /* c7 cabinet dot */
|
|
245
|
+
|
|
246
|
+
upload_art();
|
|
247
|
+
clear_bat();
|
|
248
|
+
draw_well();
|
|
249
|
+
|
|
250
|
+
for (r = 0; r < ROWS; ++r) for (c = 0; c < COLS; ++c) grid[r][c] = 0;
|
|
251
|
+
score = 0;
|
|
252
|
+
fall_timer = 0;
|
|
253
|
+
rng = 0x1357;
|
|
254
|
+
prev_pad = 0;
|
|
255
|
+
sfx_timer = 0;
|
|
256
|
+
new_piece();
|
|
257
|
+
draw_grid();
|
|
258
|
+
|
|
259
|
+
pce_joy_init();
|
|
260
|
+
bg_enable();
|
|
261
|
+
|
|
262
|
+
for (;;) {
|
|
263
|
+
u8 fall_rate;
|
|
264
|
+
waitvsync();
|
|
265
|
+
|
|
266
|
+
draw_piece(1); /* erase old piece footprint */
|
|
267
|
+
|
|
268
|
+
pad = pce_joy_read();
|
|
269
|
+
if ((pad & PCE_JOY_LEFT) && !(prev_pad & PCE_JOY_LEFT)
|
|
270
|
+
&& !collides((int8_t)(piece_x - 1), piece_y)) piece_x--;
|
|
271
|
+
if ((pad & PCE_JOY_RIGHT) && !(prev_pad & PCE_JOY_RIGHT)
|
|
272
|
+
&& !collides((int8_t)(piece_x + 1), piece_y)) piece_x++;
|
|
273
|
+
if ((pad & PCE_JOY_I) && !(prev_pad & PCE_JOY_I)) {
|
|
274
|
+
u8 t = piece[0]; piece[0] = piece[1]; piece[1] = piece[2]; piece[2] = t;
|
|
275
|
+
psg_tone(1, 0x300, 18); sfx_timer = 3;
|
|
276
|
+
}
|
|
277
|
+
if ((pad & PCE_JOY_II) && !(prev_pad & PCE_JOY_II)) {
|
|
278
|
+
while (!collides(piece_x, (int8_t)(piece_y + 1))) piece_y++;
|
|
279
|
+
lock_piece();
|
|
280
|
+
new_piece();
|
|
281
|
+
psg_tone(1, 0x140, 22); sfx_timer = 4;
|
|
282
|
+
prev_pad = pad;
|
|
283
|
+
if (sfx_timer) { --sfx_timer; }
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
prev_pad = pad;
|
|
287
|
+
|
|
288
|
+
fall_rate = (pad & PCE_JOY_DOWN) ? 4 : 30;
|
|
289
|
+
fall_timer++;
|
|
290
|
+
if (fall_timer >= fall_rate) {
|
|
291
|
+
fall_timer = 0;
|
|
292
|
+
if (collides(piece_x, (int8_t)(piece_y + 1))) {
|
|
293
|
+
lock_piece();
|
|
294
|
+
new_piece();
|
|
295
|
+
} else {
|
|
296
|
+
piece_y++;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
draw_piece(0); /* draw piece at new position */
|
|
301
|
+
|
|
302
|
+
if (sfx_timer) { --sfx_timer; if (sfx_timer == 0) { psg_off(0); psg_off(1); } }
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PC Engine "racing" — a top-down lane-racer scaffold.
|
|
3
|
+
*
|
|
4
|
+
* Drive a car at the bottom of the screen up a 3-lane road. LEFT/RIGHT switch
|
|
5
|
+
* lanes; obstacle cars spawn at the top and slide down toward you. Dodge them —
|
|
6
|
+
* a collision freezes the game for a beat, then auto-resets. The road scrolls
|
|
7
|
+
* (dashed lane stripes animate via the BG Y-scroll register) and speed grows
|
|
8
|
+
* with your distance score. Mirrors the NES/Genesis/SNES/GB/SMS racing
|
|
9
|
+
* scaffolds, translated to the PCE helper API.
|
|
10
|
+
*
|
|
11
|
+
* Cars are hardware sprites; the road (grey lanes between green shoulders with
|
|
12
|
+
* animated dashed lane lines) is the BG tilemap, so the screen is clearly a
|
|
13
|
+
* road scene (clears the verify gate).
|
|
14
|
+
*
|
|
15
|
+
* PCE notes (see pce_hw.h / MENTAL_MODEL.md):
|
|
16
|
+
* - disp_enable() turns on BG + sprites + the VBlank IRQ (waitvsync needs it).
|
|
17
|
+
* - the road scroll is BG Y-scroll: vdc_set_reg(VDC_BYR, scroll).
|
|
18
|
+
* - .bss must be non-empty (pce_video.c's _pce_keep[] covers it).
|
|
19
|
+
*
|
|
20
|
+
* cc65 is C89 — declare locals at the top of a block.
|
|
21
|
+
*/
|
|
22
|
+
#include <pce.h>
|
|
23
|
+
#include <stdint.h> /* int16_t for the per-frame speed step */
|
|
24
|
+
#include "pce_hw.h"
|
|
25
|
+
|
|
26
|
+
/* ---- VRAM layout (word addresses) --------------------------------------- */
|
|
27
|
+
#define BAT_VRAM 0x0000
|
|
28
|
+
#define FONT_VRAM 0x1000
|
|
29
|
+
#define GRASS_VRAM 0x1400 /* shoulder grass (colour 1) */
|
|
30
|
+
#define ROAD_VRAM 0x1410 /* plain road (colour 2) */
|
|
31
|
+
#define DASH_VRAM 0x1420 /* road with a lane dash (colour 3) */
|
|
32
|
+
#define PLAYER_VRAM 0x1800 /* 16x16 player car */
|
|
33
|
+
#define ENEMY_VRAM 0x1840 /* 16x16 enemy car */
|
|
34
|
+
|
|
35
|
+
#define BAT_ENTRY(pal, vram) ((u16)(((pal) << 12) | ((vram) >> 4)))
|
|
36
|
+
|
|
37
|
+
#define LANE_L_X 76
|
|
38
|
+
#define LANE_M_X 120
|
|
39
|
+
#define LANE_R_X 164
|
|
40
|
+
#define PLAYER_Y 176
|
|
41
|
+
#define MAX_OBST 4
|
|
42
|
+
|
|
43
|
+
/* ---- font (digits only) ------------------------------------------------- */
|
|
44
|
+
#define NUM_GLYPHS 10
|
|
45
|
+
static const u8 FONT5x7[NUM_GLYPHS][7] = {
|
|
46
|
+
{0x0E,0x11,0x13,0x15,0x19,0x11,0x0E},
|
|
47
|
+
{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E},
|
|
48
|
+
{0x0E,0x11,0x01,0x02,0x04,0x08,0x1F},
|
|
49
|
+
{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E},
|
|
50
|
+
{0x02,0x06,0x0A,0x12,0x1F,0x02,0x02},
|
|
51
|
+
{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E},
|
|
52
|
+
{0x06,0x08,0x10,0x1E,0x11,0x11,0x0E},
|
|
53
|
+
{0x1F,0x01,0x02,0x04,0x08,0x08,0x08},
|
|
54
|
+
{0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E},
|
|
55
|
+
{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/* ---- state -------------------------------------------------------------- */
|
|
59
|
+
typedef struct { u16 x, y; u8 alive; } Car;
|
|
60
|
+
|
|
61
|
+
static Car player;
|
|
62
|
+
static Car obst[MAX_OBST];
|
|
63
|
+
static u16 score;
|
|
64
|
+
static u8 spawn_timer;
|
|
65
|
+
static u8 crash_timer;
|
|
66
|
+
static u8 player_lane;
|
|
67
|
+
static u8 road_scroll;
|
|
68
|
+
static u16 rng;
|
|
69
|
+
static u8 pad, prev_pad;
|
|
70
|
+
static u8 sfx_timer;
|
|
71
|
+
static u16 tile_buf[16];
|
|
72
|
+
static u16 spr_buf[64];
|
|
73
|
+
|
|
74
|
+
static const u16 lane_x[3] = { LANE_L_X, LANE_M_X, LANE_R_X };
|
|
75
|
+
|
|
76
|
+
static void make_solid_tile(u16 *t, u8 ci) {
|
|
77
|
+
u8 r;
|
|
78
|
+
u8 p0 = (ci & 1) ? 0xFF : 0x00;
|
|
79
|
+
u8 p1 = (ci & 2) ? 0xFF : 0x00;
|
|
80
|
+
u8 p2 = (ci & 4) ? 0xFF : 0x00;
|
|
81
|
+
u8 p3 = (ci & 8) ? 0xFF : 0x00;
|
|
82
|
+
for (r = 0; r < 8; ++r) {
|
|
83
|
+
t[r] = (u16)(p0 | (p1 << 8));
|
|
84
|
+
t[r + 8] = (u16)(p2 | (p3 << 8));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* road tile with a centred lane dash in colour 3 (top half of the tile) */
|
|
89
|
+
static void make_dash_tile(u16 *t) {
|
|
90
|
+
u8 r;
|
|
91
|
+
make_solid_tile(t, 2); /* base road (colour 2) */
|
|
92
|
+
for (r = 0; r < 4; ++r) {
|
|
93
|
+
/* centre 4px (mask 0x18) -> colour 3 (planes 0+1): add plane0 bits */
|
|
94
|
+
t[r] = (u16)((t[r] & 0xFF00) | 0x18 | (t[r] & 0x00FF));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static void make_car_sprite(u16 vram, u8 ci) {
|
|
99
|
+
static const u16 car[16] = {
|
|
100
|
+
0x0660, 0x0660, 0x3FFC, 0x7FFE, 0x7FFE, 0x7FFE, 0x6FF6, 0x6FF6,
|
|
101
|
+
0x7FFE, 0x7FFE, 0x6FF6, 0x6FF6, 0x7FFE, 0x3FFC, 0x6006, 0x6006
|
|
102
|
+
};
|
|
103
|
+
u8 r;
|
|
104
|
+
for (r = 0; r < 64; ++r) spr_buf[r] = 0;
|
|
105
|
+
for (r = 0; r < 16; ++r) {
|
|
106
|
+
if (ci & 1) spr_buf[r] = car[r];
|
|
107
|
+
if (ci & 2) spr_buf[r + 16] = car[r];
|
|
108
|
+
if (ci & 4) spr_buf[r + 32] = car[r];
|
|
109
|
+
}
|
|
110
|
+
load_tiles(vram, spr_buf, 64);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static void upload_font(void) {
|
|
114
|
+
u8 g, row, bits, plane0;
|
|
115
|
+
for (g = 0; g < NUM_GLYPHS; ++g) {
|
|
116
|
+
for (row = 0; row < 16; ++row) tile_buf[row] = 0;
|
|
117
|
+
for (row = 0; row < 7; ++row) {
|
|
118
|
+
bits = FONT5x7[g][row];
|
|
119
|
+
plane0 = 0;
|
|
120
|
+
if (bits & 0x10) plane0 |= 0x40;
|
|
121
|
+
if (bits & 0x08) plane0 |= 0x20;
|
|
122
|
+
if (bits & 0x04) plane0 |= 0x10;
|
|
123
|
+
if (bits & 0x02) plane0 |= 0x08;
|
|
124
|
+
if (bits & 0x01) plane0 |= 0x04;
|
|
125
|
+
tile_buf[row] = (u16)plane0;
|
|
126
|
+
}
|
|
127
|
+
load_tiles((u16)(FONT_VRAM + g * 16), tile_buf, 16);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Paint the road: grass shoulders, grey road in the middle, dashed lane lines
|
|
132
|
+
* between the three lanes. Player X spans ~76..164 -> BAT cols ~9..22. */
|
|
133
|
+
static void draw_road(void) {
|
|
134
|
+
u8 r, c;
|
|
135
|
+
u16 grass = BAT_ENTRY(0, GRASS_VRAM);
|
|
136
|
+
u16 road = BAT_ENTRY(0, ROAD_VRAM);
|
|
137
|
+
u16 dash = BAT_ENTRY(0, DASH_VRAM);
|
|
138
|
+
u16 e;
|
|
139
|
+
for (r = 0; r < 32; ++r) {
|
|
140
|
+
vram_set_write_addr((u16)(BAT_VRAM + r * 32));
|
|
141
|
+
for (c = 0; c < 32; ++c) {
|
|
142
|
+
if (c < 8 || c > 23) {
|
|
143
|
+
e = grass;
|
|
144
|
+
} else if ((c == 12 || c == 17) && (r & 1)) {
|
|
145
|
+
e = dash; /* dashed lane dividers, every other row */
|
|
146
|
+
} else {
|
|
147
|
+
e = road;
|
|
148
|
+
}
|
|
149
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
150
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static void put_glyph(u8 col, u8 row, u8 digit) {
|
|
156
|
+
u16 e = BAT_ENTRY(0, (u16)(FONT_VRAM + digit * 16));
|
|
157
|
+
vram_set_write_addr((u16)(BAT_VRAM + row * 32 + col));
|
|
158
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
159
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static void draw_score(void) {
|
|
163
|
+
u16 v = score;
|
|
164
|
+
u8 d0, d1, d2, d3;
|
|
165
|
+
d3 = (u8)(v % 10); v /= 10;
|
|
166
|
+
d2 = (u8)(v % 10); v /= 10;
|
|
167
|
+
d1 = (u8)(v % 10); v /= 10;
|
|
168
|
+
d0 = (u8)(v % 10);
|
|
169
|
+
put_glyph(1, 1, d0);
|
|
170
|
+
put_glyph(2, 1, d1);
|
|
171
|
+
put_glyph(3, 1, d2);
|
|
172
|
+
put_glyph(4, 1, d3);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static u8 aabb(Car *a, Car *b) {
|
|
176
|
+
return (u8)(a->x < b->x + 14 && a->x + 14 > b->x &&
|
|
177
|
+
a->y < b->y + 14 && a->y + 14 > b->y);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static u16 next_rand(void) {
|
|
181
|
+
rng = (u16)(rng * 25173u + 13849u);
|
|
182
|
+
return rng;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static void reset_run(void) {
|
|
186
|
+
u8 i;
|
|
187
|
+
player_lane = 1;
|
|
188
|
+
player.x = lane_x[1];
|
|
189
|
+
player.y = PLAYER_Y;
|
|
190
|
+
player.alive = 1;
|
|
191
|
+
for (i = 0; i < MAX_OBST; ++i) obst[i].alive = 0;
|
|
192
|
+
score = 0;
|
|
193
|
+
spawn_timer = 0;
|
|
194
|
+
crash_timer = 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static void spawn_obst(void) {
|
|
198
|
+
u8 i;
|
|
199
|
+
for (i = 0; i < MAX_OBST; ++i) {
|
|
200
|
+
if (!obst[i].alive) {
|
|
201
|
+
obst[i].x = lane_x[(next_rand() >> 9) % 3];
|
|
202
|
+
obst[i].y = 0;
|
|
203
|
+
obst[i].alive = 1;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
void main(void) {
|
|
210
|
+
u8 i;
|
|
211
|
+
|
|
212
|
+
_pce_keep[0] = 0;
|
|
213
|
+
|
|
214
|
+
/* palette */
|
|
215
|
+
vce_set_color(0, PCE_RGB(0, 1, 0)); /* backdrop dark green */
|
|
216
|
+
vce_set_color(1, PCE_RGB(1, 5, 1)); /* BG c1: grass */
|
|
217
|
+
vce_set_color(2, PCE_RGB(2, 2, 2)); /* BG c2: road grey */
|
|
218
|
+
vce_set_color(3, PCE_RGB(7, 7, 1)); /* BG c3: yellow lane dash */
|
|
219
|
+
vce_set_color(256, PCE_RGB(0, 0, 0)); /* spr pal0 transparent */
|
|
220
|
+
vce_set_color(257, PCE_RGB(2, 5, 7)); /* spr pal0 c1: cyan player */
|
|
221
|
+
vce_set_color(272, PCE_RGB(0, 0, 0)); /* spr pal1 transparent */
|
|
222
|
+
vce_set_color(273, PCE_RGB(7, 1, 1)); /* spr pal1 c1: red enemy */
|
|
223
|
+
|
|
224
|
+
upload_font();
|
|
225
|
+
make_solid_tile(tile_buf, 1); load_tiles(GRASS_VRAM, tile_buf, 16);
|
|
226
|
+
make_solid_tile(tile_buf, 2); load_tiles(ROAD_VRAM, tile_buf, 16);
|
|
227
|
+
make_dash_tile(tile_buf); load_tiles(DASH_VRAM, tile_buf, 16);
|
|
228
|
+
make_car_sprite(PLAYER_VRAM, 1); /* colour 1 */
|
|
229
|
+
make_car_sprite(ENEMY_VRAM, 1); /* colour 1 (sub-pal 1 = red) */
|
|
230
|
+
|
|
231
|
+
draw_road();
|
|
232
|
+
|
|
233
|
+
rng = 0xBEEF;
|
|
234
|
+
road_scroll = 0;
|
|
235
|
+
prev_pad = 0;
|
|
236
|
+
sfx_timer = 0;
|
|
237
|
+
reset_run();
|
|
238
|
+
draw_score();
|
|
239
|
+
|
|
240
|
+
pce_joy_init();
|
|
241
|
+
disp_enable();
|
|
242
|
+
|
|
243
|
+
for (;;) {
|
|
244
|
+
u8 slot;
|
|
245
|
+
int16_t step;
|
|
246
|
+
waitvsync();
|
|
247
|
+
|
|
248
|
+
/* stage sprites: player + obstacles */
|
|
249
|
+
slot = 0;
|
|
250
|
+
set_sprite(slot++, player.x, player.y, PLAYER_VRAM >> 6, 0);
|
|
251
|
+
for (i = 0; i < MAX_OBST; ++i) {
|
|
252
|
+
u16 ey = obst[i].alive ? obst[i].y : 0x1F0;
|
|
253
|
+
set_sprite(slot++, obst[i].x, ey, ENEMY_VRAM >> 6, 1);
|
|
254
|
+
}
|
|
255
|
+
satb_dma();
|
|
256
|
+
|
|
257
|
+
pad = pce_joy_read();
|
|
258
|
+
|
|
259
|
+
if (crash_timer > 0) {
|
|
260
|
+
crash_timer--;
|
|
261
|
+
if (crash_timer == 0) reset_run();
|
|
262
|
+
prev_pad = pad;
|
|
263
|
+
if (sfx_timer) { --sfx_timer; if (sfx_timer == 0) psg_off(0); }
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* lane switch (edge-triggered) */
|
|
268
|
+
if ((pad & PCE_JOY_LEFT) && !(prev_pad & PCE_JOY_LEFT) && player_lane > 0) { player_lane--; psg_tone(1, 0x2C0, 16); sfx_timer = 3; }
|
|
269
|
+
if ((pad & PCE_JOY_RIGHT) && !(prev_pad & PCE_JOY_RIGHT) && player_lane < 2) { player_lane++; psg_tone(1, 0x2C0, 16); sfx_timer = 3; }
|
|
270
|
+
player.x = lane_x[player_lane];
|
|
271
|
+
prev_pad = pad;
|
|
272
|
+
|
|
273
|
+
/* speed grows with score */
|
|
274
|
+
step = (int16_t)(2 + (score / 400));
|
|
275
|
+
if (step > 5) step = 5;
|
|
276
|
+
|
|
277
|
+
/* scroll the road to sell motion */
|
|
278
|
+
road_scroll = (u8)(road_scroll + step);
|
|
279
|
+
vdc_set_reg(VDC_BYR, (u16)road_scroll);
|
|
280
|
+
|
|
281
|
+
for (i = 0; i < MAX_OBST; ++i) {
|
|
282
|
+
if (!obst[i].alive) continue;
|
|
283
|
+
obst[i].y = (u16)(obst[i].y + step);
|
|
284
|
+
if (obst[i].y >= 216) obst[i].alive = 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
spawn_timer++;
|
|
288
|
+
if (spawn_timer >= 40) { spawn_timer = 0; spawn_obst(); }
|
|
289
|
+
|
|
290
|
+
for (i = 0; i < MAX_OBST; ++i) {
|
|
291
|
+
if (obst[i].alive && aabb(&player, &obst[i])) {
|
|
292
|
+
crash_timer = 70;
|
|
293
|
+
psg_tone(0, 0x080, 28); /* crash buzz */
|
|
294
|
+
sfx_timer = 16;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (score < 9999) score++;
|
|
300
|
+
if ((score & 7) == 0) draw_score();
|
|
301
|
+
|
|
302
|
+
if (sfx_timer) { --sfx_timer; if (sfx_timer == 0) { psg_off(0); psg_off(1); } }
|
|
303
|
+
}
|
|
304
|
+
}
|