romdevtools 0.16.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 +60 -12
- package/CHANGELOG.md +258 -0
- 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/tool-registry.js +26 -1
- 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/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 +76 -3
- 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/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
|
@@ -11,6 +11,7 @@ extern void gg_vdp_init(void);
|
|
|
11
11
|
extern void gg_vdp_display_on(void);
|
|
12
12
|
extern void gg_load_palette(const uint8_t *palette);
|
|
13
13
|
extern void gg_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
|
|
14
|
+
extern void gg_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
|
|
14
15
|
extern void gg_vblank_wait(void);
|
|
15
16
|
extern uint8_t gg_joypad_read(void);
|
|
16
17
|
extern void gg_sprite_init(void);
|
|
@@ -37,8 +38,8 @@ extern void gg_sat_upload(void);
|
|
|
37
38
|
* (entries 16-31) reading garbage = invisible sprites. Sprite colour 1 = entry
|
|
38
39
|
* 17 (white). */
|
|
39
40
|
static const uint8_t palette[64] = {
|
|
40
|
-
/* BG 0-15:
|
|
41
|
-
0x20,0x02,
|
|
41
|
+
/* BG 0-15: 0 = dark navy backdrop, 1 = court green, 2 = court line white */
|
|
42
|
+
0x20,0x02, 0x60,0x00, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
42
43
|
0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
43
44
|
/* SPRITE 16-31: 16=transparent, 17=white */
|
|
44
45
|
0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
|
|
@@ -52,6 +53,42 @@ static const uint8_t tile_solid[32] = {
|
|
|
52
53
|
0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
|
|
53
54
|
};
|
|
54
55
|
|
|
56
|
+
/* Three BG tiles for the court, loaded into the BG tile bank at $0000:
|
|
57
|
+
* tile 0 = court green (colour 1), tile 1 = court line / border
|
|
58
|
+
* (colour 2 = white), tile 2 = dashed net (colour 2 stripe on green). */
|
|
59
|
+
static const uint8_t bg_tiles[96] = {
|
|
60
|
+
/* tile 0 = court green (colour 1 -> plane 0 set) */
|
|
61
|
+
0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
|
|
62
|
+
0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
|
|
63
|
+
0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
|
|
64
|
+
0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
|
|
65
|
+
/* tile 1 = court line / border (colour 2 -> plane 1 set) */
|
|
66
|
+
0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
|
|
67
|
+
0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
|
|
68
|
+
0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
|
|
69
|
+
0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
|
|
70
|
+
/* tile 2 = net: centre column colour 2, rest colour 1 (green) */
|
|
71
|
+
0xFF,0x18,0x00,0x00, 0xFF,0x18,0x00,0x00,
|
|
72
|
+
0xFF,0x18,0x00,0x00, 0xFF,0x18,0x00,0x00,
|
|
73
|
+
0xFF,0x18,0x00,0x00, 0xFF,0x18,0x00,0x00,
|
|
74
|
+
0xFF,0x18,0x00,0x00, 0xFF,0x18,0x00,0x00,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/* Paint the court inside the GG visible region (cols 6..25, rows 3..20):
|
|
78
|
+
* green field, white border lines around the perimeter, dashed net down
|
|
79
|
+
* the centre column. BG tile bank is $0000. */
|
|
80
|
+
static void draw_court(void) {
|
|
81
|
+
uint8_t row, col;
|
|
82
|
+
for (row = 3; row <= 20; row++) {
|
|
83
|
+
for (col = 6; col <= 25; col++) {
|
|
84
|
+
uint8_t t = 0; /* green field */
|
|
85
|
+
if (row == 3 || row == 20 || col == 6 || col == 25) t = 1; /* border */
|
|
86
|
+
else if (col == 15 && (row & 1)) t = 2; /* dashed centre net */
|
|
87
|
+
gg_set_tilemap_cell(row, col, t, 0);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
55
92
|
static int16_t p1y, p2y, bx, by;
|
|
56
93
|
static int8_t bdx, bdy;
|
|
57
94
|
static uint8_t score_p1, score_p2;
|
|
@@ -76,7 +113,13 @@ void main(void) {
|
|
|
76
113
|
uint8_t i;
|
|
77
114
|
gg_vdp_init();
|
|
78
115
|
gg_load_palette(palette);
|
|
79
|
-
gg_load_tiles(
|
|
116
|
+
gg_load_tiles(0x0000, bg_tiles, 96); /* BG court tiles -> BG bank $0000 */
|
|
117
|
+
gg_load_tiles(0x2000, tile_solid, 32); /* paddle/ball sprite tile -> $2000 */
|
|
118
|
+
{
|
|
119
|
+
uint8_t r, c;
|
|
120
|
+
for (r = 0; r < 28; r++) for (c = 0; c < 32; c++) gg_set_tilemap_cell(r, c, 0, 0);
|
|
121
|
+
}
|
|
122
|
+
draw_court();
|
|
80
123
|
gg_sprite_init();
|
|
81
124
|
sfx_init();
|
|
82
125
|
gg_vdp_display_on();
|
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
#include <lynx.h>
|
|
16
16
|
|
|
17
17
|
void main(void) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/* Cycle the centre square through warm/bright shades only — NOT the
|
|
19
|
+
* blues, which would blend into the blue background and make the
|
|
20
|
+
* screen read as "almost one colour". */
|
|
21
|
+
static const unsigned char palette[6] = {
|
|
22
|
+
COLOR_RED, COLOR_YELLOW, COLOR_GREEN,
|
|
23
|
+
COLOR_WHITE, COLOR_LIGHTGREEN, COLOR_PURPLE,
|
|
21
24
|
};
|
|
22
25
|
unsigned char shade = 0;
|
|
23
26
|
unsigned int frame = 0;
|
|
@@ -26,16 +29,44 @@ void main(void) {
|
|
|
26
29
|
tgi_init();
|
|
27
30
|
|
|
28
31
|
for (;;) {
|
|
29
|
-
|
|
32
|
+
/* CANONICAL LYNX FRAME LOOP — full redraw every frame, in this order:
|
|
33
|
+
* 1. WAIT for Suzy's blitter to finish the previous frame. Drawing
|
|
34
|
+
* while it's mid-flight loses the frame → black screen. This is
|
|
35
|
+
* the #1 "Lynx stays blank" trap.
|
|
36
|
+
* 2. CLEAR with a full-screen tgi_bar in the background colour, NOT
|
|
37
|
+
* tgi_clear() — which leaves the back page stale in this
|
|
38
|
+
* toolchain+emulator path (the other genre scaffolds all do the
|
|
39
|
+
* same; see shmup.c's LYNX-1 note).
|
|
40
|
+
* 3. DRAW everything.
|
|
41
|
+
* 4. tgi_updatedisplay() to push the frame. */
|
|
42
|
+
while (tgi_busy()) { }
|
|
43
|
+
|
|
44
|
+
/* Blue field so the screen is obviously not blank. */
|
|
45
|
+
tgi_setcolor(COLOR_BLUE);
|
|
46
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
|
|
47
|
+
|
|
48
|
+
/* A fixed green frame around the centre — always a distinct colour,
|
|
49
|
+
* so the screen never collapses to a single shade even while the
|
|
50
|
+
* inner square is cycling. */
|
|
51
|
+
tgi_setcolor(COLOR_GREEN);
|
|
52
|
+
tgi_bar(50, 30, 110, 72);
|
|
53
|
+
|
|
54
|
+
/* Colour-cycling square in the centre. */
|
|
30
55
|
tgi_setcolor(palette[shade]);
|
|
31
|
-
tgi_bar(
|
|
56
|
+
tgi_bar(60, 36, 100, 66);
|
|
57
|
+
|
|
58
|
+
/* Greeting on top. The tgi font is 8 px wide; "BUILT WITH ROMDEV"
|
|
59
|
+
* is 17 chars = 136 px, so start near the left edge to stay inside
|
|
60
|
+
* the 160-px-wide screen. */
|
|
32
61
|
tgi_setcolor(COLOR_WHITE);
|
|
33
|
-
tgi_outtextxy(
|
|
62
|
+
tgi_outtextxy(40, 14, "HELLO LYNX");
|
|
63
|
+
tgi_outtextxy(12, 86, "BUILT WITH ROMDEV");
|
|
34
64
|
tgi_updatedisplay();
|
|
35
65
|
|
|
36
66
|
frame++;
|
|
37
|
-
if ((frame
|
|
38
|
-
shade
|
|
67
|
+
if ((frame % 24) == 0) { /* advance the inner square */
|
|
68
|
+
shade++;
|
|
69
|
+
if (shade >= 6) shade = 0;
|
|
39
70
|
}
|
|
40
71
|
}
|
|
41
72
|
}
|
|
@@ -98,8 +98,35 @@ void main(void) {
|
|
|
98
98
|
* — drawing while the blitter is mid-flight loses the frame → black.
|
|
99
99
|
* (Copied from the shmup scaffold, the LYNX-1 fix.) */
|
|
100
100
|
while (tgi_busy()) { }
|
|
101
|
+
|
|
102
|
+
/* ── Background scene (drawn every frame). Without it the playfield is
|
|
103
|
+
* a near-flat single colour and the render-health audit flags the
|
|
104
|
+
* screen as blank. A framed "well" in the centre with lit side panels
|
|
105
|
+
* keeps several distinct colours well under the threshold:
|
|
106
|
+
* - blue cabinet backdrop
|
|
107
|
+
* - dark-grey side panels flanking the well
|
|
108
|
+
* - black well interior so the falling blocks read clearly
|
|
109
|
+
* - light-grey well frame + a faint grid texture behind the cells. */
|
|
110
|
+
tgi_setcolor(COLOR_BLUE);
|
|
111
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* cabinet backdrop */
|
|
112
|
+
tgi_setcolor(COLOR_DARKGREY);
|
|
113
|
+
tgi_bar(0, 0, GRID_X - 5, 101); /* left side panel */
|
|
114
|
+
tgi_bar(GRID_X + COLS * CELL_PX + 4, 0, 159, 101); /* right side panel */
|
|
101
115
|
tgi_setcolor(COLOR_BLACK);
|
|
102
|
-
tgi_bar(
|
|
116
|
+
tgi_bar(GRID_X - 2, GRID_Y - 2,
|
|
117
|
+
GRID_X + COLS * CELL_PX + 1, GRID_Y + ROWS * CELL_PX + 1); /* well */
|
|
118
|
+
/* faint grid texture so the empty well is never one flat colour */
|
|
119
|
+
tgi_setcolor(COLOR_DARKGREY);
|
|
120
|
+
for (r = 0; r <= ROWS; r++)
|
|
121
|
+
tgi_line(GRID_X, GRID_Y + r * CELL_PX, GRID_X + COLS * CELL_PX - 1, GRID_Y + r * CELL_PX);
|
|
122
|
+
for (c = 0; c <= COLS; c++)
|
|
123
|
+
tgi_line(GRID_X + c * CELL_PX, GRID_Y, GRID_X + c * CELL_PX, GRID_Y + ROWS * CELL_PX - 1);
|
|
124
|
+
/* well frame */
|
|
125
|
+
tgi_setcolor(COLOR_LIGHTGREY);
|
|
126
|
+
tgi_line(GRID_X - 2, GRID_Y - 2, GRID_X - 2, GRID_Y + ROWS * CELL_PX + 1);
|
|
127
|
+
tgi_line(GRID_X + COLS * CELL_PX + 1, GRID_Y - 2, GRID_X + COLS * CELL_PX + 1, GRID_Y + ROWS * CELL_PX + 1);
|
|
128
|
+
tgi_line(GRID_X - 2, GRID_Y + ROWS * CELL_PX + 1, GRID_X + COLS * CELL_PX + 1, GRID_Y + ROWS * CELL_PX + 1);
|
|
129
|
+
|
|
103
130
|
/* grid */
|
|
104
131
|
for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) {
|
|
105
132
|
if (grid[r][c] != 0) {
|
|
@@ -23,6 +23,8 @@ void main(void) {
|
|
|
23
23
|
uint8_t game_over = 0;
|
|
24
24
|
uint8_t joy, i;
|
|
25
25
|
uint32_t rng = 1;
|
|
26
|
+
uint8_t scroll = 0; /* animates the road dashes so the track moves */
|
|
27
|
+
int16_t y;
|
|
26
28
|
|
|
27
29
|
tgi_install(&lynx_160_102_16_tgi);
|
|
28
30
|
tgi_init();
|
|
@@ -36,14 +38,37 @@ void main(void) {
|
|
|
36
38
|
* — drawing while the blitter is mid-flight loses the frame → black.
|
|
37
39
|
* (Copied from the shmup scaffold, the LYNX-1 fix.) */
|
|
38
40
|
while (tgi_busy()) { }
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
|
|
42
|
+
/* ── Background scene (drawn every frame). Without it the track is a
|
|
43
|
+
* near-flat single colour and the render-health audit flags the
|
|
44
|
+
* screen as blank. A full road with grass shoulders + animated lane
|
|
45
|
+
* dashes keeps several distinct colours well under the threshold:
|
|
46
|
+
* - green grass shoulders on both sides
|
|
47
|
+
* - mid-grey tarmac with darker-grey lane bands
|
|
48
|
+
* - white scrolling centre dashes + solid edge lines. */
|
|
49
|
+
tgi_setcolor(COLOR_GREEN);
|
|
50
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* grass base */
|
|
51
|
+
tgi_setcolor(COLOR_GREY);
|
|
52
|
+
tgi_bar(20, 0, 148, 101); /* tarmac */
|
|
53
|
+
/* darker lane bands so the road isn't one flat grey */
|
|
42
54
|
tgi_setcolor(COLOR_DARKGREY);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
tgi_bar(20, 0, 53, 101);
|
|
56
|
+
tgi_bar(96, 0, 128, 101);
|
|
57
|
+
/* solid road edges */
|
|
58
|
+
tgi_setcolor(COLOR_WHITE);
|
|
59
|
+
tgi_line(20, 0, 20, 101);
|
|
60
|
+
tgi_line(148, 0, 148, 101);
|
|
61
|
+
/* animated dashed lane dividers (scroll downward) */
|
|
62
|
+
for (y = (int16_t)scroll - 12; y < 102; y += 12) {
|
|
63
|
+
tgi_bar(53, (unsigned)(y < 0 ? 0 : y), 55, (unsigned)(y + 6 > 101 ? 101 : y + 6));
|
|
64
|
+
tgi_bar(96, (unsigned)(y < 0 ? 0 : y), 98, (unsigned)(y + 6 > 101 ? 101 : y + 6));
|
|
65
|
+
}
|
|
66
|
+
/* grass rumble strips for extra colour texture */
|
|
67
|
+
tgi_setcolor(COLOR_LIGHTGREEN);
|
|
68
|
+
for (y = (int16_t)scroll - 8; y < 102; y += 16) {
|
|
69
|
+
tgi_bar(0, (unsigned)(y < 0 ? 0 : y), 6, (unsigned)(y + 6 > 101 ? 101 : y + 6));
|
|
70
|
+
tgi_bar(153, (unsigned)(y < 0 ? 0 : y), 159, (unsigned)(y + 6 > 101 ? 101 : y + 6));
|
|
71
|
+
}
|
|
47
72
|
|
|
48
73
|
tgi_setcolor(COLOR_YELLOW);
|
|
49
74
|
tgi_bar((unsigned)player.x - 4, (unsigned)player.y - 4, (unsigned)player.x + 4, (unsigned)player.y + 4);
|
|
@@ -54,6 +79,8 @@ void main(void) {
|
|
|
54
79
|
tgi_updatedisplay();
|
|
55
80
|
sfx_update();
|
|
56
81
|
|
|
82
|
+
scroll += 2; if (scroll >= 12) scroll -= 12; /* advance road dashes */
|
|
83
|
+
|
|
57
84
|
if (game_over > 0) {
|
|
58
85
|
game_over--;
|
|
59
86
|
if (game_over == 0) {
|
|
@@ -50,8 +50,15 @@ static void spawn(void) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/* Scrolling starfield: a handful of stars that drift down so the dark
|
|
54
|
+
* space field is never a flat single colour (would read as "blank"). */
|
|
55
|
+
#define N_STARS 24
|
|
56
|
+
static uint8_t star_x[N_STARS];
|
|
57
|
+
static uint8_t star_y[N_STARS];
|
|
58
|
+
|
|
53
59
|
void main(void) {
|
|
54
60
|
uint8_t joy, fire_now, i, j;
|
|
61
|
+
uint32_t srng = 0x1234;
|
|
55
62
|
|
|
56
63
|
tgi_install(&lynx_160_102_16_tgi);
|
|
57
64
|
tgi_init();
|
|
@@ -61,6 +68,12 @@ void main(void) {
|
|
|
61
68
|
player.x = 76; player.y = 90; player.alive = 1;
|
|
62
69
|
for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
|
|
63
70
|
for (i = 0; i < MAX_ENEMIES; i++) enemies[i].alive = 0;
|
|
71
|
+
for (i = 0; i < N_STARS; i++) {
|
|
72
|
+
srng = srng * 1103515245u + 12345u;
|
|
73
|
+
star_x[i] = (uint8_t)((srng >> 16) % 160);
|
|
74
|
+
srng = srng * 1103515245u + 12345u;
|
|
75
|
+
star_y[i] = (uint8_t)((srng >> 16) % 102);
|
|
76
|
+
}
|
|
64
77
|
spawn_timer = 0;
|
|
65
78
|
prev_btn = 0;
|
|
66
79
|
|
|
@@ -76,10 +89,31 @@ void main(void) {
|
|
|
76
89
|
* 3. DRAW every object.
|
|
77
90
|
* 4. tgi_updatedisplay() to push the frame. */
|
|
78
91
|
while (tgi_busy()) { }
|
|
79
|
-
tgi_setcolor(COLOR_BLACK);
|
|
80
|
-
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* maxx/maxy = 159/101 = full screen */
|
|
81
92
|
|
|
82
|
-
/*
|
|
93
|
+
/* ── Background scene (drawn every frame; without it the dark space
|
|
94
|
+
* field is a near-flat single colour and the render-health audit
|
|
95
|
+
* flags the screen as blank). Layered bands keep any one colour well
|
|
96
|
+
* under the threshold:
|
|
97
|
+
* - deep-blue upper space
|
|
98
|
+
* - grey nebula band across the middle
|
|
99
|
+
* - green planet surface along the bottom
|
|
100
|
+
* - a drifting white/yellow starfield over the space. */
|
|
101
|
+
tgi_setcolor(COLOR_BLUE);
|
|
102
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* base space field */
|
|
103
|
+
tgi_setcolor(COLOR_GREY);
|
|
104
|
+
tgi_bar(0, 34, 159, 60); /* nebula band */
|
|
105
|
+
tgi_setcolor(COLOR_GREEN);
|
|
106
|
+
tgi_bar(0, 84, 159, 101); /* planet surface */
|
|
107
|
+
tgi_setcolor(COLOR_LIGHTGREEN);
|
|
108
|
+
tgi_bar(0, 78, 159, 83); /* surface horizon */
|
|
109
|
+
/* starfield (bright specks; also drifts downward each frame) */
|
|
110
|
+
tgi_setcolor(COLOR_WHITE);
|
|
111
|
+
for (i = 0; i < N_STARS; i++) {
|
|
112
|
+
tgi_setpixel(star_x[i], star_y[i]);
|
|
113
|
+
tgi_setpixel(star_x[i], (star_y[i] + 1) % 102);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Render game objects on top */
|
|
83
117
|
tgi_setcolor(COLOR_YELLOW);
|
|
84
118
|
tgi_bar(player.x, player.y, player.x + 6, player.y + 6);
|
|
85
119
|
tgi_setcolor(COLOR_WHITE);
|
|
@@ -93,6 +127,11 @@ void main(void) {
|
|
|
93
127
|
tgi_updatedisplay();
|
|
94
128
|
sfx_update();
|
|
95
129
|
|
|
130
|
+
/* drift the starfield downward */
|
|
131
|
+
for (i = 0; i < N_STARS; i++) {
|
|
132
|
+
if (star_y[i] >= 101) star_y[i] = 0; else star_y[i]++;
|
|
133
|
+
}
|
|
134
|
+
|
|
96
135
|
/* Input + state */
|
|
97
136
|
joy = joy_read(JOY_1);
|
|
98
137
|
fire_now = JOY_BTN_1(joy) ? 1 : 0;
|
|
@@ -21,6 +21,7 @@ void main(void) {
|
|
|
21
21
|
int16_t p1y = 40, p2y = 40, bx = 78, by = 48;
|
|
22
22
|
int8_t bdx = 2, bdy = 1;
|
|
23
23
|
uint8_t joy;
|
|
24
|
+
int16_t ny; /* loop var for the dashed centre net */
|
|
24
25
|
|
|
25
26
|
tgi_install(&lynx_160_102_16_tgi);
|
|
26
27
|
tgi_init();
|
|
@@ -33,8 +34,34 @@ void main(void) {
|
|
|
33
34
|
* — drawing while the blitter is mid-flight loses the frame → black.
|
|
34
35
|
* (Copied from the shmup scaffold, the LYNX-1 fix.) */
|
|
35
36
|
while (tgi_busy()) { }
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
/* ── Background scene (drawn every frame). Without it the court is a
|
|
39
|
+
* near-flat single colour and the render-health audit flags the
|
|
40
|
+
* screen as blank. A two-tone court with boards + net markings keeps
|
|
41
|
+
* several distinct colours well under the threshold:
|
|
42
|
+
* - green centre court
|
|
43
|
+
* - lighter-green end zones behind each paddle
|
|
44
|
+
* - dark-grey boards top and bottom
|
|
45
|
+
* - white boundary, dashed centre net + centre circle. */
|
|
46
|
+
tgi_setcolor(COLOR_GREEN);
|
|
47
|
+
tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* court grass */
|
|
48
|
+
tgi_setcolor(COLOR_LIGHTGREEN);
|
|
49
|
+
tgi_bar(0, COURT_TOP, 52, COURT_BOT - 1); /* left end zone */
|
|
50
|
+
tgi_bar(107, COURT_TOP, 159, COURT_BOT - 1); /* right end zone */
|
|
51
|
+
tgi_setcolor(COLOR_DARKGREY);
|
|
52
|
+
tgi_bar(0, 0, 159, COURT_TOP - 1); /* top boards */
|
|
53
|
+
tgi_bar(0, COURT_BOT, 159, 101); /* bottom boards */
|
|
54
|
+
/* white court boundary + dashed centre net + centre circle */
|
|
55
|
+
tgi_setcolor(COLOR_WHITE);
|
|
56
|
+
tgi_line(0, COURT_TOP, 159, COURT_TOP);
|
|
57
|
+
tgi_line(0, COURT_BOT, 159, COURT_BOT);
|
|
58
|
+
for (ny = COURT_TOP; ny < COURT_BOT; ny += 8)
|
|
59
|
+
tgi_bar(79, (unsigned)ny, 80, (unsigned)(ny + 3 > COURT_BOT ? COURT_BOT : ny + 3));
|
|
60
|
+
tgi_line(70, 40, 90, 40);
|
|
61
|
+
tgi_line(70, 60, 90, 60);
|
|
62
|
+
tgi_line(70, 40, 70, 60);
|
|
63
|
+
tgi_line(90, 40, 90, 60);
|
|
64
|
+
|
|
38
65
|
tgi_setcolor(COLOR_WHITE);
|
|
39
66
|
tgi_bar(PADDLE_X1, (unsigned)p1y, PADDLE_X1 + PADDLE_W - 1, (unsigned)(p1y + PADDLE_H - 1));
|
|
40
67
|
tgi_bar(PADDLE_X2, (unsigned)p2y, PADDLE_X2 + PADDLE_W - 1, (unsigned)(p2y + PADDLE_H - 1));
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/* ── platformer/main.c — MSX SIDE-SCROLLING platformer scaffold ──────
|
|
2
|
+
*
|
|
3
|
+
* Mirrors the SMS/GB/etc platformer scaffolds, translated to the MSX VDP
|
|
4
|
+
* via the romdev helper lib (msx_hw.h + msx_vdp.c).
|
|
5
|
+
*
|
|
6
|
+
* The world is 512 px wide (64 cells); the screen-2 name table is only 32
|
|
7
|
+
* cells (256 px) and wraps, so a world wider than one screen needs COLUMN
|
|
8
|
+
* STREAMING: each time the camera crosses an 8-px boundary we rewrite the
|
|
9
|
+
* name-table column about to scroll into view with the next world column's
|
|
10
|
+
* tiles. Screen 2 has no smooth-pixel-scroll register, so the camera moves
|
|
11
|
+
* in whole 8-px cells — the streaming gives a clean tile-by-tile scroll.
|
|
12
|
+
*
|
|
13
|
+
* Subpixel state (x/y in 1/16-pixel units) for fine gravity/acceleration;
|
|
14
|
+
* the player sprite draws in SCREEN space ((worldX>>4) - camX).
|
|
15
|
+
*
|
|
16
|
+
* Controls: joystick LEFT/RIGHT walks, trigger A jumps (only when grounded).
|
|
17
|
+
*
|
|
18
|
+
* Cartridge rule: INIT must never return — main() ends in for(;;).
|
|
19
|
+
*/
|
|
20
|
+
#include "msx_hw.h"
|
|
21
|
+
|
|
22
|
+
/* ── interrupt-free vblank sync (poll VDP status S#0 bit 7) ────────────── */
|
|
23
|
+
__sfr __at 0x99 VDPSTATUS;
|
|
24
|
+
static void vsync(void) {
|
|
25
|
+
(void)VDPSTATUS;
|
|
26
|
+
while (!(VDPSTATUS & 0x80)) {
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/* jump uses the BIOS GTTRIG wrapper (gttrig) provided by msx_hw.h. */
|
|
30
|
+
|
|
31
|
+
#define T_OPEN 0
|
|
32
|
+
#define T_WALL 1
|
|
33
|
+
|
|
34
|
+
#define WORLD_COLS 64
|
|
35
|
+
#define WORLD_W (WORLD_COLS * 8)
|
|
36
|
+
#define SCREEN_W 256
|
|
37
|
+
#define VIS_ROWS 24
|
|
38
|
+
|
|
39
|
+
/* background tile patterns (8x8) */
|
|
40
|
+
static const uint8_t TILE_OPEN[8] = {0,0,0,0,0,0,0,0};
|
|
41
|
+
static const uint8_t TILE_WALL[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
|
|
42
|
+
|
|
43
|
+
#define COL_OPEN 0x14 /* light-blue "open" (sky) on dark blue */
|
|
44
|
+
#define COL_WALL 0xE4 /* grey wall on dark blue */
|
|
45
|
+
|
|
46
|
+
/* player sprite (8x8) */
|
|
47
|
+
static const uint8_t SPR_PLAYER[8] = {0x3C,0x7E,0xFF,0xFF,0xFF,0xFF,0x7E,0x3C};
|
|
48
|
+
#define COL_PLAYER 9 /* red-ish */
|
|
49
|
+
|
|
50
|
+
typedef struct { int16_t x, y, w, h; } Rect;
|
|
51
|
+
|
|
52
|
+
/* platforms in WORLD coords, spread across the 512-px world */
|
|
53
|
+
static const Rect platforms[] = {
|
|
54
|
+
{ 0, 176, 512, 16 }, /* floor spans the world */
|
|
55
|
+
{ 32, 144, 56, 8 },
|
|
56
|
+
{ 120, 144, 64, 8 },
|
|
57
|
+
{ 200, 112, 48, 8 },
|
|
58
|
+
{ 56, 96, 40, 8 },
|
|
59
|
+
{ 288, 136, 64, 8 },
|
|
60
|
+
{ 384, 104, 56, 8 },
|
|
61
|
+
{ 440, 152, 48, 8 },
|
|
62
|
+
{ 320, 72, 48, 8 }
|
|
63
|
+
};
|
|
64
|
+
#define N_PLATFORMS (sizeof(platforms) / sizeof(platforms[0]))
|
|
65
|
+
|
|
66
|
+
static int16_t px, py; /* player pos, 1/16-px units */
|
|
67
|
+
static int16_t vx, vy;
|
|
68
|
+
static int16_t camX; /* camera X in pixels (cell-aligned) */
|
|
69
|
+
static int16_t lastCamCol;
|
|
70
|
+
|
|
71
|
+
static void load_tiles(void) {
|
|
72
|
+
uint8_t third;
|
|
73
|
+
uint16_t poff;
|
|
74
|
+
for (third = 0; third < 3; third++) {
|
|
75
|
+
poff = (uint16_t)((uint16_t)third << 11);
|
|
76
|
+
msx_vram_write((uint16_t)(VRAM_PATTERN + poff + 0), TILE_OPEN, 8);
|
|
77
|
+
msx_vram_write((uint16_t)(VRAM_PATTERN + poff + 8), TILE_WALL, 8);
|
|
78
|
+
msx_fill_vram((uint16_t)(VRAM_COLOR + poff + 0), 8, COL_OPEN);
|
|
79
|
+
msx_fill_vram((uint16_t)(VRAM_COLOR + poff + 8), 8, COL_WALL);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static uint8_t cell_is_wall(int16_t col, uint8_t row) {
|
|
84
|
+
int16_t cx = (int16_t)(col << 3);
|
|
85
|
+
int16_t cy = (int16_t)((int16_t)row << 3);
|
|
86
|
+
uint8_t i;
|
|
87
|
+
const Rect *p;
|
|
88
|
+
for (i = 0; i < N_PLATFORMS; i++) {
|
|
89
|
+
p = &platforms[i];
|
|
90
|
+
if (cx + 8 > p->x && cx < p->x + p->w
|
|
91
|
+
&& cy + 8 > p->y && cy < p->y + p->h) return 1;
|
|
92
|
+
}
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static uint8_t on_platform(int16_t ipx, int16_t ipy) {
|
|
97
|
+
uint8_t i;
|
|
98
|
+
const Rect *p;
|
|
99
|
+
for (i = 0; i < N_PLATFORMS; i++) {
|
|
100
|
+
p = &platforms[i];
|
|
101
|
+
if (ipy + 8 == p->y && ipx + 8 > p->x && ipx < p->x + p->w) return 1;
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* write one world column into its wrapped name-table column */
|
|
107
|
+
static void paint_column(int16_t worldCol) {
|
|
108
|
+
uint8_t ntCol, row, tile;
|
|
109
|
+
uint16_t addr;
|
|
110
|
+
if (worldCol < 0 || worldCol >= WORLD_COLS) return;
|
|
111
|
+
ntCol = (uint8_t)(worldCol & 31);
|
|
112
|
+
for (row = 0; row < VIS_ROWS; row++) {
|
|
113
|
+
tile = cell_is_wall(worldCol, row) ? T_WALL : T_OPEN;
|
|
114
|
+
addr = (uint16_t)(VRAM_NAME + (uint16_t)row * 32 + ntCol);
|
|
115
|
+
msx_vram_write(addr, &tile, 1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static void paint_initial(void) {
|
|
120
|
+
int16_t c;
|
|
121
|
+
for (c = 0; c < 32; c++) paint_column(c);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
void main(void) {
|
|
125
|
+
const int16_t GRAVITY = 10;
|
|
126
|
+
const int16_t MOVE = 24;
|
|
127
|
+
const int16_t JUMP = -200;
|
|
128
|
+
const int16_t MAXFALL = 280;
|
|
129
|
+
uint8_t dir, trig, prev_trig, grounded, blip;
|
|
130
|
+
|
|
131
|
+
msx_set_screen2();
|
|
132
|
+
msx_clear_sprites();
|
|
133
|
+
load_tiles();
|
|
134
|
+
msx_fill_vram(VRAM_NAME, 32 * 24, T_OPEN);
|
|
135
|
+
msx_vram_write((uint16_t)(VRAM_SPRPAT + 0), SPR_PLAYER, 8);
|
|
136
|
+
|
|
137
|
+
px = (int16_t)(16 << 4);
|
|
138
|
+
py = (int16_t)(64 << 4);
|
|
139
|
+
vx = 0; vy = 0;
|
|
140
|
+
camX = 0; lastCamCol = 0;
|
|
141
|
+
prev_trig = 0; blip = 0;
|
|
142
|
+
|
|
143
|
+
paint_initial();
|
|
144
|
+
|
|
145
|
+
for (;;) {
|
|
146
|
+
int16_t ipx, ipy, npy, sx, camCol;
|
|
147
|
+
int32_t np;
|
|
148
|
+
uint8_t i, landed;
|
|
149
|
+
const Rect *p;
|
|
150
|
+
|
|
151
|
+
vsync();
|
|
152
|
+
|
|
153
|
+
ipx = (int16_t)(px >> 4);
|
|
154
|
+
ipy = (int16_t)(py >> 4);
|
|
155
|
+
|
|
156
|
+
/* camera follows the player, centered, clamped, snapped to a cell */
|
|
157
|
+
camX = (int16_t)(ipx - (SCREEN_W / 2 - 4));
|
|
158
|
+
if (camX < 0) camX = 0;
|
|
159
|
+
if (camX > WORLD_W - SCREEN_W) camX = (int16_t)(WORLD_W - SCREEN_W);
|
|
160
|
+
camX = (int16_t)(camX & ~7);
|
|
161
|
+
|
|
162
|
+
camCol = (int16_t)(camX >> 3);
|
|
163
|
+
while (camCol > lastCamCol) { lastCamCol++; paint_column((int16_t)(lastCamCol + 31)); }
|
|
164
|
+
while (camCol < lastCamCol) { lastCamCol--; paint_column(lastCamCol); }
|
|
165
|
+
|
|
166
|
+
sx = (int16_t)(ipx - camX);
|
|
167
|
+
if (sx < 0) sx = 0;
|
|
168
|
+
if (sx > 248) sx = 248;
|
|
169
|
+
msx_set_sprite(0, (uint8_t)sx, (uint8_t)ipy, 0, COL_PLAYER);
|
|
170
|
+
|
|
171
|
+
dir = msx_read_joystick(1);
|
|
172
|
+
if (dir == STICK_CENTER) dir = msx_read_joystick(0);
|
|
173
|
+
trig = (uint8_t)(gttrig(1) || gttrig(2));
|
|
174
|
+
|
|
175
|
+
vx = 0;
|
|
176
|
+
if (dir == STICK_LEFT || dir == STICK_UL || dir == STICK_DL) vx = (int16_t)(-MOVE);
|
|
177
|
+
if (dir == STICK_RIGHT || dir == STICK_UR || dir == STICK_DR) vx = MOVE;
|
|
178
|
+
|
|
179
|
+
grounded = on_platform(ipx, ipy);
|
|
180
|
+
if (trig && !prev_trig && grounded) { vy = JUMP; msx_psg_tone(0, 0x180, 12); blip = 6; }
|
|
181
|
+
prev_trig = trig;
|
|
182
|
+
|
|
183
|
+
vy = (int16_t)(vy + GRAVITY);
|
|
184
|
+
if (vy > MAXFALL) vy = MAXFALL;
|
|
185
|
+
if (grounded && vy > 0) vy = 0;
|
|
186
|
+
|
|
187
|
+
px = (int16_t)(px + vx);
|
|
188
|
+
if (px < 0) px = 0;
|
|
189
|
+
if (px > (int16_t)((WORLD_W - 8) << 4)) px = (int16_t)((WORLD_W - 8) << 4);
|
|
190
|
+
|
|
191
|
+
np = (int32_t)py + (int32_t)vy;
|
|
192
|
+
npy = (int16_t)(np >> 4);
|
|
193
|
+
if (vy > 0) {
|
|
194
|
+
landed = 0;
|
|
195
|
+
for (i = 0; i < N_PLATFORMS; i++) {
|
|
196
|
+
p = &platforms[i];
|
|
197
|
+
if (ipy + 8 <= p->y && npy + 8 >= p->y
|
|
198
|
+
&& ipx + 8 > p->x && ipx < p->x + p->w) {
|
|
199
|
+
py = (int16_t)((p->y - 8) << 4);
|
|
200
|
+
vy = 0;
|
|
201
|
+
landed = 1;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (!landed) py = (int16_t)np;
|
|
206
|
+
} else {
|
|
207
|
+
py = (int16_t)np;
|
|
208
|
+
}
|
|
209
|
+
if (py > (int16_t)(192 << 4)) { py = (int16_t)(64 << 4); vy = 0; }
|
|
210
|
+
|
|
211
|
+
if (blip) { blip--; if (!blip) msx_psg_off(0); }
|
|
212
|
+
}
|
|
213
|
+
}
|