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
@@ -15,6 +15,7 @@ extern void gg_vdp_display_on(void);
15
15
  extern void gg_vdp_set_addr(uint16_t addr, uint8_t prefix);
16
16
  extern void gg_load_palette(const uint8_t *palette);
17
17
  extern void gg_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
18
+ extern void gg_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
18
19
  extern void gg_vblank_wait(void);
19
20
  extern uint8_t gg_joypad_read(void);
20
21
  extern void gg_sprite_init(void);
@@ -52,14 +53,55 @@ extern void gg_sat_upload(void);
52
53
  * array left the sprite palette (entries 16-31) reading past the array = garbage
53
54
  * = invisible sprites.) */
54
55
  static const uint8_t palette[64] = {
55
- /* BG 0-15: entry 0 = dark navy backdrop */
56
- 0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
56
+ /* BG 0-15: 0 = backdrop, 1 = deep space blue, 2 = lighter space blue,
57
+ * 3 = star white. */
58
+ 0x20,0x02, 0x30,0x04, 0x80,0x08, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0,
57
59
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
58
60
  /* SPRITE 16-31: 16=transparent, 17=white, 18=yellow, 19=red */
59
61
  0,0, 0xFF,0x0F, 0xFF,0x00, 0x0F,0x00, 0,0, 0,0, 0,0, 0,0,
60
62
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
61
63
  };
62
64
 
