romdevtools 0.13.0 → 0.15.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 (124) hide show
  1. package/AGENTS.md +21 -14
  2. package/CHANGELOG.md +125 -1
  3. package/README.md +13 -8
  4. package/examples/atari2600/main.asm +1 -1
  5. package/examples/atari2600/templates/default.asm +1 -1
  6. package/examples/atari2600/templates/paddle.asm +59 -47
  7. package/examples/atari7800/main.c +1 -1
  8. package/examples/atari7800/templates/default.c +1 -1
  9. package/examples/atari7800/templates/music_demo.c +1 -1
  10. package/examples/c64/main.c +1 -1
  11. package/examples/c64/templates/platformer.c +2 -2
  12. package/examples/c64/templates/puzzle.c +1 -1
  13. package/examples/c64/templates/racing.c +3 -3
  14. package/examples/c64/templates/shmup.c +6 -5
  15. package/examples/c64/templates/sports.c +4 -4
  16. package/examples/gb/main.asm +1 -1
  17. package/examples/gb/main.c +1 -1
  18. package/examples/gb/templates/puzzle.c +1 -1
  19. package/examples/gb/templates/racing.c +1 -1
  20. package/examples/gb/templates/shmup.c +1 -1
  21. package/examples/gba/templates/gba_hello.c +1 -1
  22. package/examples/gba/templates/maxmod_demo.c +1 -1
  23. package/examples/gba/templates/puzzle.c +17 -3
  24. package/examples/gba/templates/racing.c +16 -2
  25. package/examples/gba/templates/shmup.c +23 -4
  26. package/examples/gba/templates/tonc_hello.c +6 -4
  27. package/examples/gbc/main.asm +1 -1
  28. package/examples/gbc/templates/puzzle.c +1 -1
  29. package/examples/gbc/templates/racing.c +1 -1
  30. package/examples/gbc/templates/shmup.c +1 -1
  31. package/examples/genesis/main.s +1 -1
  32. package/examples/genesis/templates/puzzle.c +1 -1
  33. package/examples/genesis/templates/racing.c +45 -1
  34. package/examples/genesis/templates/shmup.c +12 -3
  35. package/examples/genesis/templates/shmup_2p.c +2 -2
  36. package/examples/genesis/templates/sports.c +39 -0
  37. package/examples/gg/templates/hello_sprite.c +38 -23
  38. package/examples/gg/templates/music_demo.c +11 -8
  39. package/examples/gg/templates/platformer.c +37 -15
  40. package/examples/gg/templates/racing.c +25 -12
  41. package/examples/gg/templates/shmup.c +12 -6
  42. package/examples/gg/templates/sports.c +30 -16
  43. package/examples/gg/templates/tile_engine.c +24 -10
  44. package/examples/lynx/templates/platformer.c +7 -1
  45. package/examples/lynx/templates/puzzle.c +8 -2
  46. package/examples/lynx/templates/racing.c +7 -1
  47. package/examples/lynx/templates/sports.c +7 -1
  48. package/examples/nes/main.c +2 -2
  49. package/examples/nes/space-shooter/nes_runtime.h +1 -1
  50. package/examples/nes/templates/default.c +4 -1
  51. package/examples/nes/templates/racing.c +50 -1
  52. package/examples/pce/main.c +1 -1
  53. package/examples/sms/templates/hello_sprite.c +1 -1
  54. package/examples/sms/templates/music_demo.c +1 -1
  55. package/examples/sms/templates/puzzle.c +1 -1
  56. package/examples/sms/templates/racing.c +1 -1
  57. package/examples/sms/templates/shmup.c +1 -1
  58. package/examples/sms/templates/shmup_2p.c +2 -2
  59. package/examples/snes/main.asm +1 -1
  60. package/examples/snes/templates/c-hello-data.asm +309 -14
  61. package/examples/snes/templates/c-hello.c +13 -2
  62. package/examples/snes/templates/default.c +1 -1
  63. package/examples/snes/templates/hello_sprite-data.asm +300 -2
  64. package/examples/snes/templates/hello_sprite.c +10 -1
  65. package/examples/snes/templates/music_demo-data.asm +300 -2
  66. package/examples/snes/templates/music_demo.c +10 -1
  67. package/examples/snes/templates/platformer-data.asm +300 -2
  68. package/examples/snes/templates/platformer.c +10 -1
  69. package/examples/snes/templates/puzzle-data.asm +300 -2
  70. package/examples/snes/templates/puzzle.c +11 -1
  71. package/examples/snes/templates/racing-data.asm +300 -2
  72. package/examples/snes/templates/racing.c +40 -4
  73. package/examples/snes/templates/shmup-data.asm +299 -6
  74. package/examples/snes/templates/shmup.c +11 -7
  75. package/examples/snes/templates/sports-data.asm +300 -2
  76. package/examples/snes/templates/sports.c +40 -5
  77. package/package.json +1 -1
  78. package/src/cheats/lookup.js +39 -18
  79. package/src/http/routes.js +58 -33
  80. package/src/http/skill-doc.js +10 -9
  81. package/src/http/swagger.js +1 -1
  82. package/src/http/tool-registry.js +72 -5
  83. package/src/mcp/server.js +6 -5
  84. package/src/mcp/state.js +8 -6
  85. package/src/mcp/tool-manifest.js +7 -7
  86. package/src/mcp/tools/cheats.js +4 -3
  87. package/src/mcp/tools/index.js +18 -2
  88. package/src/mcp/tools/playtest.js +48 -35
  89. package/src/mcp/tools/project.js +39 -73
  90. package/src/mcp/tools/rom-id.js +49 -4
  91. package/src/mcp/tools/tile-inspect.js +1 -1
  92. package/src/mcp/tools/toolchain.js +183 -19
  93. package/src/mcp/tools/trace-vram-source.js +3 -3
  94. package/src/mcp/tools/watch-memory.js +27 -46
  95. package/src/observer/livestream.html +41 -5
  96. package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +5 -5
  97. package/src/platforms/gb/MENTAL_MODEL.md +3 -3
  98. package/src/platforms/gb/TROUBLESHOOTING.md +1 -1
  99. package/src/platforms/gb/UPSTREAM_SOURCES.md +1 -1
  100. package/src/platforms/gb/lib/c/README.md +2 -2
  101. package/src/platforms/gb/lib/c/SDCC_GOTCHAS.md +1 -1
  102. package/src/platforms/gbc/MENTAL_MODEL.md +3 -3
  103. package/src/platforms/gbc/TROUBLESHOOTING.md +5 -5
  104. package/src/platforms/gbc/UPSTREAM_SOURCES.md +2 -2
  105. package/src/platforms/gbc/lib/c/README.md +2 -2
  106. package/src/platforms/gbc/lib/c/SDCC_GOTCHAS.md +1 -1
  107. package/src/platforms/gg/MENTAL_MODEL.md +14 -13
  108. package/src/platforms/gg/lib/c/vdp_init.c +10 -8
  109. package/src/platforms/msx/MENTAL_MODEL.md +1 -1
  110. package/src/platforms/nes/TROUBLESHOOTING.md +1 -1
  111. package/src/platforms/nes/lib/c/nes_runtime.c +28 -6
  112. package/src/platforms/pce/MENTAL_MODEL.md +1 -1
  113. package/src/platforms/pce/lib/c/pce_hw.h +1 -0
  114. package/src/platforms/pce/lib/c/pce_video.c +26 -0
  115. package/src/platforms/sms/MENTAL_MODEL.md +12 -12
  116. package/src/platforms/sms/lib/c/vdp_init.c +10 -8
  117. package/src/platforms/sms/lib/vdp_init.s +1 -1
  118. package/src/playtest/playtest.js +25 -0
  119. package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +1 -1
  120. package/src/toolchains/cc65/presets/nes/chr-ram.cfg +1 -1
  121. package/src/toolchains/cc65/presets/nes/chr-ram.crt0.s +1 -1
  122. package/src/toolchains/genesis-c/README.md +1 -1
  123. package/src/toolchains/sdcc/preflight-lint.js +47 -7
  124. package/src/toolchains/snes-c/snes-c.js +3 -7
