romdevtools 0.16.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/AGENTS.md +75 -16
  2. package/CHANGELOG.md +316 -0
  3. package/examples/README.md +2 -0
  4. package/examples/atari2600/templates/platformer.asm +460 -0
  5. package/examples/atari2600/templates/racing.asm +463 -0
  6. package/examples/atari2600/templates/shmup.asm +386 -0
  7. package/examples/atari2600/templates/sports.asm +362 -0
  8. package/examples/atari7800/templates/default.c +49 -5
  9. package/examples/atari7800/templates/hello_sprite.c +48 -4
  10. package/examples/atari7800/templates/music_demo.c +47 -2
  11. package/examples/atari7800/templates/platformer.c +43 -4
  12. package/examples/atari7800/templates/puzzle.c +39 -4
  13. package/examples/atari7800/templates/racing.c +39 -4
  14. package/examples/atari7800/templates/shmup.c +40 -2
  15. package/examples/atari7800/templates/sports.c +36 -5
  16. package/examples/c64/templates/platformer.c +19 -5
  17. package/examples/c64/templates/puzzle.c +32 -2
  18. package/examples/c64/templates/shmup.c +28 -2
  19. package/examples/c64/templates/sports.c +30 -2
  20. package/examples/c64/templates/tile_engine.c +77 -27
  21. package/examples/gb/templates/default.c +110 -16
  22. package/examples/gb/templates/hello_sprite.c +15 -6
  23. package/examples/gb/templates/music_demo.c +36 -0
  24. package/examples/gb/templates/platformer.c +28 -6
  25. package/examples/gb/templates/puzzle.c +35 -4
  26. package/examples/gb/templates/racing.c +75 -10
  27. package/examples/gb/templates/shmup.c +41 -3
  28. package/examples/gb/templates/sports.c +51 -3
  29. package/examples/gb/templates/tile_engine.c +3 -2
  30. package/examples/gba/templates/gba_hello.c +29 -11
  31. package/examples/gba/templates/maxmod_demo.c +36 -2
  32. package/examples/gba/templates/platformer.c +3 -1
  33. package/examples/gba/templates/puzzle.c +15 -3
  34. package/examples/gba/templates/racing.c +65 -3
  35. package/examples/gba/templates/shmup.c +41 -4
  36. package/examples/gba/templates/sports.c +36 -2
  37. package/examples/gba/templates/tonc_hello.c +41 -5
  38. package/examples/gba/templates/tonc_hello_sprite.c +35 -1
  39. package/examples/gbc/templates/default.c +103 -26
  40. package/examples/gbc/templates/hello_sprite.c +12 -3
  41. package/examples/gbc/templates/music_demo.c +56 -12
  42. package/examples/gbc/templates/platformer.c +28 -6
  43. package/examples/gbc/templates/puzzle.c +35 -4
  44. package/examples/gbc/templates/racing.c +88 -21
  45. package/examples/gbc/templates/shmup.c +37 -3
  46. package/examples/gbc/templates/sports.c +48 -3
  47. package/examples/gbc/templates/tile_engine.c +3 -2
  48. package/examples/genesis/main.s +53 -1
  49. package/examples/genesis/templates/hello_sprite.c +25 -3
  50. package/examples/genesis/templates/puzzle.c +37 -3
  51. package/examples/genesis/templates/racing.c +44 -11
  52. package/examples/genesis/templates/sgdk_hello.c +34 -1
  53. package/examples/genesis/templates/shmup.c +31 -1
  54. package/examples/genesis/templates/shmup_2p.c +31 -0
  55. package/examples/genesis/templates/xgm2_demo.c +20 -0
  56. package/examples/gg/templates/default.c +56 -18
  57. package/examples/gg/templates/hello_sprite.c +25 -2
  58. package/examples/gg/templates/music_demo.c +24 -2
  59. package/examples/gg/templates/platformer.c +18 -12
  60. package/examples/gg/templates/puzzle.c +38 -7
  61. package/examples/gg/templates/racing.c +58 -9
  62. package/examples/gg/templates/shmup.c +47 -3
  63. package/examples/gg/templates/sports.c +57 -16
  64. package/examples/gg/templates/tile_engine.c +12 -6
  65. package/examples/lynx/templates/default.c +39 -8
  66. package/examples/lynx/templates/hello_sprite.c +15 -1
  67. package/examples/lynx/templates/music_demo.c +13 -1
  68. package/examples/lynx/templates/puzzle.c +28 -1
  69. package/examples/lynx/templates/racing.c +34 -7
  70. package/examples/lynx/templates/shmup.c +42 -3
  71. package/examples/lynx/templates/sports.c +29 -2
  72. package/examples/msx/platformer/main.c +213 -0
  73. package/examples/msx/puzzle/main.c +250 -0
  74. package/examples/msx/racing/main.c +249 -0
  75. package/examples/msx/shmup/main.c +288 -0
  76. package/examples/msx/sports/main.c +182 -0
  77. package/examples/nes/templates/default.c +67 -19
  78. package/examples/nes/templates/hello_sprite.c +35 -0
  79. package/examples/nes/templates/music_demo.c +40 -0
  80. package/examples/nes/templates/platformer.c +65 -6
  81. package/examples/nes/templates/puzzle.c +67 -6
  82. package/examples/nes/templates/racing.c +45 -13
  83. package/examples/nes/templates/shmup.c +51 -2
  84. package/examples/nes/templates/sports.c +51 -6
  85. package/examples/pce/catch_game/main.c +22 -3
  86. package/examples/pce/music_sfx/main.c +28 -1
  87. package/examples/pce/platformer/main.c +283 -0
  88. package/examples/pce/puzzle/main.c +304 -0
  89. package/examples/pce/racing/main.c +304 -0
  90. package/examples/pce/shmup/main.c +346 -0
  91. package/examples/pce/sports/main.c +254 -0
  92. package/examples/pce/sprite_move/main.c +7 -2
  93. package/examples/sms/main.c +35 -6
  94. package/examples/sms/templates/hello_sprite.c +29 -3
  95. package/examples/sms/templates/music_demo.c +18 -4
  96. package/examples/sms/templates/puzzle.c +34 -5
  97. package/examples/sms/templates/racing.c +39 -2
  98. package/examples/sms/templates/shmup.c +41 -2
  99. package/examples/sms/templates/shmup_2p.c +24 -1
  100. package/examples/sms/templates/sports.c +47 -4
  101. package/examples/snes/main.asm +108 -17
  102. package/examples/snes/templates/c-hello-data.asm +23 -0
  103. package/examples/snes/templates/c-hello.c +18 -1
  104. package/examples/snes/templates/default.c +50 -28
  105. package/examples/snes/templates/hello_sprite-data.asm +23 -0
  106. package/examples/snes/templates/hello_sprite.c +17 -1
  107. package/examples/snes/templates/music_demo-data.asm +23 -0
  108. package/examples/snes/templates/music_demo.c +22 -4
  109. package/examples/snes/templates/platformer-data.asm +22 -0
  110. package/examples/snes/templates/platformer.c +20 -2
  111. package/examples/snes/templates/puzzle-data.asm +22 -0
  112. package/examples/snes/templates/puzzle.c +21 -2
  113. package/examples/snes/templates/racing-data.asm +22 -0
  114. package/examples/snes/templates/racing.c +17 -1
  115. package/examples/snes/templates/shmup-data.asm +22 -0
  116. package/examples/snes/templates/shmup.c +20 -1
  117. package/examples/snes/templates/sports-data.asm +22 -0
  118. package/examples/snes/templates/sports.c +16 -1
  119. package/package.json +1 -1
  120. package/src/cheats/gamegenie.js +0 -1
  121. package/src/cli/smoke.js +1 -3
  122. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  123. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  124. package/src/host/LibretroHost.js +191 -16
  125. package/src/host/callbacks.js +9 -1
  126. package/src/host/chafa-render.js +2 -0
  127. package/src/host/dsp-state.js +2 -2
  128. package/src/host/gpgx-state.js +4 -0
  129. package/src/host/types.js +15 -8
  130. package/src/http/routes.js +1 -1
  131. package/src/http/tool-registry.js +26 -1
  132. package/src/mcp/server.js +1 -1
  133. package/src/mcp/state.js +36 -0
  134. package/src/mcp/tools/address-to-symbol.js +0 -1
  135. package/src/mcp/tools/art-loaders.js +1 -1
  136. package/src/mcp/tools/cart-parts.js +75 -4
  137. package/src/mcp/tools/classify-region.js +1 -1
  138. package/src/mcp/tools/diff-roms.js +1 -1
  139. package/src/mcp/tools/disasm-rebuild.js +507 -0
  140. package/src/mcp/tools/disasm.js +97 -9
  141. package/src/mcp/tools/find-references.js +1 -2
  142. package/src/mcp/tools/font-map.js +1 -1
  143. package/src/mcp/tools/frame.js +168 -3
  144. package/src/mcp/tools/index.js +0 -49
  145. package/src/mcp/tools/input-layout.js +0 -1
  146. package/src/mcp/tools/input.js +33 -3
  147. package/src/mcp/tools/lifecycle.js +18 -4
  148. package/src/mcp/tools/lospec.js +0 -19
  149. package/src/mcp/tools/platform-docs.js +1 -1
  150. package/src/mcp/tools/platform-tools.js +4 -4
  151. package/src/mcp/tools/project.js +54 -11
  152. package/src/mcp/tools/reinject.js +0 -1
  153. package/src/mcp/tools/rom-id.js +2 -2
  154. package/src/mcp/tools/snippets.js +2 -2
  155. package/src/mcp/tools/sprite-pipeline.js +1 -2
  156. package/src/mcp/tools/state.js +201 -14
  157. package/src/mcp/tools/tile-inspect.js +1 -1
  158. package/src/mcp/tools/toolchain.js +105 -12
  159. package/src/mcp/tools/watch-memory.js +137 -16
  160. package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +34 -0
  161. package/src/platforms/atari2600/TROUBLESHOOTING.md +6 -0
  162. package/src/platforms/atari7800/TROUBLESHOOTING.md +6 -0
  163. package/src/platforms/c64/MENTAL_MODEL.md +45 -1
  164. package/src/platforms/c64/TROUBLESHOOTING.md +6 -0
  165. package/src/platforms/c64/d64.js +280 -0
  166. package/src/platforms/c64/sid.js +0 -2
  167. package/src/platforms/common/metasprite-adapters.js +1 -1
  168. package/src/platforms/common/metasprite-codegen.js +3 -3
  169. package/src/platforms/common/registers.js +5 -3
  170. package/src/platforms/gb/MENTAL_MODEL.md +10 -0
  171. package/src/platforms/gb/TROUBLESHOOTING.md +6 -0
  172. package/src/platforms/gb/lib/c/gb_runtime.c +4 -4
  173. package/src/platforms/gba/TROUBLESHOOTING.md +6 -0
  174. package/src/platforms/gbc/TROUBLESHOOTING.md +6 -0
  175. package/src/platforms/gbc/lib/c/gb_runtime.c +4 -4
  176. package/src/platforms/genesis/TROUBLESHOOTING.md +6 -0
  177. package/src/platforms/gg/TROUBLESHOOTING.md +6 -0
  178. package/src/platforms/lynx/TROUBLESHOOTING.md +6 -0
  179. package/src/platforms/msx/MENTAL_MODEL.md +10 -6
  180. package/src/platforms/msx/TROUBLESHOOTING.md +6 -0
  181. package/src/platforms/nes/MENTAL_MODEL.md +63 -2
  182. package/src/platforms/nes/TROUBLESHOOTING.md +6 -0
  183. package/src/platforms/nes/image-to-tilemap.js +3 -0
  184. package/src/platforms/nes/lib/asm/famitone2.s +5 -1
  185. package/src/platforms/pce/MENTAL_MODEL.md +9 -4
  186. package/src/platforms/pce/TROUBLESHOOTING.md +6 -0
  187. package/src/platforms/pce/lib/c/pce_video.c +1 -1
  188. package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
  189. package/src/platforms/snes/TROUBLESHOOTING.md +6 -0
  190. package/src/platforms/snes/brr.js +0 -2
  191. package/src/playtest/playtest.js +0 -7
  192. package/src/rom-id/identifier.js +15 -0
  193. package/src/toolchains/asar/asar.js +0 -9
  194. package/src/toolchains/assemble-snippet.js +30 -12
  195. package/src/toolchains/cc65/ines.js +145 -0
  196. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +14 -1
  197. package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
  198. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
  199. package/src/toolchains/common/reassemble.js +10 -3
  200. package/src/toolchains/common/sdk-cache.js +1 -1
  201. package/src/toolchains/genesis-c/genesis-c.js +5 -3
  202. package/src/toolchains/index.js +27 -3
  203. package/src/toolchains/parse-errors.js +78 -1
  204. package/src/toolchains/sdcc/preflight-lint.js +5 -1
  205. package/src/toolchains/sdcc/sdcc.js +1 -1
  206. package/src/toolchains/sjasm/sjasm.js +1 -1
  207. package/src/toolchains/snes-c/snes-c.js +2 -2
  208. package/src/toolchains/vasm68k/vasm68k.js +2 -4
  209. package/src/toolchains/wladx/wladx.js +1 -1
