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
@@ -14,6 +14,8 @@
14
14
  #define P0C1 (*(volatile uint8_t*)0x21)
15
15
  #define P0C2 (*(volatile uint8_t*)0x22)
16
16
  #define P0C3 (*(volatile uint8_t*)0x23)
17
+ #define P1C1 (*(volatile uint8_t*)0x25)
18
+ #define P2C1 (*(volatile uint8_t*)0x29)
17
19
  #define MSTAT (*(volatile uint8_t*)0x28)
18
20
  #define DPPH (*(volatile uint8_t*)0x2C)
19
21
  #define DPPL (*(volatile uint8_t*)0x30)
@@ -44,6 +46,40 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
44
46
 
45
47
  static uint8_t dl_empty[2] = { 0, 0 };
46
48
 
49
+ /* ── Background playfield ─────────────────────────────────────────
50
+ * Without a full-screen drawable the display list emits only the
51
+ * player and ~99% of the screen stays the flat BACKGRND colour
52
+ * (reads as "blank"). These full-width bands give the level a sky,
53
+ * a field, and a solid ground strip the player stands on.
54
+ *
55
+ * A single DL drawable is at most 32 bytes = 128 px wide, so a full
56
+ * 160-px line needs TWO drawables. Width = byte[3] low 5 bits (32-n);
57
+ * high 3 bits = palette. */
58
+ static const uint8_t band_pix[32] = {
59
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
60
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
61
+ };
62
+ #define MK_BAND(name, pal) static uint8_t name[11] = { \
63
+ 0, 0x40, 0, ((pal) << 5) | 0, 0, /* 128 px @ x0 */ \
64
+ 0, 0x40, 0, ((pal) << 5) | 24, 128, /* 32 px @ x128 */ \
65
+ 0 }
66
+ MK_BAND(dl_field, 1);
67
+ MK_BAND(dl_ground, 2);
68
+ /* Ground strip starts just below where the player rests (GROUND_Y). */
69
+ #define GROUND_ZONE 200
70
+
71
+ static void set_band_addr(uint8_t* dl) {
72
+ uint16_t a = (uint16_t)(uintptr_t)band_pix;
73
+ dl[0] = dl[5] = (uint8_t)(a & 0xFF);
74
+ dl[2] = dl[7] = (uint8_t)(a >> 8);
75
+ }
76
+
77
+ static uint16_t bg_zone_dl(int zone) {
78
+ if (zone >= GROUND_ZONE) return (uint16_t)(uintptr_t)dl_ground;
79
+ if (zone >= 28) return (uint16_t)(uintptr_t)dl_field;
80
+ return (uint16_t)(uintptr_t)dl_empty;
81
+ }
82
+
47
83
  #define DLL_ZONES 243
48
84
  static uint8_t dll[DLL_ZONES * 3];
49
85
 
@@ -60,7 +96,6 @@ static void set_dll_entry(int idx, uint16_t dl_ptr) {
60
96
  }
61
97
 
