romdevtools 0.15.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 (117) hide show
  1. package/AGENTS.md +61 -13
  2. package/CHANGELOG.md +289 -0
  3. package/README.md +1 -1
  4. package/examples/README.md +2 -0
  5. package/examples/atari2600/templates/platformer.asm +460 -0
  6. package/examples/atari2600/templates/racing.asm +463 -0
  7. package/examples/atari2600/templates/shmup.asm +386 -0
  8. package/examples/atari2600/templates/sports.asm +362 -0
  9. package/examples/atari7800/templates/default.c +49 -5
  10. package/examples/atari7800/templates/platformer.c +43 -4
  11. package/examples/atari7800/templates/puzzle.c +39 -4
  12. package/examples/atari7800/templates/racing.c +39 -4
  13. package/examples/atari7800/templates/shmup.c +40 -2
  14. package/examples/atari7800/templates/sports.c +36 -5
  15. package/examples/c64/templates/platformer.c +19 -5
  16. package/examples/c64/templates/puzzle.c +32 -2
  17. package/examples/c64/templates/shmup.c +28 -2
  18. package/examples/c64/templates/sports.c +30 -2
  19. package/examples/gb/templates/default.c +110 -16
  20. package/examples/gb/templates/platformer.c +25 -4
  21. package/examples/gb/templates/puzzle.c +32 -2
  22. package/examples/gb/templates/racing.c +72 -8
  23. package/examples/gb/templates/shmup.c +38 -1
  24. package/examples/gb/templates/sports.c +48 -1
  25. package/examples/gba/templates/gba_hello.c +29 -11
  26. package/examples/gba/templates/puzzle.c +15 -3
  27. package/examples/gba/templates/racing.c +65 -3
  28. package/examples/gba/templates/shmup.c +41 -4
  29. package/examples/gba/templates/sports.c +36 -2
  30. package/examples/gba/templates/tonc_hello.c +41 -5
  31. package/examples/gbc/templates/default.c +103 -26
  32. package/examples/gbc/templates/platformer.c +25 -4
  33. package/examples/gbc/templates/puzzle.c +32 -2
  34. package/examples/gbc/templates/racing.c +85 -19
  35. package/examples/gbc/templates/shmup.c +34 -1
  36. package/examples/gbc/templates/sports.c +45 -1
  37. package/examples/genesis/templates/puzzle.c +37 -3
  38. package/examples/genesis/templates/racing.c +44 -11
  39. package/examples/genesis/templates/sgdk_hello.c +34 -1
  40. package/examples/genesis/templates/shmup.c +31 -1
  41. package/examples/gg/templates/default.c +56 -18
  42. package/examples/gg/templates/platformer.c +18 -12
  43. package/examples/gg/templates/puzzle.c +38 -7
  44. package/examples/gg/templates/racing.c +51 -5
  45. package/examples/gg/templates/shmup.c +47 -3
  46. package/examples/gg/templates/sports.c +46 -3
  47. package/examples/lynx/templates/default.c +39 -8
  48. package/examples/lynx/templates/puzzle.c +28 -1
  49. package/examples/lynx/templates/racing.c +34 -7
  50. package/examples/lynx/templates/shmup.c +42 -3
  51. package/examples/lynx/templates/sports.c +29 -2
  52. package/examples/msx/platformer/main.c +213 -0
  53. package/examples/msx/puzzle/main.c +250 -0
  54. package/examples/msx/racing/main.c +249 -0
  55. package/examples/msx/shmup/main.c +288 -0
  56. package/examples/msx/sports/main.c +182 -0
  57. package/examples/nes/templates/default.c +67 -19
  58. package/examples/nes/templates/platformer.c +65 -6
  59. package/examples/nes/templates/puzzle.c +67 -6
  60. package/examples/nes/templates/racing.c +45 -13
  61. package/examples/nes/templates/shmup.c +51 -2
  62. package/examples/nes/templates/sports.c +51 -6
  63. package/examples/pce/platformer/main.c +283 -0
  64. package/examples/pce/puzzle/main.c +304 -0
  65. package/examples/pce/racing/main.c +304 -0
  66. package/examples/pce/shmup/main.c +346 -0
  67. package/examples/pce/sports/main.c +254 -0
  68. package/examples/sms/main.c +35 -6
  69. package/examples/sms/templates/puzzle.c +34 -5
  70. package/examples/sms/templates/racing.c +39 -2
  71. package/examples/sms/templates/shmup.c +41 -2
  72. package/examples/sms/templates/sports.c +43 -2
  73. package/examples/snes/templates/default.c +50 -28
  74. package/examples/snes/templates/platformer-data.asm +22 -0
  75. package/examples/snes/templates/platformer.c +16 -1
  76. package/examples/snes/templates/puzzle-data.asm +22 -0
  77. package/examples/snes/templates/puzzle.c +17 -1
  78. package/examples/snes/templates/racing-data.asm +22 -0
  79. package/examples/snes/templates/racing.c +17 -1
  80. package/examples/snes/templates/shmup-data.asm +22 -0
  81. package/examples/snes/templates/shmup.c +20 -1
  82. package/examples/snes/templates/sports-data.asm +22 -0
  83. package/examples/snes/templates/sports.c +16 -1
  84. package/package.json +1 -1
  85. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  86. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  87. package/src/host/LibretroHost.js +122 -1
  88. package/src/host/callbacks.js +9 -1
  89. package/src/host/types.js +15 -8
  90. package/src/http/skill-doc.js +1 -1
  91. package/src/http/tool-registry.js +27 -2
  92. package/src/mcp/tools/cart-parts.js +75 -3
  93. package/src/mcp/tools/disasm-rebuild.js +507 -0
  94. package/src/mcp/tools/disasm.js +95 -6
  95. package/src/mcp/tools/frame.js +168 -3
  96. package/src/mcp/tools/index.js +4 -4
  97. package/src/mcp/tools/lifecycle.js +4 -2
  98. package/src/mcp/tools/project.js +54 -9
  99. package/src/mcp/tools/state.js +201 -14
  100. package/src/mcp/tools/toolchain.js +89 -4
  101. package/src/mcp/tools/watch-memory.js +125 -14
  102. package/src/platforms/c64/MENTAL_MODEL.md +45 -1
  103. package/src/platforms/c64/d64.js +281 -0
  104. package/src/platforms/gb/MENTAL_MODEL.md +10 -0
  105. package/src/platforms/msx/MENTAL_MODEL.md +10 -6
  106. package/src/platforms/nes/MENTAL_MODEL.md +63 -2
  107. package/src/platforms/pce/MENTAL_MODEL.md +9 -4
  108. package/src/platforms/pce/lib/c/pce_video.c +1 -1
  109. package/src/rom-id/identifier.js +15 -0
  110. package/src/toolchains/cc65/cc65.js +8 -1
  111. package/src/toolchains/cc65/ines.js +145 -0
  112. package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
  113. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
  114. package/src/toolchains/common/reassemble.js +10 -2
  115. package/src/toolchains/gba-c/gba-c.js +6 -1
  116. package/src/toolchains/genesis-c/genesis-c.js +10 -2
  117. package/src/toolchains/parse-errors.js +67 -5
