romdevtools 0.16.0 → 0.21.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 (110) hide show
  1. package/AGENTS.md +60 -12
  2. package/CHANGELOG.md +258 -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/platformer.c +43 -4
  10. package/examples/atari7800/templates/puzzle.c +39 -4
  11. package/examples/atari7800/templates/racing.c +39 -4
  12. package/examples/atari7800/templates/shmup.c +40 -2
  13. package/examples/atari7800/templates/sports.c +36 -5
  14. package/examples/c64/templates/platformer.c +19 -5
  15. package/examples/c64/templates/puzzle.c +32 -2
  16. package/examples/c64/templates/shmup.c +28 -2
  17. package/examples/c64/templates/sports.c +30 -2
  18. package/examples/gb/templates/default.c +110 -16
  19. package/examples/gb/templates/platformer.c +25 -4
  20. package/examples/gb/templates/puzzle.c +32 -2
  21. package/examples/gb/templates/racing.c +72 -8
  22. package/examples/gb/templates/shmup.c +38 -1
  23. package/examples/gb/templates/sports.c +48 -1
  24. package/examples/gba/templates/gba_hello.c +29 -11
  25. package/examples/gba/templates/puzzle.c +15 -3
  26. package/examples/gba/templates/racing.c +65 -3
  27. package/examples/gba/templates/shmup.c +41 -4
  28. package/examples/gba/templates/sports.c +36 -2
  29. package/examples/gba/templates/tonc_hello.c +41 -5
  30. package/examples/gbc/templates/default.c +103 -26
  31. package/examples/gbc/templates/platformer.c +25 -4
  32. package/examples/gbc/templates/puzzle.c +32 -2
  33. package/examples/gbc/templates/racing.c +85 -19
  34. package/examples/gbc/templates/shmup.c +34 -1
  35. package/examples/gbc/templates/sports.c +45 -1
  36. package/examples/genesis/templates/puzzle.c +37 -3
  37. package/examples/genesis/templates/racing.c +44 -11
  38. package/examples/genesis/templates/sgdk_hello.c +34 -1
  39. package/examples/genesis/templates/shmup.c +31 -1
  40. package/examples/gg/templates/default.c +56 -18
  41. package/examples/gg/templates/platformer.c +18 -12
  42. package/examples/gg/templates/puzzle.c +38 -7
  43. package/examples/gg/templates/racing.c +51 -5
  44. package/examples/gg/templates/shmup.c +47 -3
  45. package/examples/gg/templates/sports.c +46 -3
  46. package/examples/lynx/templates/default.c +39 -8
  47. package/examples/lynx/templates/puzzle.c +28 -1
  48. package/examples/lynx/templates/racing.c +34 -7
  49. package/examples/lynx/templates/shmup.c +42 -3
  50. package/examples/lynx/templates/sports.c +29 -2
  51. package/examples/msx/platformer/main.c +213 -0
  52. package/examples/msx/puzzle/main.c +250 -0
  53. package/examples/msx/racing/main.c +249 -0
  54. package/examples/msx/shmup/main.c +288 -0
  55. package/examples/msx/sports/main.c +182 -0
  56. package/examples/nes/templates/default.c +67 -19
  57. package/examples/nes/templates/platformer.c +65 -6
  58. package/examples/nes/templates/puzzle.c +67 -6
  59. package/examples/nes/templates/racing.c +45 -13
  60. package/examples/nes/templates/shmup.c +51 -2
  61. package/examples/nes/templates/sports.c +51 -6
  62. package/examples/pce/platformer/main.c +283 -0
  63. package/examples/pce/puzzle/main.c +304 -0
  64. package/examples/pce/racing/main.c +304 -0
  65. package/examples/pce/shmup/main.c +346 -0
  66. package/examples/pce/sports/main.c +254 -0
  67. package/examples/sms/main.c +35 -6
  68. package/examples/sms/templates/puzzle.c +34 -5
  69. package/examples/sms/templates/racing.c +39 -2
  70. package/examples/sms/templates/shmup.c +41 -2
  71. package/examples/sms/templates/sports.c +43 -2
  72. package/examples/snes/templates/default.c +50 -28
  73. package/examples/snes/templates/platformer-data.asm +22 -0
  74. package/examples/snes/templates/platformer.c +16 -1
  75. package/examples/snes/templates/puzzle-data.asm +22 -0
  76. package/examples/snes/templates/puzzle.c +17 -1
  77. package/examples/snes/templates/racing-data.asm +22 -0
  78. package/examples/snes/templates/racing.c +17 -1
  79. package/examples/snes/templates/shmup-data.asm +22 -0
  80. package/examples/snes/templates/shmup.c +20 -1
  81. package/examples/snes/templates/sports-data.asm +22 -0
  82. package/examples/snes/templates/sports.c +16 -1
  83. package/package.json +1 -1
  84. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  85. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  86. package/src/host/LibretroHost.js +122 -1
  87. package/src/host/callbacks.js +9 -1
  88. package/src/host/types.js +15 -8
  89. package/src/http/tool-registry.js +26 -1
  90. package/src/mcp/tools/cart-parts.js +75 -3
  91. package/src/mcp/tools/disasm-rebuild.js +507 -0
  92. package/src/mcp/tools/disasm.js +95 -6
  93. package/src/mcp/tools/frame.js +168 -3
  94. package/src/mcp/tools/lifecycle.js +4 -2
  95. package/src/mcp/tools/project.js +54 -9
  96. package/src/mcp/tools/state.js +201 -14
  97. package/src/mcp/tools/toolchain.js +76 -3
  98. package/src/mcp/tools/watch-memory.js +125 -14
  99. package/src/platforms/c64/MENTAL_MODEL.md +45 -1
  100. package/src/platforms/c64/d64.js +281 -0
  101. package/src/platforms/gb/MENTAL_MODEL.md +10 -0
  102. package/src/platforms/msx/MENTAL_MODEL.md +10 -6
  103. package/src/platforms/nes/MENTAL_MODEL.md +63 -2
  104. package/src/platforms/pce/MENTAL_MODEL.md +9 -4
  105. package/src/platforms/pce/lib/c/pce_video.c +1 -1
  106. package/src/rom-id/identifier.js +15 -0
  107. package/src/toolchains/cc65/ines.js +145 -0
  108. package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
  109. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
  110. package/src/toolchains/common/reassemble.js +10 -2
