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
@@ -1,10 +1,17 @@
1
- /* ── racing.c — Game Boy top-down racing scaffold ──────────────────
1
+ /* ── racing.c — Game Boy Color top-down racing scaffold ────────────
2
2
  *
3
- * Endless 3-lane top-down dodge for the Game Boy. LEFT/RIGHT switches
4
- * lanes (edge-detected), obstacles slide down at speed = 2 + score/500
5
- * (capped). Collision triggers a 60-frame freeze + auto-reset.
3
+ * Endless 3-lane top-down dodge for the Game Boy Color. LEFT/RIGHT
4
+ * switches lanes (edge-detected), obstacles slide down at speed =
5
+ * 2 + score/500 (capped). Collision triggers a 60-frame freeze +
6
+ * auto-reset.
6
7
  *
7
- * Game Boy screen is 160×144 — 3 lanes centred around x = {48, 80, 112}.
8
+ * Game Boy screen is 160×144 — 3 lanes centred around x = {40, 76, 112}.
9
+ *
10
+ * The road is a real CGB-coloured background: green grass shoulders down
11
+ * each side, grey asphalt across the playfield, dashed white lane lines
12
+ * between the lanes (BG palette via BCPS/BCPD; LCDC bit 0 = BG ON — drop
13
+ * it and the screen is a flat colour, the #1 GB "why is it blank"
14
+ * footgun). Cars are colour sprites (OCPS/OCPD) on top.
8
15
  */
9
16
 
10
17
  #include "gb_hardware.h"
@@ -16,7 +23,7 @@
16
23
  #define PLAYER_Y 120
17
24
  #define MAX_OBSTACLES 4
18
25
 
