@henols/c64-re-tools 2.0.0-rc.8 → 2.0.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 (118) hide show
  1. package/README.md +98 -115
  2. package/package.json +1 -1
  3. package/skills/c64-assembler/SKILL.md +22 -6
  4. package/skills/c64-assembler/scripts/assemble.ts +42 -14
  5. package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
  6. package/skills/c64-basic/SKILL.md +10 -1
  7. package/skills/c64-basic/scripts/basic.ts +3 -3
  8. package/skills/c64-disk/SKILL.md +13 -4
  9. package/skills/c64-disk/scripts/disk.ts +7 -7
  10. package/skills/c64-disk/scripts/{containers.ts → lib/containers.ts} +2 -2
  11. package/skills/c64-docs/SKILL.md +138 -0
  12. package/skills/c64-docs/references/page-types.md +141 -0
  13. package/skills/c64-docs/references/style.md +43 -0
  14. package/skills/c64-docs/scripts/generate.ts +109 -0
  15. package/skills/c64-docs/scripts/lib/links.ts +167 -0
  16. package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
  17. package/skills/c64-docs/scripts/lib/reference.ts +406 -0
  18. package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
  19. package/skills/c64-docs/scripts/link.ts +239 -0
  20. package/skills/c64-emulator/SKILL.md +16 -5
  21. package/skills/c64-knowledge/SKILL.md +140 -18
  22. package/skills/c64-knowledge/scripts/knowledge.ts +737 -112
  23. package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
  24. package/skills/c64-memory-map/SKILL.md +11 -2
  25. package/skills/c64-memory-map/scripts/memmap.ts +2 -2
  26. package/skills/c64-provenance/SKILL.md +66 -11
  27. package/skills/c64-provenance/scripts/{compare.ts → lib/compare.ts} +3 -3
  28. package/skills/c64-provenance/scripts/lib/library.ts +584 -0
  29. package/skills/c64-provenance/scripts/{runtime.ts → lib/runtime.ts} +2 -2
  30. package/skills/c64-provenance/scripts/{separate.ts → lib/separate.ts} +3 -3
  31. package/skills/c64-provenance/scripts/provenance.ts +242 -40
  32. package/skills/c64-reverse-engineering/SKILL.md +51 -26
  33. package/skills/c64-source/SKILL.md +76 -28
  34. package/skills/c64-source/scripts/generate.ts +425 -127
  35. package/skills/c64-source/scripts/{loader.ts → lib/loader.ts} +84 -25
  36. package/skills/c64-source/scripts/lib/source.ts +1121 -0
  37. package/skills/c64-static-analysis/SKILL.md +35 -18
  38. package/skills/c64-static-analysis/scripts/analyze.ts +184 -55
  39. package/skills/c64-static-analysis/scripts/{coverage.ts → lib/coverage.ts} +19 -25
  40. package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +6 -2
  41. package/skills/c64-static-analysis/scripts/{idioms.ts → lib/idioms.ts} +21 -12
  42. package/skills/c64-static-analysis/scripts/{instructions.ts → lib/instructions.ts} +1 -1
  43. package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
  44. package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
  45. package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
  46. package/skills/c64-testing/SKILL.md +36 -7
  47. package/skills/c64-testing/references/scenario-format.md +12 -8
  48. package/skills/c64-testing/scripts/checklist.ts +3 -3
  49. package/skills/c64-testing/scripts/diff.ts +167 -0
  50. package/skills/c64-testing/scripts/lib/diff.ts +70 -0
  51. package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +45 -34
  52. package/skills/c64-testing/scripts/run.ts +73 -21
  53. package/skills/c64-testing/scripts/test.ts +5 -5
  54. package/skills/c64-unpacker/SKILL.md +23 -9
  55. package/skills/c64-unpacker/scripts/{evidence.ts → lib/evidence.ts} +1 -1
  56. package/skills/c64-unpacker/scripts/{loads.ts → lib/loads.ts} +4 -4
  57. package/skills/c64-unpacker/scripts/{machine.ts → lib/machine.ts} +4 -4
  58. package/skills/c64-unpacker/scripts/{stages.ts → lib/stages.ts} +17 -5
  59. package/skills/c64-unpacker/scripts/unpack.ts +41 -24
  60. package/src/c64.ts +103 -5
  61. package/src/cli/cleanup.ts +1 -1
  62. package/src/cli/main.ts +7 -4
  63. package/src/cli/status.ts +70 -3
  64. package/src/graphics.ts +158 -0
  65. package/src/host/main.ts +43 -8
  66. package/src/host/server.ts +10 -3
  67. package/src/host/tools/petcat.ts +1 -1
  68. package/src/host/tools/status.ts +1 -1
  69. package/src/host/vice/adapter.ts +4 -3
  70. package/src/host/vice/process.ts +2 -2
  71. package/src/host/vice/screen.ts +1 -42
  72. package/src/host/vice/session.ts +196 -23
  73. package/src/host-client/autostart.ts +1 -1
  74. package/src/host-client/connect.ts +1 -1
  75. package/src/host-client/coverage.ts +2 -29
  76. package/src/host-client/image.ts +66 -0
  77. package/src/host-client/steps.ts +9 -4
  78. package/src/host-client/tools.ts +1 -0
  79. package/src/host-client/transfer.ts +39 -11
  80. package/src/host-client/vice-session.ts +33 -16
  81. package/src/knowledge/conflicts.ts +161 -0
  82. package/src/knowledge/database.ts +150 -38
  83. package/src/knowledge/expression.ts +209 -0
  84. package/src/knowledge/graph.ts +104 -0
  85. package/src/knowledge/history.ts +70 -22
  86. package/src/knowledge/import.ts +64 -55
  87. package/src/knowledge/join.ts +211 -0
  88. package/src/knowledge/program.ts +266 -0
  89. package/src/knowledge/read.ts +258 -27
  90. package/src/knowledge/schema.ts +199 -75
  91. package/src/knowledge/text.ts +415 -0
  92. package/src/knowledge/write.ts +740 -57
  93. package/src/mcp/main.ts +1 -1
  94. package/src/mcp/output.ts +15 -8
  95. package/src/mcp/server.ts +3 -1
  96. package/src/mcp/tools/debug.ts +97 -16
  97. package/src/mcp/tools/execution.ts +38 -5
  98. package/src/mcp/tools/machine.ts +11 -2
  99. package/src/mcp/tools/media.ts +18 -13
  100. package/src/mcp/tools/memory.ts +17 -8
  101. package/src/mcp/tools/video.ts +9 -9
  102. package/src/native/discover.ts +2 -2
  103. package/src/native/ghidra/analyze.ts +2 -2
  104. package/src/native/ghidra/index.ts +1 -1
  105. package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
  106. package/src/native/ghidra/scripts/C64Export.java +1 -1
  107. package/src/native/ghidra/scripts/C64Prepare.java +7 -1
  108. package/src/native/types.ts +14 -8
  109. package/{skills/c64-source/scripts → src}/opcodes.ts +75 -3
  110. package/src/platform.ts +70 -7
  111. package/src/project.ts +180 -3
  112. package/src/protocol/messages.ts +1 -1
  113. package/src/protocol/tools.ts +2 -16
  114. package/src/protocol/vice.ts +124 -15
  115. package/src/script.ts +46 -7
  116. package/src/trace.ts +55 -23
  117. package/skills/c64-assembler/scripts/compare.ts +0 -84
  118. package/skills/c64-source/scripts/source.ts +0 -595