@@ -1,19 +1,23 @@
1
1
  /* ── default.c — minimal Game Boy Color (CGB) starter ─────────────
2
2
  *
3
- * Boots the LCD, cycles a real CGB background palette via the BCPS
4
- * + BCPD palette-RAM registers. Smallest possible "ROM that does
5
- * something visible IN COLOR" use this as the starting point when
6
- * you want CGB-mode color, not DMG greyscale.
3
+ * A "hello, it works! IN COLOR" screen: a tiled background (two bands
4
+ * + a centre box) drawn with a real CGB palette, plus a sprite that
5
+ * bounces around. The very first GBC build shows recognizable content
6
+ * not a flat colour. Use this as the starting point when you're not
7
+ * yet sure what you want to build; edit from here.
7
8
  *
8
9
  * GBC-specific notes:
9
10
  * - CGB uses BCPS/BCPD ($FF68/$FF69) to write into 64 bytes of
10
- * palette RAM (8 BG palettes × 4 colors × 2 bytes each, BGR555
11
- * little-endian). DMG-only BGP ($FF47) does nothing in CGB mode.
11
+ * palette RAM (8 BG palettes × 4 colours × 2 bytes each, BGR555
12
+ * little-endian) and OCPS/OCPD for the sprite palettes. The DMG-only
13
+ * BGP/OBP0/OBP1 ($FF47-$49) registers do nothing in CGB mode.
14
+ * - You MUST put tiles in VRAM and enable the BG (LCDC bit 0) or the
15
+ * screen stays one flat colour — the #1 GB "why is it blank" footgun.
16
+ * We upload tiles to $8000 and select LCDC_TILE_DATA_LO (unsigned
17
+ * $8000 addressing) so tile index N lives at $8000 + N*16.
12
18
  * - patchGbHeader on a .gbc file sets $0143 = $80 (CGB-aware) by
13
- * default. The corresponding `.gb` default uses BGP — don't
19
+ * default. The corresponding `.gb` default uses DMG BGP — don't
14
20
  * cross-pollinate the two trees.
15
- * - This template is intentionally DIFFERENT from examples/gb/
16
- * templates/default.c — that one demonstrates DMG palettes.
17
21
  *
18
22
  * For something more game-shaped, peek at other templates in this dir:
19
23
  * - hello_sprite — sprite + d-pad movement
@@ -25,37 +29,110 @@
25
29
  #include "gb_hardware.h"
26
30
  #include "gb_runtime.h"
27
31
 
28
- static const uint16_t palettes[4] = {
29
- 0x7FFF, /* white */
30
- 0x3DEF, /* light grey */
31
- 0x2108, /* dark grey */
32
- 0x0000, /* black */
32
+ /* Three 8×8 tiles, 2bpp (16 bytes each: row N = byte 2N low-bits, 2N+1
33
+ * high-bits). Colour index per tile pixel selects into the 4-colour
34
+ * palette below.
35
+ * tile 0 — blank (all colour 0 — reserved so OAM Y=0 doesn't glitch)
36
+ * tile 1 — solid (all colour 1 — the background fill / bands)
37
+ * tile 2 — sprite (a filled diamond in colour 3) */
38
+ static const uint8_t tiles[3 * 16] = {
39
+ /* tile 0: blank */
40
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
41
+ /* tile 1: solid colour 1 (low plane all-on, high plane all-off) */
42
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
43
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
44
+ /* tile 2: diamond in colour 3 (both planes set on the diamond pixels) */
45
+ 0x18,0x18, 0x3C,0x3C, 0x7E,0x7E, 0xFF,0xFF,
46
+ 0xFF,0xFF, 0x7E,0x7E, 0x3C,0x3C, 0x18,0x18,
33
47
  };
