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
@@ -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;
@@ -75,6 +113,24 @@ static void upload_tile(uint8_t slot, const uint8_t *src) {
75
113
  for (i = 0; i < 16; i++) dst[i] = src[i];
76
114
  }
77
115
 
116
+ /* Paint the road into BG map 0 ($9800). 20×18 visible cells:
117
+ * col 0 = grass shoulder (left)
118
+ * col 19 = grass shoulder (right)
119
+ * cols 1..18 = asphalt, with dashed lane lines at the two lane
120
+ * boundaries (cols 6 and 12). Dashes alternate per row. */
121
+ static void draw_road(void) {
122
+ uint8_t *bg = BG_MAP_0;
123
+ uint8_t r, c, t;
124
+ for (r = 0; r < 18; r++) {
125
+ for (c = 0; c < 20; c++) {
126
+ if (c == 0 || c == 19) t = T_GRASS;
127
+ else if (c == 6 || c == 12) t = (r & 1) ? T_LANE_A : T_LANE_B;
128
+ else t = T_ASPHALT;
129
+ bg[r * 32 + c] = t;
130
+ }
131
+ }
132
+ }
133
+
78
134
  void main(void) {
79
135
  uint8_t pad;
80
136
  uint8_t i;
@@ -83,18 +139,26 @@ void main(void) {
83
139
  lcd_init_default();
84
140
  LCDC = 0;
85
141
 
86
- upload_tile(0, tile_blank);
87
- upload_tile(1, tile_car_p1);
88
- upload_tile(2, tile_car_en);
142
+ upload_tile(T_CAR_P1, tile_car_p1);
143
+ upload_tile(T_CAR_EN, tile_car_en);
144
+ upload_tile(T_ASPHALT, tile_asphalt);
145
+ upload_tile(T_GRASS, tile_grass);
146
+ upload_tile(T_LANE_A, tile_laneA);
147
+ upload_tile(T_LANE_B, tile_laneB);
89
148
 
149
+ /* DMG BG palette: 0 white, 1 light, 2 dark, 3 black. The road reads
150
+ * as asphalt(dark) + grass(light) + white dashes. */
151
+ BGP = 0xE4;
90
152
  OCPS = 0x80;
91
153
  for (i = 0; i < 4; i++) {
92
154
  OCPD = (uint8_t)(obj_palette[i] & 0xFF);
93
155
  OCPD = (uint8_t)((obj_palette[i] >> 8) & 0xFF);
94
156
  }
95
157
 
158
+ draw_road();
159
+
96
160
  oam_clear();
97
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
161
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
98
162
  sound_init();
99
163
 
100
164
  reset_run();
@@ -105,13 +169,13 @@ void main(void) {
105
169
 
106
170
  /* Stage OAM — player + obstacles. */
107
171
  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);
172
+ oam_set(0, (uint8_t)(player.y + 16), (uint8_t)(player.x + 8), T_CAR_P1, 0);
109
173
  for (i = 0; i < MAX_OBSTACLES; i++) {
110
174
  if (obstacles[i].alive) {
111
175
  oam_set((uint8_t)(1 + i),
112
176
  (uint8_t)(obstacles[i].y + 16),
113
177
  (uint8_t)(obstacles[i].x + 8),
114
- 2, 0);
178
+ T_CAR_EN, 0);
115
179
  }
116
180
  }
117
181
  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,
@@ -86,6 +104,17 @@ static void upload_tile(uint8_t slot, const uint8_t *src) {
86
104
  for (i = 0; i < 16; i++) dst[i] = src[i];
87
105
  }
88
106
 
107
+ /* Paint a starfield into BG map 0 ($9800): fill the visible 20×18 with the
108
+ * dithered space tile, then scatter bright stars on a fixed pseudo-pattern
109
+ * so the field reads as deep space rather than a flat colour. */
110
+ static void draw_starfield(void) {
111
+ uint8_t *bg = BG_MAP_0;
112
+ uint8_t r, c;
113
+ for (r = 0; r < 18; r++)
114
+ for (c = 0; c < 20; c++)
115
+ bg[r * 32 + c] = ((r * 7 + c * 5) % 11 == 0) ? T_STAR : T_SPACE;
116
+ }
117
+
89
118
  void main(void) {
90
119
  uint8_t pad, prev = 0;
91
120
  uint8_t i, j;
@@ -97,6 +126,12 @@ void main(void) {
97
126
  upload_tile(1, tile_ship);
98
127
  upload_tile(2, tile_bullet);
99
128
  upload_tile(3, tile_enemy);
129
+ upload_tile(T_SPACE, tile_space);
130
+ upload_tile(T_STAR, tile_star);
131
+
132
+ /* DMG BG palette: 0 dark, 1 mid-dark, 2 light, 3 white — the dithered
133
+ * space tile mixes shades 0+1 (deep space), stars use shade 3 (white). */
134
+ BGP = 0xE4;
100
135
 
101
136
  OCPS = 0x80;
102
137
  for (i = 0; i < 4; i++) {
@@ -104,6 +139,8 @@ void main(void) {
104
139
  OCPD = (uint8_t)((obj_palette[i] >> 8) & 0xFF);
105
140
  }
106
141
 
142
+ draw_starfield();
143
+
107
144
  player.x = 76; player.y = 130; player.alive = 1;
108
145
  for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
109
146
  for (i = 0; i < MAX_ENEMIES; i++) enemies[i].alive = 0;
@@ -111,7 +148,7 @@ void main(void) {
111
148
  spawn_timer = 0;
112
149
 
113
150
  oam_clear();
114
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
151
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
115
152
  sound_init();
116
153
 
117
154
  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;
@@ -57,6 +81,21 @@ static void upload_tile(uint8_t slot, const uint8_t *src) {
57
81
  for (i = 0; i < 16; i++) dst[i] = src[i];
58
82
  }
59
83
 
84
+ /* Paint the Pong court into BG map 0 ($9800): dithered turf everywhere,
85
+ * solid top/bottom rails, and a dashed net down the centre column. */
86
+ static void draw_court(void) {
87
+ uint8_t *bg = BG_MAP_0;
88
+ uint8_t r, c, t;
89
+ for (r = 0; r < 18; r++) {
90
+ for (c = 0; c < 20; c++) {
91
+ if (r == 0 || r == 17) t = T_WALL; /* top / bottom rail */
92
+ else if (c == 9 || c == 10) t = (r & 1) ? T_NET : T_COURT; /* net dashes */
93
+ else t = T_COURT;
94
+ bg[r * 32 + c] = t;
95
+ }
96
+ }
97
+ }
98
+
60
99
  void main(void) {
61
100
  uint8_t pad;
62
101
  uint8_t i;
@@ -67,6 +106,13 @@ void main(void) {
67
106
 
68
107
  upload_tile(0, tile_blank);
69
108
  upload_tile(1, tile_solid);
109
+ upload_tile(T_COURT, tile_court);
110
+ upload_tile(T_NET, tile_net);
111
+ upload_tile(T_WALL, tile_wall);
112
+
113
+ /* DMG BG palette: 0 dark, 1 mid, 2 light, 3 white — the dithered turf
114
+ * mixes shades 0+1, rails/net use shade 2. */
115
+ BGP = 0xE4;
70
116
 
71
117
  OCPS = 0x80;
72
118
  for (i = 0; i < 4; i++) {
@@ -74,8 +120,9 @@ void main(void) {
74
120
  OCPD = (uint8_t)((obj_palette[i] >> 8) & 0xFF);
75
121
  }
76
122
 
123
+ draw_court();
77
124
  oam_clear();
78
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
125
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
79
126
  sound_init();
80
127
 
81
128
  reset_match();
@@ -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
  }
@@ -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
 
@@ -35,6 +35,22 @@ static const u32 tile_solid_1[8] = {
35
35
  0x11111111, 0x11111111, 0x11111111, 0x11111111,
36
36
  0x11111111, 0x11111111, 0x11111111, 0x11111111,
37
37
  };
38
+ /* BG court tiles (4bpp). The court fills BG1 so the arena isn't flat black
39
+ * (paddles+ball alone on black read as blank to a human — frame verify <92%).
40
+ * TILE_COURT (idx1 green): the playing surface, tiled across the screen.
41
+ * TILE_NET (idx2 white): a dashed vertical centre net. */
42
+ #define TILE_COURT 1
43
+ #define TILE_NET 2
44
+ /* Court surface as a two-green checkerboard (idx1 + idx3) so no single colour
45
+ * dominates the screen — a flat one-colour court still trips the blank check. */
46
+ static const u32 tile_court[8] = {
47
+ 0x11331133, 0x11331133, 0x11331133, 0x11331133,
48
+ 0x33113311, 0x33113311, 0x33113311, 0x33113311,
49
+ };
50
+ static const u32 tile_net[8] = {
51
+ 0x00022000, 0x00022000, 0x00000000, 0x00000000,
52
+ 0x00022000, 0x00022000, 0x00000000, 0x00000000,
53
+ };
38
54
 
39
55
  static OBJ_ATTR obj_buffer[128];
40
56
 
@@ -76,9 +92,27 @@ int main(void) {
76
92
 
77
93
  sfx_init();
78
94
 
79
- /* TTE for scores + hint. */
95
+ /* TTE for scores + hint (BG0). */
80
96
  tte_init_chr4c_default(0, BG_CBB(0) | BG_SBB(31));
81
- REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_OBJ | DCNT_OBJ_1D;
97
+
98
+ /* Court background on BG1 so the arena reads as a real Pong court, not
99
+ * flat black. Tiles in char-block 1, map in screen-block 29. */
100
+ pal_bg_mem[1] = RGB15(2, 12, 4); /* court green (light) */
101
+ pal_bg_mem[2] = CLR_WHITE; /* net */
102
+ pal_bg_mem[3] = RGB15(1, 8, 3); /* court green (dark) */
103
+ tonccpy(&tile_mem[1][TILE_COURT], tile_court, sizeof(tile_court));
104
+ tonccpy(&tile_mem[1][TILE_NET], tile_net, sizeof(tile_net));
105
+ {
106
+ SCR_ENTRY *cmap = se_mem[29];
107
+ int tx, ty;
108
+ for (ty = 0; ty < 32; ty++)
109
+ for (tx = 0; tx < 32; tx++)
110
+ cmap[ty * 32 + tx] = SE_BUILD(
111
+ (tx == 15) ? TILE_NET : TILE_COURT, 0, 0, 0);
112
+ }
113
+ REG_BG1CNT = BG_CBB(1) | BG_SBB(29) | BG_REG_32x32 | BG_4BPP | BG_PRIO(3);
114
+
115
+ REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_BG1 | DCNT_OBJ | DCNT_OBJ_1D;
82
116
  tte_write("#{P:16,2}P1");
83
117
  tte_write("#{P:208,2}P2");
84
118
  tte_write("#{P:36,150}UP/DOWN MOVES YOUR PADDLE");
@@ -31,17 +31,53 @@
31
31
 
32
32
  #include <tonc.h>
33
33
 
34
+ /* ── Backdrop tiles (4bpp, 8 rows × 32 bits) ─────────────────────────
35
+ * Two solid colour tiles so the whole BG0 map reads as a checkerboard,
36
+ * not a flat blank backdrop. Every nibble of tile 1 = palette index 1,
37
+ * every nibble of tile 2 = palette index 2 — so the tile is one solid
38
+ * colour. (m3_fill's tiled-mode equivalent: paint the whole screen.) */
39
+ static const u32 tile_solid1[8] = {
40
+ 0x11111111, 0x11111111, 0x11111111, 0x11111111,
41
+ 0x11111111, 0x11111111, 0x11111111, 0x11111111,
42
+ };
43
+ static const u32 tile_solid2[8] = {
44
+ 0x22222222, 0x22222222, 0x22222222, 0x22222222,
45
+ 0x22222222, 0x22222222, 0x22222222, 0x22222222,
46
+ };
47
+
34
48
  int main(void) {
49
+ /* ── Filled tiled backdrop on BG0 ────────────────────────────
50
+ * Without this the screen is just the black backdrop colour and a
51
+ * few text glyphs — which reads as "blank". We lay a two-tone
52
+ * checkerboard across the entire 32x32 BG0 map so a clear majority
53
+ * of the screen is coloured (the GBA tiled-mode analogue of
54
+ * m3_fill-ing a Mode-3 framebuffer). Tile data → char-block 0,
55
+ * map → screen-block 28 (clear of TTE's char-block 2 / SBB 30). */
56
+ pal_bg_mem[0] = CLR_BLACK;
57
+ pal_bg_mem[1] = RGB15(3, 6, 14); /* deep sky blue */
58
+ pal_bg_mem[2] = RGB15(2, 4, 9); /* darker navy */
59
+ tonccpy(&tile_mem[0][1], tile_solid1, sizeof(tile_solid1));
60
+ tonccpy(&tile_mem[0][2], tile_solid2, sizeof(tile_solid2));
61
+ REG_BG0CNT = BG_CBB(0) | BG_SBB(28) | BG_REG_32x32 | BG_4BPP | BG_PRIO(3);
62
+ {
63
+ SCR_ENTRY *map = se_mem[28];
64
+ for (int ty = 0; ty < 32; ty++)
65
+ for (int tx = 0; tx < 32; tx++)
66
+ map[ty * 32 + tx] = SE_BUILD(1 + ((tx ^ ty) & 1), 0, 0, 0);
67
+ }
68
+
35
69
  /* Initialise TTE in 4-bits-per-pixel chr-mode with the built-in
36
70
  * sys8 font. Cleanest API in the entire GBA ecosystem — one call
37
- * gets you a usable text terminal on BG0.
71
+ * gets you a usable text terminal. We put it on BG1 (char-block 2,
72
+ * screen-block 30) so it sits cleanly in front of the BG0 backdrop.
38
73
  *
39
74
  * NOTE: we deliberately do NOT call tte_init_con() — that lives
40
75
  * in the excluded tte_iohook.c (the libsysbase bridge). Without
41
76
  * it, printf/iprintf don't route through TTE — but `tte_write` /
42
77
  * `tte_printf` work directly without any libsysbase plumbing,
43
78
  * which is what the Tonc tutorial uses everywhere anyway. */
44
- tte_init_chr4c_default(0, BG_CBB(0) | BG_SBB(31));
79
+ tte_init_chr4c_default(1, BG_CBB(2) | BG_SBB(30));
80
+ REG_BG1CNT |= BG_PRIO(0); /* text in front of the backdrop */
45
81
 
46
82
  /* ── IRQ setup ── REQUIRED for VBlankIntrWait() to work ──────
47
83
  * Without this, the BIOS halts the CPU on the first
@@ -50,9 +86,9 @@ int main(void) {
50
86
  irq_init(NULL);
51
87
  irq_add(II_VBLANK, NULL);
52
88
 
53
- /* Set DISPCNT — turn on BG0 (where TTE drew the font tiles into
54
- * char-base 0 + screen-base 31). DCNT_MODE0 is the tile-BG mode. */
55
- REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
89
+ /* Set DISPCNT — turn on BG0 (the filled backdrop) and BG1 (TTE
90
+ * text). DCNT_MODE0 is the tile-BG mode. */
91
+ REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_BG1;
56
92
 
57
93
  /* Draw text. tte_write moves the internal cursor; \n wraps. */
58
94
  tte_write("#{P:32,32}"); /* position cursor at pixel (32,32) */