package/src/host/main.ts CHANGED
@@ -7,6 +7,7 @@ import { ProcessSupervisor } from "../native/processes.ts";
7
7
  import { startTrace, trace } from "../trace.ts";
8
8
  import { ListenError, parsePort, startHostServer, stopSignal } from "./server.ts";
9
9
  import { viceFlaws, type ViceVersion } from "../native/discover.ts";
10
+ import { inspect } from "./tools/c1541.ts";
10
11
  import { createToolDispatcher } from "./tools/index.ts";
11
12
  import { checkViceStarts, findVice } from "./vice/process.ts";
12
13
  import { viceSessionFactory } from "./vice/session.ts";
@@ -15,7 +16,7 @@ import { PACKAGE } from "../package.ts";
15
16
  const HELP = `c64-re-tools-host
16
17
 
17
18
  Usage:
18
- npx -y --package=${PACKAGE.name}@${PACKAGE.tag} c64-re-tools-host [--port <port>]
19
+ npx -y --package=${PACKAGE.name}@${PACKAGE.tag} c64-re-tools-host [--port <port>] [--log-console] [--log-dir <directory>]
19
20
  c64-re-tools-host --help
20
21
 
21
22
  Runs the c64-re-tools Host Runtime in the foreground. It first starts VICE
@@ -27,8 +28,16 @@ connected c64-re-tools-mcp process, and runs the tools that come with VICE
27
28
  emulator and tool it started.
28
29
 
29
30
  Options:
30
- --port <port> Listen on this port instead (0 picks a free port).
31
- --help Show this help.`;
31
+ --port <port> Listen on this port instead (0 picks a free port).
32
+ --log-console Also print each request with its result, and each run
33
+ of c1541 or petcat, to the console.
34
+ --log-dir <directory> Write the same events as JSON lines to a file in this
35
+ directory, with a copy of each VICE log.
36
+ --help Show this help.
37
+
38
+ The host always logs its start and stop, connections, failures, and each call
39
+ from a skill script (c1541, petcat) with its outcome. --log-console adds the
40
+ calls of the MCP server and the tool runs. C64RT_TRACE does not apply to the host.`;
32
41
 
33
42
  function log(line: string): void {
34
43
  process.stderr.write(`c64-re-tools-host: ${line}\n`);
@@ -36,9 +45,12 @@ function log(line: string): void {
36
45
  }
37
46
 
38
47
  async function main(): Promise<number> {
39
- let values: { help?: boolean; port?: string };
48
+ let values: { help?: boolean; port?: string; "log-console"?: boolean; "log-dir"?: string };
40
49
  try {
41
- ({ values } = parseArgs({ options: { help: { type: "boolean" }, port: { type: "string" } }, strict: true }));
50
+ ({ values } = parseArgs({
51
+ options: { help: { type: "boolean" }, port: { type: "string" }, "log-console": { type: "boolean" }, "log-dir": { type: "string" } },
52
+ strict: true,
53
+ }));
42
54
  } catch (error) {
43
55
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n\n${HELP}\n`);
44
56
  return 2;
