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
|
@@ -40,13 +40,27 @@ extern void gg_sat_upload(void);
|
|
|
40
40
|
#define SCREEN_W 160 /* GG visible window is 160 px wide */
|
|
41
41
|
#define VIS_ROWS 24
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
/* ── Game Gear visible viewport ──────────────────────────────────────
|
|
44
|
+
* Only the centered 160x144 of the 256x192 frame shows. The BG is
|
|
45
|
+
* scrolled so world column camX/8 appears at fetch pixel VIS_X0, and the
|
|
46
|
+
* player sprite is drawn at (worldX - camX) + VIS_X0 so it stays aligned
|
|
47
|
+
* with the BG inside the visible window. */
|
|
48
|
+
#define VIS_X0 48
|
|
49
|
+
#define VIS_Y0 24
|
|
50
|
+
#define VIS_X1 207 /* 48 + 160 - 1 */
|
|
51
|
+
#define VIS_Y1 167 /* 24 + 144 - 1 */
|
|
52
|
+
|
|
53
|
+
/* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
|
|
54
|
+
* gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
|
|
55
|
+
* (entries 16-31) reading garbage = invisible sprites. BG colour 1 = entry 1
|
|
56
|
+
* (mid-grey wall); sprite colour 1 = entry 17 (white player). */
|
|
57
|
+
static const uint8_t palette[64] = {
|
|
58
|
+
/* BG 0-15: entry 0 = dark navy backdrop, entry 1 = mid-grey wall */
|
|
59
|
+
0x20,0x02, 0xAA,0x0A, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
60
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
61
|
+
/* SPRITE 16-31: 16=transparent, 17=white player */
|
|
62
|
+
0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
63
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
50
64
|
};
|
|
51
65
|
|
|
52
66
|
static const uint8_t bg_tiles[32 * 2] = {
|
|
@@ -123,7 +137,10 @@ static void paint_initial(void) {
|
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
void main(void) {
|
|
126
|
-
|
|
140
|
+
/* Start above the x=32..88 platform (world y=144) so the player lands on
|
|
141
|
+
* it inside the GG visible vertical band [VIS_Y0..VIS_Y1], not on the
|
|
142
|
+
* floor at world y=176 which sits just below the visible window. */
|
|
143
|
+
int16_t px = 48 << 4, py = 96 << 4;
|
|
127
144
|
int16_t vx = 0, vy = 0;
|
|
128
145
|
int16_t camX = 0, lastCamCol = 0;
|
|
129
146
|
uint8_t prev = 0;
|
|
@@ -170,13 +187,18 @@ void main(void) {
|
|
|
170
187
|
while (camCol > lastCamCol) { lastCamCol++; paint_column(lastCamCol + 31); }
|
|
171
188
|
while (camCol < lastCamCol) { lastCamCol--; paint_column(lastCamCol); }
|
|
172
189
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
*
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
190
|
+
/* Scroll so world column (camX/8) lands at fetch pixel VIS_X0 — the
|
|
191
|
+
* left edge of the GG visible window. (SMS shows the whole 256-px
|
|
192
|
+
* fetch and uses R8 = -camX; the GG only shows the centered 160-px
|
|
193
|
+
* crop, so we bias by +VIS_X0.) */
|
|
194
|
+
gg_vdp_write_reg(8, (uint8_t)((VIS_X0 - camX) & 0xFF));
|
|
195
|
+
|
|
196
|
+
/* Player X is biased into the visible window so it stays pinned to the
|
|
197
|
+
* horizontally-scrolled BG: sx = (worldX - camX) + VIS_X0. The BG is
|
|
198
|
+
* NOT vertically scrolled (R9 = 0), so world rows map 1:1 to fetch rows
|
|
199
|
+
* and the player Y needs no bias — world Y already lands in the visible
|
|
200
|
+
* band [VIS_Y0..VIS_Y1] for the level layout below. */
|
|
201
|
+
sx = (ipx - camX) + VIS_X0;
|
|
180
202
|
gg_sprite_set(0, (uint8_t)sx, (uint8_t)ipy, 0);
|
|
181
203
|
gg_sat_upload();
|
|
182
204
|
|
|
@@ -24,18 +24,31 @@ extern void gg_sprite_init(void);
|
|
|
24
24
|
extern void gg_sprite_set(uint8_t slot, uint8_t x, uint8_t y, uint8_t tile);
|
|
25
25
|
extern void gg_sat_upload(void);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
#define
|
|
27
|
+
/* ── Game Gear visible viewport ──────────────────────────────────────
|
|
28
|
+
* Only the centered 160x144 of the 256x192 frame shows. Lanes and the
|
|
29
|
+
* player must sit inside [VIS_X0..VIS_X1] x [VIS_Y0..VIS_Y1]. */
|
|
30
|
+
#define VIS_X0 48
|
|
31
|
+
#define VIS_Y0 24
|
|
32
|
+
#define VIS_X1 207 /* 48 + 160 - 1 */
|
|
33
|
+
#define VIS_Y1 167 /* 24 + 144 - 1 */
|
|
34
|
+
|
|
35
|
+
#define LANE_LEFT_X (VIS_X0 + 28) /* 76 */
|
|
36
|
+
#define LANE_MID_X ((VIS_X0 + VIS_X1) / 2 - 4) /* ~123 */
|
|
37
|
+
#define LANE_RIGHT_X (VIS_X1 - 36) /* 171 */
|
|
38
|
+
#define PLAYER_Y (VIS_Y1 - 16)
|
|
31
39
|
#define MAX_OBSTACLES 4
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
/* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
|
|
42
|
+
* gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
|
|
43
|
+
* (entries 16-31) reading garbage = invisible sprites. Sprite colour 1 = entry
|
|
44
|
+
* 17 (white), colour 2 = entry 18 (red). */
|
|
45
|
+
static const uint8_t palette[64] = {
|
|
46
|
+
/* BG 0-15: entry 0 = dark navy backdrop */
|
|
47
|
+
0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
48
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
49
|
+
/* SPRITE 16-31: 16=transparent, 17=white, 18=red */
|
|
50
|
+
0,0, 0xFF,0x0F, 0x0F,0x00, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
51
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
39
52
|
};
|
|
40
53
|
|
|
41
54
|
/* Two sprite tiles — player (colour 1) + enemy (colour 2). */
|
|
@@ -86,7 +99,7 @@ static void spawn_obstacle(void) {
|
|
|
86
99
|
for (i = 0; i < MAX_OBSTACLES; i++) {
|
|
87
100
|
if (!obstacles[i].alive) {
|
|
88
101
|
obstacles[i].x = lane_x[(spawn_timer * 13) % 3];
|
|
89
|
-
obstacles[i].y =
|
|
102
|
+
obstacles[i].y = VIS_Y0; /* enter at the top of the visible window */
|
|
90
103
|
obstacles[i].alive = 1;
|
|
91
104
|
return;
|
|
92
105
|
}
|
|
@@ -142,7 +155,7 @@ void main(void) {
|
|
|
142
155
|
for (i = 0; i < MAX_OBSTACLES; i++) {
|
|
143
156
|
if (!obstacles[i].alive) continue;
|
|
144
157
|
obstacles[i].y = (uint8_t)(obstacles[i].y + step);
|
|
145
|
-
if (obstacles[i].y >=
|
|
158
|
+
if (obstacles[i].y >= VIS_Y1) obstacles[i].alive = 0; /* off visible bottom */
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
spawn_timer = (uint8_t)(spawn_timer + 1);
|
|
@@ -46,12 +46,18 @@ extern void gg_sat_upload(void);
|
|
|
46
46
|
#define T_BULLET 1
|
|
47
47
|
#define T_ENEMY 2
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
/* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
|
|
50
|
+
* Entries 0-15 = BG, 16-31 = SPRITE. (The earlier 32-byte SMS-style array was
|
|
51
|
+
* the GG #1 invisible-sprite bug: gg_load_palette reads 64 bytes, so a 32-byte
|
|
52
|
+
* array left the sprite palette (entries 16-31) reading past the array = garbage
|
|
53
|
+
* = invisible sprites.) */
|
|
54
|
+
static const uint8_t palette[64] = {
|
|
55
|
+
/* BG 0-15: entry 0 = dark navy backdrop */
|
|
56
|
+
0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
57
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
58
|
+
/* SPRITE 16-31: 16=transparent, 17=white, 18=yellow, 19=red */
|
|
59
|
+
0,0, 0xFF,0x0F, 0xFF,0x00, 0x0F,0x00, 0,0, 0,0, 0,0, 0,0,
|
|
60
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
static const uint8_t sprite_tiles[32 * 3] = {
|
|
@@ -17,19 +17,32 @@ extern void gg_sprite_init(void);
|
|
|
17
17
|
extern void gg_sprite_set(uint8_t slot, uint8_t x, uint8_t y, uint8_t tile);
|
|
18
18
|
extern void gg_sat_upload(void);
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
/* ── Game Gear visible viewport ──────────────────────────────────────
|
|
21
|
+
* Only the centered 160x144 of the 256x192 frame shows. Keep the whole
|
|
22
|
+
* court inside [VIS_X0..VIS_X1] x [VIS_Y0..VIS_Y1] or it's off-screen. */
|
|
23
|
+
#define VIS_X0 48
|
|
24
|
+
#define VIS_Y0 24
|
|
25
|
+
#define VIS_X1 207 /* 48 + 160 - 1 */
|
|
26
|
+
#define VIS_Y1 167 /* 24 + 144 - 1 */
|
|
27
|
+
|
|
28
|
+
#define COURT_TOP VIS_Y0
|
|
29
|
+
#define COURT_BOT VIS_Y1
|
|
22
30
|
#define PADDLE_H 24
|
|
23
31
|
#define BALL_SIZE 8
|
|
24
|
-
#define PADDLE_X1
|
|
25
|
-
#define PADDLE_X2
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
#define PADDLE_X1 (VIS_X0 + 8) /* near the visible left edge */
|
|
33
|
+
#define PADDLE_X2 (VIS_X1 - 16) /* near the visible right edge */
|
|
34
|
+
|
|
35
|
+
/* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
|
|
36
|
+
* gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
|
|
37
|
+
* (entries 16-31) reading garbage = invisible sprites. Sprite colour 1 = entry
|
|
38
|
+
* 17 (white). */
|
|
39
|
+
static const uint8_t palette[64] = {
|
|
40
|
+
/* BG 0-15: entry 0 = dark navy backdrop */
|
|
41
|
+
0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
42
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
43
|
+
/* SPRITE 16-31: 16=transparent, 17=white */
|
|
44
|
+
0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
45
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
33
46
|
};
|
|
34
47
|
|
|
35
48
|
static const uint8_t tile_solid[32] = {
|
|
@@ -45,15 +58,16 @@ static uint8_t score_p1, score_p2;
|
|
|
45
58
|
static uint8_t serve_timer;
|
|
46
59
|
|
|
47
60
|
static void serve_ball(uint8_t to_left) {
|
|
48
|
-
bx =
|
|
49
|
-
by =
|
|
61
|
+
bx = (VIS_X0 + VIS_X1) / 2;
|
|
62
|
+
by = (VIS_Y0 + VIS_Y1) / 2;
|
|
50
63
|
bdx = to_left ? -2 : 2;
|
|
51
64
|
bdy = ((score_p1 + score_p2) & 1) ? -1 : 1;
|
|
52
65
|
serve_timer = 30;
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
static void reset_match(void) {
|
|
56
|
-
p1y =
|
|
69
|
+
p1y = (VIS_Y0 + VIS_Y1) / 2 - PADDLE_H / 2;
|
|
70
|
+
p2y = p1y;
|
|
57
71
|
score_p1 = 0; score_p2 = 0;
|
|
58
72
|
serve_ball(0);
|
|
59
73
|
}
|
|
@@ -130,8 +144,8 @@ void main(void) {
|
|
|
130
144
|
sfx_tone(0, 250, 3);
|
|
131
145
|
}
|
|
132
146
|
|
|
133
|
-
if (bx <
|
|
134
|
-
if (bx >
|
|
147
|
+
if (bx < VIS_X0) { if (score_p2 < 9) score_p2++; sfx_noise(20); serve_ball(0); }
|
|
148
|
+
if (bx > VIS_X1 - BALL_SIZE) { if (score_p1 < 9) score_p1++; sfx_tone(0, 180, 16); serve_ball(1); }
|
|
135
149
|
}
|
|
136
150
|
} while (1);
|
|
137
151
|
}
|
|
@@ -30,15 +30,28 @@ extern void gg_sat_upload(void);
|
|
|
30
30
|
|
|
31
31
|
#define T_OPEN 0
|
|
32
32
|
#define T_WALL 1
|
|
33
|
-
#define T_SPR 0 /* sprite tile uses sprite-tile-bank (R6=
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
#define T_SPR 0 /* sprite tile uses sprite-tile-bank (R6=0xFF → $2000) */
|
|
34
|
+
|
|
35
|
+
/* ── Game Gear visible viewport ──────────────────────────────────────
|
|
36
|
+
* The 32x24 name table fills the whole 256x192 frame, but only the
|
|
37
|
+
* centered 160x144 SHOWS: fetch pixels [VIS_X0..VIS_X1] x [VIS_Y0..VIS_Y1]
|
|
38
|
+
* (tile columns 6..25, rows 3..20). Place the player sprite inside it. */
|
|
39
|
+
#define VIS_X0 48
|
|
40
|
+
#define VIS_Y0 24
|
|
41
|
+
#define VIS_X1 207 /* 48 + 160 - 1 */
|
|
42
|
+
#define VIS_Y1 167 /* 24 + 144 - 1 */
|
|
43
|
+
|
|
44
|
+
/* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
|
|
45
|
+
* gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
|
|
46
|
+
* (entries 16-31) reading garbage = invisible sprites. BG colour 1 = entry 1
|
|
47
|
+
* (dark grey wall); sprite colour 1 = entry 17 (white player). */
|
|
48
|
+
static const uint8_t palette[64] = {
|
|
49
|
+
/* BG 0-15: entry 0 = dark navy backdrop, entry 1 = dark grey wall */
|
|
50
|
+
0x20,0x02, 0x66,0x06, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
51
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
52
|
+
/* SPRITE 16-31: 16=transparent, 17=white player */
|
|
53
|
+
0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
54
|
+
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
42
55
|
};
|
|
43
56
|
|
|
44
57
|
static const uint8_t bg_tiles[32 * 2] = {
|
|
@@ -92,7 +105,8 @@ static uint8_t solid_at(int16_t px, int16_t py) {
|
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
void main(void) {
|
|
95
|
-
|
|
108
|
+
/* Start the player inside the visible window (tile ~10,10 = pixel 80,80). */
|
|
109
|
+
int16_t px = 80, py = 80;
|
|
96
110
|
|
|
97
111
|
gg_vdp_init();
|
|
98
112
|
gg_load_palette(palette);
|
|
@@ -43,7 +43,13 @@ void main(void) {
|
|
|
43
43
|
sfx_init();
|
|
44
44
|
|
|
45
45
|
for (;;) {
|
|
46
|
-
|
|
46
|
+
/* Lynx frame loop: WAIT for the blitter, then clear with a full-screen
|
|
47
|
+
* tgi_bar (NOT tgi_clear, which leaves the back page stale on this core)
|
|
48
|
+
* — drawing while the blitter is mid-flight loses the frame → black.
|
|
49
|
+
* (Copied from the shmup scaffold, the LYNX-1 fix.) */
|
|
50
|
+
while (tgi_busy()) { }
|
|
51
|
+
tgi_setcolor(COLOR_BLACK);
|
|
52
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
|
|
47
53
|
tgi_setcolor(COLOR_GREY);
|
|
48
54
|
for (i = 0; i < N_PLATFORMS; i++) {
|
|
49
55
|
tgi_bar(platforms[i].x, platforms[i].y, platforms[i].x + platforms[i].w - 1, platforms[i].y + platforms[i].h - 1);
|
|
@@ -62,7 +62,7 @@ static void lock_piece(void) {
|
|
|
62
62
|
a = grid[r][c]; b = grid[r][c+1]; d = grid[r][c+2];
|
|
63
63
|
if (a != 0 && a == b && b == d) {
|
|
64
64
|
grid[r][c] = 0; grid[r][c+1] = 0; grid[r][c+2] = 0;
|
|
65
|
-
if (score <
|
|
65
|
+
if (score < 65500u) score += 30;
|
|
66
66
|
sfx_tone(0, 60, 10);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -93,7 +93,13 @@ void main(void) {
|
|
|
93
93
|
new_piece();
|
|
94
94
|
|
|
95
95
|
for (;;) {
|
|
96
|
-
|
|
96
|
+
/* Lynx frame loop: WAIT for the blitter, then clear with a full-screen
|
|
97
|
+
* tgi_bar (NOT tgi_clear, which leaves the back page stale on this core)
|
|
98
|
+
* — drawing while the blitter is mid-flight loses the frame → black.
|
|
99
|
+
* (Copied from the shmup scaffold, the LYNX-1 fix.) */
|
|
100
|
+
while (tgi_busy()) { }
|
|
101
|
+
tgi_setcolor(COLOR_BLACK);
|
|
102
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
|
|
97
103
|
/* grid */
|
|
98
104
|
for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) {
|
|
99
105
|
if (grid[r][c] != 0) {
|
|
@@ -31,7 +31,13 @@ void main(void) {
|
|
|
31
31
|
for (i = 0; i < MAX_OBS; i++) obs[i].alive = 0;
|
|
32
32
|
|
|
33
33
|
for (;;) {
|
|
34
|
-
|
|
34
|
+
/* Lynx frame loop: WAIT for the blitter, then clear with a full-screen
|
|
35
|
+
* tgi_bar (NOT tgi_clear, which leaves the back page stale on this core)
|
|
36
|
+
* — drawing while the blitter is mid-flight loses the frame → black.
|
|
37
|
+
* (Copied from the shmup scaffold, the LYNX-1 fix.) */
|
|
38
|
+
while (tgi_busy()) { }
|
|
39
|
+
tgi_setcolor(COLOR_BLACK);
|
|
40
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
|
|
35
41
|
/* lane lines */
|
|
36
42
|
tgi_setcolor(COLOR_DARKGREY);
|
|
37
43
|
tgi_line(28, 0, 28, 101);
|
|
@@ -28,7 +28,13 @@ void main(void) {
|
|
|
28
28
|
sfx_init();
|
|
29
29
|
|
|
30
30
|
for (;;) {
|
|
31
|
-
|
|
31
|
+
/* Lynx frame loop: WAIT for the blitter, then clear with a full-screen
|
|
32
|
+
* tgi_bar (NOT tgi_clear, which leaves the back page stale on this core)
|
|
33
|
+
* — drawing while the blitter is mid-flight loses the frame → black.
|
|
34
|
+
* (Copied from the shmup scaffold, the LYNX-1 fix.) */
|
|
35
|
+
while (tgi_busy()) { }
|
|
36
|
+
tgi_setcolor(COLOR_BLACK);
|
|
37
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
|
|
32
38
|
tgi_setcolor(COLOR_WHITE);
|
|
33
39
|
tgi_bar(PADDLE_X1, (unsigned)p1y, PADDLE_X1 + PADDLE_W - 1, (unsigned)(p1y + PADDLE_H - 1));
|
|
34
40
|
tgi_bar(PADDLE_X2, (unsigned)p2y, PADDLE_X2 + PADDLE_W - 1, (unsigned)(p2y + PADDLE_H - 1));
|
package/examples/nes/main.c
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* 5. Enables the PPU and loops forever (NMI does all the real work).
|
|
10
10
|
*
|
|
11
11
|
* BUILD: this file expects linkerConfig: "chr-ram" so the CHR-RAM
|
|
12
|
-
* region is writable at runtime. Pass it to
|
|
12
|
+
* region is writable at runtime. Pass it to build:
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* build({ output: "rom",
|
|
15
15
|
* platform: "nes",
|
|
16
16
|
* language: "c",
|
|
17
17
|
* linkerConfig: "chr-ram",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ── nes_runtime.h — neslib-shaped runtime for cc65 NES builds ───
|
|
2
|
-
* Auto-included on every `
|
|
2
|
+
* Auto-included on every `build({ output: "rom", platform:"nes", language:"c"})`.
|
|
3
3
|
*
|
|
4
4
|
* API mirrors Shiru's neslib so existing tutorials port cleanly:
|
|
5
5
|
*
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
#include "nes_runtime.h"
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/* 4 VISIBLE hues — never 0x0F (black), so a screenshot on ANY phase of the
|
|
17
|
+
* cycle shows a non-black backdrop (NES-2: the old cycle started on + passed
|
|
18
|
+
* through black, so a fresh agent's screenshot could land on a "blank" frame). */
|
|
19
|
+
static const uint8_t bg_colors[4] = { 0x21, 0x01, 0x11, 0x31 };
|
|
17
20
|
|
|
18
21
|
void main(void) {
|
|
19
22
|
uint8_t palette[32];
|
|
@@ -56,6 +56,26 @@ static const uint8_t tile_digits[10 * 16] = {
|
|
|
56
56
|
#define T_CAR_ENEMY 2
|
|
57
57
|
#define T_DIGIT0 3
|
|
58
58
|
|
|
59
|
+
/* ── Background road tiles ───────────────────────────────────────────
|
|
60
|
+
* Default PPUCTRL ($90) reads BG patterns from pattern table 1 ($1000),
|
|
61
|
+
* so these go to CHR $1000+ and are indexed independently of the sprite
|
|
62
|
+
* tiles above. Colour 1 (white, BG palette 0) draws the markings; the
|
|
63
|
+
* grey backdrop (colour 0) is the road surface.
|
|
64
|
+
*
|
|
65
|
+
* BG_T_EDGE: a solid 2px vertical stripe — the road shoulder line.
|
|
66
|
+
* BG_T_LANE: a 2px vertical dash (on for 4 rows, off for 4) — the
|
|
67
|
+
* dashed centre lane marking when stacked down a column. */
|
|
68
|
+
#define BG_T_EDGE 1
|
|
69
|
+
#define BG_T_LANE 2
|
|
70
|
+
static const uint8_t bg_tile_edge[16] = {
|
|
71
|
+
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, /* plane 0 (colour bit 0) */
|
|
72
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
73
|
+
};
|
|
74
|
+
static const uint8_t bg_tile_lane[16] = {
|
|
75
|
+
0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, /* dash: 4 on, 4 off */
|
|
76
|
+
0, 0, 0, 0, 0, 0, 0, 0,
|
|
77
|
+
};
|
|
78
|
+
|
|
59
79
|
static const uint8_t palette[32] = {
|
|
60
80
|
/* BG palettes — light grey backdrop simulates road */
|
|
61
81
|
0x10, 0x30, 0x16, 0x12,
|
|
@@ -93,6 +113,29 @@ static uint8_t aabb(Car *a, Car *b) {
|
|
|
93
113
|
&& a->y < b->y + 8 && a->y + 8 > b->y;
|
|
94
114
|
}
|
|
95
115
|
|
|
116
|
+
/* Draw the static road into nametable 0 ($2000): solid shoulder lines on
|
|
117
|
+
* the outside of the outer lanes and dashed dividers between the three
|
|
118
|
+
* lanes. PPU must be OFF — call from init (uses vram_unsafe_set). Tile
|
|
119
|
+
* columns: lanes sit at 11/15/19, so dividers go at 13/17 and shoulders
|
|
120
|
+
* just outside at 9/21. */
|
|
121
|
+
#define ROAD_TOP_ROW 2
|
|
122
|
+
#define ROAD_BOT_ROW 27
|
|
123
|
+
#define ROAD_EDGE_L 9
|
|
124
|
+
#define ROAD_EDGE_R 21
|
|
125
|
+
#define ROAD_DIV_1 13
|
|
126
|
+
#define ROAD_DIV_2 17
|
|
127
|
+
static void draw_road(void) {
|
|
128
|
+
uint8_t row;
|
|
129
|
+
uint16_t base;
|
|
130
|
+
for (row = ROAD_TOP_ROW; row <= ROAD_BOT_ROW; row++) {
|
|
131
|
+
base = (uint16_t)(0x2000 + (uint16_t)row * 32);
|
|
132
|
+
vram_unsafe_set((uint16_t)(base + ROAD_EDGE_L), BG_T_EDGE);
|
|
133
|
+
vram_unsafe_set((uint16_t)(base + ROAD_EDGE_R), BG_T_EDGE);
|
|
134
|
+
vram_unsafe_set((uint16_t)(base + ROAD_DIV_1), BG_T_LANE);
|
|
135
|
+
vram_unsafe_set((uint16_t)(base + ROAD_DIV_2), BG_T_LANE);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
96
139
|
static void reset_run(void) {
|
|
97
140
|
uint8_t i;
|
|
98
141
|
player_lane = 1;
|
|
@@ -138,7 +181,13 @@ void main(void) {
|
|
|
138
181
|
chr_ram_upload(T_CAR_ENEMY * 16, tile_car_enemy, 16);
|
|
139
182
|
chr_ram_upload(T_DIGIT0 * 16, tile_digits, sizeof(tile_digits));
|
|
140
183
|
|
|
184
|
+
/* BG road tiles live in pattern table 1 ($1000) — that's where the
|
|
185
|
+
* default PPUCTRL ($90) tells the PPU to read background patterns. */
|
|
186
|
+
chr_ram_upload((uint16_t)(0x1000 + BG_T_EDGE * 16), bg_tile_edge, 16);
|
|
187
|
+
chr_ram_upload((uint16_t)(0x1000 + BG_T_LANE * 16), bg_tile_lane, 16);
|
|
188
|
+
|
|
141
189
|
palette_load(palette);
|
|
190
|
+
draw_road(); /* paint the static road while the PPU is off */
|
|
142
191
|
oam_clear();
|
|
143
192
|
ppu_on_all();
|
|
144
193
|
sound_init();
|
|
@@ -198,6 +247,6 @@ void main(void) {
|
|
|
198
247
|
}
|
|
199
248
|
}
|
|
200
249
|
|
|
201
|
-
if (score <
|
|
250
|
+
if (score < 65500u) score++;
|
|
202
251
|
}
|
|
203
252
|
}
|
package/examples/pce/main.c
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Boots to a guaranteed-VISIBLE screen using cc65's conio (text) library, which
|
|
5
5
|
* initializes the HuC6270 VDC + HuC6260 VCE for you and uploads a font to VRAM.
|
|
6
|
-
* Build with:
|
|
6
|
+
* Build with: build({ output: "rom", platform: "pce" }) (language defaults to C).
|
|
7
7
|
*
|
|
8
8
|
* FOOTGUN — the empty-BSS crt0 trap (cc65 pce/crt0.s line 84):
|
|
9
9
|
* The PCE crt0 clears .bss with `tii __BSS_RUN__, __BSS_RUN__+1, __BSS_SIZE__-1`.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - 64 sprite slots × 4 bytes (Y / X / tile / unused)
|
|
12
12
|
*
|
|
13
13
|
* Multi-file project — main.c plus the runtime .c files. Build with:
|
|
14
|
-
*
|
|
14
|
+
* build({ output: "rom", platform:"sms", language:"c",
|
|
15
15
|
* sources: { "main.c": ..., "vdp_init.c": ..., ... },
|
|
16
16
|
* includes: { "sms_hw.h": ... }})
|
|
17
17
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* them by name-table index).
|
|
13
13
|
*
|
|
14
14
|
* Build via createProject({platform:"sms", template:"music_demo"}) or
|
|
15
|
-
*
|
|
15
|
+
* build({ output: "rom", platform:"sms", language:"c", sources:{ "main.c": ...,
|
|
16
16
|
* "sms_music.c": ... , ...runtime... }}).
|
|
17
17
|
*/
|
|
18
18
|
|
|
@@ -126,7 +126,7 @@ static void lock_piece(void) {
|
|
|
126
126
|
a = grid[r][c]; b = grid[r][c + 1]; d = grid[r][c + 2];
|
|
127
127
|
if (a != 0 && a == b && b == d) {
|
|
128
128
|
grid[r][c] = 0; grid[r][c + 1] = 0; grid[r][c + 2] = 0;
|
|
129
|
-
if (score <
|
|
129
|
+
if (score < 65500u) score = (uint16_t)(score + 30);
|
|
130
130
|
sfx_tone(0, 200, 10); /* triple-clear chime */
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -157,7 +157,7 @@ void main(void) {
|
|
|
157
157
|
if (aabb(&bullets[i], &enemies[j])) {
|
|
158
158
|
bullets[i].alive = 0;
|
|
159
159
|
enemies[j].alive = 0;
|
|
160
|
-
if (score <
|
|
160
|
+
if (score < 65500u) score = (uint16_t)(score + 10);
|
|
161
161
|
sfx_noise(8);
|
|
162
162
|
break;
|
|
163
163
|
}
|
|
@@ -199,7 +199,7 @@ void main(void) {
|
|
|
199
199
|
if (p1_bullets[i].alive && aabb(&p1_bullets[i], &enemies[j])) {
|
|
200
200
|
p1_bullets[i].alive = 0;
|
|
201
201
|
enemies[j].alive = 0;
|
|
202
|
-
if (score_p1 <
|
|
202
|
+
if (score_p1 < 65500u) score_p1 = (uint16_t)(score_p1 + 10);
|
|
203
203
|
sfx_noise(8);
|
|
204
204
|
break;
|
|
205
205
|
}
|
|
@@ -209,7 +209,7 @@ void main(void) {
|
|
|
209
209
|
if (p2_bullets[i].alive && aabb(&p2_bullets[i], &enemies[j])) {
|
|
210
210
|
p2_bullets[i].alive = 0;
|
|
211
211
|
enemies[j].alive = 0;
|
|
212
|
-
if (score_p2 <
|
|
212
|
+
if (score_p2 < 65500u) score_p2 = (uint16_t)(score_p2 + 10);
|
|
213
213
|
sfx_noise(8);
|
|
214
214
|
break;
|
|
215
215
|
}
|
package/examples/snes/main.asm
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
; - nmi_safe.asm: vblank handler skeleton
|
|
21
21
|
;
|
|
22
22
|
; BUILD: complete LoROM image, no extra options needed.
|
|
23
|
-
;
|
|
23
|
+
; build({ output: "rom", platform: "snes", source: /* this file */ });
|
|
24
24
|
|
|
25
25
|
lorom
|
|
26
26
|
|