65
+ /* Three BG tiles for the starfield, loaded into the BG tile bank at
66
+ * $0000:
67
+ * tile 0 = deep space (solid colour 1)
68
+ * tile 1 = lighter space band (solid colour 2)
69
+ * tile 2 = space with a star (mostly colour 1, one colour-3 pixel) */
70
+ static const uint8_t bg_tiles[96] = {
71
+ /* tile 0 = deep space (colour 1 → plane 0 set) */
72
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
73
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
74
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
75
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
76
+ /* tile 1 = lighter band (colour 2 → plane 1 set) */
77
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
78
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
79
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
80
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
81
+ /* tile 2 = deep space + a star: row 3 col 3 = colour 3 (planes 0+1),
82
+ * everything else colour 1 (plane 0). */
83
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
84
+ 0xFF,0x00,0x00,0x00, 0xFF,0x10,0x00,0x00,
85
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
86
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
87
+ };
88
+
89
+ /* Paint the visible viewport with a banded starfield so the screen is
90
+ * clearly space, not a flat backdrop. Visible name-table region is cols
91
+ * 6..25, rows 3..20. BG tile bank is $0000. */
92
+ static void draw_starfield(void) {
93
+ uint8_t row, col;
94
+ for (row = 0; row < 28; row++)
95
+ for (col = 0; col < 32; col++) gg_set_tilemap_cell(row, col, 0, 0);
96
+ for (row = 3; row <= 20; row++) {
97
+ for (col = 6; col <= 25; col++) {
98
+ uint8_t t = (row & 2) ? 1 : 0; /* alternating depth bands */
99
+ if (((row * 7 + col * 5) & 7) == 0) t = 2; /* sparse stars */
100
+ gg_set_tilemap_cell(row, col, t, 0);
101
+ }
102
+ }
103
+ }
104
+
63
105
  static const uint8_t sprite_tiles[32 * 3] = {
64
106
  /* T_SHIP — diamond using colour 1 (white) */
65
107
  0x18,0x00,0x00,0x00, 0x3C,0x00,0x00,0x00,
@@ -121,7 +163,9 @@ void main(void) {
121
163
 
122
164
  gg_vdp_init();
123
165
  gg_load_palette(palette);
124
- gg_load_tiles(0x2000, sprite_tiles, 32 * 3);
166
+ gg_load_tiles(0x0000, bg_tiles, 96); /* BG tiles → BG bank $0000 */
167
+ gg_load_tiles(0x2000, sprite_tiles, 32 * 3); /* sprite tiles → $2000 */
168
+ draw_starfield();
125
169
 
126
170
  /* Start the ship centered, near the bottom of the VISIBLE region. */
127
171
  player.x = (uint8_t)(VIS_X0 + VIS_W / 2 - 4); player.y = (uint8_t)(VIS_Y1 - 16); player.alive = 1;
@@ -11,6 +11,7 @@ extern void gg_vdp_init(void);
11
11
  extern void gg_vdp_display_on(void);
12
12
  extern void gg_load_palette(const uint8_t *palette);
13
13
  extern void gg_load_tiles(uint16_t vram_dest, const uint8_t *src, uint16_t byte_count);
14
+ extern void gg_set_tilemap_cell(uint8_t row, uint8_t col, uint8_t tile_idx, uint8_t attr);
14
15
  extern void gg_vblank_wait(void);
15
16
  extern uint8_t gg_joypad_read(void);
16
17
  extern void gg_sprite_init(void);
@@ -29,16 +30,19 @@ extern void gg_sat_upload(void);
29
30
  #define COURT_BOT VIS_Y1
30
31
  #define PADDLE_H 24
31
32
  #define BALL_SIZE 8
32
- #define PADDLE_X1 (VIS_X0 + 8) /* near the visible left edge */
33
- #define PADDLE_X2 (VIS_X1 - 16) /* near the visible right edge */
33
+ /* Explicit (uint8_t) casts: these fit a byte but SDCC warns (158) on the
34
+ * implicit int->uint8_t narrowing when the computed macro is passed to the
35
+ * uint8_t x/y args of gg_sprite_set. */
36
+ #define PADDLE_X1 ((uint8_t)(VIS_X0 + 8)) /* near the visible left edge */
37
+ #define PADDLE_X2 ((uint8_t)(VIS_X1 - 16)) /* near the visible right edge */
34
38
 
35
39
  /* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
36
40
  * gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
37
41
  * (entries 16-31) reading garbage = invisible sprites. Sprite colour 1 = entry
38
42
  * 17 (white). */
39
43
  static const uint8_t palette[64] = {
40
- /* BG 0-15: entry 0 = dark navy backdrop */
41
- 0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
44
+ /* BG 0-15: 0 = dark navy backdrop, 1 = court green, 2 = court line white */
45
+ 0x20,0x02, 0x60,0x00, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0,
42
46
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
43
47
  /* SPRITE 16-31: 16=transparent, 17=white */
44
48
  0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
@@ -52,6 +56,42 @@ static const uint8_t tile_solid[32] = {
52
56
  0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
53
57
  };
54
58
 
59
+ /* Three BG tiles for the court, loaded into the BG tile bank at $0000:
60
+ * tile 0 = court green (colour 1), tile 1 = court line / border
61
+ * (colour 2 = white), tile 2 = dashed net (colour 2 stripe on green). */
62
+ static const uint8_t bg_tiles[96] = {
63
+ /* tile 0 = court green (colour 1 -> plane 0 set) */
64
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
65
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
66
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
67
+ 0xFF,0x00,0x00,0x00, 0xFF,0x00,0x00,0x00,
68
+ /* tile 1 = court line / border (colour 2 -> plane 1 set) */
69
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
70
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
71
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
72
+ 0x00,0xFF,0x00,0x00, 0x00,0xFF,0x00,0x00,
73
+ /* tile 2 = net: centre column colour 2, rest colour 1 (green) */
74
+ 0xFF,0x18,0x00,0x00, 0xFF,0x18,0x00,0x00,
75
+ 0xFF,0x18,0x00,0x00, 0xFF,0x18,0x00,0x00,
76
+ 0xFF,0x18,0x00,0x00, 0xFF,0x18,0x00,0x00,
77
+ 0xFF,0x18,0x00,0x00, 0xFF,0x18,0x00,0x00,
78
+ };
79
+
80
+ /* Paint the court inside the GG visible region (cols 6..25, rows 3..20):
81
+ * green field, white border lines around the perimeter, dashed net down
82
+ * the centre column. BG tile bank is $0000. */
83
+ static void draw_court(void) {
84
+ uint8_t row, col;
85
+ for (row = 3; row <= 20; row++) {
86
+ for (col = 6; col <= 25; col++) {
87
+ uint8_t t = 0; /* green field */
88
+ if (row == 3 || row == 20 || col == 6 || col == 25) t = 1; /* border */
89
+ else if (col == 15 && (row & 1)) t = 2; /* dashed centre net */
90
+ gg_set_tilemap_cell(row, col, t, 0);
91
+ }
92
+ }
93
+ }
94
+
55
95
  static int16_t p1y, p2y, bx, by;
56
96
  static int8_t bdx, bdy;
57
97
  static uint8_t score_p1, score_p2;
@@ -76,7 +116,13 @@ void main(void) {
76
116
  uint8_t i;
77
117
  gg_vdp_init();
78
118
  gg_load_palette(palette);
79
- gg_load_tiles(0x2000, tile_solid, 32);
119
+ gg_load_tiles(0x0000, bg_tiles, 96); /* BG court tiles -> BG bank $0000 */
120
+ gg_load_tiles(0x2000, tile_solid, 32); /* paddle/ball sprite tile -> $2000 */
121
+ {
122
+ uint8_t r, c;
123
+ for (r = 0; r < 28; r++) for (c = 0; c < 32; c++) gg_set_tilemap_cell(r, c, 0, 0);
124
+ }
125
+ draw_court();
80
126
  gg_sprite_init();
81
127
  sfx_init();
82
128
  gg_vdp_display_on();
@@ -84,8 +130,9 @@ void main(void) {
84
130
  reset_match();
85
131
 
86
132
  do {
87
- uint8_t p1, p2;
133
+ uint8_t p1;
88
134
  uint8_t slot;
135
+ int16_t target;
89
136
  gg_vblank_wait();
90
137
  sfx_update();
91
138
 
@@ -102,20 +149,14 @@ void main(void) {
102
149
  gg_sat_upload();
103
150
 
104
151
  p1 = gg_joypad_read();
105
- p2 = 0; /* GG has only one controller — always AI for the right paddle */
106
152
 
107
153
  if ((p1 & JOY_UP) && p1y > COURT_TOP) p1y -= 2;
108
154
  if ((p1 & JOY_DOWN) && p1y < COURT_BOT - PADDLE_H) p1y += 2;
109
155
 
110
- /* P2 input if any, otherwise AI. */
111
- if (p2 != 0) {
112
- if ((p2 & JOY_UP) && p2y > COURT_TOP) p2y -= 2;
113
- if ((p2 & JOY_DOWN) && p2y < COURT_BOT - PADDLE_H) p2y += 2;
114
- } else {
115
- int16_t target = by - PADDLE_H / 2;
116
- if (p2y < target && p2y < COURT_BOT - PADDLE_H) p2y += 1;
117
- else if (p2y > target && p2y > COURT_TOP) p2y -= 1;
118
- }
156
+ /* GG has only one controller — the right paddle is always AI. */
157
+ target = by - PADDLE_H / 2;
158
+ if (p2y < target && p2y < COURT_BOT - PADDLE_H) p2y += 1;
159
+ else if (p2y > target && p2y > COURT_TOP) p2y -= 1;
119
160
 
120
161
  if (serve_timer > 0) {
121
162
  serve_timer--;
@@ -46,8 +46,9 @@ extern void gg_sat_upload(void);
46
46
  * (entries 16-31) reading garbage = invisible sprites. BG colour 1 = entry 1
47
47
  * (dark grey wall); sprite colour 1 = entry 17 (white player). */
48
48
  static const uint8_t palette[64] = {
49
- /* BG 0-15: entry 0 = dark navy backdrop, entry 1 = dark grey wall */
50
- 0x20,0x02, 0x66,0x06, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
49
+ /* BG 0-15: 0 = dark navy backdrop, 1 = grey wall, 2 = teal floor,
50
+ * 3 = blue floor (the two floor-dither tones). */
51
+ 0x20,0x02, 0x66,0x06, 0xC8,0x08, 0x80,0x0C, 0,0, 0,0, 0,0, 0,0,
51
52
  0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
52
53
  /* SPRITE 16-31: 16=transparent, 17=white player */
53
54
  0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
@@ -55,10 +56,15 @@ static const uint8_t palette[64] = {
55
56
  };
56
57
 
57
58
  static const uint8_t bg_tiles[32 * 2] = {
58
- /* T_OPEN — blank */
59
- 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
60
- 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
61
- /* T_WALL bordered block */
59
+ /* T_OPEN — dithered floor: plane1=0xFF (colour-2 bit always on), plane0
60
+ * alternates 0xAA/0x55 so pixels flip between colour 2 (teal) and colour 3
61
+ * (blue). The open floor now fills with TWO tones instead of the backdrop,
62
+ * so the screen never reads as a single flat colour. */
63
+ 0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
64
+ 0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
65
+ 0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
66
+ 0xAA,0xFF,0x00,0x00, 0x55,0xFF,0x00,0x00,
67
+ /* T_WALL — bordered block (colour 1, grey) */
62
68
  0xFF,0x00,0x00,0x00, 0x81,0x00,0x00,0x00,
63
69
  0x81,0x00,0x00,0x00, 0x81,0x00,0x00,0x00,
64
70
  0x81,0x00,0x00,0x00, 0x81,0x00,0x00,0x00,
@@ -15,9 +15,12 @@
15
15
  #include <lynx.h>
16
16
 
17
17
  void main(void) {
18
- static const unsigned char palette[8] = {
19
- COLOR_RED, COLOR_YELLOW, COLOR_GREEN, COLOR_LIGHTBLUE,
20
- COLOR_BLUE, COLOR_PURPLE, COLOR_LIGHTGREEN, COLOR_WHITE,
18
+ /* Cycle the centre square through warm/bright shades only — NOT the
19
+ * blues, which would blend into the blue background and make the
20
+ * screen read as "almost one colour". */
21
+ static const unsigned char palette[6] = {
22
+ COLOR_RED, COLOR_YELLOW, COLOR_GREEN,
23
+ COLOR_WHITE, COLOR_LIGHTGREEN, COLOR_PURPLE,
21
24
  };
22
25
  unsigned char shade = 0;
23
26
  unsigned int frame = 0;
@@ -26,16 +29,44 @@ void main(void) {
26
29
  tgi_init();
27
30
 
28
31
  for (;;) {
29
- tgi_clear();
32
+ /* CANONICAL LYNX FRAME LOOP — full redraw every frame, in this order:
33
+ * 1. WAIT for Suzy's blitter to finish the previous frame. Drawing
34
+ * while it's mid-flight loses the frame → black screen. This is
35
+ * the #1 "Lynx stays blank" trap.
36
+ * 2. CLEAR with a full-screen tgi_bar in the background colour, NOT
37
+ * tgi_clear() — which leaves the back page stale in this
38
+ * toolchain+emulator path (the other genre scaffolds all do the
39
+ * same; see shmup.c's LYNX-1 note).
40
+ * 3. DRAW everything.
41
+ * 4. tgi_updatedisplay() to push the frame. */
42
+ while (tgi_busy()) { }
43
+
44
+ /* Blue field so the screen is obviously not blank. */
45
+ tgi_setcolor(COLOR_BLUE);
46
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
47
+
48
+ /* A fixed green frame around the centre — always a distinct colour,
49
+ * so the screen never collapses to a single shade even while the
50
+ * inner square is cycling. */
51
+ tgi_setcolor(COLOR_GREEN);
52
+ tgi_bar(50, 30, 110, 72);
53
+
54
+ /* Colour-cycling square in the centre. */
30
55
  tgi_setcolor(palette[shade]);
31
- tgi_bar(70, 40, 90, 62);
56
+ tgi_bar(60, 36, 100, 66);
57
+
58
+ /* Greeting on top. The tgi font is 8 px wide; "BUILT WITH ROMDEV"
59
+ * is 17 chars = 136 px, so start near the left edge to stay inside
60
+ * the 160-px-wide screen. */
32
61
  tgi_setcolor(COLOR_WHITE);
33
- tgi_outtextxy(2, 2, "HELLO LYNX");
62
+ tgi_outtextxy(40, 14, "HELLO LYNX");
63
+ tgi_outtextxy(12, 86, "BUILT WITH ROMDEV");
34
64
  tgi_updatedisplay();
35
65
 
36
66
  frame++;
37
- if ((frame & 0x1F) == 0) { /* every 32 frames */
38
- shade = (shade + 1) & 0x07;
67
+ if ((frame % 24) == 0) { /* advance the inner square */
68
+ shade++;
69
+ if (shade >= 6) shade = 0;
39
70
  }
40
71
  }
41
72
  }
@@ -22,7 +22,21 @@ void main(void) {
22
22
  sfx_tone(0, 80, 12); /* boot chime */
23
23
 
24
24
  for (;;) {
25
- tgi_clear();
25
+ /* CANONICAL LYNX FRAME LOOP — full redraw every frame:
26
+ * 1. WAIT for Suzy's blitter to finish the previous frame. Drawing
27
+ * while it's mid-flight loses the frame → black screen. This is
28
+ * the #1 "Lynx stays blank" trap (tgi_clear alone leaves the back
29
+ * page stale on this core, so we clear with a full-screen bar). */
30
+ while (tgi_busy()) { }
31
+
32
+ /* Blue field so the screen is obviously not blank... */
33
+ tgi_setcolor(COLOR_BLUE);
34
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
35
+ /* ...with a green band so no single colour fills the whole screen. */
36
+ tgi_setcolor(COLOR_GREEN);
37
+ tgi_bar(0, 60, tgi_getmaxx(), 102);
38
+
39
+ /* The joystick-driven player square on top. */
26
40
  tgi_setcolor(COLOR_YELLOW);
27
41
  tgi_bar(x, y, x + 8, y + 8);
28
42
  tgi_setcolor(COLOR_WHITE);
@@ -26,7 +26,19 @@ void main(void) {
26
26
  lynx_snd_play(0, (unsigned char *)demo_music);
27
27
 
28
28
  for (;;) {
29
- tgi_clear();
29
+ /* CANONICAL LYNX FRAME LOOP — full redraw every frame: WAIT for Suzy's
30
+ * blitter (drawing mid-flight loses the frame → black), then clear with
31
+ * a full-screen bar (tgi_clear leaves the back page stale on this core)
32
+ * before drawing. The #1 "Lynx stays blank" trap. */
33
+ while (tgi_busy()) { }
34
+
35
+ /* Two colour bands so the backdrop is obviously not blank and no single
36
+ * colour fills the whole screen (a flat fill still reads as "blank"). */
37
+ tgi_setcolor(COLOR_BLUE);
38
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
39
+ tgi_setcolor(COLOR_PURPLE);
40
+ tgi_bar(0, 56, tgi_getmaxx(), 102);
41
+
30
42
  tgi_setcolor(COLOR_WHITE);
31
43
  tgi_outtextxy(20, 20, "LYNX MUSIC DEMO");
32
44
  tgi_setcolor(COLOR_YELLOW);
@@ -98,8 +98,35 @@ void main(void) {
98
98
  * — drawing while the blitter is mid-flight loses the frame → black.
99
99
  * (Copied from the shmup scaffold, the LYNX-1 fix.) */
100
100
  while (tgi_busy()) { }
101
+
102
+ /* ── Background scene (drawn every frame). Without it the playfield is
103
+ * a near-flat single colour and the render-health audit flags the
104
+ * screen as blank. A framed "well" in the centre with lit side panels
105
+ * keeps several distinct colours well under the threshold:
106
+ * - blue cabinet backdrop
107
+ * - dark-grey side panels flanking the well
108
+ * - black well interior so the falling blocks read clearly
109
+ * - light-grey well frame + a faint grid texture behind the cells. */
110
+ tgi_setcolor(COLOR_BLUE);
111
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* cabinet backdrop */
112
+ tgi_setcolor(COLOR_DARKGREY);
113
+ tgi_bar(0, 0, GRID_X - 5, 101); /* left side panel */
114
+ tgi_bar(GRID_X + COLS * CELL_PX + 4, 0, 159, 101); /* right side panel */
101
115
  tgi_setcolor(COLOR_BLACK);
102
- tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
116
+ tgi_bar(GRID_X - 2, GRID_Y - 2,
117
+ GRID_X + COLS * CELL_PX + 1, GRID_Y + ROWS * CELL_PX + 1); /* well */
118
+ /* faint grid texture so the empty well is never one flat colour */
119
+ tgi_setcolor(COLOR_DARKGREY);
120
+ for (r = 0; r <= ROWS; r++)
121
+ tgi_line(GRID_X, GRID_Y + r * CELL_PX, GRID_X + COLS * CELL_PX - 1, GRID_Y + r * CELL_PX);
122
+ for (c = 0; c <= COLS; c++)
123
+ tgi_line(GRID_X + c * CELL_PX, GRID_Y, GRID_X + c * CELL_PX, GRID_Y + ROWS * CELL_PX - 1);
124
+ /* well frame */
125
+ tgi_setcolor(COLOR_LIGHTGREY);
126
+ tgi_line(GRID_X - 2, GRID_Y - 2, GRID_X - 2, GRID_Y + ROWS * CELL_PX + 1);
127
+ tgi_line(GRID_X + COLS * CELL_PX + 1, GRID_Y - 2, GRID_X + COLS * CELL_PX + 1, GRID_Y + ROWS * CELL_PX + 1);
128
+ tgi_line(GRID_X - 2, GRID_Y + ROWS * CELL_PX + 1, GRID_X + COLS * CELL_PX + 1, GRID_Y + ROWS * CELL_PX + 1);
129
+
103
130
  /* grid */
104
131
  for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) {
105
132
  if (grid[r][c] != 0) {
@@ -23,6 +23,8 @@ void main(void) {
23
23
  uint8_t game_over = 0;
24
24
  uint8_t joy, i;
25
25
  uint32_t rng = 1;
26
+ uint8_t scroll = 0; /* animates the road dashes so the track moves */
27
+ int16_t y;
26
28
 
27
29
  tgi_install(&lynx_160_102_16_tgi);
28
30
  tgi_init();
@@ -36,14 +38,37 @@ void main(void) {
36
38
  * — drawing while the blitter is mid-flight loses the frame → black.
37
39
  * (Copied from the shmup scaffold, the LYNX-1 fix.) */
38
40
  while (tgi_busy()) { }
39
- tgi_setcolor(COLOR_BLACK);
40
- tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
41
- /* lane lines */
41
+
42
+ /* ── Background scene (drawn every frame). Without it the track is a
43
+ * near-flat single colour and the render-health audit flags the
44
+ * screen as blank. A full road with grass shoulders + animated lane
45
+ * dashes keeps several distinct colours well under the threshold:
46
+ * - green grass shoulders on both sides
47
+ * - mid-grey tarmac with darker-grey lane bands
48
+ * - white scrolling centre dashes + solid edge lines. */
49
+ tgi_setcolor(COLOR_GREEN);
50
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* grass base */
51
+ tgi_setcolor(COLOR_GREY);
52
+ tgi_bar(20, 0, 148, 101); /* tarmac */
53
+ /* darker lane bands so the road isn't one flat grey */
42
54
  tgi_setcolor(COLOR_DARKGREY);
43
- tgi_line(28, 0, 28, 101);
44
- tgi_line(72, 0, 72, 101);
45
- tgi_line(116, 0, 116, 101);
46
- tgi_line(160, 0, 160, 101);
55
+ tgi_bar(20, 0, 53, 101);
56
+ tgi_bar(96, 0, 128, 101);
57
+ /* solid road edges */
58
+ tgi_setcolor(COLOR_WHITE);
59
+ tgi_line(20, 0, 20, 101);
60
+ tgi_line(148, 0, 148, 101);
61
+ /* animated dashed lane dividers (scroll downward) */
62
+ for (y = (int16_t)scroll - 12; y < 102; y += 12) {
63
+ tgi_bar(53, (unsigned)(y < 0 ? 0 : y), 55, (unsigned)(y + 6 > 101 ? 101 : y + 6));
64
+ tgi_bar(96, (unsigned)(y < 0 ? 0 : y), 98, (unsigned)(y + 6 > 101 ? 101 : y + 6));
65
+ }
66
+ /* grass rumble strips for extra colour texture */
67
+ tgi_setcolor(COLOR_LIGHTGREEN);
68
+ for (y = (int16_t)scroll - 8; y < 102; y += 16) {
69
+ tgi_bar(0, (unsigned)(y < 0 ? 0 : y), 6, (unsigned)(y + 6 > 101 ? 101 : y + 6));
70
+ tgi_bar(153, (unsigned)(y < 0 ? 0 : y), 159, (unsigned)(y + 6 > 101 ? 101 : y + 6));
71
+ }
47
72
 
48
73
  tgi_setcolor(COLOR_YELLOW);
49
74
  tgi_bar((unsigned)player.x - 4, (unsigned)player.y - 4, (unsigned)player.x + 4, (unsigned)player.y + 4);
@@ -54,6 +79,8 @@ void main(void) {
54
79
  tgi_updatedisplay();
55
80
  sfx_update();
56
81
 
82
+ scroll += 2; if (scroll >= 12) scroll -= 12; /* advance road dashes */
83
+
57
84
  if (game_over > 0) {
58
85
  game_over--;
59
86
  if (game_over == 0) {
@@ -50,8 +50,15 @@ static void spawn(void) {
50
50
  }
51
51
  }
52
52
 
53
+ /* Scrolling starfield: a handful of stars that drift down so the dark
54
+ * space field is never a flat single colour (would read as "blank"). */
55
+ #define N_STARS 24
56
+ static uint8_t star_x[N_STARS];
57
+ static uint8_t star_y[N_STARS];
58
+
53
59
  void main(void) {
54
60
  uint8_t joy, fire_now, i, j;
61
+ uint32_t srng = 0x1234;
55
62
 
56
63
  tgi_install(&lynx_160_102_16_tgi);
57
64
  tgi_init();
@@ -61,6 +68,12 @@ void main(void) {
61
68
  player.x = 76; player.y = 90; player.alive = 1;
62
69
  for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
63
70
  for (i = 0; i < MAX_ENEMIES; i++) enemies[i].alive = 0;
71
+ for (i = 0; i < N_STARS; i++) {
72
+ srng = srng * 1103515245u + 12345u;
73
+ star_x[i] = (uint8_t)((srng >> 16) % 160);
74
+ srng = srng * 1103515245u + 12345u;
75
+ star_y[i] = (uint8_t)((srng >> 16) % 102);
76
+ }
64
77
  spawn_timer = 0;
65
78
  prev_btn = 0;
66
79
 
@@ -76,10 +89,31 @@ void main(void) {
76
89
  * 3. DRAW every object.
77
90
  * 4. tgi_updatedisplay() to push the frame. */
78
91
  while (tgi_busy()) { }
79
- tgi_setcolor(COLOR_BLACK);
80
- tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* maxx/maxy = 159/101 = full screen */
81
92
 
82
- /* Render */
93
+ /* ── Background scene (drawn every frame; without it the dark space
94
+ * field is a near-flat single colour and the render-health audit
95
+ * flags the screen as blank). Layered bands keep any one colour well
96
+ * under the threshold:
97
+ * - deep-blue upper space
98
+ * - grey nebula band across the middle
99
+ * - green planet surface along the bottom
100
+ * - a drifting white/yellow starfield over the space. */
101
+ tgi_setcolor(COLOR_BLUE);
102
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* base space field */
103
+ tgi_setcolor(COLOR_GREY);
104
+ tgi_bar(0, 34, 159, 60); /* nebula band */
105
+ tgi_setcolor(COLOR_GREEN);
106
+ tgi_bar(0, 84, 159, 101); /* planet surface */
107
+ tgi_setcolor(COLOR_LIGHTGREEN);
108
+ tgi_bar(0, 78, 159, 83); /* surface horizon */
109
+ /* starfield (bright specks; also drifts downward each frame) */
110
+ tgi_setcolor(COLOR_WHITE);
111
+ for (i = 0; i < N_STARS; i++) {
112
+ tgi_setpixel(star_x[i], star_y[i]);
113
+ tgi_setpixel(star_x[i], (star_y[i] + 1) % 102);
114
+ }
115
+
116
+ /* Render game objects on top */
83
117
  tgi_setcolor(COLOR_YELLOW);
84
118
  tgi_bar(player.x, player.y, player.x + 6, player.y + 6);
85
119
  tgi_setcolor(COLOR_WHITE);
@@ -93,6 +127,11 @@ void main(void) {
93
127
  tgi_updatedisplay();
94
128
  sfx_update();
95
129
 
130
+ /* drift the starfield downward */
131
+ for (i = 0; i < N_STARS; i++) {
132
+ if (star_y[i] >= 101) star_y[i] = 0; else star_y[i]++;
133
+ }
134
+
96
135
  /* Input + state */
97
136
  joy = joy_read(JOY_1);
98
137
  fire_now = JOY_BTN_1(joy) ? 1 : 0;
@@ -21,6 +21,7 @@ void main(void) {
21
21
  int16_t p1y = 40, p2y = 40, bx = 78, by = 48;
22
22
  int8_t bdx = 2, bdy = 1;
23
23
  uint8_t joy;
24
+ int16_t ny; /* loop var for the dashed centre net */
24
25
 
25
26
  tgi_install(&lynx_160_102_16_tgi);
26
27
  tgi_init();
@@ -33,8 +34,34 @@ void main(void) {
33
34
  * — drawing while the blitter is mid-flight loses the frame → black.
34
35
  * (Copied from the shmup scaffold, the LYNX-1 fix.) */
35
36
  while (tgi_busy()) { }
36
- tgi_setcolor(COLOR_BLACK);
37
- tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
37
+
38
+ /* ── Background scene (drawn every frame). Without it the court is a
39
+ * near-flat single colour and the render-health audit flags the
40
+ * screen as blank. A two-tone court with boards + net markings keeps
41
+ * several distinct colours well under the threshold:
42
+ * - green centre court
43
+ * - lighter-green end zones behind each paddle
44
+ * - dark-grey boards top and bottom
45
+ * - white boundary, dashed centre net + centre circle. */
46
+ tgi_setcolor(COLOR_GREEN);
47
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy()); /* court grass */
48
+ tgi_setcolor(COLOR_LIGHTGREEN);
49
+ tgi_bar(0, COURT_TOP, 52, COURT_BOT - 1); /* left end zone */
50
+ tgi_bar(107, COURT_TOP, 159, COURT_BOT - 1); /* right end zone */
51
+ tgi_setcolor(COLOR_DARKGREY);
52
+ tgi_bar(0, 0, 159, COURT_TOP - 1); /* top boards */
53
+ tgi_bar(0, COURT_BOT, 159, 101); /* bottom boards */
54
+ /* white court boundary + dashed centre net + centre circle */
55
+ tgi_setcolor(COLOR_WHITE);
56
+ tgi_line(0, COURT_TOP, 159, COURT_TOP);
57
+ tgi_line(0, COURT_BOT, 159, COURT_BOT);
58
+ for (ny = COURT_TOP; ny < COURT_BOT; ny += 8)
59
+ tgi_bar(79, (unsigned)ny, 80, (unsigned)(ny + 3 > COURT_BOT ? COURT_BOT : ny + 3));
60
+ tgi_line(70, 40, 90, 40);
61
+ tgi_line(70, 60, 90, 60);
62
+ tgi_line(70, 40, 70, 60);
63
+ tgi_line(90, 40, 90, 60);
64
+
38
65
  tgi_setcolor(COLOR_WHITE);
39
66
  tgi_bar(PADDLE_X1, (unsigned)p1y, PADDLE_X1 + PADDLE_W - 1, (unsigned)(p1y + PADDLE_H - 1));
40
67
  tgi_bar(PADDLE_X2, (unsigned)p2y, PADDLE_X2 + PADDLE_W - 1, (unsigned)(p2y + PADDLE_H - 1));