romdevtools 0.16.0 → 0.21.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 (110) hide show
  1. package/AGENTS.md +60 -12
  2. package/CHANGELOG.md +258 -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/platformer.c +43 -4
  10. package/examples/atari7800/templates/puzzle.c +39 -4
  11. package/examples/atari7800/templates/racing.c +39 -4
  12. package/examples/atari7800/templates/shmup.c +40 -2
  13. package/examples/atari7800/templates/sports.c +36 -5
  14. package/examples/c64/templates/platformer.c +19 -5
  15. package/examples/c64/templates/puzzle.c +32 -2
  16. package/examples/c64/templates/shmup.c +28 -2
  17. package/examples/c64/templates/sports.c +30 -2
  18. package/examples/gb/templates/default.c +110 -16
  19. package/examples/gb/templates/platformer.c +25 -4
  20. package/examples/gb/templates/puzzle.c +32 -2
  21. package/examples/gb/templates/racing.c +72 -8
  22. package/examples/gb/templates/shmup.c +38 -1
  23. package/examples/gb/templates/sports.c +48 -1
  24. package/examples/gba/templates/gba_hello.c +29 -11
  25. package/examples/gba/templates/puzzle.c +15 -3
  26. package/examples/gba/templates/racing.c +65 -3
  27. package/examples/gba/templates/shmup.c +41 -4
  28. package/examples/gba/templates/sports.c +36 -2
  29. package/examples/gba/templates/tonc_hello.c +41 -5
  30. package/examples/gbc/templates/default.c +103 -26
  31. package/examples/gbc/templates/platformer.c +25 -4
  32. package/examples/gbc/templates/puzzle.c +32 -2
  33. package/examples/gbc/templates/racing.c +85 -19
  34. package/examples/gbc/templates/shmup.c +34 -1
  35. package/examples/gbc/templates/sports.c +45 -1
  36. package/examples/genesis/templates/puzzle.c +37 -3
  37. package/examples/genesis/templates/racing.c +44 -11
  38. package/examples/genesis/templates/sgdk_hello.c +34 -1
  39. package/examples/genesis/templates/shmup.c +31 -1
  40. package/examples/gg/templates/default.c +56 -18
  41. package/examples/gg/templates/platformer.c +18 -12
  42. package/examples/gg/templates/puzzle.c +38 -7
  43. package/examples/gg/templates/racing.c +51 -5
  44. package/examples/gg/templates/shmup.c +47 -3
  45. package/examples/gg/templates/sports.c +46 -3
  46. package/examples/lynx/templates/default.c +39 -8
  47. package/examples/lynx/templates/puzzle.c +28 -1
  48. package/examples/lynx/templates/racing.c +34 -7
  49. package/examples/lynx/templates/shmup.c +42 -3
  50. package/examples/lynx/templates/sports.c +29 -2
  51. package/examples/msx/platformer/main.c +213 -0
  52. package/examples/msx/puzzle/main.c +250 -0
  53. package/examples/msx/racing/main.c +249 -0
  54. package/examples/msx/shmup/main.c +288 -0
  55. package/examples/msx/sports/main.c +182 -0
  56. package/examples/nes/templates/default.c +67 -19
  57. package/examples/nes/templates/platformer.c +65 -6
  58. package/examples/nes/templates/puzzle.c +67 -6
  59. package/examples/nes/templates/racing.c +45 -13
  60. package/examples/nes/templates/shmup.c +51 -2
  61. package/examples/nes/templates/sports.c +51 -6
  62. package/examples/pce/platformer/main.c +283 -0
  63. package/examples/pce/puzzle/main.c +304 -0
  64. package/examples/pce/racing/main.c +304 -0
  65. package/examples/pce/shmup/main.c +346 -0
  66. package/examples/pce/sports/main.c +254 -0
  67. package/examples/sms/main.c +35 -6
  68. package/examples/sms/templates/puzzle.c +34 -5
  69. package/examples/sms/templates/racing.c +39 -2
  70. package/examples/sms/templates/shmup.c +41 -2
  71. package/examples/sms/templates/sports.c +43 -2
  72. package/examples/snes/templates/default.c +50 -28
  73. package/examples/snes/templates/platformer-data.asm +22 -0
  74. package/examples/snes/templates/platformer.c +16 -1
  75. package/examples/snes/templates/puzzle-data.asm +22 -0
  76. package/examples/snes/templates/puzzle.c +17 -1
  77. package/examples/snes/templates/racing-data.asm +22 -0
  78. package/examples/snes/templates/racing.c +17 -1
  79. package/examples/snes/templates/shmup-data.asm +22 -0
  80. package/examples/snes/templates/shmup.c +20 -1
  81. package/examples/snes/templates/sports-data.asm +22 -0
  82. package/examples/snes/templates/sports.c +16 -1
  83. package/package.json +1 -1
  84. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  85. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  86. package/src/host/LibretroHost.js +122 -1
  87. package/src/host/callbacks.js +9 -1
  88. package/src/host/types.js +15 -8
  89. package/src/http/tool-registry.js +26 -1
  90. package/src/mcp/tools/cart-parts.js +75 -3
  91. package/src/mcp/tools/disasm-rebuild.js +507 -0
  92. package/src/mcp/tools/disasm.js +95 -6
  93. package/src/mcp/tools/frame.js +168 -3
  94. package/src/mcp/tools/lifecycle.js +4 -2
  95. package/src/mcp/tools/project.js +54 -9
  96. package/src/mcp/tools/state.js +201 -14
  97. package/src/mcp/tools/toolchain.js +76 -3
  98. package/src/mcp/tools/watch-memory.js +125 -14
  99. package/src/platforms/c64/MENTAL_MODEL.md +45 -1
  100. package/src/platforms/c64/d64.js +281 -0
  101. package/src/platforms/gb/MENTAL_MODEL.md +10 -0
  102. package/src/platforms/msx/MENTAL_MODEL.md +10 -6
  103. package/src/platforms/nes/MENTAL_MODEL.md +63 -2
  104. package/src/platforms/pce/MENTAL_MODEL.md +9 -4
  105. package/src/platforms/pce/lib/c/pce_video.c +1 -1
  106. package/src/rom-id/identifier.js +15 -0
  107. package/src/toolchains/cc65/ines.js +145 -0
  108. package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
  109. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
  110. package/src/toolchains/common/reassemble.js +10 -2
