c64bridge 1.0.3 → 1.0.5

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 (58) hide show
  1. package/README.md +12 -12
  2. package/data/embeddings_basic.json +1 -1
  3. package/data/embeddings_hardware.json +1 -1
  4. package/data/embeddings_mixed.json +1 -1
  5. package/data/embeddings_other.json +1 -1
  6. package/data/graphics/character-set.csv +64 -64
  7. package/dist/.tsbuildinfo +1 -1
  8. package/dist/c64Client.js +68 -61
  9. package/dist/config.js +75 -38
  10. package/dist/device.js +154 -120
  11. package/dist/knowledge.js +2 -1
  12. package/dist/petscii.js +18 -15
  13. package/dist/petsciiArt.js +40 -14
  14. package/dist/rag/indexer.js +2 -1
  15. package/dist/sidwave.js +1 -1
  16. package/dist/sidwaveCompiler.js +3 -9
  17. package/dist/tools/audio.js +34 -8
  18. package/dist/tools/debug.js +9 -9
  19. package/dist/tools/errors.js +12 -1
  20. package/dist/tools/hex.js +9 -0
  21. package/dist/tools/input.js +13 -7
  22. package/dist/tools/memory.js +5 -4
  23. package/dist/tools/meta/artifacts.js +8 -3
  24. package/dist/tools/meta/audio.js +25 -28
  25. package/dist/tools/meta/background.js +6 -1
  26. package/dist/tools/meta/config.js +12 -4
  27. package/dist/tools/meta/configInventory.js +4 -1
  28. package/dist/tools/meta/filesystem.js +37 -10
  29. package/dist/tools/meta/graphics.js +10 -15
  30. package/dist/tools/meta/memory.js +12 -18
  31. package/dist/tools/meta/program.js +44 -16
  32. package/dist/tools/pollValidator.js +30 -2
  33. package/dist/tools/programRunners.js +5 -4
  34. package/dist/tools/registry/batch.js +4 -2
  35. package/dist/tools/registry/config.js +1 -1
  36. package/dist/tools/registry/disk.js +5 -0
  37. package/dist/tools/registry/extract.js +3 -0
  38. package/dist/tools/registry/graphics.js +1 -0
  39. package/dist/tools/registry/program.js +1 -1
  40. package/dist/tools/registry/sound.js +3 -3
  41. package/dist/tools/registry/system.js +3 -3
  42. package/dist/tools/storage.js +2 -2
  43. package/dist/tools/translation/assembler.js +150 -47
  44. package/dist/tools/translation/basicTokenizer.js +39 -9
  45. package/dist/tools/types.js +11 -4
  46. package/dist/vicBitmap.js +16 -0
  47. package/dist/vice/mockServer.js +7 -3
  48. package/dist/vice/viceClient.js +16 -6
  49. package/mcp/schemas/c64_config.schema.json +2 -2
  50. package/mcp/schemas/c64_program.schema.json +10 -18
  51. package/mcp/schemas/c64_sound.schema.json +8 -6
  52. package/mcp/schemas/c64_system.schema.json +9 -7
  53. package/mcp/server.json +1 -1
  54. package/mcp/tools.json +58 -43
  55. package/mcp.json +1 -1
  56. package/package.json +1 -1
  57. package/scripts/mockC64Server.mjs +98 -19
  58. package/server.json +2 -2
@@ -185,8 +185,7 @@
185
185
  "boolean",
186
186
  "null"
187
187
  ],
188
- "description": "Capture debugreg state",
189
- "default": true
188
+ "description": "Capture Ultimate debug registers (C64U/U64 only). Defaults to true on C64U/U64 and false on U2/VICE."
190
189
  },
191
190
  "platforms": {
192
191
  "type": [
@@ -199,13 +198,10 @@
199
198
  "minLength": 1,
200
199
  "enum": [
201
200
  "vice",
202
- "c64u"
201
+ "c64u",
202
+ "u2"
203
203
  ]
204
- },
205
- "default": [
206
- "vice",
207
- "c64u"
208
- ]
204
+ }
209
205
  },
210
206
  "messageTemplate": {
211
207
  "type": [
@@ -221,7 +217,7 @@
221
217
  "boolean",
222
218
  "null"
223
219
  ],
224
- "description": "Capture a framebuffer screenshot and save it as a PNG per backend.",
220
+ "description": "Capture a framebuffer screenshot on C64U/U64 or VICE. U2 skips screenshots by default; explicitly requesting one on U2 fails before execution.",
225
221
  "default": true
226
222
  },