62
98
  static void build_dll(uint8_t y) {
63
- uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
64
99
  int i;
65
100
  for (i = 0; i < DLL_ZONES; i++) {
66
101
  uint16_t dl;
@@ -74,7 +109,7 @@ static void build_dll(uint8_t y) {
74
109
  case 5: dl = (uint16_t)(uintptr_t)dl_row5; break;
75
110
  case 6: dl = (uint16_t)(uintptr_t)dl_row6; break;
76
111
  case 7: dl = (uint16_t)(uintptr_t)dl_row7; break;
77
- default: dl = empty; break;
112
+ default: dl = bg_zone_dl(i); break;
78
113
  }
79
114
  set_dll_entry(i, dl);
80
115
  }
@@ -112,13 +147,17 @@ void main(void) {
112
147
  set_dl_addr(dl_row5, player_row5);
113
148
  set_dl_addr(dl_row6, player_row6);
114
149
  set_dl_addr(dl_row7, player_row7);
150
+ set_band_addr(dl_field);
151
+ set_band_addr(dl_ground);
115
152
  set_x((uint8_t)px);
116
153
  build_dll((uint8_t)(py16 >> 4));
117
154
 
118
- BACKGRND = 0x84;
119
- P0C1 = 0x46;
155
+ BACKGRND = 0x84; /* sky */
156
+ P0C1 = 0x46; /* player */
120
157
  P0C2 = 0x0F;
121
158
  P0C3 = 0x36;
159
+ P1C1 = 0x96; /* distant field (teal) */
160
+ P2C1 = 0x24; /* ground (brown) */
122
161
  CHARBASE = 0;
123
162
  OFFSET = 0;
124
163
 
@@ -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)
@@ -53,6 +55,37 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
53
55
 
54
56
  static uint8_t dl_empty[2] = { 0, 0 };
55
57
 
58
+ /* ── Background well ──────────────────────────────────────────────
59
+ * Without a full-screen drawable the display list emits only the
60
+ * falling block and ~99% of the screen stays the flat BACKGRND colour
61
+ * (reads as "blank"). Each well zone draws three full-width segments:
62
+ * a side wall (palette 2), the playfield well in the centre where the
63
+ * piece falls (palette 1), the other wall (palette 2). Width =
64
+ * byte[3] low 5 bits (32-n); high 3 bits = palette. */
65
+ static const uint8_t band_pix[16] = {
66
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
67
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
68
+ };
69
+ /* 12 bytes (48 px) wall @ x0, 16 bytes (64 px) well @ x48,
70
+ * 12 bytes (48 px) wall @ x112, terminator. */
71
+ static uint8_t dl_well[16] = {
72
+ 0, 0x40, 0, (2 << 5) | 20, 0,
73
+ 0, 0x40, 0, (1 << 5) | 16, 48,
74
+ 0, 0x40, 0, (2 << 5) | 20, 112,
75
+ 0
76
+ };
77
+
78
+ static void set_well_addr(void) {
79
+ uint16_t a = (uint16_t)(uintptr_t)band_pix;
80
+ dl_well[0] = dl_well[5] = dl_well[10] = (uint8_t)(a & 0xFF);
81
+ dl_well[2] = dl_well[7] = dl_well[12] = (uint8_t)(a >> 8);
82
+ }
83
+
84
+ static uint16_t bg_zone_dl(int zone) {
85
+ if (zone >= 32 && zone < 200) return (uint16_t)(uintptr_t)dl_well;
86
+ return (uint16_t)(uintptr_t)dl_empty;
87
+ }
88
+
56
89
  #define DLL_ZONES 243
57
90
  static uint8_t dll[DLL_ZONES * 3];
58
91
 
@@ -80,7 +113,6 @@ static void set_x(uint8_t x) {
80
113
  }
81
114
 
82
115
  static void build_dll(int y) {
83
- uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
84
116
  int i;
85
117
  for (i = 0; i < DLL_ZONES; i++) {
86
118
  uint16_t dl;
@@ -94,7 +126,7 @@ static void build_dll(int y) {
94
126
  case 5: dl = (uint16_t)(uintptr_t)dl_row5; break;
95
127
  case 6: dl = (uint16_t)(uintptr_t)dl_row6; break;
96
128
  case 7: dl = (uint16_t)(uintptr_t)dl_row7; break;
97
- default: dl = empty; break;
129
+ default: dl = bg_zone_dl(i); break;
98
130
  }
99
131
  set_dll_entry(i, dl);
100
132
  }
@@ -120,13 +152,16 @@ void main(void) {
120
152
  piece_x_col = COLS / 2;
121
153
  piece_y = TOP_Y;
122
154
  color_cycle = 0;
155
+ set_well_addr();
123
156
  set_x((uint8_t)(60 + piece_x_col * CELL_W_PIX));
124
157
  build_dll(piece_y);
125
158
 
126
- BACKGRND = 0x00;
127
- P0C1 = 0x46; /* red */
159
+ BACKGRND = 0x00; /* black surround */
160
+ P0C1 = 0x46; /* falling piece (red) */
128
161
  P0C2 = 0x46;
129
162
  P0C3 = 0x46;
163
+ P1C1 = 0x02; /* well interior (dark blue-grey) */
164
+ P2C1 = 0x08; /* well walls (steel) */
130
165
  CHARBASE = 0;
131
166
  OFFSET = 0;
132
167
 
@@ -17,6 +17,8 @@
17
17
  #define P0C1 (*(volatile uint8_t*)0x21)
18
18
  #define P0C2 (*(volatile uint8_t*)0x22)
19
19
  #define P0C3 (*(volatile uint8_t*)0x23)
20
+ #define P1C1 (*(volatile uint8_t*)0x25)
21
+ #define P2C1 (*(volatile uint8_t*)0x29)
20
22
  #define MSTAT (*(volatile uint8_t*)0x28)
21
23
  #define DPPH (*(volatile uint8_t*)0x2C)
22
24
  #define DPPL (*(volatile uint8_t*)0x30)
@@ -44,6 +46,37 @@ MK_DL(dl_row4); MK_DL(dl_row5); MK_DL(dl_row6); MK_DL(dl_row7);
44
46
 
45
47
  static uint8_t dl_empty[2] = { 0, 0 };
46
48
 
49
+ /* ── Background road ──────────────────────────────────────────────
50
+ * Without a full-screen drawable the display list emits only the car
51
+ * and ~99% of the screen stays the flat BACKGRND colour (reads as
52
+ * "blank"). Each road zone draws three full-width segments: grass on
53
+ * the left (palette 1), the grey road down the centre (palette 2),
54
+ * grass on the right (palette 1). Width = byte[3] low 5 bits (32-n);
55
+ * high 3 bits = palette. */
56
+ static const uint8_t band_pix[16] = {
57
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,
58
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
59
+ };
60
+ /* 8 bytes (32 px) grass @ x0, 16 bytes (64 px) road @ x32,
61
+ * 8 bytes (32 px) grass @ x96, terminator. */
62
+ static uint8_t dl_road[16] = {
63
+ 0, 0x40, 0, (1 << 5) | 24, 0,
64
+ 0, 0x40, 0, (2 << 5) | 16, 32,
65
+ 0, 0x40, 0, (1 << 5) | 24, 96,
66
+ 0
67
+ };
68
+
69
+ static void set_road_addr(void) {
70
+ uint16_t a = (uint16_t)(uintptr_t)band_pix;
71
+ dl_road[0] = dl_road[5] = dl_road[10] = (uint8_t)(a & 0xFF);
72
+ dl_road[2] = dl_road[7] = dl_road[12] = (uint8_t)(a >> 8);
73
+ }
74
+
75
+ static uint16_t bg_zone_dl(int zone) {
76
+ if (zone >= 16 && zone < 220) return (uint16_t)(uintptr_t)dl_road;
77
+ return (uint16_t)(uintptr_t)dl_empty;
78
+ }
79
+
47
80
  #define DLL_ZONES 243
48
81
  static uint8_t dll[DLL_ZONES * 3];
49
82
 
@@ -71,7 +104,6 @@ static void set_x(uint8_t x) {
71
104
  }
72
105
 
73
106
  static void build_dll(void) {
74
- uint16_t empty = (uint16_t)(uintptr_t)dl_empty;
75
107
  int i;
76
108
  for (i = 0; i < DLL_ZONES; i++) {
77
109
  uint16_t dl;
@@ -85,7 +117,7 @@ static void build_dll(void) {
85
117
  case 5: dl = (uint16_t)(uintptr_t)dl_row5; break;
86
118
  case 6: dl = (uint16_t)(uintptr_t)dl_row6; break;
87
119
  case 7: dl = (uint16_t)(uintptr_t)dl_row7; break;
88
- default: dl = empty; break;
120
+ default: dl = bg_zone_dl(i); break;
89
121
  }
90
122
  set_dll_entry(i, dl);
91
123
  }
@@ -110,13 +142,16 @@ void main(void) {
110
142
  set_dl_addr(dl_row7, car_row7);
111
143
 
112
144
  lane = 1;
145
+ set_road_addr();
113
146
  set_x(lane_xs[lane]);
114
147
  build_dll();
115
148
 
116
- BACKGRND = 0x88;
117
- P0C1 = 0x46;
149
+ BACKGRND = 0x88; /* sky/horizon */
150
+ P0C1 = 0x46; /* car */
118
151
  P0C2 = 0x0F;
119
152
  P0C3 = 0x36;
153
+ P1C1 = 0xC8; /* roadside grass (green) */
154
+ P2C1 = 0x06; /* road surface (grey) */
120
155
  CHARBASE = 0;
121
156
  OFFSET = 0;
122
157
 
@@ -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);