@@ -29,6 +29,8 @@
29
29
  #define T_CAR_EN (TILE_USER_INDEX + 1)
30
30
  #define T_LANE (TILE_USER_INDEX + 2) /* dashed lane divider (BG_B) */
31
31
  #define T_EDGE (TILE_USER_INDEX + 3) /* solid road edge (BG_B) */
32
+ #define T_GRASS (TILE_USER_INDEX + 4) /* roadside backdrop (BG_A) */
33
+ #define T_ASPHALT (TILE_USER_INDEX + 5) /* road surface backdrop(BG_A) */
32
34
 
33
35
  static const u32 tile_car_p1[8] = {
34
36
  0x01111110, 0x11111111, 0x12222221, 0x11111111,
@@ -51,6 +53,18 @@ static const u32 tile_edge[8] = {
51
53
  0x00000022, 0x00000022, 0x00000022, 0x00000022,
52
54
  0x00000022, 0x00000022, 0x00000022, 0x00000022,
53
55
  };
56
+ /* Roadside grass (colour 5) with a couple of darker tufts (colour 6) so
57
+ * it isn't a flat fill — tiled down both shoulders on BG_A. */
58
+ static const u32 tile_grass[8] = {
59
+ 0x55555555, 0x55556555, 0x55555555, 0x65555555,
60
+ 0x55555555, 0x55555565, 0x55555555, 0x55655555,
61
+ };
62
+ /* Road asphalt (colour 6) with faint speckle (colour 5) — tiled across
63
+ * the driving surface on BG_A, behind the BG_B lane markings + cars. */
64
+ static const u32 tile_asphalt[8] = {
65
+ 0x66666666, 0x66666566, 0x66666666, 0x56666666,
66
+ 0x66666666, 0x66666656, 0x66666666, 0x66566666,
67
+ };
54
68
 
55
69
  /* The road lives on BG_B (8×8 cells). Two dashed dividers sit between the
56
70
  * three lanes; solid edges frame the outermost lanes. */
@@ -61,15 +75,28 @@ static const u32 tile_edge[8] = {
61
75
  #define ROAD_EDGE_L ((LANE_LEFT_X - 12) / 8)
62
76
  #define ROAD_EDGE_R ((LANE_RIGHT_X + 12) / 8)
63
77
 
78
+ /* Far plane (BG_A): grass shoulders + asphalt driving surface, tiled
79
+ * across the whole 40x28 screen so the road no longer floats on black.
80
+ * Drawn at low priority; the BG_B markings + sprite cars sit on top. */
81
+ static void draw_backdrop(void) {
82
+ s16 r, c;
83
+ for (r = 0; r < 28; r++)
84
+ for (c = 0; c < 40; c++) {
85
+ u16 t = (c >= ROAD_EDGE_L && c <= ROAD_EDGE_R) ? T_ASPHALT : T_GRASS;
86
+ VDP_setTileMapXY(BG_A, TILE_ATTR_FULL(PAL0, 0, 0, 0, t), c, r);
87
+ }
88
+ }
89
+
64
90
  static void draw_road(void) {
65
91
  s16 r;
66
92
  for (r = ROAD_TOP_ROW; r <= ROAD_BOT_ROW; r++) {
67
- /* Left edge (stripe on its right), right edge (hflipped). */
68
- VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_EDGE), ROAD_EDGE_L, r);
69
- VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 1, T_EDGE), ROAD_EDGE_R, r);
93
+ /* Left edge (stripe on its right), right edge (hflipped). HIGH
94
+ * priority so the markings sit above the BG_A asphalt backdrop. */
95
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 1, 0, 0, T_EDGE), ROAD_EDGE_L, r);
96
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 1, 0, 1, T_EDGE), ROAD_EDGE_R, r);
70
97
  /* Two dashed lane dividers. */
