@henols/c64-re-tools 2.0.1 → 2.1.0-rc.2

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 (104) hide show
  1. package/README.md +8 -4
  2. package/package.json +1 -1
  3. package/skills/c64-assembler/SKILL.md +24 -9
  4. package/skills/c64-assembler/scripts/assembler.ts +95 -0
  5. package/skills/c64-basic/SKILL.md +17 -9
  6. package/skills/c64-basic/scripts/basic.ts +26 -36
  7. package/skills/c64-disk/SKILL.md +17 -7
  8. package/skills/c64-disk/scripts/disk.ts +130 -104
  9. package/skills/c64-disk/scripts/lib/containers.ts +10 -12
  10. package/skills/c64-docs/SKILL.md +47 -28
  11. package/skills/c64-docs/references/style.md +6 -6
  12. package/skills/c64-docs/scripts/docs.ts +323 -0
  13. package/skills/c64-docs/scripts/lib/source-map.ts +1 -1
  14. package/skills/c64-emulator/SKILL.md +11 -7
  15. package/skills/c64-knowledge/SKILL.md +34 -21
  16. package/skills/c64-knowledge/scripts/knowledge.ts +597 -718
  17. package/skills/c64-knowledge/scripts/lib/listing.ts +5 -7
  18. package/skills/c64-memory-map/SKILL.md +18 -10
  19. package/skills/c64-memory-map/scripts/memory-map.ts +81 -0
  20. package/skills/c64-provenance/SKILL.md +82 -26
  21. package/skills/c64-provenance/scripts/lib/compare.ts +8 -12
  22. package/skills/c64-provenance/scripts/lib/leftovers.ts +442 -0
  23. package/skills/c64-provenance/scripts/lib/library.ts +15 -25
  24. package/skills/c64-provenance/scripts/lib/runtime.ts +12 -37
  25. package/skills/c64-provenance/scripts/lib/separate.ts +9 -10
  26. package/skills/c64-provenance/scripts/provenance.ts +222 -252
  27. package/skills/c64-reverse-engineering/SKILL.md +18 -15
  28. package/skills/c64-source/SKILL.md +28 -16
  29. package/skills/c64-source/scripts/lib/loader.ts +52 -77
  30. package/skills/c64-source/scripts/lib/source.ts +33 -42
  31. package/skills/c64-source/scripts/{generate.ts → source.ts} +68 -109
  32. package/skills/c64-static-analysis/SKILL.md +24 -13
  33. package/skills/c64-static-analysis/scripts/lib/coverage.ts +3 -4
  34. package/skills/c64-static-analysis/scripts/lib/findings.ts +2 -2
  35. package/skills/c64-static-analysis/scripts/lib/instructions.ts +1 -1
  36. package/skills/c64-static-analysis/scripts/lib/macros.ts +6 -8
  37. package/skills/c64-static-analysis/scripts/{analyze.ts → static-analysis.ts} +58 -118
  38. package/skills/c64-testing/SKILL.md +94 -19
  39. package/skills/c64-testing/references/recording-format.md +125 -0
  40. package/skills/c64-testing/references/scenario-format.md +24 -19
  41. package/skills/c64-testing/scripts/{diff.ts → lib/diff-files.ts} +18 -69
  42. package/skills/c64-testing/scripts/lib/diff.ts +5 -3
  43. package/skills/c64-testing/scripts/lib/play.ts +334 -0
  44. package/skills/c64-testing/scripts/lib/recording.ts +662 -0
  45. package/skills/c64-testing/scripts/lib/replay.ts +105 -0
  46. package/skills/c64-testing/scripts/{run.ts → lib/runs.ts} +36 -59
  47. package/skills/c64-testing/scripts/lib/scenario.ts +73 -81
  48. package/skills/c64-testing/scripts/testing.ts +189 -0
  49. package/skills/c64-unpacker/SKILL.md +42 -25
  50. package/skills/c64-unpacker/scripts/lib/evidence.ts +3 -4
  51. package/skills/c64-unpacker/scripts/lib/loads.ts +12 -10
  52. package/skills/c64-unpacker/scripts/lib/machine.ts +0 -2
  53. package/skills/c64-unpacker/scripts/lib/stages.ts +14 -19
  54. package/skills/c64-unpacker/scripts/{unpack.ts → unpacker.ts} +109 -211
  55. package/src/c64.ts +35 -20
  56. package/src/cli/main.ts +2 -2
  57. package/src/errors.ts +38 -0
  58. package/src/graphics.ts +3 -1
  59. package/src/host/server.ts +3 -2
  60. package/src/host/vice/session.ts +4 -4
  61. package/src/host-client/entry-state.ts +106 -0
  62. package/src/host-client/image.ts +21 -10
  63. package/src/host-client/steps.ts +285 -108
  64. package/src/host-client/transfer.ts +21 -15
  65. package/src/host-client/vice-session.ts +1 -1
  66. package/src/knowledge/database.ts +3 -23
  67. package/src/knowledge/expression.ts +8 -11
  68. package/src/knowledge/history.ts +2 -2
  69. package/src/knowledge/import.ts +13 -14
  70. package/src/knowledge/names.ts +50 -0
  71. package/src/knowledge/program.ts +11 -10
  72. package/src/knowledge/read.ts +16 -21
  73. package/src/knowledge/schema.ts +9 -3
  74. package/src/knowledge/text.ts +5 -5
  75. package/src/knowledge/write.ts +93 -137
  76. package/src/mcp/inputs.ts +105 -0
  77. package/src/mcp/output.ts +2 -13
  78. package/src/mcp/server.ts +12 -60
  79. package/src/mcp/tools/debug.ts +17 -12
  80. package/src/mcp/tools/execution.ts +5 -32
  81. package/src/mcp/tools/input.ts +7 -32
  82. package/src/mcp/tools/media.ts +6 -5
  83. package/src/mcp/tools/memory.ts +11 -38
  84. package/src/mcp/tools/video.ts +6 -5
  85. package/src/native/c64run.ts +162 -0
  86. package/src/native/discover.ts +8 -0
  87. package/src/native/dxa.ts +7 -6
  88. package/src/native/ghidra/analyze.ts +2 -1
  89. package/src/native/ghidra/index.ts +2 -1
  90. package/src/native/processes.ts +4 -3
  91. package/src/native/status.ts +25 -4
  92. package/src/native/types.ts +3 -7
  93. package/src/opcodes.ts +13 -13
  94. package/src/platform.ts +9 -9
  95. package/src/project.ts +16 -26
  96. package/src/protocol/messages.ts +3 -21
  97. package/src/protocol/vice.ts +7 -7
  98. package/src/script.ts +491 -49
  99. package/skills/c64-assembler/scripts/assemble.ts +0 -138
  100. package/skills/c64-docs/scripts/generate.ts +0 -109
  101. package/skills/c64-docs/scripts/link.ts +0 -239
  102. package/skills/c64-memory-map/scripts/memmap.ts +0 -92
  103. package/skills/c64-testing/scripts/checklist.ts +0 -51
  104. package/skills/c64-testing/scripts/test.ts +0 -65
