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
@@ -4,7 +4,12 @@
4
4
  * lanes (edge-detected), obstacles slide down at speed = 2 + score/500
5
5
  * (capped). Collision triggers a 60-frame freeze + auto-reset.
6
6
  *
7
- * Game Boy screen is 160×144 — 3 lanes centred around x = {48, 80, 112}.
7
+ * Game Boy screen is 160×144 — 3 lanes centred around x = {40, 76, 112}.
8
+ *
9
+ * The road is a real background: grass shoulders down each side, dark
10
+ * asphalt across the playfield, and dashed white lane lines between the
11
+ * lanes (LCDC bit 0 = BG ON — drop it and the screen is a flat colour,
12
+ * the #1 GB "why is it blank" footgun). Cars are sprites on top.
8
13
  */
9
14
 
10
15
  #include "gb_hardware.h"
@@ -16,7 +21,7 @@
16
21
  #define PLAYER_Y 120
17
22
  #define MAX_OBSTACLES 4
18
23
 
19
- static const uint8_t tile_blank[16] = { 0 };
24
+ /* ── Sprite tiles (cars) ──────────────────────────────────────────── */
20
25
  static const uint8_t tile_car_p1[16] = {
21
26
  0x3C,0x00, 0x7E,0x00, 0x42,0x00, 0x7E,0x00,
22
27
  0x7E,0x00, 0x42,0x00, 0x7E,0x00, 0x66,0x00,
@@ -26,8 +31,41 @@ static const uint8_t tile_car_en[16] = {
26
31
  0x00,0x7E, 0x00,0x42, 0x00,0x7E, 0x00,0x66,
27
32
  };
28
33
 
34
+ /* ── BG tiles (road) ──────────────────────────────────────────────── */
35
+ /* 2bpp: row N = byte 2N (low plane) + byte 2N+1 (high plane).
36
+ * asphalt — all colour 2 (mid-dark)
37
+ * grass — all colour 1 (light, the shoulders)
38
+ * laneA/B — dashed white (colour 3) lane line, two phases for the dashes */
39
+ static const uint8_t tile_asphalt[16] = {
40
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
41
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
42
+ };
43
+ static const uint8_t tile_grass[16] = {
44
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
45
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
46
+ };
47
+ /* lane line = a 2px-wide colour-3 stripe down the centre of the cell;
48
+ * phase A draws the top half, phase B the bottom half → dashes. */
49
+ static const uint8_t tile_laneA[16] = {
50
+ 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18,
51
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
52
+ };
53
+ static const uint8_t tile_laneB[16] = {
54
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
55
+ 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18,
56
+ };
57
+
58
+ /* OBJ palette: 0 transparent, 1 white (player), 2 red, 3 green. */
29
59
  static const uint16_t obj_palette[4] = { 0x7FFF, 0x7FFF, 0x001F, 0x03E0 };
30
60
 
61
+ /* Tile indices in VRAM. Sprites and BG share the $8000 table here. */
62
+ #define T_CAR_P1 1
63
+ #define T_CAR_EN 2
64
+ #define T_ASPHALT 3
65
+ #define T_GRASS 4
66
+ #define T_LANE_A 5
67
+ #define T_LANE_B 6
68
+
31
69
  typedef struct { int16_t x, y; uint8_t alive; } Car;
32
70
 
33
71
  static Car player;
@@ -71,8 +109,27 @@ static void spawn_obstacle(void) {
71
109
 
72
110
  static void upload_tile(uint8_t slot, const uint8_t *src) {
73
111
  uint8_t *dst = (uint8_t *)(0x8000 + slot * 16);
74
- uint8_t i;
75
- for (i = 0; i < 16; i++) dst[i] = src[i];
112
+ /* memcpy_vram (pointer-walk) — NOT an indexed dst[i]=src[i] loop, which
113
+ * SDCC sm83 miscompiles when dst points into VRAM ($8000-$9FFF). */
114
+ memcpy_vram(dst, src, 16);
115
+ }
116
+
117
+ /* Paint the road into BG map 0 ($9800). 20×18 visible cells:
118
+ * col 0 = grass shoulder (left)
119
+ * col 19 = grass shoulder (right)
120
+ * cols 1..18 = asphalt, with dashed lane lines at the two lane
121
+ * boundaries (cols 6 and 12). Dashes alternate per row. */
122
+ static void draw_road(void) {
123
+ uint8_t *bg = BG_MAP_0;
124
+ uint8_t r, c, t;
125
+ for (r = 0; r < 18; r++) {
126
+ for (c = 0; c < 20; c++) {
127
+ if (c == 0 || c == 19) t = T_GRASS;
128
+ else if (c == 6 || c == 12) t = (r & 1) ? T_LANE_A : T_LANE_B;
129
+ else t = T_ASPHALT;
130
+ bg[r * 32 + c] = t;
131
+ }
132
+ }
76
133
  }
77
134
 
78
135
  void main(void) {
@@ -83,18 +140,26 @@ void main(void) {
83
140
  lcd_init_default();
84
141
  LCDC = 0;
85
142
 
86
- upload_tile(0, tile_blank);
87
- upload_tile(1, tile_car_p1);
88
- upload_tile(2, tile_car_en);
143
+ upload_tile(T_CAR_P1, tile_car_p1);
144
+ upload_tile(T_CAR_EN, tile_car_en);
145
+ upload_tile(T_ASPHALT, tile_asphalt);
146
+ upload_tile(T_GRASS, tile_grass);
147
+ upload_tile(T_LANE_A, tile_laneA);
148
+ upload_tile(T_LANE_B, tile_laneB);
89
149
 
150
+ /* DMG BG palette: 0 white, 1 light, 2 dark, 3 black. The road reads
151
+ * as asphalt(dark) + grass(light) + white dashes. */
152
+ BGP = 0xE4;
90
153
  OCPS = 0x80;
91
154
  for (i = 0; i < 4; i++) {
92
155
  OCPD = (uint8_t)(obj_palette[i] & 0xFF);
93
156
  OCPD = (uint8_t)((obj_palette[i] >> 8) & 0xFF);
94
157
  }
95
158
 
159
+ draw_road();
160
+
96
161
  oam_clear();
97
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
162
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
98
163
  sound_init();
99
164
 
100
165
  reset_run();
@@ -105,13 +170,13 @@ void main(void) {
105
170
 
106
171
  /* Stage OAM — player + obstacles. */
107
172
  for (i = 0; i < 40; i++) oam_set(i, 0, 0, 0, 0);
108
- oam_set(0, (uint8_t)(player.y + 16), (uint8_t)(player.x + 8), 1, 0);
173
+ oam_set(0, (uint8_t)(player.y + 16), (uint8_t)(player.x + 8), T_CAR_P1, 0);
109
174
  for (i = 0; i < MAX_OBSTACLES; i++) {
110
175
  if (obstacles[i].alive) {
111
176
  oam_set((uint8_t)(1 + i),
112
177
  (uint8_t)(obstacles[i].y + 16),
113
178
  (uint8_t)(obstacles[i].x + 8),
114
- 2, 0);
179
+ T_CAR_EN, 0);
115
180
  }
116
181
  }
117
182
  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 scrolling-looking starfield so the screen is
30
+ * never one flat colour (LCDC_BG_ON below — drop it and the screen reads
31
+ * as blank, the #1 GB "why is it blank" footgun).
32
+ * tile_space — a 50/50 dither of colour 0 (dark) + colour 1, so even an
33
+ * empty patch of space mixes two palette shades and never
34
+ * lets one colour dominate the frame.
35
+ * tile_star — a bright colour-3 "+" 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,
@@ -82,8 +100,20 @@ static void spawn(void) {
82
100
 
83
101
  static void upload_tile(uint8_t slot, const uint8_t *src) {
84
102
  uint8_t *dst = (uint8_t *)(0x8000 + slot * 16);
85
- uint8_t i;
86
- for (i = 0; i < 16; i++) dst[i] = src[i];
103
+ /* memcpy_vram (pointer-walk) — NOT an indexed dst[i]=src[i] loop, which
104
+ * SDCC sm83 miscompiles when dst points into VRAM ($8000-$9FFF). */
105
+ memcpy_vram(dst, src, 16);
106
+ }
107
+
108
+ /* Paint a starfield into BG map 0 ($9800): fill the visible 20×18 with the
109
+ * dithered space tile, then scatter bright stars on a fixed pseudo-pattern
110
+ * so the field reads as deep space rather than a flat colour. */
111
+ static void draw_starfield(void) {
112
+ uint8_t *bg = BG_MAP_0;
113
+ uint8_t r, c;
114
+ for (r = 0; r < 18; r++)
115
+ for (c = 0; c < 20; c++)
116
+ bg[r * 32 + c] = ((r * 7 + c * 5) % 11 == 0) ? T_STAR : T_SPACE;
87
117
  }
88
118
 
89
119
  void main(void) {
@@ -97,6 +127,12 @@ void main(void) {
97
127
  upload_tile(1, tile_ship);
98
128
  upload_tile(2, tile_bullet);
99
129
  upload_tile(3, tile_enemy);
130
+ upload_tile(T_SPACE, tile_space);
131
+ upload_tile(T_STAR, tile_star);
132
+
133
+ /* DMG BG palette: 0 dark, 1 mid-dark, 2 light, 3 white — the dithered
134
+ * space tile mixes shades 0+1 (deep space), stars use shade 3 (white). */
135
+ BGP = 0xE4;
100
136
 
101
137
  OCPS = 0x80;
102
138
  for (i = 0; i < 4; i++) {
@@ -104,6 +140,8 @@ void main(void) {
104
140
  OCPD = (uint8_t)((obj_palette[i] >> 8) & 0xFF);
105
141
  }
106
142
 
143
+ draw_starfield();
144
+
107
145
  player.x = 76; player.y = 130; player.alive = 1;
108
146
  for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
109
147
  for (i = 0; i < MAX_ENEMIES; i++) enemies[i].alive = 0;
@@ -111,7 +149,7 @@ void main(void) {
111
149
  spawn_timer = 0;
112
150
 
113
151
  oam_clear();
114
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
152
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
115
153
  sound_init();
116
154
 
117
155
  while (1) {
@@ -28,6 +28,30 @@ 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 colour 0 + colour 1 (the turf), so even
36
+ * an empty patch mixes two shades and never dominates.
37
+ * tile_net — a dashed vertical centre-net stripe (colour 2).
38
+ * tile_wall — a solid colour-2 border for the top / bottom rails. */
39
+ static const uint8_t tile_court[16] = {
40
+ 0x55,0x55, 0xAA,0xAA, 0x55,0x55, 0xAA,0xAA,
41
+ 0x55,0x55, 0xAA,0xAA, 0x55,0x55, 0xAA,0xAA,
42
+ };
43
+ static const uint8_t tile_net[16] = {
44
+ 0x18,0x18, 0x18,0x18, 0x00,0x00, 0x00,0x00,
45
+ 0x18,0x18, 0x18,0x18, 0x00,0x00, 0x00,0x00,
46
+ };
47
+ static const uint8_t tile_wall[16] = {
48
+ 0x00,0x00, 0xFF,0xFF, 0xFF,0xFF, 0x00,0x00,
49
+ 0x00,0x00, 0xFF,0xFF, 0xFF,0xFF, 0x00,0x00,
50
+ };
51
+ #define T_COURT 2
52
+ #define T_NET 3
53
+ #define T_WALL 4
54
+
31
55
  static const uint16_t obj_palette[4] = { 0x7FFF, 0x001F, 0x03E0, 0x7C00 };
32
56
 
33
57
  static int16_t p1y, p2y, bx, by;
@@ -53,8 +77,24 @@ static void reset_match(void) {
53
77
 
54
78
  static void upload_tile(uint8_t slot, const uint8_t *src) {
55
79
  uint8_t *dst = (uint8_t *)(0x8000 + slot * 16);
56
- uint8_t i;
57
- for (i = 0; i < 16; i++) dst[i] = src[i];
80
+ /* memcpy_vram (pointer-walk) — NOT an indexed dst[i]=src[i] loop, which
81
+ * SDCC sm83 miscompiles when dst points into VRAM ($8000-$9FFF). */
82
+ memcpy_vram(dst, src, 16);
83
+ }
84
+
85
+ /* Paint the Pong court into BG map 0 ($9800): dithered turf everywhere,
86
+ * solid top/bottom rails, and a dashed net down the centre column. */
87
+ static void draw_court(void) {
88
+ uint8_t *bg = BG_MAP_0;
89
+ uint8_t r, c, t;
90
+ for (r = 0; r < 18; r++) {
91
+ for (c = 0; c < 20; c++) {
92
+ if (r == 0 || r == 17) t = T_WALL; /* top / bottom rail */
93
+ else if (c == 9 || c == 10) t = (r & 1) ? T_NET : T_COURT; /* net dashes */
94
+ else t = T_COURT;
95
+ bg[r * 32 + c] = t;
96
+ }
97
+ }
58
98
  }
59
99
 
60
100
  void main(void) {
@@ -67,6 +107,13 @@ void main(void) {
67
107
 
68
108
  upload_tile(0, tile_blank);
69
109
  upload_tile(1, tile_solid);
110
+ upload_tile(T_COURT, tile_court);
111
+ upload_tile(T_NET, tile_net);
112
+ upload_tile(T_WALL, tile_wall);
113
+
114
+ /* DMG BG palette: 0 dark, 1 mid, 2 light, 3 white — the dithered turf
115
+ * mixes shades 0+1, rails/net use shade 2. */
116
+ BGP = 0xE4;
70
117
 
71
118
  OCPS = 0x80;
72
119
  for (i = 0; i < 4; i++) {
@@ -74,8 +121,9 @@ void main(void) {
74
121
  OCPD = (uint8_t)((obj_palette[i] >> 8) & 0xFF);
75
122
  }
76
123
 
124
+ draw_court();
77
125
  oam_clear();
78
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
126
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
79
127
  sound_init();
80
128
 
81
129
  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) {
@@ -1,8 +1,8 @@
1
1
  /* ── gba_hello.c — Game Boy Advance libgba starter ──────────────────
2
2
  *
3
3
  * Idiomatic GBA C against the devkitPro libgba SDK — same shape every
4
- * published devkitARM tutorial uses. Mode 3 framebuffer, draws a red
5
- * pixel that moves left/right via the d-pad.
4
+ * published devkitARM tutorial uses. Mode 3 (240×160 bitmap) framebuffer:
5
+ * draws a navy sky + green ground + a yellow box you move with the d-pad.
6
6
  *
7
7
  * Build via romdev:
8
8
  * build({ output: "rom", platform:"gba", language:"c", runtime:"libgba",
@@ -35,7 +35,22 @@
35
35
 
36
36
  #include <gba.h>
37
37
 
38
+ #define SCR_W 240
39
+ #define SCR_H 160
40
+ #define BOX 16 /* movable box size in pixels */
41
+
42
+ /* Fill a solid rectangle in the Mode-3 framebuffer. */
43
+ static void fill_rect(int x, int y, int w, int h, u16 color) {
44
+ int yy, xx;
45
+ for (yy = y; yy < y + h; yy++)
46
+ for (xx = x; xx < x + w; xx++)
47
+ MODE3_FB[yy][xx] = color;
48
+ }
49
+
38
50
  int main(void) {
51
+ int x = (SCR_W - BOX) / 2;
52
+ int y = (SCR_H - BOX) / 2;
53
+
39
54
  /* MODE_3: 240×160 framebuffer, 15-bit color, BG2 = the framebuffer. */
40
55
  REG_DISPCNT = MODE_3 | BG2_ON;
41
56
 
@@ -43,22 +58,25 @@ int main(void) {
43
58
  irqInit();
44
59
  irqEnable(IRQ_VBLANK);
45
60
 
46
- int x = 120;
47
- int y = 80;
48
-
49
61
  while (1) {
50
62
  VBlankIntrWait();
51
63
 
52
64
  /* Read d-pad. REG_KEYINPUT is active-LOW (bit clear = pressed).
53
65
  * Invert + mask to get "is pressed" semantics. */
54
66
  u16 keys = ~REG_KEYINPUT & 0x3FF;
55
- if ((keys & KEY_LEFT) && x > 0) x--;
56
- if ((keys & KEY_RIGHT) && x < 239) x++;
57
- if ((keys & KEY_UP) && y > 0) y--;
58
- if ((keys & KEY_DOWN) && y < 159) y++;
67
+ if ((keys & KEY_LEFT) && x > 0) x -= 2;
68
+ if ((keys & KEY_RIGHT) && x < SCR_W - BOX) x += 2;
69
+ if ((keys & KEY_UP) && y > 0) y -= 2;
70
+ if ((keys & KEY_DOWN) && y < SCR_H - BOX) y += 2;
59
71
 
60
- /* Trail effect: don't erase, just keep drawing. */
61
- MODE3_FB[y][x] = RGB5(31, 0, 0);
72
+ /* Draw a recognizable scene every frame:
73
+ * - a navy sky filling the whole framebuffer (so it is
74
+ * obviously NOT a blank screen),
75
+ * - a green ground strip along the bottom,
76
+ * - a yellow d-pad-movable box. */
77
+ fill_rect(0, 0, SCR_W, SCR_H, RGB5(2, 4, 12)); /* sky */
78
+ fill_rect(0, SCR_H-32, SCR_W, 32, RGB5(4, 18, 4)); /* ground */
79
+ fill_rect(x, y, BOX, BOX, RGB5(31, 31, 0)); /* box */
62
80
  }
63
81
  return 0;
64
82
  }
@@ -47,6 +47,19 @@ extern const u8 soundbank_bin[];
47
47
  * Adjust if you regenerate with a differently-named .xm. */
48
48
  #define MOD_CHIPTUNE 0
49
49
 
50
+ /* ── Backdrop tiles (4bpp) ───────────────────────────────────────────
51
+ * Two solid-colour tiles so we can lay a two-tone checkerboard across the
52
+ * whole BG1 map. Without a filled backdrop the screen is just the black
53
+ * backdrop colour plus a few text glyphs — which reads as "blank". */
54
+ static const u32 tile_solid1[8] = {
55
+ 0x11111111, 0x11111111, 0x11111111, 0x11111111,
56
+ 0x11111111, 0x11111111, 0x11111111, 0x11111111,
57
+ };
58
+ static const u32 tile_solid2[8] = {
59
+ 0x22222222, 0x22222222, 0x22222222, 0x22222222,
60
+ 0x22222222, 0x22222222, 0x22222222, 0x22222222,
61
+ };
62
+
50
63
  int main(void) {
51
64
  /* ── IRQ setup ── Maxmod requires mmVBlank() in the VBlank slot.
52
65
  * Without this, the mixer DMA buffers never get swapped and audio
@@ -55,9 +68,30 @@ int main(void) {
55
68
  irq_init(NULL);
56
69
  irq_add(II_VBLANK, mmVBlank);
57
70
 
58
- /* TTE setup so we can show a status banner. Standard Tonc setup. */
71
+ /* ── Filled checkerboard backdrop on BG1 ─────────────────────────
72
+ * Lay a two-tone checkerboard across the whole 32x32 BG1 map so the
73
+ * frame has real content behind the text instead of a flat black
74
+ * backdrop (which reads as "blank"). Tiles → char-block 1, map →
75
+ * screen-block 28 — clear of TTE's char-block 0 / screen-block 31. */
76
+ pal_bg_mem[0] = CLR_BLACK;
77
+ pal_bg_mem[1] = RGB15(3, 6, 14); /* deep sky blue */
78
+ pal_bg_mem[2] = RGB15(2, 4, 9); /* darker navy */
79
+ tonccpy(&tile_mem[1][1], tile_solid1, sizeof(tile_solid1));
80
+ tonccpy(&tile_mem[1][2], tile_solid2, sizeof(tile_solid2));
81
+ REG_BG1CNT = BG_CBB(1) | BG_SBB(28) | BG_REG_32x32 | BG_4BPP | BG_PRIO(3);
82
+ {
83
+ SCR_ENTRY *map = se_mem[28];
84
+ int tx, ty;
85
+ for (ty = 0; ty < 32; ty++)
86
+ for (tx = 0; tx < 32; tx++)
87
+ map[ty * 32 + tx] = SE_BUILD(1 + ((tx ^ ty) & 1), 0, 0, 0);
88
+ }
89
+
90
+ /* TTE setup so we can show a status banner. Standard Tonc setup.
91
+ * TTE text on BG0 in front of the BG1 backdrop. */
59
92
  tte_init_chr4c_default(0, BG_CBB(0) | BG_SBB(31));
60
- REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
93
+ REG_BG0CNT |= BG_PRIO(0); /* text in front of the backdrop */
94
+ REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_BG1;
61
95
 
62
96
  tte_write("#{P:24,32}");
63
97
  tte_write("Maxmod demo");
@@ -45,7 +45,9 @@ static const Rect platforms[] = {
45
45
  { 440, 120, 56, 8 },
46
46
  { 180, 52, 48, 8 },
47
47
  };
48
- #define N_PLATFORMS (sizeof(platforms) / sizeof(platforms[0]))
48
+ /* (int) cast so `for (int i = 0; i < N_PLATFORMS; ...)` doesn't compare a
49
+ * signed counter against an unsigned size_t (-Wsign-compare). */
50
+ #define N_PLATFORMS ((int)(sizeof(platforms) / sizeof(platforms[0])))
49
51
 
50
52
  static OBJ_ATTR obj_buffer[128];
51
53
 
@@ -60,6 +60,14 @@ static const u32 tile_blue[8] = {
60
60
  0x33333333, 0x33333333, 0x33333333, 0x33333333,
61
61
  0x33333333, 0x33333333, 0x33333333, 0x33333333,
62
62
  };
63
+ /* Backdrop tile (colour index 4 = steel grey): a dither so the whole screen
64
+ * reads as a "cabinet" behind the playfield instead of flat black — a lone
65
+ * 6x12 grid floating on black looks blank to a human (frame verify <92%). */
66
+ static const u32 tile_back[8] = {
67
+ 0x40404040, 0x04040404, 0x40404040, 0x04040404,
68
+ 0x40404040, 0x04040404, 0x40404040, 0x04040404,
69
+ };
70
+ #define TILE_BACK 4
63
71
 
64
72
  static u8 grid[ROWS][COLS];
65
73
 
@@ -92,7 +100,7 @@ static int tile_for(u8 cell) {
92
100
  case 1: return TILE_RED;
93
101
  case 2: return TILE_GREEN;
94
102
  case 3: return TILE_BLUE;
95
- default: return TILE_BLANK;
103
+ default: return TILE_BACK; /* empty cell shows the backdrop, not black */
96
104
  }
97
105
  }
98
106
 
@@ -168,15 +176,19 @@ int main(void) {
168
176
  pal_bg_mem[1] = CLR_RED;
169
177
  pal_bg_mem[2] = CLR_LIME;
170
178
  pal_bg_mem[3] = CLR_BLUE;
179
+ pal_bg_mem[4] = RGB15(6, 6, 9); /* steel grey backdrop */
171
180
 
172
181
  /* BG tile graphics in char-block 3 (separate from TTE which used 2). */
173
182
  tonccpy(&tile_mem[3][TILE_RED], tile_red, sizeof(tile_red));
174
183
  tonccpy(&tile_mem[3][TILE_GREEN], tile_green, sizeof(tile_green));
175
184
  tonccpy(&tile_mem[3][TILE_BLUE], tile_blue, sizeof(tile_blue));
185
+ tonccpy(&tile_mem[3][TILE_BACK], tile_back, sizeof(tile_back));
176
186
 
177
- /* Clear screen-block 28 (BG0 map). */
187
+ /* Fill screen-block 28 (BG0 map) with the backdrop tile so the whole
188
+ * screen is covered; the grid cells draw over it. (A blank/black map left
189
+ * the playfield floating on black — reads as blank.) */
178
190
  SCR_ENTRY *map = se_mem[28];
179
- for (int i = 0; i < 32 * 32; i++) map[i] = SE_BUILD(TILE_BLANK, 0, 0, 0);
191
+ for (int i = 0; i < 32 * 32; i++) map[i] = SE_BUILD(TILE_BACK, 0, 0, 0);
180
192
 
181
193
  REG_BG0CNT = BG_CBB(3) | BG_SBB(28) | BG_REG_32x32 | BG_4BPP | BG_PRIO(0);
182
194
  /* Bump TTE's BG1 to a LOWER priority so the grid (BG0, prio 0) renders
@@ -47,6 +47,29 @@ static const u32 tile_car_enemy[8] = {
47
47
  0x33333333, 0x34444443, 0x33333333, 0x03300330,
48
48
  };
49
49
 
50
+ /* ── Road backdrop tiles (4bpp) ──────────────────────────────────────
51
+ * Fill the whole BG0 with a road scene instead of a flat black screen:
52
+ * tile 4 = grass roadside (palette index 5, with darker tufts 6)
53
+ * tile 5 = asphalt road (palette index 7)
54
+ * tile 6 = asphalt + a centred white lane dash (index 8)
55
+ * tile 7 = road edge line (white index 8 down the left column) */
56
+ static const u32 tile_grass[8] = {
57
+ 0x55555555, 0x55655555, 0x55555555, 0x55556555,
58
+ 0x55555555, 0x65555555, 0x55555555, 0x55555655,
59
+ };
60
+ static const u32 tile_road[8] = {
61
+ 0x77777777, 0x77777777, 0x77777777, 0x77777777,
62
+ 0x77777777, 0x77777777, 0x77777777, 0x77777777,
63
+ };
64
+ static const u32 tile_road_dash[8] = {
65
+ 0x77877877, 0x77877877, 0x77877877, 0x77877877,
66
+ 0x77777777, 0x77777777, 0x77777777, 0x77777777,
67
+ };
68
+ static const u32 tile_road_edge[8] = {
69
+ 0x87777778, 0x87777778, 0x87777778, 0x87777778,
70
+ 0x87777778, 0x87777778, 0x87777778, 0x87777778,
71
+ };
72
+
50
73
  typedef struct { s16 x, y; u16 alive; } Car;
51
74
 
52
75
  static OBJ_ATTR obj_buffer[128];
@@ -100,6 +123,43 @@ int main(void) {
100
123
  tonccpy(&tile_mem[4][TILE_CAR_P1], tile_car_p1, sizeof(tile_car_p1));
101
124
  tonccpy(&tile_mem[4][TILE_CAR_EN], tile_car_enemy, sizeof(tile_car_enemy));
102
125
 
126
+ /* ── Road backdrop on BG0 ────────────────────────────────────────
127
+ * Paint a full-screen road scene (grass shoulders + asphalt + lane
128
+ * dashes) so the track doesn't read as a blank black screen. BG
129
+ * palette: 5 grass / 6 grass-tuft / 7 asphalt / 8 white lines. Tile
130
+ * data → char-block 0, map → screen-block 28 (clear of TTE on
131
+ * char-block 2 / screen-block 30). BG0 is lowest priority so the
132
+ * cars draw in front. The 240-px screen is 30 tiles wide; the road
133
+ * occupies columns 5..24 with grass on either shoulder. */
134
+ pal_bg_mem[0] = CLR_BLACK;
135
+ pal_bg_mem[5] = RGB15(4, 16, 5); /* grass */
136
+ pal_bg_mem[6] = RGB15(2, 11, 3); /* darker grass tuft*/
137
+ pal_bg_mem[7] = RGB15(7, 7, 8); /* asphalt */
138
+ pal_bg_mem[8] = CLR_WHITE; /* lane / edge lines*/
139
+ tonccpy(&tile_mem[0][4], tile_grass, sizeof(tile_grass));
140
+ tonccpy(&tile_mem[0][5], tile_road, sizeof(tile_road));
141
+ tonccpy(&tile_mem[0][6], tile_road_dash, sizeof(tile_road_dash));
142
+ tonccpy(&tile_mem[0][7], tile_road_edge, sizeof(tile_road_edge));
143
+ REG_BG0CNT = BG_CBB(0) | BG_SBB(28) | BG_REG_32x32 | BG_4BPP | BG_PRIO(3);
144
+ {
145
+ SCR_ENTRY *map = se_mem[28];
146
+ for (int ty = 0; ty < 32; ty++) {
147
+ for (int tx = 0; tx < 32; tx++) {
148
+ int tile;
149
+ if (tx < 5 || tx > 24) {
150
+ tile = 4; /* grass shoulder */
151
+ } else if (tx == 5 || tx == 24) {
152
+ tile = 7; /* white road edge */
153
+ } else if ((tx == 11 || tx == 18) && (ty & 1)) {
154
+ tile = 6; /* dashed lane divider */
155
+ } else {
156
+ tile = 5; /* plain asphalt */
157
+ }
158
+ map[ty * 32 + tx] = SE_BUILD(tile, 0, 0, 0);
159
+ }
160
+ }
161
+ }
162
+
103
163
  oam_init(obj_buffer, 128);
104
164
 
105
165
  /* IRQ setup — required for VBlankIntrWait() to function. */
@@ -108,9 +168,11 @@ int main(void) {
108
168
 
109
169
  sfx_init();
110
170
 
111
- /* TTE for score + hint. */
112
- tte_init_chr4c_default(0, BG_CBB(0) | BG_SBB(31));
113
- REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_OBJ | DCNT_OBJ_1D;
171
+ /* TTE for score + hint on BG1 (BG0 holds the road). char-block 2 /
172
+ * screen-block 30; priority 0 text in front of everything. */
173
+ tte_init_chr4c_default(1, BG_CBB(2) | BG_SBB(30));
174
+ REG_BG1CNT |= BG_PRIO(0);
175
+ REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_BG1 | DCNT_OBJ | DCNT_OBJ_1D;
114
176
  tte_write("#{P:160,8}SCORE 00000");
115
177
  tte_write("#{P:64,150}L/R MOVES LANE");
116
178
 
@@ -64,6 +64,20 @@ static const u32 tile_enemy[8] = {
64
64
  0x33333333, 0x03333330, 0x30000003, 0x03000030,
65
65
  };
66
66
 
67
+ /* ── Starfield backdrop tiles (4bpp) ─────────────────────────────────
68
+ * Two space-coloured fill tiles (palette indices 4 and 5) laid in
69
+ * vertical bands so the whole BG is filled — NOT a flat blank backdrop.
70
+ * A handful of star pixels (index 6) are punched into each tile so the
71
+ * field reads as space rather than a solid block. */
72
+ static const u32 tile_star_a[8] = {
73
+ 0x44464444, 0x44444444, 0x46444444, 0x44444644,
74
+ 0x44444444, 0x64444444, 0x44444444, 0x44446444,
75
+ };
76
+ static const u32 tile_star_b[8] = {
77
+ 0x55555555, 0x55655555, 0x55555555, 0x55555565,
78
+ 0x65555555, 0x55555555, 0x55556555, 0x55555555,
79
+ };
80
+
67
81
  typedef struct { s16 x, y; u16 alive; } Obj;
68
82
 
69
83
  static OBJ_ATTR obj_buffer[128];
@@ -114,6 +128,27 @@ int main(void) {
114
128
  pal_obj_bank[1][2] = CLR_YELLOW; /* bullets */
115
129
  pal_obj_bank[2][3] = CLR_RED; /* enemies */
116
130
 
131
+ /* ── Starfield backdrop on BG0 ───────────────────────────────────
132
+ * Fill the whole screen with a banded space backdrop + scattered
133
+ * stars so the playfield doesn't read as a blank black screen. BG
134
+ * palette indices 4/5 = the two space bands, 6 = star colour. Tile
135
+ * data → char-block 0, map → screen-block 28 (clear of TTE on
136
+ * char-block 2 / screen-block 30). BG0 sits at the lowest priority
137
+ * so the ship/bullets/enemies draw in front of it. */
138
+ pal_bg_mem[0] = CLR_BLACK;
139
+ pal_bg_mem[4] = RGB15(1, 2, 7); /* dark space band */
140
+ pal_bg_mem[5] = RGB15(2, 3, 10); /* lighter band */
141
+ pal_bg_mem[6] = RGB15(28, 28, 31); /* stars */
142
+ tonccpy(&tile_mem[0][4], tile_star_a, sizeof(tile_star_a));
143
+ tonccpy(&tile_mem[0][5], tile_star_b, sizeof(tile_star_b));
144
+ REG_BG0CNT = BG_CBB(0) | BG_SBB(28) | BG_REG_32x32 | BG_4BPP | BG_PRIO(3);
145
+ {
146
+ SCR_ENTRY *map = se_mem[28];
147
+ for (int ty = 0; ty < 32; ty++)
148
+ for (int tx = 0; tx < 32; tx++)
149
+ map[ty * 32 + tx] = SE_BUILD(4 + ((ty >> 1) & 1), 0, 0, 0);
150
+ }
151
+
117
152
  oam_init(obj_buffer, 128);
118
153
 
119
154
  /* IRQ setup — required for VBlankIntrWait() to function. */
@@ -128,10 +163,12 @@ int main(void) {
128
163
  score = 0;
129
164
  spawn_timer = 0;
130
165
 
131
- /* TTE on BG0 at screen-block 31, char-block 0. Renders into VRAM
132
- * tile map — no libsysbase / iprintf dependency. */
133
- tte_init_chr4c_default(0, BG_CBB(0) | BG_SBB(31));
134
- REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_OBJ | DCNT_OBJ_1D;
166
+ /* TTE on BG1 at screen-block 30, char-block 2 (BG0 holds the
167
+ * starfield). Renders into VRAM tile map — no libsysbase / iprintf
168
+ * dependency. BG1 priority 0 score/hint text in front. */
169
+ tte_init_chr4c_default(1, BG_CBB(2) | BG_SBB(30));
170
+ REG_BG1CNT |= BG_PRIO(0);
171
+ REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_BG1 | DCNT_OBJ | DCNT_OBJ_1D;
135
172
  tte_write("#{P:8,8}SCORE 00000");
136
173
  tte_write("#{P:8,144}D-PAD MOVE A FIRE");
137
174