romdevtools 0.16.0 → 0.22.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 (209) hide show
  1. package/AGENTS.md +75 -16
  2. package/CHANGELOG.md +316 -0
  3. package/examples/README.md +2 -0
  4. package/examples/atari2600/templates/platformer.asm +460 -0
  5. package/examples/atari2600/templates/racing.asm +463 -0
  6. package/examples/atari2600/templates/shmup.asm +386 -0
  7. package/examples/atari2600/templates/sports.asm +362 -0
  8. package/examples/atari7800/templates/default.c +49 -5
  9. package/examples/atari7800/templates/hello_sprite.c +48 -4
  10. package/examples/atari7800/templates/music_demo.c +47 -2
  11. package/examples/atari7800/templates/platformer.c +43 -4
  12. package/examples/atari7800/templates/puzzle.c +39 -4
  13. package/examples/atari7800/templates/racing.c +39 -4
  14. package/examples/atari7800/templates/shmup.c +40 -2
  15. package/examples/atari7800/templates/sports.c +36 -5
  16. package/examples/c64/templates/platformer.c +19 -5
  17. package/examples/c64/templates/puzzle.c +32 -2
  18. package/examples/c64/templates/shmup.c +28 -2
  19. package/examples/c64/templates/sports.c +30 -2
  20. package/examples/c64/templates/tile_engine.c +77 -27
  21. package/examples/gb/templates/default.c +110 -16
  22. package/examples/gb/templates/hello_sprite.c +15 -6
  23. package/examples/gb/templates/music_demo.c +36 -0
  24. package/examples/gb/templates/platformer.c +28 -6
  25. package/examples/gb/templates/puzzle.c +35 -4
  26. package/examples/gb/templates/racing.c +75 -10
  27. package/examples/gb/templates/shmup.c +41 -3
  28. package/examples/gb/templates/sports.c +51 -3
  29. package/examples/gb/templates/tile_engine.c +3 -2
  30. package/examples/gba/templates/gba_hello.c +29 -11
  31. package/examples/gba/templates/maxmod_demo.c +36 -2
  32. package/examples/gba/templates/platformer.c +3 -1
  33. package/examples/gba/templates/puzzle.c +15 -3
  34. package/examples/gba/templates/racing.c +65 -3
  35. package/examples/gba/templates/shmup.c +41 -4
  36. package/examples/gba/templates/sports.c +36 -2
  37. package/examples/gba/templates/tonc_hello.c +41 -5
  38. package/examples/gba/templates/tonc_hello_sprite.c +35 -1
  39. package/examples/gbc/templates/default.c +103 -26
  40. package/examples/gbc/templates/hello_sprite.c +12 -3
  41. package/examples/gbc/templates/music_demo.c +56 -12
  42. package/examples/gbc/templates/platformer.c +28 -6
  43. package/examples/gbc/templates/puzzle.c +35 -4
  44. package/examples/gbc/templates/racing.c +88 -21
  45. package/examples/gbc/templates/shmup.c +37 -3
  46. package/examples/gbc/templates/sports.c +48 -3
  47. package/examples/gbc/templates/tile_engine.c +3 -2
  48. package/examples/genesis/main.s +53 -1
  49. package/examples/genesis/templates/hello_sprite.c +25 -3
  50. package/examples/genesis/templates/puzzle.c +37 -3
  51. package/examples/genesis/templates/racing.c +44 -11
  52. package/examples/genesis/templates/sgdk_hello.c +34 -1
  53. package/examples/genesis/templates/shmup.c +31 -1
  54. package/examples/genesis/templates/shmup_2p.c +31 -0
  55. package/examples/genesis/templates/xgm2_demo.c +20 -0
  56. package/examples/gg/templates/default.c +56 -18
  57. package/examples/gg/templates/hello_sprite.c +25 -2
  58. package/examples/gg/templates/music_demo.c +24 -2
  59. package/examples/gg/templates/platformer.c +18 -12
  60. package/examples/gg/templates/puzzle.c +38 -7
  61. package/examples/gg/templates/racing.c +58 -9
  62. package/examples/gg/templates/shmup.c +47 -3
  63. package/examples/gg/templates/sports.c +57 -16
  64. package/examples/gg/templates/tile_engine.c +12 -6
  65. package/examples/lynx/templates/default.c +39 -8
  66. package/examples/lynx/templates/hello_sprite.c +15 -1
  67. package/examples/lynx/templates/music_demo.c +13 -1
  68. package/examples/lynx/templates/puzzle.c +28 -1
  69. package/examples/lynx/templates/racing.c +34 -7
  70. package/examples/lynx/templates/shmup.c +42 -3
  71. package/examples/lynx/templates/sports.c +29 -2
  72. package/examples/msx/platformer/main.c +213 -0
  73. package/examples/msx/puzzle/main.c +250 -0
  74. package/examples/msx/racing/main.c +249 -0
  75. package/examples/msx/shmup/main.c +288 -0
  76. package/examples/msx/sports/main.c +182 -0
  77. package/examples/nes/templates/default.c +67 -19
  78. package/examples/nes/templates/hello_sprite.c +35 -0
  79. package/examples/nes/templates/music_demo.c +40 -0
  80. package/examples/nes/templates/platformer.c +65 -6
  81. package/examples/nes/templates/puzzle.c +67 -6
  82. package/examples/nes/templates/racing.c +45 -13
  83. package/examples/nes/templates/shmup.c +51 -2
  84. package/examples/nes/templates/sports.c +51 -6
  85. package/examples/pce/catch_game/main.c +22 -3
  86. package/examples/pce/music_sfx/main.c +28 -1
  87. package/examples/pce/platformer/main.c +283 -0
  88. package/examples/pce/puzzle/main.c +304 -0
  89. package/examples/pce/racing/main.c +304 -0
  90. package/examples/pce/shmup/main.c +346 -0
  91. package/examples/pce/sports/main.c +254 -0
  92. package/examples/pce/sprite_move/main.c +7 -2
  93. package/examples/sms/main.c +35 -6
  94. package/examples/sms/templates/hello_sprite.c +29 -3
  95. package/examples/sms/templates/music_demo.c +18 -4
  96. package/examples/sms/templates/puzzle.c +34 -5
  97. package/examples/sms/templates/racing.c +39 -2
  98. package/examples/sms/templates/shmup.c +41 -2
  99. package/examples/sms/templates/shmup_2p.c +24 -1
  100. package/examples/sms/templates/sports.c +47 -4
  101. package/examples/snes/main.asm +108 -17
  102. package/examples/snes/templates/c-hello-data.asm +23 -0
  103. package/examples/snes/templates/c-hello.c +18 -1
  104. package/examples/snes/templates/default.c +50 -28
  105. package/examples/snes/templates/hello_sprite-data.asm +23 -0
  106. package/examples/snes/templates/hello_sprite.c +17 -1
  107. package/examples/snes/templates/music_demo-data.asm +23 -0
  108. package/examples/snes/templates/music_demo.c +22 -4
  109. package/examples/snes/templates/platformer-data.asm +22 -0
  110. package/examples/snes/templates/platformer.c +20 -2
  111. package/examples/snes/templates/puzzle-data.asm +22 -0
  112. package/examples/snes/templates/puzzle.c +21 -2
  113. package/examples/snes/templates/racing-data.asm +22 -0
  114. package/examples/snes/templates/racing.c +17 -1
  115. package/examples/snes/templates/shmup-data.asm +22 -0
  116. package/examples/snes/templates/shmup.c +20 -1
  117. package/examples/snes/templates/sports-data.asm +22 -0
  118. package/examples/snes/templates/sports.c +16 -1
  119. package/package.json +1 -1
  120. package/src/cheats/gamegenie.js +0 -1
  121. package/src/cli/smoke.js +1 -3
  122. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  123. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  124. package/src/host/LibretroHost.js +191 -16
  125. package/src/host/callbacks.js +9 -1
  126. package/src/host/chafa-render.js +2 -0
  127. package/src/host/dsp-state.js +2 -2
  128. package/src/host/gpgx-state.js +4 -0
  129. package/src/host/types.js +15 -8
  130. package/src/http/routes.js +1 -1
  131. package/src/http/tool-registry.js +26 -1
  132. package/src/mcp/server.js +1 -1
  133. package/src/mcp/state.js +36 -0
  134. package/src/mcp/tools/address-to-symbol.js +0 -1
  135. package/src/mcp/tools/art-loaders.js +1 -1
  136. package/src/mcp/tools/cart-parts.js +75 -4
  137. package/src/mcp/tools/classify-region.js +1 -1
  138. package/src/mcp/tools/diff-roms.js +1 -1
  139. package/src/mcp/tools/disasm-rebuild.js +507 -0
  140. package/src/mcp/tools/disasm.js +97 -9
  141. package/src/mcp/tools/find-references.js +1 -2
  142. package/src/mcp/tools/font-map.js +1 -1
  143. package/src/mcp/tools/frame.js +168 -3
  144. package/src/mcp/tools/index.js +0 -49
  145. package/src/mcp/tools/input-layout.js +0 -1
  146. package/src/mcp/tools/input.js +33 -3
  147. package/src/mcp/tools/lifecycle.js +18 -4
  148. package/src/mcp/tools/lospec.js +0 -19
  149. package/src/mcp/tools/platform-docs.js +1 -1
  150. package/src/mcp/tools/platform-tools.js +4 -4
  151. package/src/mcp/tools/project.js +54 -11
  152. package/src/mcp/tools/reinject.js +0 -1
  153. package/src/mcp/tools/rom-id.js +2 -2
  154. package/src/mcp/tools/snippets.js +2 -2
  155. package/src/mcp/tools/sprite-pipeline.js +1 -2
  156. package/src/mcp/tools/state.js +201 -14
  157. package/src/mcp/tools/tile-inspect.js +1 -1
  158. package/src/mcp/tools/toolchain.js +105 -12
  159. package/src/mcp/tools/watch-memory.js +137 -16
  160. package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +34 -0
  161. package/src/platforms/atari2600/TROUBLESHOOTING.md +6 -0
  162. package/src/platforms/atari7800/TROUBLESHOOTING.md +6 -0
  163. package/src/platforms/c64/MENTAL_MODEL.md +45 -1
  164. package/src/platforms/c64/TROUBLESHOOTING.md +6 -0
  165. package/src/platforms/c64/d64.js +280 -0
  166. package/src/platforms/c64/sid.js +0 -2
  167. package/src/platforms/common/metasprite-adapters.js +1 -1
  168. package/src/platforms/common/metasprite-codegen.js +3 -3
  169. package/src/platforms/common/registers.js +5 -3
  170. package/src/platforms/gb/MENTAL_MODEL.md +10 -0
  171. package/src/platforms/gb/TROUBLESHOOTING.md +6 -0
  172. package/src/platforms/gb/lib/c/gb_runtime.c +4 -4
  173. package/src/platforms/gba/TROUBLESHOOTING.md +6 -0
  174. package/src/platforms/gbc/TROUBLESHOOTING.md +6 -0
  175. package/src/platforms/gbc/lib/c/gb_runtime.c +4 -4
  176. package/src/platforms/genesis/TROUBLESHOOTING.md +6 -0
  177. package/src/platforms/gg/TROUBLESHOOTING.md +6 -0
  178. package/src/platforms/lynx/TROUBLESHOOTING.md +6 -0
  179. package/src/platforms/msx/MENTAL_MODEL.md +10 -6
  180. package/src/platforms/msx/TROUBLESHOOTING.md +6 -0
  181. package/src/platforms/nes/MENTAL_MODEL.md +63 -2
  182. package/src/platforms/nes/TROUBLESHOOTING.md +6 -0
  183. package/src/platforms/nes/image-to-tilemap.js +3 -0
  184. package/src/platforms/nes/lib/asm/famitone2.s +5 -1
  185. package/src/platforms/pce/MENTAL_MODEL.md +9 -4
  186. package/src/platforms/pce/TROUBLESHOOTING.md +6 -0
  187. package/src/platforms/pce/lib/c/pce_video.c +1 -1
  188. package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
  189. package/src/platforms/snes/TROUBLESHOOTING.md +6 -0
  190. package/src/platforms/snes/brr.js +0 -2
  191. package/src/playtest/playtest.js +0 -7
  192. package/src/rom-id/identifier.js +15 -0
  193. package/src/toolchains/asar/asar.js +0 -9
  194. package/src/toolchains/assemble-snippet.js +30 -12
  195. package/src/toolchains/cc65/ines.js +145 -0
  196. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +14 -1
  197. package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
  198. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
  199. package/src/toolchains/common/reassemble.js +10 -3
  200. package/src/toolchains/common/sdk-cache.js +1 -1
  201. package/src/toolchains/genesis-c/genesis-c.js +5 -3
  202. package/src/toolchains/index.js +27 -3
  203. package/src/toolchains/parse-errors.js +78 -1
  204. package/src/toolchains/sdcc/preflight-lint.js +5 -1
  205. package/src/toolchains/sdcc/sdcc.js +1 -1
  206. package/src/toolchains/sjasm/sjasm.js +1 -1
  207. package/src/toolchains/snes-c/snes-c.js +2 -2
  208. package/src/toolchains/vasm68k/vasm68k.js +2 -4
  209. package/src/toolchains/wladx/wladx.js +1 -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
+ }
@@ -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
+ }