romdevtools 0.15.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 (117) hide show
  1. package/AGENTS.md +61 -13
  2. package/CHANGELOG.md +289 -0
  3. package/README.md +1 -1
  4. package/examples/README.md +2 -0
  5. package/examples/atari2600/templates/platformer.asm +460 -0
  6. package/examples/atari2600/templates/racing.asm +463 -0
  7. package/examples/atari2600/templates/shmup.asm +386 -0
  8. package/examples/atari2600/templates/sports.asm +362 -0
  9. package/examples/atari7800/templates/default.c +49 -5
  10. package/examples/atari7800/templates/platformer.c +43 -4
  11. package/examples/atari7800/templates/puzzle.c +39 -4
  12. package/examples/atari7800/templates/racing.c +39 -4
  13. package/examples/atari7800/templates/shmup.c +40 -2
  14. package/examples/atari7800/templates/sports.c +36 -5
  15. package/examples/c64/templates/platformer.c +19 -5
  16. package/examples/c64/templates/puzzle.c +32 -2
  17. package/examples/c64/templates/shmup.c +28 -2
  18. package/examples/c64/templates/sports.c +30 -2
  19. package/examples/gb/templates/default.c +110 -16
  20. package/examples/gb/templates/platformer.c +25 -4
  21. package/examples/gb/templates/puzzle.c +32 -2
  22. package/examples/gb/templates/racing.c +72 -8
  23. package/examples/gb/templates/shmup.c +38 -1
  24. package/examples/gb/templates/sports.c +48 -1
  25. package/examples/gba/templates/gba_hello.c +29 -11
  26. package/examples/gba/templates/puzzle.c +15 -3
  27. package/examples/gba/templates/racing.c +65 -3
  28. package/examples/gba/templates/shmup.c +41 -4
  29. package/examples/gba/templates/sports.c +36 -2
  30. package/examples/gba/templates/tonc_hello.c +41 -5
  31. package/examples/gbc/templates/default.c +103 -26
  32. package/examples/gbc/templates/platformer.c +25 -4
  33. package/examples/gbc/templates/puzzle.c +32 -2
  34. package/examples/gbc/templates/racing.c +85 -19
  35. package/examples/gbc/templates/shmup.c +34 -1
  36. package/examples/gbc/templates/sports.c +45 -1
  37. package/examples/genesis/templates/puzzle.c +37 -3
  38. package/examples/genesis/templates/racing.c +44 -11
  39. package/examples/genesis/templates/sgdk_hello.c +34 -1
  40. package/examples/genesis/templates/shmup.c +31 -1
  41. package/examples/gg/templates/default.c +56 -18
  42. package/examples/gg/templates/platformer.c +18 -12
  43. package/examples/gg/templates/puzzle.c +38 -7
  44. package/examples/gg/templates/racing.c +51 -5
  45. package/examples/gg/templates/shmup.c +47 -3
  46. package/examples/gg/templates/sports.c +46 -3
  47. package/examples/lynx/templates/default.c +39 -8
  48. package/examples/lynx/templates/puzzle.c +28 -1
  49. package/examples/lynx/templates/racing.c +34 -7
  50. package/examples/lynx/templates/shmup.c +42 -3
  51. package/examples/lynx/templates/sports.c +29 -2
  52. package/examples/msx/platformer/main.c +213 -0
  53. package/examples/msx/puzzle/main.c +250 -0
  54. package/examples/msx/racing/main.c +249 -0
  55. package/examples/msx/shmup/main.c +288 -0
  56. package/examples/msx/sports/main.c +182 -0
  57. package/examples/nes/templates/default.c +67 -19
  58. package/examples/nes/templates/platformer.c +65 -6
  59. package/examples/nes/templates/puzzle.c +67 -6
  60. package/examples/nes/templates/racing.c +45 -13
  61. package/examples/nes/templates/shmup.c +51 -2
  62. package/examples/nes/templates/sports.c +51 -6
  63. package/examples/pce/platformer/main.c +283 -0
  64. package/examples/pce/puzzle/main.c +304 -0
  65. package/examples/pce/racing/main.c +304 -0
  66. package/examples/pce/shmup/main.c +346 -0
  67. package/examples/pce/sports/main.c +254 -0
  68. package/examples/sms/main.c +35 -6
  69. package/examples/sms/templates/puzzle.c +34 -5
  70. package/examples/sms/templates/racing.c +39 -2
  71. package/examples/sms/templates/shmup.c +41 -2
  72. package/examples/sms/templates/sports.c +43 -2
  73. package/examples/snes/templates/default.c +50 -28
  74. package/examples/snes/templates/platformer-data.asm +22 -0
  75. package/examples/snes/templates/platformer.c +16 -1
  76. package/examples/snes/templates/puzzle-data.asm +22 -0
  77. package/examples/snes/templates/puzzle.c +17 -1
  78. package/examples/snes/templates/racing-data.asm +22 -0
  79. package/examples/snes/templates/racing.c +17 -1
  80. package/examples/snes/templates/shmup-data.asm +22 -0
  81. package/examples/snes/templates/shmup.c +20 -1
  82. package/examples/snes/templates/sports-data.asm +22 -0
  83. package/examples/snes/templates/sports.c +16 -1
  84. package/package.json +1 -1
  85. package/src/cores/wasm/vice_x64_libretro.js +1 -1
  86. package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
  87. package/src/host/LibretroHost.js +122 -1
  88. package/src/host/callbacks.js +9 -1
  89. package/src/host/types.js +15 -8
  90. package/src/http/skill-doc.js +1 -1
  91. package/src/http/tool-registry.js +27 -2
  92. package/src/mcp/tools/cart-parts.js +75 -3
  93. package/src/mcp/tools/disasm-rebuild.js +507 -0
  94. package/src/mcp/tools/disasm.js +95 -6
  95. package/src/mcp/tools/frame.js +168 -3
  96. package/src/mcp/tools/index.js +4 -4
  97. package/src/mcp/tools/lifecycle.js +4 -2
  98. package/src/mcp/tools/project.js +54 -9
  99. package/src/mcp/tools/state.js +201 -14
  100. package/src/mcp/tools/toolchain.js +89 -4
  101. package/src/mcp/tools/watch-memory.js +125 -14
  102. package/src/platforms/c64/MENTAL_MODEL.md +45 -1
  103. package/src/platforms/c64/d64.js +281 -0
  104. package/src/platforms/gb/MENTAL_MODEL.md +10 -0
  105. package/src/platforms/msx/MENTAL_MODEL.md +10 -6
  106. package/src/platforms/nes/MENTAL_MODEL.md +63 -2
  107. package/src/platforms/pce/MENTAL_MODEL.md +9 -4
  108. package/src/platforms/pce/lib/c/pce_video.c +1 -1
  109. package/src/rom-id/identifier.js +15 -0
  110. package/src/toolchains/cc65/cc65.js +8 -1
  111. package/src/toolchains/cc65/ines.js +145 -0
  112. package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
  113. package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
  114. package/src/toolchains/common/reassemble.js +10 -2
  115. package/src/toolchains/gba-c/gba-c.js +6 -1
  116. package/src/toolchains/genesis-c/genesis-c.js +10 -2
  117. package/src/toolchains/parse-errors.js +67 -5
