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
@@ -20,6 +20,8 @@
20
20
  #define P0C1 (*(volatile uint8_t*)0x21)
21
21
  #define P0C2 (*(volatile uint8_t*)0x22)
22
22
  #define P0C3 (*(volatile uint8_t*)0x23)
23
+ #define P1C1 (*(volatile uint8_t*)0x25)
24
+ #define P2C1 (*(volatile uint8_t*)0x29)
23
25
  #define MSTAT (*(volatile uint8_t*)0x28)
24
26
  #define DPPH (*(volatile uint8_t*)0x2C)
25
27
  #define DPPL (*(volatile uint8_t*)0x30)
@@ -50,6 +52,39 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
50
52
 
51
53
  static uint8_t dl_empty[2] = { 0, 0 };
52
54
 
55
+ /* ── Background playfield ─────────────────────────────────────────
56
+ * Without a full-screen drawable the display list emits only the
57
+ * ship and ~99% of the screen stays the flat BACKGRND colour (reads
58
+ * as "blank"). These full-width bands fill the non-ship zones with a
59
+ * starfield-style background so the frame has real content.
60
+ *
61
+ * A single DL drawable is at most 32 bytes = 128 px wide, so a full
62
+ * 160-px line needs TWO drawables. Width = byte[3] low 5 bits (32-n);
63
+ * high 3 bits = palette. */
64
+ static const uint8_t band_pix[32] = {
65
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
66
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
67
+ };
68
+ #define MK_BAND(name, pal) static uint8_t name[11] = { \
69
+ 0, 0x40, 0, ((pal) << 5) | 0, 0, /* 128 px @ x0 */ \
70
+ 0, 0x40, 0, ((pal) << 5) | 24, 128, /* 32 px @ x128 */ \
71
+ 0 }
72
+ MK_BAND(dl_field, 1);
73
+ MK_BAND(dl_ground, 2);
74
+ #define GROUND_ZONE 188
75
+
76
+ static void set_band_addr(uint8_t* dl) {
77
+ uint16_t a = (uint16_t)(uintptr_t)band_pix;
78
+ dl[0] = dl[5] = (uint8_t)(a & 0xFF);
79
+ dl[2] = dl[7] = (uint8_t)(a >> 8);
80
+ }
81
+
82
+ static uint16_t bg_zone_dl(int zone) {
83
+ if (zone >= GROUND_ZONE) return (uint16_t)(uintptr_t)dl_ground;
84
+ if (zone >= 28) return (uint16_t)(uintptr_t)dl_field;
85
+ return (uint16_t)(uintptr_t)dl_empty;
86
+ }
87
+
53
88
  #define DLL_ZONES 243
54
89
  static uint8_t dll[DLL_ZONES * 3];
55
90
 
@@ -74,7 +109,6 @@ static void set_x(uint8_t x) {
74
109
  }
75
110
 