@@ -0,0 +1,288 @@
1
+ /* ── shmup/main.c — MSX vertical-shooter scaffold (screen 2) ─────────
2
+ *
3
+ * Mirrors the NES/Genesis/SNES/GB/SMS shmup scaffolds, translated to the
4
+ * MSX VDP via the romdev MSX helper lib (msx_hw.h + msx_vdp.c).
5
+ *
6
+ * Player ship (sprite plane 0) + 4 bullet slots (planes 1-4) + 4 enemy
7
+ * slots (planes 5-8), a wave spawner, and AABB collisions. Score is drawn
8
+ * as on-screen tiles ("SCORE 000") along the top row. The whole 32x24
9
+ * screen-2 name table is painted with a banded starfield so the display is
10
+ * clearly space, not a flat backdrop.
11
+ *
12
+ * Controls: joystick LEFT/RIGHT/UP/DOWN moves the ship, trigger A fires.
13
+ * We read joystick PORT 1 (and the keyboard cursor on stick 0 as a
14
+ * fallback), and GTTRIG for the fire button.
15
+ *
16
+ * Cartridge rule: INIT must never return, so main() ends in for(;;).
17
+ *
18
+ * Hardware path (all through the MSX helper lib):
19
+ * - msx_set_screen2() screen 2 (GRAPHIC II), 256x192, display ON
20
+ * - msx_vram_write() upload tile font + sprite patterns to VRAM
21
+ * - msx_set_sprite() position the ship/bullets/enemies each frame
22
+ * - msx_read_joystick() BIOS GTSTCK — 0=center, 1-8 = direction CW
23
+ * - msx_psg_tone/off() fire blip + explosion noise
24
+ * - vsync() one game step per VDP frame (interrupt-free)
25
+ */
26
+ #include "msx_hw.h"
27
+
28
+ /* ── interrupt-free vblank sync (poll VDP status S#0 bit 7) ────────────── */
29
+ __sfr __at 0x99 VDPSTATUS;
30
+ static void vsync(void) {
31
+ (void)VDPSTATUS;
32
+ while (!(VDPSTATUS & 0x80)) {
33
+ }
34
+ }
35
+
36
+ /* fire-button trigger uses the BIOS GTTRIG wrapper (gttrig) from msx_hw.h:
37
+ * gttrig(0)=space/any, gttrig(1)/gttrig(2)=port-A/B triggers. */
38
+
39
+ #define MAX_BULLETS 4
40
+ #define MAX_ENEMIES 4
41
+
42
+ /* ── tile font: SPACE, S C O R E, digits, plus a couple starfield tiles ── */
43
+ #define T_SPACE 0
44
+ #define T_S 1
45
+ #define T_C 2
46
+ #define T_O 3
47
+ #define T_R 4
48
+ #define T_E 5
49
+ #define T_0 6 /* digits 0..9 are consecutive: T_0 + n */
50
+ #define T_DEEP 16 /* solid deep-space band (dark blue) */
51
+ #define T_BAND 17 /* solid lighter space band (medium blue) */
52
+ #define T_STAR1 18 /* deep-space cell with a faint star */
53
+ #define T_STAR2 19 /* lighter-band cell with a bright star */
54
+
55
+ static const uint8_t font[20][8] = {
56
+ /* 0 SPACE */ {0,0,0,0,0,0,0,0},
57
+ /* 1 S */ {0x7C,0xC0,0xC0,0x78,0x0C,0x0C,0xF8,0x00},
58
+ /* 2 C */ {0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00},
59
+ /* 3 O */ {0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00},
60
+ /* 4 R */ {0xFC,0xC6,0xC6,0xFC,0xD8,0xCC,0xC6,0x00},
61
+ /* 5 E */ {0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xFE,0x00},
62
+ /* 6 0 */ {0x7C,0xCE,0xDE,0xF6,0xE6,0xC6,0x7C,0x00},
63
+ /* 7 1 */ {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00},
64
+ /* 8 2 */ {0x7C,0xC6,0x06,0x1C,0x70,0xC0,0xFE,0x00},
65
+ /* 9 3 */ {0x7C,0xC6,0x06,0x3C,0x06,0xC6,0x7C,0x00},
66
+ /* 10 4 */ {0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x00},
67
+ /* 11 5 */ {0xFE,0xC0,0xFC,0x06,0x06,0xC6,0x7C,0x00},
68
+ /* 12 6 */ {0x3C,0x60,0xC0,0xFC,0xC6,0xC6,0x7C,0x00},
69
+ /* 13 7 */ {0xFE,0x06,0x0C,0x18,0x30,0x30,0x30,0x00},
70
+ /* 14 8 */ {0x7C,0xC6,0xC6,0x7C,0xC6,0xC6,0x7C,0x00},
71
+ /* 15 9 */ {0x7C,0xC6,0xC6,0x7E,0x06,0x0C,0x78,0x00},
72
+ /* 16 DEEP (solid fill) */ {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
73
+ /* 17 BAND (solid fill) */ {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},
74
+ /* 18 STAR1 (deep + dot) */ {0xFF,0xFF,0xFF,0xEF,0xFF,0xFF,0xFF,0xFF},
75
+ /* 19 STAR2 (band + dot) */ {0xFF,0xEF,0xEF,0x83,0xEF,0xEF,0xFF,0xFF}
76
+ };
77
+
78
+ /* colour bytes per glyph: high nibble fg, low nibble bg.
79
+ * TMS9918 fixed palette: 1=black, 4=dark blue, 5=med blue, 14=grey, 15=white.
80
+ * The solid space tiles fill entirely with their fg colour. The star tiles
81
+ * use a white star (fg) over the band colour (bg). */
82
+ #define COL_TEXT 0xF1 /* white text on black */
83
+ #define COL_DEEP 0x44 /* solid dark-blue deep space */
84
+ #define COL_BAND 0x55 /* solid medium-blue band */
85
+ #define COL_STAR1 0xF4 /* white star pixel over dark-blue field */
86
+ #define COL_STAR2 0xF5 /* white star over medium-blue band */
87
+
88
+ /* ── sprite patterns (8x8) ─────────────────────────────────────────────── */
89
+ static const uint8_t spr_ship[8] = {0x18,0x3C,0x7E,0x7E,0xFF,0xFF,0xDB,0x81};
90
+ static const uint8_t spr_bullet[8] = {0x18,0x3C,0x3C,0x3C,0x3C,0x3C,0x18,0x00};
91
+ static const uint8_t spr_enemy[8] = {0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81};
92
+
93
+ #define PAT_SHIP 0
94
+ #define PAT_BULLET 1
95
+ #define PAT_ENEMY 2
96
+
97
+ /* TMS9918 fixed sprite palette: 15=white, 10=yellow, 9=red(ish) */
98
+ #define COL_SHIP 15
99
+ #define COL_BULLET 10
100
+ #define COL_ENEMY 9
101
+
102
+ typedef struct { uint8_t x, y, alive; } Obj;
103
+
104
+ static Obj player;
105
+ static Obj bullets[MAX_BULLETS];
106
+ static Obj enemies[MAX_ENEMIES];
107
+ static uint16_t score;
108
+ static uint8_t spawn_timer;
109
+ static uint16_t rng;
110
+ static uint8_t blip;
111
+
112
+ static uint8_t next_rand(void) {
113
+ rng ^= (uint16_t)(rng << 7);
114
+ rng ^= (uint16_t)(rng >> 9);
115
+ rng ^= (uint16_t)(rng << 8);
116
+ return (uint8_t)(rng & 0xFF);
117
+ }
118
+
119
+ /* upload the glyph patterns into ALL THREE screen-2 pattern thirds */
120
+ static void load_font(void) {
121
+ uint8_t third, i;
122
+ uint16_t patbase, colbase;
123
+ for (third = 0; third < 3; third++) {
124
+ patbase = (uint16_t)(VRAM_PATTERN + ((uint16_t)third << 11));
125
+ colbase = (uint16_t)(VRAM_COLOR + ((uint16_t)third << 11));
126
+ for (i = 0; i < 20; i++) {
127
+ uint8_t col = COL_TEXT;
128
+ if (i == T_DEEP) col = COL_DEEP;
129
+ else if (i == T_BAND) col = COL_BAND;
130
+ else if (i == T_STAR1) col = COL_STAR1;
131
+ else if (i == T_STAR2) col = COL_STAR2;
132
+ msx_vram_write((uint16_t)(patbase + ((uint16_t)i << 3)), font[i], 8);
133
+ msx_fill_vram((uint16_t)(colbase + ((uint16_t)i << 3)), 8, col);
134
+ }
135
+ }
136
+ }
137
+
138
+ static void put_tile(uint8_t col, uint8_t row, uint8_t tile) {
139
+ msx_vram_write((uint16_t)(VRAM_NAME + (uint16_t)row * 32 + col), &tile, 1);
140
+ }
141
+
142
+ /* paint a banded starfield across the WHOLE 32x24 name table: alternating
143
+ * 3-row bands of deep/medium blue (so neither colour dominates) with a sparse
144
+ * scattering of white stars. Row 0 stays blank for the SCORE line. */
145
+ static void draw_starfield(void) {
146
+ uint8_t row, col, band, tile, h;
147
+ msx_fill_vram(VRAM_NAME, 32, T_SPACE); /* blank row 0 (HUD) */
148
+ for (row = 1; row < 24; row++) {
149
+ band = (uint8_t)(((row / 3) & 1)); /* alternate every 3 rows */
150
+ for (col = 0; col < 32; col++) {
151
+ h = (uint8_t)((row * 7 + col * 5) & 15);
152
+ if (h == 0) tile = band ? T_STAR2 : T_STAR1; /* a star */
153
+ else tile = band ? T_BAND : T_DEEP; /* solid band */
154
+ put_tile(col, row, tile);
155
+ }
156
+ }
157
+ }
158
+
159
+ static void draw_label(void) {
160
+ put_tile(1, 0, T_S); put_tile(2, 0, T_C); put_tile(3, 0, T_O);
161
+ put_tile(4, 0, T_R); put_tile(5, 0, T_E);
162
+ }
163
+
164
+ static void draw_score(void) {
165
+ uint16_t s = score;
166
+ put_tile(7, 0, (uint8_t)(T_0 + (s / 100) % 10));
167
+ put_tile(8, 0, (uint8_t)(T_0 + (s / 10) % 10));
168
+ put_tile(9, 0, (uint8_t)(T_0 + s % 10));
169
+ }
170
+
171
+ static uint8_t aabb(Obj *a, Obj *b) {
172
+ return a->x < b->x + 8 && a->x + 8 > b->x
173
+ && a->y < b->y + 8 && a->y + 8 > b->y;
174
+ }
175
+
176
+ static void fire(void) {
177
+ uint8_t i;
178
+ for (i = 0; i < MAX_BULLETS; i++) {
179
+ if (!bullets[i].alive) {
180
+ bullets[i].x = player.x;
181
+ bullets[i].y = (uint8_t)(player.y - 8);
182
+ bullets[i].alive = 1;
183
+ msx_psg_tone(0, 0x100, 12);
184
+ blip = 4;
185
+ return;
186
+ }
187
+ }
188
+ }
189
+
190
+ static void spawn(void) {
191
+ uint8_t i;
192
+ for (i = 0; i < MAX_ENEMIES; i++) {
193
+ if (!enemies[i].alive) {
194
+ enemies[i].x = (uint8_t)(8 + (next_rand() % 232));
195
+ enemies[i].y = 16;
196
+ enemies[i].alive = 1;
197
+ return;
198
+ }
199
+ }
200
+ }
201
+
202
+ void main(void) {
203
+ uint8_t i, j, dir, trig, prev_trig;
204
+
205
+ msx_set_screen2();
206
+ msx_clear_sprites();
207
+ load_font();
208
+ draw_starfield();
209
+ draw_label();
210
+
211
+ msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_SHIP * 8), spr_ship, 8);
212
+ msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_BULLET * 8), spr_bullet, 8);
213
+ msx_vram_write((uint16_t)(VRAM_SPRPAT + PAT_ENEMY * 8), spr_enemy, 8);
214
+
215
+ player.x = 120; player.y = 160; player.alive = 1;
216
+ for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
217
+ for (i = 0; i < MAX_ENEMIES; i++) enemies[i].alive = 0;
218
+ score = 0;
219
+ spawn_timer = 0;
220
+ rng = 0xACE1;
221
+ blip = 0;
222
+ prev_trig = 0;
223
+ draw_score();
224
+
225
+ for (;;) {
226
+ vsync();
227
+
228
+ dir = msx_read_joystick(1);
229
+ if (dir == STICK_CENTER) dir = msx_read_joystick(0);
230
+ trig = (uint8_t)(gttrig(1) || gttrig(2));
231
+
232
+ if ((dir == STICK_LEFT || dir == STICK_UL || dir == STICK_DL)
233
+ && player.x > 4) player.x = (uint8_t)(player.x - 2);
234
+ if ((dir == STICK_RIGHT || dir == STICK_UR || dir == STICK_DR)
235
+ && player.x < 248) player.x = (uint8_t)(player.x + 2);
236
+ if ((dir == STICK_UP || dir == STICK_UL || dir == STICK_UR)
237
+ && player.y > 16) player.y = (uint8_t)(player.y - 2);
238
+ if ((dir == STICK_DOWN || dir == STICK_DL || dir == STICK_DR)
239
+ && player.y < 168) player.y = (uint8_t)(player.y + 2);
240
+
241
+ if (trig && !prev_trig) fire();
242
+ prev_trig = trig;
243
+
244
+ /* advance bullets */
245
+ for (i = 0; i < MAX_BULLETS; i++) {
246
+ if (!bullets[i].alive) continue;
247
+ if (bullets[i].y < 18) { bullets[i].alive = 0; continue; }
248
+ bullets[i].y = (uint8_t)(bullets[i].y - 4);
249
+ }
250
+ /* advance enemies */
251
+ for (i = 0; i < MAX_ENEMIES; i++) {
252
+ if (!enemies[i].alive) continue;
253
+ enemies[i].y = (uint8_t)(enemies[i].y + 1);
254
+ if (enemies[i].y >= 184) enemies[i].alive = 0;
255
+ }
256
+ spawn_timer = (uint8_t)(spawn_timer + 1);
257
+ if (spawn_timer >= 28) { spawn_timer = 0; spawn(); }
258
+
259
+ /* bullet vs enemy */
260
+ for (i = 0; i < MAX_BULLETS; i++) {
261
+ if (!bullets[i].alive) continue;
262
+ for (j = 0; j < MAX_ENEMIES; j++) {
263
+ if (!enemies[j].alive) continue;
264
+ if (aabb(&bullets[i], &enemies[j])) {
265
+ bullets[i].alive = 0;
266
+ enemies[j].alive = 0;
267
+ if (score < 999) { score++; draw_score(); }
268
+ msx_psg_tone(1, 0x400, 14);
269
+ blip = 6;
270
+ break;
271
+ }
272
+ }
273
+ }
274
+
275
+ if (blip) { blip--; if (!blip) { msx_psg_off(0); msx_psg_off(1); } }
276
+
277
+ /* push sprites */
278
+ msx_set_sprite(0, player.x, player.y, PAT_SHIP, COL_SHIP);
279
+ for (i = 0; i < MAX_BULLETS; i++)
280
+ msx_set_sprite((uint8_t)(1 + i), bullets[i].x,
281
+ bullets[i].alive ? bullets[i].y : SPRITE_END_Y,
282
+ PAT_BULLET, COL_BULLET);
283
+ for (i = 0; i < MAX_ENEMIES; i++)
284
+ msx_set_sprite((uint8_t)(5 + i), enemies[i].x,
285
+ enemies[i].alive ? enemies[i].y : SPRITE_END_Y,
286
+ PAT_ENEMY, COL_ENEMY);
287
+ }
288
+ }
@@ -0,0 +1,182 @@
1
+ /* ── sports/main.c — MSX two-player Pong scaffold (screen 2) ─────────
2
+ *
3
+ * Mirrors the SMS/GB/etc Pong scaffolds, translated to the MSX VDP via the
4
+ * romdev helper lib (msx_hw.h + msx_vdp.c).
5
+ *
6
+ * The court (green field + white top/bottom + sidelines + dashed centre
7
+ * net) fills the whole 32x24 screen-2 name table. Two paddles (each three
8
+ * stacked 8x8 sprites) and a ball are sprites.
9
+ *
10
+ * Controls:
11
+ * Player 1 — joystick PORT 1 UP/DOWN moves the left paddle.
12
+ * Player 2 — joystick PORT 2 UP/DOWN moves the right paddle; when no
13
+ * second pad is present (stick 2 reads centre) the right paddle falls
14
+ * back to chase-the-ball AI, so the game is playable solo. Plug a
15
+ * second pad in mid-session and player 2 just starts working.
16
+ *
17
+ * Cartridge rule: INIT must never return — main() ends in for(;;).
18
+ */
19
+ #include "msx_hw.h"
20
+
21
+ /* ── interrupt-free vblank sync (poll VDP status S#0 bit 7) ────────────── */
22
+ __sfr __at 0x99 VDPSTATUS;
23
+ static void vsync(void) {
24
+ (void)VDPSTATUS;
25
+ while (!(VDPSTATUS & 0x80)) {
26
+ }
27
+ }
28
+
29
+ #define COURT_TOP 16
30
+ #define COURT_BOT 184
31
+ #define PADDLE_H 24
32
+ #define BALL_SIZE 8
33
+ #define PADDLE_X1 16
34
+ #define PADDLE_X2 232
35
+
36
+ /* tile patterns (8x8) for the court */
37
+ #define T_FIELD 0
38
+ #define T_LINE 1
39
+ #define T_NET 2
40
+
41
+ static const uint8_t TILE_FIELD[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
42
+ static const uint8_t TILE_LINE[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
43
+ static const uint8_t TILE_NET[8] = {0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00};
44
+
45
+ /* colour bytes (hi fg, lo bg). 3=green(dark), 12=green(light), 15=white */
46
+ #define COL_FIELD 0x21 /* dark green field on black */
47
+ #define COL_LINE 0xF1 /* white line on black */
48
+ #define COL_NET 0xF2 /* white net dashes on dark-green field */
49
+
50
+ /* paddle/ball sprite pattern (8x8 solid block) */
51
+ static const uint8_t SPR_BLOCK[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
52
+ #define COL_SPR 15 /* white */
53
+
54
+ static int16_t p1y, p2y, bx, by;
55
+ static int8_t bdx, bdy;
56
+ static uint8_t score_p1, score_p2;
57
+ static uint8_t serve_timer;
58
+ static uint8_t blip;
59
+
60
+ static void load_tiles(void) {
61
+ uint8_t third;
62
+ uint16_t pat, col;
63
+ for (third = 0; third < 3; third++) {
64
+ pat = (uint16_t)(VRAM_PATTERN + ((uint16_t)third << 11));
65
+ col = (uint16_t)(VRAM_COLOR + ((uint16_t)third << 11));
66
+ msx_vram_write((uint16_t)(pat + T_FIELD * 8), TILE_FIELD, 8);
67
+ msx_vram_write((uint16_t)(pat + T_LINE * 8), TILE_LINE, 8);
68
+ msx_vram_write((uint16_t)(pat + T_NET * 8), TILE_NET, 8);
69
+ msx_fill_vram((uint16_t)(col + T_FIELD * 8), 8, COL_FIELD);
70
+ msx_fill_vram((uint16_t)(col + T_LINE * 8), 8, COL_LINE);
71
+ msx_fill_vram((uint16_t)(col + T_NET * 8), 8, COL_NET);
72
+ }
73
+ }
74
+
75
+ static void set_cell(uint8_t row, uint8_t col, uint8_t tile) {
76
+ msx_vram_write((uint16_t)(VRAM_NAME + (uint16_t)row * 32 + col), &tile, 1);
77
+ }
78
+
79
+ static void draw_court(void) {
80
+ uint8_t row, col, t;
81
+ for (row = 0; row < 24; row++) {
82
+ for (col = 0; col < 32; col++) {
83
+ t = T_FIELD;
84
+ if (row <= 1 || row >= 22) t = T_LINE;
85
+ else if (col == 1 || col == 30) t = T_LINE;
86
+ else if (col == 16) t = T_NET;
87
+ set_cell(row, col, t);
88
+ }
89
+ }
90
+ }
91
+
92
+ static void serve_ball(uint8_t to_left) {
93
+ bx = 124;
94
+ by = 90;
95
+ bdx = to_left ? -2 : 2;
96
+ bdy = ((score_p1 + score_p2) & 1) ? -1 : 1;
97
+ serve_timer = 30;
98
+ }
99
+
100
+ static void reset_match(void) {
101
+ p1y = 84; p2y = 84;
102
+ score_p1 = 0; score_p2 = 0;
103
+ serve_ball(0);
104
+ }
105
+
106
+ void main(void) {
107
+ uint8_t i, slot, p1, p2;
108
+ int16_t target;
109
+
110
+ msx_set_screen2();
111
+ msx_clear_sprites();
112
+ load_tiles();
113
+ draw_court();
114
+ msx_vram_write((uint16_t)(VRAM_SPRPAT + 0), SPR_BLOCK, 8);
115
+
116
+ blip = 0;
117
+ reset_match();
118
+
119
+ for (;;) {
120
+ vsync();
121
+
122
+ /* push sprites: left paddle (3 cells), right paddle (3), ball */
123
+ slot = 0;
124
+ for (i = 0; i < PADDLE_H / 8; i++)
125
+ msx_set_sprite(slot++, PADDLE_X1, (uint8_t)(p1y + i * 8), 0, COL_SPR);
126
+ for (i = 0; i < PADDLE_H / 8; i++)
127
+ msx_set_sprite(slot++, PADDLE_X2, (uint8_t)(p2y + i * 8), 0, COL_SPR);
128
+ msx_set_sprite(slot++, (uint8_t)bx, (uint8_t)by, 0, COL_SPR);
129
+
130
+ p1 = msx_read_joystick(1);
131
+ p2 = msx_read_joystick(2);
132
+
133
+ if ((p1 == STICK_UP || p1 == STICK_UL || p1 == STICK_UR)
134
+ && p1y > COURT_TOP) p1y -= 3;
135
+ if ((p1 == STICK_DOWN || p1 == STICK_DL || p1 == STICK_DR)
136
+ && p1y < COURT_BOT - PADDLE_H) p1y += 3;
137
+
138
+ if (p2 != STICK_CENTER) {
139
+ if ((p2 == STICK_UP || p2 == STICK_UL || p2 == STICK_UR)
140
+ && p2y > COURT_TOP) p2y -= 3;
141
+ if ((p2 == STICK_DOWN || p2 == STICK_DL || p2 == STICK_DR)
142
+ && p2y < COURT_BOT - PADDLE_H) p2y += 3;
143
+ } else {
144
+ target = (int16_t)(by - PADDLE_H / 2);
145
+ if (p2y < target && p2y < COURT_BOT - PADDLE_H) p2y += 2;
146
+ else if (p2y > target && p2y > COURT_TOP) p2y -= 2;
147
+ }
148
+
149
+ if (serve_timer > 0) {
150
+ serve_timer--;
151
+ } else {
152
+ bx = (int16_t)(bx + bdx);
153
+ by = (int16_t)(by + bdy);
154
+ if (by < COURT_TOP) { by = COURT_TOP; bdy = (int8_t)(-bdy); msx_psg_tone(1, 0x300, 8); blip = 3; }
155
+ if (by + BALL_SIZE > COURT_BOT) { by = (int16_t)(COURT_BOT - BALL_SIZE); bdy = (int8_t)(-bdy); msx_psg_tone(1, 0x300, 8); blip = 3; }
156
+
157
+ if (bdx < 0
158
+ && bx <= PADDLE_X1 + 8
159
+ && bx + BALL_SIZE >= PADDLE_X1
160
+ && by + BALL_SIZE > p1y
161
+ && by < p1y + PADDLE_H) {
162
+ bdx = (int8_t)(-bdx);
163
+ bx = PADDLE_X1 + 8;
164
+ msx_psg_tone(0, 0x200, 10); blip = 4;
165
+ }
166
+ if (bdx > 0
167
+ && bx + BALL_SIZE >= PADDLE_X2
168
+ && bx <= PADDLE_X2 + 8
169
+ && by + BALL_SIZE > p2y
170
+ && by < p2y + PADDLE_H) {
171
+ bdx = (int8_t)(-bdx);
172
+ bx = (int16_t)(PADDLE_X2 - BALL_SIZE);
173
+ msx_psg_tone(0, 0x200, 10); blip = 4;
174
+ }
175
+
176
+ if (bx < 4) { if (score_p2 < 9) score_p2++; msx_psg_tone(0, 0x500, 14); blip = 8; serve_ball(0); }
177
+ if (bx > 252) { if (score_p1 < 9) score_p1++; msx_psg_tone(0, 0x180, 14); blip = 8; serve_ball(1); }
178
+ }
179
+
180
+ if (blip) { blip--; if (!blip) { msx_psg_off(0); msx_psg_off(1); } }
181
+ }
182
+ }
@@ -1,44 +1,92 @@
1
1
  /* ── default.c — minimal NES starter ────────────────────────────
2
2
  *
3
- * Cycles the backdrop color (palette entry $3F00) through 4 shades.
4
- * Smallest possible "ROM that does something visible" use this as
5
- * the starting point when you're not yet sure what you want to build.
3
+ * A "hello, it works!" screen: a tiled background band + a bouncing
4
+ * sprite, so the very first build shows recognizable content (not a
5
+ * flat color). Smallest starting point when you're not yet sure what
6
+ * you want to build — edit from here.
6
7
  *
7
8
  * For something more game-shaped:
8
9
  * - hello_sprite — sprite + d-pad movement
9
10
  * - tile_engine — 32×30 nametable + rooms + door transitions
11
+ * - scaffold({op:'game', genre:'shmup'|'platformer'|...}) — full genres
10
12
  *
11
- * Both available via createProject({platform:"nes", template:"..."}).
13
+ * NES uses CHR-RAM here, so we upload our tile graphics at boot before
14
+ * turning rendering on (an empty CHR-RAM = a blank screen — the #1 NES
15
+ * "why is it black" footgun; see TROUBLESHOOTING.md).
12
16
  */
13
17
 
14
18
  #include "nes_runtime.h"
15
19
 
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 };
20
+ /* Two 8×8 tiles in 2bpp NES format (plane0 = low bit, plane1 = high bit;
21
+ * 8 bytes each). Tile 1 = a solid filled block (color 1). Tile 2 = a simple
22
+ * face/box so the sprite reads as an object. */
23
+ static const uint8_t tiles[32] = {
24
+ /* tile 1 — solid block: plane0 all-on, plane1 all-off → every pixel color 1 */
25
+ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
26
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
27
+ /* tile 2 — sprite: a filled diamond/box (color 1 outline + fill) */
28
+ 0x18,0x3C,0x7E,0xFF,0xFF,0x7E,0x3C,0x18,
29
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
30
+ };
31
+
32
+ /* 32-byte palette: BG palette 0 gives the block a bright color on a blue
33
+ * backdrop; sprite palette 0 colour 1 = yellow so the sprite pops. */
34
+ static const uint8_t palette[32] = {
35
+ 0x01, 0x30, 0x21, 0x0F, /* BG0: backdrop blue, block white, ... */
36
+ 0x01, 0x30, 0x21, 0x0F,
37
+ 0x01, 0x30, 0x21, 0x0F,
38
+ 0x01, 0x30, 0x21, 0x0F,
39
+ 0x0F, 0x28, 0x16, 0x30, /* SPR0: colour1 yellow */
40
+ 0x0F, 0x28, 0x16, 0x30,
41
+ 0x0F, 0x28, 0x16, 0x30,
42
+ 0x0F, 0x28, 0x16, 0x30,
43
+ };
20
44
 
21
45
  void main(void) {
22
- uint8_t palette[32];
23
- uint8_t i;
24
- uint8_t shade = 0;
46
+ uint8_t x, y;
47
+ uint8_t sx = 120, sy = 96; /* sprite position */
48
+ int8_t dx = 1, dy = 1; /* sprite velocity */
25
49
  uint8_t frame = 0;
26
50
 
27
- for (i = 0; i < 32; i++) palette[i] = bg_colors[0];
28
-
29
51
  ppu_off();
52
+ /* The runtime's default PPUCTRL puts SPRITES at pattern table $0000 and the
53
+ * BACKGROUND at $1000 — two separate 4KB tables. So upload the block tile to
54
+ * the BG table ($1000, slot 1 → $1010) AND the sprite tile to the sprite
55
+ * table ($0000, slot 2 → $0020). (Uploading only to $0000 = invisible BG, the
56
+ * classic "my tiles are in the wrong pattern table" NES gotcha.) */
57
+ chr_ram_upload(0x1010, tiles, 16); /* BG block → BG tile index 1 */
58
+ chr_ram_upload(0x0020, tiles + 16, 16); /* SPR object → sprite tile index 2 */
30
59
  palette_load(palette);
31
60
  oam_clear();
61
+
62
+ /* Populate the nametable DIRECTLY while rendering is off. We use
63
+ * vram_unsafe_set (a raw PPU write) here on purpose: the friendly tile_set()
64
+ * goes through the NMI-flushed queue (24 entries), which would deadlock if you
65
+ * push hundreds of tiles before turning the PPU on. The "write VRAM directly
66
+ * while off, then turn on" idiom is the standard NES way to lay out a screen.
67
+ * Tile index 1 = the solid block. */
68
+ for (x = 0; x < 32; x++) {
69
+ vram_unsafe_set((uint16_t)(0x2000 + 4 * 32 + x), 1); /* top band (row 4) */
70
+ vram_unsafe_set((uint16_t)(0x2000 + 25 * 32 + x), 1); /* bottom band (row 25) */
71
+ }
72
+ for (y = 10; y < 18; y++) {
73
+ for (x = 8; x < 24; x++)
74
+ vram_unsafe_set((uint16_t)(0x2000 + (uint16_t)y * 32 + x), 1); /* center box */
75
+ }
76
+
32
77
  ppu_on_all();
33
78
 
34
79
  for (;;) {
80
+ /* stage the sprite BEFORE waiting (NMI DMAs shadow_oam at vblank). */
81
+ oam_clear();
82
+ oam_spr(sx, sy, 2, 0); /* tile 2, sprite palette 0 */
35
83
  ppu_wait_nmi();
84
+
36
85
  ++frame;
37
- if ((frame & 0x1F) == 0) { /* every 32 frames */
38
- shade = (uint8_t)((shade + 1) & 0x03);
39
- /* Only the backdrop entry ($3F00) needs updating for a flash.
40
- * vram_set queues the write NMI commits it next vblank. */
41
- vram_set(0x3F00, bg_colors[shade]);
42
- }
86
+ /* bounce the sprite around the visible area */
87
+ sx = (uint8_t)(sx + dx);
88
+ sy = (uint8_t)(sy + dy);
89
+ if (sx < 8 || sx > 240) dx = (int8_t)-dx;
90
+ if (sy < 16 || sy > 208) dy = (int8_t)-dy;
43
91
  }
44
92
  }
@@ -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();