c64bridge 0.7.4 → 0.8.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 (107) hide show
  1. package/.github/prompts/README.md +14 -4
  2. package/.github/prompts/assembly-routine-session.prompt.md +9 -9
  3. package/.github/prompts/basic-program-session.prompt.md +11 -9
  4. package/.github/prompts/drive-management-session.prompt.md +9 -9
  5. package/.github/prompts/hello-world-session.prompt.md +17 -0
  6. package/.github/prompts/petscii-art-session.prompt.md +9 -9
  7. package/.github/prompts/play-classic-game.prompt.md +9 -9
  8. package/.github/prompts/pr-converge.prompt.md +13 -0
  9. package/.github/prompts/sid-music-session.prompt.md +9 -9
  10. package/AGENTS.md +59 -60
  11. package/README.md +550 -191
  12. package/data/assembly/assembly-spec.md +290 -1
  13. package/data/context/bootstrap.md +26 -2
  14. package/data/context/fast-paths.md +44 -0
  15. package/data/graphics/petscii-style-guide.md +6 -2
  16. package/data/graphics/sprite-charset-best-practices.md +5 -5
  17. package/dist/.tsbuildinfo +1 -1
  18. package/dist/c64Client.js +990 -26
  19. package/dist/config.js +5 -1
  20. package/dist/device.js +680 -57
  21. package/dist/diagnostics.js +371 -0
  22. package/dist/mcp/metadata.js +39 -0
  23. package/dist/mcp-server.js +170 -20
  24. package/dist/platform.js +10 -2
  25. package/dist/prompts/registry.js +166 -309
  26. package/dist/rag/init.js +37 -1
  27. package/dist/rag/knowledgeIndex.js +18 -6
  28. package/dist/rag/rateLimiter.js +2 -2
  29. package/dist/streamCapture.js +135 -0
  30. package/dist/tools/audio.js +3 -1
  31. package/dist/tools/debug.js +470 -0
  32. package/dist/tools/developer.js +10 -4
  33. package/dist/tools/graphics.js +183 -17
  34. package/dist/tools/machineControl.js +11 -5
  35. package/dist/tools/memory.js +19 -10
  36. package/dist/tools/meta/audio.js +240 -1
  37. package/dist/tools/meta/config.js +20 -28
  38. package/dist/tools/meta/configInventory.js +104 -0
  39. package/dist/tools/meta/diagnostics.js +45 -2
  40. package/dist/tools/meta/graphics.js +14 -6
  41. package/dist/tools/meta/index.js +1 -0
  42. package/dist/tools/meta/memory.js +20 -6
  43. package/dist/tools/meta/program.js +473 -0
  44. package/dist/tools/pollValidator.js +4 -2
  45. package/dist/tools/programRunners.js +8 -4
  46. package/dist/tools/registry/config.js +17 -0
  47. package/dist/tools/registry/disk.js +7 -0
  48. package/dist/tools/registry/drive.js +3 -0
  49. package/dist/tools/registry/graphics.js +111 -27
  50. package/dist/tools/registry/index.js +6 -0
  51. package/dist/tools/registry/memory.js +1 -0
  52. package/dist/tools/registry/platform.js +121 -0
  53. package/dist/tools/registry/program.js +26 -8
  54. package/dist/tools/registry/sound.js +96 -0
  55. package/dist/tools/registry/system.js +15 -2
  56. package/dist/tools/storage.js +1 -0
  57. package/dist/tools/streaming.js +1 -0
  58. package/dist/tools/types.js +38 -4
  59. package/dist/tools/vice.js +160 -0
  60. package/dist/vicBitmap.js +317 -0
  61. package/dist/vice/mockServer.js +439 -0
  62. package/dist/vice/process.js +310 -0
  63. package/dist/vice/readiness.js +18 -2
  64. package/dist/vice/viceClient.js +382 -22
  65. package/doc/developer.md +62 -24
  66. package/doc/img/backend-switch/hello-c64u.png +0 -0
  67. package/doc/img/backend-switch/hello-vice-and-c64u.png +0 -0
  68. package/doc/img/backend-switch/hello-vice.png +0 -0
  69. package/doc/img/backend-switch/metadata.json +50 -0
  70. package/doc/plans/vice-backend-switch/plan.md +486 -0
  71. package/doc/plans/vice-backend-switch-2/plan.md +48 -0
  72. package/doc/plans/vice-support/notes.md +1 -0
  73. package/doc/plans/vice-support/plan.md +5 -0
  74. package/doc/plans/vice-support/rollout.md +23 -17
  75. package/doc/reviews/vice-support-review.md +185 -0
  76. package/doc/troubleshooting-mcp.md +35 -6
  77. package/doc/vice/support-matrix.md +54 -0
  78. package/mcp/prompts.json +292 -0
  79. package/mcp/protocol-examples.json +46 -0
  80. package/mcp/resources.json +940 -0
  81. package/mcp/schemas/c64_config.schema.json +350 -0
  82. package/mcp/schemas/c64_debug.schema.json +380 -0
  83. package/mcp/schemas/c64_disk.schema.json +278 -0
  84. package/mcp/schemas/c64_drive.schema.json +120 -0
  85. package/mcp/schemas/c64_extract.schema.json +302 -0
  86. package/mcp/schemas/c64_graphics.schema.json +333 -0
  87. package/mcp/schemas/c64_memory.schema.json +155 -0
  88. package/mcp/schemas/c64_printer.schema.json +218 -0
  89. package/mcp/schemas/c64_program.schema.json +391 -0
  90. package/mcp/schemas/c64_rag.schema.json +67 -0
  91. package/mcp/schemas/c64_select_backend.schema.json +32 -0
  92. package/mcp/schemas/c64_sound.schema.json +644 -0
  93. package/mcp/schemas/c64_stream.schema.json +61 -0
  94. package/mcp/schemas/c64_system.schema.json +227 -0
  95. package/mcp/schemas/c64_vice.schema.json +63 -0
  96. package/mcp/server.json +15 -0
  97. package/mcp/tools.json +4517 -0
  98. package/mcp.json +83 -9
  99. package/package.json +8 -3
  100. package/scripts/capture-backend-switch-screenshots.mjs +276 -0
  101. package/scripts/generate-mcp-interface.ts +289 -0
  102. package/scripts/invoke-bun.mjs +67 -21
  103. package/scripts/mockC64Server.mjs +162 -6
  104. package/scripts/run-coverage.mjs +284 -0
  105. package/scripts/run-tests.mjs +95 -39
  106. package/scripts/run-tests.ts +378 -73
  107. package/scripts/update-readme.ts +147 -13