@@ -2,119 +2,145 @@
2
2
  // Host Runtime and extracts files into the project. T64 tape files and CRT
3
3
  // cartridge files are read locally: no host tool reads them.
4
4
 
5
- import { mkdirSync, writeFileSync } from "node:fs";
6
- import { dirname } from "node:path";
7
- import { parseArgs } from "node:util";
8
-
9
5
  import { formatC64Address } from "#src/c64.ts";
6
+ import { isRefusal, Refusal } from "#src/errors.ts";
10
7
  import { inspectDisk } from "#src/host-client/tools.ts";
11
- import { readProjectFile, writeProjectFile } from "#src/host-client/transfer.ts";
12
- import { resolveProjectPath } from "#src/project.ts";
13
- import { runScript, UsageError } from "#src/script.ts";
14
- import { ContainerError, parseCrt, parseT64, tapeFile } from "./lib/containers.ts";
15
-
16
- const USAGE = `disk.ts <action> <image> [name] [--out <file>]
17
-
18
- directory <image> disk name, id, free blocks and the files
19
- bam <image> free and used sectors of each track, and the
20
- used sectors that no file holds
21
- entry <image> <name> the directory entry of one file
22
- chain <image> <name> the track and sector of each block of one file
23
- read <image> <name> --out <file> write the bytes of one file into the project
24
- crt <file.crt> --out <dir> write each CHIP packet of a cartridge as a PRG
8
+ import { readProjectFile, writeReported } from "#src/host-client/transfer.ts";
9
+ import { OPTIONS, runScript, type Call, type SubjectSpec } from "#src/script.ts";
10
+ import { parseCrt, parseT64, tapeFile } from "./lib/containers.ts";
25
11
 
26
- <image> is a .d64, .d71, .d81 or .g64 file, relative to the project folder.
27
- directory and read also take a .t64 tape file, and directory a .crt cartridge
28
- file; the script reads those itself. A CRT can have hardware type 0 (normal
29
- 8K, 16K or Ultimax), 5 (Ocean), 19 (Magic Desk) or 32 (EasyFlash).
30
- <name> is the file name as the directory shows it. Write a byte that has no
31
- character as {$xx}, for example {$c1}. The result is one JSON object.`;
12
+ const IMAGE: SubjectSpec = { name: "<image>", help: "a .d64, .d71, .d81 or .g64 disk image, relative to the project folder" };
13
+ const NAME: SubjectSpec = { name: "<name>", help: "the file name as the directory shows it. Write a byte that has no character as {$xx}, for example {$c1}" };
32
14
 
33
- const ACTIONS = ["directory", "bam", "entry", "chain", "read", "crt"] as const;
34
- type Action = (typeof ACTIONS)[number];
15
+ type Kind = "disk" | "t64" | "crt";
16
+ const kindOf = (image: string): Kind => (image.toLowerCase().endsWith(".t64") ? "t64" : image.toLowerCase().endsWith(".crt") ? "crt" : "disk");
35
17
 
