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
@@ -43,6 +43,37 @@ extern const unsigned char music_data[];
43
43
 
44
44
  static const unsigned char bg_colors[4] = { 0x0F, 0x01, 0x21, 0x31 };
45
45
 
46
+ /* Two BG tiles so the backdrop isn't a single flat colour (a uniform
47
+ * screen reads >=92% one colour and fails the blank-screen check):
48
+ * tile 1 — solid colour 1
49
+ * tile 2 — solid colour 2
50
+ * We checkerboard them across the nametable below. NES BG fetches from
51
+ * $1000-$1FFF under the default PPUCTRL (bit 4 set), so BG tiles upload
52
+ * there. */
53
+ static const unsigned char bg_tiles[2 * 16] = {
54
+ /* tile 1: solid colour 1 (plane 0 all set, plane 1 clear) */
55
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
56
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
57
+ /* tile 2: solid colour 2 (plane 1 all set, plane 0 clear) */
58
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
59
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
60
+ };
61
+
62
+ /* Fill nametable 0 ($2000) with a checkerboard of tiles 1 and 2 so the
63
+ * screen shows two distinct colours. Attribute table left at palette 0.
64
+ * Caller must have the PPU off. */
65
+ static void fill_bg(void) {
66
+ unsigned int addr;
67
+ unsigned char y, x;
68
+ for (y = 0; y < 30; y++) {
69
+ addr = 0x2000 + (unsigned int)y * 32;
70
+ for (x = 0; x < 32; x++) {
71
+ vram_unsafe_set(addr, (unsigned char)(((x ^ y) & 1) + 1));
72
+ ++addr;
73
+ }
74
+ }
75
+ }
76
+
46
77
  void main(void) {
47
78
  unsigned char palette[32];
48
79
  unsigned char i;
@@ -50,9 +81,18 @@ void main(void) {
50
81
  unsigned char frame = 0;
51
82
 
52
83
  for (i = 0; i < 32; i++) palette[i] = bg_colors[0];
84
+ /* BG palette 0: backdrop black, colour 1 blue, colour 2 red — gives the
85
+ * checkerboard two visibly different cells. */
86
+ palette[0] = 0x0F; /* $3F00 backdrop */
87
+ palette[1] = 0x11; /* colour 1 — blue */
88
+ palette[2] = 0x16; /* colour 2 — red */
53
89
 
54
90
  ppu_off();
55
91
  palette_load(palette);
92
+ /* Upload the two BG tiles to the BG pattern table at $1000. */
93
+ chr_ram_upload(0x1000, bg_tiles, sizeof(bg_tiles));
94
+ /* Paint the checkerboard backdrop so the screen is visibly NOT blank. */
95
+ fill_bg();
56
96
  oam_clear();
57
97
 
58
98
  /* Start the music BEFORE rendering — FamiToneInit takes a few
@@ -28,7 +28,7 @@
28
28
 
29
29
  #include "nes_runtime.h"
30
30
 
31
- /* ── Tiles: player idle/jump + platform block ────────────────────── */
31
+ /* ── Tiles: player idle/jump + platform block (SPRITES, table $0000) ── */
32
32
  static const uint8_t tile_blank[16] = { 0 };
33
33
  static const uint8_t tile_player_idle[16] = {
34
34
  0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, /* plane 0 — round blob */
@@ -43,12 +43,46 @@ static const uint8_t tile_platform[16] = {
43
43
  0, 0, 0, 0, 0, 0, 0, 0,
44
44
  };
45
45
 
46
+ /* ── BG scenery tiles (BACKGROUND pattern table $1000) ────────────────
47
+ * Painted into the nametable so the world reads as a real outdoor scene on
48
+ * boot (sky + clouds + dirt) instead of sprites floating on flat black. The
49
+ * BG backdrop colour (palette[0]) is sky blue, so the colours used here are
50
+ * cloud white (idx1), dirt brown (idx2) and grass green (idx3).
51
+ *
52
+ * BG_CLOUD — a puffy cloud (idx1) dotted across the upper sky.
53
+ * BG_DIRT — solid dirt fill (idx2) for the ground band.
54
+ * BG_GRASS — a grass-topped dirt tile (idx3 cap over idx2) for the
55
+ * surface row of the ground. */
56
+ #define BG_CLOUD 1 /* BG tile index 1 → uploaded to $1010 */
57
+ #define BG_DIRT 2 /* BG tile index 2 → uploaded to $1020 */
58
+ #define BG_GRASS 3 /* BG tile index 3 → uploaded to $1030 */
59
+ static const uint8_t bg_tile_cloud[16] = {
60
+ 0x00, 0x18, 0x3C, 0x7E, 0x7E, 0x00, 0x00, 0x00, /* plane 0 → cloud (idx1) */
61
+ 0, 0, 0, 0, 0, 0, 0, 0,
62
+ };
63
+ static const uint8_t bg_tile_dirt[16] = {
64
+ 0, 0, 0, 0, 0, 0, 0, 0, /* plane 0 clear */
65
+ 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, /* plane 1 → dirt (idx2) */
66
+ };
67
+ static const uint8_t bg_tile_grass[16] = {
68
+ 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* plane0: top 2 rows on */
69
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* plane1: all rows on */
70
+ }; /* rows 0-1 → both planes = idx3 (grass cap); rows 2-7 → plane1 = idx2 (dirt) */
71
+
46
72
  static const uint8_t palette[32] = {
47
- 0x0F, 0x10, 0x20, 0x30,
48
- 0x0F, 0x10, 0x20, 0x30,
49
- 0x0F, 0x10, 0x20, 0x30,
50
- 0x0F, 0x10, 0x20, 0x30,
51
- 0x0F, 0x21, 0x32, 0x30, /* sp0: player — blue */
73
+ /* BG0: sky-blue backdrop, cloud white (idx1), dirt brown (idx2),
74
+ * grass green (idx3) */
75
+ 0x21, 0x30, 0x17, 0x2A,
76
+ 0x21, 0x30, 0x17, 0x2A,
77
+ 0x21, 0x30, 0x17, 0x2A,
78
+ 0x21, 0x30, 0x17, 0x2A,
79
+ /* The universal backdrop ($3F00) is MIRRORED at $3F10 — the sprite
80
+ * palette-0 colour-0 slot. palette_load writes all 32 bytes in order, so
81
+ * byte 16 (this slot) is the LAST write to that mirror and therefore wins.
82
+ * Keep it equal to the BG backdrop (sky blue) or the sky renders as
83
+ * whatever colour-0 you put here, not the BG[0] above. (Sprite colour 0 is
84
+ * transparent regardless, so this never affects how sprites draw.) */
85
+ 0x21, 0x21, 0x32, 0x30, /* sp0: player — blue (colour 0 = backdrop mirror) */
52
86
  0x0F, 0x18, 0x28, 0x38, /* sp1: platforms — green */
53
87
  0x0F, 0x16, 0x06, 0x36,
54
88
  0x0F, 0x2A, 0x1A, 0x0A,
@@ -102,8 +136,33 @@ void main(void) {
102
136
  chr_ram_upload(0x0020, tile_player_jump, 16);
103
137
  chr_ram_upload(0x0030, tile_platform, 16);
104
138
 
139
+ /* BG scenery tiles go in pattern table 1 ($1000) — where the default
140
+ * PPUCTRL points the background fetch. */
141
+ chr_ram_upload(0x1010, bg_tile_cloud, 16);
142
+ chr_ram_upload(0x1020, bg_tile_dirt, 16);
143
+ chr_ram_upload(0x1030, bg_tile_grass, 16);
144
+
105
145
  palette_load(palette);
106
146
 
147
+ /* Paint the outdoor scene into the nametable while rendering is off
148
+ * (vram_unsafe_set = raw write; tile_set's NMI queue would deadlock before
149
+ * ppu_on). Sky-blue backdrop shows through the empty upper rows; we dot
150
+ * clouds across it, cap the ground with a grass row, and fill the bottom
151
+ * band with solid dirt. The sprite platforms still draw on top of this. */
152
+ {
153
+ uint16_t r, c;
154
+ /* clouds scattered through the upper sky (rows 2..9) */
155
+ for (r = 2; r < 10; r++)
156
+ for (c = (uint16_t)((r * 3) % 6); c < 32; c += 6)
157
+ vram_unsafe_set((uint16_t)(0x2000 + r * 32 + c), BG_CLOUD);
158
+ /* grass cap row + solid dirt to the bottom of the screen */
159
+ for (c = 0; c < 32; c++)
160
+ vram_unsafe_set((uint16_t)(0x2000 + 24 * 32 + c), BG_GRASS);
161
+ for (r = 25; r < 30; r++)
162
+ for (c = 0; c < 32; c++)
163
+ vram_unsafe_set((uint16_t)(0x2000 + r * 32 + c), BG_DIRT);
164
+ }
165
+
107
166
  oam_clear();
108
167
  ppu_on_all();
109
168
  sound_init();
@@ -53,12 +53,39 @@ static const uint8_t tile_block_b[16] = {
53
53
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
54
54
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
55
55
  };
56
+ /* BG scenery tiles — painted into the nametable so the playfield reads as a
57
+ * real machine on boot (without them the screen is just sprites on flat
58
+ * black). All live in the BACKGROUND pattern table ($1000); the block tiles
59
+ * above are sprites ($0000).
60
+ *
61
+ * BG_WALL — solid bordered block (idx 1, steel blue): the well frame.
62
+ * BG_BRICK — a brick/dither pattern (idx 2) tiling the cabinet wall that
63
+ * surrounds the well, so the whole screen is covered.
64
+ * BG_INNER — a faint grid speck (idx 3) lining the inside of the well so
65
+ * empty cells read as a recessed playfield, not a black hole. */
66
+ static const uint8_t tile_wall[16] = {
67
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
68
+ 0, 0, 0, 0, 0, 0, 0, 0,
69
+ };
70
+ static const uint8_t tile_brick[16] = {
71
+ 0, 0, 0, 0, 0, 0, 0, 0, /* plane 0 clear */
72
+ 0xFF, 0x80, 0x80, 0x80, 0xFF, 0x08, 0x08, 0x08, /* plane 1 → colour 2 brick */
73
+ };
74
+ static const uint8_t tile_inner[16] = {
75
+ 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, /* plane 0 specks */
76
+ 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, /* plane 1 too → colour 3 */
77
+ };
78
+ #define BG_WALL 1 /* BG tile index 1 → uploaded to $1010 */
79
+ #define BG_BRICK 2 /* BG tile index 2 → uploaded to $1020 */
80
+ #define BG_INNER 3 /* BG tile index 3 → uploaded to $1030 */
56
81
 
57
82
  static const uint8_t palette[32] = {
58
- 0x0F, 0x10, 0x20, 0x30,
59
- 0x0F, 0x10, 0x20, 0x30,
60
- 0x0F, 0x10, 0x20, 0x30,
61
- 0x0F, 0x10, 0x20, 0x30,
83
+ /* BG0: backdrop near-black, wall = steel blue (idx1), brick = brown
84
+ * (idx2), inner grid = dark grey (idx3). */
85
+ 0x0F, 0x11, 0x17, 0x00,
86
+ 0x0F, 0x11, 0x17, 0x00,
87
+ 0x0F, 0x11, 0x17, 0x00,
88
+ 0x0F, 0x11, 0x17, 0x00,
62
89
  /* Sprite palette 0: idx1=red, idx2=green, idx3=blue */
63
90
  0x0F, 0x16, 0x1A, 0x12,
64
91
  0x0F, 0x16, 0x1A, 0x12,
@@ -156,11 +183,45 @@ void main(void) {
156
183
  int8_t i;
157
184
 
158
185
  ppu_off();
159
- chr_ram_upload(0x0000, tile_blank, 16);
160
- chr_ram_upload(0x0010, tile_block_r, 16);
186
+ chr_ram_upload(0x0000, tile_blank, 16); /* sprite slot 0 */
187
+ chr_ram_upload(0x0010, tile_block_r, 16); /* sprite slots 1..3 */
161
188
  chr_ram_upload(0x0020, tile_block_g, 16);
162
189
  chr_ram_upload(0x0030, tile_block_b, 16);
190
+ chr_ram_upload(0x1010, tile_wall, 16); /* BG slot 1 (background table) */
191
+ chr_ram_upload(0x1020, tile_brick, 16); /* BG slot 2 (cabinet wall) */
192
+ chr_ram_upload(0x1030, tile_inner, 16); /* BG slot 3 (well interior) */
163
193
  palette_load(palette);
194
+
195
+ /* Draw the cabinet + well into the nametable while rendering is off
196
+ * (vram_unsafe_set = raw PPU write; the friendly tile_set queue would
197
+ * deadlock before ppu_on). The grid is 6 wide × 12 tall at pixel origin
198
+ * (80,16) → tile cols 10..15, rows 2..13; frame it one cell out. We first
199
+ * tile the WHOLE screen with brick (the machine cabinet), then carve the
200
+ * recessed well interior, then stamp the steel-blue frame on top — so the
201
+ * screen is fully covered instead of sprites floating on black. */
202
+ {
203
+ uint16_t gx0 = ORIGIN_X / 8, gy0 = ORIGIN_Y / 8; /* 10, 2 */
204
+ uint16_t gx1 = gx0 + GRID_W, gy1 = gy0 + GRID_H; /* 16, 14 (exclusive) */
205
+ uint16_t cc, rr;
206
+ /* whole-screen cabinet brick */
207
+ for (rr = 0; rr < 30; rr++)
208
+ for (cc = 0; cc < 32; cc++)
209
+ vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + cc), BG_BRICK);
210
+ /* recessed well interior (inside the frame) */
211
+ for (rr = gy0; rr < gy1; rr++)
212
+ for (cc = gx0; cc < gx1; cc++)
213
+ vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + cc), BG_INNER);
214
+ /* steel frame one cell out around the well */
215
+ for (cc = gx0 - 1; cc <= gx1; cc++) {
216
+ vram_unsafe_set((uint16_t)(0x2000 + (gy0 - 1) * 32 + cc), BG_WALL); /* top */
217
+ vram_unsafe_set((uint16_t)(0x2000 + gy1 * 32 + cc), BG_WALL); /* bottom */
218
+ }
219
+ for (rr = gy0 - 1; rr <= gy1; rr++) {
220
+ vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + (gx0 - 1)), BG_WALL); /* left */
221
+ vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + gx1), BG_WALL); /* right */
222
+ }
223
+ }
224
+
164
225
  oam_clear();