71
- VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_LANE), LANE_DIV1_COL, r);
72
- VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 0, 0, 0, T_LANE), LANE_DIV2_COL, r);
98
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 1, 0, 0, T_LANE), LANE_DIV1_COL, r);
99
+ VDP_setTileMapXY(BG_B, TILE_ATTR_FULL(PAL0, 1, 0, 0, T_LANE), LANE_DIV2_COL, r);
73
100
  }
74
101
  }
75
102
 
@@ -125,18 +152,24 @@ static void render_score(void) {
125
152
  int main(bool hard) {
126
153
  (void)hard;
127
154
 
128
- /* PAL0 = player (white + blue trim). PAL1 = enemy (red + dark). */
155
+ /* PAL0 = player (white + blue trim) + road backdrop. PAL1 = enemy. */
129
156
  PAL_setColor(0 + 1, 0x0EEE); /* white body */
130
157
  PAL_setColor(0 + 2, 0x0AAA); /* roof grey */
158
+ PAL_setColor(0 + 5, 0x0260); /* roadside grass */
159
+ PAL_setColor(0 + 6, 0x0222); /* asphalt grey */
131
160
  PAL_setColor(16 + 3, 0x000E); /* enemy red */
132
161
  PAL_setColor(16 + 4, 0x0666); /* enemy roof */
133
162
 
134
- VDP_loadTileData(tile_car_p1, T_CAR_P1, 1, DMA);
135
- VDP_loadTileData(tile_car_enemy, T_CAR_EN, 1, DMA);
136
- VDP_loadTileData(tile_lane, T_LANE, 1, DMA);
137
- VDP_loadTileData(tile_edge, T_EDGE, 1, DMA);
163
+ VDP_loadTileData(tile_car_p1, T_CAR_P1, 1, DMA);
164
+ VDP_loadTileData(tile_car_enemy, T_CAR_EN, 1, DMA);
165
+ VDP_loadTileData(tile_lane, T_LANE, 1, DMA);
166
+ VDP_loadTileData(tile_edge, T_EDGE, 1, DMA);
167
+ VDP_loadTileData(tile_grass, T_GRASS, 1, DMA);
168
+ VDP_loadTileData(tile_asphalt, T_ASPHALT, 1, DMA);
138
169
 
139
- /* Draw the static road (edges + dashed lane dividers) once on BG_B. */
170
+ /* Draw the grass+asphalt backdrop (BG_A) then the road markings on
171
+ * BG_B over it. */
172
+ draw_backdrop();
140
173
  draw_road();
141
174
 
142
175
  VDP_drawText("SCORE", 28, 2);
@@ -21,6 +21,23 @@
21
21
 
22
22
  #include <genesis.h>
23
23
 
24
+ /* Two simple 8x8 background tiles for the far plane (BG_B). Tiling them
25
+ * in a checkerboard fills the whole screen so it doesn't read as a blank
26
+ * black backdrop. T_BG_A is a framed block (colour 1 border, colour 2
27
+ * fill); T_BG_B is the same block in colour 3 — alternating them gives a
28
+ * two-tone grid with a clear majority of non-backdrop pixels. */
29
+ #define T_BG_A (TILE_USER_INDEX + 0)
30
+ #define T_BG_B (TILE_USER_INDEX + 1)
31
+
32
+ static const u32 tile_bg_a[8] = {
33
+ 0x11111111, 0x12222221, 0x12222221, 0x12222221,
34
+ 0x12222221, 0x12222221, 0x12222221, 0x11111111,
35
+ };
36
+ static const u32 tile_bg_b[8] = {
37
+ 0x11111111, 0x13333331, 0x13333331, 0x13333331,
38
+ 0x13333331, 0x13333331, 0x13333331, 0x11111111,
39
+ };
40
+
24
41
  int main(bool hard) {
25
42
  /* Boot info: hard == TRUE on power-on, FALSE on soft reset (we
26
43
  * could re-init differently in each case; this minimum starter
@@ -28,7 +45,23 @@ int main(bool hard) {
28
45
  (void)hard;
29
46
 
30
47
  /* SGDK initialized the VDP + default palette in sega.s + libmd
31
- * before main() ran. Just draw text. */
48
+ * before main() ran. We add a tiled background so the screen isn't a
49
+ * flat black backdrop, then draw the text on top. */
50
+ PAL_setColor(16 + 1, 0x0444); /* dark grey grid border */
51
+ PAL_setColor(16 + 2, 0x0A22); /* deep blue fill */
52
+ PAL_setColor(16 + 3, 0x022A); /* deep red fill (2nd block) */
53
+
54
+ VDP_loadTileData(tile_bg_a, T_BG_A, 1, DMA);
55
+ VDP_loadTileData(tile_bg_b, T_BG_B, 1, DMA);
56
+
57
+ /* Fill the far plane (BG_B) with a checkerboard of the two blocks so
58
+ * a clear majority of the visible 40x28 cells are non-backdrop. */
59
+ for (u16 cy = 0; cy < 28; cy++)
60
+ for (u16 cx = 0; cx < 40; cx++)
61
+ VDP_setTileMapXY(BG_B,
62
+ TILE_ATTR_FULL(PAL1, 0, 0, 0, ((cx ^ cy) & 1) ? T_BG_A : T_BG_B),
63
+ cx, cy);
64
+
32
65
  VDP_drawText("HELLO SEGA GENESIS", 10, 12);
33
66
  VDP_drawText("BUILT WITH ROM-DEV-MCP", 8, 14);
34
67
 
@@ -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;
@@ -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 */
@@ -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);
@@ -40,17 +41,38 @@ extern void gg_sat_upload(void);
40
41
 
41
42
  /* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
42
43
  * 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). */
44
+ * (entries 16-31) reading garbage = invisible sprites. BG colour 1 = grass
45
+ * green, BG colour 2 = road grey. Sprite colour 1 = entry 17 (white),
46
+ * colour 2 = entry 18 (red). */
45
47
  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,
48
+ /* BG 0-15: 0 = dark navy backdrop, 1 = grass green, 2 = road grey */
49
+ 0x20,0x02, 0x60,0x00, 0x66,0x06, 0,0, 0,0, 0,0, 0,0, 0,0,
48
50
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
49
51
  /* SPRITE 16-31: 16=transparent, 17=white, 18=red */
50
52
  0,0, 0xFF,0x0F, 0x0F,0x00, 0,0, 0,0, 0,0, 0,0, 0,0,
51
53
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
52
54
  };
53
55
 
56
+ /* Three BG tiles for the track, loaded into the BG tile bank at $0000:
57
+ * tile 0 = blank (all zeros → shows the backdrop colour)
58
+ * tile 1 = solid grass (colour 1)
59
+ * tile 2 = solid road (colour 2) */
60
+ static const uint8_t bg_tiles[96] = {
61
+ /* BG tile 0 = blank */
62
+ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
63
+ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
64
+ /* BG tile 1 = grass (colour 1 → plane 0 set) */
65
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
66
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
67
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
68
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
69
+ /* BG tile 2 = road (colour 2 → plane 1 set) */
70
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
71
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
72
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
73
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
74
+ };
75
+
54
76
  /* Two sprite tiles — player (colour 1) + enemy (colour 2). */
55
77
  static const uint8_t tiles[64] = {
56
78
  /* Tile 0 = player car (colour 1 → plane 0 set) */
@@ -65,6 +87,28 @@ static const uint8_t tiles[64] = {
65
87
  0x00,0x7E,0x00,0x00, 0x00,0x66,0x00,0x00,
66
88
  };
67
89
 
90
+ /* Paint the visible viewport: grey road down the centre lanes, green
91
+ * grass on the shoulders. Visible name-table region is cols 6..25,
92
+ * rows 3..20 (the centered 160×144). BG tile bank is $0000. */
93
+ #define VIS_COL_MIN 6
94
+ #define VIS_COL_MAX 25
95
+ #define VIS_ROW_MIN 3
96
+ #define VIS_ROW_MAX 20
97
+ static void draw_track(void) {
98
+ uint8_t row, col;
99
+ /* Blank the whole 32×28 name table to backdrop (tile 0). */
100
+ for (row = 0; row < 28; row++)
101
+ for (col = 0; col < 32; col++) gg_set_tilemap_cell(row, col, 0, 0);
102
+ /* Paint the visible viewport: road (tile 2) down the central lanes,
103
+ * grass (tile 1) on the shoulders. */
104
+ for (row = VIS_ROW_MIN; row <= VIS_ROW_MAX; row++) {
105
+ for (col = VIS_COL_MIN; col <= VIS_COL_MAX; col++) {
106
+ uint8_t road = (col >= VIS_COL_MIN + 4 && col <= VIS_COL_MAX - 4);
107
+ gg_set_tilemap_cell(row, col, road ? 2 : 1, 0);
108
+ }
109
+ }
110
+ }
111
+
68
112
  typedef struct { uint8_t x, y, alive; } Car;
69
113
 
70
114
  static Car player;
@@ -110,7 +154,9 @@ void main(void) {
110
154
  uint8_t i;
111
155
  gg_vdp_init();
112
156
  gg_load_palette(palette);
113
- gg_load_tiles(0x2000, tiles, 64);
157
+ gg_load_tiles(0x0000, bg_tiles, 96); /* BG tiles → BG bank $0000 */
158
+ gg_load_tiles(0x2000, tiles, 64); /* sprite tiles → sprite bank $2000 */
159
+ draw_track();
114
160
  gg_sprite_init();
115
161
  sfx_init();
116
162
  gg_vdp_display_on();
@@ -15,6 +15,7 @@ extern void gg_vdp_display_on(void);
15
15
  extern void gg_vdp_set_addr(uint16_t addr, uint8_t prefix);
16
16
  extern void gg_load_palette(const uint8_t *palette);
17
17
  extern void gg_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
18
+ extern void gg_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
18
19
  extern void gg_vblank_wait(void);
19
20
  extern uint8_t gg_joypad_read(void);
20
21
  extern void gg_sprite_init(void);
@@ -52,14 +53,55 @@ extern void gg_sat_upload(void);
52
53
  * array left the sprite palette (entries 16-31) reading past the array = garbage
53
54
  * = invisible sprites.) */
54
55
  static const uint8_t palette[64] = {
55
- /* BG 0-15: entry 0 = dark navy backdrop */
56
- 0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
56
+ /* BG 0-15: 0 = backdrop, 1 = deep space blue, 2 = lighter space blue,
57
+ * 3 = star white. */
58
+ 0x20,0x02, 0x30,0x04, 0x80,0x08, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0,
57
59
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
58
60
  /* SPRITE 16-31: 16=transparent, 17=white, 18=yellow, 19=red */
59
61
  0,0, 0xFF,0x0F, 0xFF,0x00, 0x0F,0x00, 0,0, 0,0, 0,0, 0,0,
60
62
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
61
63
  };
62
64
 
65
+ /* Three BG tiles for the starfield, loaded into the BG tile bank at
66
+ * $0000:
67
+ * tile 0 = deep space (solid colour 1)
68
+ * tile 1 = lighter space band (solid colour 2)
69
+ * tile 2 = space with a star (mostly colour 1, one colour-3 pixel) */
70
+ static const uint8_t bg_tiles[96] = {
71
+ /* tile 0 = deep space (colour 1 → plane 0 set) */
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,
75
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
76
+ /* tile 1 = lighter band (colour 2 → plane 1 set) */
77
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
78
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
79
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
80
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
81
+ /* tile 2 = deep space + a star: row 3 col 3 = colour 3 (planes 0+1),
82
+ * everything else colour 1 (plane 0). */
83
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
84
+ 0xFF,0x00,0x00,0x00, 0xFF,0x10,0x00,0x00,
85
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
86
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
87
+ };
88
+
89
+ /* Paint the visible viewport with a banded starfield so the screen is
90
+ * clearly space, not a flat backdrop. Visible name-table region is cols
91
+ * 6..25, rows 3..20. BG tile bank is $0000. */
92
+ static void draw_starfield(void) {
93
+ uint8_t row, col;
94
+ for (row = 0; row < 28; row++)
95
+ for (col = 0; col < 32; col++) gg_set_tilemap_cell(row, col, 0, 0);
96
+ for (row = 3; row <= 20; row++) {
97
+ for (col = 6; col <= 25; col++) {
98
+ uint8_t t = (row & 2) ? 1 : 0; /* alternating depth bands */
99
+ if (((row * 7 + col * 5) & 7) == 0) t = 2; /* sparse stars */
100
+ gg_set_tilemap_cell(row, col, t, 0);
101
+ }
102
+ }
103
+ }
104
+
63
105
  static const uint8_t sprite_tiles[32 * 3] = {
64
106
  /* T_SHIP — diamond using colour 1 (white) */
65
107
  0x18,0x00,0x00,0x00, 0x3C,0x00,0x00,0x00,
@@ -121,7 +163,9 @@ void main(void) {
121
163
 
122
164
  gg_vdp_init();
123
165
  gg_load_palette(palette);
124
- gg_load_tiles(0x2000, sprite_tiles, 32 * 3);
166
+ gg_load_tiles(0x0000, bg_tiles, 96); /* BG tiles → BG bank $0000 */
167
+ gg_load_tiles(0x2000, sprite_tiles, 32 * 3); /* sprite tiles → $2000 */
168
+ draw_starfield();
125
169
 
126
170
  /* Start the ship centered, near the bottom of the VISIBLE region. */
127
171
  player.x = (uint8_t)(VIS_X0 + VIS_W / 2 - 4); player.y = (uint8_t)(VIS_Y1 - 16); player.alive = 1;