@@ -40,13 +40,27 @@ extern void gg_sat_upload(void);
40
40
  #define SCREEN_W 160 /* GG visible window is 160 px wide */
41
41
  #define VIS_ROWS 24
42
42
 
43
- static const uint8_t palette[32] = {
44
- /* BG: backdrop blue, wall mid-grey */
45
- 0x10,0x14,0x00,0x00, 0x00,0x00,0x00,0x00,
46
- 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
47
- /* Sprite: player red */
48
- 0x00,0x03,0x00,0x00, 0x00,0x00,0x00,0x00,
49
- 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
43
+ /* ── Game Gear visible viewport ──────────────────────────────────────
44
+ * Only the centered 160x144 of the 256x192 frame shows. The BG is
45
+ * scrolled so world column camX/8 appears at fetch pixel VIS_X0, and the
46
+ * player sprite is drawn at (worldX - camX) + VIS_X0 so it stays aligned
47
+ * with the BG inside the visible window. */
48
+ #define VIS_X0 48
49
+ #define VIS_Y0 24
50
+ #define VIS_X1 207 /* 48 + 160 - 1 */
51
+ #define VIS_Y1 167 /* 24 + 144 - 1 */
52
+
53
+ /* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
54
+ * gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
55
+ * (entries 16-31) reading garbage = invisible sprites. BG colour 1 = entry 1
56
+ * (mid-grey wall); sprite colour 1 = entry 17 (white player). */
57
+ static const uint8_t palette[64] = {
58
+ /* BG 0-15: entry 0 = dark navy backdrop, entry 1 = mid-grey wall */
59
+ 0x20,0x02, 0xAA,0x0A, 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
+ /* SPRITE 16-31: 16=transparent, 17=white player */
62
+ 0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
63
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
50
64
  };
51
65
 
52
66
  static const uint8_t bg_tiles[32 * 2] = {
@@ -123,7 +137,10 @@ static void paint_initial(void) {
123
137
  }
124
138
 
125
139
  void main(void) {
126
- int16_t px = 16 << 4, py = 64 << 4;
140
+ /* Start above the x=32..88 platform (world y=144) so the player lands on
141
+ * it inside the GG visible vertical band [VIS_Y0..VIS_Y1], not on the
142
+ * floor at world y=176 which sits just below the visible window. */
143
+ int16_t px = 48 << 4, py = 96 << 4;
127
144
  int16_t vx = 0, vy = 0;
128
145
  int16_t camX = 0, lastCamCol = 0;
129
146
  uint8_t prev = 0;
@@ -170,13 +187,18 @@ void main(void) {
170
187
  while (camCol > lastCamCol) { lastCamCol++; paint_column(lastCamCol + 31); }
171
188
  while (camCol < lastCamCol) { lastCamCol--; paint_column(lastCamCol); }
172
189
 
173
- gg_vdp_write_reg(8, (uint8_t)(-camX & 0xFF));
174
-
175
- /* Player drawn in SCREEN space. The GG visible window starts 48 px
176
- * into the 256-px fetch, but the SAT X is in full-frame coords and
177
- * R8 shifts both alike, so worldX - camX lands the sprite correctly
178
- * relative to the scrolled BG. */
179
- sx = ipx - camX;
190
+ /* Scroll so world column (camX/8) lands at fetch pixel VIS_X0 — the
191
+ * left edge of the GG visible window. (SMS shows the whole 256-px
192
+ * fetch and uses R8 = -camX; the GG only shows the centered 160-px
193
+ * crop, so we bias by +VIS_X0.) */
194
+ gg_vdp_write_reg(8, (uint8_t)((VIS_X0 - camX) & 0xFF));
195
+
196
+ /* Player X is biased into the visible window so it stays pinned to the
197
+ * horizontally-scrolled BG: sx = (worldX - camX) + VIS_X0. The BG is
198
+ * NOT vertically scrolled (R9 = 0), so world rows map 1:1 to fetch rows
199
+ * and the player Y needs no bias — world Y already lands in the visible
200
+ * band [VIS_Y0..VIS_Y1] for the level layout below. */
201
+ sx = (ipx - camX) + VIS_X0;
180
202
  gg_sprite_set(0, (uint8_t)sx, (uint8_t)ipy, 0);
181
203
  gg_sat_upload();
182
204
 
@@ -24,18 +24,31 @@ extern void gg_sprite_init(void);
24
24
  extern void gg_sprite_set(uint8_t slot, uint8_t x, uint8_t y, uint8_t tile);
25
25
  extern void gg_sat_upload(void);
26
26
 
27
- #define LANE_LEFT_X 72
28
- #define LANE_MID_X 124
29
- #define LANE_RIGHT_X 176
30
- #define PLAYER_Y 160
27
+ /* ── Game Gear visible viewport ──────────────────────────────────────
28
+ * Only the centered 160x144 of the 256x192 frame shows. Lanes and the
29
+ * player must sit inside [VIS_X0..VIS_X1] x [VIS_Y0..VIS_Y1]. */
30
+ #define VIS_X0 48
31
+ #define VIS_Y0 24
32
+ #define VIS_X1 207 /* 48 + 160 - 1 */
33
+ #define VIS_Y1 167 /* 24 + 144 - 1 */
34
+
35
+ #define LANE_LEFT_X (VIS_X0 + 28) /* 76 */
36
+ #define LANE_MID_X ((VIS_X0 + VIS_X1) / 2 - 4) /* ~123 */
37
+ #define LANE_RIGHT_X (VIS_X1 - 36) /* 171 */
38
+ #define PLAYER_Y (VIS_Y1 - 16)
31
39
  #define MAX_OBSTACLES 4
32
40
 
33
- static const uint8_t palette[32] = {
34
- 0x10, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
35
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
36
- /* Sprite palette: white (1), red (2) */
37
- 0x00, 0x3F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
38
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
41
+ /* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
42
+ * gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
43
+ * (entries 16-31) reading garbage = invisible sprites. Sprite colour 1 = entry
44
+ * 17 (white), colour 2 = entry 18 (red). */
45
+ static const uint8_t palette[64] = {
46
+ /* BG 0-15: entry 0 = dark navy backdrop */
47
+ 0x20,0x02, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
48
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
49
+ /* SPRITE 16-31: 16=transparent, 17=white, 18=red */
50
+ 0,0, 0xFF,0x0F, 0x0F,0x00, 0,0, 0,0, 0,0, 0,0, 0,0,
51
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
39
52
  };
40
53
 
41
54
  /* Two sprite tiles — player (colour 1) + enemy (colour 2). */
@@ -86,7 +99,7 @@ static void spawn_obstacle(void) {
86
99
  for (i = 0; i < MAX_OBSTACLES; i++) {
87
100
  if (!obstacles[i].alive) {
88
101
  obstacles[i].x = lane_x[(spawn_timer * 13) % 3];
89
- obstacles[i].y = 0;
102
+ obstacles[i].y = VIS_Y0; /* enter at the top of the visible window */
90
103
  obstacles[i].alive = 1;
91
104
  return;
92
105
  }
@@ -142,7 +155,7 @@ void main(void) {
142
155
  for (i = 0; i < MAX_OBSTACLES; i++) {
143
156
  if (!obstacles[i].alive) continue;
144
157
  obstacles[i].y = (uint8_t)(obstacles[i].y + step);
145
- if (obstacles[i].y >= 184) obstacles[i].alive = 0;
158
+ if (obstacles[i].y >= VIS_Y1) obstacles[i].alive = 0; /* off visible bottom */
146
159
  }
147
160
 
148
161
  spawn_timer = (uint8_t)(spawn_timer + 1);
@@ -46,12 +46,18 @@ extern void gg_sat_upload(void);
46
46
  #define T_BULLET 1
47
47
  #define T_ENEMY 2
48
48
 
49
- static const uint8_t palette[32] = {
50
- 0x10,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
51
- 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
52
- /* Sprite palette: white, yellow, red */
53
- 0x00,0x3F,0x0F,0x03, 0x00,0x00,0x00,0x00,
54
- 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
49
+ /* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
50
+ * Entries 0-15 = BG, 16-31 = SPRITE. (The earlier 32-byte SMS-style array was
51
+ * the GG #1 invisible-sprite bug: gg_load_palette reads 64 bytes, so a 32-byte
52
+ * array left the sprite palette (entries 16-31) reading past the array = garbage
53
+ * = invisible sprites.) */
54
+ 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,
57
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
58
+ /* SPRITE 16-31: 16=transparent, 17=white, 18=yellow, 19=red */
59
+ 0,0, 0xFF,0x0F, 0xFF,0x00, 0x0F,0x00, 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,
55
61
  };
56
62
 
57
63
  static const uint8_t sprite_tiles[32 * 3] = {
@@ -17,19 +17,32 @@ extern void gg_sprite_init(void);
17
17
  extern void gg_sprite_set(uint8_t slot, uint8_t x, uint8_t y, uint8_t tile);
18
18
  extern void gg_sat_upload(void);
19
19
 
20
- #define COURT_TOP 8
21
- #define COURT_BOT 184
20
+ /* ── Game Gear visible viewport ──────────────────────────────────────
21
+ * Only the centered 160x144 of the 256x192 frame shows. Keep the whole
22
+ * court inside [VIS_X0..VIS_X1] x [VIS_Y0..VIS_Y1] or it's off-screen. */
23
+ #define VIS_X0 48
24
+ #define VIS_Y0 24
25
+ #define VIS_X1 207 /* 48 + 160 - 1 */
26
+ #define VIS_Y1 167 /* 24 + 144 - 1 */
27
+
28
+ #define COURT_TOP VIS_Y0
29
+ #define COURT_BOT VIS_Y1
22
30
  #define PADDLE_H 24
23
31
  #define BALL_SIZE 8
24
- #define PADDLE_X1 16
25
- #define PADDLE_X2 232
26
-
27
- static const uint8_t palette[32] = {
28
- 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
29
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
30
- /* Sprite palette: white at idx 1 */
31
- 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
32
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
32
+ #define PADDLE_X1 (VIS_X0 + 8) /* near the visible left edge */
33
+ #define PADDLE_X2 (VIS_X1 - 16) /* near the visible right edge */
34
+
35
+ /* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
36
+ * gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
37
+ * (entries 16-31) reading garbage = invisible sprites. Sprite colour 1 = entry
38
+ * 17 (white). */
39
+ 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,
42
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
43
+ /* SPRITE 16-31: 16=transparent, 17=white */
44
+ 0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
45
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
33
46
  };
34
47
 
35
48
  static const uint8_t tile_solid[32] = {
@@ -45,15 +58,16 @@ static uint8_t score_p1, score_p2;
45
58
  static uint8_t serve_timer;
46
59
 
47
60
  static void serve_ball(uint8_t to_left) {
48
- bx = 124;
49
- by = 90;
61
+ bx = (VIS_X0 + VIS_X1) / 2;
62
+ by = (VIS_Y0 + VIS_Y1) / 2;
50
63
  bdx = to_left ? -2 : 2;
51
64
  bdy = ((score_p1 + score_p2) & 1) ? -1 : 1;
52
65
  serve_timer = 30;
53
66
  }
54
67
 
55
68
  static void reset_match(void) {
56
- p1y = 84; p2y = 84;
69
+ p1y = (VIS_Y0 + VIS_Y1) / 2 - PADDLE_H / 2;
70
+ p2y = p1y;
57
71
  score_p1 = 0; score_p2 = 0;
58
72
  serve_ball(0);
59
73
  }
@@ -130,8 +144,8 @@ void main(void) {
130
144
  sfx_tone(0, 250, 3);
131
145
  }
132
146
 
133
- if (bx < 4) { if (score_p2 < 9) score_p2++; sfx_noise(20); serve_ball(0); }
134
- if (bx > 252) { if (score_p1 < 9) score_p1++; sfx_tone(0, 180, 16); serve_ball(1); }
147
+ if (bx < VIS_X0) { if (score_p2 < 9) score_p2++; sfx_noise(20); serve_ball(0); }
148
+ if (bx > VIS_X1 - BALL_SIZE) { if (score_p1 < 9) score_p1++; sfx_tone(0, 180, 16); serve_ball(1); }
135
149
  }
136
150
  } while (1);
137
151
  }
@@ -30,15 +30,28 @@ extern void gg_sat_upload(void);
30
30
 
31
31
  #define T_OPEN 0
32
32
  #define T_WALL 1
33
- #define T_SPR 0 /* sprite tile uses sprite-tile-bank (R6=0xFB) */
34
-
35
- static const uint8_t palette[32] = {
36
- /* BG palette: backdrop blue, wall colour 1 dark grey, colour 2 light grey */
37
- 0x10,0x14,0x2A,0x00, 0x00,0x00,0x00,0x00,
38
- 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
39
- /* Sprite palette: white at idx 1 */
40
- 0x00,0x3F,0x00,0x00, 0x00,0x00,0x00,0x00,
41
- 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
33
+ #define T_SPR 0 /* sprite tile uses sprite-tile-bank (R6=0xFF → $2000) */
34
+
35
+ /* ── Game Gear visible viewport ──────────────────────────────────────
36
+ * The 32x24 name table fills the whole 256x192 frame, but only the
37
+ * centered 160x144 SHOWS: fetch pixels [VIS_X0..VIS_X1] x [VIS_Y0..VIS_Y1]
38
+ * (tile columns 6..25, rows 3..20). Place the player sprite inside it. */
39
+ #define VIS_X0 48
40
+ #define VIS_Y0 24
41
+ #define VIS_X1 207 /* 48 + 160 - 1 */
42
+ #define VIS_Y1 167 /* 24 + 144 - 1 */
43
+
44
+ /* GG palette = 32 entries × 2 bytes (4-4-4 BGR LE): low=(g<<4)|r, high=b.
45
+ * gg_load_palette reads 64 bytes; a 32-byte array leaves the sprite palette
46
+ * (entries 16-31) reading garbage = invisible sprites. BG colour 1 = entry 1
47
+ * (dark grey wall); sprite colour 1 = entry 17 (white player). */
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,
51
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
52
+ /* SPRITE 16-31: 16=transparent, 17=white player */
53
+ 0,0, 0xFF,0x0F, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
54
+ 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0,
42
55
  };
43
56
 
44
57
  static const uint8_t bg_tiles[32 * 2] = {
@@ -92,7 +105,8 @@ static uint8_t solid_at(int16_t px, int16_t py) {
92
105
  }
93
106
 
94
107
  void main(void) {
95
- int16_t px = 16, py = 16;
108
+ /* Start the player inside the visible window (tile ~10,10 = pixel 80,80). */
109
+ int16_t px = 80, py = 80;
96
110
 
97
111
  gg_vdp_init();
98
112
  gg_load_palette(palette);
@@ -43,7 +43,13 @@ void main(void) {
43
43
  sfx_init();
44
44
 
45
45
  for (;;) {
46
- tgi_clear();
46
+ /* Lynx frame loop: WAIT for the blitter, then clear with a full-screen
47
+ * tgi_bar (NOT tgi_clear, which leaves the back page stale on this core)
48
+ * — drawing while the blitter is mid-flight loses the frame → black.
49
+ * (Copied from the shmup scaffold, the LYNX-1 fix.) */
50
+ while (tgi_busy()) { }
51
+ tgi_setcolor(COLOR_BLACK);
52
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
47
53
  tgi_setcolor(COLOR_GREY);
48
54
  for (i = 0; i < N_PLATFORMS; i++) {
49
55
  tgi_bar(platforms[i].x, platforms[i].y, platforms[i].x + platforms[i].w - 1, platforms[i].y + platforms[i].h - 1);
@@ -62,7 +62,7 @@ static void lock_piece(void) {
62
62
  a = grid[r][c]; b = grid[r][c+1]; d = grid[r][c+2];
63
63
  if (a != 0 && a == b && b == d) {
64
64
  grid[r][c] = 0; grid[r][c+1] = 0; grid[r][c+2] = 0;
65
- if (score < 65500) score += 30;
65
+ if (score < 65500u) score += 30;
66
66
  sfx_tone(0, 60, 10);
67
67
  }
68
68
  }
@@ -93,7 +93,13 @@ void main(void) {
93
93
  new_piece();
94
94
 
95
95
  for (;;) {
96
- tgi_clear();
96
+ /* Lynx frame loop: WAIT for the blitter, then clear with a full-screen
97
+ * tgi_bar (NOT tgi_clear, which leaves the back page stale on this core)
98
+ * — drawing while the blitter is mid-flight loses the frame → black.
99
+ * (Copied from the shmup scaffold, the LYNX-1 fix.) */
100
+ while (tgi_busy()) { }
101
+ tgi_setcolor(COLOR_BLACK);
102
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
97
103
  /* grid */
98
104
  for (r = 0; r < ROWS; r++) for (c = 0; c < COLS; c++) {
99
105
  if (grid[r][c] != 0) {
@@ -31,7 +31,13 @@ void main(void) {
31
31
  for (i = 0; i < MAX_OBS; i++) obs[i].alive = 0;
32
32
 
33
33
  for (;;) {
34
- tgi_clear();
34
+ /* Lynx frame loop: WAIT for the blitter, then clear with a full-screen
35
+ * tgi_bar (NOT tgi_clear, which leaves the back page stale on this core)
36
+ * — drawing while the blitter is mid-flight loses the frame → black.
37
+ * (Copied from the shmup scaffold, the LYNX-1 fix.) */
38
+ while (tgi_busy()) { }
39
+ tgi_setcolor(COLOR_BLACK);
40
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
35
41
  /* lane lines */
36
42
  tgi_setcolor(COLOR_DARKGREY);
37
43
  tgi_line(28, 0, 28, 101);
@@ -28,7 +28,13 @@ void main(void) {
28
28
  sfx_init();
29
29
 
30
30
  for (;;) {
31
- tgi_clear();
31
+ /* Lynx frame loop: WAIT for the blitter, then clear with a full-screen
32
+ * tgi_bar (NOT tgi_clear, which leaves the back page stale on this core)
33
+ * — drawing while the blitter is mid-flight loses the frame → black.
34
+ * (Copied from the shmup scaffold, the LYNX-1 fix.) */
35
+ while (tgi_busy()) { }
36
+ tgi_setcolor(COLOR_BLACK);
37
+ tgi_bar(0, 0, tgi_getmaxx(), tgi_getmaxy());
32
38
  tgi_setcolor(COLOR_WHITE);
33
39
  tgi_bar(PADDLE_X1, (unsigned)p1y, PADDLE_X1 + PADDLE_W - 1, (unsigned)(p1y + PADDLE_H - 1));
34
40
  tgi_bar(PADDLE_X2, (unsigned)p2y, PADDLE_X2 + PADDLE_W - 1, (unsigned)(p2y + PADDLE_H - 1));
@@ -9,9 +9,9 @@
9
9
  * 5. Enables the PPU and loops forever (NMI does all the real work).
10
10
  *
11
11
  * BUILD: this file expects linkerConfig: "chr-ram" so the CHR-RAM
12
- * region is writable at runtime. Pass it to buildSource:
12
+ * region is writable at runtime. Pass it to build:
13
13
  *
14
- * buildSource({
14
+ * build({ output: "rom",
15
15
  * platform: "nes",
16
16
  * language: "c",
17
17
  * linkerConfig: "chr-ram",
@@ -1,5 +1,5 @@
1
1
  /* ── nes_runtime.h — neslib-shaped runtime for cc65 NES builds ───
2
- * Auto-included on every `buildSource({platform:"nes", language:"c"})`.
2
+ * Auto-included on every `build({ output: "rom", platform:"nes", language:"c"})`.
3
3
  *
4
4
  * API mirrors Shiru's neslib so existing tutorials port cleanly:
5
5
  *
@@ -13,7 +13,10 @@
13
13
 
14
14
  #include "nes_runtime.h"
15
15
 
16
- static const uint8_t bg_colors[4] = { 0x0F, 0x01, 0x21, 0x31 };
16
+ /* 4 VISIBLE hues never 0x0F (black), so a screenshot on ANY phase of the
17
+ * cycle shows a non-black backdrop (NES-2: the old cycle started on + passed
18
+ * through black, so a fresh agent's screenshot could land on a "blank" frame). */
19
+ static const uint8_t bg_colors[4] = { 0x21, 0x01, 0x11, 0x31 };
17
20
 
18
21
  void main(void) {
19
22
  uint8_t palette[32];
@@ -56,6 +56,26 @@ static const uint8_t tile_digits[10 * 16] = {
56
56
  #define T_CAR_ENEMY 2
57
57
  #define T_DIGIT0 3
58
58
 
59
+ /* ── Background road tiles ───────────────────────────────────────────
60
+ * Default PPUCTRL ($90) reads BG patterns from pattern table 1 ($1000),
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.
64
+ *
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. */
68
+ #define BG_T_EDGE 1
69
+ #define BG_T_LANE 2
70
+ static const uint8_t bg_tile_edge[16] = {
71
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, /* plane 0 (colour bit 0) */
72
+ 0, 0, 0, 0, 0, 0, 0, 0,
73
+ };
74
+ static const uint8_t bg_tile_lane[16] = {
75
+ 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, /* dash: 4 on, 4 off */
76
+ 0, 0, 0, 0, 0, 0, 0, 0,
77
+ };
78
+
59
79
  static const uint8_t palette[32] = {
60
80
  /* BG palettes — light grey backdrop simulates road */
61
81
  0x10, 0x30, 0x16, 0x12,
@@ -93,6 +113,29 @@ static uint8_t aabb(Car *a, Car *b) {
93
113
  && a->y < b->y + 8 && a->y + 8 > b->y;
94
114
  }
95
115
 
116
+ /* Draw the static road into nametable 0 ($2000): solid shoulder lines on
117
+ * the outside of the outer lanes and dashed dividers between the three
118
+ * lanes. PPU must be OFF — call from init (uses vram_unsafe_set). Tile
119
+ * columns: lanes sit at 11/15/19, so dividers go at 13/17 and shoulders
120
+ * just outside at 9/21. */
121
+ #define ROAD_TOP_ROW 2
122
+ #define ROAD_BOT_ROW 27
123
+ #define ROAD_EDGE_L 9
124
+ #define ROAD_EDGE_R 21
125
+ #define ROAD_DIV_1 13
126
+ #define ROAD_DIV_2 17
127
+ static void draw_road(void) {
128
+ uint8_t row;
129
+ uint16_t base;
130
+ for (row = ROAD_TOP_ROW; row <= ROAD_BOT_ROW; row++) {
131
+ base = (uint16_t)(0x2000 + (uint16_t)row * 32);
132
+ vram_unsafe_set((uint16_t)(base + ROAD_EDGE_L), BG_T_EDGE);
133
+ vram_unsafe_set((uint16_t)(base + ROAD_EDGE_R), BG_T_EDGE);
134
+ vram_unsafe_set((uint16_t)(base + ROAD_DIV_1), BG_T_LANE);
135
+ vram_unsafe_set((uint16_t)(base + ROAD_DIV_2), BG_T_LANE);
136
+ }
137
+ }
138
+
96
139
  static void reset_run(void) {
97
140
  uint8_t i;
98
141
  player_lane = 1;
@@ -138,7 +181,13 @@ void main(void) {
138
181
  chr_ram_upload(T_CAR_ENEMY * 16, tile_car_enemy, 16);
139
182
  chr_ram_upload(T_DIGIT0 * 16, tile_digits, sizeof(tile_digits));
140
183
 
184
+ /* BG road tiles live in pattern table 1 ($1000) — that's where the
185
+ * 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);
188
+
141
189
  palette_load(palette);
190
+ draw_road(); /* paint the static road while the PPU is off */
142
191
  oam_clear();
143
192
  ppu_on_all();
144
193
  sound_init();
@@ -198,6 +247,6 @@ void main(void) {
198
247
  }
199
248
  }
200
249
 
201
- if (score < 65500) score++;
250
+ if (score < 65500u) score++;
202
251
  }
203
252
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Boots to a guaranteed-VISIBLE screen using cc65's conio (text) library, which
5
5
  * initializes the HuC6270 VDC + HuC6260 VCE for you and uploads a font to VRAM.
6
- * Build with: buildSource({ platform: "pce" }) (language defaults to C).
6
+ * Build with: build({ output: "rom", platform: "pce" }) (language defaults to C).
7
7
  *
8
8
  * FOOTGUN — the empty-BSS crt0 trap (cc65 pce/crt0.s line 84):
9
9
  * The PCE crt0 clears .bss with `tii __BSS_RUN__, __BSS_RUN__+1, __BSS_SIZE__-1`.
@@ -11,7 +11,7 @@
11
11
  * - 64 sprite slots × 4 bytes (Y / X / tile / unused)
12
12
  *
13
13
  * Multi-file project — main.c plus the runtime .c files. Build with:
14
- * buildSource({platform:"sms", language:"c",
14
+ * build({ output: "rom", platform:"sms", language:"c",
15
15
  * sources: { "main.c": ..., "vdp_init.c": ..., ... },
16
16
  * includes: { "sms_hw.h": ... }})
17
17
  *
@@ -12,7 +12,7 @@
12
12
  * them by name-table index).
13
13
  *
14
14
  * Build via createProject({platform:"sms", template:"music_demo"}) or
15
- * buildSource({platform:"sms", language:"c", sources:{ "main.c": ...,
15
+ * build({ output: "rom", platform:"sms", language:"c", sources:{ "main.c": ...,
16
16
  * "sms_music.c": ... , ...runtime... }}).
17
17
  */
18
18
 
@@ -126,7 +126,7 @@ static void lock_piece(void) {
126
126
  a = grid[r][c]; b = grid[r][c + 1]; d = grid[r][c + 2];
127
127
  if (a != 0 && a == b && b == d) {
128
128
  grid[r][c] = 0; grid[r][c + 1] = 0; grid[r][c + 2] = 0;
129
- if (score < 65500) score = (uint16_t)(score + 30);
129
+ if (score < 65500u) score = (uint16_t)(score + 30);
130
130
  sfx_tone(0, 200, 10); /* triple-clear chime */
131
131
  }
132
132
  }
@@ -156,6 +156,6 @@ void main(void) {
156
156
  }
157
157
  }
158
158
 
159
- if (score < 65500) score = (uint16_t)(score + 1);
159
+ if (score < 65500u) score = (uint16_t)(score + 1);
160
160
  } while (1);
161
161
  }
@@ -157,7 +157,7 @@ void main(void) {
157
157
  if (aabb(&bullets[i], &enemies[j])) {
158
158
  bullets[i].alive = 0;
159
159
  enemies[j].alive = 0;
160
- if (score < 65500) score = (uint16_t)(score + 10);
160
+ if (score < 65500u) score = (uint16_t)(score + 10);
161
161
  sfx_noise(8);
162
162
  break;
163
163
  }
@@ -199,7 +199,7 @@ void main(void) {
199
199
  if (p1_bullets[i].alive && aabb(&p1_bullets[i], &enemies[j])) {
200
200
  p1_bullets[i].alive = 0;
201
201
  enemies[j].alive = 0;
202
- if (score_p1 < 65500) score_p1 = (uint16_t)(score_p1 + 10);
202
+ if (score_p1 < 65500u) score_p1 = (uint16_t)(score_p1 + 10);
203
203
  sfx_noise(8);
204
204
  break;
205
205
  }
@@ -209,7 +209,7 @@ void main(void) {
209
209
  if (p2_bullets[i].alive && aabb(&p2_bullets[i], &enemies[j])) {
210
210
  p2_bullets[i].alive = 0;
211
211
  enemies[j].alive = 0;
212
- if (score_p2 < 65500) score_p2 = (uint16_t)(score_p2 + 10);
212
+ if (score_p2 < 65500u) score_p2 = (uint16_t)(score_p2 + 10);
213
213
  sfx_noise(8);
214
214
  break;
215
215
  }
@@ -20,7 +20,7 @@
20
20
  ; - nmi_safe.asm: vblank handler skeleton
21
21
  ;
22
22
  ; BUILD: complete LoROM image, no extra options needed.
23
- ; buildSource({ platform: "snes", source: /* this file */ });
23
+ ; build({ output: "rom", platform: "snes", source: /* this file */ });
24
24
 
25
25
  lorom
26
26