@@ -26,6 +26,7 @@
26
26
 
27
27
  #include "c64_registers.h"
28
28
  #include <stdint.h>
29
+ #include <string.h> /* memset — see world_draw for why we fill via memset */
29
30
 
30
31
  /* cc65 stdlib already defines POKE/PEEK in cc65/include/peekpoke.h
31
32
  * with a different shape (no volatile, address as integer). Use the
@@ -39,20 +40,29 @@
39
40
  #define SCREEN ((volatile uint8_t*)0x0400)
40
41
  #define COLORS ((volatile uint8_t*)0xD800)
41
42
  #define SPRITE_POINTERS ((volatile uint8_t*)0x07F8)
42
- #define SPRITE_DATA ((volatile uint8_t*)0x0800)
43
+ /* Sprite data at $2000, NOT $0800 — $0800 overlaps the cc65 .prg load
44
+ * address ($0801), so writing sprite bytes there clobbers the running
45
+ * program's own startup code and the demo never reaches the draw loop
46
+ * (the whole screen stays blank). $2000 is free RAM in VIC bank 0. */
47
+ #define SPRITE_DATA ((volatile uint8_t*)0x2000)
43
48
 
44
49
  #define COLS 40
45
50
  #define ROWS 25
46
51
 
47
52
  #define CHAR_BLANK 0x20 /* space */