227
223
  "restoreActiveBackend": {
@@ -623,8 +619,7 @@
623
619
  "boolean",
624
620
  "null"
625
621
  ],
626
- "description": "Capture debugreg state",
627
- "default": true
622
+ "description": "Capture Ultimate debug registers (C64U/U64 only). Defaults to true on C64U/U64 and false on U2/VICE."
628
623
  },
629
624
  "op": {
630
625
  "const": "bundle_run"
@@ -667,13 +662,10 @@
667
662
  "minLength": 1,
668
663
  "enum": [
669
664
  "vice",
670
- "c64u"
665
+ "c64u",
666
+ "u2"
671
667
  ]
672
- },
673
- "default": [
674
- "vice",
675
- "c64u"
676
- ]
668
+ }
677
669
  },
678
670
  "messageTemplate": {
679
671
  "type": [
@@ -697,7 +689,7 @@
697
689
  "boolean",
698
690
  "null"
699
691
  ],
700
- "description": "Capture a framebuffer screenshot and save it as a PNG per backend.",
692
+ "description": "Capture a framebuffer screenshot on C64U/U64 or VICE. U2 skips screenshots by default; explicitly requesting one on U2 fails before execution.",
701
693
  "default": true
702
694
  },
703
695
  "outputPath": {
@@ -223,7 +223,7 @@
223
223
  "string",
224
224
  "null"
225
225
  ],
226
- "description": "Playback target format.",
226
+ "description": "Playback target format. PRG works on all backends; SID playback requires C64U/U64 or U2 (SID export with dryRun works everywhere).",
227
227
  "enum": [
228
228
  "prg",
229
229
  "sid"
@@ -249,7 +249,8 @@
249
249
  "minLength": 1,
250
250
  "enum": [
251
251
  "vice",
252
- "c64u"
252
+ "c64u",
253
+ "u2"
253
254
  ]
254
255
  }
255
256
  },
@@ -815,7 +816,7 @@
815
816
  "string",
816
817
  "null"
817
818
  ],
818
- "description": "Playback target format.",
819
+ "description": "Playback target format. PRG works on all backends; SID playback requires C64U/U64 or U2 (SID export with dryRun works everywhere).",
819
820
  "enum": [
820
821
  "prg",
821
822
  "sid"
@@ -876,7 +877,8 @@
876
877
  "minLength": 1,
877
878
  "enum": [
878
879
  "vice",
879
- "c64u"
880
+ "c64u",
881
+ "u2"
880
882
  ]
881
883
  }
882
884
  },
@@ -885,7 +887,7 @@
885
887
  "boolean",
886
888
  "null"
887
889
  ],
888
- "description": "Capture and analyze audio on supported backends after playback starts.",
890
+ "description": "Capture and analyze audio after playback starts: native streaming on C64U/U64, host audio input on U2/VICE (PortAudio required).",
889
891
  "default": true
890
892
  },
891
893
  "analysisDurationSeconds": {
@@ -969,7 +971,7 @@
969
971
  "string",
970
972
  "null"
971
973
  ],
972
- "description": "Playback artifact format.",
974
+ "description": "Playback artifact format. PRG works on all backends; SID attachment playback requires C64U/U64 or U2.",
973
975
  "enum": [
974
976
  "prg",
975
977
  "sid"
@@ -22,8 +22,9 @@
22
22
  },
23
23
  "name": {
24
24
  "type": "string",
25
- "description": "Unique task name",
26
- "minLength": 1
25
+ "description": "Unique task name (letters, digits, spaces, '_', '-', '.'; must not start with '.' or a space).",
26
+ "minLength": 1,
27
+ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_. -]*$"
27
28
  },