76
111
  static void build_dll(int y) {
77
- uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
78
112
  int i;
79
113
  for (i = 0; i < DLL_ZONES; i++) {
80
114
  uint16_t dl;
@@ -88,7 +122,7 @@ static void build_dll(int y) {
88
122
  case 5: dl = (uint16_t)(uintptr_t)dl_row5; break;
89
123
  case 6: dl = (uint16_t)(uintptr_t)dl_row6; break;
90
124
  case 7: dl = (uint16_t)(uintptr_t)dl_row7; break;
91
- default: dl = empty; break;
125
+ default: dl = bg_zone_dl(i); break;
92
126
  }
93
127
  set_dll_entry(i, dl);
94
128
  }
@@ -111,6 +145,8 @@ void main(void) {
111
145
  set_dl_addr(dl_row5, ship_row5);
112
146
  set_dl_addr(dl_row6, ship_row6);
113
147
  set_dl_addr(dl_row7, ship_row7);
148
+ set_band_addr(dl_field);
149
+ set_band_addr(dl_ground);
114
150
 
115
151
  player_x = 80;
116
152
  player_y = 180;
@@ -121,6 +157,8 @@ void main(void) {
121
157
  P0C1 = 0x0F;
122
158
  P0C2 = 0x1C;
123
159
  P0C3 = 0x46;
160
+ P1C1 = 0x84; /* upper nebula band (deep blue) */
161
+ P2C1 = 0x82; /* lower nebula band (darker blue) */
124
162
  CHARBASE = 0;
125
163
  OFFSET = 0;
126
164
 
@@ -21,6 +21,8 @@
21
21
  #define P0C1 (*(volatile uint8_t*)0x21)
22
22
  #define P0C2 (*(volatile uint8_t*)0x22)
23
23
  #define P0C3 (*(volatile uint8_t*)0x23)
24
+ #define P1C1 (*(volatile uint8_t*)0x25)
25
+ #define P2C1 (*(volatile uint8_t*)0x29)
24
26
  #define MSTAT (*(volatile uint8_t*)0x28)
25
27
  #define DPPH (*(volatile uint8_t*)0x2C)
26
28
  #define DPPL (*(volatile uint8_t*)0x30)
@@ -60,6 +62,29 @@ static uint8_t scanline_dls[PLAY_LINES * DL_BYTES_PER_LINE];
60
62
 
61
63
  static uint8_t dl_empty[2] = { 0, 0 };
62
64
 
65
+ /* ── Court border bands ───────────────────────────────────────────
66
+ * The court itself (per-scanline DLs below) only draws two thin
67
+ * paddles + a ball, so on a black screen ~99% of the frame is blank.
68
+ * Fill the zones above and below the court with full-width bands so
69
+ * the court is framed by visible walls. A single DL drawable is at
70
+ * most 32 bytes = 128 px wide, so a full 160-px line needs TWO
71
+ * drawables. Width = byte[3] low 5 bits (32-n); high 3 bits = palette. */
72
+ static const uint8_t band_pix[32] = {
73
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
74
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
75
+ };
76
+ #define MK_BAND(name, pal) static uint8_t name[11] = { \
77
+ 0, 0x40, 0, ((pal) << 5) | 0, 0, /* 128 px @ x0 */ \
78
+ 0, 0x40, 0, ((pal) << 5) | 24, 128, /* 32 px @ x128 */ \
79
+ 0 }
80
+ MK_BAND(dl_wall, 1);
81
+
82
+ static void set_band_addr(uint8_t* dl) {
83
+ uint16_t a = (uint16_t)(uintptr_t)band_pix;
84
+ dl[0] = dl[5] = (uint8_t)(a & 0xFF);
85
+ dl[2] = dl[7] = (uint8_t)(a >> 8);
86
+ }
87
+
63
88
  #define DLL_ZONES 243
64
89
  static uint8_t dll[DLL_ZONES * 3];
65
90
 
@@ -92,19 +117,23 @@ static uint8_t emit_obj(uint8_t* dl, uint8_t off, uint16_t data_addr,
92
117
 
93
118
  /* Rebuild ALL scanline DLs + DLL based on current object positions. */
94
119
  static void rebuild(void) {
120
+ uint16_t wall = (uint16_t)(uintptr_t)dl_wall;
95
121
  uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
96
122
  uint16_t solid = (uint16_t)(uintptr_t)solid_row;
97
123
  int line;
98
124
  int i;
99
125
 
100
- /* DLL: point each play-area line at its scanline DL slot; everything
101
- * else at the empty DL. */
126
+ /* DLL: point each play-area line at its scanline DL slot; frame the
127
+ * court with full-width wall bands above and below, leaving a thin
128
+ * empty gutter right at the court edges. */
102
129
  for (i = 0; i < DLL_ZONES; i++) {
103
130
  if (i >= COURT_TOP && i < COURT_BOT) {
104
131
  uint16_t dlp = (uint16_t)(uintptr_t)&scanline_dls[(i - COURT_TOP) * DL_BYTES_PER_LINE];
105
132
  set_dll_entry(i, dlp);
133
+ } else if (i >= COURT_TOP - 8 && i < COURT_BOT + 8) {
134
+ set_dll_entry(i, empty); /* small gutter around the court */
106
135
  } else {
107
- set_dll_entry(i, empty);
136
+ set_dll_entry(i, wall);
108
137
  }
109
138
  }
110
139
 
@@ -151,13 +180,15 @@ void main(void) {
151
180
  p1y = 110; p2y = 110;
152
181
  serve_ball(0);
153
182
 
154
- BACKGRND = 0x00; /* black */
155
- P0C1 = 0x0F; /* white sprite */
183
+ BACKGRND = 0x00; /* black court */
184
+ P0C1 = 0x0F; /* white paddles + ball */
156
185
  P0C2 = 0x0F;
157
186
  P0C3 = 0x0F;
187
+ P1C1 = 0x48; /* court walls (blue) */
158
188
  CHARBASE = 0;
159
189
  OFFSET = 0;
160
190
 
191
+ set_band_addr(dl_wall);
161
192
  rebuild();
162
193
 
163
194
  dll_addr = (uint16_t)(uintptr_t)dll;
@@ -96,10 +96,24 @@ static void render_view(uint8_t coarseCol) {
96
96
  uint16_t off = (uint16_t)r * 40 + sc;
97
97
  if (wc < WORLD_COLS && world_is_wall((uint8_t)wc, r)) {
98
98
  SCREEN[off] = 0xA0; /* reverse-space solid block */
99
- COLORS[off] = 0x0C; /* mid grey */
99
+ COLORS[off] = 0x0C; /* mid grey platform */
100
+ } else if (r >= 22) {
101
+ /* Ground fill below the floor row: dithered earth so the lower
102
+ * band reads as solid terrain, not void. */
103
+ SCREEN[off] = 0xA0;
104
+ COLORS[off] = (((uint8_t)wc ^ r) & 1) ? 0x09 : 0x08; /* brown / orange */
100
105
  } else {
101
- SCREEN[off] = 0x20; /* space */
102
- COLORS[off] = 0x06; /* bg color */
106
+ /* Textured sky so two colours share the backdrop and neither the
107
+ * sky nor the border dominates the frame. Sparse '.' stars on a
108
+ * coarse lattice add detail; reverse-space everywhere else gives a
109
+ * filled (non-blank) sky band that scrolls with the world. */
110
+ if (((wc * 3u + r * 7u) % 23u) == 0u) {
111
+ SCREEN[off] = 0x2E; /* '.' distant detail */
112
+ COLORS[off] = 0x01; /* white */
113
+ } else {
114
+ SCREEN[off] = 0xA0; /* solid block sky */
115
+ COLORS[off] = (((uint8_t)wc ^ (r >> 1)) & 1) ? 0x06 : 0x0E; /* blue / light blue */
116
+ }
103
117
  }
104
118
  }
105
119
  }
@@ -133,8 +147,8 @@ void main(void) {
133
147
  copy_sprite(0, player_sprite);
134
148
  SPRITE_POINTERS[0] = 0x80; /* $2000/64 */
135
149
  POKE(VIC_SPR_COL(0), 0x07); /* yellow player */
136
- POKE(VIC_BORDER, 0x06); /* dark blue */
137
- POKE(VIC_BG0, 0x06);
150
+ POKE(VIC_BORDER, 0x00); /* black border frames the scene */
151
+ POKE(VIC_BG0, 0x06); /* sky-blue (shows through any gaps) */
138
152
 
139
153
  render_view(0); /* paint the initial 40-col view */
140
154
 
@@ -40,6 +40,35 @@ static void wait_vblank(void) {
40
40
  while (PEEK(VIC_RASTER) >= 250) { }
41
41
  }
42
42
 
43
+ /* Paint the playfield surround so the board reads as a real puzzle screen
44
+ * instead of a tiny well floating in a black void: a dithered backdrop fills
45
+ * the whole 40x25 matrix (two dark blues, so two colours share the screen
46
+ * and neither dominates), then a bright frame is drawn one cell outside the
47
+ * 6x12 well, and the well interior is cleared to black so the falling blocks
48
+ * pop. Call ONCE before draw_grid(); draw_grid() owns the interior after. */
49
+ static void draw_field(void) {
50
+ uint16_t i;
51
+ uint8_t r, c;
52
+ int8_t fr, fc;
53
+ for (i = 0; i < 1000; i++) {
54
+ SCREEN[i] = 0xA0; /* solid block backdrop */
55
+ COLORS[i] = ((i ^ (i >> 5)) & 1) ? 0x06 : 0x0E; /* blue / light blue */
56
+ }
57
+ /* Bright frame one cell outside the well. */
58
+ for (fc = -1; fc <= COLS; fc++) {
59
+ r = (uint8_t)(GRID_R - 1); SCREEN[r * 40 + GRID_C + fc] = 0xA0; COLORS[r * 40 + GRID_C + fc] = 0x01;
60
+ r = (uint8_t)(GRID_R + ROWS); SCREEN[r * 40 + GRID_C + fc] = 0xA0; COLORS[r * 40 + GRID_C + fc] = 0x01;
61
+ }
62
+ for (fr = -1; fr <= ROWS; fr++) {
63
+ r = (uint8_t)(GRID_R + fr);
64
+ SCREEN[r * 40 + GRID_C - 1] = 0xA0; COLORS[r * 40 + GRID_C - 1] = 0x01;
65
+ SCREEN[r * 40 + GRID_C + COLS] = 0xA0; COLORS[r * 40 + GRID_C + COLS] = 0x01;
66
+ }
67
+ /* Clear the well interior to black so colored blocks stand out. */
68
+ for (r = 0; r < ROWS; r++)
69
+ for (c = 0; c < COLS; c++) SCREEN[(GRID_R + r) * 40 + GRID_C + c] = ' ';
70
+ }
71
+
43
72
  static uint8_t rng_pick(void) {
44
73
  rng = rng * 1103515245u + 12345u;
45
74
  return (uint8_t)(1 + (rng >> 16) % 3);
@@ -131,14 +160,15 @@ static void lock_piece(void) {
131
160
 
132
161
  void main(void) {
133
162
  uint8_t r, c, pad, prev = 0, fall_rate, t;
134
- POKE(VIC_BORDER, 0x00);
135
- POKE(VIC_BG0, 0x00);
163
+ POKE(VIC_BORDER, 0x06); /* blue border frames the playfield */
164
+ POKE(VIC_BG0, 0x00); /* black well interior so blocks pop */
136
165
 
137
166
  for (r = 0; r < ROWS; r++)
138
167
  for (c = 0; c < COLS; c++) grid[r][c] = 0;
139
168
 
140
169
  score = 0; fall_timer = 0;
141
170
  sfx_init();
171
+ draw_field(); /* paint the textured surround + well frame */
142
172
  new_piece();
143
173
  draw_grid();
144
174
 
@@ -19,6 +19,9 @@
19
19
  #define POKE(addr, val) (*(volatile uint8_t*)(addr) = (val))
20
20
  #define PEEK(addr) (*(volatile uint8_t*)(addr))
21
21
 
22
+ #define SCREEN ((volatile uint8_t*)0x0400)
23
+ #define COLORS ((volatile uint8_t*)0xD800)
24
+
22
25
  #define SPRITE_POINTERS ((volatile uint8_t*)0x07F8)
23
26
  #define SPRITE_DATA_BASE 0x2000 /* sprite N data at $2000 + N*64 — NOT $0800,
24
27
  * which collides with the $0801 .prg load (C64-1) */
@@ -105,6 +108,28 @@ static void wait_vblank(void) {
105
108
  while (PEEK(VIC_RASTER) >= 250) { }
106
109
  }
107
110
 
111
+ /* Paint a deep-space backdrop into the 40x25 character matrix so the
112
+ * playfield reads as space instead of a flat black void. Every cell gets
113
+ * a dithered "nebula" char (reverse-space 0xA0) in one of two dark blues,
114
+ * so two colours share the screen and no single colour dominates. A sparse
115
+ * scatter of bright '.' stars (drawn as a normal glyph over the dither)
116
+ * adds twinkle. Cosmetic only — sprites still move over the top. */
117
+ static void draw_starfield(void) {
118
+ uint16_t i;
119
+ uint8_t r, c;
120
+ for (i = 0; i < 1000; i++) {
121
+ SCREEN[i] = 0xA0; /* solid block fills the cell */
122
+ COLORS[i] = ((i ^ (i >> 5)) & 1) ? 0x06 : 0x0B; /* blue / dark grey */
123
+ }
124
+ /* Scatter stars on a coarse lattice so ~1 in 12 cells twinkles. */
125
+ for (r = 1; r < 25; r += 3) {
126
+ for (c = (uint8_t)(r * 5u % 7u); c < 40; c += 7) {
127
+ SCREEN[r * 40 + c] = 0x2E; /* '.' star glyph */
128
+ COLORS[r * 40 + c] = ((r + c) & 1) ? 0x01 : 0x0F; /* white / l.grey */
129
+ }
130
+ }
131
+ }
132
+
108
133
  static void copy_sprite(uint8_t slot, const uint8_t *data) {
109
134
  uint8_t i;
110
135
  volatile uint8_t *dst = (volatile uint8_t*)(SPRITE_DATA_BASE + slot * 64);
@@ -130,8 +155,9 @@ void main(void) {
130
155
  for (i = 0; i < MAX_BULLETS; i++) POKE(VIC_SPR_COL(SLOT_BULLET0 + i), 0x01); /* white */
131
156
  for (i = 0; i < MAX_ENEMIES; i++) POKE(VIC_SPR_COL(SLOT_ENEMY0 + i), 0x02); /* red */
132
157
 
133
- POKE(VIC_BORDER, 0x00);
134
- POKE(VIC_BG0, 0x00);
158
+ POKE(VIC_BORDER, 0x00); /* black border frames the starfield */
159
+ POKE(VIC_BG0, 0x06); /* deep-blue space background */
160
+ draw_starfield(); /* paint the textured space backdrop */
135
161
 
136
162
  player.x = 152; player.y = 200; player.alive = 1;
137
163
  for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
@@ -11,6 +11,9 @@
11
11
  #define POKE(addr, val) (*(volatile uint8_t*)(addr) = (val))
12
12
  #define PEEK(addr) (*(volatile uint8_t*)(addr))
13
13
 
14
+ #define SCREEN ((volatile uint8_t*)0x0400)
15
+ #define COLORS ((volatile uint8_t*)0xD800)
16
+
14
17
  #define JOY_UP 0x01
15
18
  #define JOY_DOWN 0x02
16
19
 
@@ -39,6 +42,30 @@ static void wait_vblank(void) {
39
42
  while (PEEK(VIC_RASTER) >= 250) { }
40
43
  }
41
44
 
45
+ /* Paint a Pong court into the 40x25 character matrix so the table reads as
46
+ * a real court instead of a flat black void: a dithered green playfield
47
+ * (two greens, so two colours share the screen and neither dominates),
48
+ * solid top/bottom boundary rails, and a dashed centre net. Cosmetic only —
49
+ * the paddle/ball sprites move over the top. */
50
+ static void draw_court(void) {
51
+ uint16_t i;
52
+ uint8_t r, c;
53
+ /* Dithered green playfield fills every cell. */
54
+ for (i = 0; i < 1000; i++) {
55
+ SCREEN[i] = 0xA0; /* solid block */
56
+ COLORS[i] = ((i ^ (i >> 5)) & 1) ? 0x05 : 0x09; /* green / brown */
57
+ }
58
+ /* Top + bottom boundary rails (rows 1 and 23) in white. */
59
+ for (c = 0; c < 40; c++) {
60
+ SCREEN[1 * 40 + c] = 0xA0; COLORS[1 * 40 + c] = 0x01;
61
+ SCREEN[23 * 40 + c] = 0xA0; COLORS[23 * 40 + c] = 0x01;
62
+ }
63
+ /* Dashed centre net (column 20). */
64
+ for (r = 2; r < 23; r++) {
65
+ if (r & 1) { SCREEN[r * 40 + 20] = 0xA0; COLORS[r * 40 + 20] = 0x01; }
66
+ }
67
+ }
68
+
42
69
  static void copy_sprite(uint8_t slot, const uint8_t *data) {
43
70
  uint8_t i;
44
71
  volatile uint8_t *dst = (volatile uint8_t*)(0x2000 + slot * 64); /* $2000, not $0800 (collides w/ $0801 .prg) */
@@ -63,8 +90,9 @@ void main(void) {
63
90
  POKE(VIC_SPR_COL(1), 0x01);
64
91
  POKE(VIC_SPR_COL(2), 0x07); /* yellow ball */
65
92
 
66
- POKE(VIC_BORDER, 0x00);
67
- POKE(VIC_BG0, 0x00);
93
+ POKE(VIC_BORDER, 0x00); /* black border frames the court */
94
+ POKE(VIC_BG0, 0x05); /* green court background */
95
+ draw_court(); /* paint the textured Pong court */
68
96
 
69
97
  /* P1 paddle on left, P2 on right. */
70
98
  POKE(VIC_SPRITE_X(0), 30);
@@ -1,11 +1,17 @@
1
1
  /* ── default.c — minimal Game Boy (DMG) starter ───────────────────
2
2
  *
3
- * Boots the LCD, cycles the DMG background palette through 4 shade
4
- * arrangements via BGP ($FF47). Smallest possible "ROM that does
5
- * something visible" use this as the starting point when you're
6
- * not yet sure what you want to build.
3
+ * A "hello, it works!" screen: a tiled background (a dithered field with
4
+ * two bands + a centre box) plus a sprite that bounces around. The very
5
+ * first build shows recognizable content not a flat colour. The DMG
6
+ * background palette (BGP, $FF47) also cycles through 4 shade
7
+ * arrangements so you can SEE the palette path is alive. Use this as the
8
+ * starting point when you're not yet sure what you want to build.
7
9
  *
8
10
  * GB-specific notes for the agent:
11
+ * - You MUST put tiles in VRAM *and* enable the BG (LCDC bit 0) or the
12
+ * screen stays one flat colour — the #1 GB "why is it blank" footgun.
13
+ * We upload tiles to $8000 and select LCDC_TILE_DATA_LO (unsigned
14
+ * $8000 addressing) so tile index N lives at $8000 + N*16.
9
15
  * - DMG uses the BGP/OBP0/OBP1 registers — NOT the CGB BCPS/BCPD
10
16
  * palette RAM. The GBC tree's default uses BCPS; don't copy that
11
17
  * into a DMG project or your screen will stay one shade.
@@ -28,31 +34,119 @@
28
34
  #include "gb_hardware.h"
29
35
  #include "gb_runtime.h"
30
36
 
31
- /* Four BGP arrangements; each byte packs 4 color indices, 2 bits each:
32
- * bits 7-6 = color for tile-index 3 (darkest source)
33
- * bits 5-4 = color for tile-index 2
34
- * bits 3-2 = color for tile-index 1
35
- * bits 1-0 = color for tile-index 0 (lightest source)
36
- * Color: 0 = white, 1 = light grey, 2 = dark grey, 3 = black. */
37
+ /* Six 8×8 tiles, 2bpp (16 bytes each: row N = byte 2N low-plane, 2N+1
38
+ * high-plane). The colour index per pixel (0..3) selects a shade through
39
+ * BGP. We spread indices 1, 2 and 3 across the screen spatially so no
40
+ * single shade ever fills the frame — regardless of the BGP arrangement.
41
+ * tile 0 blank (all index 0)
42
+ * tile 1 solid index 1 (top band)
43
+ * tile 2 — solid index 2 (bottom band)
44
+ * tile 3 — dither idx1/idx2 (the textured backdrop — mixes two shades
45
+ * inside every cell so the field is never flat)
46
+ * tile 4 — solid index 3 (centre box + border)
47
+ * tile 5 — sprite diamond (index 3) */
48
+ static const uint8_t tiles[6 * 16] = {
49
+ /* 0: blank */
50
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
51
+ /* 1: solid index 1 (low plane on, high plane off) */
52
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
53
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
54
+ /* 2: solid index 2 (low plane off, high plane on) */
55
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
56
+ 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
57
+ /* 3: dither — checkerboard of index 1 and index 2 */
58
+ 0x55,0xAA, 0xAA,0x55, 0x55,0xAA, 0xAA,0x55,
59
+ 0x55,0xAA, 0xAA,0x55, 0x55,0xAA, 0xAA,0x55,
60
+ /* 4: solid index 3 (both planes on) */
61
+ 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF,
62
+ 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF,
63
+ /* 5: diamond in index 3 (both planes set on the diamond pixels) */
64
+ 0x18,0x18, 0x3C,0x3C, 0x7E,0x7E, 0xFF,0xFF,
65
+ 0xFF,0xFF, 0x7E,0x7E, 0x3C,0x3C, 0x18,0x18,
66
+ };
67
+
68
+ #define T_BLANK 0
69
+ #define T_BAND1 1
70
+ #define T_BAND2 2
71
+ #define T_FIELD 3
72
+ #define T_BOX 4
73
+ #define T_SPRITE 5
74
+
75
+ /* Four BGP arrangements; each byte packs 4 colour indices, 2 bits each:
76
+ * bits 7-6 = shade for index 3, bits 5-4 = index 2,
77
+ * bits 3-2 = index 1, bits 1-0 = index 0.
78
+ * Shade: 0 = white, 1 = light grey, 2 = dark grey, 3 = black.
79
+ * Every entry keeps index 1 != index 2 so the dithered field always shows
80
+ * two distinct shades (the screen is never one flat colour, even mid-cycle). */
37
81
  static const uint8_t bgp_shades[4] = {
38
- 0xE4, /* normal: 3=black 2=dark 1=light 0=white */
39
- 0x90, /* inverted-ish: 3=dark 2=light 1=white 0=white */
40
- 0x39, /* shifted: 3=white 2=dark 1=dark 0=light */
41
- 0x00, /* all-white (DMG visible: "off"-looking screen) */
82
+ 0xE4, /* normal: 3=black 2=dark 1=light 0=white */
83
+ 0x90, /* dim: 3=dark 2=light 1=white 0=white */
84
+ 0x39, /* shifted: 3=white 2=dark 1=dark 0=light */
85
+ 0x1B, /* inverted: 3=white 2=light 1=dark 0=black */
42
86
  };
43
87
 
88
+ static void upload_tiles(void) {
89
+ memcpy_vram((void *)0x8000, tiles, sizeof(tiles));
90
+ }
91
+
92
+ /* Paint the BG map (32×32; we fill the visible 20×18). A dithered field
93
+ * everywhere, two solid bands, a solid border and a centre box, so the
94
+ * screen reads as real content rather than a flat shade. */
95
+ static void draw_backdrop(void) {
96
+ uint8_t *bg = BG_MAP_0; /* $9800 */
97
+ uint8_t x, y;
98
+ for (y = 0; y < 18; y++)
99
+ for (x = 0; x < 20; x++)
100
+ bg[y * 32 + x] = T_FIELD;
101
+ for (x = 0; x < 20; x++) {
102
+ bg[0 * 32 + x] = T_BOX; /* top border */
103
+ bg[17 * 32 + x] = T_BOX; /* bottom border */
104
+ bg[3 * 32 + x] = T_BAND1; /* upper band */
105
+ bg[14 * 32 + x] = T_BAND2; /* lower band */
106
+ }
107
+ for (y = 0; y < 18; y++) {
108
+ bg[y * 32 + 0] = T_BOX; /* left border */
109
+ bg[y * 32 + 19] = T_BOX; /* right border */
110
+ }
111
+ for (y = 7; y < 11; y++)
112
+ for (x = 7; x < 13; x++)
113
+ bg[y * 32 + x] = T_BOX; /* centre box */
114
+ }
115
+
44
116
  void main(void) {
45
117
  uint8_t shade = 0;
46
118
  uint16_t frame = 0;
119
+ uint8_t sx = 76, sy = 64; /* sprite screen position */
120
+ int8_t dx = 1, dy = 1; /* sprite velocity */
121
+
122
+ lcd_init_default(); /* LCD on, BGP=0xE4, BG+OBJ enabled */
123
+ LCDC = 0;
124
+
125
+ upload_tiles();
126
+ BGP = bgp_shades[0];
127
+ draw_backdrop();
47
128
 
48
- lcd_init_default(); /* turns LCD on with BGP=0xE4, BG+OBJ enabled */
129
+ oam_clear();
130
+ oam_set(0, (uint8_t)(sy + 16), (uint8_t)(sx + 8), T_SPRITE, 0);
131
+
132
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
49
133
 
50
134
  for (;;) {
51
135
  wait_vblank();
136
+ oam_dma_flush();
137
+
52
138
  frame++;
53
- if ((frame & 0x1F) == 0) { /* every 32 frames */
139
+ if ((frame & 0x1F) == 0) { /* every 32 frames: cycle BGP */
54
140
  shade = (uint8_t)((shade + 1) & 0x03);
55
141
  BGP = bgp_shades[shade];
56
142
  }
143
+
144
+ /* Bounce the sprite around the 160×144 visible area. */
145
+ sx = (uint8_t)(sx + dx);
146
+ sy = (uint8_t)(sy + dy);
147
+ if (sx < 1 || sx > 152) dx = (int8_t)-dx;
148
+ if (sy < 1 || sy > 136) dy = (int8_t)-dy;
149
+ oam_clear();
150
+ oam_set(0, (uint8_t)(sy + 16), (uint8_t)(sx + 8), T_SPRITE, 0);
57
151
  }
58
152
  }
@@ -25,9 +25,26 @@ static const uint8_t tile_platform[16] = {
25
25
  0xFF,0xFF, 0x80,0x80, 0x80,0x80, 0x80,0x80,
26
26
  0x80,0x80, 0x80,0x80, 0x80,0x80, 0xFF,0xFF,
27
27
  };
28
+ /* ── Backdrop tiles ───────────────────────────────────────────────────
29
+ * Fill the whole world so the screen is never one flat colour (the #1 GB
30
+ * "why is it blank" footgun). tile_sky is a sparse dot pattern over the
31
+ * sky; tile_ground is a textured dirt fill under the floor line. */
32
+ static const uint8_t tile_sky[16] = {
33
+ 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x20,0x20,
34
+ 0x00,0x00, 0x00,0x00, 0x02,0x02, 0x00,0x00,
35
+ };
36
+ static const uint8_t tile_ground[16] = {
37
+ 0xFF,0x00, 0xDB,0x24, 0xFF,0x00, 0x6D,0x92,
38
+ 0xFF,0x00, 0xDB,0x24, 0xFF,0x00, 0x6D,0x92,
39
+ };
40
+ #define T_BLANK 0
41
+ #define T_PLATFORM 2
42
+ #define T_SKY 3
43
+ #define T_GROUND 4
28
44
 
29
45
  static const uint16_t obj_palette[4] = { 0x7FFF, 0x001F, 0x03E0, 0x7C00 };
30
- static const uint16_t bg_palette[4] = { 0x7FFF, 0x5294, 0x294A, 0x0000 };
46
+ /* BG palette: 0 sky-blue, 1 mid, 2 dirt-dark, 3 near-black detail. */
47
+ static const uint16_t bg_palette[4] = { 0x7E10, 0x5294, 0x114A, 0x0000 };
31
48
 
32
49
  typedef struct { int16_t x, y, w, h; } Rect;
33
50
 
@@ -71,8 +88,10 @@ static void paint_platforms(void) {
71
88
  const Rect *p;
72
89
  /* k MUST be uint16_t: 32*18 = 576 > 255, so a uint8_t counter would
73
90
  * never reach the bound and this loop would spin forever (the BG map
74
- * never clears, main() never starts). Classic SDCC limited-range trap. */
75
- for (k = 0; k < 32 * 18; k++) map[k] = 0; /* blank tile slot 0 */
91
+ * never clears, main() never starts). Classic SDCC limited-range trap.
92
+ * Fill sky above the floor line (row 16 = y 128) and textured ground
93
+ * at and below it, so the whole world is a real scene, not blank. */
94
+ for (k = 0; k < 32 * 18; k++) map[k] = (k >= 16 * 32) ? T_GROUND : T_SKY;
76
95
  for (i = 0; i < N_PLATFORMS; i++) {
77
96
  p = &platforms[i];
78
97
  cx = p->x >> 3;
@@ -81,7 +100,7 @@ static void paint_platforms(void) {
81
100
  ch = (p->h + 7) >> 3;
82
101
  for (j = 0; j < cw; j++) {
83
102
  if (cx + j < 32 && cy < 32)
84
- map[cy * 32 + cx + j] = 2; /* tile slot 2 = platform */
103
+ map[cy * 32 + cx + j] = T_PLATFORM; /* platform top edge */
85
104
  }
86
105
  }
87
106
  }
@@ -108,6 +127,8 @@ void main(void) {
108
127
  upload_tile(0, tile_blank);
109
128
  upload_tile(1, tile_player);
110
129
  upload_tile(2, tile_platform);
130
+ upload_tile(T_SKY, tile_sky);
131
+ upload_tile(T_GROUND, tile_ground);
111
132
 
112
133
  OCPS = 0x80;
113
134
  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;