@@ -47,7 +59,18 @@ async function main(): Promise<number> {
47
59
  process.stdout.write(`${HELP}\n`);
48
60
  return 0;
49
61
  }
50
- startTrace("host", { warn: log });
62
+ const logDir = values["log-dir"];
63
+ if (logDir === "") {
64
+ process.stderr.write("--log-dir needs a directory.\n");
65
+ return 2;
66
+ }
67
+ startTrace("host", {
68
+ target: {
69
+ ...(logDir === undefined ? {} : { dir: { path: logDir, given: `--log-dir ${logDir}` } }),
70
+ ...(values["log-console"] ? { echo: (line: string) => process.stderr.write(`c64-re-tools-host: ${line}\n`) } : {}),
71
+ },
72
+ warn: log,
73
+ });
51
74
 
52
75
  const port = values.port === undefined ? DEFAULT_HOST_PORT : parsePort(values.port);
53
76
  if (port === undefined) {
@@ -55,6 +78,9 @@ async function main(): Promise<number> {
55
78
  return 2;
56
79
  }
57
80
 
81
+ // First, so that every start, also a refused one, tells which runtime runs.
82
+ log(`${PACKAGE.name} ${PACKAGE.version} (Node.js ${process.version}, ${process.platform} ${process.arch})`);
83
+
58
84
  // Without an emulator the runtime is useless: refuse to start, by name and with the remedy.
59
85
  try {
60
86
  findVice(process.env);
@@ -80,7 +106,7 @@ async function main(): Promise<number> {
80
106
  }
81
107
  log(`VICE ${vice.major}.${vice.minor}${vice.revision === undefined ? "" : ` r${vice.revision}`} starts`);
82
108
  const flaws = viceFlaws(vice);
83
- // A known VICE flaw is named, not worked around (D56).
109
+ // A known VICE flaw is named, not worked around.
84
110
  for (const flaw of flaws) log(`Known VICE flaw: ${flaw}`);
85
111
 
86
112
  let server;
@@ -89,7 +115,16 @@ async function main(): Promise<number> {
89
115
  host: "0.0.0.0",
90
116
  port,
91
117
  log,
92
- createViceSession: viceSessionFactory({ supervisor, log, flaws }),
118
+ createViceSession: viceSessionFactory({
119
+ supervisor,
120
+ log,
121
+ flaws,
122
+ // c1541 lists the disk, so an autostart index selects a program.
123
+ directory: async (imageType, image) => {
124
+ const { result } = await inspect({ action: "directory", imageType }, image, { supervisor, signal: new AbortController().signal, log });
125
+ return result.action === "directory" ? result.entries : [];
126
+ },
127
+ }),
93
128
  tools: createToolDispatcher({ supervisor, log, vice }),
94
129
  });
95
130
  } catch (error) {
@@ -4,7 +4,7 @@ import { encodeFrame, MessageReader } from "../protocol/framing.ts";
4
4
  import { checkHello, isOneOf, HEARTBEAT_TIMEOUT_MS, parseClientMessage, ProtocolError, type HostMessage, type ReceivedRequest, type VideoStandard, type WireError, WireFailure } from "../protocol/messages.ts";
5
5
  import { TOOL_OPERATIONS, validateToolParams, type ToolOperation, type ToolOperations } from "../protocol/tools.ts";
6
6
  import { attachmentCount, VICE_OPERATIONS, validateViceParams, type ViceOperation, type ViceOperations } from "../protocol/vice.ts";
7
- import { elapsedMs, trace } from "../trace.ts";
7
+ import { elapsedMs, summarize, trace } from "../trace.ts";
8
8
 
9
9
  /** One live VICE session, owned by one connection. */
10
10
  export interface ViceSessionHandle {
@@ -234,8 +234,15 @@ class Connection {
234
234
 
235
235
  async #answer(request: ReceivedRequest, attachments: Buffer[]): Promise<void> {
236
236
  const started = performance.now();
237
- const record = (outcome: Record<string, unknown>) =>
238
- trace().event("request", { connection: this.#id, op: request.op, id: request.id, params: request.params, attachments: attachments.length, ms: elapsedMs(started), ...outcome });
237
+ const record = (outcome: Record<string, unknown>) => {
238
+ const ms = elapsedMs(started);
239
+ trace().event("request", { connection: this.#id, op: request.op, id: request.id, params: request.params, attachments: attachments.length, ms, ...outcome });
240
+ // A call from a skill script (c1541, petcat, status) is always logged; the many calls of a VICE session only in the trace.
241
+ if (this.#isTool) {
242
+ const what = outcome.ok === true ? "ok" : `${String(outcome.code)}: ${String(outcome.message)}`;
243
+ this.#log(`${request.op} ${JSON.stringify(summarize(request.params) ?? {})}: ${what} (${ms} ms)`);
244
+ }
245
+ };
239
246
  try {
240
247
  let result: unknown;
241
248
  let replyAttachments: Buffer[] = [];
@@ -18,7 +18,7 @@ const TIMEOUT_MS = 30_000;
18
18
  const LISTING = "listing.txt";
19
19
  /**
20
20
  * petcat's listing lines (" 10 sys2061"), as number and text. petcat
21
- * prints π ($ff) as `~`, which is no PETSCII character: the text has π (F-13).
21
+ * prints π ($ff) as `~`, which is no PETSCII character: the text has π.
22
22
  */
23
23
  export function parseListing(listing: string): Array<{ number: number; text: string }> {
24
24
  const lines: Array<{ number: number; text: string }> = [];
@@ -38,7 +38,7 @@ export async function hostStatus(context: ToolContext, started?: ViceVersion): P
38
38
  );
39
39
  const tools = started === undefined ? probed : [{ name: VICE.name, found: true, path: findVice(context.env ?? process.env), version: versionText(started), runs: true }, ...probed];
40
40
  const checked = tools.map((status, index) => requireMinimum(status, specs[index]!));
41
- // VICE runs with its known flaws; status names them (D56).
41
+ // VICE runs with its known flaws; status names them.
42
42
  const vice = checked[0]!;
43
43
  const version = vice.runs ? viceVersionOf(vice.version ?? "") : undefined;
44
44
  const flaws = version === undefined ? [] : viceFlaws(version);
@@ -4,7 +4,8 @@
4
4
  import { WireFailure, type VideoStandard } from "../../protocol/messages.ts";
5
5
  import { type Comparison, type Condition, type BacktraceFrame, type HistoryEntry, type Instruction, type MemmapRange, type ProfileEntry, type JoystickState, type MemoryView, type RegisterValues, type Registers, type Space } from "../../protocol/vice.ts";
6
6
  import { COVERAGE, formatC64Address, formatC64Byte } from "../../c64.ts";
7
- import { decodeDisplay, decodePalette, visibleFrame, type IndexedFrame } from "./screen.ts";
7
+ import type { IndexedFrame } from "../../graphics.ts";
8
+ import { decodeDisplay, decodePalette, visibleFrame } from "./screen.ts";
8
9
  import type { TextMonitor } from "./text-monitor.ts";
9
10
  import type { CiaHidden } from "./chips.ts";
10
11
  import {
@@ -260,7 +261,7 @@ function unknownForm(what: string, lines: string[]): WireFailure {
260
261
  return new WireFailure("operation-failed", `VICE printed the ${what} in a form that c64-re-tools does not read: ${JSON.stringify(lines.slice(0, 3))}`);
261
262
  }
262
263
 
263
- /** The coverage byte (D40) of one memmap row's I/O, ROM and RAM columns, each "rwx" with "-" for no access. */
264
+ /** The coverage byte of one memmap row's I/O, ROM and RAM columns, each "rwx" with "-" for no access. */
264
265
  export function memmapFlags(io: string, rom: string, ram: string): number {
265
266
  let flags = 0;
266
267
  if (ram[0] === "r") flags |= COVERAGE.ramRead;
@@ -434,7 +435,7 @@ export class ViceAdapter {
434
435
 
435
436
  /**
436
437
  * The timer latches and the interrupt mask of the CIA at `base`, from VICE's
437
- * text-monitor dump: no register read gives them (D63).
438
+ * text-monitor dump: no register read gives them.
438
439
  */
439
440
  async ciaHidden(base: 0xdc00 | 0xdd00): Promise<CiaHidden> {
440
441
  const answer = await this.#computerText(`io ${hex4(base)}`);
@@ -12,7 +12,7 @@
12
12
  // - autostart of a PRG needs `-autostartprgmode 1` (inject into RAM);
13
13
  // - the joyport command reaches the C64 only through the "Joyport I/O
14
14
  // simulation" device (37). In VICE 3.10 and older it holds bits 5-7 of
15
- // both CIA 1 ports low (VICE bug #2202, a known flaw named by D56);
15
+ // both CIA 1 ports low (VICE bug #2202, a known flaw);
16
16
  // - with the default JAM action a jammed CPU hangs silently; -jamaction 2
17
17
  // enters the monitor instead;
18
18
  // - the monitor's profiler prints numbers in the host locale, so VICE runs
@@ -97,7 +97,7 @@ export function viceArguments(options: {
97
97
  // Autostart injects a PRG into RAM; the default mode copies it to a disk image first.
98
98
  "-autostartprgmode",
99
99
  "1",
100
- // VICE's default waits a random time before an autostart types its LOAD, so two runs of one file differ (R2-12).
100
+ // VICE's default waits a random time before an autostart types its LOAD, so two runs of one file differ.
101
101
  "+autostart-delay-random",
102
102
  // The same RAM after every power-up: a hard reset is a power cycle, and VICE's default can flip
103
103
  // random bits, so two runs from one start state could differ.
@@ -1,18 +1,10 @@
1
1
  // Canonical emulator-frame capture: VICE's own rendered VIC-II frame, never a
2
2
  // desktop screenshot; the frame comparison and difference image for baselines.
3
3
 
4
- import { crc32, deflateSync } from "node:zlib";
5
-
4
+ import type { IndexedFrame } from "../../graphics.ts";
6
5
  import type { VideoStandard } from "../../protocol/messages.ts";
7
6
  import { FRAME_SIZE } from "../../protocol/vice.ts";
8
7
 
9
- export interface IndexedFrame {
10
- width: number;
11
- height: number;
12
- /** One palette index per pixel, row by row. */
13
- pixels: Uint8Array;
14
- }
15
-
16
8
  export interface RawDisplay {
17
9
  /** Full buffer size, blanking included. */
18
10
  bufferWidth: number;
@@ -83,39 +75,6 @@ export function decodePalette(body: Buffer): Array<[number, number, number]> {
83
75
  return colours;
84
76
  }
85
77
 
86
- function chunk(type: string, data: Uint8Array): Buffer {
87
- const length = Buffer.alloc(4);
88
- length.writeUInt32BE(data.length, 0);
89
- const body = Buffer.concat([Buffer.from(type, "latin1"), data]);
90
- const crc = Buffer.alloc(4);
91
- crc.writeUInt32BE(crc32(body), 0);
92
- return Buffer.concat([length, body, crc]);
93
- }
94
-
95
- /** Encodes an indexed frame as an 8-bit palette PNG. */
96
- export function encodePng(frame: IndexedFrame, palette: ReadonlyArray<readonly [number, number, number]>): Buffer {
97
- for (const index of frame.pixels) {
98
- if (index >= palette.length) throw new Error(`pixel colour ${index} is not in the ${palette.length}-colour palette`);
99
- }
100
- const header = Buffer.alloc(13);
101
- header.writeUInt32BE(frame.width, 0);
102
- header.writeUInt32BE(frame.height, 4);
103
- header[8] = 8; // bit depth
104
- header[9] = 3; // indexed colour
105
- const rows = Buffer.alloc((frame.width + 1) * frame.height);
106
- for (let row = 0; row < frame.height; row++) {
107
- // Filter byte 0 (none), then the row.
108
- rows.set(frame.pixels.subarray(row * frame.width, (row + 1) * frame.width), row * (frame.width + 1) + 1);
109
- }
110
- return Buffer.concat([
111
- Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
112
- chunk("IHDR", header),
113
- chunk("PLTE", Buffer.from(palette.flat())),
114
- chunk("IDAT", deflateSync(rows)),
115
- chunk("IEND", new Uint8Array(0)),
116
- ]);
117
- }
118
-
119
78
  export interface FrameDifference {
120
79
  mismatchingPixels: number;
121
80
  /** Pixels compared: the frame minus the masked ones. */