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,254 @@
1
+ /*
2
+ * PC Engine "sports" — a Pong-style two-paddle scaffold.
3
+ *
4
+ * Two paddles and a bouncing ball on a netted court. The d-pad moves player 1's
5
+ * (left) paddle up/down. Player 2's (right) paddle follows the ball with a
6
+ * chase-AI so the game is playable solo. The ball deflects off paddles and the
7
+ * top/bottom court lines; a ball past either edge scores for the other side and
8
+ * re-serves. Score is shown with background digit tiles. Mirrors the
9
+ * NES/Genesis/SNES/GB/SMS sports scaffolds.
10
+ *
11
+ * Paddles + ball are hardware sprites; the court (green field, white border
12
+ * lines, dashed centre net) is the BG tilemap, so the screen is clearly a
13
+ * sports court (clears the verify gate).
14
+ *
15
+ * PCE notes (see pce_hw.h / MENTAL_MODEL.md):
16
+ * - disp_enable() turns on BG + sprites + the VBlank IRQ (waitvsync needs it).
17
+ * - .bss must be non-empty (pce_video.c's _pce_keep[] covers it).
18
+ *
19
+ * cc65 is C89 — declare locals at the top of a block.
20
+ */
21
+ #include <pce.h>
22
+ #include <stdint.h> /* int8_t/int16_t for ball velocity + positions */
23
+ #include "pce_hw.h"
24
+
25
+ /* ---- VRAM layout (word addresses) --------------------------------------- */
26
+ #define BAT_VRAM 0x0000
27
+ #define FONT_VRAM 0x1000 /* digit tiles */
28
+ #define GREEN_VRAM 0x1400 /* court field (colour 1) */
29
+ #define LINE_VRAM 0x1410 /* court line / border (colour 2) */
30
+ #define NET_VRAM 0x1420 /* dashed centre net */
31
+ #define PADDLE_VRAM 0x1800 /* 16x16 paddle segment */
32
+ #define BALL_VRAM 0x1840 /* 16x16 ball */
33
+
34
+ #define BAT_ENTRY(pal, vram) ((u16)(((pal) << 12) | ((vram) >> 4)))
35
+
36
+ #define COURT_TOP 24
37
+ #define COURT_BOT 216
38
+ #define PADDLE_H 48 /* 3 stacked 16px sprite segments */
39
+ #define BALL_SIZE 12
40
+ #define PADDLE_X1 16
41
+ #define PADDLE_X2 224
42
+
43
+ /* ---- font (digits only) ------------------------------------------------- */
44
+ #define NUM_GLYPHS 10
45
+ static const u8 FONT5x7[NUM_GLYPHS][7] = {
46
+ {0x0E,0x11,0x13,0x15,0x19,0x11,0x0E}, /* 0 */
47
+ {0x04,0x0C,0x04,0x04,0x04,0x04,0x0E}, /* 1 */
48
+ {0x0E,0x11,0x01,0x02,0x04,0x08,0x1F}, /* 2 */
49
+ {0x1F,0x02,0x04,0x02,0x01,0x11,0x0E}, /* 3 */
50
+ {0x02,0x06,0x0A,0x12,0x1F,0x02,0x02}, /* 4 */
51
+ {0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E}, /* 5 */
52
+ {0x06,0x08,0x10,0x1E,0x11,0x11,0x0E}, /* 6 */
53
+ {0x1F,0x01,0x02,0x04,0x08,0x08,0x08}, /* 7 */
54
+ {0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E}, /* 8 */
55
+ {0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C} /* 9 */
56
+ };
57
+
58
+ /* ---- state -------------------------------------------------------------- */
59
+ static int16_t p1y, p2y, bx, by;
60
+ static int8_t bdx, bdy;
61
+ static u8 score_p1, score_p2;
62
+ static u8 serve_timer;
63
+ static u8 pad;
64
+ static u16 tile_buf[16];
65
+ static u16 spr_buf[64];
66
+ static u8 sfx_timer;
67
+
68
+ static void make_solid_tile(u16 *t, u8 ci) {
69
+ u8 r;
70
+ u8 p0 = (ci & 1) ? 0xFF : 0x00;
71
+ u8 p1 = (ci & 2) ? 0xFF : 0x00;
72
+ u8 p2 = (ci & 4) ? 0xFF : 0x00;
73
+ u8 p3 = (ci & 8) ? 0xFF : 0x00;
74
+ for (r = 0; r < 8; ++r) {
75
+ t[r] = (u16)(p0 | (p1 << 8));
76
+ t[r + 8] = (u16)(p2 | (p3 << 8));
77
+ }
78
+ }
79
+
80
+ /* net tile: green field (colour 1) with a colour-2 vertical dash centre column */
81
+ static void make_net_tile(u16 *t) {
82
+ u8 r;
83
+ for (r = 0; r < 8; ++r) {
84
+ u8 dash = (r < 5); /* dashed: top 5 rows of each tile are the dash */
85
+ u8 p1 = dash ? 0x18 : 0x00; /* centre 2 px -> colour 2 (plane1) */
86
+ t[r] = (u16)(0x00FF | (p1 << 8)); /* plane0 full (green) + dash */
87
+ t[r + 8] = 0x0000;
88
+ }
89
+ }
90
+
91
+ static void make_paddle_sprite(void) {
92
+ u8 r;
93
+ for (r = 0; r < 64; ++r) spr_buf[r] = 0;
94
+ /* a solid 8px-wide vertical bar centred in the 16px cell, colour 1 */
95
+ for (r = 0; r < 16; ++r) spr_buf[r] = 0x0FF0;
96
+ load_tiles(PADDLE_VRAM, spr_buf, 64);
97
+ }
98
+
99
+ static void make_ball_sprite(void) {
100
+ static const u16 ball[16] = {
101
+ 0x0000, 0x0000, 0x07E0, 0x0FF0, 0x1FF8, 0x1FF8, 0x3FFC, 0x3FFC,
102
+ 0x3FFC, 0x3FFC, 0x1FF8, 0x1FF8, 0x0FF0, 0x07E0, 0x0000, 0x0000
103
+ };
104
+ u8 r;
105
+ for (r = 0; r < 64; ++r) spr_buf[r] = 0;
106
+ for (r = 0; r < 16; ++r) spr_buf[r] = ball[r]; /* colour 1 */
107
+ load_tiles(BALL_VRAM, spr_buf, 64);
108
+ }
109
+
110
+ static void upload_font(void) {
111
+ u8 g, row, bits, plane0;
112
+ for (g = 0; g < NUM_GLYPHS; ++g) {
113
+ for (row = 0; row < 16; ++row) tile_buf[row] = 0;
114
+ for (row = 0; row < 7; ++row) {
115
+ bits = FONT5x7[g][row];
116
+ plane0 = 0;
117
+ if (bits & 0x10) plane0 |= 0x40;
118
+ if (bits & 0x08) plane0 |= 0x20;
119
+ if (bits & 0x04) plane0 |= 0x10;
120
+ if (bits & 0x02) plane0 |= 0x08;
121
+ if (bits & 0x01) plane0 |= 0x04;
122
+ tile_buf[row] = (u16)plane0;
123
+ }
124
+ load_tiles((u16)(FONT_VRAM + g * 16), tile_buf, 16);
125
+ }
126
+ }
127
+
128
+ static void draw_court(void) {
129
+ u8 r, c;
130
+ u16 g = BAT_ENTRY(0, GREEN_VRAM);
131
+ u16 ln = BAT_ENTRY(0, LINE_VRAM);
132
+ u16 nt = BAT_ENTRY(0, NET_VRAM);
133
+ u16 e;
134
+ for (r = 0; r < 32; ++r) {
135
+ vram_set_write_addr((u16)(BAT_VRAM + r * 32));
136
+ for (c = 0; c < 32; ++c) {
137
+ if (r <= 2 || r >= 27) e = ln; /* top/bottom border */
138
+ else if (c == 1 || c == 30) e = ln; /* sidelines */
139
+ else if (c == 16) e = nt; /* centre net */
140
+ else e = g; /* field */
141
+ VDC_DATA_LO = (u8)(e & 0xFF);
142
+ VDC_DATA_HI = (u8)(e >> 8);
143
+ }
144
+ }
145
+ }
146
+
147
+ static void put_glyph(u8 col, u8 row, u8 digit) {
148
+ u16 e = BAT_ENTRY(0, (u16)(FONT_VRAM + digit * 16));
149
+ vram_set_write_addr((u16)(BAT_VRAM + row * 32 + col));
150
+ VDC_DATA_LO = (u8)(e & 0xFF);
151
+ VDC_DATA_HI = (u8)(e >> 8);
152
+ }
153
+
154
+ static void draw_scores(void) {
155
+ put_glyph(12, 1, (u8)(score_p1 % 10));
156
+ put_glyph(19, 1, (u8)(score_p2 % 10));
157
+ }
158
+
159
+ static void serve_ball(u8 to_left) {
160
+ bx = 120; by = 110;
161
+ bdx = to_left ? -2 : 2;
162
+ bdy = ((score_p1 + score_p2) & 1) ? -1 : 1;
163
+ serve_timer = 40;
164
+ }
165
+
166
+ void main(void) {
167
+ u8 i;
168
+
169
+ _pce_keep[0] = 0;
170
+
171
+ /* palette */
172
+ vce_set_color(0, PCE_RGB(0, 1, 0)); /* backdrop dark green */
173
+ vce_set_color(1, PCE_RGB(0, 4, 1)); /* BG c1: court green */
174
+ vce_set_color(2, PCE_RGB(7, 7, 7)); /* BG c2: white lines/net/digit */
175
+ vce_set_color(256, PCE_RGB(0, 0, 0)); /* spr pal0 transparent */
176
+ vce_set_color(257, PCE_RGB(7, 7, 7)); /* spr pal0 c1: white paddle */
177
+ vce_set_color(272, PCE_RGB(0, 0, 0)); /* spr pal1 transparent */
178
+ vce_set_color(273, PCE_RGB(7, 7, 0)); /* spr pal1 c1: yellow ball */
179
+
180
+ upload_font();
181
+ make_solid_tile(tile_buf, 1); load_tiles(GREEN_VRAM, tile_buf, 16);
182
+ make_solid_tile(tile_buf, 2); load_tiles(LINE_VRAM, tile_buf, 16);
183
+ make_net_tile(tile_buf); load_tiles(NET_VRAM, tile_buf, 16);
184
+ make_paddle_sprite();
185
+ make_ball_sprite();
186
+
187
+ draw_court();
188
+
189
+ p1y = 90; p2y = 90;
190
+ score_p1 = 0; score_p2 = 0;
191
+ sfx_timer = 0;
192
+ serve_ball(0);
193
+ draw_scores();
194
+
195
+ pce_joy_init();
196
+ disp_enable();
197
+
198
+ for (;;) {
199
+ u8 slot;
200
+ int16_t target;
201
+ waitvsync();
202
+
203
+ /* stage sprites: P1 paddle (3 segs), P2 paddle (3 segs), ball */
204
+ slot = 0;
205
+ for (i = 0; i < 3; ++i)
206
+ set_sprite(slot++, PADDLE_X1, (u16)(p1y + i * 16), PADDLE_VRAM >> 6, 0);
207
+ for (i = 0; i < 3; ++i)
208
+ set_sprite(slot++, PADDLE_X2, (u16)(p2y + i * 16), PADDLE_VRAM >> 6, 0);
209
+ set_sprite(slot++, (u16)bx, (u16)by, BALL_VRAM >> 6, 1);
210
+ satb_dma();
211
+
212
+ pad = pce_joy_read();
213
+
214
+ /* P1 control */
215
+ if ((pad & PCE_JOY_UP) && p1y > COURT_TOP) p1y -= 3;
216
+ if ((pad & PCE_JOY_DOWN) && p1y < COURT_BOT - PADDLE_H) p1y += 3;
217
+
218
+ /* P2 chase-AI */
219
+ target = (int16_t)(by - PADDLE_H / 2 + BALL_SIZE / 2);
220
+ if (p2y < target && p2y < COURT_BOT - PADDLE_H) p2y += 2;
221
+ else if (p2y > target && p2y > COURT_TOP) p2y -= 2;
222
+
223
+ if (serve_timer > 0) {
224
+ serve_timer--;
225
+ } else {
226
+ bx = (int16_t)(bx + bdx);
227
+ by = (int16_t)(by + bdy);
228
+
229
+ if (by < COURT_TOP) { by = COURT_TOP; bdy = (int8_t)(-bdy); psg_tone(1, 0x280, 18); sfx_timer = 4; }
230
+ if (by + BALL_SIZE > COURT_BOT) { by = (int16_t)(COURT_BOT - BALL_SIZE); bdy = (int8_t)(-bdy); psg_tone(1, 0x280, 18); sfx_timer = 4; }
231
+
232
+ /* left paddle */
233
+ if (bdx < 0 && bx <= PADDLE_X1 + 12 && bx + BALL_SIZE >= PADDLE_X1 &&
234
+ by + BALL_SIZE > p1y && by < p1y + PADDLE_H) {
235
+ bdx = (int8_t)(-bdx);
236
+ bx = PADDLE_X1 + 12;
237
+ psg_tone(0, 0x200, 22); sfx_timer = 4;
238
+ }
239
+ /* right paddle */
240
+ if (bdx > 0 && bx + BALL_SIZE >= PADDLE_X2 && bx <= PADDLE_X2 + 12 &&
241
+ by + BALL_SIZE > p2y && by < p2y + PADDLE_H) {
242
+ bdx = (int8_t)(-bdx);
243
+ bx = (int16_t)(PADDLE_X2 - BALL_SIZE);
244
+ psg_tone(0, 0x200, 22); sfx_timer = 4;
245
+ }
246
+
247
+ /* scoring */
248
+ if (bx < 2) { if (score_p2 < 9) score_p2++; draw_scores(); psg_tone(0, 0x100, 24); sfx_timer = 8; serve_ball(0); }
249
+ if (bx > 246) { if (score_p1 < 9) score_p1++; draw_scores(); psg_tone(0, 0x100, 24); sfx_timer = 8; serve_ball(1); }
250
+ }
251
+
252
+ if (sfx_timer) { --sfx_timer; if (sfx_timer == 0) { psg_off(0); psg_off(1); } }
253
+ }
254
+ }
@@ -90,7 +90,10 @@ static void make_sprite(void) {
90
90
  }
