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.
Files changed (117) hide show
  1. package/AGENTS.md +61 -13
  2. package/CHANGELOG.md +289 -0
  3. package/README.md +1 -1
  4. package/examples/README.md +2 -0
  5. package/examples/atari2600/templates/platformer.asm +460 -0
  6. package/examples/atari2600/templates/racing.asm +463 -0
  7. package/examples/atari2600/templates/shmup.asm +386 -0
  8. package/examples/atari2600/templates/sports.asm +362 -0
  9. package/examples/atari7800/templates/default.c +49 -5
  10. package/examples/atari7800/templates/platformer.c +43 -4
  11. package/examples/atari7800/templates/puzzle.c +39 -4
  12. package/examples/atari7800/templates/racing.c +39 -4
  13. package/examples/atari7800/templates/shmup.c +40 -2
  14. package/examples/atari7800/templates/sports.c +36 -5
  15. package/examples/c64/templates/platformer.c +19 -5
  16. package/examples/c64/templates/puzzle.c +32 -2
  17. package/examples/c64/templates/shmup.c +28 -2
  18. package/examples/c64/templates/sports.c +30 -2
  19. package/examples/gb/templates/default.c +110 -16
  20. package/examples/gb/templates/platformer.c +25 -4
  21. package/examples/gb/templates/puzzle.c +32 -2
  22. package/examples/gb/templates/racing.c +72 -8
  23. package/examples/gb/templates/shmup.c +38 -1
  24. package/examples/gb/templates/sports.c +48 -1
  25. package/examples/gba/templates/gba_hello.c +29 -11
  26. package/examples/gba/templates/puzzle.c +15 -3
  27. package/examples/gba/templates/racing.c +65 -3
  28. package/examples/gba/templates/shmup.c +41 -4
  29. package/examples/gba/templates/sports.c +36 -2
  30. package/examples/gba/templates/tonc_hello.c +41 -5
  31. package/examples/gbc/templates/default.c +103 -26
  32. package/examples/gbc/templates/platformer.c +25 -4
  33. package/examples/gbc/templates/puzzle.c +32 -2
  34. package/examples/gbc/templates/racing.c +85 -19
  35. package/examples/gbc/templates/shmup.c +34 -1
  36. package/examples/gbc/templates/sports.c +45 -1
  37. package/examples/genesis/templates/puzzle.c +37 -3
  38. package/examples/genesis/templates/racing.c +44 -11
  39. package/examples/genesis/templates/sgdk_hello.c +34 -1
  40. package/examples/genesis/templates/shmup.c +31 -1
  41. package/examples/gg/templates/default.c +56 -18
  42. package/examples/gg/templates/platformer.c +18 -12
  43. package/examples/gg/templates/puzzle.c +38 -7
  44. package/examples/gg/templates/racing.c +51 -5
  45. package/examples/gg/templates/shmup.c +47 -3
  46. package/examples/gg/templates/sports.c +46 -3
  47. package/examples/lynx/templates/default.c +39 -8
  48. package/examples/lynx/templates/puzzle.c +28 -1
  49. package/examples/lynx/templates/racing.c +34 -7
  50. package/examples/lynx/templates/shmup.c +42 -3
  51. package/examples/lynx/templates/sports.c +29 -2
  52. package/examples/msx/platformer/main.c +213 -0
  53. package/examples/msx/puzzle/main.c +250 -0
  54. package/examples/msx/racing/main.c +249 -0
  55. package/examples/msx/shmup/main.c +288 -0
  56. package/examples/msx/sports/main.c +182 -0
  57. package/examples/nes/templates/default.c +67 -19
  58. package/examples/nes/templates/platformer.c +65 -6
  59. package/examples/nes/templates/puzzle.c +67 -6
  60. package/examples/nes/templates/racing.c +45 -13
  61. package/examples/nes/templates/shmup.c +51 -2
  62. package/examples/nes/templates/sports.c +51 -6
  63. package/examples/pce/platformer/main.c +283 -0
  64. package/examples/pce/puzzle/main.c +304 -0
  65. package/examples/pce/racing/main.c +304 -0
  66. package/examples/pce/shmup/main.c +346 -0
  67. package/examples/pce/sports/main.c +254 -0
  68. package/examples/sms/main.c +35 -6
  69. package/examples/sms/templates/puzzle.c +34 -5
  70. package/examples/sms/templates/racing.c +39 -2
  71. package/examples/sms/templates/shmup.c +41 -2
  72. package/examples/sms/templates/sports.c +43 -2
  73. package/examples/snes/templates/default.c +50 -28
  74. package/examples/snes/templates/platformer-data.asm +22 -0
  75. package/examples/snes/templates/platformer.c +16 -1
  76. package/examples/snes/templates/puzzle-data.asm +22 -0
  77. package/examples/snes/templates/puzzle.c +17 -1
  78. package/examples/snes/templates/racing-data.asm +22 -0
  79. package/examples/snes/templates/racing.c +17 -1
  80. package/examples/snes/templates/shmup-data.asm +22 -0
  81. package/examples/snes/templates/shmup.c +20 -1
  82. package/examples/snes/templates/sports-data.asm +22 -0
  83. package/examples/snes/templates/sports.c +16 -1
  84. package/package.json +1 -1
  85. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  86. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  87. package/src/host/LibretroHost.js +122 -1
  88. package/src/host/callbacks.js +9 -1
  89. package/src/host/types.js +15 -8
  90. package/src/http/skill-doc.js +1 -1
  91. package/src/http/tool-registry.js +27 -2
  92. package/src/mcp/tools/cart-parts.js +75 -3
  93. package/src/mcp/tools/disasm-rebuild.js +507 -0
  94. package/src/mcp/tools/disasm.js +95 -6
  95. package/src/mcp/tools/frame.js +168 -3
  96. package/src/mcp/tools/index.js +4 -4
  97. package/src/mcp/tools/lifecycle.js +4 -2
  98. package/src/mcp/tools/project.js +54 -9
  99. package/src/mcp/tools/state.js +201 -14
  100. package/src/mcp/tools/toolchain.js +89 -4
  101. package/src/mcp/tools/watch-memory.js +125 -14
  102. package/src/platforms/c64/MENTAL_MODEL.md +45 -1
  103. package/src/platforms/c64/d64.js +281 -0
  104. package/src/platforms/gb/MENTAL_MODEL.md +10 -0
  105. package/src/platforms/msx/MENTAL_MODEL.md +10 -6
  106. package/src/platforms/nes/MENTAL_MODEL.md +63 -2
  107. package/src/platforms/pce/MENTAL_MODEL.md +9 -4
  108. package/src/platforms/pce/lib/c/pce_video.c +1 -1
  109. package/src/rom-id/identifier.js +15 -0
  110. package/src/toolchains/cc65/cc65.js +8 -1
  111. package/src/toolchains/cc65/ines.js +145 -0
  112. package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
  113. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
  114. package/src/toolchains/common/reassemble.js +10 -2
  115. package/src/toolchains/gba-c/gba-c.js +6 -1
  116. package/src/toolchains/genesis-c/genesis-c.js +10 -2
  117. package/src/toolchains/parse-errors.js +67 -5