@@ -1,5 +1,6 @@
1
1
  import { Buffer } from "node:buffer";
2
2
  import { createPetsciiArt } from "../petsciiArt.js";
3
+ import { importImageAsVicBitmap } from "../vicBitmap.js";
3
4
  import { defineToolModule, OPERATION_DISCRIMINATOR, } from "./types.js";
4
5
  import { booleanSchema, numberSchema, objectSchema, optionalSchema, stringSchema, } from "./schema.js";
5
6
  import { jsonResult, textResult } from "./responses.js";
@@ -49,6 +50,37 @@ function decodeSpriteString(value, path) {
49
50
  throw new ToolValidationError("Unable to parse sprite hex string", { path, cause: error });
50
51
  }
51
52
  }
53
+ function parseAddressValue(value) {
54
+ const trimmed = value.trim();
55
+ if (!trimmed) {
56
+ return null;
57
+ }
58
+ const normalised = trimmed.startsWith("$") ? `0x${trimmed.slice(1)}` : trimmed;
59
+ const parsed = Number(normalised);
60
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 0xFFFF) {
61
+ return null;
62
+ }
63
+ return parsed;
64
+ }
65
+ const addressSchema = {
66
+ jsonSchema: {
67
+ description: "Absolute C64 memory address, provided as an integer or hex string such as $2000.",
68
+ type: ["integer", "string"],
69
+ },
70
+ parse(value, path) {
71
+ const resolvedPath = path ?? "$";
72
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 0xFFFF) {
73
+ return value;
74
+ }
75
+ if (typeof value === "string") {
76
+ const parsed = parseAddressValue(value);
77
+ if (parsed !== null) {
78
+ return parsed;
79
+ }
80
+ }
81
+ throw new ToolValidationError("Address must be an integer or hex string within $0000-$FFFF", { path: resolvedPath });
82
+ },
83
+ };
52
84
  const spriteBytesSchema = {
53
85
  jsonSchema: {
54
86
  description: "63-byte sprite definition provided as base64/hex string or array of bytes.",
@@ -90,7 +122,7 @@ const spriteBytesSchema = {
90
122
  },
91
123
  };
92
124
  const spriteArgsSchema = objectSchema({
93
- description: "Generate a PRG that displays a single sprite using raw sprite data.",
125
+ description: "Display a single sprite by writing supplied sprite data into RAM and patching the relevant VIC-II registers.",
94
126
  properties: {
95
127
  sprite: spriteBytesSchema,
96
128
  index: numberSchema({
@@ -221,7 +253,7 @@ const petsciiImageArgsSchema = objectSchema({
221
253
  maximum: 15,
222
254
  })),
223
255
  dryRun: booleanSchema({
224
- description: "When true, skip uploading the BASIC program to the C64.",
256
+ description: "When true, skip uploading the generated BASIC program to the C64.",
225
257
  default: false,
226
258
  }),
227
259
  bitmap: optionalSchema(bitmapSchema),
@@ -251,6 +283,49 @@ const renderPetsciiScreenArgsSchema = objectSchema({
251
283
  required: ["text"],
252
284
  additionalProperties: false,
253
285
  });
286
+ const vicBitmapModeSchema = {
287
+ jsonSchema: {
288
+ description: "Bitmap encoding mode to generate for the VIC-II display.",
289
+ type: "string",
290
+ enum: ["hires", "multicolor"],
291
+ },
292
+ parse(value, path) {
293
+ if (value === "hires" || value === "multicolor") {
294
+ return value;
295
+ }
296
+ throw new ToolValidationError("Bitmap format must be either hires or multicolor", { path: path ?? "$" });
297
+ },
298
+ };
299
+ const generateBitmapArgsSchema = objectSchema({
300
+ description: "Import an image file, convert it to a VIC-II bitmap, write it into RAM, and enable bitmap mode.",
301
+ properties: {
302
+ imagePath: stringSchema({
303
+ description: "Filesystem path of the source image (PNG, JPEG, BMP, GIF, TIFF, and other Jimp-supported formats).",
304
+ minLength: 1,
305
+ }),
306
+ format: vicBitmapModeSchema,
307
+ bitmapAddress: optionalSchema(addressSchema, 0x2000),
308
+ screenAddress: optionalSchema(addressSchema, 0x0400),
309
+ borderColor: optionalSchema(numberSchema({
310
+ description: "Border colour index (0-15).",
311
+ integer: true,
312
+ minimum: 0,
313
+ maximum: 15,
314
+ }), 0),
315
+ backgroundColor: optionalSchema(numberSchema({
316
+ description: "Background colour index (0-15). Used for multicolor mode and aspect-ratio padding.",
317
+ integer: true,
318
+ minimum: 0,
319
+ maximum: 15,
320
+ }), 0),
321
+ preserveAspect: booleanSchema({
322
+ description: "Preserve the source image aspect ratio and pad with the background colour.",
323
+ default: true,
324
+ }),
325
+ },
326
+ required: ["imagePath", "format"],
327
+ additionalProperties: false,
328
+ });
254
329
  function stripOperationDiscriminator(value) {
255
330
  const { [OPERATION_DISCRIMINATOR]: _ignored, ...rest } = value;
256
331
  return rest;
@@ -258,7 +333,7 @@ function stripOperationDiscriminator(value) {
258
333
  async function executeGenerateSprite(rawArgs, ctx) {
259
334
  try {
260
335
  const parsed = spriteArgsSchema.parse(rawArgs ?? {});
261
- ctx.logger.info("Generating sprite PRG", {
336
+ ctx.logger.info("Rendering sprite", {
262
337
  index: parsed.index,
263
338
  x: parsed.x,
264
339
  y: parsed.y,
@@ -274,11 +349,11 @@ async function executeGenerateSprite(rawArgs, ctx) {
274
349
  multicolour: parsed.multicolour,
275
350
  });
276
351
  if (!result.success) {
277
- throw new ToolExecutionError("C64 firmware reported failure while running sprite PRG", {
352
+ throw new ToolExecutionError("C64 firmware reported failure while rendering sprite", {
278
353
  details: normaliseFailure(result.details),
279
354
  });
280
355
  }
281
- return textResult("Sprite PRG generated and executed successfully.", {
356
+ return textResult("Sprite rendered successfully.", {
282
357
  success: true,
283
358
  index: parsed.index,
284
359
  x: parsed.x,
@@ -293,7 +368,9 @@ async function executeGenerateSprite(rawArgs, ctx) {
293
368
  if (error instanceof ToolError) {
294
369
  return toolErrorResult(error);
295
370
  }
296
- return unknownErrorResult(error);
371
+ return toolErrorResult(new ToolExecutionError("Unable to render sprite", {
372
+ details: normaliseFailure(error instanceof Error ? { message: error.message } : error),
373
+ }));
297
374
  }
298
375
  }
299
376
  async function executeRenderPetscii(rawArgs, ctx) {
@@ -322,7 +399,75 @@ async function executeRenderPetscii(rawArgs, ctx) {
322
399
  if (error instanceof ToolError) {
323
400
  return toolErrorResult(error);
324
401
  }
325
- return unknownErrorResult(error);
402
+ return toolErrorResult(new ToolExecutionError("Unable to render PETSCII screen", {
403
+ details: normaliseFailure(error instanceof Error ? { message: error.message } : error),
404
+ }));
405
+ }
406
+ }
407
+ async function executeGenerateBitmap(rawArgs, ctx) {
408
+ try {
409
+ const parsed = generateBitmapArgsSchema.parse(rawArgs ?? {});
410
+ ctx.logger.info("Rendering bitmap", {
411
+ imagePath: parsed.imagePath,
412
+ format: parsed.format,
413
+ bitmapAddress: parsed.bitmapAddress,
414
+ screenAddress: parsed.screenAddress,
415
+ preserveAspect: parsed.preserveAspect,
416
+ });
417
+ const prepared = await importImageAsVicBitmap({
418
+ imagePath: parsed.imagePath,
419
+ mode: parsed.format,
420
+ preserveAspect: parsed.preserveAspect,
421
+ backgroundColor: parsed.backgroundColor,
422
+ borderColor: parsed.borderColor,
423
+ });
424
+ const result = await ctx.client.displayBitmap(prepared, {
425
+ bitmapAddress: parsed.bitmapAddress,
426
+ screenAddress: parsed.screenAddress,
427
+ });
428
+ if (!result.success) {
429
+ throw new ToolExecutionError("C64 firmware reported failure while displaying bitmap image", {
430
+ details: normaliseFailure(result.details),
431
+ });
432
+ }
433
+ const details = toRecord(result.details) ?? {};
434
+ const data = {
435
+ mode: prepared.mode,
436
+ imagePath: parsed.imagePath,
437
+ sourceWidth: prepared.sourceWidth,
438
+ sourceHeight: prepared.sourceHeight,
439
+ logicalWidth: prepared.logicalWidth,
440
+ logicalHeight: prepared.logicalHeight,
441
+ displayWidth: prepared.displayWidth,
442
+ displayHeight: prepared.displayHeight,
443
+ backgroundColor: prepared.backgroundColor,
444
+ borderColor: prepared.borderColor,
445
+ bitmapAddress: details.bitmapAddress ?? null,
446
+ screenAddress: details.screenAddress ?? null,
447
+ colorRamAddress: details.colorRamAddress ?? null,
448
+ bank: details.bank ?? null,
449
+ registers: details.registers ?? null,
450
+ bitmapBytes: prepared.bitmapData.length,
451
+ screenBytes: prepared.screenRam.length,
452
+ colorRamBytes: prepared.colorRam.length,
453
+ };
454
+ return jsonResult(data, {
455
+ success: true,
456
+ mode: prepared.mode,
457
+ bank: details.bank ?? null,
458
+ bitmapAddress: details.bitmapAddress ?? null,
459
+ screenAddress: details.screenAddress ?? null,
460
+ sourceWidth: prepared.sourceWidth,
461
+ sourceHeight: prepared.sourceHeight,
462
+ });
463
+ }
464
+ catch (error) {
465
+ if (error instanceof ToolError) {
466
+ return toolErrorResult(error);
467
+ }
468
+ return toolErrorResult(new ToolExecutionError("Unable to import or display bitmap image", {
469
+ details: normaliseFailure(error instanceof Error ? { message: error.message } : error),
470
+ }));
326
471
  }
327
472
  }
328
473
  async function executeCreatePetscii(rawArgs, ctx) {
@@ -392,9 +537,10 @@ async function executeCreatePetscii(rawArgs, ctx) {
392
537
  }
393
538
  }
394
539
  export const graphicsOperationHandlers = {
395
- create_petscii: async (args, ctx) => executeCreatePetscii(stripOperationDiscriminator(args), ctx),
396
- render_petscii: async (args, ctx) => executeRenderPetscii(stripOperationDiscriminator(args), ctx),
397
- generate_sprite: async (args, ctx) => executeGenerateSprite(stripOperationDiscriminator(args), ctx),
540
+ render_petscii_art: async (args, ctx) => executeCreatePetscii(stripOperationDiscriminator(args), ctx),
541
+ render_bitmap: async (args, ctx) => executeGenerateBitmap(stripOperationDiscriminator(args), ctx),
542
+ render_petscii_text: async (args, ctx) => executeRenderPetscii(stripOperationDiscriminator(args), ctx),
543
+ render_sprite: async (args, ctx) => executeGenerateSprite(stripOperationDiscriminator(args), ctx),
398
544
  };
399
545
  export const graphicsModule = defineToolModule({
400
546
  domain: "graphics",
@@ -412,9 +558,9 @@ export const graphicsModule = defineToolModule({
412
558
  ],
413
559
  tools: [
414
560
  {
415
- name: "generate_sprite",
416
- description: "Generate and execute a PRG that displays a sprite from raw 63-byte data. See c64://specs/vic for registers.",
417
- summary: "Uploads minimal machine code to copy sprite data, configure VIC-II, and render a sprite.",
561
+ name: "render_sprite",
562
+ description: "Display supplied 63-byte sprite data at the requested position and colour by writing memory and patching VIC-II registers directly. See c64://specs/vic for registers.",
563
+ summary: "Writes sprite data into RAM, updates the sprite pointer table, and patches VIC-II registers to render a sprite preview.",
418
564
  inputSchema: spriteArgsSchema.jsonSchema,
419
565
  relatedResources: ["c64://specs/vic"],
420
566
  relatedPrompts: ["graphics-demo", "assembly-program"],
@@ -431,13 +577,31 @@ export const graphicsModule = defineToolModule({
431
577
  "Use when the user supplies sprite bytes or asks to preview graphics quickly; describe resulting coordinates and colours.",
432
578
  "Remind the user that sprites live in banked memory so further tweaks may require write_memory calls.",
433
579
  ],
580
+ supportedPlatforms: ["c64u", "vice"],
434
581
  async execute(args, ctx) {
435
582
  return executeGenerateSprite(args, ctx);
436
583
  },
437
584
  },
438
585
  {
439
- name: "render_petscii",
440
- description: "Render PETSCII text to the screen with optional border/background colours. See c64://specs/basic.",
586
+ name: "render_bitmap",
587
+ description: "Import an image file, convert it to a VIC-II bitmap, write it into RAM, and display it.",
588
+ summary: "Decodes a source image, quantizes it into C64 colours, writes bitmap/screen/color RAM, and displays it in hires or multicolor bitmap mode.",
589
+ inputSchema: generateBitmapArgsSchema.jsonSchema,
590
+ relatedResources: ["c64://specs/vic", "c64://context/bootstrap"],
591
+ relatedPrompts: ["graphics-demo"],
592
+ tags: ["bitmap", "vic", "image"],
593
+ workflowHints: [
594
+ "Use when the user wants to display an external image on screen rather than generate PETSCII art.",
595
+ "Call out the selected bitmap and screen RAM addresses so follow-up memory inspection or raster work stays grounded.",
596
+ ],
597
+ supportedPlatforms: ["c64u", "vice"],
598
+ async execute(args, ctx) {
599
+ return executeGenerateBitmap(args, ctx);
600
+ },
601
+ },
602
+ {
603
+ name: "render_petscii_text",
604
+ description: "Display PETSCII text with optional border and background colours. See c64://specs/basic.",
441
605
  summary: "Generates a BASIC program that clears the screen, sets colours, and prints text.",
442
606
  inputSchema: renderPetsciiScreenArgsSchema.jsonSchema,
443
607
  relatedResources: ["c64://specs/basic", "c64://context/bootstrap"],
@@ -455,13 +619,14 @@ export const graphicsModule = defineToolModule({
455
619
  "Call after generating PETSCII text or when the user wants border/background colour changes applied.",
456
620
  "Echo the colour indices and mention CLEAR + PRINT so the user knows what ran.",
457
621
  ],
622
+ supportedPlatforms: ["c64u", "vice"],
458
623
  async execute(args, ctx) {
459
624
  return executeRenderPetscii(args, ctx);
460
625
  },
461
626
  },
462
627
  {
463
- name: "create_petscii",
464
- description: "Create PETSCII art from prompts or text, optionally run it on the C64, and return metadata including PETSCII codes and glyphs. See c64://specs/basic, c64://specs/vic, and c64://specs/charset.",
628
+ name: "render_petscii_art",
629
+ description: "Create PETSCII art from prompts, text, or bitmap input, and optionally display it on the C64. Returns metadata including PETSCII codes and glyphs. See c64://specs/basic, c64://specs/vic, and c64://specs/charset.",
465
630
  summary: "Synthesises PETSCII art, generates a BASIC program with preview metadata (petsciiCodes, glyphs, dimensions), and uploads it unless dry-run is requested.",
466
631
  inputSchema: petsciiImageArgsSchema.jsonSchema,
467
632
  relatedResources: ["c64://specs/basic", "c64://specs/vic", "c64://specs/charset"],
@@ -480,6 +645,7 @@ export const graphicsModule = defineToolModule({
480
645
  "Response includes petsciiCodes array and glyphs for character-level inspection.",
481
646
  "Provide follow-up suggestions like saving the PRG or capturing the screen after rendering.",
482
647
  ],
648
+ supportedPlatforms: ["c64u", "vice"],
483
649
  async execute(args, ctx) {
484
650
  return executeCreatePetscii(args, ctx);
485
651
  },
@@ -26,7 +26,7 @@ function normaliseFailure(details) {
26
26
  }
27
27
  const resetArgsSchema = createNoArgsSchema("No arguments required to reset the C64.");
28
28
  const rebootArgsSchema = createNoArgsSchema("No arguments required to reboot the C64 firmware.");
29
- const pauseArgsSchema = createNoArgsSchema("No arguments required to pause the machine via DMA.");
29
+ const pauseArgsSchema = createNoArgsSchema("No arguments required to pause the machine.");
30
30
  const resumeArgsSchema = createNoArgsSchema("No arguments required to resume the machine after a pause.");
31
31
  const poweroffArgsSchema = createNoArgsSchema("No arguments required to power off the machine.");
32
32
  const menuButtonArgsSchema = createNoArgsSchema("No arguments required to toggle the Ultimate menu button.");
@@ -54,6 +54,7 @@ export const machineControlModule = defineToolModule({
54
54
  workflowHints: [
55
55
  "Use when the user wants a quick restart without losing power; mention that memory contents may persist.",
56
56
  ],
57
+ supportedPlatforms: ["c64u", "vice"],
57
58
  async execute(args, ctx) {
58
59
  try {
59
60
  resetArgsSchema.parse(args ?? {});
@@ -91,6 +92,7 @@ export const machineControlModule = defineToolModule({
91
92
  workflowHints: [
92
93
  "Choose reboot when configuration changed or hardware is stuck; warn that it will interrupt any running program.",
93
94
  ],
95
+ supportedPlatforms: ["c64u", "vice"],
94
96
  async execute(args, ctx) {
95
97
  try {
96
98
  rebootArgsSchema.parse(args ?? {});
@@ -117,8 +119,8 @@ export const machineControlModule = defineToolModule({
117
119
  },
118
120
  {
119
121
  name: "pause",
120
- description: "Pause the machine using DMA halt. See memory safety checklist in c64://context/bootstrap.",
121
- summary: "Suspends CPU execution until resumed.",
122
+ description: "Pause the machine on Ultimate hardware. See memory safety checklist in c64://context/bootstrap.",
123
+ summary: "Suspends CPU execution on Ultimate hardware until resumed.",
122
124
  inputSchema: pauseArgsSchema.jsonSchema,
123
125
  tags: ["pause"],
124
126
  prerequisites: [],
@@ -128,6 +130,7 @@ export const machineControlModule = defineToolModule({
128
130
  workflowHints: [
129
131
  "Pause when the user needs a stable memory snapshot; remind them to resume to continue execution.",
130
132
  ],
133
+ supportedPlatforms: ["c64u"],
131
134
  async execute(args, ctx) {
132
135
  try {
133
136
  pauseArgsSchema.parse(args ?? {});
@@ -154,8 +157,8 @@ export const machineControlModule = defineToolModule({
154
157
  },
155
158
  {
156
159
  name: "resume",
157
- description: "Resume the machine after a DMA pause.",
158
- summary: "Releases the DMA halt so the CPU can continue.",
160
+ description: "Resume the machine after an Ultimate hardware pause.",
161
+ summary: "Releases the Ultimate hardware pause so the CPU can continue.",
159
162
  inputSchema: resumeArgsSchema.jsonSchema,
160
163
  tags: ["resume"],
161
164
  prerequisites: ["pause"],
@@ -165,6 +168,7 @@ export const machineControlModule = defineToolModule({
165
168
  workflowHints: [
166
169
  "Call after a pause or diagnostic halt and confirm the machine is running again.",
167
170
  ],
171
+ supportedPlatforms: ["c64u"],
168
172
  async execute(args, ctx) {
169
173
  try {
170
174
  resumeArgsSchema.parse(args ?? {});
@@ -202,6 +206,7 @@ export const machineControlModule = defineToolModule({
202
206
  workflowHints: [
203
207
  "Use when the user explicitly requests power off; remind them to power on manually or via drive controls afterwards.",
204
208
  ],
209
+ supportedPlatforms: ["c64u", "vice"],
205
210
  async execute(args, ctx) {
206
211
  try {
207
212
  poweroffArgsSchema.parse(args ?? {});
@@ -239,6 +244,7 @@ export const machineControlModule = defineToolModule({
239
244
  workflowHints: [
240
245
  "Use when the user needs to open or close the Ultimate menu; suggest following up with drive operations if relevant.",
241
246
  ],
247
+ supportedPlatforms: ["c64u"],
242
248
  async execute(args, ctx) {
243
249
  try {
244
250
  menuButtonArgsSchema.parse(args ?? {});
@@ -75,6 +75,10 @@ function resolveAddressLabel(details, fallback) {
75
75
  function resolveLength(details) {
76
76
  return typeof details.length === "number" ? details.length : undefined;
77
77
  }
78
+ function supportsMachinePause(ctx) {
79
+ const platformId = ctx.platform?.id;
80
+ return platformId !== "vice";
81
+ }
78
82
  const readScreenArgsSchema = objectSchema({
79
83
  description: "No arguments are required for reading the current screen contents.",
80
84
  properties: {},
@@ -111,7 +115,7 @@ const writeMemoryArgsSchema = objectSchema({
111
115
  pattern: /^[\s_0-9A-Fa-f$]+$/,
112
116
  }),
113
117
  verify: booleanSchema({
114
- description: "When true, pause and verify the write by reading back the affected range.",
118
+ description: "When true, verify the write by reading before and after the change; Ultimate hardware also pauses during verification.",
115
119
  default: false,
116
120
  }),
117
121
  expected: optionalSchema(stringSchema({
@@ -208,15 +212,18 @@ async function executeWriteMemory(rawArgs, ctx) {
208
212
  details: detailRecord,
209
213
  });
210
214
  }
215
+ const canPause = supportsMachinePause(ctx);
211
216
  let paused = false;
212
217
  try {
213
- const pauseResult = await ctx.client.pause();
214
- if (!pauseResult.success) {
215
- throw new ToolExecutionError("C64 firmware reported failure while pausing", {
216
- details: normaliseFailure(pauseResult.details),
217
- });
218
+ if (canPause) {
219
+ const pauseResult = await ctx.client.pause();
220
+ if (!pauseResult.success) {
221
+ throw new ToolExecutionError("C64 firmware reported failure while pausing", {
222
+ details: normaliseFailure(pauseResult.details),
223
+ });
224
+ }
225
+ paused = true;
218
226
  }
219
- paused = true;
220
227
  const expectedBytes = expectedInfo?.bytes ?? new Uint8Array();
221
228
  const maskBytes = maskInfo?.bytes;
222
229
  const readLength = Math.max(1, Math.max(writeInfo.bytes.length, expectedBytes.length));
@@ -306,6 +313,7 @@ async function executeWriteMemory(rawArgs, ctx) {
306
313
  details: detailRecord,
307
314
  verified: true,
308
315
  verification: verificationMetadata,
316
+ paused,
309
317
  });
310
318
  }
311
319
  finally {
@@ -344,6 +352,7 @@ export const memoryOperationHandlers = {
344
352
  export const memoryModule = defineToolModule({
345
353
  domain: "memory",
346
354
  summary: "Screen, main memory, and low-level inspection utilities.",
355
+ supportedPlatforms: ["c64u", "vice"],
347
356
  resources: [
348
357
  "c64://context/bootstrap",
349
358
  "c64://specs/basic",
@@ -387,7 +396,7 @@ export const memoryModule = defineToolModule({
387
396
  relatedResources: ["c64://context/bootstrap", "c64://specs/assembly", "c64://docs/index"],
388
397
  relatedPrompts: ["memory-debug", "assembly-program"],
389
398
  tags: ["memory", "hex"],
390
- prerequisites: ["pause"],
399
+ prerequisites: [],
391
400
  examples: [
392
401
  {
393
402
  name: "Read screen memory",
@@ -406,12 +415,12 @@ export const memoryModule = defineToolModule({
406
415
  {
407
416
  name: "write",
408
417
  description: "Write a hexadecimal byte sequence into main memory at the specified address. See c64://context/bootstrap for safety rules.",
409
- summary: "Resolves symbols, validates hex data, and writes bytes to RAM via Ultimate firmware.",
418
+ summary: "Resolves symbols, validates hex data, and writes bytes to RAM.",
410
419
  inputSchema: writeMemoryArgsSchema.jsonSchema,
411
420
  relatedResources: ["c64://context/bootstrap", "c64://specs/assembly", "c64://docs/index"],
412
421
  relatedPrompts: ["memory-debug", "assembly-program"],
413
422
  tags: ["memory", "hex", "write"],
414
- prerequisites: ["pause", "read"],
423
+ prerequisites: ["read"],
415
424
  examples: [
416
425
  {
417
426
  name: "Write to screen",