28
29
  "operation": {
29
30
  "type": "string",
@@ -91,7 +92,7 @@
91
92
  "required": [
92
93
  "op"
93
94
  ],
94
- "description": "System operations available via the c64_system tool.\n\nOperations:\n- pause: Pause the machine until resumed. Required inputs: none.\n- resume: Resume CPU execution after a pause. Required inputs: none.\n- reset: Issue a soft reset without cutting power. Required inputs: none.\n- reboot: Trigger a firmware reboot to recover from faults. Required inputs: none.\n- poweroff: Request a controlled shutdown via the Ultimate firmware. Required inputs: none.\n- power_cycle: Return the active backend to a fresh state. C64U/U64 Tool Menu verification requires machine:menu_screen (a C64U firmware version that provides it, or U64 3.15+); U2-family reboots through REST. Required inputs: none.\n- menu: Toggle the Ultimate menu button for navigation. Required inputs: none.\n- read_menu_screen: Read the active Ultimate menu's raw character and colour matrix through machine:menu_screen (a C64U firmware version that provides it, or U64/U2 3.15+). Required inputs: none.\n- start_task: Start a named background task that runs on an interval. Required inputs: name, operation. Optional inputs: arguments, intervalMs, maxIterations.\n- stop_task: Stop a specific background task and clear its timer. Required inputs: name.\n- stop_all_tasks: Stop every running background task and persist state. Required inputs: none.\n- list_tasks: List known background tasks with status metadata. Required inputs: none.\n- performance_report: Summarize diagnostics spans and tool latencies from the current or latest MCP session. Required inputs: none. Optional inputs: scope, includeTimeline, maxEntries.",
95
+ "description": "System operations available via the c64_system tool.\n\nOperations:\n- pause: Pause the machine until resumed. Required inputs: none.\n- resume: Resume CPU execution after a pause. Required inputs: none.\n- reset: Issue a soft reset without cutting power. Required inputs: none.\n- reboot: Trigger a firmware reboot to recover from faults. Required inputs: none.\n- poweroff: Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off. Required inputs: none.\n- power_cycle: Return the active backend to a fresh state. C64U/U64 Tool Menu verification requires machine:menu_screen (a C64U firmware version that provides it, or U64 3.15+); U2-family reboots through REST. Required inputs: none.\n- menu: Toggle the Ultimate menu button for navigation. Required inputs: none.\n- read_menu_screen: Read the active Ultimate menu's raw character and colour matrix through machine:menu_screen (a C64U firmware version that provides it, or U64/U2 3.15+). Required inputs: none.\n- start_task: Start a named background task that runs on an interval. Required inputs: name, operation. Optional inputs: arguments, intervalMs, maxIterations.\n- stop_task: Stop a specific background task and clear its timer. Required inputs: name.\n- stop_all_tasks: Stop every running background task and persist state. Required inputs: none.\n- list_tasks: List known background tasks with status metadata. Required inputs: none.\n- performance_report: Summarize diagnostics spans and tool latencies from the current or latest MCP session. Required inputs: none. Optional inputs: scope, includeTimeline, maxEntries.",
95
96
  "additionalProperties": false,
96
97
  "x-c64bridge-operations": [
97
98
  {
@@ -172,12 +173,12 @@
172
173
  },
173
174
  {
174
175
  "op": "poweroff",
175
- "description": "Request a controlled shutdown via the Ultimate firmware.",
176
+ "description": "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off.",
176
177
  "required": [],
177
178
  "optional": [],
178
179
  "inputSchema": {
179
180
  "type": "object",
180
- "description": "Request a controlled shutdown via the Ultimate firmware.",
181
+ "description": "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off.",
181
182
  "properties": {
182
183
  "op": {
183
184
  "const": "poweroff"
@@ -264,8 +265,9 @@
264
265
  "properties": {
265
266
  "name": {
266
267
  "type": "string",
267
- "description": "Unique task name",
268
- "minLength": 1
268
+ "description": "Unique task name (letters, digits, spaces, '_', '-', '.'; must not start with '.' or a space).",
269
+ "minLength": 1,
270
+ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_. -]*$"
269
271
  },
270
272
  "operation": {
271
273
  "type": "string",
package/mcp/server.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c64bridge",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Stdio-based Model Context Protocol (MCP) server that bridges Commodore 64 Ultimate hardware with AI agents.",
5
5
  "transports": [
6
6
  "stdio"
package/mcp/tools.json CHANGED
@@ -191,8 +191,7 @@
191
191
  "boolean",
192
192
  "null"
193
193
  ],
194
- "description": "Capture debugreg state",
195
- "default": true
194
+ "description": "Capture Ultimate debug registers (C64U/U64 only). Defaults to true on C64U/U64 and false on U2/VICE."
196
195
  },
197
196
  "platforms": {
198
197
  "type": [
@@ -205,13 +204,10 @@
205
204
  "minLength": 1,
206
205
  "enum": [
207
206
  "vice",
208
- "c64u"
207
+ "c64u",
208
+ "u2"
209
209
  ]
210
- },
211
- "default": [
212
- "vice",
213
- "c64u"
214
- ]
210
+ }
215
211
  },
216
212
  "messageTemplate": {
217
213
  "type": [
@@ -227,7 +223,7 @@
227
223
  "boolean",
228
224
  "null"
229
225
  ],
230
- "description": "Capture a framebuffer screenshot and save it as a PNG per backend.",
226
+ "description": "Capture a framebuffer screenshot on C64U/U64 or VICE. U2 skips screenshots by default; explicitly requesting one on U2 fails before execution.",
231
227
  "default": true
232
228
  },
233
229
  "restoreActiveBackend": {
@@ -629,8 +625,7 @@
629
625
  "boolean",
630
626
  "null"
631
627
  ],
632
- "description": "Capture debugreg state",
633
- "default": true
628
+ "description": "Capture Ultimate debug registers (C64U/U64 only). Defaults to true on C64U/U64 and false on U2/VICE."
634
629
  },
635
630
  "op": {
636
631
  "const": "bundle_run"
@@ -673,13 +668,10 @@
673
668
  "minLength": 1,
674
669
  "enum": [
675
670
  "vice",
676
- "c64u"
671
+ "c64u",
672
+ "u2"
677
673
  ]
678
- },
679
- "default": [
680
- "vice",
681
- "c64u"
682
- ]
674
+ }
683
675
  },
684
676
  "messageTemplate": {
685
677
  "type": [
@@ -703,7 +695,7 @@
703
695
  "boolean",
704
696
  "null"
705
697
  ],
706
- "description": "Capture a framebuffer screenshot and save it as a PNG per backend.",
698
+ "description": "Capture a framebuffer screenshot on C64U/U64 or VICE. U2 skips screenshots by default; explicitly requesting one on U2 fails before execution.",
707
699
  "default": true
708
700
  },
709
701
  "outputPath": {
@@ -815,10 +807,6 @@
815
807
  "run_crt": [
816
808
  "c64u",
817
809
  "u2"
818
- ],
819
- "bundle_run": [
820
- "c64u",
821
- "u2"
822
810
  ]
823
811
  }
824
812
  }
@@ -1829,7 +1817,7 @@
1829
1817
  "string",
1830
1818
  "null"
1831
1819
  ],