91
91
 
92
92
  /* Draw a 32x32-cell checkerboard of TILE_A / TILE_B across the BAT. The default
93
- * PCE virtual screen is 32x32 cells (256x256 px), which covers the display. */
93
+ * PCE virtual screen is 32x32 cells (256x256 px), which covers the display.
94
+ * A two-colour checkerboard (green + dark teal) makes the whole playfield read
95
+ * as a real, visible background — a SOLID single-colour fill instead looks blank
96
+ * to a human (one colour covers >92% of the screen), so we alternate by cell. */
94
97
  static void fill_bat(void) {
95
98
  u16 ea = BAT_ENTRY(TILE_A_VRAM, 0);
96
99
  u16 eb = BAT_ENTRY(TILE_B_VRAM, 0);
@@ -98,7 +101,9 @@ static void fill_bat(void) {
98
101
  for (r = 0; r < 32; ++r) {
99
102
  vram_set_write_addr((u16)(BAT_VRAM + r * 32));
100
103
  for (col = 0; col < 32; ++col) {
101
- e = ea; /* solid background so the sprite stands out */
104
+ /* 2x2-cell checkerboard: alternates green/teal so the background is
105
+ * unmistakably present while the sprite still stands out clearly. */
106
+ e = (((r >> 1) ^ (col >> 1)) & 1) ? eb : ea;
102
107
  VDC_DATA_LO = (u8)(e & 0xFF);
103
108
  VDC_DATA_HI = (u8)(e >> 8);
104
109
  }
@@ -47,9 +47,10 @@ static void vdp_init(void) {
47
47
  }
48
48
 
49
49
  /* ─── Palette + tile data ─────────────────────────────────────────── */
50
- /* SMS CRAM: 2-2-2 BGR. Entry 0 = backdrop. */
50
+ /* SMS CRAM: 2-2-2 BGR. Entry 0 = backdrop. Entries: 1 = yellow 'H',
51
+ * 2 = blue panel, 3 = dark-blue panel (the checkerboard fill colours). */
51
52
  static const uint8_t palette[32] = {
52
- 0x30, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* BG: blue, yellow */
53
+ 0x10, 0x0F, 0x30, 0x14, 0x00, 0x00, 0x00, 0x00, /* BG: backdrop, yellow, blue, dk-blue */
53
54
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
54
55
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* sprite palette unused */
55
56
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -68,6 +69,23 @@ static const uint8_t tile_h[32] = {
68
69
  0x00, 0x00, 0x00, 0x00,
69
70
  };
70
71
 
72
+ /* Tile 2 = solid colour 2 (blue), tile 3 = solid colour 3 (dark blue).
73
+ * The whole 32×24 screen is painted as a 2-colour checkerboard so the
74
+ * screen is obviously not blank and no single colour dominates. Plane 1
75
+ * set → colour 2; planes 0+1 set → colour 3. */
76
+ static const uint8_t tile_fill2[32] = {
77
+ 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
78
+ 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
79
+ 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
80
+ 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
81
+ };
82
+ static const uint8_t tile_fill3[32] = {
83
+ 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
84
+ 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
85
+ 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
86
+ 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
87
+ };
88
+
71
89
  /* ─── Upload helpers ──────────────────────────────────────────────── */
72
90
  static void load_palette(void) {
73
91
  uint8_t i;
@@ -77,16 +95,27 @@ static void load_palette(void) {
77
95
 
78
96
  static void load_tile(void) {
79
97
  uint8_t i;
80
- /* Tile 1 at VRAM offset 32 (= tile_idx * 32). Tile 0 left blank. */
98
+ /* Tile 1 = 'H' at VRAM offset 32 (= tile_idx * 32). Tile 0 left blank. */
81
99
  vdp_set_addr(32, VDP_VRAM_WRITE);
82
100
  for (i = 0; i < 32; i++) PORT_VDP_DATA = tile_h[i];
101
+ /* Tile 2 = blue fill (offset 64), tile 3 = dark-blue fill (offset 96). */
102
+ vdp_set_addr(64, VDP_VRAM_WRITE);
103
+ for (i = 0; i < 32; i++) PORT_VDP_DATA = tile_fill2[i];
104
+ vdp_set_addr(96, VDP_VRAM_WRITE);
105
+ for (i = 0; i < 32; i++) PORT_VDP_DATA = tile_fill3[i];
83
106
  }
84
107
 
108
+ /* Paint the whole 32×24 visible screen as a blue/dark-blue checkerboard so
109
+ * the screen is obviously not blank and no single colour dominates. */
85
110
  static void clear_name_table(void) {
86
- uint16_t i;
111
+ uint8_t row, col;
87
112
  vdp_set_addr(0x3800, VDP_VRAM_WRITE);
88
- /* 32 cols × 28 rows × 2 bytes = 1792 entries. */
89
- for (i = 0; i < 1792; i++) PORT_VDP_DATA = 0;
113
+ for (row = 0; row < 24; row++) {
114
+ for (col = 0; col < 32; col++) {
115
+ PORT_VDP_DATA = ((row ^ col) & 1) ? 2 : 3; /* checkerboard tiles 2/3 */
116
+ PORT_VDP_DATA = 0; /* attr: BG palette, no flip */
117
+ }
118
+ }
90
119
  }
91
120
 
92
121
  static void place_h(void) {
@@ -28,22 +28,37 @@ extern void sms_vdp_write_reg(uint8_t reg, uint8_t value);
28
28
  extern void sms_vdp_set_addr(uint16_t addr, uint8_t prefix);
29
29
  extern void sms_load_palette(const uint8_t *palette);
30
30
  extern void sms_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
31
+ extern void sms_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
31
32
  extern void sms_vblank_wait(void);
32
33
  extern uint8_t sms_joypad_read(void);
33
34
  extern void sms_sprite_init(void);
34
35
  extern void sms_sprite_set(uint8_t slot, uint8_t x, uint8_t y, uint8_t tile);
35
36
  extern void sms_sat_upload(void);
36
37
 
37
- /* BG palette: backdrop blue + yellow. Sprite palette (entries 16-31)
38
- * we set white at index 17 so our sprite is visible.
38
+ /* BG palette: backdrop blue, colour 1 = teal, colour 2 = navy (the two
39
+ * tones of the dithered BG). Sprite palette (entries 16-31) sets white at
40
+ * index 17 so our sprite is visible.
39
41
  * SMS CRAM is 2-2-2 BGR: 0x00=black, 0x3F=white. */
40
42
  static const uint8_t palette[32] = {
41
- 0x10,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
43
+ 0x10,0x38,0x20,0x00, 0x00,0x00,0x00,0x00,
42
44
  0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
43
45
  0x00,0x3F,0x00,0x00, 0x00,0x00,0x00,0x00,
44
46
  0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
45
47
  };
46
48
 
49
+ /* Two BG tiles in the BG bank at $0000. Tile 0 is a dithered checkerboard
50
+ * (plane0/plane1 alternate per row) so the whole BG fills with TWO colours
51
+ * and no single colour dominates the frame — a flat one-colour fill still
52
+ * reads as a blank screen. */
53
+ static const uint8_t bg_tiles[32 * 1] = {
54
+ /* T_BG — dither: plane0=0xAA→colour 1, plane1=0x55→colour 2, swapped
55
+ * each row so it reads as a fine checkerboard. */
56
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
57
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
58
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
59
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
60
+ };
61
+
47
62
  /* One 8×8 sprite tile (4bpp interleaved). Filled square in color 1. */
48
63
  static const uint8_t sprite_tile[32] = {
49
64
  0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
@@ -52,6 +67,14 @@ static const uint8_t sprite_tile[32] = {
52
67
  0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
53
68
  };
54
69
 
70
+ /* Fill the whole 32×28 name table with the dithered BG tile. */
71
+ static void draw_bg(void) {
72
+ uint8_t row, col;
73
+ for (row = 0; row < 28; row++)
74
+ for (col = 0; col < 32; col++)
75
+ sms_set_tilemap_cell(row, col, 0, 0);
76
+ }
77
+
55
78
  void main(void) {
56
79
  uint8_t x = 124; /* mid-screen X */
57
80
  uint8_t y = 88; /* mid-screen Y */
@@ -59,6 +82,9 @@ void main(void) {
59
82
 
60
83
  sms_vdp_init();
61
84
  sms_load_palette(palette);
85
+ /* BG dither tile → BG bank $0000, then paint the whole name table. */
86
+ sms_load_tiles(0x0000, bg_tiles, 32);
87
+ draw_bg();
62
88
  /* Upload one sprite tile to VRAM $2000 (sprite tile area). */
63
89
  sms_load_tiles(0x2000, sprite_tile, 32);
64
90
 
@@ -29,10 +29,11 @@ extern void sms_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte
29
29
  extern void sms_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
30
30
  extern void sms_vblank_wait(void);
31
31
 
32
- /* BG palette: backdrop dark blue, fg colour 1 = bright cyan, colour 2 = yellow.
33
- * SMS CRAM is 2-2-2 BGR — 0x30 = bright blue, 0x3F = white, 0x0F = yellow. */
32
+ /* BG palette: backdrop dark blue, fg colour 1 = bright cyan, colour 2 = yellow,
33
+ * colour 3 = navy (the second dither tone behind the text).
34
+ * SMS CRAM is 2-2-2 BGR — 0x20 = blue, 0x3F = white/cyan, 0x0F = yellow. */
34
35
  static const uint8_t palette[32] = {
35
- 0x20, 0x3F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00,
36
+ 0x20, 0x3F, 0x0F, 0x28, 0x00, 0x00, 0x00, 0x00,
36
37
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
37
38
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
38
39
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -100,8 +101,19 @@ static const uint8_t font_tiles[] = {
100
101
  0xC3,0x00,0x00,0x00, 0xC3,0x00,0x00,0x00,
101
102
  0xC3,0x00,0x00,0x00, 0xC3,0x00,0x00,0x00,
102
103
  0x66,0x00,0x00,0x00, 0x3C,0x00,0x00,0x00,
104
+
105
+ /* tile 9 — dithered BG. plane1=0xFF (colour-2 bit always on), plane0
106
+ * alternates 0xAA/0x55 so pixels flip between colour 2 (yellow) and
107
+ * colour 3 (navy). Fills the whole field with TWO tones so no single
108
+ * colour dominates, while the cyan (colour 1) text stays distinct. */
109
+ 0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
110
+ 0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
111
+ 0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
112
+ 0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
103
113
  };
104
114
 
115
+ #define T_DITHER 9
116
+
105
117
  /* Tile indices for each char in our message — 'S' 'M' 'S' ' ' 'M' 'U'
106
118
  * 'S' 'I' 'C' ' ' 'D' 'E' 'M' 'O'. 14 cells total. */
107
119
  static const uint8_t message[14] = {
@@ -114,9 +126,11 @@ static const uint8_t message[14] = {
114
126
  static void clear_name_table(void) {
115
127
  uint8_t row;
116
128
  uint8_t col;
129
+ /* Fill with the dithered BG tile (not blank) so the whole screen reads
130
+ * as a two-tone field and never as a blank backdrop. */
117
131
  for (row = 0; row < 28; row++) {
118
132
  for (col = 0; col < 32; col++) {
119
- sms_set_tilemap_cell(row, col, 0, 0);
133
+ sms_set_tilemap_cell(row, col, T_DITHER, 0);
120
134
  }
121
135
  }
122
136
  }
@@ -25,16 +25,19 @@ extern uint8_t sms_joypad_read(void);
25
25
  #define T_R 1
26
26
  #define T_G 2
27
27
  #define T_B 3
28
+ #define T_WALL 4 /* well border */
29
+ #define T_FIELD 5 /* empty well interior */
28
30
 
29
31
  static const uint8_t palette[32] = {
30
- /* BG palette: backdrop black, red, green, blue */
31
- 0x00,0x03,0x0C,0x30, 0x00,0x00,0x00,0x00,
32
+ /* BG palette: 0 backdrop navy, 1 red, 2 green, 3 blue, 4 wall grey,
33
+ * 5 dim field blue */
34
+ 0x10,0x03,0x0C,0x30, 0x15,0x14, 0x00,0x00,
32
35
  0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
33
36
  0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
34
37
  0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
35
38
  };
36
39
 
37
- static const uint8_t bg_tiles[32 * 4] = {
40
+ static const uint8_t bg_tiles[32 * 6] = {
38
41
  /* T_BLANK */
39
42
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
40
43
  0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
@@ -53,6 +56,16 @@ static const uint8_t bg_tiles[32 * 4] = {
53
56
  0xFF,0xFF,0x00,0x00, 0xFF,0xFF,0x00,0x00,
54
57
  0xFF,0xFF,0x00,0x00, 0xFF,0xFF,0x00,0x00,
55
58
  0xFF,0xFF,0x00,0x00, 0xFF,0xFF,0x00,0x00,
59
+ /* T_WALL — colour 4 fill (plane 2 set) */
60
+ 0x00,0x00,0xFF,0x00, 0x00,0x00,0xFF,0x00,
61
+ 0x00,0x00,0xFF,0x00, 0x00,0x00,0xFF,0x00,
62
+ 0x00,0x00,0xFF,0x00, 0x00,0x00,0xFF,0x00,
63
+ 0x00,0x00,0xFF,0x00, 0x00,0x00,0xFF,0x00,
64
+ /* T_FIELD — colour 5 fill (planes 0+2 set) = dim field */
65
+ 0xFF,0x00,0xFF,0x00, 0xFF,0x00,0xFF,0x00,
66
+ 0xFF,0x00,0xFF,0x00, 0xFF,0x00,0xFF,0x00,
67
+ 0xFF,0x00,0xFF,0x00, 0xFF,0x00,0xFF,0x00,
68
+ 0xFF,0x00,0xFF,0x00, 0xFF,0x00,0xFF,0x00,
56
69
  };
57
70
 
58
71
  static uint8_t grid[ROWS][COLS];
@@ -76,7 +89,7 @@ static uint8_t tile_for(uint8_t c) {
76
89
  if (c == 1) return T_R;
77
90
  if (c == 2) return T_G;
78
91
  if (c == 3) return T_B;
79
- return T_BLANK;
92
+ return T_FIELD; /* empty cell shows the dim well interior, not backdrop */
80
93
  }
81
94
 
82
95
  static void draw_cell(int8_t col, int8_t row, uint8_t cell) {
@@ -85,6 +98,21 @@ static void draw_cell(int8_t col, int8_t row, uint8_t cell) {
85
98
  sms_set_tilemap_cell((uint8_t)(row + 1), (uint8_t)(col + 7), tile_for(cell), 0);
86
99
  }
87
100
 
101
+ /* Draw the well: a grey border frame around the 6x12 play field with a dim
102
+ * field interior, so the playfield is clearly visible even when empty.
103
+ * The grid maps cell (col,row) -> tilemap (row+1, col+7), i.e. rows 1..12
104
+ * cols 7..12. Frame the perimeter at rows 0..13, cols 6..13. */
105
+ static void draw_well(void) {
106
+ uint8_t r, c;
107
+ for (r = 0; r <= 13; r++) {
108
+ for (c = 6; c <= 13; c++) {
109
+ uint8_t t = T_FIELD;
110
+ if (r == 0 || r == 13 || c == 6 || c == 13) t = T_WALL;
111
+ sms_set_tilemap_cell(r, c, t, 0);
112
+ }
113
+ }
114
+ }
115
+
88
116
  static void draw_grid(void) {
89
117
  int8_t r, c;
90
118
  for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) draw_cell(c, r, grid[r][c]);
@@ -151,7 +179,7 @@ void main(void) {
151
179
 
152
180
  sms_vdp_init();
153
181
  sms_load_palette(palette);
154
- sms_load_tiles(0x0000, bg_tiles, 32 * 4);
182
+ sms_load_tiles(0x0000, bg_tiles, 32 * 6);
155
183
 
156
184
  for (r = 0; r < 24; r++) for (c = 0; c < 32; c++) sms_set_tilemap_cell(r, c, T_BLANK, 0);
157
185
  for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) grid[r][c] = 0;
@@ -159,6 +187,7 @@ void main(void) {
159
187
  score = 0;
160
188
  fall_timer = 0;
161
189
  new_piece();
190
+ draw_well();
162
191
  draw_grid();
163
192
 
164
193
  sfx_init();
@@ -18,6 +18,7 @@ extern void sms_vdp_init(void);
18
18
  extern void sms_vdp_display_on(void);
19
19
  extern void sms_load_palette(const uint8_t *palette);
20
20
  extern void sms_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
21
+ extern void sms_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
21
22
  extern void sms_vblank_wait(void);
22
23
  extern uint8_t sms_joypad_read(void);
23
24
  extern void sms_sprite_init(void);
@@ -31,13 +32,47 @@ extern void sms_sat_upload(void);
31
32
  #define MAX_OBSTACLES 4
32
33
 
33
34
  static const uint8_t palette[32] = {
34
- 0x10, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
35
+ /* BG: 0 = dark navy backdrop, 1 = grass green, 2 = road grey */
36
+ 0x10, 0x08, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00,
35
37
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
36
38
  /* Sprite palette: white (1), red (2) */
37
39
  0x00, 0x3F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
38
40
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
39
41
  };
40
42
 
43
+ /* Three BG tiles for the track, loaded into the BG tile bank at $0000:
44
+ * tile 0 = blank (backdrop), tile 1 = grass (colour 1), tile 2 = road
45
+ * (colour 2). The track fills the whole 32x24 SMS screen so the display
46
+ * is a clear road scene, not a flat backdrop. */
47
+ static const uint8_t bg_tiles[96] = {
48
+ /* BG tile 0 = blank */
49
+ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
50
+ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
51
+ /* BG tile 1 = grass (colour 1 -> plane 0 set) */
52
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
53
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
54
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
55
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
56
+ /* BG tile 2 = road (colour 2 -> plane 1 set) */
57
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
58
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
59
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
60
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
61
+ };
62
+
63
+ /* Paint the whole 32x24 SMS screen: grey road down the centre lanes,
64
+ * green grass on the shoulders. BG tile bank is $0000. The road spans the
65
+ * three lanes (player X 72..184 -> roughly cols 8..23). */
66
+ static void draw_track(void) {
67
+ uint8_t row, col;
68
+ for (row = 0; row < 24; row++) {
69
+ for (col = 0; col < 32; col++) {
70
+ uint8_t road = (col >= 8 && col <= 23);
71
+ sms_set_tilemap_cell(row, col, road ? 2 : 1, 0);
72
+ }
73
+ }
74
+ }
75
+
41
76
  /* Two sprite tiles — player (colour 1) + enemy (colour 2). */
42
77
  static const uint8_t tiles[64] = {
43
78
  /* Tile 0 = player car (colour 1 → plane 0 set) */
@@ -97,7 +132,9 @@ void main(void) {
97
132
  uint8_t i;
98
133
  sms_vdp_init();
99
134
  sms_load_palette(palette);
100
- sms_load_tiles(0x2000, tiles, 64);
135
+ sms_load_tiles(0x0000, bg_tiles, 96); /* BG tiles -> BG bank $0000 */
136
+ sms_load_tiles(0x2000, tiles, 64); /* sprite tiles -> sprite bank $2000 */
137
+ draw_track();
101
138
  sms_sprite_init();
102
139
  sfx_init();
103
140
  sms_vdp_display_on();