36
- async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }> {
37
- const { values, positionals } = parseArgs({ args: argv, strict: true, allowPositionals: true, options: { out: { type: "string" }, help: { type: "boolean" } } });
38
- if (values.help) throw new UsageError("");
39
- const [action, image, name, ...extra] = positionals;
40
- if (!ACTIONS.includes(action as Action)) throw new UsageError(`the action must be one of ${ACTIONS.join(", ")}`);
41
- if (image === undefined) throw new UsageError("the disk image is necessary");
42
- const named = action === "entry" || action === "chain" || action === "read";
43
- if (named && name === undefined) throw new UsageError(`${action} needs a file name`);
44
- if ((!named && name !== undefined) || extra.length > 0) throw new UsageError("too many arguments");
45
- if ((action === "read" || action === "crt") && values.out === undefined) throw new UsageError(`${action} needs --out`);
46
- if (action !== "read" && action !== "crt" && values.out !== undefined) throw new UsageError("only read and crt take --out");
47
- let outPath: string | undefined;
48
- if (values.out !== undefined) {
49
- try {
50
- outPath = resolveProjectPath(values.out);
51
- } catch {
52
- throw new UsageError(`--out must be a path relative to the project folder that stays inside it, not ${values.out}`);
53
- }
18
+ /** Runs `body` and names the file in a refusal of its content. */
19
+ function about<T>(image: string, body: () => T): T {
20
+ try {
21
+ return body();
22
+ } catch (error) {
23
+ if (isRefusal(error) && error.code === "invalid-input") throw new Refusal(error.code, `${image}: ${error.message}`);
24
+ throw error;
54
25
  }
26
+ }
55
27
 
56
- const kind = image.toLowerCase().endsWith(".t64") ? "t64" : image.toLowerCase().endsWith(".crt") ? "crt" : "disk";
57
- if (action === "crt" && kind !== "crt") throw new UsageError("crt needs a .crt file");
58
- if (kind !== "disk") {
59
- try {
60
- return local(kind, action as Action, image, name, values.out);
61
- } catch (error) {
62
- if (error instanceof ContainerError) throw new UsageError(`${image}: ${error.message}`);
63
- throw error;
64
- }
65
- }
66
- const result = await inspectDisk({ image, action: action as Exclude<Action, "crt">, ...(name === undefined ? {} : { name }) });
67
- if ("found" in result && !result.found) return { output: { found: false }, failed: true };
68
- if (result.action === "read" && result.found) {
69
- mkdirSync(dirname(outPath!), { recursive: true });
70
- writeFileSync(outPath!, result.data!);
71
- return { output: { found: true, name: result.name, bytes: result.bytes, output: values.out }, failed: false };
72
- }
28
+ /** A file that the image does not hold. */
29
+ const noFile = (image: string, name: string) => new Refusal("not-found", `${image} has no file ${name}. Give the name as directory shows it.`);
30
+
31
+ /** directory, bam, entry and chain of a disk image through c1541. */
32
+ async function inspect(call: Call, action: "directory" | "bam" | "entry" | "chain"): Promise<unknown> {
33
+ const image = call.subject(0);
34
+ const name = call.subjects[1];
35
+ const result = await inspectDisk({ image, action, ...(name === undefined ? {} : { name }) });
36
+ if ("found" in result && !result.found) throw noFile(image, name!);
73
37
  const { action: _action, ...output } = result;
74
- return { output, failed: false };
38
+ return output;
75
39
  }
76
40
 
77
- /** A tape or cartridge file, read here. */
78
- function local(kind: "t64" | "crt", action: Action, image: string, name: string | undefined, out: string | undefined): { output: unknown; failed: boolean } {
79
- const { bytes } = readProjectFile(image);
80
- if (kind === "t64") {
81
- if (action !== "directory" && action !== "read") throw new UsageError(`a T64 file has only directory and read, not ${action}`);
82
- const tape = parseT64(bytes);
83
- const shown = (entry: (typeof tape.entries)[number]) => ({
84
- name: entry.name,
85
- start: formatC64Address(entry.start),
86
- end: formatC64Address(entry.end),
87
- bytes: entry.bytes,
88
- ...(entry.fixedSize ? { fixedSize: true } : {}),
89
- });
90
- if (action === "directory") return { output: { name: tape.name, entries: tape.entries.map(shown) }, failed: false };
91
- const entry = tape.entries.find((candidate) => candidate.name.toLowerCase() === name!.toLowerCase());
92
- if (entry === undefined) return { output: { found: false }, failed: true };
93
- const file = tapeFile(bytes, entry);
94
- writeProjectFile(out!, file, { what: "--out" });
95
- return { output: { found: true, name: entry.name, bytes: file.length, output: out }, failed: false };
96
- }
97
- if (action !== "directory" && action !== "crt") throw new UsageError(`a CRT file has only directory and crt, not ${action}`);
98
- const crt = parseCrt(bytes);
99
- const chips = crt.chips.map((chip) => ({
100
- bank: chip.bank,
101
- address: formatC64Address(chip.address),
102
- size: chip.size,
103
- space: `bank${chip.bank}`,
104
- file: `bank${chip.bank}-${chip.address.toString(16).padStart(4, "0")}.prg`,
105
- }));
106
- const output = { name: crt.name, type: crt.type, typeName: crt.typeName, exrom: crt.exrom, game: crt.game, ...(crt.mode === undefined ? {} : { mode: crt.mode }), chips };
107
- if (action === "directory") return { output: { ...output, chips: chips.map(({ file: _file, ...chip }) => chip) }, failed: false };
108
- crt.chips.forEach((chip, index) => writeProjectFile(`${out}/${chips[index]!.file}`, Buffer.concat([Buffer.from([chip.address & 0xff, chip.address >> 8]), chip.data]), { what: "--out" }));
109
- writeProjectFile(`${out}/crt.json`, Buffer.from(`${JSON.stringify(output, null, 2)}\n`), { what: "--out" });
110
- return { output: { ...output, out }, failed: false };
41
+ /** The directory of a T64 tape file. */
42
+ function tapeDirectory(image: string) {
43
+ const tape = about(image, () => parseT64(readProjectFile(image).bytes));
44
+ return {
45
+ name: tape.name,
46
+ entries: tape.entries.map((entry) => ({ name: entry.name, start: formatC64Address(entry.start), end: formatC64Address(entry.end), bytes: entry.bytes, ...(entry.fixedSize ? { fixedSize: true } : {}) })),
47
+ };
111
48
  }
112
49
 
113
- await runScript(
114
- async (argv) => {
115
- const { output, failed } = await run(argv);
116
- if (failed) process.exitCode = 1;
117
- return output;
118
- },
119
- { usage: USAGE },
120
- );
50
+ /** A CRT cartridge file: its header and CHIP packets, each with the PRG file name that crt writes. */
51
+ function cartridge(image: string) {
52
+ const crt = about(image, () => parseCrt(readProjectFile(image).bytes));
53
+ const chips = crt.chips.map((chip) => ({ bank: chip.bank, address: formatC64Address(chip.address), size: chip.size, space: `bank${chip.bank}`, file: `bank${chip.bank}-${chip.address.toString(16).padStart(4, "0")}.prg` }));
54
+ return { crt, header: { name: crt.name, type: crt.type, typeName: crt.typeName, exrom: crt.exrom, game: crt.game, ...(crt.mode === undefined ? {} : { mode: crt.mode }) }, chips };
55
+ }
56
+
57
+ /** A command that takes only a disk image. */
58
+ const diskOnly = (call: Call, action: string) => {
59
+ if (kindOf(call.subject(0)) !== "disk") throw new Refusal("invalid-input", `${action} needs a .d64, .d71, .d81 or .g64 disk image, not ${call.subject(0)}`);
60
+ };
61
+
62
+ await runScript({
63
+ name: "disk",
64
+ summary: "Inspects a disk image with c1541 and extracts files into the project. T64 tape files and CRT cartridge files are read here. A CRT can have hardware type 0 (normal 8K, 16K or Ultimax), 5 (Ocean), 19 (Magic Desk) or 32 (EasyFlash).",
65
+ commands: [
66
+ {
67
+ name: "directory",
68
+ summary: "The disk name, the id, the free blocks and the files. It also reads a .t64 tape file and a .crt cartridge file.",
69
+ subjects: [{ ...IMAGE, help: "a disk image, a .t64 tape file or a .crt cartridge file, relative to the project folder" }],
70
+ run: async (call) => {
71
+ const image = call.subject(0);
72
+ const kind = kindOf(image);
73
+ if (kind === "t64") return tapeDirectory(image);
74
+ if (kind === "crt") {
75
+ const { header, chips } = cartridge(image);
76
+ return { ...header, chips: chips.map(({ file: _file, ...chip }) => chip) };
77
+ }
78
+ return inspect(call, "directory");
79
+ },
80
+ },
81
+ {
82
+ name: "bam",
83
+ summary: "The free and used sectors of each track, and the used sectors that no file holds.",
84
+ subjects: [IMAGE],
85
+ run: (call) => {
86
+ diskOnly(call, "bam");
87
+ return inspect(call, "bam");
88
+ },
89
+ },
90
+ {
91
+ name: "entry",
92
+ summary: "The directory entry of one file.",
93
+ subjects: [IMAGE, NAME],
94
+ run: (call) => {
95
+ diskOnly(call, "entry");
96
+ return inspect(call, "entry");
97
+ },
98
+ },
99
+ {
100
+ name: "chain",
101
+ summary: "The track and the sector of each block of one file.",
102
+ subjects: [IMAGE, NAME],
103
+ run: (call) => {
104
+ diskOnly(call, "chain");
105
+ return inspect(call, "chain");
106
+ },
107
+ },
108
+ {
109
+ name: "read",
110
+ summary: "Writes the bytes of one file into the project. It also reads a .t64 tape file.",
111
+ subjects: [{ ...IMAGE, help: "a disk image or a .t64 tape file, relative to the project folder" }, NAME],
112
+ options: { out: { ...OPTIONS.out, value: "<file>", help: "the file to write, relative to the project folder", required: true } },
113
+ run: async (call) => {
114
+ const [image, name] = [call.subject(0), call.subject(1)];
115
+ const out = call.string("out")!;
116
+ const kind = kindOf(image);
117
+ if (kind === "crt") throw new Refusal("invalid-input", `read takes a disk image or a .t64 tape file. Give crt for ${image}`);
118
+ if (kind === "t64") {
119
+ const { bytes } = readProjectFile(image);
120
+ const tape = about(image, () => parseT64(bytes));
121
+ const entry = tape.entries.find((candidate) => candidate.name.toLowerCase() === name.toLowerCase());
122
+ if (entry === undefined) throw noFile(image, name);
123
+ return { name: entry.name, ...writeReported(out, tapeFile(bytes, entry), { what: "--out" }) };
124
+ }
125
+ const result = await inspectDisk({ image, action: "read", name });
126
+ if (result.action !== "read" || !result.found) throw noFile(image, name);
127
+ return { name: result.name, ...writeReported(out, result.data!, { what: "--out" }) };
128
+ },
129
+ },
130
+ {
131
+ name: "crt",
132
+ summary: "Writes each CHIP packet of a cartridge as a PRG, and crt.json with the header and the packets.",
133
+ subjects: [{ name: "<file.crt>", help: "a .crt cartridge file, relative to the project folder" }],
134
+ options: { out: { ...OPTIONS.out, value: "<dir>", help: "the folder for the files, relative to the project folder", required: true } },
135
+ run: (call) => {
136
+ const image = call.subject(0);
137
+ if (kindOf(image) !== "crt") throw new Refusal("invalid-input", `crt needs a .crt file, not ${image}`);
138
+ const out = call.string("out")!;
139
+ const { crt, header, chips } = cartridge(image);
140
+ const files = crt.chips.map((chip, index) => writeReported(`${out}/${chips[index]!.file}`, Buffer.concat([Buffer.from([chip.address & 0xff, chip.address >> 8]), chip.data]), { what: "--out" }));
141
+ files.push(writeReported(`${out}/crt.json`, `${JSON.stringify({ ...header, chips }, null, 2)}\n`, { what: "--out" }));
142
+ return { ...header, chips, files };
143
+ },
144
+ },
145
+ ],
146
+ });
@@ -4,9 +4,7 @@
4
4
  // raw pulses and go through the emulator only.
5
5
 
6
6
  import { formatC64Address, petsciiNameToText } from "#src/c64.ts";
7
-
8
- /** A refusal of a container file: the caller turns it into a usage error. */
9
- export class ContainerError extends Error {}
7
+ import { Refusal } from "#src/errors.ts";
10
8
 
11
9
  /** The CRT hardware types this toolkit reads, by type number. */
12
10
  export const CRT_TYPES: Record<number, string> = { 0: "normal", 5: "Ocean", 19: "Magic Desk", 32: "EasyFlash" };
@@ -36,32 +34,32 @@ const SIGNATURE = "C64 CARTRIDGE ";
36
34
 
37
35
  export function parseCrt(bytes: Uint8Array): Crt {
38
36
  const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
39
- if (buffer.length < 0x40 || buffer.toString("latin1", 0, 16) !== SIGNATURE) throw new ContainerError("the file is not a CRT: it does not start with C64 CARTRIDGE");
37
+ if (buffer.length < 0x40 || buffer.toString("latin1", 0, 16) !== SIGNATURE) throw new Refusal("invalid-input", "the file is not a CRT: it does not start with C64 CARTRIDGE");
40
38
  const headerLength = buffer.readUInt32BE(0x10);
41
39
  const type = buffer.readUInt16BE(0x16);
42
40
  const exrom = buffer[0x18]!;
43
41
  const game = buffer[0x19]!;
44
42
  const typeName = CRT_TYPES[type];
45
43
  if (typeName === undefined) {
46
- throw new ContainerError(`the CRT has hardware type ${type}; this toolkit reads types ${Object.entries(CRT_TYPES).map(([number, name]) => `${number} (${name})`).join(", ")}`);
44
+ throw new Refusal("invalid-input", `the CRT has hardware type ${type}; this toolkit reads types ${Object.entries(CRT_TYPES).map(([number, name]) => `${number} (${name})`).join(", ")}`);
47
45
  }
48
46
  const name = buffer.toString("latin1", 0x20, 0x40).replace(/\0+$/, "");
49
47
  const chips: CrtChip[] = [];
50
48
  let offset = Math.max(headerLength, 0x40);
51
49
  while (offset + 0x10 <= buffer.length) {
52
- if (buffer.toString("latin1", offset, offset + 4) !== "CHIP") throw new ContainerError(`the CRT has no CHIP packet at offset ${offset}`);
50
+ if (buffer.toString("latin1", offset, offset + 4) !== "CHIP") throw new Refusal("invalid-input", `the CRT has no CHIP packet at offset ${offset}`);
53
51
  const packetLength = buffer.readUInt32BE(offset + 4);
54
52
  const chipType = buffer.readUInt16BE(offset + 8);
55
53
  const bank = buffer.readUInt16BE(offset + 10);
56
54
  const address = buffer.readUInt16BE(offset + 12);
57
55
  const size = buffer.readUInt16BE(offset + 14);
58
- if (packetLength < 0x10 || offset + packetLength > buffer.length) throw new ContainerError(`the CHIP packet at offset ${offset} runs past the end of the file`);
59
- if (bank > 255) throw new ContainerError(`the CHIP packet at offset ${offset} has bank ${bank}; banks go up to 255`);
60
- if (size > packetLength - 0x10 || address + size > 0x10000) throw new ContainerError(`the CHIP packet at offset ${offset} has a wrong size ${size} at ${formatC64Address(address)}`);
56
+ if (packetLength < 0x10 || offset + packetLength > buffer.length) throw new Refusal("invalid-input", `the CHIP packet at offset ${offset} runs past the end of the file`);
57
+ if (bank > 255) throw new Refusal("invalid-input", `the CHIP packet at offset ${offset} has bank ${bank}; banks go up to 255`);
58
+ if (size > packetLength - 0x10 || address + size > 0x10000) throw new Refusal("invalid-input", `the CHIP packet at offset ${offset} has a wrong size ${size} at ${formatC64Address(address)}`);
61
59
  if (size > 0) chips.push({ bank, address, size, chipType, data: buffer.subarray(offset + 0x10, offset + 0x10 + size) });
62
60
  offset += packetLength;
63
61
  }
64
- if (chips.length === 0) throw new ContainerError("the CRT has no CHIP packet with data");
62
+ if (chips.length === 0) throw new Refusal("invalid-input", "the CRT has no CHIP packet with data");
65
63
  const crt: Crt = { name, type, typeName, exrom, game, chips };
66
64
  if (type === 0) crt.mode = exrom === 0 && game === 1 ? "8k" : exrom === 0 && game === 0 ? "16k" : "ultimax";
67
65
  return crt;
@@ -87,7 +85,7 @@ export interface Tape {
87
85
 
88
86
  export function parseT64(bytes: Uint8Array): Tape {
89
87
  const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
90
- if (buffer.length < 0x40 || !buffer.toString("latin1", 0, 32).startsWith("C64")) throw new ContainerError("the file is not a T64: it does not start with C64");
88
+ if (buffer.length < 0x40 || !buffer.toString("latin1", 0, 32).startsWith("C64")) throw new Refusal("invalid-input", "the file is not a T64: it does not start with C64");
91
89
  const maxEntries = buffer.readUInt16LE(0x22);
92
90
  const name = buffer.toString("latin1", 0x28, 0x40).replace(/[\0 ]+$/, "");
93
91
  const raw: Array<Omit<TapeEntry, "bytes" | "end"> & { end: number }> = [];
@@ -109,7 +107,7 @@ export function parseT64(bytes: Uint8Array): Tape {
109
107
  // Many T64 files have a wrong end address: the next file or the end of the file bounds each one.
110
108
  const offsets = [...raw.map((entry) => entry.offset), buffer.length].sort((a, b) => a - b);
111
109
  const entries = raw.map((entry): TapeEntry => {
112
- if (entry.offset >= buffer.length) throw new ContainerError(`the T64 entry ${entry.name} starts past the end of the file`);
110
+ if (entry.offset >= buffer.length) throw new Refusal("invalid-input", `the T64 entry ${entry.name} starts past the end of the file`);
113
111
  const available = offsets.find((offset) => offset > entry.offset)! - entry.offset;
114
112
  const stated = entry.end - entry.start;
115
113
  const bytes = stated > 0 && stated <= available ? stated : Math.min(available, 0x10000 - entry.start);
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: c64-docs
3
- description: Use this skill to write the documentation of a C64 program. The documentation explains the flows, state machines, tables, data formats and graphics, with links to the source lines and to the assets. A script generates the reference pages from the knowledge and the source. You write the other pages by the method of this skill. A second script adds the links to each name and address and checks every link. Use it after the source is identical, or to keep the documentation correct after a change to the knowledge.
3
+ description: Use this skill to write the documentation of a C64 program. The documentation explains the flows, state machines, tables, data formats and graphics, with links to the source lines and to the assets. The script generates the reference pages from the knowledge and the source. You write the other pages by the method of this skill. A second command of the script adds the links to each name and address and checks every link. Use it after the source is identical, or to keep the documentation correct after a change to the knowledge.
4
4
  ---
5
5
 
6
6
  # c64-docs
@@ -9,10 +9,10 @@ description: Use this skill to write the documentation of a C64 program. The doc
9
9
 
10
10
  Write documentation that tells how a program works, and that points to the code. The documentation is Markdown in the project. The entry page is `README.md` in the project folder. The other pages and the reference are in `docs/`. The documentation has two parts:
11
11
 
12
- - **The reference.** `generate.ts` generates it from the knowledge, the source map of c64-source and the image. It has a page for each module. The page gives the routines, their callers and callees, the data and a call graph. It also has pages for the names, the variables, the tables, the memory map and the graphics. Do not edit these pages. The next run writes over them.
12
+ - **The reference.** `docs.ts generate` generates it from the knowledge, the source map of c64-source and the image. It has a page for each module. The page gives the routines, their callers and callees, the data and a call graph. It also has pages for the names, the variables, the tables, the memory map and the graphics. Do not edit these pages. The next run writes over them.
13
13
  - **The pages that you write.** They tell how the program works. They give the flows, the state machines, the data formats and the task of each part. Use the page types in `references/page-types.md` and the rules in `references/style.md`.
14
14
 
15
- `link.ts` connects the two parts. In each page that you write, a name in backticks gets a link to its line in the source. The address after the name gets a link to the entry of the name in the reference. When the source changes, `link.ts` moves the links to the new lines.
15
+ `docs.ts link` connects the two parts. In each page that you write, a name in backticks gets a link to its line in the source. The address after the name gets a link to the entry of the name in the reference. When the source changes, `docs.ts link` moves the links to the new lines.
16
16
 
17
17
  This skill does not change the knowledge.
18
18
 
@@ -24,44 +24,62 @@ This skill does not change the knowledge.
24
24
 
25
25
  ## Tools / execution path
26
26
 
27
- The scripts run on this machine. They do not use the emulator or the host runtime.
27
+ The script runs on this machine. It does not use the emulator or the host runtime.
28
28
 
29
29
  ```
30
- node <skill>/scripts/generate.ts --source src --out docs/reference
31
- node <skill>/scripts/link.ts README.md docs --source src
32
- node <skill>/scripts/link.ts README.md docs --source src --check
30
+ node <skill>/scripts/docs.ts generate src --out docs/reference
31
+ node <skill>/scripts/docs.ts link README.md docs --source src
32
+ node <skill>/scripts/docs.ts link README.md docs --source src --check
33
33
  ```
34
34
 
35
35
  | Option | Meaning |
36
36
  |---|---|
37
- | `--source src` | The source directory of c64-source. Both scripts read `src/source-map.json`. |
38
- | `--out docs/reference` | The directory of the reference. `generate.ts` writes only there. It refuses to start when the directory holds a page that it did not write. |
39
- | `README.md docs` | The pages of the documentation: `.md` files and directories. `link.ts` reads each file and every `.md` file below each directory. It changes only the pages that you wrote. It checks the pages of the reference. |
40
- | `--reference <dir>` | The directory of the reference for `link.ts`. The default is the `reference` directory of the first directory that has one, for example `docs/reference`. |
41
- | `--check` | `link.ts` writes nothing. It fails when a link is not up to date or a page has a problem. |
37
+ | `src` | `docs.ts generate`: the source directory of c64-source. The command reads `src/source-map.json`. |
38
+ | `--source src` | `docs.ts link`: the source directory of c64-source. The command reads `src/source-map.json`. |
39
+ | `--out docs/reference` | The directory of the reference. `docs.ts generate` writes only there. It refuses to start when the directory holds a page that it did not write. |
40
+ | `README.md docs` | The pages of the documentation: `.md` files and directories. `docs.ts link` reads each file and every `.md` file below each directory. It changes only the pages that you wrote. It checks the pages of the reference. |
41
+ | `--reference <dir>` | The directory of the reference for `docs.ts link`. The default is the `reference` directory of the first directory that has one, for example `docs/reference`. |
42
+ | `--check` | `docs.ts link` writes nothing. `result` is `FAIL` when a link is not up to date or a page has a problem. |
43
+ | `--max-listed 20` | List at most this number of items in each list. The default is 50. |
44
+
45
+ ### Calling the script
46
+
47
+ - Call a script as `node <skill>/scripts/<script>.ts <command> [subjects] [options]`. The command comes first.
48
+ - `<script>.ts --help` lists the commands. `<script>.ts <command> --help` gives the subjects and the options of one command. The help is JSON: `{"text": "..."}`.
49
+ - An address is `$` and 1 to 4 hex digits, for example `'$c000'` or `'$fb'`, or a name. A name is a symbol, else an analyzer name that only one analyzer gives. Quote an address in a shell.
50
+ - A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
51
+ - Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
52
+ - The result is one JSON object on one line. A file that the script writes shows as `{"out", "bytes", "sha256"}`. A list shows as `{"count", "listed"}`, and `rest` tells where to get the rest of a long list.
53
+ - An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
54
+ - The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
42
55
 
43
56
  ### Project folder
44
57
 
45
58
  - Paths are relative to the project folder. The project folder is the nearest folder at or above the working directory that holds `.c64-re-tools/`.
46
- - Give `--project <folder>` to each script when the working directory is not in the project.
59
+ - If the working directory is not in a project and has projects below it, the script refuses and lists them. Then give `--project <folder>` to each script.
60
+ - Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
61
+ - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence test:<file>` or `--evidence file:<file>` of c64-knowledge. These two refuse a file in `.c64-re-tools/local/`.
62
+ - Do not move a coverage map after `coverage` of c64-knowledge imported it. `--evidence coverage:<map>` names the map by its path at the import. The knowledge keeps the coverage and the SHA-256 of the map, so the evidence stays valid.
63
+ - Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
64
+ - A shell redirect (`>`) is relative to the working directory, not to the project folder. From the folder above the project, write `<folder>/.c64-re-tools/local/result.json`.
47
65
 
48
66
  ## Workflow
49
67
 
50
68
  1. Read the knowledge with the c64-knowledge skill. The documentation can only be as good as the knowledge. Each routine needs a name and a comment. Each table needs a region and a name. Each file of the source needs a module with a title. Record what is missing with c64-knowledge.
51
69
  2. Generate the source with c64-source. Make sure that `check.identical` is `true`. The script also writes `source-map.json`.
52
- 3. Run `generate.ts`. Read `index.md` of the reference. A module with no routines, or a routine with no comment, shows what the knowledge does not have yet.
70
+ 3. Run `docs.ts generate`. Read `index.md` of the reference. A module with no routines, or a routine with no comment, shows what the knowledge does not have yet.
53
71
  4. Make the plan of the pages. Start from the modules and the call graphs. Find the main loop, the interrupt chain, the dispatch tables, the state machines and the data formats. Each subsystem gets one page in `docs/`. The entry page `README.md` in the project folder lists the pages. Use the page types in `references/page-types.md`.
54
72
  5. Write each page. Read the code and the knowledge for each fact. Run the program with c64-testing or c64-emulator when a fact needs a run. Write each name with its address, for example `` `draw_room` `$09BC` ``. Mark each fact with its evidence. Several subagents can write the pages at the same time, one page each. Give each subagent `references/page-types.md` and `references/style.md`.
55
73
  6. Record each new fact in the knowledge first: a name, a comment, a region or a module. Then the source and the reference show it too. A fact only in a page is lost at the next change of the knowledge.
56
- 7. Run `link.ts`. Read `problems` and correct each one. Then run `link.ts --check`. Make sure that `problems.count` is 0 and that `notUpToDate.links` is 0.
57
- 8. When the project folder is in a workspace with other projects, find `README.md` at the root of the workspace. This page lists each program of the workspace. Add one row for the program, with a link to `<project>/README.md` and one sentence. If the page does not exist, make it. Use the workspace index in `references/page-types.md`. `link.ts` does not read this page, because the page is not in the project folder.
74
+ 7. Run `docs.ts link`. Read `problems` and correct each one. Then run `docs.ts link --check`. Make sure that `result` is `PASS`.
75
+ 8. When the project folder is in a workspace with other projects, find `README.md` at the root of the workspace. This page lists each program of the workspace. Add one row for the program, with a link to `<project>/README.md` and one sentence. If the page does not exist, make it. Use the workspace index in `references/page-types.md`. `docs.ts link` does not read this page, because the page is not in the project folder.
58
76
  9. After each change to the knowledge, do steps 2, 3 and 7 again. The links then go to the new lines.
59
77
 
60
78
  ## The links
61
79
 
62
- `link.ts` changes these forms in the pages that you write. It does not change a code block, a mermaid diagram or an HTML comment. The examples are for a page in `docs/`. In `README.md`, the links start with `src/` and `docs/reference/`.
80
+ `docs.ts link` changes these forms in the pages that you write. It does not change a code block, a mermaid diagram or an HTML comment. The examples are for a page in `docs/`. In `README.md`, the links start with `src/` and `docs/reference/`.
63
81
 
64
- | You write | `link.ts` writes |
82
+ | You write | `docs.ts link` writes |
65
83
  |---|---|
66
84
  | `` `draw_room` `$09BC` `` | `` [`draw_room`](../src/room_draw.asm#L6) [`$09BC`](reference/modules/room_draw.md#draw_room) `` |
67
85
  | `` `draw_room` (`$09BC`) `` | the same links, in the brackets |
@@ -71,8 +89,8 @@ node <skill>/scripts/link.ts README.md docs --source src --check
71
89
 
72
90
  - A name is a name of the knowledge or of the source. A word in backticks that is no name stays as it is.
73
91
  - In a list such as `` `a`/`b` `$1000`/`$1010` ``, each name gets its link. The addresses stay as they are.
74
- - `link.ts` writes a link again when its line moves. A link that you made to another target stays as you wrote it.
75
- - `link.ts` checks every link and image: the file must exist, a `#anchor` must exist in the page, and a `#L` line must exist in the file.
92
+ - `docs.ts link` writes a link again when its line moves. A link that you made to another target stays as you wrote it.
93
+ - `docs.ts link` checks every link and image: the file must exist, a `#anchor` must exist in the page, and a `#L` line must exist in the file.
76
94
 
77
95
  ## The reference
78
96
 
@@ -105,25 +123,26 @@ Do not store:
105
123
 
106
124
  ## Result
107
125
 
108
- `generate.ts` gives one JSON object:
126
+ `docs.ts generate`:
109
127
 
110
128
  - `out`, `index` and `revision`.
111
129
  - `pages`, `entries`, `tables`, `images` and `edges`: the counts of the reference.
130
+ - `files`: the pages that it wrote, each with `out`, `bytes` and `sha256`.
112
131
  - `program`: `the knowledge`, or a note when the knowledge has no program. Without the program, the tables are not decoded and the graphics page has no images.
113
132
 
114
- `link.ts` gives one JSON object:
133
+ `docs.ts link`:
115
134
 
135
+ - `result`: `PASS` when no page has a problem. `FAIL` when a page has a problem, or with `--check` when a link is not up to date.
116
136
  - `pages`: the count of the pages that it read.
117
- - `linked`: the count of the links that it wrote, and the pages that it changed. With `--check`, `notUpToDate` gives the links that are not up to date.
118
- - `problems`: `count`, `byKind` and the first 50 problems. Each problem has `at` (the page and the line), `kind` and `message`.
119
-
120
- The exit status is 0 when there is no problem. The exit status is 1 when there is a problem, or with `--check` when a link is not up to date. The exit status is 2 for a wrong argument.
137
+ - `reference`: the directory of the reference, or a note when there is no reference.
138
+ - `linked`: the count of the links that it wrote (`links`), and the pages that it changed. With `--check`, `notUpToDate` gives the links that are not up to date.
139
+ - `problems`: `byKind` and the list of problems. Each problem has `at` (the page and the line), `kind` and `message`.
121
140
 
122
141
  ## Failure and conflicts
123
142
 
124
143
  - `not-found` for `source-map.json`: generate the source with c64-source. Then run the script again.
125
- - `invalid-input` that names an old revision: the knowledge changed after the source. Generate the source again, then run `generate.ts`.
126
- - `invalid-input` that names a page in the reference directory: the page is not a page of `generate.ts`. Move the page out of the directory. Do not give `--out` a directory with pages that you wrote.
144
+ - `invalid-input` that names an old revision: the knowledge changed after the source. Generate the source again, then run `docs.ts generate`.
145
+ - `invalid-input` that names a page in the reference directory: the page is not a page of `docs.ts generate`. Move the page out of the directory. Do not give `--out` a directory with pages that you wrote.
127
146
  - `unknown-name`: the page uses a name that is not in the knowledge. Record the name with c64-knowledge, or correct the page.
128
147
  - `wrong-address`: the address after the name is not the address of the name, and it is not a place inside it. The message gives the routine that holds the address. Correct the name or the address. For a value, write `` `scroll_moves` = `$10` ``.
129
148
  - `missing-file`, `missing-anchor` or `missing-line`: a link goes to nothing. Correct the link. An image of an asset must exist in the project.
@@ -1,14 +1,14 @@
1
1
  # Style of the pages
2
2
 
3
- These rules apply to each page that you write. `link.ts` checks the rules that a script can check.
3
+ These rules apply to each page that you write. `docs.ts link` checks the rules that a script can check.
4
4
 
5
5
  ## Names and addresses
6
6
 
7
- - Write each routine, table and variable with its name and its address: `` `draw_room` `$09BC` ``. `link.ts` gives the name a link to its line, and the address a link to its entry in the reference.
7
+ - Write each routine, table and variable with its name and its address: `` `draw_room` `$09BC` ``. `docs.ts link` gives the name a link to its line, and the address a link to its entry in the reference.
8
8
  - Use the name of the knowledge. Do not make a name that is only in the page. When a part has no good name, record a name with c64-knowledge first.
9
9
  - For a place inside a routine, write the name of the routine and the address of the place: `` `draw_room` `$0A04` ``. The address gets a link to its line.
10
10
  - For a byte inside a table or a variable, write the name with an offset: `` `room_script_vars+3` `$4E9A` ``.
11
- - For a value, write an equals sign: `` `scroll_moves` = `$10` ``. Without the sign, `link.ts` reads `$10` as an address.
11
+ - For a value, write an equals sign: `` `scroll_moves` = `$10` ``. Without the sign, `docs.ts link` reads `$10` as an address.
12
12
  - Write addresses with `$` and hex digits in upper case: `$09BC`. Write values in hex with `$`, or in decimal without `$`.
13
13
  - Write a range with a hyphen: `` `$4E97-$4E9B` ``.
14
14
  - Write the name and the address in backticks. A name without backticks gets no link.
@@ -27,7 +27,7 @@ These rules apply to each page that you write. `link.ts` checks the rules that a
27
27
  - Use mermaid for the diagrams: `flowchart` for calls and data flow, `stateDiagram-v2` for state machines. Write the name and the address in each node: `"draw_room $09BC"`.
28
28
  - A diagram gets no links. Give the links in a table or a list under the diagram.
29
29
  - Keep a diagram small: at most about 20 nodes. Make two diagrams before one diagram that a reader cannot read.
30
- - Embed an image only from the project. It can be an image of the reference (`reference/images/`) or a PNG that a skill made. It can also be an asset that a script of the project made from a format of the program, for example `../assets/rooms/room00.png`. `link.ts` checks that each image exists.
30
+ - Embed an image only from the project. It can be an image of the reference (`reference/images/`) or a PNG that a skill made. It can also be an asset that a script of the project made from a format of the program, for example `../assets/rooms/room00.png`. `docs.ts link` checks that each image exists.
31
31
  - Give each image one line of text that tells what it shows.
32
32
 
33
33
  ## Text
@@ -39,5 +39,5 @@ These rules apply to each page that you write. `link.ts` checks the rules that a
39
39
 
40
40
  ## Generated pages
41
41
 
42
- - Do not edit a page in the reference. Change the knowledge, generate the source, and run `generate.ts` again.
43
- - Do not put a page that you write in the reference directory. `generate.ts` refuses to write when one is there.
42
+ - Do not edit a page in the reference. Change the knowledge, generate the source, and run `docs.ts generate` again.
43
+ - Do not put a page that you write in the reference directory. `docs.ts generate` refuses to write when one is there.