1832
- "description": "Playback target format.",
1820
+ "description": "Playback target format. PRG works on all backends; SID playback requires C64U/U64 or U2 (SID export with dryRun works everywhere).",
1833
1821
  "enum": [
1834
1822
  "prg",
1835
1823
  "sid"
@@ -1855,7 +1843,8 @@
1855
1843
  "minLength": 1,
1856
1844
  "enum": [
1857
1845
  "vice",
1858
- "c64u"
1846
+ "c64u",
1847
+ "u2"
1859
1848
  ]
1860
1849
  }
1861
1850
  },
@@ -2421,7 +2410,7 @@
2421
2410
  "string",
2422
2411
  "null"
2423
2412
  ],
2424
- "description": "Playback target format.",
2413
+ "description": "Playback target format. PRG works on all backends; SID playback requires C64U/U64 or U2 (SID export with dryRun works everywhere).",
2425
2414
  "enum": [
2426
2415
  "prg",
2427
2416
  "sid"
@@ -2482,7 +2471,8 @@
2482
2471
  "minLength": 1,
2483
2472
  "enum": [
2484
2473
  "vice",
2485
- "c64u"
2474
+ "c64u",
2475
+ "u2"
2486
2476
  ]
2487
2477
  }
2488
2478
  },
@@ -2491,7 +2481,7 @@
2491
2481
  "boolean",
2492
2482
  "null"
2493
2483
  ],
2494
- "description": "Capture and analyze audio on supported backends after playback starts.",
2484
+ "description": "Capture and analyze audio after playback starts: native streaming on C64U/U64, host audio input on U2/VICE (PortAudio required).",
2495
2485
  "default": true
2496
2486
  },
2497
2487
  "analysisDurationSeconds": {
@@ -2575,7 +2565,7 @@
2575
2565
  "string",
2576
2566
  "null"
2577
2567
  ],
2578
- "description": "Playback artifact format.",
2568
+ "description": "Playback artifact format. PRG works on all backends; SID attachment playback requires C64U/U64 or U2.",
2579
2569
  "enum": [
2580
2570
  "prg",
2581
2571
  "sid"
@@ -2838,13 +2828,18 @@
2838
2828
  ],
2839
2829
  "pipeline": [
2840
2830
  "c64u",
2841
- "u2"
2831
+ "u2",
2832
+ "vice"
2842
2833
  ],
