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
@@ -30,8 +30,22 @@
30
30
  #define T_SHIP (TILE_USER_INDEX + 1)
31
31
  #define T_BULLET (TILE_USER_INDEX + 2)
32
32
  #define T_ENEMY (TILE_USER_INDEX + 3)
33
+ #define T_SPACE (TILE_USER_INDEX + 4) /* nebula backdrop A (BG_B) */
34
+ #define T_STARS (TILE_USER_INDEX + 5) /* nebula backdrop B (BG_B) */
33
35
 
34
36
  static const u32 tile_blank[8] = { 0,0,0,0,0,0,0,0 };
37
+ /* Deep-space backdrop tiled across BG_B so the playfield isn't a flat
38
+ * black void. Two distinct nebula blocks are checkerboarded so no single
39
+ * colour dominates the screen: T_SPACE is colour-4 nebula with colour-5
40
+ * star dots; T_STARS swaps the roles (colour-5 field, colour-4 dots). */
41
+ static const u32 tile_space[8] = {
42
+ 0x44444444, 0x44455444, 0x44444444, 0x54444445,
43
+ 0x44444444, 0x44455444, 0x44444444, 0x54444445,
44
+ };
45
+ static const u32 tile_stars[8] = {
46
+ 0x55555555, 0x55544555, 0x55555555, 0x45555554,
47
+ 0x55555555, 0x55544555, 0x55555555, 0x45555554,
48
+ };
35
49
  static const u32 tile_ship[8] = {
36
50
  0x00011000, 0x00011000, 0x00111100, 0x00111100,
37
51
  0x01111110, 0x01111110, 0x11111111, 0x11000011,
@@ -102,8 +116,10 @@ int main(bool hard) {
102
116
 
103
117
  /* PAL0 — used by player + font */
104
118
  PAL_setColor(0 + 1, 0x0EEE); /* ship white */
105
- /* PAL1 — bullet */
119
+ /* PAL1 — bullet + space backdrop */
106
120
  PAL_setColor(16 + 2, 0x00EE); /* bullet yellow */
121
+ PAL_setColor(16 + 4, 0x0600); /* nebula deep blue */
122
+ PAL_setColor(16 + 5, 0x0402); /* nebula violet */
107
123
  /* PAL2 — enemy */
108
124
  PAL_setColor(32 + 3, 0x000E); /* enemy red */
109
125
 
@@ -113,6 +129,20 @@ int main(bool hard) {
113
129
  VDP_loadTileData(tile_ship, T_SHIP, 1, DMA);
114
130
  VDP_loadTileData(tile_bullet, T_BULLET, 1, DMA);
115
131
  VDP_loadTileData(tile_enemy, T_ENEMY, 1, DMA);
132
+ VDP_loadTileData(tile_space, T_SPACE, 1, DMA);
133
+ VDP_loadTileData(tile_stars, T_STARS, 1, DMA);
134
+
135
+ /* Fill the far plane (BG_B) with the space backdrop so the screen
136
+ * isn't an empty black void; sprinkle denser star clusters for
137
+ * variety. Sprites (ship/bullets/enemies) always draw above the
138
+ * planes, so the gameplay reads on top of this with no priority
139
+ * juggling. */
140
+ for (u16 cy = 0; cy < 28; cy++)
141
+ for (u16 cx = 0; cx < 40; cx++)
142
+ VDP_setTileMapXY(BG_B,
143
+ TILE_ATTR_FULL(PAL1, 0, 0, 0,
144
+ ((cx ^ cy) & 1) ? T_STARS : T_SPACE),
145
+ cx, cy);
116
146
 
117
147
  player.x = 152; player.y = 180; player.alive = TRUE;
118
148
  for (u16 i = 0; i < MAX_BULLETS; i++) bullets[i].alive = FALSE;
@@ -30,8 +30,22 @@
30
30
  #define T_SHIP_P2 (TILE_USER_INDEX + 2)
31
31
  #define T_BULLET (TILE_USER_INDEX + 3)
32
32
  #define T_ENEMY (TILE_USER_INDEX + 4)
33
+ #define T_SPACE (TILE_USER_INDEX + 5) /* nebula backdrop A (BG_B) */
34
+ #define T_STARS (TILE_USER_INDEX + 6) /* nebula backdrop B (BG_B) */
33
35
 
34
36
  static const u32 tile_blank[8] = { 0,0,0,0,0,0,0,0 };
37
+ /* Deep-space backdrop tiled across BG_B so the shared playfield isn't a
38
+ * flat black void. Two distinct nebula blocks are checkerboarded so no
39
+ * single colour dominates: T_SPACE is colour-5 nebula with colour-6 star
40
+ * dots; T_STARS swaps the roles. Palette 1, colours set in main(). */
41
+ static const u32 tile_space[8] = {
42
+ 0x55555555, 0x55566555, 0x55555555, 0x65555556,
43
+ 0x55555555, 0x55566555, 0x55555555, 0x65555556,
44
+ };
45
+ static const u32 tile_stars[8] = {
46
+ 0x66666666, 0x66655666, 0x66666666, 0x56666665,
47
+ 0x66666666, 0x66655666, 0x66666666, 0x56666665,
48
+ };
35
49
  /* P1 ship — palette 0 colour 1 (white). */
36
50
  static const u32 tile_ship_p1[8] = {
37
51
  0x00011000, 0x00011000, 0x00111100, 0x00111100,
@@ -116,6 +130,10 @@ int main(bool hard) {
116
130
  PAL_setColor(0 + 1, 0x0EEE);
117
131
  PAL_setColor(0 + 2, 0x00EE);
118
132
  PAL_setColor(0 + 4, 0x000E);
133
+ /* Palette 1 = space backdrop (colours 5/6, kept clear of the PAL0
134
+ * sprite colours so the shared planes never clash). */
135
+ PAL_setColor(16 + 5, 0x0600); /* nebula deep blue */
136
+ PAL_setColor(16 + 6, 0x0402); /* nebula violet */
119
137
  /* Palette 2 = enemy red */
120
138
  PAL_setColor(32 + 3, 0x00EE);
121
139
 
@@ -126,6 +144,19 @@ int main(bool hard) {
126
144
  VDP_loadTileData(tile_ship_p2, T_SHIP_P2, 1, DMA);
127
145
  VDP_loadTileData(tile_bullet, T_BULLET, 1, DMA);
128
146
  VDP_loadTileData(tile_enemy, T_ENEMY, 1, DMA);
147
+ VDP_loadTileData(tile_space, T_SPACE, 1, DMA);
148
+ VDP_loadTileData(tile_stars, T_STARS, 1, DMA);
149
+
150
+ /* Fill the far plane (BG_B) with the space backdrop so the shared
151
+ * playfield isn't an empty black void; sprites always draw above the
152
+ * planes, so both ships + bullets + enemies read on top with no
153
+ * priority juggling. */
154
+ for (u16 cy = 0; cy < 28; cy++)
155
+ for (u16 cx = 0; cx < 40; cx++)
156
+ VDP_setTileMapXY(BG_B,
157
+ TILE_ATTR_FULL(PAL1, 0, 0, 0,
158
+ ((cx ^ cy) & 1) ? T_STARS : T_SPACE),
159
+ cx, cy);
129
160
 
130
161
  p1.x = 100; p1.y = 180; p1.alive = TRUE;
131
162
  p2.x = 220; p2.y = 180; p2.alive = TRUE;
@@ -25,9 +25,29 @@
25
25
  * (the byte layout produced by `xgm2tool input.vgm output.xgc`). */
26
26
  extern const u8 music_xgm[];
27
27
 
28
+ /* A backdrop block tiled across plane B so the title screen isn't a flat
29
+ * black void (text alone on black reads as "blank" to a human). Colour
30
+ * index 4 field with a colour-5 frame — both set below. */
31
+ static const u32 tile_bg[8] = {
32
+ 0x44444444, 0x45555554, 0x45000054, 0x45000054,
33
+ 0x45000054, 0x45000054, 0x45555554, 0x44444444,
34
+ };
35
+
36
+ #define T_BG (TILE_USER_INDEX + 0)
37
+
28
38
  int main(bool hard) {
29
39
  (void)hard;
30
40
 
41
+ /* Plane-B backdrop colours (palette 1) + a tiled backdrop so the
42
+ * music-demo title screen has a visible background behind the text.
43
+ * Plane A (the font) draws above plane B, so the text reads on top. */
44
+ PAL_setColor(16 + 4, 0x0840); /* dark green field */
45
+ PAL_setColor(16 + 5, 0x0C60); /* lighter frame */
46
+ VDP_loadTileData(tile_bg, T_BG, 1, DMA);
47
+ for (u16 cy = 0; cy < 28; cy++)
48
+ for (u16 cx = 0; cx < 40; cx++)
49
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL1, 0, 0, 0, T_BG), cx, cy);
50
+
31
51
  /* Title screen text — drawn into VDP plane A's default font region. */
32
52
  VDP_drawText("XGM2 MUSIC DEMO", 12, 10);
33
53
  VDP_drawText("ROM-DEV-MCP / SGDK", 10, 12);
@@ -1,17 +1,17 @@
1
- /* Game Gear hello-world. Writes a yellow 'H' tile to VRAM, places it in
2
- * the center of the 160×144 visible viewport, enables display. Pressing
3
- * P1-B1 scrolls the BG by one pixel per frame.
1
+ /* Game Gear hello-world. Paints the whole 160×144 visible viewport with
2
+ * a blue BG panel, drops a yellow 'H' tile in its centre, enables
3
+ * display. Pressing P1-B1 scrolls the BG by one pixel per frame.
4
4
  *
5
- * Self-contained — inlines the VDP helpers so this single file + the
5
+ * Self-contained — inlines the VDP helpers so this single file plus the
6
6
  * bundled gg_crt0.s are all you need to compile and run. For a more
7
- * modular multi-file project that pulls in src/platforms/gg/lib/c/*,
7
+ * modular multi-file project that pulls in the bundled GG runtime,
8
8
  * see `hello_sprite` and `tile_engine`.
9
9
  *
10
10
  * GG specifics this file demonstrates (read MENTAL_MODEL.md for details):
11
11
  * - Visible viewport is 160×144 centered in the 256×192 framebuffer.
12
12
  * OAM/name-table coords are in 256×192 space; the visible region
13
- * starts at hw coord (48, 24). The 'H' goes at name-table row 12
14
- * col 16 = visible center.
13
+ * starts at hw coord (48, 24). The 'H' goes at name-table row 11
14
+ * col 15 = visible center.
15
15
  * - CRAM entries are 12-bit BGR (2 bytes each, little-endian), 16 BG
16
16
  * colors at $0000-$1F and 16 sprite colors at $0020-$3F.
17
17
  * - VDP R6 default is 0xFB → sprite tiles read from $0000 (NOT $2000
@@ -86,13 +86,17 @@ static void vdp_init(void) {
86
86
  static const uint8_t palette[32] = {
87
87
  0x00, 0x00, /* 0 backdrop (black) */
88
88
  0xFF, 0x00, /* 1 yellow */
89
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
89
+ 0x00, 0x0F, /* 2 blue (panel fill A) */
90
+ 0x0F, 0x00, /* 3 red (panel fill B) */
91
+ 0x00, 0x00, 0x00, 0x00,
90
92
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
91
93
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
92
94
  };
93
95
 
94
- /* 8×8 4bpp tile of an 'H'. Mode 4 stores 4 bytes per scanline (one per
95
- * bitplane). Setting plane 0 only color index 1 = yellow. */
96
+ /* Tile 1 = an 'H' glyph. Tile 2 = a solid colour-2 fill used to paint
97
+ * the whole visible viewport so the screen is obviously not blank.
98
+ * Mode 4 stores 4 bytes per scanline (one per bitplane). Plane 0 set →
99
+ * colour index 1; plane 1 set → colour index 2. */
96
100
  static const uint8_t tile_h[32] = {
97
101
  0x66, 0x00, 0x00, 0x00,
98
102
  0x66, 0x00, 0x00, 0x00,
@@ -103,6 +107,21 @@ static const uint8_t tile_h[32] = {
103
107
  0x66, 0x00, 0x00, 0x00,
104
108
  0x00, 0x00, 0x00, 0x00,
105
109
  };
110
+ /* Tile 2 = solid colour 2 (blue). Tile 3 = solid colour 3 (red). The
111
+ * viewport is painted as a blue/red checkerboard so no single colour
112
+ * dominates the screen — a clear "it works" panel, not a flat fill. */
113
+ static const uint8_t tile_fill2[32] = {
114
+ 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
115
+ 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
116
+ 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
117
+ 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
118
+ };
119
+ static const uint8_t tile_fill3[32] = {
120
+ 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
121
+ 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
122
+ 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
123
+ 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
124
+ };
106
125
 
107
126
  static void load_palette(void) {
108
127
  uint8_t i;
@@ -110,23 +129,42 @@ static void load_palette(void) {
110
129
  for (i = 0; i < 32; i++) PORT_VDP_DATA = palette[i];
111
130
  }
112
131
 
113
- static void load_tile(void) {
132
+ static void load_tiles(void) {
114
133
  uint8_t i;
115
134
  vdp_set_addr(32, VDP_VRAM_WRITE); /* tile slot 1 = VRAM offset 32 */
116
135
  for (i = 0; i < 32; i++) PORT_VDP_DATA = tile_h[i];
136
+ vdp_set_addr(64, VDP_VRAM_WRITE); /* tile slot 2 = VRAM offset 64 */
137
+ for (i = 0; i < 32; i++) PORT_VDP_DATA = tile_fill2[i];
138
+ vdp_set_addr(96, VDP_VRAM_WRITE); /* tile slot 3 = VRAM offset 96 */
139
+ for (i = 0; i < 32; i++) PORT_VDP_DATA = tile_fill3[i];
117
140
  }
118
141
 
119
- static void clear_name_table(void) {
142
+ /* Clear the whole 32×28 name table, then paint the visible 160×144
143
+ * viewport (cols 6..25, rows 3..20) as a solid blue panel inside a red
144
+ * border frame — a clean "it works" screen that is obviously neither
145
+ * blank nor a single flat colour. */
146
+ static void draw_background(void) {
147
+ uint8_t row, col, fill;
120
148
  uint16_t i;
121
149
  vdp_set_addr(0x3800, VDP_VRAM_WRITE);
122
150
  for (i = 0; i < 1792; i++) PORT_VDP_DATA = 0;
151
+ for (row = GG_VIS_ROW_MIN; row <= GG_VIS_ROW_MAX; row++) {
152
+ vdp_set_addr(0x3800 + (row * 32 + GG_VIS_COL_MIN) * 2, VDP_VRAM_WRITE);
153
+ for (col = GG_VIS_COL_MIN; col <= GG_VIS_COL_MAX; col++) {
154
+ if (row == GG_VIS_ROW_MIN || row == GG_VIS_ROW_MAX ||
155
+ col == GG_VIS_COL_MIN || col == GG_VIS_COL_MAX)
156
+ fill = 3; /* red border */
157
+ else
158
+ fill = 2; /* blue interior */
159
+ PORT_VDP_DATA = fill;
160
+ PORT_VDP_DATA = 0;
161
+ }
162
+ }
123
163
  }
124
164
 
125
- /* Drop the 'H' at name-table (row 12, col 16). Visible GG viewport
126
- * sits at name-table cols 6..25 / rows 3..20 — so (12,16) is roughly
127
- * the visible center. */
165
+ /* Drop the 'H' at name-table (row 11, col 15) the visible centre. */
128
166
  static void place_h(void) {
129
- vdp_set_addr(0x3800 + (12 * 32 + 16) * 2, VDP_VRAM_WRITE);
167
+ vdp_set_addr(0x3800 + (11 * 32 + 15) * 2, VDP_VRAM_WRITE);
130
168
  PORT_VDP_DATA = 1;
131
169
  PORT_VDP_DATA = 0;
132
170
  }
@@ -140,8 +178,8 @@ void main(void) {
140
178
 
141
179
  vdp_init();
142
180
  load_palette();
143
- load_tile();
144
- clear_name_table();
181
+ load_tiles();
182
+ draw_background();
145
183
  place_h();
146
184
 
147
185
  vdp_write_reg(1, 0xE0); /* display ON, vblank IRQ on, 192-line */
@@ -29,6 +29,7 @@ extern void gg_vdp_write_reg(uint8_t reg, uint8_t value);
29
29
  extern void gg_vdp_set_addr(uint16_t addr, uint8_t prefix);
30
30
  extern void gg_load_palette(const uint8_t *palette);
31
31
  extern void gg_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
32
+ extern void gg_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
32
33
  extern void gg_vblank_wait(void);
33
34
  extern uint8_t gg_joypad_read(void);
34
35
  extern void gg_sprite_init(void);
@@ -41,14 +42,33 @@ extern void gg_sat_upload(void);
41
42
  * array = garbage = INVISIBLE sprites. Sprite colour index N uses entry 16+N,
42
43
  * so sprite colour 1 = entry 17 (white here). */
43
44
  static const uint8_t palette[64] = {
44
- /* BG 0-15: entry 0 = dark navy backdrop */
45
- 0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
45
+ /* BG 0-15: 0 = dark navy backdrop, 1 = teal, 2 = blue (dither tones) */
46
+ 0x20,0x02, 0xC8,0x08, 0x80,0x0C, 0,0, 0,0, 0,0, 0,0, 0,0,
46
47
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
47
48
  /* SPRITE 16-31: 16=transparent, 17=white */
48
49
  0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
49
50
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
50
51
  };
51
52
 
53
+ /* One dithered BG tile (BG bank $0000): plane0/plane1 alternate so pixels
54
+ * flip between colour 1 (teal) and colour 2 (blue). Filling the name table
55
+ * with it gives a two-tone backdrop so the frame is never a flat colour —
56
+ * a uniform fill still reads as a blank screen. The dither fills the whole
57
+ * 256x192 frame, so it shows in the GG's centered 160x144 window too. */
58
+ static const uint8_t bg_tile[32] = {
59
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
60
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
61
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
62
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
63
+ };
64
+
65
+ static void draw_bg(void) {
66
+ uint8_t row, col;
67
+ for (row = 0; row < 28; row++)
68
+ for (col = 0; col < 32; col++)
69
+ gg_set_tilemap_cell(row, col, 0, 0);
70
+ }
71
+
52
72
  /* ── Game Gear visible viewport ──────────────────────────────────────
53
73
  * Sprite OAM uses SMS HARDWARE coordinates (256x192 space), but the GG
54
74
  * LCD only shows the CENTER 160x144. Keep the sprite inside this box or
@@ -73,6 +93,9 @@ void main(void) {
73
93
 
74
94
  gg_vdp_init();
75
95
  gg_load_palette(palette);
96
+ /* BG dither tile → BG bank $0000, paint the whole name table. */
97
+ gg_load_tiles(0x0000, bg_tile, 32);
98
+ draw_bg();
76
99
  /* Upload one sprite tile to VRAM $2000 (sprite tile area). */
77
100
  gg_load_tiles(0x2000, sprite_tile, 32);
78
101
 
@@ -30,6 +30,7 @@ extern void gg_vdp_display_on(void);
30
30
  extern void gg_vdp_set_addr(uint16_t addr, uint8_t prefix);
31
31
  extern void gg_load_palette(const uint8_t *palette);
32
32
  extern void gg_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
33
+ extern void gg_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
33
34
  extern void gg_vblank_wait(void);
34
35
  extern uint8_t gg_joypad_read(void);
35
36
  extern void gg_sprite_init(void);
@@ -41,14 +42,32 @@ extern void gg_sat_upload(void);
41
42
  * (entries 16-31) reading garbage = invisible sprites. Sprite palette:
42
43
  * 16 = transparent, 17 = white, 18 = green, 19 = red. */
43
44
  static const uint8_t palette[64] = {
44
- /* BG 0-15: entry 0 = dark navy backdrop */
45
- 0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
45
+ /* BG 0-15: 0 = dark navy backdrop, 1 = teal, 2 = blue (dither tones) */
46
+ 0x20,0x02, 0xC8,0x08, 0x80,0x0C, 0,0, 0,0, 0,0, 0,0, 0,0,
46
47
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
47
48
  /* SPRITE 16-31: 16=transparent, 17=white, 18=green, 19=red */
48
49
  0,0, 0xFF,0x0F, 0xF0,0x00, 0x0F,0x00, 0,0, 0,0, 0,0, 0,0,
49
50
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
50
51
  };
51
52
 
53
+ /* One dithered BG tile (BG bank $0000): plane0/plane1 alternate so pixels
54
+ * flip between colour 1 (teal) and colour 2 (blue). Filling the name table
55
+ * with it gives a two-tone backdrop behind the song indicators so the frame
56
+ * never reads as a single flat colour. */
57
+ static const uint8_t bg_tile[32] = {
58
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
59
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
60
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
61
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
62
+ };
63
+
64
+ static void draw_bg(void) {
65
+ uint8_t row, col;
66
+ for (row = 0; row < 28; row++)
67
+ for (col = 0; col < 32; col++)
68
+ gg_set_tilemap_cell(row, col, 0, 0);
69
+ }
70
+
52
71
  /* Three 8×8 sprite tiles, 4bpp interleaved (4 planes × 8 rows):
53
72
  * tile 0: solid color 1 (white)
54
73
  * tile 1: solid color 2 (green)
@@ -99,6 +118,9 @@ void main(void) {
99
118
 
100
119
  gg_vdp_init();
101
120
  gg_load_palette(palette);
121
+ /* BG dither tile → BG bank $0000, paint the whole name table. */
122
+ gg_load_tiles(0x0000, bg_tile, 32);
123
+ draw_bg();
102
124
  gg_load_tiles(0x2000, sprite_tiles, sizeof(sprite_tiles));
103
125
  gg_sprite_init();
104
126
 
@@ -52,11 +52,12 @@ extern void gg_sat_upload(void);
52
52
 
53
53
  /* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
54
54
  * gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
55
- * (entries 16-31) reading garbage = invisible sprites. BG colour 1 = entry 1
56
- * (mid-grey wall); sprite colour 1 = entry 17 (white player). */
55
+ * (entries 16-31) reading garbage = invisible sprites. BG colour 1 = light
56
+ * sky blue, 2 = darker sky blue (the sky dither), 3 = mid-grey wall; sprite
57
+ * colour 1 = entry 17 (white player). */
57
58
  static const uint8_t palette[64] = {
58
- /* BG 0-15: entry 0 = dark navy backdrop, entry 1 = mid-grey wall */
59
- 0x20,0x02, 0xAA,0x0A, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
59
+ /* BG 0-15: 0 = dark navy backdrop, 1 = light sky, 2 = dark sky, 3 = wall grey */
60
+ 0x20,0x02, 0xFB,0x0F, 0xC8,0x0C, 0x88,0x08, 0,0, 0,0, 0,0, 0,0,
60
61
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
61
62
  /* SPRITE 16-31: 16=transparent, 17=white player */
62
63
  0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
@@ -64,14 +65,19 @@ static const uint8_t palette[64] = {
64
65
  };
65
66
 
66
67
  static const uint8_t bg_tiles[32 * 2] = {
67
- /* T_OPEN — blank */
68
- 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
69
- 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
70
- /* T_WALL — solid block in colour 1 */
71
- 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
72
- 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
73
- 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
74
- 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
68
+ /* T_OPEN — dithered sky: pixels alternate colour 1 (light) / colour 2
69
+ * (dark) so the sky fills the screen but no single colour dominates.
70
+ * plane0 = 0xAA (cols 0,2,4,6 -> colour 1), plane1 = 0x55 (cols 1,3,5,7
71
+ * -> colour 2). */
72
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
73
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
74
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
75
+ 0xAA,0x55,0x00,0x00, 0x55,0xAA,0x00,0x00,
76
+ /* T_WALL — solid block in colour 3 (planes 0+1 set) */
77
+ 0xFF,0xFF,0x00,0x00, 0xFF,0xFF,0x00,0x00,
78
+ 0xFF,0xFF,0x00,0x00, 0xFF,0xFF,0x00,0x00,
79
+ 0xFF,0xFF,0x00,0x00, 0xFF,0xFF,0x00,0x00,
80
+ 0xFF,0xFF,0x00,0x00, 0xFF,0xFF,0x00,0x00,
75
81
  };
76
82
 
77
83
  static const uint8_t player_tile[32] = {
@@ -25,16 +25,19 @@ extern uint8_t gg_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,13 +89,30 @@ 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
 
95
+ /* GG shows only the centered cols 6..25 / rows 3..20. Place the 6×12 grid
96
+ * at tilemap cols 7..12, rows 4..15 so the whole well sits inside that
97
+ * visible band. */
82
98
  static void draw_cell(int8_t col, int8_t row, uint8_t cell) {
83
99
  if (row < 0 || row >= ROWS) return;
84
- /* Centre the 6-col grid; +7 columns left margin, +1 row top margin. */
85
- gg_set_tilemap_cell((uint8_t)(row + 1), (uint8_t)(col + 7), tile_for(cell), 0);
100
+ gg_set_tilemap_cell((uint8_t)(row + 4), (uint8_t)(col + 7), tile_for(cell), 0);
101
+ }
102
+
103
+ /* Draw the well: a grey border frame around the 6×12 play field with a dim
104
+ * field interior, so the playfield is clearly visible even when empty. The
105
+ * grid maps cell (col,row) -> tilemap (row+4, col+7) = rows 4..15 cols 7..12.
106
+ * Frame the perimeter at rows 3..16, cols 6..13 — inside the GG viewport. */
107
+ static void draw_well(void) {
108
+ uint8_t r, c;
109
+ for (r = 3; r <= 16; r++) {
110
+ for (c = 6; c <= 13; c++) {
111
+ uint8_t t = T_FIELD;
112
+ if (r == 3 || r == 16 || c == 6 || c == 13) t = T_WALL;
113
+ gg_set_tilemap_cell(r, c, t, 0);
114
+ }
115
+ }
86
116
  }
87
117
 
88
118
  static void draw_grid(void) {
@@ -151,7 +181,7 @@ void main(void) {
151
181
 
152
182
  gg_vdp_init();
153
183
  gg_load_palette(palette);
154
- gg_load_tiles(0x0000, bg_tiles, 32 * 4);
184
+ gg_load_tiles(0x0000, bg_tiles, 32 * 6);
155
185
 
156
186
  for (r = 0; r < 24; r++) for (c = 0; c < 32; c++) gg_set_tilemap_cell(r, c, T_BLANK, 0);
157
187
  for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) grid[r][c] = 0;
@@ -159,6 +189,7 @@ void main(void) {
159
189
  score = 0;
160
190
  fall_timer = 0;
161
191
  new_piece();
192
+ draw_well();
162
193
  draw_grid();
163
194
 
164
195
  sfx_init();
@@ -18,6 +18,7 @@ extern void gg_vdp_init(void);
18
18
  extern void gg_vdp_display_on(void);
19
19
  extern void gg_load_palette(const uint8_t *palette);
20
20
  extern void gg_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
21
+ extern void gg_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
21
22
  extern void gg_vblank_wait(void);
22
23
  extern uint8_t gg_joypad_read(void);
23
24
  extern void gg_sprite_init(void);
@@ -32,25 +33,49 @@ extern void gg_sat_upload(void);
32
33
  #define VIS_X1 207 /* 48 + 160 - 1 */
33
34
  #define VIS_Y1 167 /* 24 + 144 - 1 */
34
35
 
35
- #define LANE_LEFT_X (VIS_X0 + 28) /* 76 */
36
- #define LANE_MID_X ((VIS_X0 + VIS_X1) / 2 - 4) /* ~123 */
37
- #define LANE_RIGHT_X (VIS_X1 - 36) /* 171 */
38
- #define PLAYER_Y (VIS_Y1 - 16)
36
+ /* Explicit (uint8_t) casts: the computed int expressions all fit in a byte,
37
+ * but SDCC warns (158) on the implicit int->uint8_t narrowing in the const
38
+ * initializers below unless the conversion is spelled out. */
39
+ #define LANE_LEFT_X ((uint8_t)(VIS_X0 + 28)) /* 76 */
40
+ #define LANE_MID_X ((uint8_t)((VIS_X0 + VIS_X1) / 2 - 4)) /* ~123 */
41
+ #define LANE_RIGHT_X ((uint8_t)(VIS_X1 - 36)) /* 171 */
42
+ #define PLAYER_Y ((uint8_t)(VIS_Y1 - 16))
39
43
  #define MAX_OBSTACLES 4
40
44
 
41
45
  /* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
42
46
  * gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
43
- * (entries 16-31) reading garbage = invisible sprites. Sprite colour 1 = entry
44
- * 17 (white), colour 2 = entry 18 (red). */
47
+ * (entries 16-31) reading garbage = invisible sprites. BG colour 1 = grass
48
+ * green, BG colour 2 = road grey. Sprite colour 1 = entry 17 (white),
49
+ * colour 2 = entry 18 (red). */
45
50
  static const uint8_t palette[64] = {
46
- /* BG 0-15: entry 0 = dark navy backdrop */
47
- 0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
51
+ /* BG 0-15: 0 = dark navy backdrop, 1 = grass green, 2 = road grey */
52
+ 0x20,0x02, 0x60,0x00, 0x66,0x06, 0,0, 0,0, 0,0, 0,0, 0,0,
48
53
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
49
54
  /* SPRITE 16-31: 16=transparent, 17=white, 18=red */
50
55
  0,0, 0xFF,0x0F, 0x0F,0x00, 0,0, 0,0, 0,0, 0,0, 0,0,
51
56
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
52
57
  };
53
58
 
59
+ /* Three BG tiles for the track, loaded into the BG tile bank at $0000:
60
+ * tile 0 = blank (all zeros → shows the backdrop colour)
61
+ * tile 1 = solid grass (colour 1)
62
+ * tile 2 = solid road (colour 2) */
63
+ static const uint8_t bg_tiles[96] = {
64
+ /* BG tile 0 = blank */
65
+ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
66
+ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
67
+ /* BG tile 1 = grass (colour 1 → plane 0 set) */
68
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
69
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
70
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
71
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
72
+ /* BG tile 2 = road (colour 2 → plane 1 set) */
73
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
74
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
75
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
76
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
77
+ };
78
+
54
79
  /* Two sprite tiles — player (colour 1) + enemy (colour 2). */
55
80
  static const uint8_t tiles[64] = {
56
81
  /* Tile 0 = player car (colour 1 → plane 0 set) */
@@ -65,6 +90,28 @@ static const uint8_t tiles[64] = {
65
90
  0x00,0x7E,0x00,0x00, 0x00,0x66,0x00,0x00,
66
91
  };
67
92
 
93
+ /* Paint the visible viewport: grey road down the centre lanes, green
94
+ * grass on the shoulders. Visible name-table region is cols 6..25,
95
+ * rows 3..20 (the centered 160×144). BG tile bank is $0000. */
96
+ #define VIS_COL_MIN 6
97
+ #define VIS_COL_MAX 25
98
+ #define VIS_ROW_MIN 3
99
+ #define VIS_ROW_MAX 20
100
+ static void draw_track(void) {
101
+ uint8_t row, col;
102
+ /* Blank the whole 32×28 name table to backdrop (tile 0). */
103
+ for (row = 0; row < 28; row++)
104
+ for (col = 0; col < 32; col++) gg_set_tilemap_cell(row, col, 0, 0);
105
+ /* Paint the visible viewport: road (tile 2) down the central lanes,
106
+ * grass (tile 1) on the shoulders. */
107
+ for (row = VIS_ROW_MIN; row <= VIS_ROW_MAX; row++) {
108
+ for (col = VIS_COL_MIN; col <= VIS_COL_MAX; col++) {
109
+ uint8_t road = (col >= VIS_COL_MIN + 4 && col <= VIS_COL_MAX - 4);
110
+ gg_set_tilemap_cell(row, col, road ? 2 : 1, 0);
111
+ }
112
+ }
113
+ }
114
+
68
115
  typedef struct { uint8_t x, y, alive; } Car;
69
116
 
70
117
  static Car player;
@@ -110,7 +157,9 @@ void main(void) {
110
157
  uint8_t i;
111
158
  gg_vdp_init();
112
159
  gg_load_palette(palette);
113
- gg_load_tiles(0x2000, tiles, 64);
160
+ gg_load_tiles(0x0000, bg_tiles, 96); /* BG tiles → BG bank $0000 */
161
+ gg_load_tiles(0x2000, tiles, 64); /* sprite tiles → sprite bank $2000 */
162
+ draw_track();
114
163
  gg_sprite_init();
115
164
  sfx_init();
116
165
  gg_vdp_display_on();