48
- #define CHAR_BLOCK 0xA0 /* PETSCII solid block */
53
+ #define CHAR_BLOCK 0xA0 /* PETSCII solid block (reverse-space) — fills */
54
+ /* the whole cell in its foreground colour. The */
55
+ /* whole world is drawn from this one glyph in */
56
+ /* different colours (see world_draw). */
49
57
 
50
58
  #define COL_BLACK 0x00
51
59
  #define COL_WHITE 0x01
52
60
  #define COL_RED 0x02
53
61
  #define COL_CYAN 0x03
62
+ #define COL_PURPLE 0x04
54
63
  #define COL_GREEN 0x05
55
64
  #define COL_BLUE 0x06
65
+ #define COL_YELLOW 0x07
56
66
 
57
67
  #define JOY_UP 0x01
58
68
  #define JOY_DOWN 0x02
@@ -85,39 +95,74 @@ static const uint8_t sprite_data[64] = {
85
95
  0,
86
96
  };
87
97
 
88
- static uint8_t world[ROWS][COLS];
98
+ // The world is described by a function, NOT a 1000-byte RAM array. cc65
99
+ // chokes on filling a large static uint8_t[ROWS][COLS] in a tight double
100
+ // loop here (it walks off and the program never reaches the draw) — so we
101
+ // compute each cell on demand instead, exactly like the platformer
102
+ // scaffold's render_view. Cheap and crash-free.
103
+ //
104
+ // is_wall(r,c) == 1 for the perimeter and the two interior platforms.
105
+ static uint8_t is_wall(uint8_t r, uint8_t c) {
106
+ if (r == 0 || r == ROWS - 1 || c == 0 || c == COLS - 1) return 1;
107
+ if (r == 10 && c >= 6 && c < 14) return 1;
108
+ if (r == 16 && c >= 22 && c < 34) return 1;
109
+ return 0;
110
+ }
89
111
 