2843
2834
  "analyze": [
2844
- "c64u"
2835
+ "c64u",
2836
+ "u2",
2837
+ "vice"
2845
2838
  ],
2846
2839
  "record_analyze": [
2847
- "c64u"
2840
+ "c64u",
2841
+ "u2",
2842
+ "vice"
2848
2843
  ]
2849
2844
  }
2850
2845
  }
@@ -2877,8 +2872,9 @@
2877
2872
  },
2878
2873
  "name": {
2879
2874
  "type": "string",
2880
- "description": "Unique task name",
2881
- "minLength": 1
2875
+ "description": "Unique task name (letters, digits, spaces, '_', '-', '.'; must not start with '.' or a space).",
2876
+ "minLength": 1,
2877
+ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_. -]*$"
2882
2878
  },
2883
2879
  "operation": {
2884
2880
  "type": "string",
@@ -2946,7 +2942,7 @@
2946
2942
  "required": [
2947
2943
  "op"
2948
2944
  ],
2949
- "description": "System operations available via the c64_system tool.\n\nOperations:\n- pause: Pause the machine until resumed. Required inputs: none.\n- resume: Resume CPU execution after a pause. Required inputs: none.\n- reset: Issue a soft reset without cutting power. Required inputs: none.\n- reboot: Trigger a firmware reboot to recover from faults. Required inputs: none.\n- poweroff: Request a controlled shutdown via the Ultimate firmware. Required inputs: none.\n- power_cycle: Return the active backend to a fresh state. C64U/U64 Tool Menu verification requires machine:menu_screen (a C64U firmware version that provides it, or U64 3.15+); U2-family reboots through REST. Required inputs: none.\n- menu: Toggle the Ultimate menu button for navigation. Required inputs: none.\n- read_menu_screen: Read the active Ultimate menu's raw character and colour matrix through machine:menu_screen (a C64U firmware version that provides it, or U64/U2 3.15+). Required inputs: none.\n- start_task: Start a named background task that runs on an interval. Required inputs: name, operation. Optional inputs: arguments, intervalMs, maxIterations.\n- stop_task: Stop a specific background task and clear its timer. Required inputs: name.\n- stop_all_tasks: Stop every running background task and persist state. Required inputs: none.\n- list_tasks: List known background tasks with status metadata. Required inputs: none.\n- performance_report: Summarize diagnostics spans and tool latencies from the current or latest MCP session. Required inputs: none. Optional inputs: scope, includeTimeline, maxEntries.",
2945
+ "description": "System operations available via the c64_system tool.\n\nOperations:\n- pause: Pause the machine until resumed. Required inputs: none.\n- resume: Resume CPU execution after a pause. Required inputs: none.\n- reset: Issue a soft reset without cutting power. Required inputs: none.\n- reboot: Trigger a firmware reboot to recover from faults. Required inputs: none.\n- poweroff: Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off. Required inputs: none.\n- power_cycle: Return the active backend to a fresh state. C64U/U64 Tool Menu verification requires machine:menu_screen (a C64U firmware version that provides it, or U64 3.15+); U2-family reboots through REST. Required inputs: none.\n- menu: Toggle the Ultimate menu button for navigation. Required inputs: none.\n- read_menu_screen: Read the active Ultimate menu's raw character and colour matrix through machine:menu_screen (a C64U firmware version that provides it, or U64/U2 3.15+). Required inputs: none.\n- start_task: Start a named background task that runs on an interval. Required inputs: name, operation. Optional inputs: arguments, intervalMs, maxIterations.\n- stop_task: Stop a specific background task and clear its timer. Required inputs: name.\n- stop_all_tasks: Stop every running background task and persist state. Required inputs: none.\n- list_tasks: List known background tasks with status metadata. Required inputs: none.\n- performance_report: Summarize diagnostics spans and tool latencies from the current or latest MCP session. Required inputs: none. Optional inputs: scope, includeTimeline, maxEntries.",
2950
2946
  "additionalProperties": false,
2951
2947
  "x-c64bridge-operations": [
2952
2948
  {
@@ -3027,12 +3023,12 @@
3027
3023
  },
3028
3024
  {
3029
3025
  "op": "poweroff",
3030
- "description": "Request a controlled shutdown via the Ultimate firmware.",
3026
+ "description": "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off.",
3031
3027
  "required": [],
3032
3028
  "optional": [],
3033
3029
  "inputSchema": {
3034
3030
  "type": "object",
3035
- "description": "Request a controlled shutdown via the Ultimate firmware.",
3031
+ "description": "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off.",
3036
3032
  "properties": {
3037
3033
  "op": {
3038
3034
  "const": "poweroff"
@@ -3119,8 +3115,9 @@
3119
3115
  "properties": {
3120
3116
  "name": {
3121
3117
  "type": "string",
3122
- "description": "Unique task name",
3123
- "minLength": 1
3118
+ "description": "Unique task name (letters, digits, spaces, '_', '-', '.'; must not start with '.' or a space).",
3119
+ "minLength": 1,
3120
+ "pattern": "^[A-Za-z0-9_][A-Za-z0-9_. -]*$"
3124
3121
  },
3125
3122
  "operation": {
3126
3123
  "type": "string",
@@ -3338,6 +3335,10 @@
3338
3335
  "vice"
3339
3336
  ],
3340
3337
  "operationPlatforms": {
3338
+ "poweroff": [
3339
+ "c64u",
3340
+ "vice"
3341
+ ],
3341
3342
  "pause": [
3342
3343
  "c64u",
3343
3344
  "u2"
@@ -4183,7 +4184,13 @@
4183
4184
  "c64u",
4184
4185
  "u2",
4185
4186
  "vice"
4186
- ]
4187
+ ],
4188
+ "operationPlatforms": {
4189
+ "capture_frame": [
4190
+ "c64u",
4191
+ "vice"
4192
+ ]
4193
+ }
4187
4194
  }
4188
4195
  },
4189
4196
  {
@@ -5732,7 +5739,7 @@
5732
5739
  "boolean",
5733
5740
  "null"
5734
5741
  ],
5735
- "description": "When true, save the restored configuration to flash immediately.",
5742
+ "description": "When true, save the restored configuration to flash immediately (C64U/U64 and U2 only).",
5736
5743
  "default": false
5737
5744
  },
5738
5745
  "root": {
@@ -6131,7 +6138,7 @@
6131
6138
  "boolean",
6132
6139
  "null"
6133
6140
  ],
6134
- "description": "When true, save the restored configuration to flash immediately.",
6141
+ "description": "When true, save the restored configuration to flash immediately (C64U/U64 and U2 only).",
6135
6142
  "default": false
6136
6143
  },
6137
6144
  "op": {
@@ -6954,8 +6961,16 @@
6954
6961
  "Summarise output file paths or sample counts so users can inspect artifacts quickly."
6955
6962
  ],
6956
6963
  "platforms": [
6957
- "c64u"
6958
- ]
6964
+ "c64u",
6965
+ "u2",
6966
+ "vice"
6967
+ ],
6968
+ "operationPlatforms": {
6969
+ "fs_stats": [
6970
+ "c64u",
6971
+ "u2"
6972
+ ]
6973
+ }
6959
6974
  }
6960
6975
  },