19
- static const uint8_t tile_blank[16] = { 0 };
26
+ /* ── Sprite tiles (cars) ──────────────────────────────────────────── */
20
27
  static const uint8_t tile_car_p1[16] = {
21
28
  0x3C,0x00, 0x7E,0x00, 0x42,0x00, 0x7E,0x00,
22
29
  0x7E,0x00, 0x42,0x00, 0x7E,0x00, 0x66,0x00,
@@ -26,8 +33,43 @@ static const uint8_t tile_car_en[16] = {
26
33
  0x00,0x7E, 0x00,0x42, 0x00,0x7E, 0x00,0x66,
27
34
  };
28
35
 
29
- static const uint16_t obj_palette[4] = { 0x7FFF, 0x7FFF, 0x001F, 0x03E0 };
30
- static const uint16_t bg_palette[4] = { 0x1842, 0x2945, 0x4A53, 0x7FFF }; /* asphalt lane lines */
36
+ /* ── BG tiles (road) ──────────────────────────────────────────────── */
37
+ /* 2bpp: row N = byte 2N (low plane) + byte 2N+1 (high plane).
38
+ * asphalt — all colour 2 (grey)
39
+ * grass — all colour 1 (green)
40
+ * laneA/B — dashed colour-3 (white) lane line, two phases for dashes */
41
+ static const uint8_t tile_asphalt[16] = {
42
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
43
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
44
+ };
45
+ static const uint8_t tile_grass[16] = {
46
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
47
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
48
+ };
49
+ /* lane line = a 2px-wide colour-3 stripe down the centre of the cell;
50
+ * phase A draws the top half, phase B the bottom half → dashes. */
51
+ static const uint8_t tile_laneA[16] = {
52
+ 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18,
53
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
54
+ };
55
+ static const uint8_t tile_laneB[16] = {
56
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
57
+ 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18,
58
+ };
59
+
60
+ /* CGB palettes (BGR555).
61
+ * BG palette 0: 0 unused, 1 green grass, 2 grey asphalt, 3 white line. */
62
+ static const uint16_t bg_palette[4] = { 0x0000, 0x0320, 0x4210, 0x7FFF };
63
+ /* OBJ palette 0: 0 transparent, 1 white (player), 2 red (enemy), 3 green. */
64
+ static const uint16_t obj_palette[4] = { 0x0000, 0x7FFF, 0x001F, 0x03E0 };
65
+
66
+ /* Tile indices in VRAM. Sprites and BG share the $8000 table here. */
67
+ #define T_CAR_P1 1
68
+ #define T_CAR_EN 2
69
+ #define T_ASPHALT 3
70
+ #define T_GRASS 4
71
+ #define T_LANE_A 5
72
+ #define T_LANE_B 6
31
73
 
32
74
  typedef struct { int16_t x, y; uint8_t alive; } Car;
33
75
 
@@ -72,8 +114,27 @@ static void spawn_obstacle(void) {
72
114
 
73
115
  static void upload_tile(uint8_t slot, const uint8_t *src) {
74
116
  uint8_t *dst = (uint8_t *)(0x8000 + slot * 16);
75
- uint8_t i;
76
- for (i = 0; i < 16; i++) dst[i] = src[i];
117
+ /* memcpy_vram (pointer-walk) — NOT an indexed dst[i]=src[i] loop, which
118
+ * SDCC sm83 miscompiles when dst points into VRAM ($8000-$9FFF). */
119
+ memcpy_vram(dst, src, 16);
120
+ }
121
+
122
+ /* Paint the road into BG map 0 ($9800). 20×18 visible cells:
123
+ * col 0 = grass shoulder (left)
124
+ * col 19 = grass shoulder (right)
125
+ * cols 1..18 = asphalt, with dashed lane lines at the two lane
126
+ * boundaries (cols 6 and 12). Dashes alternate per row. */
127
+ static void draw_road(void) {
128
+ uint8_t *bg = BG_MAP_0;
129
+ uint8_t r, c, t;
130
+ for (r = 0; r < 18; r++) {
131
+ for (c = 0; c < 20; c++) {
132
+ if (c == 0 || c == 19) t = T_GRASS;
133
+ else if (c == 6 || c == 12) t = (r & 1) ? T_LANE_A : T_LANE_B;
134
+ else t = T_ASPHALT;
135
+ bg[r * 32 + c] = t;
136
+ }
137
+ }
77
138
  }
78
139
 
79
140
  void main(void) {
@@ -84,23 +145,29 @@ void main(void) {
84
145
  lcd_init_default();
85
146
  LCDC = 0;
86
147
 
87
- upload_tile(0, tile_blank);
88
- upload_tile(1, tile_car_p1);
89
- upload_tile(2, tile_car_en);
148
+ upload_tile(T_CAR_P1, tile_car_p1);
149
+ upload_tile(T_CAR_EN, tile_car_en);
150
+ upload_tile(T_ASPHALT, tile_asphalt);
151
+ upload_tile(T_GRASS, tile_grass);
152
+ upload_tile(T_LANE_A, tile_laneA);
153
+ upload_tile(T_LANE_B, tile_laneB);
90
154
 
91
- OCPS = 0x80;
92
- for (i = 0; i < 4; i++) {
93
- OCPD = (uint8_t)(obj_palette[i] & 0xFF);
94
- OCPD = (uint8_t)((obj_palette[i] >> 8) & 0xFF);
95
- }
155
+ /* CGB palettes. */
96
156
  BCPS = 0x80;
97
157
  for (i = 0; i < 4; i++) {
98
158
  BCPD = (uint8_t)(bg_palette[i] & 0xFF);
99
159
  BCPD = (uint8_t)((bg_palette[i] >> 8) & 0xFF);
100
160
  }
161
+ OCPS = 0x80;
162
+ for (i = 0; i < 4; i++) {
163
+ OCPD = (uint8_t)(obj_palette[i] & 0xFF);
164
+ OCPD = (uint8_t)((obj_palette[i] >> 8) & 0xFF);
165
+ }
166
+
167
+ draw_road();
101
168
 
102
169
  oam_clear();
103
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
170
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
104
171
  sound_init();
105
172
 
106
173
  reset_run();
@@ -111,13 +178,13 @@ void main(void) {
111
178
 
112
179
  /* Stage OAM — player + obstacles. */
113
180
  for (i = 0; i < 40; i++) oam_set(i, 0, 0, 0, 0);
114
- oam_set(0, (uint8_t)(player.y + 16), (uint8_t)(player.x + 8), 1, 0);
181
+ oam_set(0, (uint8_t)(player.y + 16), (uint8_t)(player.x + 8), T_CAR_P1, 0);
115
182
  for (i = 0; i < MAX_OBSTACLES; i++) {
116
183
  if (obstacles[i].alive) {
117
184
  oam_set((uint8_t)(1 + i),
118
185
  (uint8_t)(obstacles[i].y + 16),
119
186
  (uint8_t)(obstacles[i].x + 8),
120
- 2, 0);
187
+ T_CAR_EN, 0);
121
188
  }
122
189
  }
123
190
  oam_dma_flush();
@@ -25,6 +25,24 @@ static const uint8_t tile_ship[16] = {
25
25
  0x18,0x18, 0x3C,0x3C, 0x7E,0x7E, 0xFF,0xFF,
26
26
  0xFF,0xFF, 0x7E,0x7E, 0x3C,0x3C, 0x18,0x18,
27
27
  };
28
+ /* ── BG tiles (starfield) ─────────────────────────────────────────────
29
+ * The background is a real starfield so the screen is never one flat
30
+ * colour (LCDC_BG_ON below — drop it and the screen reads as blank, the
31
+ * #1 GB "why is it blank" footgun).
32
+ * tile_space — a 50/50 dither of palette colours 0 (deep space blue) +
33
+ * 1 (mid blue), so even an empty patch of space mixes two
34
+ * shades and never lets one colour dominate the frame.
35
+ * tile_star — a bright colour-3 (white) "+" star on the dithered field. */
36
+ static const uint8_t tile_space[16] = {
37
+ 0x55,0x00, 0xAA,0x00, 0x55,0x00, 0xAA,0x00,
38
+ 0x55,0x00, 0xAA,0x00, 0x55,0x00, 0xAA,0x00,
39
+ };
40
+ static const uint8_t tile_star[16] = {
41
+ 0x10,0x10, 0x10,0x10, 0x54,0x54, 0x38,0x38,
42
+ 0x54,0x54, 0x10,0x10, 0x10,0x10, 0x00,0x00,
43
+ };
44
+ #define T_SPACE 4
45
+ #define T_STAR 5
28
46
  static const uint8_t tile_bullet[16] = {
29
47
  0x00,0x00, 0x18,0x18, 0x3C,0x3C, 0x3C,0x3C,
30
48
  0x3C,0x3C, 0x3C,0x3C, 0x18,0x18, 0x00,0x00,
@@ -90,8 +108,20 @@ static void spawn(void) {
90
108
 
91
109
  static void upload_tile(uint8_t slot, const uint8_t *src) {
92
110
  uint8_t *dst = (uint8_t *)(0x8000 + slot * 16);
93
- uint8_t i;
94
- for (i = 0; i < 16; i++) dst[i] = src[i];
111
+ /* memcpy_vram (pointer-walk) — NOT an indexed dst[i]=src[i] loop, which
112
+ * SDCC sm83 miscompiles when dst points into VRAM ($8000-$9FFF). */
113
+ memcpy_vram(dst, src, 16);
114
+ }
115
+
116
+ /* Paint a starfield into BG map 0 ($9800): fill the visible 20×18 with the
117
+ * dithered space tile, then scatter bright stars on a fixed pseudo-pattern
118
+ * so the field reads as deep space rather than a flat colour. */
119
+ static void draw_starfield(void) {
120
+ uint8_t *bg = BG_MAP_0;
121
+ uint8_t r, c;
122
+ for (r = 0; r < 18; r++)
123
+ for (c = 0; c < 20; c++)
124
+ bg[r * 32 + c] = ((r * 7 + c * 5) % 11 == 0) ? T_STAR : T_SPACE;
95
125
  }
96
126
 
97
127
  void main(void) {
@@ -105,6 +135,8 @@ void main(void) {
105
135
  upload_tile(1, tile_ship);
106
136
  upload_tile(2, tile_bullet);
107
137
  upload_tile(3, tile_enemy);
138
+ upload_tile(T_SPACE, tile_space);
139
+ upload_tile(T_STAR, tile_star);
108
140
 
109
141
  /* Sprite palette 0 — uploaded to OCPS/OCPD (CGB-only registers). */
110
142
  OCPS = 0x80;
@@ -120,6 +152,8 @@ void main(void) {
120
152
  BCPD = (uint8_t)((bg_palette[i] >> 8) & 0xFF);
121
153
  }
122
154
 
155
+ draw_starfield();
156
+
123
157
  player.x = 76; player.y = 130; player.alive = 1;
124
158
  for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
125
159
  for (i = 0; i < MAX_ENEMIES; i++) enemies[i].alive = 0;
@@ -127,7 +161,7 @@ void main(void) {
127
161
  spawn_timer = 0;
128
162
 
129
163
  oam_clear();
130
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
164
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
131
165
  sound_init();
132
166
 
133
167
  while (1) {
@@ -28,6 +28,31 @@ static const uint8_t tile_solid[16] = {
28
28
  0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF,
29
29
  };
30
30
 
31
+ /* ── BG tiles (the court) ──────────────────────────────────────────────
32
+ * A real playfield behind the paddles so the screen is never one flat
33
+ * colour (LCDC_BG_ON below — drop it and it reads as blank, the #1 GB
34
+ * "why is it blank" footgun).
35
+ * tile_court — a 50/50 dither of palette colours 0 + 1 (the green turf),
36
+ * so even an empty patch mixes two shades and never
37
+ * dominates the frame.
38
+ * tile_net — a dashed vertical centre-net stripe (colour 2).
39
+ * tile_wall — a solid colour-2 border for the top / bottom rails. */
40
+ static const uint8_t tile_court[16] = {
41
+ 0x55,0x55, 0xAA,0xAA, 0x55,0x55, 0xAA,0xAA,
42
+ 0x55,0x55, 0xAA,0xAA, 0x55,0x55, 0xAA,0xAA,
43
+ };
44
+ static const uint8_t tile_net[16] = {
45
+ 0x18,0x18, 0x18,0x18, 0x00,0x00, 0x00,0x00,
46
+ 0x18,0x18, 0x18,0x18, 0x00,0x00, 0x00,0x00,
47
+ };
48
+ static const uint8_t tile_wall[16] = {
49
+ 0x00,0x00, 0xFF,0xFF, 0xFF,0xFF, 0x00,0x00,
50
+ 0x00,0x00, 0xFF,0xFF, 0xFF,0xFF, 0x00,0x00,
51
+ };
52
+ #define T_COURT 2
53
+ #define T_NET 3
54
+ #define T_WALL 4
55
+
31
56
  static const uint16_t obj_palette[4] = { 0x7FFF, 0x001F, 0x03E0, 0x7C00 };
32
57
  static const uint16_t bg_palette[4] = { 0x2104, 0x294A, 0x4631, 0x7FFF }; /* deep court green */
33
58
 
@@ -54,8 +79,24 @@ static void reset_match(void) {
54
79
 
55
80
  static void upload_tile(uint8_t slot, const uint8_t *src) {
56
81
  uint8_t *dst = (uint8_t *)(0x8000 + slot * 16);
57
- uint8_t i;
58
- for (i = 0; i < 16; i++) dst[i] = src[i];
82
+ /* memcpy_vram (pointer-walk) — NOT an indexed dst[i]=src[i] loop, which
83
+ * SDCC sm83 miscompiles when dst points into VRAM ($8000-$9FFF). */
84
+ memcpy_vram(dst, src, 16);
85
+ }
86
+
87
+ /* Paint the Pong court into BG map 0 ($9800): dithered turf everywhere,
88
+ * solid top/bottom rails, and a dashed net down the centre column. */
89
+ static void draw_court(void) {
90
+ uint8_t *bg = BG_MAP_0;
91
+ uint8_t r, c, t;
92
+ for (r = 0; r < 18; r++) {
93
+ for (c = 0; c < 20; c++) {
94
+ if (r == 0 || r == 17) t = T_WALL; /* top / bottom rail */
95
+ else if (c == 9 || c == 10) t = (r & 1) ? T_NET : T_COURT; /* net dashes */
96
+ else t = T_COURT;
97
+ bg[r * 32 + c] = t;
98
+ }
99
+ }
59
100
  }
60
101
 
61
102
  void main(void) {
@@ -68,6 +109,9 @@ void main(void) {
68
109
 
69
110
  upload_tile(0, tile_blank);
70
111
  upload_tile(1, tile_solid);
112
+ upload_tile(T_COURT, tile_court);
113
+ upload_tile(T_NET, tile_net);
114
+ upload_tile(T_WALL, tile_wall);
71
115
 
72
116
  OCPS = 0x80;
73
117
  for (i = 0; i < 4; i++) {
@@ -80,8 +124,9 @@ void main(void) {
80
124
  BCPD = (uint8_t)((bg_palette[i] >> 8) & 0xFF);
81
125
  }
82
126
 
127
+ draw_court();
83
128
  oam_clear();
84
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
129
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
85
130
  sound_init();
86
131
 
87
132
  reset_match();
@@ -143,8 +143,9 @@ static const uint8_t rooms[ROOMS][ROWS * COLS] = {
143
143
 
144
144
  /* ── Helpers ────────────────────────────────────────────────────── */
145
145
  static void copy_to_vram(uint8_t *dst, const uint8_t *src, uint16_t n) {
146
- uint16_t i;
147
- for (i = 0; i < n; i++) dst[i] = src[i];
146
+ /* Delegate to the runtime's pointer-walk copy — an indexed dst[i]=src[i]
147
+ * loop into VRAM is miscompiled by SDCC sm83. */
148
+ memcpy_vram(dst, src, n);
148
149
  }
149
150
 
150
151
  static void load_bg_palette(void) {
@@ -89,7 +89,7 @@ Start:
89
89
  move.w #$0000,VDP_DATA ; color 0: transparent / backdrop
90
90
  move.w #$0EEE,VDP_DATA ; color 1: white-ish ($0E in each nybble)
91
91
  move.w #$00EE,VDP_DATA ; color 2: yellow
92
- move.w #$0E00,VDP_DATA ; color 3: blue (unused)
92
+ move.w #$0840,VDP_DATA ; color 3: dark teal (backdrop fill)
93
93
 
94
94
  ; -----------------------------------------------------------
95
95
  ; Upload one 4bpp tile (8x8 'H', color 2 = yellow) to VRAM at
@@ -104,6 +104,34 @@ Start:
104
104
  move.l (a1)+,VDP_DATA ; each row = one longword (4 bytes)
105
105
  dbra d0,.tile_loop
106
106
 
107
+ ; -----------------------------------------------------------
108
+ ; Upload two PATTERNED backdrop tiles (#2 and #3) to VRAM at
109
+ ; byte offsets $40 and $60 (VRAM cmd $40400000). Each is a teal
110
+ ; (color 3) field sprinkled with white (color 1) dots, with the
111
+ ; roles swapped between the two so that when we checkerboard them
112
+ ; across the plane no single colour dominates the screen — a
113
+ ; uniform fill still reads as "blank" to a human, so we vary it.
114
+ ; 16 longwords total (8 rows × 2 tiles) written back-to-back.
115
+ move.l #$40400000,VDP_CTRL
116
+ lea TileBgA(pc),a1
117
+ moveq #16-1,d0 ; 8 rows × 2 tiles
118
+ .bg_tile_loop:
119
+ move.l (a1)+,VDP_DATA
120
+ dbra d0,.bg_tile_loop
121
+
122
+ ; -----------------------------------------------------------
123
+ ; Fill the ENTIRE plane A name table, checkerboarding tiles #2
124
+ ; and #3 so there's a varied visible background behind the 'H'.
125
+ ; Plane A base is $C000; plane size is 64x32 = 2048 cells. VRAM
126
+ ; write at $C000 = cmd $40000003. d1 toggles 2↔3 each cell.
127
+ move.l #$40000003,VDP_CTRL
128
+ move.w #2048-1,d0 ; 2048 cells to write
129
+ moveq #2,d1 ; start with tile #2
130
+ .bg_fill_loop:
131
+ move.w d1,VDP_DATA ; tile d1 (pal 0, no flip, low pri)
132
+ eor.w #$0001,d1 ; toggle 2↔3 (tile index xor 1)
133
+ dbra d0,.bg_fill_loop
134
+
107
135
  ; -----------------------------------------------------------
108
136
  ; Place tile #1 in plane A near screen center.
109
137
  ; Plane A base is at VRAM $C000 (default after VDP init).
@@ -159,3 +187,27 @@ TileH:
159
187
  dc.l $20000020
160
188
  dc.l $20000020
161
189
  dc.l $00000000 ; row 7: blank
190
+
191
+ ; -----------------------------------------------------------------------
192
+ ; Two patterned backdrop tiles, 4bpp, uploaded back-to-back as #2 and #3.
193
+ ; TileBgA = teal (color 3) field with white (color 1) dots; TileBgB swaps
194
+ ; the roles (white field, teal dots). Checkerboarding them across the
195
+ ; plane keeps any single colour well under the "blank" threshold.
196
+ TileBgA: ; tile #2 — teal field, white dots
197
+ dc.l $33333333
198
+ dc.l $33133313
199
+ dc.l $33333333
200
+ dc.l $13333331
201
+ dc.l $33333333
202
+ dc.l $33133313
203
+ dc.l $33333333
204
+ dc.l $13333331
205
+ TileBgB: ; tile #3 — white field, teal dots
206
+ dc.l $11111111
207
+ dc.l $11311131
208
+ dc.l $11111111
209
+ dc.l $31111113
210
+ dc.l $11111111
211
+ dc.l $11311131
212
+ dc.l $11111111
213
+ dc.l $31111113
@@ -29,6 +29,17 @@ static const u32 tile_data[8] = {
29
29
  0x11111111, 0x11111111, 0x11111111, 0x11111111,
30
30
  };
31
31
 
32
+ /* A checkered backdrop block tiled across plane B so the screen isn't a
33
+ * flat black void (a lone sprite on black reads as "blank" to a human).
34
+ * Colour index 4 with a thin colour-5 frame — we set both below. */
35
+ static const u32 tile_bg[8] = {
36
+ 0x44444444, 0x45555554, 0x45000054, 0x45000054,
37
+ 0x45000054, 0x45000054, 0x45555554, 0x44444444,
38
+ };
39
+
40
+ #define T_SPRITE (TILE_USER_INDEX + 0)
41
+ #define T_BG (TILE_USER_INDEX + 1)
42
+
32
43
  int main(bool hard) {
33
44
  (void)hard;
34
45
 
@@ -38,11 +49,22 @@ int main(bool hard) {
38
49
 
39
50
  /* Make sure sprite palette 0 entry 1 is white so we can see our
40
51
  * tile. SGDK uses 0RRR0GGG0BBB packed words (BGR, 3 bits each). */
41
- PAL_setColor(1, 0x0EEE); /* near-white */
52
+ PAL_setColor(1, 0x0EEE); /* near-white sprite */
53
+ /* Plane-B backdrop colours (palette 1). */
54
+ PAL_setColor(16 + 4, 0x0640); /* dark teal field */
55
+ PAL_setColor(16 + 5, 0x0860); /* lighter frame */
42
56
 
43
57
  /* Upload the user tile to VRAM at TILE_USER_INDEX (everything
44
58
  * below that is reserved for SGDK's font + system tiles). */
45
- VDP_loadTileData(tile_data, TILE_USER_INDEX, 1, DMA);
59
+ VDP_loadTileData(tile_data, T_SPRITE, 1, DMA);
60
+ VDP_loadTileData(tile_bg, T_BG, 1, DMA);
61
+
62
+ /* Tile plane B with the backdrop block so there's a visible
63
+ * background behind the sprite + text. Sprites + the font plane (A)
64
+ * always draw above plane B, so the d-pad sprite reads on top. */
65
+ for (u16 cy = 0; cy < 28; cy++)
66
+ for (u16 cx = 0; cx < 40; cx++)
67
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, T_BG), cx, cy);
46
68
 
47
69
  VDP_drawText("D-PAD MOVES THE SPRITE", 8, 2);
48
70
  VDP_drawText("START FOR SOFT RESET", 9, 4);
@@ -66,7 +88,7 @@ int main(bool hard) {
66
88
  * SPRITE_SIZE(1,1) = 8×8. TILE_ATTR_FULL(palette,prio,vflip,
67
89
  * hflip,tile_index). */
68
90
  VDP_setSprite(0, px, py, SPRITE_SIZE(1, 1),
69
- TILE_ATTR_FULL(PAL0, 0, 0, 0, TILE_USER_INDEX));
91
+ TILE_ATTR_FULL(PAL0, 1, 0, 0, T_SPRITE));
70
92
  VDP_updateSprites(1, DMA);
71
93
 
72
94
  SYS_doVBlankProcess();
@@ -29,8 +29,23 @@
29
29
  #define T_RED (TILE_USER_INDEX + 1)
30
30
  #define T_GREEN (TILE_USER_INDEX + 2)
31
31
  #define T_BLUE (TILE_USER_INDEX + 3)
32
+ #define T_BG (TILE_USER_INDEX + 4) /* full-screen backdrop (BG_A) */
33
+ #define T_WELL (TILE_USER_INDEX + 5) /* play-well backdrop (BG_A) */
32
34
 
33
35
  static const u32 tile_blank[8] = { 0,0,0,0,0,0,0,0 };
36
+ /* Backdrop block for the far plane: a framed cell (colour 4 border /
37
+ * colour 5 fill) tiled across the whole screen so the playfield no
38
+ * longer floats on a flat black backdrop. */
39
+ static const u32 tile_bg[8] = {
40
+ 0x44444444, 0x45555554, 0x45555554, 0x45555554,
41
+ 0x45555554, 0x45555554, 0x45555554, 0x44444444,
42
+ };
43
+ /* A darker, recessed cell drawn behind the play column so the well reads
44
+ * as an inset board rather than part of the surrounding wall. */
45
+ static const u32 tile_well[8] = {
46
+ 0x44444444, 0x40000004, 0x40000004, 0x40000004,
47
+ 0x40000004, 0x40000004, 0x40000004, 0x44444444,
48
+ };
34
49
  static const u32 tile_red[8] = {
35
50
  0x11111111, 0x11111111, 0x11111111, 0x11111111,
36
51
  0x11111111, 0x11111111, 0x11111111, 0x11111111,
@@ -91,8 +106,11 @@ static void draw_cell(s16 col, s16 row) {
91
106
  /* Each grid cell is CELL_PX/8 = 2 tiles square. */
92
107
  for (u16 dy = 0; dy < 2; dy++) {
93
108
  for (u16 dx = 0; dx < 2; dx++) {
109
+ /* Cells use the EMPTY-or-coloured tile. Empty cells stay
110
+ * transparent so the BG_A well backdrop shows through; filled
111
+ * cells are HIGH priority so they sit above that backdrop. */
94
112
  VDP_setTileMapXY(BG_B,
95
- TILE_ATTR_FULL(pal_for(v), 0, 0, 0, tile_for(v)),
113
+ TILE_ATTR_FULL(pal_for(v), v ? 1 : 0, 0, 0, tile_for(v)),
96
114
  col * 2 + dx + 6,
97
115
  row * 2 + dy + 1);
98
116
  }
@@ -116,7 +134,7 @@ static void draw_piece(s16 col, s16 row, bool clear) {
116
134
  for (u16 dy = 0; dy < 2; dy++)
117
135
  for (u16 dx = 0; dx < 2; dx++)
118
136
  VDP_setTileMapXY(BG_B,
119
- TILE_ATTR_FULL(pal_for(v), 0, 0, 0, tile_for(v)),
137
+ TILE_ATTR_FULL(pal_for(v), v ? 1 : 0, 0, 0, tile_for(v)),
120
138
  col * 2 + dx + 6,
121
139
  r * 2 + dy + 1);
122
140
  }
@@ -166,15 +184,31 @@ static void render_score(void) {
166
184
  int main(bool hard) {
167
185
  (void)hard;
168
186
 
169
- /* Palette 1: tile colours for red/green/blue cells. */
187
+ /* Palette 1: tile colours for red/green/blue cells + the backdrop. */
170
188
  PAL_setColor(16 + 1, 0x000E); /* red */
171
189
  PAL_setColor(16 + 2, 0x00E0); /* green */
172
190
  PAL_setColor(16 + 3, 0x0E00); /* blue */
191
+ PAL_setColor(16 + 4, 0x0420); /* backdrop wall border */
192
+ PAL_setColor(16 + 5, 0x0610); /* backdrop wall fill */
173
193
 
174
194
  VDP_loadTileData(tile_blank, T_BLANK, 1, DMA);
175
195
  VDP_loadTileData(tile_red, T_RED, 1, DMA);
176
196
  VDP_loadTileData(tile_green, T_GREEN, 1, DMA);
177
197
  VDP_loadTileData(tile_blue, T_BLUE, 1, DMA);
198
+ VDP_loadTileData(tile_bg, T_BG, 1, DMA);
199
+ VDP_loadTileData(tile_well, T_WELL, 1, DMA);
200
+
201
+ /* Far plane (BG_A): tile the whole 40x28 screen with the wall block,
202
+ * then recess the 12x24-cell play column so the grid sits in an inset
203
+ * well. The grid (BG_B) draws over this with HIGH priority. */
204
+ for (u16 cy = 0; cy < 28; cy++)
205
+ for (u16 cx = 0; cx < 40; cx++)
206
+ VDP_setTileMapXY(BG_A,
207
+ TILE_ATTR_FULL(PAL1, 0, 0, 0, T_BG), cx, cy);
208
+ for (u16 cy = 1; cy <= 24; cy++)
209
+ for (u16 cx = 6; cx <= 17; cx++)
210
+ VDP_setTileMapXY(BG_A,
211
+ TILE_ATTR_FULL(PAL1, 0, 0, 0, T_WELL), cx, cy);
178
212
 
179
213
  for (s16 r = 0; r < ROWS; r++)
180
214
  for (s16 c = 0; c < COLS; c++)
@@ -29,6 +29,8 @@
29
29
  #define T_CAR_EN (TILE_USER_INDEX + 1)
30
30
  #define T_LANE (TILE_USER_INDEX + 2) /* dashed lane divider (BG_B) */
31
31
  #define T_EDGE (TILE_USER_INDEX + 3) /* solid road edge (BG_B) */
32
+ #define T_GRASS (TILE_USER_INDEX + 4) /* roadside backdrop (BG_A) */
33
+ #define T_ASPHALT (TILE_USER_INDEX + 5) /* road surface backdrop(BG_A) */
32
34
 
33
35
  static const u32 tile_car_p1[8] = {
34
36
  0x01111110, 0x11111111, 0x12222221, 0x11111111,
@@ -51,6 +53,18 @@ static const u32 tile_edge[8] = {
51
53
  0x00000022, 0x00000022, 0x00000022, 0x00000022,
52
54
  0x00000022, 0x00000022, 0x00000022, 0x00000022,
53
55
  };
56
+ /* Roadside grass (colour 5) with a couple of darker tufts (colour 6) so
57
+ * it isn't a flat fill — tiled down both shoulders on BG_A. */
58
+ static const u32 tile_grass[8] = {
59
+ 0x55555555, 0x55556555, 0x55555555, 0x65555555,
60
+ 0x55555555, 0x55555565, 0x55555555, 0x55655555,
61
+ };
62
+ /* Road asphalt (colour 6) with faint speckle (colour 5) — tiled across
63
+ * the driving surface on BG_A, behind the BG_B lane markings + cars. */
64
+ static const u32 tile_asphalt[8] = {
65
+ 0x66666666, 0x66666566, 0x66666666, 0x56666666,
66
+ 0x66666666, 0x66666656, 0x66666666, 0x66566666,
67
+ };
54
68
 
55
69
  /* The road lives on BG_B (8×8 cells). Two dashed dividers sit between the
56
70
  * three lanes; solid edges frame the outermost lanes. */
@@ -61,15 +75,28 @@ static const u32 tile_edge[8] = {
61
75
  #define ROAD_EDGE_L ((LANE_LEFT_X - 12) / 8)
62
76
  #define ROAD_EDGE_R ((LANE_RIGHT_X + 12) / 8)
63
77
 
78
+ /* Far plane (BG_A): grass shoulders + asphalt driving surface, tiled
79
+ * across the whole 40x28 screen so the road no longer floats on black.
80
+ * Drawn at low priority; the BG_B markings + sprite cars sit on top. */
81
+ static void draw_backdrop(void) {
82
+ s16 r, c;
83
+ for (r = 0; r < 28; r++)
84
+ for (c = 0; c < 40; c++) {
85
+ u16 t = (c >= ROAD_EDGE_L && c <= ROAD_EDGE_R) ? T_ASPHALT : T_GRASS;
86
+ VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(PAL0, 0, 0, 0, t), c, r);
87
+ }
88
+ }
89
+
64
90
  static void draw_road(void) {
65
91
  s16 r;
66
92
  for (r = ROAD_TOP_ROW; r <= ROAD_BOT_ROW; r++) {
67
- /* Left edge (stripe on its right), right edge (hflipped). */
68
- VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_EDGE), ROAD_EDGE_L, r);
69
- VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 1, T_EDGE), ROAD_EDGE_R, r);
93
+ /* Left edge (stripe on its right), right edge (hflipped). HIGH
94
+ * priority so the markings sit above the BG_A asphalt backdrop. */
95
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 1, 0, 0, T_EDGE), ROAD_EDGE_L, r);
96
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 1, 0, 1, T_EDGE), ROAD_EDGE_R, r);
70
97
  /* Two dashed lane dividers. */
71
- VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_LANE), LANE_DIV1_COL, r);
72
- VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_LANE), LANE_DIV2_COL, r);
98
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 1, 0, 0, T_LANE), LANE_DIV1_COL, r);
99
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 1, 0, 0, T_LANE), LANE_DIV2_COL, r);
73
100
  }
74
101
  }
75
102
 
@@ -125,18 +152,24 @@ static void render_score(void) {
125
152
  int main(bool hard) {
126
153
  (void)hard;
127
154
 
128
- /* PAL0 = player (white + blue trim). PAL1 = enemy (red + dark). */
155
+ /* PAL0 = player (white + blue trim) + road backdrop. PAL1 = enemy. */
129
156
  PAL_setColor(0 + 1, 0x0EEE); /* white body */
130
157
  PAL_setColor(0 + 2, 0x0AAA); /* roof grey */
158
+ PAL_setColor(0 + 5, 0x0260); /* roadside grass */
159
+ PAL_setColor(0 + 6, 0x0222); /* asphalt grey */
131
160
  PAL_setColor(16 + 3, 0x000E); /* enemy red */
132
161
  PAL_setColor(16 + 4, 0x0666); /* enemy roof */
133
162
 
134
- VDP_loadTileData(tile_car_p1, T_CAR_P1, 1, DMA);
135
- VDP_loadTileData(tile_car_enemy, T_CAR_EN, 1, DMA);
136
- VDP_loadTileData(tile_lane, T_LANE, 1, DMA);
137
- VDP_loadTileData(tile_edge, T_EDGE, 1, DMA);
163
+ VDP_loadTileData(tile_car_p1, T_CAR_P1, 1, DMA);
164
+ VDP_loadTileData(tile_car_enemy, T_CAR_EN, 1, DMA);
165
+ VDP_loadTileData(tile_lane, T_LANE, 1, DMA);
166
+ VDP_loadTileData(tile_edge, T_EDGE, 1, DMA);
167
+ VDP_loadTileData(tile_grass, T_GRASS, 1, DMA);
168
+ VDP_loadTileData(tile_asphalt, T_ASPHALT, 1, DMA);
138
169
 
139
- /* Draw the static road (edges + dashed lane dividers) once on BG_B. */
170
+ /* Draw the grass+asphalt backdrop (BG_A) then the road markings on
171
+ * BG_B over it. */
172
+ draw_backdrop();
140
173
  draw_road();
141
174
 
142
175
  VDP_drawText("SCORE", 28, 2);
@@ -21,6 +21,23 @@
21
21
 
22
22
  #include <genesis.h>
23
23
 
24
+ /* Two simple 8x8 background tiles for the far plane (BG_B). Tiling them
25
+ * in a checkerboard fills the whole screen so it doesn't read as a blank
26
+ * black backdrop. T_BG_A is a framed block (colour 1 border, colour 2
27
+ * fill); T_BG_B is the same block in colour 3 — alternating them gives a
28
+ * two-tone grid with a clear majority of non-backdrop pixels. */
29
+ #define T_BG_A (TILE_USER_INDEX + 0)
30
+ #define T_BG_B (TILE_USER_INDEX + 1)
31
+
32
+ static const u32 tile_bg_a[8] = {
33
+ 0x11111111, 0x12222221, 0x12222221, 0x12222221,
34
+ 0x12222221, 0x12222221, 0x12222221, 0x11111111,
35
+ };
36
+ static const u32 tile_bg_b[8] = {
37
+ 0x11111111, 0x13333331, 0x13333331, 0x13333331,
38
+ 0x13333331, 0x13333331, 0x13333331, 0x11111111,
39
+ };
40
+
24
41
  int main(bool hard) {
25
42
  /* Boot info: hard == TRUE on power-on, FALSE on soft reset (we
26
43
  * could re-init differently in each case; this minimum starter
@@ -28,7 +45,23 @@ int main(bool hard) {
28
45
  (void)hard;
29
46
 
30
47
  /* SGDK initialized the VDP + default palette in sega.s + libmd
31
- * before main() ran. Just draw text. */
48
+ * before main() ran. We add a tiled background so the screen isn't a
49
+ * flat black backdrop, then draw the text on top. */
50
+ PAL_setColor(16 + 1, 0x0444); /* dark grey grid border */
51
+ PAL_setColor(16 + 2, 0x0A22); /* deep blue fill */
52
+ PAL_setColor(16 + 3, 0x022A); /* deep red fill (2nd block) */
53
+
54
+ VDP_loadTileData(tile_bg_a, T_BG_A, 1, DMA);
55
+ VDP_loadTileData(tile_bg_b, T_BG_B, 1, DMA);
56
+
57
+ /* Fill the far plane (BG_B) with a checkerboard of the two blocks so
58
+ * a clear majority of the visible 40x28 cells are non-backdrop. */
59
+ for (u16 cy = 0; cy < 28; cy++)
60
+ for (u16 cx = 0; cx < 40; cx++)
61
+ VDP_setTileMapXY(BG_B,
62
+ TILE_ATTR_FULL(PAL1, 0, 0, 0, ((cx ^ cy) & 1) ? T_BG_A : T_BG_B),
63
+ cx, cy);
64
+
32
65
  VDP_drawText("HELLO SEGA GENESIS", 10, 12);
33
66
  VDP_drawText("BUILT WITH ROM-DEV-MCP", 8, 14);
34
67