@@ -0,0 +1,463 @@
1
+ ; ── racing.asm — Atari 2600 RACING genre scaffold (top-down) ──────────
2
+ ;
3
+ ; The 2600 had a deep racing catalogue — Enduro, Indy 500, Night Driver,
4
+ ; Pole Position, Grand Prix. This scaffold is the HONEST 2600 racer: a
5
+ ; TOP-DOWN, vertically-scrolling lane racer (the same idiom Enduro uses),
6
+ ; NOT a pseudo-3D road — projecting a 3D road needs a per-line table the
7
+ ; 4 KB/76-cycle budget can't spare in a starter, and a top-down racer is
8
+ ; a fully period-correct, recognizable racing game on its own.
9
+ ;
10
+ ; TIA object roles:
11
+ ; P0 = the player's car (8-px sprite) near the bottom, steers L/R.
12
+ ; PF = the road: reflected playfield draws the two ROAD EDGES (left
13
+ ; rail mirrors to the right rail) plus a dashed CENTRE LINE that
14
+ ; scrolls upward every frame to convey forward speed.
15
+ ; P1 = an oncoming/lead car you must avoid (8-px sprite) that drifts
16
+ ; down the road; reused each time it passes the bottom, dropped
17
+ ; back to the top in a (deterministic) new lane.
18
+ ; M0 = a second smaller hazard (a cone / debris) in another lane.
19
+ ;
20
+ ; Gameplay: hold LEFT/RIGHT on the joystick to weave between the rails;
21
+ ; survive the descending traffic. Your SPEED (and the score) ramps up
22
+ ; the longer you last — the centre-line dashes scroll faster and the
23
+ ; traffic descends faster. A collision (TIA P0-vs-P1 / P0-vs-M0) flashes
24
+ ; the screen red and resets your speed. Extend it with M1 as a 3rd
25
+ ; hazard, a fuel gauge via a PF bar, or NUSIZ1 for two-abreast traffic.
26
+ ;
27
+ ; TIMING DISCIPLINE (learned the hard way in paddle.asm):
28
+ ; * 262 lines EXACTLY = 3 VSYNC + 37 VBLANK + 192 visible + 30 overscan.
29
+ ; * The 3 object-positioning WSYNCs are COUNTED against the 37 VBLANK
30
+ ; lines (so the VBLANK delay loop is #34, not #37).
31
+ ; * The visible region is a TWO-LINE KERNEL: one scanline of render
32
+ ; work (road edges + centre line + one car test) is ~80+ cycles and
33
+ ; does NOT fit in 76; splitting across two WSYNCs doubles the budget.
34
+ ; 96 passes x 2 lines = 192 visible lines.
35
+
36
+ processor 6502
37
+ org $F000
38
+
39
+ VSYNC = $00
40
+ VBLANK = $01
41
+ WSYNC = $02
42
+ NUSIZ0 = $04
43
+ NUSIZ1 = $05
44
+ COLUP0 = $06
45
+ COLUP1 = $07
46
+ COLUPF = $08
47
+ COLUBK = $09
48
+ CTRLPF = $0A
49
+ PF0 = $0D
50
+ PF1 = $0E
51
+ PF2 = $0F
52
+ RESP0 = $10
53
+ RESP1 = $11
54
+ RESM0 = $12
55
+ GRP0 = $1B
56
+ GRP1 = $1C
57
+ ENAM0 = $1D
58
+ HMP0 = $20
59
+ HMP1 = $21
60
+ HMM0 = $22
61
+ HMOVE = $2A
62
+ HMCLR = $2B
63
+ CXPPMM = $07 ; READ: bit7 = P0/P1 collided
64
+ CXP0FB = $02 ; READ: bit6 = P0/missile-or-ball... we use CXM0P
65
+ CXM0P = $00 ; READ: bit6 = M0/P0 collided
66
+ CXCLR = $2C
67
+ SWCHA = $280
68
+ INPT4 = $0C ; P0 fire (active-low, bit7) — unused here but handy
69
+ ; TIA audio
70
+ AUDC0 = $15
71
+ AUDF0 = $17
72
+ AUDV0 = $19
73
+
74
+ ; ── Zero-page state ───────────────────────────────────────────────────
75
+ P_X = $80 ; player car X (visible column 0..159)
76
+ E1_X = $81 ; enemy car P1 X
77
+ E1_Y = $82 ; enemy car P1 top scanline (counts with the beam)
78
+ E2_X = $83 ; hazard M0 X
79
+ E2_Y = $84 ; hazard M0 top scanline
80
+ SPEED = $85 ; current speed (1..6) — drives scroll + descent
81
+ SCROLL = $86 ; centre-line dash phase (0..7)
82
+ FRAME = $87
83
+ SCORE = $88 ; distance survived / ramps speed
84
+ SFX_LEFT = $89 ; frames remaining on active sfx
85
+ FLASH = $8A ; >0 = crash flash frames remaining
86
+ TMP = $8B
87
+
88
+ START:
89
+ SEI
90
+ CLD
91
+ LDX #$FF
92
+ TXS
93
+ LDA #0
94
+ .clr:
95
+ STA $00,X
96
+ DEX
97
+ BNE .clr
98
+
99
+ ; Initial positions
100
+ LDA #76
101
+ STA P_X ; player mid-road, near bottom
102
+ LDA #50
103
+ STA E1_X
104
+ LDA #170
105
+ STA E1_Y ; enemy car starts up top
106
+ LDA #104
107
+ STA E2_X
108
+ LDA #150
109
+ STA E2_Y
110
+ LDA #1
111
+ STA SPEED
112
+
113
+ ; Colours
114
+ LDA #$00 ; black "tarmac" background
115
+ STA COLUBK
116
+ LDA #$1E ; yellow player car
117
+ STA COLUP0
118
+ LDA #$36 ; pink/red oncoming car (also colours M0 hazard)
119
+ STA COLUP1
120
+ LDA #$0E ; white road markings
121
+ STA COLUPF
122
+
123
+ ; M0 hazard shares P0 colour normally; we want it to read as debris.
124
+ ; Make it 2px wide.
125
+ LDA #%00010000 ; NUSIZ0: missile 2x wide (bits 4-5), P0 single
126
+ STA NUSIZ0
127
+
128
+ ; Playfield: reflected so the left rail mirrors to a right rail, and
129
+ ; SCORE_COLOR priority not needed. CTRLPF bit0 = reflect.
130
+ LDA #%00000001
131
+ STA CTRLPF
132
+
133
+ ; Boot chime — confirms TIA audio is wired (engine "rev").
134
+ LDA #$03
135
+ STA AUDC0
136
+ LDA #$0A
137
+ STA AUDF0
138
+ LDA #$0C
139
+ STA AUDV0
140
+ LDA #18
141
+ STA SFX_LEFT
142
+
143
+ MAIN:
144
+ INC FRAME
145
+
146
+ ; ── VSYNC (3 lines) ──
147
+ LDA #2
148
+ STA VSYNC
149
+ STA WSYNC
150
+ STA WSYNC
151
+ STA WSYNC
152
+ LDA #0
153
+ STA VSYNC
154
+
155
+ ; ── VBLANK (37 lines: 34 here + 3 positioning WSYNCs below) ──
156
+ LDA #2
157
+ STA VBLANK
158
+ LDX #34
159
+ .vb:
160
+ STA WSYNC
161
+ DEX
162
+ BNE .vb
163
+
164
+ ; ── Steering: joystick port A left/right, every 2nd frame ──
165
+ LDA FRAME
166
+ AND #$01
167
+ BNE .skipmove
168
+ LDA SWCHA
169
+ ASL ; bit7 = P0 Right
170
+ BCS .nr
171
+ LDA P_X
172
+ CMP #128
173
+ BCS .nr
174
+ INC P_X
175
+ INC P_X
176
+ .nr:
177
+ ASL ; bit6 = P0 Left
178
+ BCS .nl
179
+ LDA P_X
180
+ CMP #28
181
+ BCC .nl
182
+ DEC P_X
183
+ DEC P_X
184
+ .nl:
185
+ .skipmove:
186
+
187
+ ; ── Crash flash countdown ──
188
+ LDA FLASH
189
+ BEQ .noflash
190
+ DEC FLASH
191
+ .noflash:
192
+
193
+ ; ── Scroll the dashed centre line upward at SPEED px/frame ──
194
+ ; SCROLL is the phase 0..7; subtract SPEED, wrap mod 8.
195
+ LDA SCROLL
196
+ SEC
197
+ SBC SPEED
198
+ AND #$07
199
+ STA SCROLL
200
+
201
+ ; ── Descend traffic at SPEED px/frame (smaller Y = lower on screen,
202
+ ; because Y counts 192->1 with the beam). So descending = DEC Y. ──
203
+ LDA E1_Y
204
+ SEC
205
+ SBC SPEED
206
+ STA E1_Y
207
+ CMP #20 ; passed the bottom?
208
+ BCS .e1ok
209
+ ; recycle to top, new deterministic lane from FRAME
210
+ LDA #186
211
+ STA E1_Y
212
+ LDA FRAME
213
+ AND #$3F
214
+ CLC
215
+ ADC #40
216
+ STA E1_X
217
+ INC SCORE ; survived a car
218
+ ; ramp speed every time SCORE crosses a multiple of 4 (cap at 6)
219
+ LDA SCORE
220
+ AND #$03
221
+ BNE .e1ok
222
+ LDA SPEED
223
+ CMP #6
224
+ BCS .e1ok
225
+ INC SPEED
226
+ ; speed-up "rev" sfx
227
+ LDA #$03
228
+ STA AUDC0
229
+ LDA #$08
230
+ STA AUDF0
231
+ LDA #$0C
232
+ STA AUDV0
233
+ LDA #10
234
+ STA SFX_LEFT
235
+ .e1ok:
236
+
237
+ ; Hazard M0 descends a touch faster (SPEED+1).
238
+ LDA E2_Y
239
+ SEC
240
+ SBC SPEED
241
+ SBC #0 ; (placeholder; SPEED already applied)
242
+ STA E2_Y
243
+ CMP #18
244
+ BCS .e2ok
245
+ LDA #182
246
+ STA E2_Y
247
+ LDA FRAME
248
+ EOR #$5A
249
+ AND #$3F
250
+ CLC
251
+ ADC #36
252
+ STA E2_X
253
+ .e2ok:
254
+
255
+ ; ── Collision check (read TIA collision latches from LAST frame) ──
256
+ ; P0 vs P1 → CXPPMM bit7. M0 vs P0 → CXM0P bit6.
257
+ BIT CXPPMM
258
+ BMI .crash ; bit7 set = P0/P1 overlapped
259
+ BIT CXM0P
260
+ BVS .crash ; bit6 set = M0/P0 overlapped
261
+ JMP .nocrash
262
+ .crash:
263
+ ; Reset speed, flash the screen, recycle the offending traffic up top,
264
+ ; play a crash tone.
265
+ LDA #1
266
+ STA SPEED
267
+ LDA #12
268
+ STA FLASH
269
+ LDA #186
270
+ STA E1_Y
271
+ LDA #182
272
+ STA E2_Y
273
+ LDA #$08 ; noisy crash
274
+ STA AUDC0
275
+ LDA #$1F
276
+ STA AUDF0
277
+ LDA #$0F
278
+ STA AUDV0
279
+ LDA #14
280
+ STA SFX_LEFT
281
+ .nocrash:
282
+ STA CXCLR ; clear collision latches for the next frame
283
+
284
+ ; ── sfx countdown ──
285
+ LDA SFX_LEFT
286
+ BEQ .sfxdone
287
+ DEC SFX_LEFT
288
+ BNE .sfxdone
289
+ LDA #0
290
+ STA AUDV0
291
+ .sfxdone:
292
+
293
+ ; ── Position objects (race-the-beam) — 3 WSYNC-bounded lines ──
294
+ ; Use the divide-by-15 coarse approach (good enough for a starter; the
295
+ ; cars sit on lane centres). HMCLR first so stale HM values don't drift.
296
+ STA WSYNC
297
+ STA HMCLR
298
+ LDX P_X
299
+ LDA #0
300
+ .p0pos:
301
+ CPX #15
302
+ BCC .p0done
303
+ SEC
304
+ SBC #15
305
+ TAX
306
+ JMP .p0pos
307
+ .p0done:
308
+ STA RESP0
309
+ ; P1 (enemy car)
310
+ STA WSYNC
311
+ LDX E1_X
312
+ LDA #0
313
+ .p1pos:
314
+ CPX #15
315
+ BCC .p1done
316
+ SEC
317
+ SBC #15
318
+ TAX
319
+ JMP .p1pos
320
+ .p1done:
321
+ STA RESP1
322
+ ; M0 (hazard)
323
+ STA WSYNC
324
+ LDX E2_X
325
+ LDA #0
326
+ .m0pos:
327
+ CPX #15
328
+ BCC .m0done
329
+ SEC
330
+ SBC #15
331
+ TAX
332
+ JMP .m0pos
333
+ .m0done:
334
+ STA RESM0
335
+ STA HMOVE
336
+
337
+ ; Crash flash: paint background dark-red while FLASH active.
338
+ LDA FLASH
339
+ BEQ .bgblack
340
+ LDA #$42 ; dark red
341
+ STA COLUBK
342
+ JMP .bgdone
343
+ .bgblack:
344
+ LDA #$00
345
+ STA COLUBK
346
+ .bgdone:
347
+
348
+ LDA #0
349
+ STA VBLANK
350
+
351
+ ; ── Visible (192 lines) — TWO-LINE KERNEL ──
352
+ ; Each pass renders TWO scanlines:
353
+ ; line A: road edges (PF) + scrolling centre dash (PF) + player car.
354
+ ; line B: enemy car (P1) + hazard (M0).
355
+ ; Y counts 192 -> 2 in steps of 2. 96 passes = 192 lines.
356
+ LDY #192
357
+ .draw:
358
+ ; ---- line A: road + player car ----
359
+ STA WSYNC
360
+ ; Road rails via PF0 (reflected → left+right rails). The rails are the
361
+ ; OUTER playfield pixels; PF0's high nibble shows on screen pixels
362
+ ; 4..7 (the leftmost visible chunk after the 4-px PF0 gap), mirrored to
363
+ ; the right edge by CTRLPF reflect. A constant rail every line.
364
+ LDA #%00010000 ; one rail bar on each side
365
+ STA PF0
366
+ ; Centre dash via PF2 — a dash that appears on some scanline groups,
367
+ ; phased by SCROLL so it crawls upward. (Y+SCROLL)&8 picks dash on/off.
368
+ TYA
369
+ CLC
370
+ ADC SCROLL
371
+ AND #%00001000
372
+ BEQ .nodash
373
+ LDA #%00011000 ; centre pixels of PF2 (maps near screen middle)
374
+ STA PF2
375
+ JMP .dashdone
376
+ .nodash:
377
+ LDA #0
378
+ STA PF2
379
+ .dashdone:
380
+ ; Player car: 8 rows starting at P_Y region near the bottom (~Y 30..22).
381
+ ; Window: (Y - 22) in [0..7] → index CAR bitmap.
382
+ TYA
383
+ SEC
384
+ SBC #22
385
+ CMP #8
386
+ BCS .pblank
387
+ TAX
388
+ LDA CAR,X
389
+ STA GRP0
390
+ JMP .pdone
391
+ .pblank:
392
+ LDA #0
393
+ STA GRP0
394
+ .pdone:
395
+
396
+ ; ---- line B: enemy car + hazard ----
397
+ STA WSYNC
398
+ ; Enemy car P1: 8 rows starting at E1_Y.
399
+ TYA
400
+ SEC
401
+ SBC E1_Y
402
+ CMP #8
403
+ BCS .eblank
404
+ TAX
405
+ LDA CAR,X
406
+ STA GRP1
407
+ JMP .edone
408
+ .eblank:
409
+ LDA #0
410
+ STA GRP1
411
+ .edone:
412
+ ; Hazard M0: enable for 4 rows around E2_Y.
413
+ TYA
414
+ SEC
415
+ SBC E2_Y
416
+ CMP #4
417
+ BCS .hblank
418
+ LDA #2
419
+ STA ENAM0
420
+ JMP .hdone
421
+ .hblank:
422
+ LDA #0
423
+ STA ENAM0
424
+ .hdone:
425
+
426
+ DEY
427
+ DEY
428
+ BNE .draw
429
+
430
+ ; ── Overscan (30 lines) — clear the playfield so it doesn't bleed ──
431
+ LDA #0
432
+ STA PF0
433
+ STA PF1
434
+ STA PF2
435
+ STA GRP0
436
+ STA GRP1
437
+ STA ENAM0
438
+ LDA #2
439
+ STA VBLANK
440
+ LDX #30
441
+ .os:
442
+ STA WSYNC
443
+ DEX
444
+ BNE .os
445
+
446
+ JMP MAIN
447
+
448
+ ; ── 8-row top-down car silhouette (windshield + body) ──
449
+ CAR:
450
+ .byte %00111100
451
+ .byte %01111110
452
+ .byte %01011010
453
+ .byte %01111110
454
+ .byte %11111111
455
+ .byte %11111111
456
+ .byte %01011010
457
+ .byte %01111110
458
+
459
+ ; ── Vector table ──
460
+ org $FFFA
461
+ .word START
462
+ .word START
463
+ .word START