6961
6976
  {
package/mcp.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c64bridge",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Stdio-based Model Context Protocol (MCP) server that bridges Commodore 64 Ultimate hardware with AI agents.",
5
5
  "entry": "dist/index.js",
6
6
  "type": "stdio",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c64bridge",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "mcpName": "io.github.chrisgleissner/c64bridge",
5
5
  "description": "C64 Bridge MCP server for Commodore 64 Ultimate hardware and VICE emulator control",
6
6
  "repository": {
@@ -250,6 +250,14 @@ function cancelHeartbeatOnWrite(state, address, length) {
250
250
  }
251
251
 
252
252
  function seedReadyPrompt(state) {
253
+ // After a reset the KERNAL clears screen RAM to spaces and BASIC sets up an
254
+ // empty program: TXTTAB=$0801, VARTAB/ARYTAB/STREND=$0803, FRETOP/MEMSIZ=$A000
255
+ // (as read from an Ultimate 64 after machine:reset).
256
+ state.memory.fill(toScreenCode(" "), 0x0400, 0x0400 + 1000);
257
+ state.memory.set([0x01, 0x08, 0x03, 0x08, 0x03, 0x08, 0x03, 0x08, 0x00, 0xa0], 0x2b);
258
+ state.memory.set([0x00, 0xa0], 0x37);
259
+ state.memory.set([0x00, 0x00], 0x0801);
260
+
253
261
  const text = "READY.";
254
262
  const buffer = new Uint8Array(text.length + 1);
255
263
  for (let i = 0; i < text.length; i += 1) {
@@ -259,6 +267,26 @@ function seedReadyPrompt(state) {
259
267
  state.memory.set(buffer, 0x0400);
260
268
  }
261
269
 
270
+ /**
271
+ * BASIC LINKPRG ($A533): walk the line chain from $0801 and rewrite each link
272
+ * pointer to the byte after the line's terminating zero. Stops at a zero link
273
+ * high byte, as the ROM does; the step limit only guards the mock.
274
+ */
275
+ function relinkBasicProgram(memory) {
276
+ let line = 0x0801;
277
+ for (let steps = 0; steps < 0x10000 && line < 0xfffb; steps += 1) {
278
+ if (memory[line + 1] === 0) return;
279
+ let y = 4;
280
+ do {
281
+ y += 1;
282
+ } while (line + y < 0xffff && memory[line + y] !== 0);
283
+ const next = line + y + 1;
284
+ memory[line] = next & 0xff;
285
+ memory[line + 1] = (next >> 8) & 0xff;
286
+ line = next;
287
+ }
288
+ }
289
+
262
290
  export async function startMockC64Server(options = {}) {
263
291
  const state = createInitialState();
264
292
  const streamRuntime = createStreamRuntime();
@@ -269,9 +297,14 @@ export async function startMockC64Server(options = {}) {
269
297
  // seed memory with READY prompt at $0400 using screen codes
270
298
  seedReadyPrompt(state);
271
299
 
300
+ // Firmware drive ids (route_drives.h DriveToSubsys): a and b are the
301
+ // emulated drives, softiec the IEC file system; anything else is rejected.
302
+ const FIRMWARE_DRIVE_BUS_IDS = { a: 8, b: 9, softiec: 11 };
303
+
272
304
  function ensureDriveState(id) {
273
305
  if (!state.drives[id]) {
274
306
  state.drives[id] = {
307
+ busId: FIRMWARE_DRIVE_BUS_IDS[id] ?? null,
275
308
  mountedImage: null,
276
309
  mode: "1541",
277
310
  power: "off",
@@ -282,14 +315,20 @@ export async function startMockC64Server(options = {}) {
282
315
  return state.drives[id];
283
316
  }
284
317
 
285
- ensureDriveState("drive8");
318
+ function seedDrives() {
319
+ ensureDriveState("a");
320
+ ensureDriveState("b");
321
+ ensureDriveState("softiec");
322
+ }
323
+
324
+ seedDrives();
286
325
 
287
326
  function resetState() {
288
327
  stopAllStreams();
289
328
  const fresh = createInitialState();
290
329
  Object.assign(state, fresh);
291
330
  seedReadyPrompt(state);
292
- ensureDriveState("drive8");
331
+ seedDrives();
293
332
  }
294
333
 
295
334
  function stopAllStreams() {
@@ -401,18 +440,32 @@ export async function startMockC64Server(options = {}) {
401
440
  }
402
441
 
403
442
  if (method === "GET" && url === "/v1/drives") {
404
- const drives = {};
405
- for (const [driveId, driveState] of Object.entries(state.drives)) {
406
- drives[driveId] = {
407
- enabled: driveState.power !== "off",
408
- power: driveState.power,
409
- mode: driveState.mode,
410
- // Mirror the real firmware's DriveInfo shape (image_path is a
411
- // plain string), not the internal mount-params object.
412
- image_path: driveState.mountedImage?.image ?? null,
413
- type: driveState.mountedImage?.type ?? driveState.mode ?? null,
443
+ // Firmware shape (route_drives.cc drive_info, IecInterface::info): a list
444
+ // of single-key objects. Power is `enabled`; the mounted image is split
445
+ // into its directory (image_path) and file name (image_file). The soft
446
+ // IEC drive is listed as "IEC Drive" although its route id is softiec,
447
+ // and the printer is listed but is not a drive.
448
+ const splitImage = (image) => {
449
+ if (!image) return { image_file: "", image_path: "" };
450
+ const slash = image.lastIndexOf("/");
451
+ return { image_file: image.slice(slash + 1), image_path: slash > 0 ? image.slice(0, slash) : "/" };
452
+ };
453
+ const drives = ["a", "b"].map((driveId) => {
454
+ const driveState = state.drives[driveId];
455
+ return {
456
+ [driveId]: {
457
+ enabled: driveState.power !== "off",
458
+ bus_id: driveState.busId,
459
+ type: driveState.mode,
460
+ rom: `${driveState.mode}.rom`,
461
+ ...splitImage(driveState.mountedImage?.image),
462
+ },
414
463
  };
415
- }
464
+ });
465
+ drives.push(
466
+ { "IEC Drive": { bus_id: state.drives.softiec.busId, enabled: state.drives.softiec.power !== "off", type: "DOS emulation", last_error: "00, OK, 00, 00", partitions: [{ id: 1, path: "/Temp/" }] } },
467
+ { "Printer Emulation": { bus_id: 4, enabled: false } },
468
+ );
416
469
  sendJson(res, { drives });
417
470
  return;
418
471
  }
@@ -526,11 +579,20 @@ export async function startMockC64Server(options = {}) {
526
579
  state.lastLoadedPrg = prg;
527
580
  state.loadCount += 1;
528
581
 
529
- // Mirror real firmware: place the PRG body at its embedded load
530
- // address without starting it (no RUN, no BASIC pointer changes).
582
+ // Mirror Ultimate 64 firmware 3.15: the PRG body is placed at its
583
+ // embedded load address without starting it, and, as after a BASIC
584
+ // LOAD, VARTAB/ARYTAB/STREND are set to the end address and the BASIC
585
+ // program at $0801 is relinked (which rewrites raw code at $0801).
531
586
  if (prg.length >= 2) {
532
587
  const loadAddress = prg.readUInt16LE(0);
533
- state.memory.set(prg.subarray(2), loadAddress);
588
+ const body = prg.subarray(2);
589
+ state.memory.set(body.subarray(0, 0x10000 - loadAddress), loadAddress);
590
+ const endAddress = Math.min(0xffff, loadAddress + body.length);
591
+ for (const pointer of [0x2d, 0x2f, 0x31]) {
592
+ state.memory[pointer] = endAddress & 0xff;
593
+ state.memory[pointer + 1] = endAddress >> 8;
594
+ }
595
+ relinkBasicProgram(state.memory);
534
596
  }
535
597
 
536
598
  sendJson(res, { result: "ok", bytes: prg.length });
@@ -564,9 +626,22 @@ export async function startMockC64Server(options = {}) {
564
626
  }
565
627
 
566
628
  const attachment = Buffer.concat(chunks);
629
+ // Like the firmware, a multipart upload is a list of files in part
630
+ // order: the SID first, optional song lengths second. A text field
631
+ // (for example a JSON-serialised Buffer) is not a SID upload.
632
+ const contentType = String(req.headers["content-type"] ?? "");
633
+ const files = contentType.startsWith("multipart/form-data")
634
+ ? [...(await new Response(attachment, { headers: { "content-type": contentType } }).formData()).values()]
635
+ : null;
636
+ if (files && (files.length === 0 || files.some((part) => typeof part === "string"))) {
637
+ sendJson(res, { errors: ["Upload of file failed."] }, 400);
638
+ return;
639
+ }
640
+ const sid = files ? Buffer.from(await files[0].arrayBuffer()) : attachment;
641
+ const songlengths = files?.[1] ? Buffer.from(await files[1].arrayBuffer()) : null;
567
642
  state.sidAttachmentCount += 1;
568
- state.lastSidAttachment = { songnr, bytes: attachment.length };
569
- sendJson(res, { result: "sidplay_attachment", bytes: attachment.length, songnr });
643
+ state.lastSidAttachment = { songnr, bytes: sid.length, sid, songlengths };
644
+ sendJson(res, { result: "sidplay_attachment", bytes: sid.length, songnr });
570
645
  return;
571
646
  }
572
647
  }
@@ -816,8 +891,12 @@ export async function startMockC64Server(options = {}) {
816
891
  if (routeUrl) {
817
892
  const match = /^\/v1\/drives\/([^:]+):(mount|remove|reset|on|off|load_rom|set_mode)$/.exec(routeUrl.pathname);
818
893
  if (match) {
819
- const driveId = decodeURIComponent(match[1]);
894
+ const driveId = decodeURIComponent(match[1]).toLowerCase();
820
895
  const action = match[2];
896
+ if (!Object.hasOwn(FIRMWARE_DRIVE_BUS_IDS, driveId)) {
897
+ sendJson(res, { errors: [`Invalid Drive '${decodeURIComponent(match[1])}'`] }, 400);
898
+ return;
899
+ }
821
900
  const driveState = ensureDriveState(driveId);
822
901
 
823
902
  const respond = (payload) => {