165
226
  ppu_on_all();
166
227
  sound_init();
@@ -59,14 +59,22 @@ static const uint8_t tile_digits[10 * 16] = {
59
59
  /* ── Background road tiles ───────────────────────────────────────────
60
60
  * Default PPUCTRL ($90) reads BG patterns from pattern table 1 ($1000),
61
61
  * so these go to CHR $1000+ and are indexed independently of the sprite
62
- * tiles above. Colour 1 (white, BG palette 0) draws the markings; the
63
- * grey backdrop (colour 0) is the road surface.
62
+ * tiles above. The grey backdrop (colour 0) is the road surface; colour 1
63
+ * (white) draws the markings, colour 2 (green) the grass, colour 3 the
64
+ * dark seam in the tarmac.
64
65
  *
65
- * BG_T_EDGE: a solid 2px vertical stripe — the road shoulder line.
66
- * BG_T_LANE: a 2px vertical dash (on for 4 rows, off for 4) — the
67
- * dashed centre lane marking when stacked down a column. */
66
+ * BG_T_EDGE: a solid 2px vertical stripe — the road shoulder line.
67
+ * BG_T_LANE: a 2px vertical dash (on 4 rows / off 4) — the dashed centre
68
+ * lane marking when stacked down a column.
69
+ * BG_T_GRASS: a textured green roadside (colour 2 hatch) so the area
70
+ * outside the shoulders isn't flat — fills the screen sides.
71
+ * BG_T_ROAD: a faint tarmac texture (a couple of colour-3 specks) tiled
72
+ * across the driving surface so the road doesn't read as one
73
+ * solid grey block. */
68
74
  #define BG_T_EDGE 1
69
75
  #define BG_T_LANE 2
76
+ #define BG_T_GRASS 3
77
+ #define BG_T_ROAD 4
70
78
  static const uint8_t bg_tile_edge[16] = {
71
79
  0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, /* plane 0 (colour bit 0) */
72
80
  0, 0, 0, 0, 0, 0, 0, 0,
@@ -75,13 +83,21 @@ static const uint8_t bg_tile_lane[16] = {
75
83
  0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, /* dash: 4 on, 4 off */
76
84
  0, 0, 0, 0, 0, 0, 0, 0,
77
85
  };
86
+ static const uint8_t bg_tile_grass[16] = {
87
+ 0, 0, 0, 0, 0, 0, 0, 0, /* plane 0 clear */
88
+ 0xEE, 0xBB, 0xEE, 0xBB, 0xEE, 0xBB, 0xEE, 0xBB, /* plane 1 → colour 2 hatch */
89
+ };
90
+ static const uint8_t bg_tile_road[16] = {
91
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, /* plane 0 specks */
92
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, /* plane 1 too → colour 3 */
93
+ };
78
94
 
79
95
  static const uint8_t palette[32] = {
80
- /* BG palettes — light grey backdrop simulates road */
81
- 0x10, 0x30, 0x16, 0x12,
82
- 0x10, 0x30, 0x16, 0x12,
83
- 0x10, 0x30, 0x16, 0x12,
84
- 0x10, 0x30, 0x16, 0x12,
96
+ /* BG palettes — light grey backdrop simulates road; idx2 = grass green */
97
+ 0x10, 0x30, 0x1A, 0x00,
98
+ 0x10, 0x30, 0x1A, 0x00,
99
+ 0x10, 0x30, 0x1A, 0x00,
100
+ 0x10, 0x30, 0x1A, 0x00,
85
101
  /* Sprite palettes */
86
102
  0x10, 0x21, 0x16, 0x30, /* sp0: blue car (P1) */
87
103
  0x10, 0x16, 0x21, 0x30, /* sp1: red enemy */
@@ -125,8 +141,22 @@ static uint8_t aabb(Car *a, Car *b) {
125
141
  #define ROAD_DIV_1 13
126
142
  #define ROAD_DIV_2 17
127
143
  static void draw_road(void) {
128
- uint8_t row;
144
+ uint8_t row, col;
129
145
  uint16_t base;
146
+ /* Fill the WHOLE nametable so nothing reads as flat colour: grass on the
147
+ * roadside (outside the shoulders) and a faint tarmac texture on the
148
+ * driving surface. Then stamp the shoulder + lane markings on top. */
149
+ for (row = 0; row < 30; row++) {
150
+ base = (uint16_t)(0x2000 + (uint16_t)row * 32);
151
+ for (col = 0; col < 32; col++) {
152
+ if (col < ROAD_EDGE_L || col > ROAD_EDGE_R) {
153
+ vram_unsafe_set((uint16_t)(base + col), BG_T_GRASS); /* roadside */
154
+ } else if (((row + col) & 3) == 0) {
155
+ vram_unsafe_set((uint16_t)(base + col), BG_T_ROAD); /* tarmac speck */
156
+ }
157
+ /* else leave colour-0 grey road surface */
158
+ }
159
+ }
130
160
  for (row = ROAD_TOP_ROW; row <= ROAD_BOT_ROW; row++) {
131
161
  base = (uint16_t)(0x2000 + (uint16_t)row * 32);
132
162
  vram_unsafe_set((uint16_t)(base + ROAD_EDGE_L), BG_T_EDGE);
@@ -183,8 +213,10 @@ void main(void) {
183
213
 
184
214
  /* BG road tiles live in pattern table 1 ($1000) — that's where the
185
215
  * default PPUCTRL ($90) tells the PPU to read background patterns. */
186
- chr_ram_upload((uint16_t)(0x1000 + BG_T_EDGE * 16), bg_tile_edge, 16);
187
- chr_ram_upload((uint16_t)(0x1000 + BG_T_LANE * 16), bg_tile_lane, 16);
216
+ chr_ram_upload((uint16_t)(0x1000 + BG_T_EDGE * 16), bg_tile_edge, 16);
217
+ chr_ram_upload((uint16_t)(0x1000 + BG_T_LANE * 16), bg_tile_lane, 16);
218
+ chr_ram_upload((uint16_t)(0x1000 + BG_T_GRASS * 16), bg_tile_grass, 16);
219
+ chr_ram_upload((uint16_t)(0x1000 + BG_T_ROAD * 16), bg_tile_road, 16);
188
220
 
189
221
  palette_load(palette);
190
222
  draw_road(); /* paint the static road while the PPU is off */
@@ -38,10 +38,34 @@ static const uint8_t tile_enemy[16] = {
38
38
  0x81, 0x42, 0x24, 0xFF, 0xFF, 0x24, 0x42, 0x81, /* plane 0 — spider-ish */
39
39
  0, 0, 0, 0, 0, 0, 0, 0,
40
40
  };
41
+ /* BG starfield tiles — painted across the whole nametable so the screen
42
+ * reads as a real "space" scene on boot instead of flat black. They live in
43
+ * the BACKGROUND pattern table ($1000), separate from the sprite tiles above
44
+ * (the runtime puts BG at $1000, sprites at $0000).
45
+ *
46
+ * BG_DUST — a faint checkerboard "space dust" that tiles seamlessly; the
47
+ * base layer that covers the whole field so it never reads blank.
48
+ * BG_STAR — three small stars (colour 1) sprinkled over the dust.
49
+ * BG_BRITE — a single bright + star (colour 2) for the rare close star. */
50
+ static const uint8_t tile_dust[16] = {
51
+ 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, /* plane 0: checker (idx 1) */
52
+ 0, 0, 0, 0, 0, 0, 0, 0,
53
+ };
54
+ static const uint8_t tile_star[16] = {
55
+ 0x00, 0x08, 0x00, 0x42, 0x00, 0x00, 0x20, 0x01, /* plane 0: four small stars */
56
+ 0, 0, 0, 0, 0, 0, 0, 0,
57
+ };
58
+ static const uint8_t tile_brite[16] = {
59
+ 0x00, 0x00, 0x10, 0x38, 0x10, 0x00, 0x00, 0x00, /* plane 0: + arms (idx ... ) */
60
+ 0x00, 0x00, 0x10, 0x38, 0x10, 0x00, 0x00, 0x00, /* plane 1 set too → colour 3 */
61
+ };
62
+ #define BG_DUST 1 /* BG tile index 1 → uploaded to $1010 */
63
+ #define BG_STAR 2 /* BG tile index 2 → uploaded to $1020 */
64
+ #define BG_BRITE 3 /* BG tile index 3 → uploaded to $1030 */
41
65
 
42
66
  /* ── Palette ─────────────────────────────────────────────────────── */
43
67
  static const uint8_t palette[32] = {
44
- /* BG (unused but PPU reads $3F00 backdrop) */
68
+ /* BG0: backdrop near-black, star colour = dim white */
45
69
  0x0F, 0x10, 0x20, 0x30,
46
70
  0x0F, 0x10, 0x20, 0x30,
47
71
  0x0F, 0x10, 0x20, 0x30,
@@ -126,14 +150,39 @@ void main(void) {
126
150
 
127
151
  ppu_off();
128
152
 
129
- /* Upload tile data — blank in slot 0 + 3 sprite tiles in slots 1..3. */
153
+ /* Upload tile data — blank in slot 0 + 3 sprite tiles in slots 1..3,
154
+ * all in the SPRITE pattern table ($0000). */
130
155
  chr_ram_upload(0x0000, tile_blank, 16);
131
156
  chr_ram_upload(0x0010, tile_ship, 16);
132
157
  chr_ram_upload(0x0020, tile_bullet, 16);
133
158
  chr_ram_upload(0x0030, tile_enemy, 16);
159
+ /* Upload the starfield tiles to the BACKGROUND pattern table
160
+ * ($1010/$1020/$1030 = BG slots 1/2/3). */
161
+ chr_ram_upload(0x1010, tile_dust, 16);
162
+ chr_ram_upload(0x1020, tile_star, 16);
163
+ chr_ram_upload(0x1030, tile_brite, 16);
134
164
 
135
165
  palette_load(palette);
136
166
 
167
+ /* Paint a full starfield directly into the nametable while the PPU is off
168
+ * (vram_unsafe_set = raw write; tile_set's NMI queue would deadlock
169
+ * before ppu_on). Every one of the 32×30 cells gets the faint "dust" base,
170
+ * with small stars sprinkled every few cells and the odd bright star — a
171
+ * deterministic scatter so the backdrop is unambiguously "space", densely
172
+ * filled rather than flat black. */
173
+ {
174
+ uint16_t r, cc;
175
+ uint8_t tile;
176
+ for (r = 0; r < 30; r++) {
177
+ for (cc = 0; cc < 32; cc++) {
178
+ tile = BG_DUST; /* base dust everywhere */
179
+ if (((r * 5 + cc * 3) % 7) == 0) tile = BG_STAR; /* sprinkle stars */
180
+ if (((r * 3 + cc * 7) % 23) == 0) tile = BG_BRITE; /* rare bright one */
181
+ vram_unsafe_set((uint16_t)(0x2000 + r * 32 + cc), tile);
182
+ }
183
+ }
184
+ }
185
+
137
186
  oam_clear();
138
187
  ppu_on_all();
139
188
  sound_init();
@@ -34,6 +34,27 @@ static const uint8_t tile_ball[16] = {
34
34
  0x00, 0x3C, 0x7E, 0x7E, 0x7E, 0x7E, 0x3C, 0x00,
35
35
  0, 0, 0, 0, 0, 0, 0, 0,
36
36
  };
37
+ /* BG court tiles (background pattern table $1000), so the court reads as a
38
+ * real Pong arena on boot instead of sprites on flat black:
39
+ * BG_WALL — solid bar (idx1 white): the top/bottom rails.
40
+ * BG_NET — dashed vertical bar (idx1 white): the centre net.
41
+ * BG_FLOOR — a faint court-floor hatch (idx2 green) tiled across the whole
42
+ * playfield so the arena surface is covered, not black. */
43
+ static const uint8_t tile_wall[16] = {
44
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
45
+ 0, 0, 0, 0, 0, 0, 0, 0,
46
+ };
47
+ static const uint8_t tile_net[16] = {
48
+ 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, /* dashed vertical bar */
49
+ 0, 0, 0, 0, 0, 0, 0, 0,
50
+ };
51
+ static const uint8_t tile_floor[16] = {
52
+ 0, 0, 0, 0, 0, 0, 0, 0, /* plane 0 clear */
53
+ 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, /* plane 1 → colour 2 checker */
54
+ };
55
+ #define BG_WALL 1 /* BG slot 1 → $1010 */
56
+ #define BG_NET 2 /* BG slot 2 → $1020 */
57
+ #define BG_FLOOR 3 /* BG slot 3 → $1030 */
37
58
  /* Digits 0-9 (3 wide × 5 tall, padded to 8×8). Used for the score HUD. */
38
59
  static const uint8_t tile_digits[10 * 16] = {
39
60
  /* 0 */ 0xE0,0xA0,0xA0,0xA0,0xE0,0x00,0x00,0x00, 0,0,0,0,0,0,0,0,
@@ -55,11 +76,12 @@ static const uint8_t tile_digits[10 * 16] = {
55
76
  #define T_DIGIT0 3 /* digits live at slots 3..12 */
56
77
 
57
78
  static const uint8_t palette[32] = {
58
- /* BG palettes only the backdrop matters here */
59
- 0x0F, 0x30, 0x10, 0x00,
60
- 0x0F, 0x30, 0x10, 0x00,
61
- 0x0F, 0x30, 0x10, 0x00,
62
- 0x0F, 0x30, 0x10, 0x00,
79
+ /* BG0: backdrop near-black, court walls/net = white (idx1),
80
+ * court-floor hatch = dark green (idx2) */
81
+ 0x0F, 0x30, 0x1A, 0x00,
82
+ 0x0F, 0x30, 0x1A, 0x00,
83
+ 0x0F, 0x30, 0x1A, 0x00,
84
+ 0x0F, 0x30, 0x1A, 0x00,
63
85
  /* Sprite palettes */
64
86
  0x0F, 0x30, 0x16, 0x12, /* sp0: white paddle */
65
87
  0x0F, 0x30, 0x16, 0x12, /* sp1: white ball */
@@ -103,13 +125,36 @@ void main(void) {
103
125
 
104
126
  ppu_off();
105
127
 
106
- /* Upload tiles. */
128
+ /* Upload sprite tiles (sprite pattern table $0000). */
107
129
  chr_ram_upload(T_BLANK * 16, tile_blank, 16);
108
130
  chr_ram_upload(T_PADDLE * 16, tile_paddle, 16);
109
131
  chr_ram_upload(T_BALL * 16, tile_ball, 16);
110
132
  chr_ram_upload(T_DIGIT0 * 16, tile_digits, sizeof(tile_digits));
133
+ /* Upload court tiles to the BACKGROUND pattern table ($1010..$1030). */
134
+ chr_ram_upload(0x1010, tile_wall, 16);
135
+ chr_ram_upload(0x1020, tile_net, 16);
136
+ chr_ram_upload(0x1030, tile_floor, 16);
111
137
 
112
138
  palette_load(palette);
139
+
140
+ /* Paint the court into the nametable while rendering is off
141
+ * (vram_unsafe_set = raw write). First carpet the whole playfield with the
142
+ * green floor hatch so the arena surface is covered, then lay the top/
143
+ * bottom rails (rows 1 and 27) and the dashed centre net (column 15) on
144
+ * top. Without the floor the screen is just sprites on flat black. */
145
+ {
146
+ uint16_t cc, rr;
147
+ for (rr = 0; rr < 30; rr++)
148
+ for (cc = 0; cc < 32; cc++)
149
+ vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + cc), BG_FLOOR);
150
+ for (cc = 0; cc < 32; cc++) {
151
+ vram_unsafe_set((uint16_t)(0x2000 + 1 * 32 + cc), BG_WALL); /* top rail (y≈8) */
152
+ vram_unsafe_set((uint16_t)(0x2000 + 27 * 32 + cc), BG_WALL); /* bottom rail(y≈216)*/
153
+ }
154
+ for (rr = 2; rr < 27; rr++)
155
+ vram_unsafe_set((uint16_t)(0x2000 + rr * 32 + 15), BG_NET); /* centre net */
156
+ }
157
+
113
158
  oam_clear();
114
159
  ppu_on_all();
115
160
  sound_init();
@@ -27,6 +27,8 @@
27
27
  #define BAT_VRAM 0x0000 /* 32x32 background map */
28
28
  #define FONT_VRAM 0x1000 /* digit + glyph tiles (8x8, 16 words each) */
29
29
  #define BLANK_VRAM 0x1000 /* tile 0 of the font = blank */
30
+ #define FIELD_VRAM 0x1700 /* dim "field" BG tile (16 words), drawn behind
31
+ * the HUD so the playfield isn't a blank void */
30
32
  #define CATCHER_VRAM 0x1800 /* catcher sprite cell (16x16 = 64 words) */
31
33
  #define FRUIT_VRAM 0x1840 /* fruit sprite cell (16x16 = 64 words) */
32
34
 
@@ -146,15 +148,30 @@ static void upload_sprites(void) {
146
148
  load_tiles(FRUIT_VRAM, fruit_cell, 64);
147
149
  }
148
150
 
149
- /* ---- clear the whole BAT to blank tiles --------------------------------- */
151
+ /* ---- build the dim "field" BG tile -------------------------------------- */
152
+ /* A solid 8x8 tile in BG colour index 2 (the dim field blue). Filling the BAT
153
+ * with this instead of blank gives the playfield a visible background — an
154
+ * all-blank BAT reads as a near-empty backdrop (one colour > 92% of the screen,
155
+ * which looks blank to a human). plane1 set = colour index 2. */
156
+ static void upload_field(void) {
157
+ u16 i;
158
+ for (i = 0; i < 16; ++i) font_cell[i] = 0;
159
+ for (i = 0; i < 8; ++i) font_cell[i] = 0xFF00; /* plane1 (hi byte) = ci 2 */
160
+ load_tiles(FIELD_VRAM, font_cell, 16);
161
+ }
162
+
163
+ /* ---- fill the whole BAT with the field tile (a 2x2-cell checkerboard of
164
+ * field/blank), behind the HUD glyphs which are written on top afterwards ---- */
150
165
  static void clear_bat(void) {
151
166
  u16 r, c;
167
+ u16 field = BAT_ENTRY(0, FIELD_VRAM);
152
168
  u16 blank = BAT_ENTRY(0, BLANK_VRAM);
153
169
  for (r = 0; r < 32; ++r) {
154
170
  vram_set_write_addr((u16)(BAT_VRAM + r * 32));
155
171
  for (c = 0; c < 32; ++c) {
156
- VDC_DATA_LO = (u8)(blank & 0xFF);
157
- VDC_DATA_HI = (u8)(blank >> 8);
172
+ u16 e = (((r >> 1) ^ (c >> 1)) & 1) ? blank : field;
173
+ VDC_DATA_LO = (u8)(e & 0xFF);
174
+ VDC_DATA_HI = (u8)(e >> 8);
158
175
  }
159
176
  }
160
177
  }
@@ -229,6 +246,7 @@ void main(void) {
229
246
  /* palette: backdrop, BG text colours, sprite colours */
230
247
  vce_set_color(0, PCE_RGB(0, 0, 1)); /* backdrop: near-black blue */
231
248
  vce_set_color(1, PCE_RGB(7, 7, 7)); /* BG colour 1: white text */
249
+ vce_set_color(2, PCE_RGB(1, 1, 3)); /* BG colour 2: dim field blue */
232
250
  vce_set_color(256, PCE_RGB(0, 0, 0)); /* sprite transparent */
233
251
  vce_set_color(257, PCE_RGB(2, 5, 7)); /* sprite c1: cyan (catcher 0) */
234
252
  vce_set_color(259, PCE_RGB(2, 5, 7)); /* sprite c3: cyan (catcher) */
@@ -238,6 +256,7 @@ void main(void) {
238
256
 
239
257
  upload_font();
240
258
  upload_sprites();
259
+ upload_field(); /* dim field tile for a visible playfield BG */
241
260
 
242
261
  clear_bat();
243
262
  draw_hud_labels();
@@ -76,6 +76,32 @@ static void draw_bar(u8 active) {
76
76
  }
77
77
  }
78
78
 
79
+ /* Paint a decorative full-screen frame so the demo reads as a real UI panel
80
+ * instead of a near-empty backdrop. conio only inits a backdrop + font, so an
81
+ * otherwise text-only screen leaves >92% of pixels one colour, which looks
82
+ * blank to a human. We fill the top/bottom title bands and both side borders
83
+ * with a block character (the labels are drawn ON TOP afterwards). PCE conio is
84
+ * 32 cols x 28 rows. */
85
+ #define SCR_COLS 32
86
+ #define SCR_ROWS 28
87
+ static void draw_frame(void) {
88
+ u8 x, y;
89
+ /* solid top band (rows 0-1) and bottom band (rows 26-27) */
90
+ for (y = 0; y < SCR_ROWS; ++y) {
91
+ if (y < 2 || y >= SCR_ROWS - 2) {
92
+ for (x = 0; x < SCR_COLS; ++x) cputcxy(x, y, '#');
93
+ } else {
94
+ /* left + right vertical borders (two columns each for weight) */
95
+ cputcxy(0, y, '#');
96
+ cputcxy(1, y, '#');
97
+ cputcxy((u8)(SCR_COLS - 2), y, '#');
98
+ cputcxy((u8)(SCR_COLS - 1), y, '#');
99
+ }
100
+ }
101
+ /* a mid separator bar under the title so the panel has visible structure */
102
+ for (x = 2; x < SCR_COLS - 2; ++x) cputcxy(x, 5, '=');
103
+ }
104
+
79
105
  void main(void) {
80
106
  u8 pad, prev_pad;
81
107
  u8 step; /* current melody step 0..7 */
@@ -86,8 +112,9 @@ void main(void) {
86
112
 
87
113
  _keep[0] = 0;
88
114
 
89
- /* conio: clear + enable display, then paint the static labels. */
115
+ /* conio: clear + enable display, paint the frame, then the static labels. */
90
116
  clrscr();
117
+ draw_frame(); /* visible bordered panel (not a blank backdrop) */
91
118
  cputsxy(8, 8, "PC ENGINE MUSIC + SFX");
92
119
  cputsxy(8, 11, "MELODY:");
93
120
  cputsxy(8, BAR_Y - 1, "STEP:");