34
48
 
49
+ /* BG palette 0 (BGR555). Colour 0 = backdrop, colour 1 = the bands/box.
50
+ * We cycle colour 1 through these four shades each ~32 frames so you can
51
+ * SEE the CGB palette path is alive. */
52
+ static const uint16_t bg_fill_cycle[4] = {
53
+ 0x03FF, /* yellow-ish */
54
+ 0x7C00, /* blue */
55
+ 0x03E0, /* green */
56
+ 0x001F, /* red */
57
+ };
58
+
59
+ /* Sprite palette (OBJ palette 0): 0 transparent, 3 = white diamond. */
60
+ static const uint16_t obj_pal[4] = {
61
+ 0x0000, /* 0 transparent (sprite colour 0 never drawn) */
62
+ 0x7FFF, /* 1 white */
63
+ 0x03E0, /* 2 green */
64
+ 0x7FFF, /* 3 white */
65
+ };
66
+
67
+ static void set_bg_color1(uint16_t bgr555) {
68
+ /* Write BG palette 0: colour 0 = dark backdrop, colour 1 = bgr555. */
69
+ BCPS = 0x80; /* palette 0, colour 0, auto-inc */
70
+ BCPD = 0x08; BCPD = 0x21; /* colour 0 = dark grey (0x2108) */
71
+ BCPD = (uint8_t)(bgr555 & 0xFF); /* colour 1 lo */
72
+ BCPD = (uint8_t)((bgr555 >> 8) & 0xFF); /* colour 1 hi */
73
+ }
74
+
35
75
  void main(void) {
36
- uint8_t i;
76
+ uint8_t x, y;
77
+ uint8_t sx = 76, sy = 64; /* sprite screen position */
78
+ int8_t dx = 1, dy = 1; /* sprite velocity */
37
79
  uint8_t shade = 0;
38
80
  uint16_t frame = 0;
81
+ uint8_t *bg_map = BG_MAP_0; /* $9800 */
82
+ uint8_t i;
39
83
 
84
+ /* 1. LCD off (safely — lcd_init_default checks LCDC.7 first). */
40
85
  lcd_init_default();
86
+ LCDC = 0;
41
87
 
42
- /* Initial BG palette = all 4 entries cycle to the same shade. */
43
- BCPS = 0x80;
88
+ /* 2. Tiles VRAM at $8000 (unsigned addressing, see LCDC_TILE_DATA_LO). */
89
+ memcpy_vram((void *)0x8000, tiles, sizeof(tiles));
90
+
91
+ /* 3. Palettes. */
92
+ set_bg_color1(bg_fill_cycle[0]);
93
+ OCPS = 0x80;
44
94
  for (i = 0; i < 4; i++) {
45
- BCPD = (uint8_t)(palettes[shade] & 0xFF);
46
- BCPD = (uint8_t)((palettes[shade] >> 8) & 0xFF);
95
+ OCPD = (uint8_t)(obj_pal[i] & 0xFF);
96
+ OCPD = (uint8_t)((obj_pal[i] >> 8) & 0xFF);
97
+ }
98
+
99
+ /* 4. Paint the BG map (32×32; we fill the visible 20×18). Background
100
+ * is tile 0 (blank → backdrop colour); two solid bands + a centre
101
+ * box are tile 1 so the screen reads as real content. */
102
+ for (y = 0; y < 32; y++)
103
+ for (x = 0; x < 32; x++)
104
+ bg_map[y * 32 + x] = 0;
105
+ for (x = 0; x < 20; x++) {
106
+ bg_map[2 * 32 + x] = 1; /* top band (row 2) */
107
+ bg_map[15 * 32 + x] = 1; /* bottom band (row 15) */
47
108
  }
109
+ for (y = 6; y < 12; y++)
110
+ for (x = 6; x < 14; x++)
111
+ bg_map[y * 32 + x] = 1; /* centre box */
112
+
113
+ /* 5. Initial sprite. */
114
+ oam_clear();
115
+ oam_set(0, (uint8_t)(sy + 16), (uint8_t)(sx + 8), 2, 0);
116
+
117
+ /* 6. LCD on with BG + OBJ + $8000 tile addressing. */
118
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
48
119
 
49
120
  for (;;) {
50
121
  wait_vblank();
122
+ oam_dma_flush();
123
+
51
124
  frame++;
52
- if ((frame & 0x1F) == 0) { /* every 32 frames */
53
- shade = (shade + 1) & 0x03;
54
- BCPS = 0x80;
55
- for (i = 0; i < 4; i++) {
56
- BCPD = (uint8_t)(palettes[shade] & 0xFF);
57
- BCPD = (uint8_t)((palettes[shade] >> 8) & 0xFF);
58
- }
125
+ if ((frame & 0x1F) == 0) { /* every 32 frames: cycle BG colour */
126
+ shade = (uint8_t)((shade + 1) & 0x03);
127
+ set_bg_color1(bg_fill_cycle[shade]);
59
128
  }
129
+
130
+ /* Bounce the sprite around the 160×144 visible area. */
131
+ sx = (uint8_t)(sx + dx);
132
+ sy = (uint8_t)(sy + dy);
133
+ if (sx < 1 || sx > 152) dx = (int8_t)-dx;
134
+ if (sy < 1 || sy > 136) dy = (int8_t)-dy;
135
+ oam_clear();
136
+ oam_set(0, (uint8_t)(sy + 16), (uint8_t)(sx + 8), 2, 0);
60
137
  }
61
138
  }
@@ -28,9 +28,26 @@ static const uint8_t tile_platform[16] = {
28
28
  0xFF,0xFF, 0x80,0x80, 0x80,0x80, 0x80,0x80,
29
29
  0x80,0x80, 0x80,0x80, 0x80,0x80, 0xFF,0xFF,
30
30
  };
31
+ /* ── Backdrop tiles ───────────────────────────────────────────────────
32
+ * Fill the whole world so the screen is never one flat colour (the #1 GB
33
+ * "why is it blank" footgun). tile_sky is a sparse dot pattern over the
34
+ * sky; tile_ground is a textured dirt fill under the floor line. */
35
+ static const uint8_t tile_sky[16] = {
36
+ 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x20,0x20,
37
+ 0x00,0x00, 0x00,0x00, 0x02,0x02, 0x00,0x00,
38
+ };
39
+ static const uint8_t tile_ground[16] = {
40
+ 0xFF,0x00, 0xDB,0x24, 0xFF,0x00, 0x6D,0x92,
41
+ 0xFF,0x00, 0xDB,0x24, 0xFF,0x00, 0x6D,0x92,
42
+ };
43
+ #define T_BLANK 0
44
+ #define T_PLATFORM 2
45
+ #define T_SKY 3
46
+ #define T_GROUND 4
31
47
 
32
48
  static const uint16_t obj_palette[4] = { 0x7FFF, 0x001F, 0x03E0, 0x7C00 };
33
- static const uint16_t bg_palette[4] = { 0x7FFF, 0x5294, 0x294A, 0x0000 };
49
+ /* BG palette: 0 sky-blue, 1 mid, 2 dirt-dark, 3 near-black detail. */
50
+ static const uint16_t bg_palette[4] = { 0x7E10, 0x5294, 0x114A, 0x0000 };
34
51
 
35
52
  typedef struct { int16_t x, y, w, h; } Rect;
36
53
 
@@ -74,8 +91,10 @@ static void paint_platforms(void) {
74
91
  const Rect *p;
75
92
  /* k MUST be uint16_t: 32*18 = 576 > 255, so a uint8_t counter would
76
93
  * never reach the bound and this loop would spin forever (the BG map
77
- * never clears, main() never starts). Classic SDCC limited-range trap. */
78
- for (k = 0; k < 32 * 18; k++) map[k] = 0; /* blank tile slot 0 */
94
+ * never clears, main() never starts). Classic SDCC limited-range trap.
95
+ * Fill sky above the floor line (row 16 = y 128) and textured ground
96
+ * at and below it, so the whole world is a real scene, not blank. */
97
+ for (k = 0; k < 32 * 18; k++) map[k] = (k >= 16 * 32) ? T_GROUND : T_SKY;
79
98
  for (i = 0; i < N_PLATFORMS; i++) {
80
99
  p = &platforms[i];
81
100
  cx = p->x >> 3;
@@ -84,7 +103,7 @@ static void paint_platforms(void) {
84
103
  ch = (p->h + 7) >> 3;
85
104
  for (j = 0; j < cw; j++) {
86
105
  if (cx + j < 32 && cy < 32)
87
- map[cy * 32 + cx + j] = 2; /* tile slot 2 = platform */
106
+ map[cy * 32 + cx + j] = T_PLATFORM; /* platform top edge */
88
107
  }
89
108
  }
90
109
  }
@@ -111,6 +130,8 @@ void main(void) {
111
130
  upload_tile(0, tile_blank);
112
131
  upload_tile(1, tile_player);
113
132
  upload_tile(2, tile_platform);
133
+ upload_tile(T_SKY, tile_sky);
134
+ upload_tile(T_GROUND, tile_ground);
114
135
 
115
136
  OCPS = 0x80;
116
137
  for (i = 0; i < 4; i++) {
@@ -21,8 +21,22 @@
21
21
  #define T_R 1
22
22
  #define T_G 2
23
23
  #define T_B 3
24
-
25
- static const uint8_t tile_blank[16] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
24
+ #define T_WALL 4
25
+
26
+ /* tile_blank is the EMPTY-cell / backdrop tile. It is NOT all-zero: a
27
+ * subtle dither (colour 0 + faint colour 1) so the empty playfield and the
28
+ * area around the well read as a textured surface, never one flat colour
29
+ * (the #1 GB "why is it blank" footgun). Locked blocks / the active piece
30
+ * overdraw it with the R/G/B shape tiles. */
31
+ static const uint8_t tile_blank[16] = {
32
+ 0x00,0x00, 0x22,0x00, 0x00,0x00, 0x88,0x00,
33
+ 0x00,0x00, 0x22,0x00, 0x00,0x00, 0x88,0x00,
34
+ };
35
+ /* Well frame: a solid colour-2 border drawn around the play area. */
36
+ static const uint8_t tile_wall[16] = {
37
+ 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF,
38
+ 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF,
39
+ };
26
40
  /* Three distinct tile shapes (since GB BG is 2bpp, we differentiate
27
41
  * by *shape*, not colour-on-CGB). The CGB palette path could give us
28
42
  * real colours; for DMG-compatibility we use shape. */
@@ -131,6 +145,20 @@ static void upload_tile(uint8_t slot, const uint8_t *src) {
131
145
  for (i = 0; i < 16; i++) dst[i] = src[i];
132
146
  }
133
147
 
148
+ /* Draw the well frame around the 6×12 play area. Grid cells live at
149
+ * map[(row+1)*32 + (col+7)] (rows 1..12, cols 7..12), so the frame is the
150
+ * column to each side (6 and 13) and the floor row just below (row 13). */
151
+ static void draw_well(void) {
152
+ uint8_t *map = (uint8_t *)0x9800;
153
+ uint8_t r;
154
+ for (r = 1; r <= 12; r++) {
155
+ map[r * 32 + 6] = T_WALL; /* left wall */
156
+ map[r * 32 + 13] = T_WALL; /* right wall */
157
+ }
158
+ for (r = 6; r <= 13; r++)
159
+ map[13 * 32 + r] = T_WALL; /* floor */
160
+ }
161
+
134
162
  void main(void) {
135
163
  uint8_t pad, prev = 0, fall_rate, t;
136
164
  int16_t r, c;
@@ -145,6 +173,7 @@ void main(void) {
145
173
  upload_tile(T_R, tile_r);
146
174
  upload_tile(T_G, tile_g);
147
175
  upload_tile(T_B, tile_b);
176
+ upload_tile(T_WALL, tile_wall);
148
177
 
149
178
  BCPS = 0x80;
150
179
  for (i = 0; i < 4; i++) {
@@ -165,6 +194,7 @@ void main(void) {
165
194
  score = 0;
166
195
  fall_timer = 0;
167
196
  new_piece();
197
+ draw_well();
168
198
  draw_grid();
169
199
 
170
200
  LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_TILE_DATA_LO;
@@ -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
 
@@ -76,6 +118,24 @@ static void upload_tile(uint8_t slot, const uint8_t *src) {
76
118
  for (i = 0; i < 16; i++) dst[i] = src[i];
77
119
  }
78
120
 
121
+ /* Paint the road into BG map 0 ($9800). 20×18 visible cells:
122
+ * col 0 = grass shoulder (left)
123
+ * col 19 = grass shoulder (right)
124
+ * cols 1..18 = asphalt, with dashed lane lines at the two lane
125
+ * boundaries (cols 6 and 12). Dashes alternate per row. */
126
+ static void draw_road(void) {
127
+ uint8_t *bg = BG_MAP_0;
128
+ uint8_t r, c, t;
129
+ for (r = 0; r < 18; r++) {
130
+ for (c = 0; c < 20; c++) {
131
+ if (c == 0 || c == 19) t = T_GRASS;
132
+ else if (c == 6 || c == 12) t = (r & 1) ? T_LANE_A : T_LANE_B;
133
+ else t = T_ASPHALT;
134
+ bg[r * 32 + c] = t;
135
+ }
136
+ }
137
+ }
138
+
79
139
  void main(void) {
80
140
  uint8_t pad;
81
141
  uint8_t i;
@@ -84,23 +144,29 @@ void main(void) {
84
144
  lcd_init_default();
85
145
  LCDC = 0;
86
146
 
87
- upload_tile(0, tile_blank);
88
- upload_tile(1, tile_car_p1);
89
- upload_tile(2, tile_car_en);
147
+ upload_tile(T_CAR_P1, tile_car_p1);
148
+ upload_tile(T_CAR_EN, tile_car_en);
149
+ upload_tile(T_ASPHALT, tile_asphalt);
150
+ upload_tile(T_GRASS, tile_grass);
151
+ upload_tile(T_LANE_A, tile_laneA);
152
+ upload_tile(T_LANE_B, tile_laneB);
90
153
 
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
- }
154
+ /* CGB palettes. */
96
155
  BCPS = 0x80;
97
156
  for (i = 0; i < 4; i++) {
98
157
  BCPD = (uint8_t)(bg_palette[i] & 0xFF);
99
158
  BCPD = (uint8_t)((bg_palette[i] >> 8) & 0xFF);
100
159
  }
160
+ OCPS = 0x80;
161
+ for (i = 0; i < 4; i++) {
162
+ OCPD = (uint8_t)(obj_palette[i] & 0xFF);
163
+ OCPD = (uint8_t)((obj_palette[i] >> 8) & 0xFF);
164
+ }
165
+
166
+ draw_road();
101
167
 
102
168
  oam_clear();
103
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
169
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
104
170
  sound_init();
105
171
 
106
172
  reset_run();
@@ -111,13 +177,13 @@ void main(void) {
111
177
 
112
178
  /* Stage OAM — player + obstacles. */
113
179
  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);
180
+ oam_set(0, (uint8_t)(player.y + 16), (uint8_t)(player.x + 8), T_CAR_P1, 0);
115
181
  for (i = 0; i < MAX_OBSTACLES; i++) {
116
182
  if (obstacles[i].alive) {
117
183
  oam_set((uint8_t)(1 + i),
118
184
  (uint8_t)(obstacles[i].y + 16),
119
185
  (uint8_t)(obstacles[i].x + 8),
120
- 2, 0);
186
+ T_CAR_EN, 0);
121
187
  }
122
188
  }
123
189
  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,
@@ -94,6 +112,17 @@ static void upload_tile(uint8_t slot, const uint8_t *src) {
94
112
  for (i = 0; i < 16; i++) dst[i] = src[i];
95
113
  }
96
114
 
115
+ /* Paint a starfield into BG map 0 ($9800): fill the visible 20×18 with the
116
+ * dithered space tile, then scatter bright stars on a fixed pseudo-pattern
117
+ * so the field reads as deep space rather than a flat colour. */
118
+ static void draw_starfield(void) {
119
+ uint8_t *bg = BG_MAP_0;
120
+ uint8_t r, c;
121
+ for (r = 0; r < 18; r++)
122
+ for (c = 0; c < 20; c++)
123
+ bg[r * 32 + c] = ((r * 7 + c * 5) % 11 == 0) ? T_STAR : T_SPACE;
124
+ }
125
+
97
126
  void main(void) {
98
127
  uint8_t pad, prev = 0;
99
128
  uint8_t i, j;
@@ -105,6 +134,8 @@ void main(void) {
105
134
  upload_tile(1, tile_ship);
106
135
  upload_tile(2, tile_bullet);
107
136
  upload_tile(3, tile_enemy);
137
+ upload_tile(T_SPACE, tile_space);
138
+ upload_tile(T_STAR, tile_star);
108
139
 
109
140
  /* Sprite palette 0 — uploaded to OCPS/OCPD (CGB-only registers). */
110
141
  OCPS = 0x80;
@@ -120,6 +151,8 @@ void main(void) {
120
151
  BCPD = (uint8_t)((bg_palette[i] >> 8) & 0xFF);
121
152
  }
122
153
 
154
+ draw_starfield();
155
+
123
156
  player.x = 76; player.y = 130; player.alive = 1;
124
157
  for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
125
158
  for (i = 0; i < MAX_ENEMIES; i++) enemies[i].alive = 0;
@@ -127,7 +160,7 @@ void main(void) {
127
160
  spawn_timer = 0;
128
161
 
129
162
  oam_clear();
130
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
163
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
131
164
  sound_init();
132
165
 
133
166
  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
 
@@ -58,6 +83,21 @@ static void upload_tile(uint8_t slot, const uint8_t *src) {
58
83
  for (i = 0; i < 16; i++) dst[i] = src[i];
59
84
  }
60
85
 
86
+ /* Paint the Pong court into BG map 0 ($9800): dithered turf everywhere,
87
+ * solid top/bottom rails, and a dashed net down the centre column. */
88
+ static void draw_court(void) {
89
+ uint8_t *bg = BG_MAP_0;
90
+ uint8_t r, c, t;
91
+ for (r = 0; r < 18; r++) {
92
+ for (c = 0; c < 20; c++) {
93
+ if (r == 0 || r == 17) t = T_WALL; /* top / bottom rail */
94
+ else if (c == 9 || c == 10) t = (r & 1) ? T_NET : T_COURT; /* net dashes */
95
+ else t = T_COURT;
96
+ bg[r * 32 + c] = t;
97
+ }
98
+ }
99
+ }
100
+
61
101
  void main(void) {
62
102
  uint8_t pad;
63
103
  uint8_t i;
@@ -68,6 +108,9 @@ void main(void) {
68
108
 
69
109
  upload_tile(0, tile_blank);
70
110
  upload_tile(1, tile_solid);
111
+ upload_tile(T_COURT, tile_court);
112
+ upload_tile(T_NET, tile_net);
113
+ upload_tile(T_WALL, tile_wall);
71
114
 
72
115
  OCPS = 0x80;
73
116
  for (i = 0; i < 4; i++) {
@@ -80,8 +123,9 @@ void main(void) {
80
123
  BCPD = (uint8_t)((bg_palette[i] >> 8) & 0xFF);
81
124
  }
82
125
 
126
+ draw_court();
83
127
  oam_clear();
84
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
128
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
85
129
  sound_init();
86
130
 
87
131
  reset_match();
@@ -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++)