romdevtools 0.13.0 → 0.15.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 +21 -14
- package/CHANGELOG.md +125 -1
- package/README.md +13 -8
- package/examples/atari2600/main.asm +1 -1
- package/examples/atari2600/templates/default.asm +1 -1
- package/examples/atari2600/templates/paddle.asm +59 -47
- package/examples/atari7800/main.c +1 -1
- package/examples/atari7800/templates/default.c +1 -1
- package/examples/atari7800/templates/music_demo.c +1 -1
- package/examples/c64/main.c +1 -1
- package/examples/c64/templates/platformer.c +2 -2
- package/examples/c64/templates/puzzle.c +1 -1
- package/examples/c64/templates/racing.c +3 -3
- package/examples/c64/templates/shmup.c +6 -5
- package/examples/c64/templates/sports.c +4 -4
- package/examples/gb/main.asm +1 -1
- package/examples/gb/main.c +1 -1
- package/examples/gb/templates/puzzle.c +1 -1
- package/examples/gb/templates/racing.c +1 -1
- package/examples/gb/templates/shmup.c +1 -1
- package/examples/gba/templates/gba_hello.c +1 -1
- package/examples/gba/templates/maxmod_demo.c +1 -1
- package/examples/gba/templates/puzzle.c +17 -3
- package/examples/gba/templates/racing.c +16 -2
- package/examples/gba/templates/shmup.c +23 -4
- package/examples/gba/templates/tonc_hello.c +6 -4
- package/examples/gbc/main.asm +1 -1
- package/examples/gbc/templates/puzzle.c +1 -1
- package/examples/gbc/templates/racing.c +1 -1
- package/examples/gbc/templates/shmup.c +1 -1
- package/examples/genesis/main.s +1 -1
- package/examples/genesis/templates/puzzle.c +1 -1
- package/examples/genesis/templates/racing.c +45 -1
- package/examples/genesis/templates/shmup.c +12 -3
- package/examples/genesis/templates/shmup_2p.c +2 -2
- package/examples/genesis/templates/sports.c +39 -0
- package/examples/gg/templates/hello_sprite.c +38 -23
- package/examples/gg/templates/music_demo.c +11 -8
- package/examples/gg/templates/platformer.c +37 -15
- package/examples/gg/templates/racing.c +25 -12
- package/examples/gg/templates/shmup.c +12 -6
- package/examples/gg/templates/sports.c +30 -16
- package/examples/gg/templates/tile_engine.c +24 -10
- package/examples/lynx/templates/platformer.c +7 -1
- package/examples/lynx/templates/puzzle.c +8 -2
- package/examples/lynx/templates/racing.c +7 -1
- package/examples/lynx/templates/sports.c +7 -1
- package/examples/nes/main.c +2 -2
- package/examples/nes/space-shooter/nes_runtime.h +1 -1
- package/examples/nes/templates/default.c +4 -1
- package/examples/nes/templates/racing.c +50 -1
- package/examples/pce/main.c +1 -1
- package/examples/sms/templates/hello_sprite.c +1 -1
- package/examples/sms/templates/music_demo.c +1 -1
- package/examples/sms/templates/puzzle.c +1 -1
- package/examples/sms/templates/racing.c +1 -1
- package/examples/sms/templates/shmup.c +1 -1
- package/examples/sms/templates/shmup_2p.c +2 -2
- package/examples/snes/main.asm +1 -1
- package/examples/snes/templates/c-hello-data.asm +309 -14
- package/examples/snes/templates/c-hello.c +13 -2
- package/examples/snes/templates/default.c +1 -1
- package/examples/snes/templates/hello_sprite-data.asm +300 -2
- package/examples/snes/templates/hello_sprite.c +10 -1
- package/examples/snes/templates/music_demo-data.asm +300 -2
- package/examples/snes/templates/music_demo.c +10 -1
- package/examples/snes/templates/platformer-data.asm +300 -2
- package/examples/snes/templates/platformer.c +10 -1
- package/examples/snes/templates/puzzle-data.asm +300 -2
- package/examples/snes/templates/puzzle.c +11 -1
- package/examples/snes/templates/racing-data.asm +300 -2
- package/examples/snes/templates/racing.c +40 -4
- package/examples/snes/templates/shmup-data.asm +299 -6
- package/examples/snes/templates/shmup.c +11 -7
- package/examples/snes/templates/sports-data.asm +300 -2
- package/examples/snes/templates/sports.c +40 -5
- package/package.json +1 -1
- package/src/cheats/lookup.js +39 -18
- package/src/http/routes.js +58 -33
- package/src/http/skill-doc.js +10 -9
- package/src/http/swagger.js +1 -1
- package/src/http/tool-registry.js +72 -5
- package/src/mcp/server.js +6 -5
- package/src/mcp/state.js +8 -6
- package/src/mcp/tool-manifest.js +7 -7
- package/src/mcp/tools/cheats.js +4 -3
- package/src/mcp/tools/index.js +18 -2
- package/src/mcp/tools/playtest.js +48 -35
- package/src/mcp/tools/project.js +39 -73
- package/src/mcp/tools/rom-id.js +49 -4
- package/src/mcp/tools/tile-inspect.js +1 -1
- package/src/mcp/tools/toolchain.js +183 -19
- package/src/mcp/tools/trace-vram-source.js +3 -3
- package/src/mcp/tools/watch-memory.js +27 -46
- package/src/observer/livestream.html +41 -5
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +5 -5
- package/src/platforms/gb/MENTAL_MODEL.md +3 -3
- package/src/platforms/gb/TROUBLESHOOTING.md +1 -1
- package/src/platforms/gb/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gb/lib/c/README.md +2 -2
- package/src/platforms/gb/lib/c/SDCC_GOTCHAS.md +1 -1
- package/src/platforms/gbc/MENTAL_MODEL.md +3 -3
- package/src/platforms/gbc/TROUBLESHOOTING.md +5 -5
- package/src/platforms/gbc/UPSTREAM_SOURCES.md +2 -2
- package/src/platforms/gbc/lib/c/README.md +2 -2
- package/src/platforms/gbc/lib/c/SDCC_GOTCHAS.md +1 -1
- package/src/platforms/gg/MENTAL_MODEL.md +14 -13
- package/src/platforms/gg/lib/c/vdp_init.c +10 -8
- package/src/platforms/msx/MENTAL_MODEL.md +1 -1
- package/src/platforms/nes/TROUBLESHOOTING.md +1 -1
- package/src/platforms/nes/lib/c/nes_runtime.c +28 -6
- package/src/platforms/pce/MENTAL_MODEL.md +1 -1
- package/src/platforms/pce/lib/c/pce_hw.h +1 -0
- package/src/platforms/pce/lib/c/pce_video.c +26 -0
- package/src/platforms/sms/MENTAL_MODEL.md +12 -12
- package/src/platforms/sms/lib/c/vdp_init.c +10 -8
- package/src/platforms/sms/lib/vdp_init.s +1 -1
- package/src/playtest/playtest.js +25 -0
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +1 -1
- package/src/toolchains/cc65/presets/nes/chr-ram.cfg +1 -1
- package/src/toolchains/cc65/presets/nes/chr-ram.crt0.s +1 -1
- package/src/toolchains/genesis-c/README.md +1 -1
- package/src/toolchains/sdcc/preflight-lint.js +47 -7
- package/src/toolchains/snes-c/snes-c.js +3 -7
package/examples/gb/main.c
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* ── Hello, Game Boy in C — SDCC sm83 port ─────────────────────────
|
|
2
2
|
* Minimal: cycle the BG palette on every vblank.
|
|
3
3
|
*
|
|
4
|
-
* Build:
|
|
4
|
+
* Build: build({ output: "rom", platform: "gb", source: <this file>, language: "c" })
|
|
5
5
|
*
|
|
6
6
|
* SDCC 4.4.0 codegen quirks to avoid in `__sfr __at` register-heavy
|
|
7
7
|
* code:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* pixel that moves left/right via the d-pad.
|
|
6
6
|
*
|
|
7
7
|
* Build via romdev:
|
|
8
|
-
*
|
|
8
|
+
* build({ output: "rom", platform:"gba", language:"c", runtime:"libgba",
|
|
9
9
|
* source: <this file>})
|
|
10
10
|
*
|
|
11
11
|
* NOTE: the DEFAULT GBA runtime is libtonc (use `tonc_hello.c` instead
|
|
@@ -22,6 +22,20 @@
|
|
|
22
22
|
#include <tonc.h>
|
|
23
23
|
#include "gba_sfx.h"
|
|
24
24
|
|
|
25
|
+
/* draw a 5-digit score WITHOUT tte_printf (broken in this libtonc — GBA-1). */
|
|
26
|
+
static void draw_score(int x, unsigned v) {
|
|
27
|
+
char buf[24];
|
|
28
|
+
int i, n = 0;
|
|
29
|
+
buf[n++]='#'; buf[n++]='{'; buf[n++]='P'; buf[n++]=':';
|
|
30
|
+
if (x >= 100) buf[n++] = '0' + (x/100)%10;
|
|
31
|
+
if (x >= 10) buf[n++] = '0' + (x/10)%10;
|
|
32
|
+
buf[n++] = '0' + x%10;
|
|
33
|
+
buf[n++]=','; buf[n++]='8'; buf[n++]='}';
|
|
34
|
+
for (i = 4; i >= 0; i--) { buf[n+i] = '0' + (v % 10); v /= 10; }
|
|
35
|
+
n += 5; buf[n] = 0;
|
|
36
|
+
tte_write(buf);
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
#define COLS 6
|
|
26
40
|
#define ROWS 12
|
|
27
41
|
|
|
@@ -134,7 +148,7 @@ static void lock_piece(void) {
|
|
|
134
148
|
grid[r][c] = 0;
|
|
135
149
|
grid[r][c + 1] = 0;
|
|
136
150
|
grid[r][c + 2] = 0;
|
|
137
|
-
if (score <
|
|
151
|
+
if (score < 65500u) score += 30;
|
|
138
152
|
sfx_tone(1, 1700, 10); /* triple-clear chime */
|
|
139
153
|
}
|
|
140
154
|
}
|
|
@@ -211,7 +225,7 @@ int main(void) {
|
|
|
211
225
|
new_piece();
|
|
212
226
|
prev = now;
|
|
213
227
|
tte_erase_rect(88 + 6*8, 8, 88 + 11*8, 16);
|
|
214
|
-
|
|
228
|
+
draw_score(88 + 6*8, score);
|
|
215
229
|
continue;
|
|
216
230
|
}
|
|
217
231
|
prev = now;
|
|
@@ -231,7 +245,7 @@ int main(void) {
|
|
|
231
245
|
draw_piece(piece_x, piece_y, 0);
|
|
232
246
|
|
|
233
247
|
tte_erase_rect(88 + 6*8, 8, 88 + 11*8, 16);
|
|
234
|
-
|
|
248
|
+
draw_score(88 + 6*8, score);
|
|
235
249
|
}
|
|
236
250
|
return 0;
|
|
237
251
|
}
|
|
@@ -15,6 +15,20 @@
|
|
|
15
15
|
#include <tonc.h>
|
|
16
16
|
#include "gba_sfx.h"
|
|
17
17
|
|
|
18
|
+
/* draw a 5-digit score WITHOUT tte_printf (broken in this libtonc — GBA-1). */
|
|
19
|
+
static void draw_score(int x, unsigned v) {
|
|
20
|
+
char buf[24];
|
|
21
|
+
int i, n = 0;
|
|
22
|
+
buf[n++]='#'; buf[n++]='{'; buf[n++]='P'; buf[n++]=':';
|
|
23
|
+
if (x >= 100) buf[n++] = '0' + (x/100)%10;
|
|
24
|
+
if (x >= 10) buf[n++] = '0' + (x/10)%10;
|
|
25
|
+
buf[n++] = '0' + x%10;
|
|
26
|
+
buf[n++]=','; buf[n++]='8'; buf[n++]='}';
|
|
27
|
+
for (i = 4; i >= 0; i--) { buf[n+i] = '0' + (v % 10); v /= 10; }
|
|
28
|
+
n += 5; buf[n] = 0;
|
|
29
|
+
tte_write(buf);
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
#define LANE_LEFT_X 56
|
|
19
33
|
#define LANE_MID_X 116
|
|
20
34
|
#define LANE_RIGHT_X 176
|
|
@@ -149,7 +163,7 @@ int main(void) {
|
|
|
149
163
|
}
|
|
150
164
|
}
|
|
151
165
|
|
|
152
|
-
if (score <
|
|
166
|
+
if (score < 65500u) score++;
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
/* Sprite slots: 0 = player, 1..4 = obstacles. */
|
|
@@ -169,7 +183,7 @@ int main(void) {
|
|
|
169
183
|
oam_copy(oam_mem, obj_buffer, 1 + MAX_OBSTACLES);
|
|
170
184
|
|
|
171
185
|
tte_erase_rect(160 + 6*8, 8, 160 + 11*8, 16);
|
|
172
|
-
|
|
186
|
+
draw_score(160 + 6*8, score);
|
|
173
187
|
}
|
|
174
188
|
return 0;
|
|
175
189
|
}
|
|
@@ -29,6 +29,26 @@
|
|
|
29
29
|
#define TILE_BULLET 2
|
|
30
30
|
#define TILE_ENEMY 3
|
|
31
31
|
|
|
32
|
+
/* Draw a 5-digit score at pixel (x,8) WITHOUT tte_printf. The bundled libtonc's
|
|
33
|
+
* tte_printf with a %d/%05d conversion is broken (it routes through a vsnprintf
|
|
34
|
+
* path that isn't wired in this build — it garbles the output AND wedges the
|
|
35
|
+
* game loop when called per-frame, GBA-1). We build the string ourselves and
|
|
36
|
+
* use tte_write, which processes the #{P:x,y} position command but does NO
|
|
37
|
+
* format conversion → safe every frame. */
|
|
38
|
+
static void draw_score(int x, unsigned v) {
|
|
39
|
+
char buf[24];
|
|
40
|
+
int i, n = 0;
|
|
41
|
+
/* "#{P:<x>,8}" position command, then 5 decimal digits. */
|
|
42
|
+
buf[n++]='#'; buf[n++]='{'; buf[n++]='P'; buf[n++]=':';
|
|
43
|
+
if (x >= 100) buf[n++] = '0' + (x/100)%10;
|
|
44
|
+
if (x >= 10) buf[n++] = '0' + (x/10)%10;
|
|
45
|
+
buf[n++] = '0' + x%10;
|
|
46
|
+
buf[n++]=','; buf[n++]='8'; buf[n++]='}';
|
|
47
|
+
for (i = 4; i >= 0; i--) { buf[n+i] = '0' + (v % 10); v /= 10; }
|
|
48
|
+
n += 5; buf[n] = 0;
|
|
49
|
+
tte_write(buf);
|
|
50
|
+
}
|
|
51
|
+
|
|
32
52
|
/* 4bpp tiles (8 rows × 32 bits each = 32 bytes). Each nibble is a
|
|
33
53
|
* palette index. Index 0 = transparent. */
|
|
34
54
|
static const u32 tile_ship[8] = {
|
|
@@ -153,7 +173,7 @@ int main(void) {
|
|
|
153
173
|
if (aabb_hit(&bullets[i], &enemies[j])) {
|
|
154
174
|
bullets[i].alive = 0;
|
|
155
175
|
enemies[j].alive = 0;
|
|
156
|
-
if (score <
|
|
176
|
+
if (score < 65500u) score += 10;
|
|
157
177
|
sfx_noise(6); /* explosion */
|
|
158
178
|
break;
|
|
159
179
|
}
|
|
@@ -188,10 +208,9 @@ int main(void) {
|
|
|
188
208
|
|
|
189
209
|
oam_copy(oam_mem, obj_buffer, 128);
|
|
190
210
|
|
|
191
|
-
/* Score: 5
|
|
192
|
-
* writes — safe to call once per frame. */
|
|
211
|
+
/* Score: 5 digits via draw_score (NOT tte_printf — see GBA-1). */
|
|
193
212
|
tte_erase_rect(8 + 6*8, 8, 8 + 11*8, 16);
|
|
194
|
-
|
|
213
|
+
draw_score(8 + 6*8, score);
|
|
195
214
|
}
|
|
196
215
|
return 0;
|
|
197
216
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* canonical "Hello GBA" pattern from gbadev.net/tonc.
|
|
6
6
|
*
|
|
7
7
|
* Build via romdev:
|
|
8
|
-
*
|
|
8
|
+
* build({ output: "rom", platform:"gba", language:"c", source: <this file>})
|
|
9
9
|
*
|
|
10
10
|
* (defaults to runtime:"libtonc" — pass {runtime:"libgba"} to use
|
|
11
11
|
* devkitPro's libgba instead, or {runtime:"none"} for bare gcc.)
|
|
@@ -59,9 +59,11 @@ int main(void) {
|
|
|
59
59
|
tte_write("Hello, Tonc!\n");
|
|
60
60
|
tte_write("Built with romdev\n");
|
|
61
61
|
|
|
62
|
-
/*
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
/* NOTE: tte_printf with a %d/%05d conversion is broken in this libtonc
|
|
63
|
+
* build (it garbles output + can wedge the loop — GBA-1). For dynamic
|
|
64
|
+
* numbers, build the string yourself and tte_write it (see the genre
|
|
65
|
+
* scaffolds' draw_score). For static text just tte_write a literal: */
|
|
66
|
+
tte_write("#{P:32,80}Year: 2026\n");
|
|
65
67
|
|
|
66
68
|
/* Game loop. VBlankIntrWait() halts the CPU until next vblank —
|
|
67
69
|
* saves battery on real hardware. */
|
package/examples/gbc/main.asm
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
; - palette 0 entry 0 = backdrop (blue here), entry 1 = yellow
|
|
8
8
|
;
|
|
9
9
|
; Build with:
|
|
10
|
-
;
|
|
10
|
+
; build({ output: "rom", platform:"gbc", source: <this>})
|
|
11
11
|
;
|
|
12
12
|
; rgbfix is invoked automatically to fix the header checksums.
|
|
13
13
|
|
package/examples/genesis/main.s
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
; 7. Enable display and park forever.
|
|
11
11
|
;
|
|
12
12
|
; BUILD:
|
|
13
|
-
;
|
|
13
|
+
; build({ output: "rom", platform: "genesis", source: /* this file */ });
|
|
14
14
|
;
|
|
15
15
|
; vasm syntax gotchas (in case you go beyond this scaffold):
|
|
16
16
|
; - NO space after commas in operands: `move.w #$2700, sr` FAILS.
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
#define T_CAR_P1 (TILE_USER_INDEX + 0)
|
|
29
29
|
#define T_CAR_EN (TILE_USER_INDEX + 1)
|
|
30
|
+
#define T_LANE (TILE_USER_INDEX + 2) /* dashed lane divider (BG_B) */
|
|
31
|
+
#define T_EDGE (TILE_USER_INDEX + 3) /* solid road edge (BG_B) */
|
|
30
32
|
|
|
31
33
|
static const u32 tile_car_p1[8] = {
|
|
32
34
|
0x01111110, 0x11111111, 0x12222221, 0x11111111,
|
|
@@ -36,6 +38,40 @@ static const u32 tile_car_enemy[8] = {
|
|
|
36
38
|
0x03333330, 0x33333333, 0x34444443, 0x33333333,
|
|
37
39
|
0x33333333, 0x34444443, 0x33333333, 0x03300330,
|
|
38
40
|
};
|
|
41
|
+
/* Dashed lane-divider segment (colour 2 = grey): a 2px dash in the
|
|
42
|
+
* centre columns, on/off vertically so a stacked column reads as a
|
|
43
|
+
* dashed road centre-line. */
|
|
44
|
+
static const u32 tile_lane[8] = {
|
|
45
|
+
0x00022000, 0x00022000, 0x00022000, 0x00000000,
|
|
46
|
+
0x00000000, 0x00022000, 0x00022000, 0x00022000,
|
|
47
|
+
};
|
|
48
|
+
/* Solid 2px road-edge stripe (colour 2 = grey) down the right side of
|
|
49
|
+
* the tile — used on the left rail; mirrored (hflip) for the right. */
|
|
50
|
+
static const u32 tile_edge[8] = {
|
|
51
|
+
0x00000022, 0x00000022, 0x00000022, 0x00000022,
|
|
52
|
+
0x00000022, 0x00000022, 0x00000022, 0x00000022,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/* The road lives on BG_B (8×8 cells). Two dashed dividers sit between the
|
|
56
|
+
* three lanes; solid edges frame the outermost lanes. */
|
|
57
|
+
#define ROAD_TOP_ROW 1
|
|
58
|
+
#define ROAD_BOT_ROW 26
|
|
59
|
+
#define LANE_DIV1_COL ((LANE_LEFT_X + 8 + LANE_MID_X) / 16)
|
|
60
|
+
#define LANE_DIV2_COL ((LANE_MID_X + 8 + LANE_RIGHT_X) / 16)
|
|
61
|
+
#define ROAD_EDGE_L ((LANE_LEFT_X - 12) / 8)
|
|
62
|
+
#define ROAD_EDGE_R ((LANE_RIGHT_X + 12) / 8)
|
|
63
|
+
|
|
64
|
+
static void draw_road(void) {
|
|
65
|
+
s16 r;
|
|
66
|
+
for (r = ROAD_TOP_ROW; r <= ROAD_BOT_ROW; r++) {
|
|
67
|
+
/* Left edge (stripe on its right), right edge (hflipped). */
|
|
68
|
+
VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_EDGE), ROAD_EDGE_L, r);
|
|
69
|
+
VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 1, T_EDGE), ROAD_EDGE_R, r);
|
|
70
|
+
/* Two dashed lane dividers. */
|
|
71
|
+
VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_LANE), LANE_DIV1_COL, r);
|
|
72
|
+
VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_LANE), LANE_DIV2_COL, r);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
39
75
|
|
|
40
76
|
typedef struct { s16 x, y; bool alive; } Car;
|
|
41
77
|
|
|
@@ -97,6 +133,11 @@ int main(bool hard) {
|
|
|
97
133
|
|
|
98
134
|
VDP_loadTileData(tile_car_p1, T_CAR_P1, 1, DMA);
|
|
99
135
|
VDP_loadTileData(tile_car_enemy, T_CAR_EN, 1, DMA);
|
|
136
|
+
VDP_loadTileData(tile_lane, T_LANE, 1, DMA);
|
|
137
|
+
VDP_loadTileData(tile_edge, T_EDGE, 1, DMA);
|
|
138
|
+
|
|
139
|
+
/* Draw the static road (edges + dashed lane dividers) once on BG_B. */
|
|
140
|
+
draw_road();
|
|
100
141
|
|
|
101
142
|
VDP_drawText("SCORE", 28, 2);
|
|
102
143
|
VDP_drawText("L/R MOVES LANE", 13, 27);
|
|
@@ -149,7 +190,7 @@ int main(bool hard) {
|
|
|
149
190
|
}
|
|
150
191
|
}
|
|
151
192
|
|
|
152
|
-
if (score <
|
|
193
|
+
if (score < 65500u) score++;
|
|
153
194
|
}
|
|
154
195
|
|
|
155
196
|
/* SAT update — player + up to 4 obstacles = 5 sprites. */
|
|
@@ -160,6 +201,9 @@ int main(bool hard) {
|
|
|
160
201
|
VDP_setSprite(slot++, obstacles[i].x, ey, SPRITE_SIZE(1, 1),
|
|
161
202
|
TILE_ATTR_FULL(PAL1, 1, 0, 0, T_CAR_EN));
|
|
162
203
|
}
|
|
204
|
+
/* Link slots 0..slot-1 so the VDP's SAT walk draws all of them — without
|
|
205
|
+
* this the link bytes stay 0 (= end-of-list) and only slot 0 renders. */
|
|
206
|
+
VDP_linkSprites(0, slot);
|
|
163
207
|
VDP_updateSprites(slot, DMA);
|
|
164
208
|
|
|
165
209
|
render_score();
|
|
@@ -81,11 +81,15 @@ static void fire_bullet(void) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
static u16 spawn_seed; /* free-running, advances every spawn (NOT spawn_timer,
|
|
85
|
+
* which is always 28 at the spawn call → one column) */
|
|
84
86
|
static void spawn_enemy(void) {
|
|
85
87
|
for (u16 i = 0; i < MAX_ENEMIES; i++) {
|
|
86
88
|
if (!enemies[i].alive) {
|
|
87
|
-
/* Cheap
|
|
88
|
-
|
|
89
|
+
/* Cheap LCG-ish spread across the playfield so enemies don't all
|
|
90
|
+
* descend in a single column. */
|
|
91
|
+
spawn_seed = (u16)(spawn_seed * 1103 + 12345);
|
|
92
|
+
enemies[i].x = (s16)((spawn_seed >> 4) % (320 - 16) + 8);
|
|
89
93
|
enemies[i].y = -8;
|
|
90
94
|
enemies[i].alive = TRUE;
|
|
91
95
|
return;
|
|
@@ -157,7 +161,7 @@ int main(bool hard) {
|
|
|
157
161
|
if (aabb_hit(&bullets[i], &enemies[j])) {
|
|
158
162
|
bullets[i].alive = FALSE;
|
|
159
163
|
enemies[j].alive = FALSE;
|
|
160
|
-
if (score <
|
|
164
|
+
if (score < 65500u) score += 10;
|
|
161
165
|
sfx_noise(8); /* explosion */
|
|
162
166
|
break;
|
|
163
167
|
}
|
|
@@ -177,6 +181,11 @@ int main(bool hard) {
|
|
|
177
181
|
VDP_setSprite(7 + i, enemies[i].x, ey, SPRITE_SIZE(1, 1),
|
|
178
182
|
TILE_ATTR_FULL(PAL2, 1, 0, 0, T_ENEMY));
|
|
179
183
|
}
|
|
184
|
+
/* CHAIN the sprite linked list before uploading: VDP_setSprite does NOT
|
|
185
|
+
* set the link byte, and the SAT link bytes init to 0 (= "end of list"),
|
|
186
|
+
* so the VDP's sprite walk stops after slot 0 → only ONE sprite draws.
|
|
187
|
+
* VDP_linkSprites(0, N) links slots 0..N-1 so all N render. */
|
|
188
|
+
VDP_linkSprites(0, 1 + MAX_BULLETS + MAX_ENEMIES);
|
|
180
189
|
VDP_updateSprites(1 + MAX_BULLETS + MAX_ENEMIES, DMA);
|
|
181
190
|
|
|
182
191
|
render_score();
|
|
@@ -196,7 +196,7 @@ int main(bool hard) {
|
|
|
196
196
|
if (p1_bullets[i].alive && aabb(&p1_bullets[i], &enemies[j])) {
|
|
197
197
|
p1_bullets[i].alive = FALSE;
|
|
198
198
|
enemies[j].alive = FALSE;
|
|
199
|
-
if (score_p1 <
|
|
199
|
+
if (score_p1 < 65500u) score_p1 += 10;
|
|
200
200
|
sfx_noise(8);
|
|
201
201
|
break;
|
|
202
202
|
}
|
|
@@ -206,7 +206,7 @@ int main(bool hard) {
|
|
|
206
206
|
if (p2_bullets[i].alive && aabb(&p2_bullets[i], &enemies[j])) {
|
|
207
207
|
p2_bullets[i].alive = FALSE;
|
|
208
208
|
enemies[j].alive = FALSE;
|
|
209
|
-
if (score_p2 <
|
|
209
|
+
if (score_p2 < 65500u) score_p2 += 10;
|
|
210
210
|
sfx_noise(8);
|
|
211
211
|
break;
|
|
212
212
|
}
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
|
|
33
33
|
#define T_PADDLE (TILE_USER_INDEX + 0)
|
|
34
34
|
#define T_BALL (TILE_USER_INDEX + 1)
|
|
35
|
+
#define T_RAIL (TILE_USER_INDEX + 2) /* solid court rail (BG_B) */
|
|
36
|
+
#define T_NET (TILE_USER_INDEX + 3) /* dashed centre-line segment */
|
|
35
37
|
|
|
36
38
|
/* 4bpp 8×8 tile, all colour 1 → solid white block. */
|
|
37
39
|
static const u32 tile_solid[8] = {
|
|
@@ -39,6 +41,35 @@ static const u32 tile_solid[8] = {
|
|
|
39
41
|
0x11111111, 0x11111111, 0x11111111, 0x11111111,
|
|
40
42
|
};
|
|
41
43
|
|
|
44
|
+
/* Centre-net segment: a 2px-wide vertical dash down the middle of an 8×8
|
|
45
|
+
* tile (colour 1 in the centre columns, transparent elsewhere). Stacked
|
|
46
|
+
* down the court's centre column it reads as a dashed Pong net. */
|
|
47
|
+
static const u32 tile_net[8] = {
|
|
48
|
+
0x00011000, 0x00011000, 0x00011000, 0x00000000,
|
|
49
|
+
0x00011000, 0x00011000, 0x00011000, 0x00000000,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/* The court lives on BG_B (cells are 8×8): top + bottom rails plus a
|
|
53
|
+
* dashed centre net. 320px = 40 cols, COURT_TOP/BOT are pixel rows. */
|
|
54
|
+
#define COURT_COL_L (PADDLE_X1 / 8)
|
|
55
|
+
#define COURT_COL_R (PADDLE_X2 / 8)
|
|
56
|
+
#define COURT_ROW_TOP (COURT_TOP / 8)
|
|
57
|
+
#define COURT_ROW_BOT (COURT_BOT / 8 - 1)
|
|
58
|
+
#define COURT_NET_COL (COURT_W / 16)
|
|
59
|
+
|
|
60
|
+
static void draw_court(void) {
|
|
61
|
+
s16 c, r;
|
|
62
|
+
/* Top + bottom rails span the playfield width. */
|
|
63
|
+
for (c = COURT_COL_L; c <= COURT_COL_R; c++) {
|
|
64
|
+
VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_RAIL), c, COURT_ROW_TOP);
|
|
65
|
+
VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_RAIL), c, COURT_ROW_BOT);
|
|
66
|
+
}
|
|
67
|
+
/* Dashed centre net between the rails. */
|
|
68
|
+
for (r = COURT_ROW_TOP + 1; r < COURT_ROW_BOT; r++) {
|
|
69
|
+
VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_NET), COURT_NET_COL, r);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
42
73
|
static s16 p1y, p2y; /* paddle top Y, pixels */
|
|
43
74
|
static s16 bx, by; /* ball top-left, pixels */
|
|
44
75
|
static s16 bdx, bdy;
|
|
@@ -77,6 +108,11 @@ int main(bool hard) {
|
|
|
77
108
|
|
|
78
109
|
VDP_loadTileData(tile_solid, T_PADDLE, 1, DMA);
|
|
79
110
|
VDP_loadTileData(tile_solid, T_BALL, 1, DMA);
|
|
111
|
+
VDP_loadTileData(tile_solid, T_RAIL, 1, DMA);
|
|
112
|
+
VDP_loadTileData(tile_net, T_NET, 1, DMA);
|
|
113
|
+
|
|
114
|
+
/* Draw the static court (rails + centre net) once on BG_B. */
|
|
115
|
+
draw_court();
|
|
80
116
|
|
|
81
117
|
VDP_drawText("PLAYER 1", 2, 1);
|
|
82
118
|
VDP_drawText("PLAYER 2", 28, 1);
|
|
@@ -161,6 +197,9 @@ int main(bool hard) {
|
|
|
161
197
|
}
|
|
162
198
|
VDP_setSprite(slot++, bx, by, SPRITE_SIZE(1, 1),
|
|
163
199
|
TILE_ATTR_FULL(PAL0, 1, 0, 0, T_BALL));
|
|
200
|
+
/* Link slots 0..slot-1 so the VDP's SAT walk draws all of them — without
|
|
201
|
+
* this the link bytes stay 0 (= end-of-list) and only slot 0 renders. */
|
|
202
|
+
VDP_linkSprites(0, slot);
|
|
164
203
|
VDP_updateSprites(slot, DMA);
|
|
165
204
|
|
|
166
205
|
render_scores();
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
/* ── hello_sprite.c —
|
|
1
|
+
/* ── hello_sprite.c — Game Gear starter (one sprite + d-pad) ────────
|
|
2
2
|
*
|
|
3
|
-
* Drives one sprite around the
|
|
4
|
-
* Uses the bundled
|
|
5
|
-
* gg_load_palette,
|
|
3
|
+
* Drives one sprite around the Game Gear screen with the directional
|
|
4
|
+
* pad. Uses the bundled GG runtime helpers (gg_vdp_init, gg_load_tiles,
|
|
5
|
+
* gg_load_palette, gg_sprite_*, gg_vblank_wait, gg_joypad_read).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* - 256×192
|
|
7
|
+
* GG hardware notes the templates assume:
|
|
8
|
+
* - 256×192 internal frame; only the centered 160×144 region SHOWS.
|
|
9
|
+
* Keep gameplay sprites inside [VIS_X0..VIS_X1] x [VIS_Y0..VIS_Y1].
|
|
9
10
|
* - Sprite attribute table at VRAM $3F00 (configured by gg_vdp_init)
|
|
10
|
-
* - Sprite tile data at VRAM $2000 (R6 =
|
|
11
|
+
* - Sprite tile data at VRAM $2000 (R6 = 0xFF → SA13 set → $2000)
|
|
11
12
|
* - 64 sprite slots × 4 bytes (Y / X / tile / unused)
|
|
12
13
|
*
|
|
13
14
|
* Multi-file project — main.c plus the runtime .c files. Build with:
|
|
14
|
-
*
|
|
15
|
+
* build({ output: "rom", platform:"gg", language:"c",
|
|
15
16
|
* sources: { "main.c": ..., "vdp_init.c": ..., ... },
|
|
16
17
|
* includes: { "gg_hw.h": ... }})
|
|
17
18
|
*
|
|
18
|
-
* createProject({platform:"
|
|
19
|
+
* createProject({platform:"gg", template:"hello_sprite"}) copies all
|
|
19
20
|
* the bits into your project tree.
|
|
20
21
|
*/
|
|
21
22
|
#include "gg_hw.h"
|
|
@@ -34,16 +35,29 @@ extern void gg_sprite_init(void);
|
|
|
34
35
|
extern void gg_sprite_set(uint8_t slot, uint8_t x, uint8_t y, uint8_t tile);
|
|
35
36
|
extern void gg_sat_upload(void);
|
|
36
37
|
|
|
37
|
-
/*
|
|
38
|
-
*
|
|
39
|
-
* SMS
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
/* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
|
|
39
|
+
* Entries 0-15 = BG, 16-31 = SPRITE. gg_load_palette reads 64 bytes, so a
|
|
40
|
+
* 32-byte SMS-style array leaves the sprite palette (16-31) reading past the
|
|
41
|
+
* array = garbage = INVISIBLE sprites. Sprite colour index N uses entry 16+N,
|
|
42
|
+
* so sprite colour 1 = entry 17 (white here). */
|
|
43
|
+
static const uint8_t palette[64] = {
|
|
44
|
+
/* BG 0-15: entry 0 = dark navy backdrop */
|
|
45
|
+
0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
46
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
47
|
+
/* SPRITE 16-31: 16=transparent, 17=white */
|
|
48
|
+
0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
49
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
45
50
|
};
|
|
46
51
|
|
|
52
|
+
/* ── Game Gear visible viewport ──────────────────────────────────────
|
|
53
|
+
* Sprite OAM uses SMS HARDWARE coordinates (256x192 space), but the GG
|
|
54
|
+
* LCD only shows the CENTER 160x144. Keep the sprite inside this box or
|
|
55
|
+
* it's placed "correctly" in hardware yet INVISIBLE on screen. */
|
|
56
|
+
#define VIS_X0 48
|
|
57
|
+
#define VIS_Y0 24
|
|
58
|
+
#define VIS_X1 207 /* 48 + 160 - 1 */
|
|
59
|
+
#define VIS_Y1 167 /* 24 + 144 - 1 */
|
|
60
|
+
|
|
47
61
|
/* One 8×8 sprite tile (4bpp interleaved). Filled square in color 1. */
|
|
48
62
|
static const uint8_t sprite_tile[32] = {
|
|
49
63
|
0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
|
|
@@ -53,8 +67,8 @@ static const uint8_t sprite_tile[32] = {
|
|
|
53
67
|
};
|
|
54
68
|
|
|
55
69
|
void main(void) {
|
|
56
|
-
uint8_t x =
|
|
57
|
-
uint8_t y =
|
|
70
|
+
uint8_t x = (VIS_X0 + VIS_X1) / 2; /* center of the visible window */
|
|
71
|
+
uint8_t y = (VIS_Y0 + VIS_Y1) / 2;
|
|
58
72
|
uint8_t prev = 0;
|
|
59
73
|
|
|
60
74
|
gg_vdp_init();
|
|
@@ -80,10 +94,11 @@ void main(void) {
|
|
|
80
94
|
gg_sat_upload();
|
|
81
95
|
|
|
82
96
|
pad = gg_joypad_read();
|
|
83
|
-
|
|
84
|
-
if (pad &
|
|
85
|
-
if (pad &
|
|
86
|
-
if (pad &
|
|
97
|
+
/* Clamp to the visible window so the sprite never slides off-screen. */
|
|
98
|
+
if (pad & JOY_LEFT && x > VIS_X0) x = (uint8_t)(x - 2);
|
|
99
|
+
if (pad & JOY_RIGHT && x < VIS_X1 - 8) x = (uint8_t)(x + 2);
|
|
100
|
+
if (pad & JOY_UP && y > VIS_Y0) y = (uint8_t)(y - 2);
|
|
101
|
+
if (pad & JOY_DOWN && y < VIS_Y1 - 8) y = (uint8_t)(y + 2);
|
|
87
102
|
prev = pad;
|
|
88
103
|
(void)prev;
|
|
89
104
|
} while (1);
|
|
@@ -36,14 +36,17 @@ extern void gg_sprite_init(void);
|
|
|
36
36
|
extern void gg_sprite_set(uint8_t slot, uint8_t x, uint8_t y, uint8_t tile);
|
|
37
37
|
extern void gg_sat_upload(void);
|
|
38
38
|
|
|
39
|
-
/*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
/* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
|
|
40
|
+
* gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
|
|
41
|
+
* (entries 16-31) reading garbage = invisible sprites. Sprite palette:
|
|
42
|
+
* 16 = transparent, 17 = white, 18 = green, 19 = red. */
|
|
43
|
+
static const uint8_t palette[64] = {
|
|
44
|
+
/* BG 0-15: entry 0 = dark navy backdrop */
|
|
45
|
+
0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
46
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
47
|
+
/* SPRITE 16-31: 16=transparent, 17=white, 18=green, 19=red */
|
|
48
|
+
0,0, 0xFF,0x0F, 0xF0,0x00, 0x0F,0x00, 0,0, 0,0, 0,0, 0,0,
|
|
49
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
/* Three 8×8 sprite tiles, 4bpp interleaved (4 planes × 8 rows):
|