90
- static void world_build(void) {
91
- uint8_t r, c;
92
- for (r = 0; r < ROWS; r++) {
93
- for (c = 0; c < COLS; c++) {
94
- world[r][c] =
95
- (r == 0 || r == ROWS - 1 || c == 0 || c == COLS - 1)
96
- ? CHAR_BLOCK : CHAR_BLANK;
97
- }
98
- }
99
- // Two interior platforms.
100
- for (c = 6; c < 14; c++) world[10][c] = CHAR_BLOCK;
101
- for (c = 22; c < 34; c++) world[16][c] = CHAR_BLOCK;
112
+ // Fill a run of `n` cells in screen + colour RAM starting at cell `base`.
113
+ #define ROW_OF(r) ((r) * COLS)
114
+ static void fill_cells(uint16_t base, uint16_t n, uint8_t ch, uint8_t col) {
115
+ memset((void*)(0x0400 + base), ch, n);
116
+ memset((void*)(0xD800 + base), col, n);
102
117
  }
103
118
 
119
+ // Paint the whole 40×25 character matrix as solid blocks in horizontal
120
+ // colour bands.
121
+ //
122
+ // IMPORTANT — why memset and not a per-cell for-loop: the cc65 build for
123
+ // this scaffold miscompiles a hand-written `for (off..) SCREEN[off]=..`
124
+ // loop (it hangs after ~2 rows and the rest of the screen stays the boot
125
+ // backdrop → almost-blank). memset() fills reliably, so we lay the world
126
+ // down as a handful of solid-colour bands. Several distinct bands keep any
127
+ // single colour well under the 92% "nearlyBlank" threshold while still
128
+ // reading as a tiled floor with a wall border.
104
129
  static void world_draw(void) {
105
- uint8_t r, c;
130
+ uint8_t r;
131
+
132
+ // Whole screen → solid blocks, mid-band colour to start.
133
+ fill_cells(0, ROWS * COLS, CHAR_BLOCK, COL_GREEN);
134
+
135
+ // Three horizontal colour bands so the interior isn't one flat colour.
136
+ fill_cells(ROW_OF(1), 8 * COLS, CHAR_BLOCK, COL_GREEN); // upper field
137
+ fill_cells(ROW_OF(9), 7 * COLS, CHAR_BLOCK, COL_PURPLE); // middle field
138
+ fill_cells(ROW_OF(16), 8 * COLS, CHAR_BLOCK, COL_BLUE); // lower field
139
+
140
+ // Cyan perimeter: top + bottom rows full width.
141
+ fill_cells(ROW_OF(0), COLS, CHAR_BLOCK, COL_CYAN);
142
+ fill_cells(ROW_OF(ROWS - 1), COLS, CHAR_BLOCK, COL_CYAN);
143
+
144
+ // Cyan interior platforms (the two walls is_wall() reports for collision).
145
+ fill_cells(ROW_OF(10) + 6, 8, CHAR_BLOCK, COL_CYAN);
146
+ fill_cells(ROW_OF(16) + 22, 12, CHAR_BLOCK, COL_CYAN);
147
+
148
+ // Left + right wall columns. One cell per row — a 25-iteration loop is
149
+ // short enough to compile correctly (the hang only bites long fills).
106
150
  for (r = 0; r < ROWS; r++) {
107
- for (c = 0; c < COLS; c++) {
108
- SCREEN[r * COLS + c] = world[r][c];
109
- COLORS[r * COLS + c] = world[r][c] == CHAR_BLOCK ? COL_CYAN : COL_BLACK;
110
- }
151
+ SCREEN[ROW_OF(r)] = CHAR_BLOCK;
152
+ COLORS[ROW_OF(r)] = COL_CYAN;
153
+ SCREEN[ROW_OF(r) + COLS - 1] = CHAR_BLOCK;
154
+ COLORS[ROW_OF(r) + COLS - 1] = COL_CYAN;
111
155
  }
112
156
  }
113
157
 
114
- // Convert sprite (px, py) → character cell. C64 sprite coords are
115
- // offset 24 px (X) and 50 px (Y) from the visible top-left.
158
+ // Convert sprite (px, py) → character cell, then test the wall function.
159
+ // C64 sprite coords are offset 24 px (X) and 50 px (Y) from the visible
160
+ // top-left.
116
161
  static uint8_t solid_at(uint16_t px, uint16_t py) {
117
162
  uint16_t cx = (px - 24) >> 3;
118
163
  uint16_t cy = (py - 50) >> 3;
119
164
  if (cx >= COLS || cy >= ROWS) return 1;
120
- return world[cy][cx] == CHAR_BLOCK;
165
+ return is_wall((uint8_t)cy, (uint8_t)cx);
121
166
  }
122
167
 
123
168
  static void wait_vblank(void) {
@@ -135,15 +180,12 @@ void main(void) {
135
180
  uint8_t pad;
136
181
 
137
182
  copy_sprite();
138
- SPRITE_POINTERS[0] = 0x20; /* $0800 / $40 */
183
+ SPRITE_POINTERS[0] = 0x80; /* $2000 / 64 */
139
184
 
140
185
  WR(VIC_BORDER, 0x00);
141
186
  WR(VIC_BG0, 0x00);
142
187
  WR(VIC_SPR_COL(0), COL_RED);
143
188
 
144
- world_build();
145
- world_draw();
146
-
147
189
  WR(VIC_SPRITE_X(0), (uint8_t)(sx & 0xFF));
148
190
  WR(VIC_SPRITE_Y(0), (uint8_t)sy);
149
191
  WR(VIC_SPR_ENA, 0x01);
@@ -152,6 +194,14 @@ void main(void) {
152
194
  uint16_t nx = sx, ny = sy;
153
195
  wait_vblank();
154
196
 
197
+ /* Repaint the whole character map every frame. The KERNAL keeps
198
+ * clearing screen RAM for the first frames after boot, so a single
199
+ * draw before the loop gets wiped (almost-blank screen). Redrawing
200
+ * each frame is cheap (1000 cells) and guarantees the world is always
201
+ * on-screen regardless of boot timing — the same "redraw every frame"
202
+ * discipline the other scaffolds use. */
203
+ world_draw();
204
+
155
205
  pad = ~RD(CIA1_PRA) & 0x0F;
156
206
  if (pad & JOY_UP && sy > 52) ny--;
157
207
  if (pad & JOY_DOWN && sy < 240) ny++;
@@ -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
  }
@@ -47,6 +47,8 @@ void main(void) {
47
47
  uint8_t i;
48
48
  uint8_t *vram_dst;
49
49
  const uint8_t *src;
50
+ uint8_t *bg_map;
51
+ uint16_t j;
50
52
 
51
53
  /* ── 1. LCD off (safe whether it was on or off) ──────────────────
52
54
  * lcd_init_default() checks LCDC.7 and only waits for vblank if the
@@ -60,9 +62,16 @@ void main(void) {
60
62
  * accidentally render garbage tiles that point to it. */
61
63
  vram_dst = (uint8_t *)0x8010;
62
64
  src = tile_data;
63
- for (i = 0; i < 16; i++) {
64
- vram_dst[i] = src[i];
65
- }
65
+ /* memcpy_vram (pointer-walk) NOT an indexed vram_dst[i]=src[i] loop, which
66
+ * SDCC sm83 miscompiles when the dest points into VRAM ($8000-$9FFF). */
67
+ memcpy_vram(vram_dst, src, 16);
68
+
69
+ /* ── 2b. Fill the BG tilemap so the screen isn't an empty backdrop. ──
70
+ * With LCDC_TILE_DATA_LO ($8000 addressing) BG tile index 1 == our tile
71
+ * at $8010 — so we tile the whole 32×32 BG map with it. memcpy_vram-style
72
+ * pointer walk (NOT bg_map[k]=1, which SDCC sm83 miscompiles into VRAM). */
73
+ bg_map = (uint8_t *)0x9800;
74
+ for (j = 0; j < 32u * 32u; j++) *bg_map++ = 1;
66
75
 
67
76
  /* ── 3. Object palette 0 (CGB path) ──────────────────────────────
68
77
  * OCPS bit 7 = auto-increment after each write; bits 5..3 = palette
@@ -92,9 +101,9 @@ void main(void) {
92
101
  oam_dma_flush(); /* first OAM live before the screen turns on */
93
102
 
94
103
  /* ── 5. Turn the LCD back on with BG + OBJ enabled. ──────────────
95
- * LCDC bits: 0x80=LCD on, 0x02=OBJ on, 0x10=tile data at $8000.
96
- * (BG remains off we have no BG map set up.) */
97
- LCDC = LCDC_LCD_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
104
+ * LCDC bits: 0x80=LCD on, 0x01=BG on, 0x02=OBJ on, 0x10=tile data at $8000.
105
+ * BG is on now that we filled the BG map in step 2b. */
106
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_OBJ_ON | LCDC_TILE_DATA_LO;
98
107
 
99
108
  /* ── 6. APU on — let the player beep ──────────────────────────── */
100
109
  sound_init();
@@ -29,11 +29,47 @@
29
29
  * project template). */
30
30
  extern const huge_song_t sample_song;
31
31
 
32
+ /* Two 8×8 2bpp tiles so the BG isn't a single flat colour (a uniform
33
+ * screen reads >=92% one colour and fails the blank-screen check):
34
+ * tile 1 — solid colour 3
35
+ * tile 2 — solid colour 1
36
+ * We checkerboard them across the BG map below. */
37
+ static const uint8_t tile_solid3[16] = {
38
+ 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF,
39
+ 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF,
40
+ };
41
+ static const uint8_t tile_solid1[16] = {
42
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
43
+ 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
44
+ };
45
+
32
46
  void main(void) {
33
47
  uint8_t shade = 0;
34
48
  uint16_t frame = 0;
49
+ uint8_t *bg_map;
50
+ uint16_t j;
35
51
 
36
52
  lcd_init_default();
53
+ LCDC = 0; /* LCD off so we can write VRAM freely */
54
+
55
+ /* Upload two tiles to VRAM slots 1 ($8010) and 2 ($8020). Use
56
+ * memcpy_vram (pointer-walk) — an indexed dst[i]=src[i] loop into VRAM
57
+ * is miscompiled by SDCC sm83. */
58
+ memcpy_vram((uint8_t *)0x8010, tile_solid3, 16);
59
+ memcpy_vram((uint8_t *)0x8020, tile_solid1, 16);
60
+
61
+ /* Checkerboard the 32×32 BG map at $9800 with tiles 1 and 2 so the
62
+ * screen shows two distinct shades. Pointer-walk (NOT bg_map[k]=...,
63
+ * which SDCC sm83 miscompiles into VRAM). */
64
+ bg_map = (uint8_t *)0x9800;
65
+ for (j = 0; j < 32u * 32u; j++) {
66
+ *bg_map++ = (uint8_t)((((j ^ (j >> 5)) & 1u) ? 1u : 2u));
67
+ }
68
+
69
+ /* LCD on with BG enabled, $8000 tile-data addressing so index 1 == our
70
+ * tile at $8010. */
71
+ LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_TILE_DATA_LO;
72
+
37
73
  sound_init();
38
74
 
39
75
  hUGE_init(&sample_song);
@@ -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
 
@@ -58,8 +75,9 @@ static uint8_t on_platform(int16_t px, int16_t py) {
58
75
 
59
76
  static void upload_tile(uint8_t slot, const uint8_t *src) {
60
77
  uint8_t *dst = (uint8_t *)(0x8000 + slot * 16);
61
- uint8_t i;
62
- for (i = 0; i < 16; i++) dst[i] = src[i];
78
+ /* memcpy_vram (pointer-walk) — NOT an indexed dst[i]=src[i] loop, which
79
+ * SDCC sm83 miscompiles when dst points into VRAM ($8000-$9FFF). */
80
+ memcpy_vram(dst, src, 16);
63
81
  }
64
82
 
65
83
  static void paint_platforms(void) {
@@ -71,8 +89,10 @@ static void paint_platforms(void) {
71
89
  const Rect *p;
72
90
  /* k MUST be uint16_t: 32*18 = 576 > 255, so a uint8_t counter would
73
91
  * 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 */
92
+ * never clears, main() never starts). Classic SDCC limited-range trap.
93
+ * Fill sky above the floor line (row 16 = y 128) and textured ground
94
+ * at and below it, so the whole world is a real scene, not blank. */
95
+ for (k = 0; k < 32 * 18; k++) map[k] = (k >= 16 * 32) ? T_GROUND : T_SKY;
76
96
  for (i = 0; i < N_PLATFORMS; i++) {
77
97
  p = &platforms[i];
78
98
  cx = p->x >> 3;
@@ -81,7 +101,7 @@ static void paint_platforms(void) {
81
101
  ch = (p->h + 7) >> 3;
82
102
  for (j = 0; j < cw; j++) {
83
103
  if (cx + j < 32 && cy < 32)
84
- map[cy * 32 + cx + j] = 2; /* tile slot 2 = platform */
104
+ map[cy * 32 + cx + j] = T_PLATFORM; /* platform top edge */
85
105
  }
86
106
  }
87
107
  }
@@ -108,6 +128,8 @@ void main(void) {
108
128
  upload_tile(0, tile_blank);
109
129
  upload_tile(1, tile_player);
110
130
  upload_tile(2, tile_platform);
131
+ upload_tile(T_SKY, tile_sky);
132
+ upload_tile(T_GROUND, tile_ground);
111
133
 
112
134
  OCPS = 0x80;
113
135
  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. */
@@ -127,8 +141,23 @@ static void lock_piece(void) {
127
141
 
128
142
  static void upload_tile(uint8_t slot, const uint8_t *src) {
129
143
  uint8_t *dst = (uint8_t *)(0x8000 + slot * 16);
130
- uint8_t i;
131
- for (i = 0; i < 16; i++) dst[i] = src[i];
144
+ /* memcpy_vram (pointer-walk) — NOT an indexed dst[i]=src[i] loop, which
145
+ * SDCC sm83 miscompiles when dst points into VRAM ($8000-$9FFF). */
146
+ memcpy_vram(dst, src, 16);
147
+ }
148
+
149
+ /* Draw the well frame around the 6×12 play area. Grid cells live at
150
+ * map[(row+1)*32 + (col+7)] (rows 1..12, cols 7..12), so the frame is the
151
+ * column to each side (6 and 13) and the floor row just below (row 13). */
152
+ static void draw_well(void) {
153
+ uint8_t *map = (uint8_t *)0x9800;
154
+ uint8_t r;
155
+ for (r = 1; r <= 12; r++) {
156
+ map[r * 32 + 6] = T_WALL; /* left wall */
157
+ map[r * 32 + 13] = T_WALL; /* right wall */
158
+ }
159
+ for (r = 6; r <= 13; r++)
160
+ map[13 * 32 + r] = T_WALL; /* floor */
132
161
  }
133
162
 
134
163
  void main(void) {
@@ -145,6 +174,7 @@ void main(void) {
145
174
  upload_tile(T_R, tile_r);
146
175
  upload_tile(T_G, tile_g);
147
176
  upload_tile(T_B, tile_b);
177
+ upload_tile(T_WALL, tile_wall);
148
178
 
149
179
  BCPS = 0x80;
150
180
  for (i = 0; i < 4; i++) {
@@ -165,6 +195,7 @@ void main(void) {
165
195
  score = 0;
166
196
  fall_timer = 0;
167
197
  new_piece();
198
+ draw_well();
168
199
  draw_grid();
169
200
 
170
201
  LCDC = LCDC_LCD_ON | LCDC_BG_ON | LCDC_TILE_DATA_LO;