@@ -0,0 +1,250 @@
1
+ /* ── puzzle/main.c — MSX match-3 falling-block scaffold (screen 2) ───
2
+ *
3
+ * Mirrors the SMS/GB/etc puzzle scaffolds, translated to the MSX VDP via
4
+ * the romdev helper lib (msx_hw.h + msx_vdp.c).
5
+ *
6
+ * A 6-wide x 12-tall well drawn entirely with the BG tilemap: distinct
7
+ * R/G/B cell tiles, a grey border frame, and a dim field interior so the
8
+ * playfield is visible even when empty (the screen is never blank). A 1x3
9
+ * active piece falls; clears happen on a horizontal triple of one colour.
10
+ *
11
+ * Controls (joystick PORT 1 + triggers):
12
+ * LEFT/RIGHT shift the piece (edge-detected)
13
+ * trigger A rotate the colour order of the 1x3 piece
14
+ * DOWN soft-drop (fast fall)
15
+ * trigger B hard-drop + lock
16
+ *
17
+ * Cartridge rule: INIT must never return — main() ends in for(;;).
18
+ */
19
+ #include "msx_hw.h"
20
+
21
+ /* ── interrupt-free vblank sync (poll VDP status S#0 bit 7) ────────────── */
22
+ __sfr __at 0x99 VDPSTATUS;
23
+ static void vsync(void) {
24
+ (void)VDPSTATUS;
25
+ while (!(VDPSTATUS & 0x80)) {
26
+ }
27
+ }
28
+ /* triggers use the BIOS GTTRIG wrapper (gttrig) provided by msx_hw.h. */
29
+
30
+ #define COLS 6
31
+ #define ROWS 12
32
+
33
+ #define T_BLANK 0
34
+ #define T_R 1
35
+ #define T_G 2
36
+ #define T_B 3
37
+ #define T_WALL 4 /* well border */
38
+ #define T_FIELD 5 /* dim well interior */
39
+
40
+ /* 8x8 tile patterns: solid fills (the colour comes from the colour table) */
41
+ static const uint8_t TILE_SOLID[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
42
+ static const uint8_t TILE_BLANK[8] = {0,0,0,0,0,0,0,0};
43
+ static const uint8_t TILE_CELL[8] = {0x7E,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x7E};
44
+
45
+ /* colour bytes (hi nibble fg, lo nibble bg). TMS9918 palette:
46
+ * 9=red, 12=green(light), 4=blue, 14=grey, 1=black. */
47
+ #define COL_R 0x91
48
+ #define COL_G 0xC1
49
+ #define COL_B 0x41
50
+ #define COL_WALL 0xE1
51
+ #define COL_FIELD 0x41 /* same blue as B but used for the empty field */
52
+
53
+ static uint8_t grid[ROWS][COLS];
54
+ static uint8_t piece[3];
55
+ static int8_t piece_x;
56
+ static int8_t piece_y;
57
+ static uint8_t fall_timer;
58
+ static uint16_t score;
59
+ static uint16_t rng;
60
+ static uint8_t blip;
61
+
62
+ static uint16_t xorshift(void) {
63
+ rng ^= (uint16_t)(rng << 7);
64
+ rng ^= (uint16_t)(rng >> 9);
65
+ rng ^= (uint16_t)(rng << 8);
66
+ return rng;
67
+ }
68
+ static uint8_t rand_color(void) { return (uint8_t)(1 + (xorshift() % 3)); }
69
+
70
+ static uint8_t tile_for(uint8_t c) {
71
+ if (c == 1) return T_R;
72
+ if (c == 2) return T_G;
73
+ if (c == 3) return T_B;
74
+ return T_FIELD; /* empty cell shows the dim field, not the backdrop */
75
+ }
76
+
77
+ static void load_tiles(void) {
78
+ uint8_t third;
79
+ uint16_t pat, col;
80
+ for (third = 0; third < 3; third++) {
81
+ pat = (uint16_t)(VRAM_PATTERN + ((uint16_t)third << 11));
82
+ col = (uint16_t)(VRAM_COLOR + ((uint16_t)third << 11));
83
+ msx_vram_write((uint16_t)(pat + T_BLANK * 8), TILE_BLANK, 8);
84
+ msx_vram_write((uint16_t)(pat + T_R * 8), TILE_CELL, 8);
85
+ msx_vram_write((uint16_t)(pat + T_G * 8), TILE_CELL, 8);
86
+ msx_vram_write((uint16_t)(pat + T_B * 8), TILE_CELL, 8);
87
+ msx_vram_write((uint16_t)(pat + T_WALL * 8), TILE_SOLID, 8);
88
+ msx_vram_write((uint16_t)(pat + T_FIELD * 8), TILE_SOLID, 8);
89
+ msx_fill_vram((uint16_t)(col + T_BLANK * 8), 8, 0x11);
90
+ msx_fill_vram((uint16_t)(col + T_R * 8), 8, COL_R);
91
+ msx_fill_vram((uint16_t)(col + T_G * 8), 8, COL_G);
92
+ msx_fill_vram((uint16_t)(col + T_B * 8), 8, COL_B);
93
+ msx_fill_vram((uint16_t)(col + T_WALL * 8), 8, COL_WALL);
94
+ msx_fill_vram((uint16_t)(col + T_FIELD * 8), 8, COL_FIELD);
95
+ }
96
+ }
97
+
98
+ static void set_cell_tile(uint8_t row, uint8_t col, uint8_t tile) {
99
+ msx_vram_write((uint16_t)(VRAM_NAME + (uint16_t)row * 32 + col), &tile, 1);
100
+ }
101
+
102
+ /* grid cell (col,row) -> name-table (row+1, col+13): centres the well */
103
+ static void draw_cell(int8_t col, int8_t row, uint8_t cell) {
104
+ if (row < 0 || row >= ROWS) return;
105
+ set_cell_tile((uint8_t)(row + 1), (uint8_t)(col + 13), tile_for(cell));
106
+ }
107
+
108
+ /* grey frame around the 6x12 field + dim interior so it is always visible.
109
+ * field cells are rows 1..12, cols 13..18; frame rows 0..13, cols 12..19. */
110
+ static void draw_well(void) {
111
+ uint8_t r, c, t;
112
+ for (r = 0; r <= 13; r++) {
113
+ for (c = 12; c <= 19; c++) {
114
+ t = T_FIELD;
115
+ if (r == 0 || r == 13 || c == 12 || c == 19) t = T_WALL;
116
+ set_cell_tile(r, c, t);
117
+ }
118
+ }
119
+ }
120
+
121
+ static void draw_grid(void) {
122
+ int8_t r, c;
123
+ for (r = 0; r < ROWS; r++)
124
+ for (c = 0; c < COLS; c++) draw_cell(c, r, grid[r][c]);
125
+ }
126
+
127
+ static void new_piece(void) {
128
+ piece[0] = rand_color();
129
+ piece[1] = rand_color();
130
+ piece[2] = rand_color();
131
+ piece_x = COLS / 2 - 1;
132
+ piece_y = -3;
133
+ }
134
+
135
+ static uint8_t collides(int8_t col, int8_t row) {
136
+ uint8_t i;
137
+ int8_t r;
138
+ if (col < 0 || col >= COLS) return 1;
139
+ for (i = 0; i < 3; i++) {
140
+ r = (int8_t)(row + i);
141
+ if (r >= ROWS) return 1;
142
+ if (r >= 0 && grid[r][col] != 0) return 1;
143
+ }
144
+ return 0;
145
+ }
146
+
147
+ static void lock_piece(void) {
148
+ uint8_t i;
149
+ int8_t r, c;
150
+ uint8_t a, b, d;
151
+ for (i = 0; i < 3; i++) {
152
+ r = (int8_t)(piece_y + i);
153
+ if (r >= 0 && r < ROWS) grid[r][piece_x] = piece[i];
154
+ }
155
+ for (i = 0; i < 3; i++) {
156
+ r = (int8_t)(piece_y + i);
157
+ if (r < 0 || r >= ROWS) continue;
158
+ for (c = 0; c <= COLS - 3; c++) {
159
+ a = grid[r][c]; b = grid[r][c + 1]; d = grid[r][c + 2];
160
+ if (a != 0 && a == b && b == d) {
161
+ grid[r][c] = 0; grid[r][c + 1] = 0; grid[r][c + 2] = 0;
162
+ if (score < 999) score += 3;
163
+ msx_psg_tone(0, 0x180, 13);
164
+ blip = 8;
165
+ }
166
+ }
167
+ }
168
+ draw_grid();
169
+ }
170
+
171
+ static void draw_piece(uint8_t clear) {
172
+ uint8_t i;
173
+ int8_t r;
174
+ uint8_t v;
175
+ for (i = 0; i < 3; i++) {
176
+ r = (int8_t)(piece_y + i);
177
+ if (r < 0 || r >= ROWS) continue;
178
+ v = clear ? grid[r][piece_x] : piece[i];
179
+ draw_cell(piece_x, r, v);
180
+ }
181
+ }
182
+
183
+ void main(void) {
184
+ uint8_t r, c, dir, prev_dir, ta, tb, prev_ta, prev_tb, fall_rate, t;
185
+
186
+ msx_set_screen2();
187
+ msx_clear_sprites();
188
+ load_tiles();
189
+ msx_fill_vram(VRAM_NAME, 32 * 24, T_BLANK);
190
+
191
+ for (r = 0; r < ROWS; r++)
192
+ for (c = 0; c < COLS; c++) grid[r][c] = 0;
193
+
194
+ score = 0;
195
+ fall_timer = 0;
196
+ rng = 0xACE1;
197
+ blip = 0;
198
+ prev_dir = 0; prev_ta = 0; prev_tb = 0;
199
+ new_piece();
200
+ draw_well();
201
+ draw_grid();
202
+
203
+ for (;;) {
204
+ vsync();
205
+ draw_piece(1);
206
+
207
+ dir = msx_read_joystick(1);
208
+ if (dir == STICK_CENTER) dir = msx_read_joystick(0);
209
+ ta = (uint8_t)(gttrig(1) != 0);
210
+ tb = (uint8_t)(gttrig(2) != 0);
211
+
212
+ if ((dir == STICK_LEFT || dir == STICK_UL || dir == STICK_DL)
213
+ && !(prev_dir == STICK_LEFT || prev_dir == STICK_UL || prev_dir == STICK_DL)
214
+ && !collides((int8_t)(piece_x - 1), piece_y)) piece_x--;
215
+ if ((dir == STICK_RIGHT || dir == STICK_UR || dir == STICK_DR)
216
+ && !(prev_dir == STICK_RIGHT || prev_dir == STICK_UR || prev_dir == STICK_DR)
217
+ && !collides((int8_t)(piece_x + 1), piece_y)) piece_x++;
218
+
219
+ if (ta && !prev_ta) {
220
+ t = piece[0]; piece[0] = piece[1]; piece[1] = piece[2]; piece[2] = t;
221
+ msx_psg_tone(1, 0x280, 8); blip = 4;
222
+ }
223
+
224
+ if (tb && !prev_tb) {
225
+ while (!collides(piece_x, (int8_t)(piece_y + 1))) piece_y++;
226
+ lock_piece();
227
+ new_piece();
228
+ prev_dir = dir; prev_ta = ta; prev_tb = tb;
229
+ if (blip) { blip--; if (!blip) { msx_psg_off(0); msx_psg_off(1); } }
230
+ continue;
231
+ }
232
+
233
+ prev_dir = dir; prev_ta = ta; prev_tb = tb;
234
+
235
+ fall_rate = (dir == STICK_DOWN || dir == STICK_DL || dir == STICK_DR) ? 4 : 30;
236
+ fall_timer = (uint8_t)(fall_timer + 1);
237
+ if (fall_timer >= fall_rate) {
238
+ fall_timer = 0;
239
+ if (collides(piece_x, (int8_t)(piece_y + 1))) {
240
+ lock_piece();
241
+ new_piece();
242
+ } else {
243
+ piece_y++;
244
+ }
245
+ }
246
+ draw_piece(0);
247
+
248
+ if (blip) { blip--; if (!blip) { msx_psg_off(0); msx_psg_off(1); } }
249
+ }
250
+ }
@@ -0,0 +1,249 @@
1
+ /* ── racing/main.c — MSX top-down 3-lane racing scaffold (screen 2) ──
2
+ *
3
+ * Mirrors the SMS/GB/etc racing scaffolds, translated to the MSX VDP via
4
+ * the romdev helper lib (msx_hw.h + msx_vdp.c).
5
+ *
6
+ * Endless 3-lane top-down racer. Grey road down the centre lanes + green
7
+ * grass shoulders fill the whole 32x24 screen-2 name table. The player car
8
+ * sits near the bottom; obstacle cars (object pool) spawn at the top and
9
+ * slide down. Speed grows with score; an AABB crash triggers a ~60-frame
10
+ * freeze then auto-reset. SCORE is drawn as on-screen tiles.
11
+ *
12
+ * Controls: joystick PORT 1 LEFT/RIGHT (edge-detected) switches lanes.
13
+ *
14
+ * Cartridge rule: INIT must never return — main() ends in for(;;).
15
+ */
16
+ #include "msx_hw.h"
17
+
18
+ /* ── interrupt-free vblank sync (poll VDP status S#0 bit 7) ────────────── */
19
+ __sfr __at 0x99 VDPSTATUS;
20
+ static void vsync(void) {
21
+ (void)VDPSTATUS;
22
+ while (!(VDPSTATUS & 0x80)) {
23
+ }
24
+ }
25
+
26
+ #define LANE_LEFT_X 96
27
+ #define LANE_MID_X 124
28
+ #define LANE_RIGHT_X 152
29
+ #define PLAYER_Y 160
30
+ #define MAX_OBSTACLES 4
31
+
32
+ /* ── tile font (digits + S C O R E) + track tiles ─────────────────────── */
33
+ #define T_SPACE 0
34
+ #define T_S 1
35
+ #define T_C 2
36
+ #define T_O 3
37
+ #define T_R 4
38
+ #define T_E 5
39
+ #define T_0 6
40
+ #define T_GRASS 16
41
+ #define T_ROAD 17
42
+ #define T_LANE 18 /* dashed lane marker */
43
+
44
+ static const uint8_t font[19][8] = {
45
+ /* 0 SPACE */ {0,0,0,0,0,0,0,0},
46
+ /* 1 S */ {0x7C,0xC0,0xC0,0x78,0x0C,0x0C,0xF8,0x00},
47
+ /* 2 C */ {0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00},
48
+ /* 3 O */ {0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00},
49
+ /* 4 R */ {0xFC,0xC6,0xC6,0xFC,0xD8,0xCC,0xC6,0x00},
50
+ /* 5 E */ {0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xFE,0x00},
51
+ /* 6 0 */ {0x7C,0xCE,0xDE,0xF6,0xE6,0xC6,0x7C,0x00},
52
+ /* 7 1 */ {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00},
53
+ /* 8 2 */ {0x7C,0xC6,0x06,0x1C,0x70,0xC0,0xFE,0x00},
54
+ /* 9 3 */ {0x7C,0xC6,0x06,0x3C,0x06,0xC6,0x7C,0x00},
55
+ /* 10 4 */ {0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x00},
56
+ /* 11 5 */ {0xFE,0xC0,0xFC,0x06,0x06,0xC6,0x7C,0x00},
57
+ /* 12 6 */ {0x3C,0x60,0xC0,0xFC,0xC6,0xC6,0x7C,0x00},
58
+ /* 13 7 */ {0xFE,0x06,0x0C,0x18,0x30,0x30,0x30,0x00},
59
+ /* 14 8 */ {0x7C,0xC6,0xC6,0x7C,0xC6,0xC6,0x7C,0x00},
60
+ /* 15 9 */ {0x7C,0xC6,0xC6,0x7E,0x06,0x0C,0x78,0x00},
61
+ /* 16 GRASS */ {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
62
+ /* 17 ROAD */ {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
63
+ /* 18 LANE */ {0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x18}
64
+ };
65
+
66
+ /* colour bytes. 3=green(dark), 12=green(light), 14=grey, 15=white, 1=black */
67
+ #define COL_TEXT 0xF1 /* white text on black */
68
+ #define COL_GRASS 0xC1 /* light green grass on black */
69
+ #define COL_ROAD 0xE1 /* grey road on black */
70
+ #define COL_LANE 0xAE /* yellow dashes on grey road */
71
+
72
+ /* sprite patterns (8x8): player car + enemy car */
73
+ static const uint8_t spr_player[8] = {0x18,0x3C,0x24,0x3C,0x7E,0x24,0x7E,0x66};
74
+ static const uint8_t spr_enemy[8] = {0x66,0x7E,0x24,0x7E,0x3C,0x24,0x3C,0x18};
75
+ #define PAT_PLAYER 0
76
+ #define PAT_ENEMY 1
77
+ #define COL_PLAYER 15 /* white */
78
+ #define COL_ENEMY 9 /* red */
79
+
80
+ typedef struct { uint8_t x, y, alive; } Car;
81
+
82
+ static Car player;
83
+ static Car obstacles[MAX_OBSTACLES];
84
+ static uint16_t score;
85
+ static uint8_t spawn_timer;
86
+ static uint8_t game_over_timer;
87
+ static uint8_t player_lane;
88
+ static uint16_t rng;
89
+ static uint8_t blip;
90
+
91
+ static const uint8_t lane_x[3] = { LANE_LEFT_X, LANE_MID_X, LANE_RIGHT_X };
92
+
93
+ static uint8_t next_rand(void) {
94
+ rng ^= (uint16_t)(rng << 7);
95
+ rng ^= (uint16_t)(rng >> 9);
96
+ rng ^= (uint16_t)(rng << 8);
97
+ return (uint8_t)(rng & 0xFF);
98
+ }
99
+
100
+ static uint8_t aabb(Car *a, Car *b) {
101
+ return a->x < b->x + 8 && a->x + 8 > b->x
102
+ && a->y < b->y + 8 && a->y + 8 > b->y;
103
+ }
104
+
105
+ static void load_tiles(void) {
106
+ uint8_t third, i;
107
+ uint16_t pat, col;
108
+ for (third = 0; third < 3; third++) {
109
+ pat = (uint16_t)(VRAM_PATTERN + ((uint16_t)third << 11));
110
+ col = (uint16_t)(VRAM_COLOR + ((uint16_t)third << 11));
111
+ for (i = 0; i < 19; i++) {
112
+ uint8_t cc = COL_TEXT;
113
+ if (i == T_GRASS) cc = COL_GRASS;
114
+ else if (i == T_ROAD) cc = COL_ROAD;
115
+ else if (i == T_LANE) cc = COL_LANE;
116
+ msx_vram_write((uint16_t)(pat + ((uint16_t)i << 3)), font[i], 8);
117
+ msx_fill_vram((uint16_t)(col + ((uint16_t)i << 3)), 8, cc);
118
+ }
119
+ }
120
+ }
121
+
122
+ static void put_tile(uint8_t col, uint8_t row, uint8_t tile) {
123
+ msx_vram_write((uint16_t)(VRAM_NAME + (uint16_t)row * 32 + col), &tile, 1);
124
+ }
125
+
126
+ /* road spans cols ~11..20 (player X 96..152); grass elsewhere; dashed lane
127
+ * markers between the three lanes (cols 14 and 17) on alternating rows. */
128
+ static void draw_track(void) {
129
+ uint8_t row, col, t;
130
+ for (row = 0; row < 24; row++) {
131
+ for (col = 0; col < 32; col++) {
132
+ t = (col >= 11 && col <= 20) ? T_ROAD : T_GRASS;
133
+ if ((col == 14 || col == 17) && (row & 1)) t = T_LANE;
134
+ put_tile(col, row, t);
135
+ }
136
+ }
137
+ }
138
+
139
+ static void draw_label(void) {
140
+ put_tile(1, 0, T_S); put_tile(2, 0, T_C); put_tile(3, 0, T_O);
141
+ put_tile(4, 0, T_R); put_tile(5, 0, T_E);
142
+ }
143
+
144
+ static void draw_score(void) {
145
+ uint16_t s = score;
146
+ put_tile(7, 0, (uint8_t)(T_0 + (s / 100) % 10));
147
+ put_tile(8, 0, (uint8_t)(T_0 + (s / 10) % 10));
148
+ put_tile(9, 0, (uint8_t)(T_0 + s % 10));
149
+ }
150
+
151
+ static void reset_run(void) {
152
+ uint8_t i;
153
+ player_lane = 1;
154
+ player.x = lane_x[1];
155
+ player.y = PLAYER_Y;
156
+ player.alive = 1;
157
+ for (i = 0; i < MAX_OBSTACLES; i++) obstacles[i].alive = 0;
158
+ score = 0;
159
+ spawn_timer = 0;
160
+ game_over_timer = 0;
161
+ draw_score();
162
+ }
163
+
164
+ static void spawn_obstacle(void) {
165
+ uint8_t i;
166
+ for (i = 0; i < MAX_OBSTACLES; i++) {
167
+ if (!obstacles[i].alive) {
168
+ obstacles[i].x = lane_x[next_rand() % 3];
169
+ obstacles[i].y = 16;
170
+ obstacles[i].alive = 1;
171
+ return;
172
+ }
173
+ }
174
+ }
175
+
176
+ void main(void) {
177
+ uint8_t i, slot, dir, prev_dir;
178
+ int16_t step;
179
+
180
+ msx_set_screen2();
181
+ msx_clear_sprites();
182
+ load_tiles();
183
+ draw_track();
184
+ draw_label();
185
+
186
+ msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_PLAYER * 8), spr_player, 8);
187
+ msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_ENEMY * 8), spr_enemy, 8);
188
+
189
+ rng = 0xACE1;
190
+ blip = 0;
191
+ prev_dir = 0;
192
+ reset_run();
193
+
194
+ for (;;) {
195
+ vsync();
196
+
197
+ /* push sprites */
198
+ slot = 0;
199
+ msx_set_sprite(slot++, player.x, player.y, PAT_PLAYER, COL_PLAYER);
200
+ for (i = 0; i < MAX_OBSTACLES; i++)
201
+ msx_set_sprite(slot++, obstacles[i].x,
202
+ obstacles[i].alive ? obstacles[i].y : SPRITE_END_Y,
203
+ PAT_ENEMY, COL_ENEMY);
204
+
205
+ dir = msx_read_joystick(1);
206
+ if (dir == STICK_CENTER) dir = msx_read_joystick(0);
207
+
208
+ if (game_over_timer > 0) {
209
+ game_over_timer--;
210
+ if (game_over_timer == 0) reset_run();
211
+ prev_dir = dir;
212
+ if (blip) { blip--; if (!blip) msx_psg_off(0); }
213
+ continue;
214
+ }
215
+
216
+ if ((dir == STICK_LEFT || dir == STICK_UL || dir == STICK_DL)
217
+ && !(prev_dir == STICK_LEFT || prev_dir == STICK_UL || prev_dir == STICK_DL)
218
+ && player_lane > 0) { player_lane--; msx_psg_tone(1, 0x280, 6); blip = 3; }
219
+ if ((dir == STICK_RIGHT || dir == STICK_UR || dir == STICK_DR)
220
+ && !(prev_dir == STICK_RIGHT || prev_dir == STICK_UR || prev_dir == STICK_DR)
221
+ && player_lane < 2) { player_lane++; msx_psg_tone(1, 0x280, 6); blip = 3; }
222
+ player.x = lane_x[player_lane];
223
+ prev_dir = dir;
224
+
225
+ step = (int16_t)(2 + (score / 200));
226
+ if (step > 4) step = 4;
227
+
228
+ for (i = 0; i < MAX_OBSTACLES; i++) {
229
+ if (!obstacles[i].alive) continue;
230
+ obstacles[i].y = (uint8_t)(obstacles[i].y + step);
231
+ if (obstacles[i].y >= 184) obstacles[i].alive = 0;
232
+ }
233
+
234
+ spawn_timer = (uint8_t)(spawn_timer + 1);
235
+ if (spawn_timer >= 36) { spawn_timer = 0; spawn_obstacle(); }
236
+
237
+ for (i = 0; i < MAX_OBSTACLES; i++) {
238
+ if (obstacles[i].alive && aabb(&player, &obstacles[i])) {
239
+ game_over_timer = 60;
240
+ msx_psg_tone(0, 0x600, 15); blip = 12;
241
+ break;
242
+ }
243
+ }
244
+
245
+ if (score < 999) { score++; draw_score(); }
246
+
247
+ if (blip) { blip--; if (!blip) { msx_psg_off(0); msx_psg_off(1); } }
248
+ }
249
+ }