@henols/c64-re-tools 2.1.0-rc.1 → 2.1.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 (103) hide show
  1. package/package.json +1 -1
  2. package/skills/c64-assembler/SKILL.md +24 -9
  3. package/skills/c64-assembler/scripts/assembler.ts +95 -0
  4. package/skills/c64-basic/SKILL.md +17 -9
  5. package/skills/c64-basic/scripts/basic.ts +26 -36
  6. package/skills/c64-disk/SKILL.md +17 -7
  7. package/skills/c64-disk/scripts/disk.ts +130 -104
  8. package/skills/c64-disk/scripts/lib/containers.ts +10 -12
  9. package/skills/c64-docs/SKILL.md +47 -28
  10. package/skills/c64-docs/references/style.md +6 -6
  11. package/skills/c64-docs/scripts/docs.ts +323 -0
  12. package/skills/c64-docs/scripts/lib/source-map.ts +1 -1
  13. package/skills/c64-emulator/SKILL.md +11 -7
  14. package/skills/c64-knowledge/SKILL.md +34 -21
  15. package/skills/c64-knowledge/scripts/knowledge.ts +597 -718
  16. package/skills/c64-knowledge/scripts/lib/listing.ts +5 -7
  17. package/skills/c64-memory-map/SKILL.md +18 -10
  18. package/skills/c64-memory-map/scripts/memory-map.ts +81 -0
  19. package/skills/c64-provenance/SKILL.md +33 -24
  20. package/skills/c64-provenance/scripts/lib/compare.ts +8 -12
  21. package/skills/c64-provenance/scripts/lib/leftovers.ts +19 -20
  22. package/skills/c64-provenance/scripts/lib/library.ts +15 -25
  23. package/skills/c64-provenance/scripts/lib/runtime.ts +12 -37
  24. package/skills/c64-provenance/scripts/lib/separate.ts +9 -10
  25. package/skills/c64-provenance/scripts/provenance.ts +177 -279
  26. package/skills/c64-reverse-engineering/SKILL.md +13 -12
  27. package/skills/c64-source/SKILL.md +28 -16
  28. package/skills/c64-source/scripts/lib/loader.ts +52 -77
  29. package/skills/c64-source/scripts/lib/source.ts +33 -42
  30. package/skills/c64-source/scripts/{generate.ts → source.ts} +68 -109
  31. package/skills/c64-static-analysis/SKILL.md +24 -13
  32. package/skills/c64-static-analysis/scripts/lib/coverage.ts +3 -4
  33. package/skills/c64-static-analysis/scripts/lib/findings.ts +2 -2
  34. package/skills/c64-static-analysis/scripts/lib/instructions.ts +1 -1
  35. package/skills/c64-static-analysis/scripts/lib/macros.ts +6 -8
  36. package/skills/c64-static-analysis/scripts/{analyze.ts → static-analysis.ts} +58 -118
  37. package/skills/c64-testing/SKILL.md +48 -32
  38. package/skills/c64-testing/references/recording-format.md +48 -19
  39. package/skills/c64-testing/references/scenario-format.md +24 -19
  40. package/skills/c64-testing/scripts/{diff.ts → lib/diff-files.ts} +18 -69
  41. package/skills/c64-testing/scripts/lib/diff.ts +5 -3
  42. package/skills/c64-testing/scripts/lib/play.ts +244 -54
  43. package/skills/c64-testing/scripts/lib/recording.ts +153 -120
  44. package/skills/c64-testing/scripts/{replay.ts → lib/replay.ts} +28 -86
  45. package/skills/c64-testing/scripts/{run.ts → lib/runs.ts} +36 -59
  46. package/skills/c64-testing/scripts/lib/scenario.ts +73 -81
  47. package/skills/c64-testing/scripts/testing.ts +189 -0
  48. package/skills/c64-unpacker/SKILL.md +42 -25
  49. package/skills/c64-unpacker/scripts/lib/evidence.ts +3 -4
  50. package/skills/c64-unpacker/scripts/lib/loads.ts +12 -10
  51. package/skills/c64-unpacker/scripts/lib/machine.ts +0 -2
  52. package/skills/c64-unpacker/scripts/lib/stages.ts +14 -19
  53. package/skills/c64-unpacker/scripts/{unpack.ts → unpacker.ts} +109 -211
  54. package/src/c64.ts +35 -20
  55. package/src/cli/main.ts +2 -2
  56. package/src/errors.ts +38 -0
  57. package/src/graphics.ts +3 -1
  58. package/src/host/server.ts +3 -2
  59. package/src/host/vice/session.ts +4 -4
  60. package/src/host-client/entry-state.ts +106 -0
  61. package/src/host-client/image.ts +21 -10
  62. package/src/host-client/steps.ts +285 -108
  63. package/src/host-client/transfer.ts +21 -15
  64. package/src/host-client/vice-session.ts +1 -1
  65. package/src/knowledge/database.ts +3 -23
  66. package/src/knowledge/expression.ts +8 -11
  67. package/src/knowledge/history.ts +2 -2
  68. package/src/knowledge/import.ts +13 -14
  69. package/src/knowledge/names.ts +50 -0
  70. package/src/knowledge/program.ts +11 -10
  71. package/src/knowledge/read.ts +16 -21
  72. package/src/knowledge/schema.ts +9 -3
  73. package/src/knowledge/text.ts +5 -5
  74. package/src/knowledge/write.ts +93 -137
  75. package/src/mcp/inputs.ts +105 -0
  76. package/src/mcp/output.ts +2 -13
  77. package/src/mcp/server.ts +12 -60
  78. package/src/mcp/tools/debug.ts +17 -12
  79. package/src/mcp/tools/execution.ts +5 -32
  80. package/src/mcp/tools/input.ts +7 -32
  81. package/src/mcp/tools/media.ts +6 -5
  82. package/src/mcp/tools/memory.ts +11 -38
  83. package/src/mcp/tools/video.ts +6 -5
  84. package/src/native/c64run.ts +1 -1
  85. package/src/native/dxa.ts +7 -6
  86. package/src/native/ghidra/analyze.ts +2 -1
  87. package/src/native/ghidra/index.ts +2 -1
  88. package/src/native/processes.ts +4 -3
  89. package/src/native/status.ts +2 -2
  90. package/src/native/types.ts +3 -7
  91. package/src/opcodes.ts +13 -13
  92. package/src/platform.ts +9 -9
  93. package/src/project.ts +16 -26
  94. package/src/protocol/messages.ts +3 -21
  95. package/src/protocol/vice.ts +7 -7
  96. package/src/script.ts +491 -49
  97. package/skills/c64-assembler/scripts/assemble.ts +0 -138
  98. package/skills/c64-docs/scripts/generate.ts +0 -109
  99. package/skills/c64-docs/scripts/link.ts +0 -239
  100. package/skills/c64-memory-map/scripts/memmap.ts +0 -92
  101. package/skills/c64-testing/scripts/checklist.ts +0 -51
  102. package/skills/c64-testing/scripts/record.ts +0 -96
  103. package/skills/c64-testing/scripts/test.ts +0 -65
@@ -3,7 +3,7 @@
3
3
  // the bytes from coverage files, and the references in and out. Nothing here
4
4
  // reads files: knowledge.ts gives the lookups.
5
5
 
6
- import { COVERAGE } from "#src/c64.ts";
6
+ import { COVERAGE, formatC64Address, formatC64Byte } from "#src/c64.ts";
7
7
  import { MODE_LENGTH, OPCODES, type Mode } from "#src/opcodes.ts";
8
8
 
9
9
  export interface ListingSource {
@@ -41,8 +41,6 @@ export interface ListingLine {
41
41
  }
42
42
 
43
43
  const CODE = new Set(["code", "jumps"]);
44
- const hex2 = (value: number) => `$${value.toString(16).padStart(2, "0")}`;
45
- const hex4 = (value: number) => `$${value.toString(16).padStart(4, "0")}`;
46
44
  const DATA_PER_LINE = 8;
47
45
 
48
46
  /** The access class of bytes from the coverage: x, r and w as they occur, or - for none. */
@@ -57,12 +55,12 @@ function accessOf(coverage: Uint8Array | undefined, start: number, length: numbe
57
55
  /** The operand of an instruction in ACME syntax, with the name of its target where one exists. */
58
56
  function operandOf(mode: Mode, address: number, low: number, high: number, nameOf: ListingSource["nameOf"]): string {
59
57
  const word = low | (high << 8);
60
- const named = (target: number, digits: 2 | 4) => nameOf(target) ?? (digits === 2 ? hex2(target) : hex4(target));
58
+ const named = (target: number, digits: 2 | 4) => nameOf(target) ?? (digits === 2 ? formatC64Byte(target) : formatC64Address(target));
61
59
  switch (mode) {
62
60
  case "imp":
63
61
  return "";
64
62
  case "imm":
65
- return `#${hex2(low)}`;
63
+ return `#${formatC64Byte(low)}`;
66
64
  case "zp":
67
65
  return named(low, 2);
68
66
  case "zpx":
@@ -138,7 +136,7 @@ export function listing(source: ListingSource, start: number, end: number): List
138
136
  values.push(source.byteAt(at)!);
139
137
  at++;
140
138
  }
141
- if (address >= start) lines.push(line(address, values.length, `!byte ${values.map(hex2).join(",")}`));
139
+ if (address >= start) lines.push(line(address, values.length, `!byte ${values.map(formatC64Byte).join(",")}`));
142
140
  address = at;
143
141
  }
144
142
  return lines;
@@ -149,7 +147,7 @@ export function listingText(lines: readonly ListingLine[]): string {
149
147
  const out: string[] = [];
150
148
  for (const item of lines) {
151
149
  for (const comment of item.lineComments ?? []) out.push(` ; ${comment}`);
152
- const head = `${hex4(item.address)} ${item.bytes.padEnd(9)} ${(item.access ?? "").padEnd(3)}`;
150
+ const head = `${formatC64Address(item.address)} ${item.bytes.padEnd(9)} ${(item.access ?? "").padEnd(3)}`;
153
151
  const body = (item.label === undefined ? "" : `${item.label} `).padEnd(20) + item.text;
154
152
  const notes = [
155
153
  ...(item.comment === undefined ? [] : [item.comment]),
@@ -21,10 +21,10 @@ This skill does not read the emulator and does not change project knowledge.
21
21
  The script looks up local reference data. It does not use the host runtime.
22
22
 
23
23
  ```
24
- node <skill>/scripts/memmap.ts at '$d020' '$0314' '$ffd2'
25
- node <skill>/scripts/memmap.ts decode '$d018' '$15' --bank '$97'
26
- node <skill>/scripts/memmap.ts list vic
27
- node <skill>/scripts/memmap.ts export --out src/hw.asm
24
+ node <skill>/scripts/memory-map.ts at '$d020' '$0314' '$ffd2'
25
+ node <skill>/scripts/memory-map.ts decode '$d018' '$15' --bank '$97'
26
+ node <skill>/scripts/memory-map.ts list vic
27
+ node <skill>/scripts/memory-map.ts export --out src/hw.asm
28
28
  ```
29
29
 
30
30
  | Command | Result |
@@ -34,16 +34,26 @@ node <skill>/scripts/memmap.ts export --out src/hw.asm
34
34
  | `list <vic\|sid\|cia1\|cia2\|kernal\|system>` | All entries of that group. |
35
35
  | `export --out <file>` | Writes all platform names to a project file as an ACME include: one line for each name, with its meaning as a comment. A zero-page name is `NAME = $xx`. Other names are `NAME = $xxxx`. |
36
36
 
37
- Write an address or a value as `$` and hex digits, as a decimal number, or as `%` and binary digits. Put a `$` value in single quotes in a shell.
37
+ Write a value for `decode` and `--bank` as `$` and hex digits, as a decimal number, or as `%` and binary digits. Put a `$` value in single quotes in a shell.
38
38
 
39
- The script prints one JSON object.
39
+ ### Calling the script
40
+
41
+ - Call a script as `node <skill>/scripts/<script>.ts <command> [subjects] [options]`. The command comes first.
42
+ - `<script>.ts --help` lists the commands. `<script>.ts <command> --help` gives the subjects and the options of one command. The help is JSON: `{"text": "..."}`.
43
+ - 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.
44
+ - A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
45
+ - Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
46
+ - 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.
47
+ - An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
48
+ - The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
40
49
 
41
50
  ### Project folder
42
51
 
43
52
  - 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/`.
44
53
  - 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.
45
54
  - 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.
46
- - 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` of c64-knowledge. `--evidence` refuses a file in `.c64-re-tools/local/`.
55
+ - 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/`.
56
+ - 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.
47
57
  - 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.
48
58
  - 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`.
49
59
 
@@ -71,9 +81,7 @@ A conclusion about the application can be project knowledge. For example: "the p
71
81
 
72
82
  ## Result
73
83
 
74
- The platform meaning of each address, or the decoded meaning of a register value. For `export`: the file, the count of names and the bytes.
75
-
76
- The exit status is 0 for a result. The exit status is 2 for a wrong argument.
84
+ The platform meaning of each address, or the decoded meaning of a register value. For `export`: the count of names in `names`, and the file as `{out, bytes, sha256}`.
77
85
 
78
86
  ## Failure and conflicts
79
87
 
@@ -0,0 +1,81 @@
1
+ // The c64-memory-map script: the platform meaning of C64 addresses and of
2
+ // values in platform registers. Local lookup only; nothing is stored. export
3
+ // writes the platform names to a project file as an ACME include.
4
+
5
+ import { formatC64Address, formatC64Byte } from "#src/c64.ts";
6
+ import { writeReported } from "#src/host-client/transfer.ts";
7
+ import { knowledgeNames } from "#src/knowledge/names.ts";
8
+ import { decode, IO, lookup, platformNames, ROM, SYSTEM, type Entry } from "#src/platform.ts";
9
+ import { OPTIONS, runScript, UsageError } from "#src/script.ts";
10
+
11
+ const show = (entry: Entry) => ({ start: formatC64Address(entry.start), ...(entry.end !== entry.start ? { end: formatC64Address(entry.end) } : {}), name: entry.name, meaning: entry.meaning });
12
+
13
+ const LISTS: Record<string, () => Entry[]> = {
14
+ vic: () => IO[0]!.registers,
15
+ sid: () => IO[1]!.registers,
16
+ cia1: () => IO[2]!.registers,
17
+ cia2: () => IO[3]!.registers,
18
+ kernal: () => ROM,
19
+ system: () => SYSTEM,
20
+ };
21
+
22
+ await runScript({
23
+ name: "memory-map",
24
+ summary: "The platform meaning of C64 addresses and of values in platform registers.",
25
+ names: knowledgeNames,
26
+ commands: [
27
+ {
28
+ name: "at",
29
+ summary: "The platform meaning of each address: its name, area and meaning.",
30
+ subjects: [{ name: "<address>", help: "an address or a name of the knowledge", list: true }],
31
+ run: (call) => ({
32
+ addresses: call.subjects.map((text) => {
33
+ const found = lookup(call.parseAddress(text, "<address>"));
34
+ // The chip registers, color RAM and the cartridge I/O areas show only while $01 maps I/O in.
35
+ const io = found.address >= 0xd000 && found.address <= 0xdfff;
36
+ return {
37
+ ...found,
38
+ address: formatC64Address(found.address),
39
+ ...(found.mirrorOf === undefined ? {} : { mirrorOf: formatC64Address(found.mirrorOf) }),
40
+ ...(found.entryStart === undefined ? {} : { entryStart: formatC64Address(found.entryStart) }),
41
+ ...(io ? { visible: "when $01 shows I/O at $D000 (the default)" } : {}),
42
+ };
43
+ }),
44
+ }),
45
+ },
46
+ {
47
+ name: "decode",
48
+ summary: "The meaning of a value in a platform register.",
49
+ subjects: [
50
+ { name: "<register>", help: "the register: $0001, $d011, $d016, $d018, $d010, $d015-$d01f, $dc0d, $dd00 or $dd0d" },
51
+ { name: "<value>", help: "the value of the register: $ and hex, decimal, or % and binary, 0 to 255" },
52
+ ],
53
+ options: { bank: { kind: "number", value: "<value>", help: "the value of $dd00, for the VIC-II bank of a $d018 value", max: 0xff } },
54
+ run: (call) => {
55
+ const register = call.parseAddress(call.subject(0), "<register>");
56
+ const value = call.parseNumber(call.subject(1), "<value>", 0, 0xff);
57
+ return { register: formatC64Address(register), value: formatC64Byte(value), name: lookup(register).name, ...decode(register, value, call.count("bank")) };
58
+ },
59
+ },
60
+ {
61
+ name: "list",
62
+ summary: "The registers of a chip, the KERNAL jump table, or the system areas.",
63
+ subjects: [{ name: "<list>", help: `one of ${Object.keys(LISTS).join(", ")}` }],
64
+ run: (call) => {
65
+ const list = LISTS[call.subject(0)];
66
+ if (list === undefined) throw new UsageError(`<list> must be one of ${Object.keys(LISTS).join(", ")}, not ${call.subject(0)}`);
67
+ return { entries: list().map(show) };
68
+ },
69
+ },
70
+ {
71
+ name: "export",
72
+ summary: "Writes the platform names as an ACME include: NAME = $xxxx.",
73
+ options: { out: { ...OPTIONS.out, value: "<file>", help: "the include file, relative to the project folder", required: true } },
74
+ run: (call) => {
75
+ const names = platformNames();
76
+ const lines = ["; C64 platform names from the c64-memory-map skill, for ACME.", ...names.map((item) => `${item.name} = ${item.address < 0x100 ? formatC64Byte(item.address) : formatC64Address(item.address)}`.padEnd(24) + `; ${item.meaning}`)];
77
+ return { names: names.length, ...writeReported(call.string("out")!, `${lines.join("\n")}\n`, { what: "--out" }) };
78
+ },
79
+ },
80
+ ],
81
+ });
@@ -17,7 +17,7 @@ This skill does not change project knowledge.
17
17
 
18
18
  ## Inputs
19
19
 
20
- - Two or more releases, relative to the project folder. A release is a PRG file or an `image.json` from c64-unpacker capture. Extract releases from disk images with the c64-disk skill. Unpack packed releases with the c64-unpacker skill first.
20
+ - Two or more releases, relative to the project folder. A release is `knowledge` (the program in the knowledge), a PRG file, or an `image.json` from c64-unpacker capture with its `entry_state.json` beside it. Extract releases from disk images with the c64-disk skill. Unpack packed releases with the c64-unpacker skill first.
21
21
  - Optional: an address range to compare.
22
22
  - Optional: a shift for a release that loads at a different address.
23
23
  - Optional: a number of frames to run each release, and a steps file.
@@ -26,12 +26,12 @@ This skill does not change project knowledge.
26
26
 
27
27
  ## Tools / execution path
28
28
 
29
- The script compares the files locally. Only `--run` uses the host runtime: each release runs in its own emulator.
29
+ The script compares the releases locally. Only `--frames` uses the host runtime: each release runs in its own emulator.
30
30
 
31
31
  ```
32
- node <skill>/scripts/provenance.ts releases/a.prg releases/b.prg releases/c.prg
33
- node <skill>/scripts/provenance.ts releases/a.prg releases/b.prg --shift 2:-256 --from '$0900' --to '$09ff'
34
- node <skill>/scripts/provenance.ts releases/a.prg .c64-re-tools/local/b/image.json --run 600 --steps .c64-re-tools/local/start-game.json
32
+ node <skill>/scripts/provenance.ts compare releases/a.prg releases/b.prg releases/c.prg
33
+ node <skill>/scripts/provenance.ts compare releases/a.prg releases/b.prg --shift 2:-256 --range '$0900-$09ff'
34
+ node <skill>/scripts/provenance.ts compare knowledge .c64-re-tools/local/b/image.json --frames 600 --steps .c64-re-tools/local/start-game.json
35
35
  node <skill>/scripts/provenance.ts separate --out .c64-re-tools/local/separate.json
36
36
  node <skill>/scripts/provenance.ts leftovers --out .c64-re-tools/local/leftovers.json
37
37
  ```
@@ -39,23 +39,34 @@ node <skill>/scripts/provenance.ts leftovers --out .c64-re-tools/local/leftovers
39
39
  | Option | Meaning |
40
40
  |---|---|
41
41
  | `--shift 2:-256` | Move release 2 (the first release is 1) by -256 bytes. Use it again for more releases. |
42
- | `--from '$0900'` | Compare from this address. |
43
- | `--to '$09ff'` | Compare up to this address. |
44
- | `--run 600` | Run each release for this many frames, and give each listed difference its runtime evidence. |
45
- | `--steps steps/start-game.json` | With `--run`: the action steps of the c64-unpacker steps file to run before the frames, for example a key to start the game. |
42
+ | `--range '$0900-$09ff'` | Compare only the addresses in this range. |
43
+ | `--frames 600` | Run each release for this many frames, and give each listed difference its runtime evidence. |
44
+ | `--steps .c64-re-tools/local/start-game.json` | With `--frames`: a JSON list of action steps to run before the frames, for example a key to start the game. The steps are the steps of a c64-unpacker steps file. |
46
45
 
47
- The script puts each PRG at its load address. An image has bytes only in its segments. The script compares the addresses where all releases have bytes. It prints one JSON object.
46
+ The script puts each PRG at its load address. An image has bytes only in its segments. The script compares the addresses where all releases have bytes.
48
47
 
49
48
  With `--shift`, the addresses of `segments`, `common` and `differences` are the release addresses plus the `shift` of the release. The `start` and `end` of `runtime` are the addresses in the release itself, without the shift.
50
49
 
51
- With `--run`, a PRG starts with autostart and RUN. An image starts from its bytes: the script writes the segments and the entry state from `entry_state.json` into the emulator, and starts at the entry. Watchpoints on each difference stop the run at its first access.
50
+ With `--frames`, a PRG starts with autostart and RUN. An image starts from its bytes: the script writes the segments and the entry state from `entry_state.json` into the emulator, and starts at the entry. Watchpoints on each difference stop the run at its first access.
51
+
52
+ ### Calling the script
53
+
54
+ - Call a script as `node <skill>/scripts/<script>.ts <command> [subjects] [options]`. The command comes first.
55
+ - `<script>.ts --help` lists the commands. `<script>.ts <command> --help` gives the subjects and the options of one command. The help is JSON: `{"text": "..."}`.
56
+ - 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.
57
+ - A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
58
+ - Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
59
+ - 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.
60
+ - An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
61
+ - The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
52
62
 
53
63
  ### Project folder
54
64
 
55
65
  - 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/`.
56
66
  - 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.
57
67
  - 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.
58
- - 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` of c64-knowledge. `--evidence` refuses a file in `.c64-re-tools/local/`.
68
+ - 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/`.
69
+ - 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.
59
70
  - 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.
60
71
  - 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`.
61
72
 
@@ -66,7 +77,7 @@ With `--run`, a PRG starts with autostart and RUN. An image starts from its byte
66
77
 
67
78
  `suggestedShifts` has at most 3 shifts for each release. The shift with the most `matchingSequences` is first. Data that did not move and code that moved give different shifts. Try each shift. `current: true` marks the shift of this run.
68
79
  3. Read `differences`. Each one has an address range, the bytes of each release, and `groups`: the releases that have the same bytes there.
69
- 4. To know how the program uses the differences, run the script again with `--run` and the frames that get the program to the part you examine. Read `runtime` of each difference:
80
+ 4. To know how the program uses the differences, run the script again with `--frames` and the frames that get the program to the part you examine. Read `runtime` of each difference:
70
81
  - `first`: the first access in that release: `read`, `write`, `execute` or `none`. An instruction that reads and then writes the same byte, for example INC, counts as `read`. The operand bytes of an executed instruction count as `execute`, for example the value of `LDA #$03`.
71
82
  - `changed`: `true` when the bytes at the end of the run are not the bytes of the release.
72
83
  - A range that each release writes before it reads is probably runtime state, not a difference of the releases.
@@ -88,7 +99,7 @@ Use `separate` when you have only one release. It reads the program, its stages
88
99
  - `codeRegions.unreachable`: code regions of the knowledge that no chain of call and jump references reaches from the entry. Run c64-static-analysis first, so that the knowledge has the code regions and the references.
89
100
  - `outside`: RAM outside the program that the program `read` or `executed` but did not write, and code it `generated`. A loader or an earlier stage put the bytes that the program did not write. A `generated` range can have gaps of two bytes or less: these are operand bytes.
90
101
 
91
- Each list has `count` and `listed`. `listed` has the first ranges, at most `--max-listed`. `--out` writes all of them. Run more coverage runs for parts that are `untouched`, and add them with `coverage` of the c64-knowledge skill, before you decide.
102
+ Each list has `count` and `listed`. `listed` has the first ranges, at most `--max-listed` (default 50). `--out` writes all of them and the result shows the file as `{out, bytes, sha256}`. Run more coverage runs for parts that are `untouched`, and add them with `coverage` of the c64-knowledge skill, before you decide.
92
103
 
93
104
  ## Leftovers
94
105
 
@@ -136,18 +147,18 @@ Read the findings with care:
136
147
 
137
148
  A library is a folder of routine files. Each file holds one routine: its bytes and its knowledge. Keep one library beside the project folders, for example `library/` at the root of the workspace. A library is not a project folder. Commit it with the projects.
138
149
 
139
- `--library` is relative to the working directory, as `--project`. All other paths are relative to the project folder.
150
+ `--library` is relative to the project folder, as all other paths. For example, `../library` is the folder `library/` beside the project.
140
151
 
141
152
  ```
142
- node <skill>/scripts/provenance.ts routine --module '$1000' --library library --project game-a
143
- node <skill>/scripts/provenance.ts routine --start '$1000' --end '$1026' --name play --library library --project game-a
144
- node <skill>/scripts/provenance.ts match --library library --out .c64-re-tools/local/match.json --project game-b
153
+ node <skill>/scripts/provenance.ts routine '$1000' --library ../library --project game-a
154
+ node <skill>/scripts/provenance.ts routine '$1000-$1026' --name play --library ../library --project game-a
155
+ node <skill>/scripts/provenance.ts match --library ../library --out .c64-re-tools/local/match.json --project game-b
145
156
  ```
146
157
 
147
158
  To save a routine:
148
159
 
149
160
  1. Put the program into the knowledge, and name the routine there first. The file keeps the user and LLM names, comments, regions, references and operand expressions in the range. It also keeps the code regions of each origin.
150
- 2. Run `routine` with the start of a module (`--module`), or with `--start` and `--end`. A module ends at the next module or at the end of its segment.
161
+ 2. Run `routine` with the start address of a module, or with a range `<start>-<end>`. A module ends at the next module or at the end of its segment.
151
162
  - `--name` is the name of the routine and of its file. When you do not give it, the script uses the user or LLM name at the start.
152
163
  - `--title` is the title of the routine. When you do not give it, the script uses the module title.
153
164
  - A routine has at least 16 bytes. Its last instruction must end in the range.
@@ -159,7 +170,7 @@ To find the routines in a program:
159
170
  - The opcodes and the other bytes must be equal.
160
171
  - An address into the routine must point into the routine at its new address. `shift` is the address in the program minus the address in the library.
161
172
  - An operand that addresses something outside the routine can be different. `differing` lists each one with its value in the library and in the program. Examine them. For example, the program can use other variables or another SID voice.
162
- 2. Give `--out` to get a batch for `knowledge.ts apply`. The batch has the names, regions, comments, references and operand expressions of each routine at its addresses in the program. `match` does not change the knowledge.
173
+ 2. Give `--out` to get a batch for `knowledge.ts apply`. `batch` shows the file as `{out, bytes, sha256}`. The batch has the names, regions, comments, references and operand expressions of each routine at its addresses in the program. `match` does not change the knowledge.
163
174
  3. Read `batch.leftOut` before you apply the batch. The script leaves out a write in these conditions:
164
175
  - The program has a user or LLM row at that place.
165
176
  - The program uses the name at another address.
@@ -179,14 +190,12 @@ Record a confirmed conclusion as a normal comment, region or symbol with the c64
179
190
 
180
191
  ## Result
181
192
 
182
- The differing ranges with the bytes of each release and the groups that agree, and the share of identical bytes. `common` gives the ranges where all releases have bytes. `releases` gives the segments and the `shift` of each release. With `--run`, each difference also has `runtime`, and `run` tells if each release started, or why not.
193
+ The differing ranges with the bytes of each release and the groups that agree, and the share of identical bytes. `common` gives the ranges where all releases have bytes. `releases` gives the segments and the `shift` of each release. With `--frames`, each difference also has `runtime`, and `run` tells if each release started, or why not.
183
194
 
184
195
  For `leftovers`: the lists of findings, with `entry` and the number of `stages`. `noEntry`, `noStages` and `noCoverage` tell which knowledge is missing.
185
196
 
186
197
  The runtime evidence and the leftovers are evidence only. The script does not decide which bytes are original.
187
198
 
188
- The exit status is 0 for a result. The exit status is 1 for an error. The exit status is 2 for a wrong argument.
189
-
190
199
  ## Failure and conflicts
191
200
 
192
201
  - Do not call releases independent when you do not know their relation. Two cracks can come from the same source.
@@ -194,7 +203,7 @@ The exit status is 0 for a result. The exit status is 1 for an error. The exit s
194
203
  - `common` is empty: the releases do not cover the same addresses. Use `--shift`, or compare a range that all releases cover.
195
204
  - `run.releases` shows `started: false`: a PRG without a BASIC start, or an image without `entry_state.json`. The reason tells which. The other releases still give evidence.
196
205
  - `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the task. Give the message to the user and wait for the user. The message gives the remedy. Do not make a replacement for the tool or the host runtime, for example your own emulator script.
197
- - `moreDifferences: true`: the script shows only the first differences. Use `--from` and `--to` to see the others.
206
+ - `moreDifferences: true`: the script shows only the first differences. Use `--range` to see the others.
198
207
  - `not-found`: the project has no file at that path.
199
208
  - `not-found` from `match`: the library folder does not exist, or it has no routine files. Save a routine with `routine` first.
200
209
  - `conflict` from `routine`: the library has a routine with that name. Give another `--name`, or give `--replace` to save over it.
@@ -5,15 +5,12 @@
5
5
  // releases differ and which releases agree there. It never decides which
6
6
  // bytes are original: that needs evidence beyond one comparison.
7
7
 
8
- export interface Range {
9
- start: number;
10
- end: number;
11
- }
8
+ import { type AddressRange, formatC64Address } from "#src/c64.ts";
12
9
 
13
10
  export interface Release {
14
11
  name: string;
15
12
  /** Where the release has bytes, after the shift, in address order. */
16
- segments: Range[];
13
+ segments: AddressRange[];
17
14
  /** The byte at an address inside a segment. */
18
15
  byteAt: (address: number) => number;
19
16
  }
@@ -29,7 +26,7 @@ export interface DifferingRange {
29
26
 
30
27
  export interface Comparison {
31
28
  /** The addresses where every release has bytes. */
32
- common: Range[];
29
+ common: AddressRange[];
33
30
  identicalBytes: number;
34
31
  differingBytes: number;
35
32
  differences: DifferingRange[];
@@ -43,8 +40,8 @@ const JOIN_GAP = 4;
43
40
  export const covers = (release: Release, address: number) => release.segments.some((segment) => address >= segment.start && address <= segment.end);
44
41
 
45
42
  /** The ranges where every release has bytes, inside from..to. */
46
- export function commonRanges(releases: Release[], from = 0x0000, to = 0xffff): Range[] {
47
- const ranges: Range[] = [];
43
+ export function commonRanges(releases: Release[], from = 0x0000, to = 0xffff): AddressRange[] {
44
+ const ranges: AddressRange[] = [];
48
45
  for (let address = from; address <= to; address++) {
49
46
  if (!releases.every((release) => covers(release, address))) continue;
50
47
  const last = ranges.at(-1);
@@ -67,7 +64,7 @@ export function compareReleases(releases: Release[], options: { from?: number; t
67
64
  }
68
65
  // A joined range stays inside one common range, so every release has all of its bytes.
69
66
  const inSameCommon = (a: number, b: number) => common.some((range) => a >= range.start && b <= range.end);
70
- const ranges: Range[] = [];
67
+ const ranges: AddressRange[] = [];
71
68
  for (const address of differing) {
72
69
  const last = ranges.at(-1);
73
70
  if (last !== undefined && address - last.end <= JOIN_GAP && inSameCommon(last.start, address)) last.end = address;
@@ -137,9 +134,8 @@ export type ShiftSuggestion = { release: number; shift: number; matchingSequence
137
134
  */
138
135
  export function shiftSuggestion(releases: Release[], applied: number[], index: number, found: { shift: number; matches: number }): ShiftSuggestion {
139
136
  const outside = (release: Release, by: number) => release.segments.find((segment) => segment.start + by < 0 || segment.end + by > 0xffff);
140
- const hex = (value: number) => `$${value.toString(16).padStart(4, "0")}`;
141
- const why = (number: number, by: number, segment: Range) =>
142
- `release ${number} moved by ${by} puts ${hex(segment.start)}-${hex(segment.end)} ${segment.start + by < 0 ? "below $0000" : "past $ffff"}`;
137
+ const why = (number: number, by: number, segment: AddressRange) =>
138
+ `release ${number} moved by ${by} puts ${formatC64Address(segment.start)}-${formatC64Address(segment.end)} ${segment.start + by < 0 ? "below $0000" : "past $ffff"}`;
143
139
  const own = outside(releases[index]!, found.shift);
144
140
  if (own === undefined) return { release: index + 1, shift: applied[index]! + found.shift, matchingSequences: found.matches };
145
141
  const reasons = [why(index + 1, found.shift, own)];
@@ -6,13 +6,12 @@
6
6
  // its bytes from the separation. Evidence only: nothing here decides what a
7
7
  // leftover is or who made it.
8
8
 
9
- import { basicLineText } from "#src/c64.ts";
9
+ import { type AddressRange, basicLineText } from "#src/c64.ts";
10
10
  import { MNEMONICS } from "#src/opcodes.ts";
11
- import type { Range } from "./compare.ts";
12
11
  import type { Access, Separation } from "./separate.ts";
13
12
 
14
13
  export interface LeftoversInput {
15
- segments: Range[];
14
+ segments: AddressRange[];
16
15
  /** 64 KiB with the program bytes at their addresses. */
17
16
  memory: Uint8Array;
18
17
  separation: Separation;
@@ -33,7 +32,7 @@ export interface Placement {
33
32
  /** high-bit is text with bit 7 set: ASCII as the Apple II keeps it, or C64 shifted letters with $a0 spaces. Its spaces can also be $20. */
34
33
  export type TextEncoding = "petscii" | "screen" | "ascii" | "high-bit";
35
34
 
36
- export interface TextFinding extends Range, Placement {
35
+ export interface TextFinding extends AddressRange, Placement {
37
36
  encoding: TextEncoding;
38
37
  /** Screen codes with bit 7 set: the characters show reversed. */
39
38
  reversed?: boolean;
@@ -45,26 +44,26 @@ export interface TextFinding extends Range, Placement {
45
44
  mnemonics?: number;
46
45
  }
47
46
 
48
- export interface BasicFinding extends Range, Placement {
47
+ export interface BasicFinding extends AddressRange, Placement {
49
48
  /** The address of the first line where the links put it: where the lines were when BASIC linked them. */
50
49
  linkedAt: number;
51
50
  lines: string[];
52
51
  }
53
52
 
54
- export interface FillFinding extends Range, Placement {
53
+ export interface FillFinding extends AddressRange, Placement {
55
54
  pattern: number[];
56
55
  }
57
56
 
58
57
  /** Untouched bytes that are no text, BASIC, fill or code region. */
59
- export type ResidueFinding = Range & Placement;
58
+ export type ResidueFinding = AddressRange & Placement;
60
59
 
61
- export interface CopyFinding extends Range, Placement {
60
+ export interface CopyFinding extends AddressRange, Placement {
62
61
  /** The first address of the same bytes earlier in the program. */
63
62
  copyOf: number;
64
63
  }
65
64
 
66
65
  /** A code region that no call or jump reaches from the entry, and that no run executed. */
67
- export interface DeadCodeFinding extends Range, Placement {
66
+ export interface DeadCodeFinding extends AddressRange, Placement {
68
67
  type: string;
69
68
  /** false when the coverage shows that it did not run; absent without coverage. */
70
69
  executed?: false;
@@ -228,7 +227,7 @@ function ownEncoding(encoding: TextEncoding, bytes: number[], characters: Charac
228
227
  }
229
228
 
230
229
  /** The runs of one encoding in a segment that read as text. `reversed` reads the bytes with bit 7 set, without that bit. */
231
- function textRuns(memory: Uint8Array, segment: Range, encoding: TextEncoding, reversed: boolean, min: number): TextFinding[] {
230
+ function textRuns(memory: Uint8Array, segment: AddressRange, encoding: TextEncoding, reversed: boolean, min: number): TextFinding[] {
232
231
  const decode = { petscii: petsciiOrLineEnd, screen: screenCharacter, ascii: asciiCharacter, "high-bit": highAsciiCharacter }[encoding];
233
232
  const found: TextFinding[] = [];
234
233
  let run: Array<{ address: number; character: Character }> = [];
@@ -276,10 +275,10 @@ function textRuns(memory: Uint8Array, segment: Range, encoding: TextEncoding, re
276
275
  return found;
277
276
  }
278
277
 
279
- export function findText(memory: Uint8Array, segments: Range[], min = MIN_TEXT): TextFinding[] {
278
+ export function findText(memory: Uint8Array, segments: AddressRange[], min = MIN_TEXT): TextFinding[] {
280
279
  const scan = (encoding: TextEncoding, reversed = false) => segments.flatMap((segment) => textRuns(memory, segment, encoding, reversed, min));
281
280
  // A run that is mostly a run of an earlier list is the same text, read in another encoding, with stray bytes at its edges.
282
- const overlap = (a: Range, b: Range) => Math.max(0, Math.min(a.end, b.end) - Math.max(a.start, b.start) + 1);
281
+ const overlap = (a: AddressRange, b: AddressRange) => Math.max(0, Math.min(a.end, b.end) - Math.max(a.start, b.start) + 1);
283
282
  const kept: TextFinding[] = [];
284
283
  for (const list of [[...scan("high-bit"), ...scan("ascii")], scan("petscii"), [...scan("screen"), ...scan("screen", true)]]) {
285
284
  kept.push(...list.filter((run) => !kept.some((other) => overlap(run, other) * 2 > run.end - run.start + 1)));
@@ -303,7 +302,7 @@ function tokensFit(tokens: Uint8Array): boolean {
303
302
  * a finding. The links give the address where BASIC put the lines, which can
304
303
  * be another address than where they are now.
305
304
  */
306
- export function findBasic(memory: Uint8Array, segments: Range[], basicStart?: number): BasicFinding[] {
305
+ export function findBasic(memory: Uint8Array, segments: AddressRange[], basicStart?: number): BasicFinding[] {
307
306
  const found: BasicFinding[] = [];
308
307
  for (const segment of segments) {
309
308
  const word = (at: number) => memory[at]! | (memory[at + 1]! << 8);
@@ -348,7 +347,7 @@ export function findBasic(memory: Uint8Array, segments: Range[], basicStart?: nu
348
347
  }
349
348
 
350
349
  /** Runs of one byte of at least MIN_FILL bytes, and runs of a pattern of two to four bytes of at least twice that. */
351
- export function findFills(memory: Uint8Array, segments: Range[]): FillFinding[] {
350
+ export function findFills(memory: Uint8Array, segments: AddressRange[]): FillFinding[] {
352
351
  const found: FillFinding[] = [];
353
352
  for (const segment of segments) {
354
353
  for (let at = segment.start; at <= segment.end; ) {
@@ -374,7 +373,7 @@ export function findFills(memory: Uint8Array, segments: Range[]): FillFinding[]
374
373
  }
375
374
 
376
375
  /** Exact copies of at least MIN_COPY bytes of an earlier range, with enough different byte values to be no fill. */
377
- export function findCopies(memory: Uint8Array, segments: Range[]): CopyFinding[] {
376
+ export function findCopies(memory: Uint8Array, segments: AddressRange[]): CopyFinding[] {
378
377
  const found: CopyFinding[] = [];
379
378
  const seen = new Map<string, number>();
380
379
  const inside = (address: number) => segments.some((segment) => address >= segment.start && address <= segment.end);
@@ -399,8 +398,8 @@ export function findCopies(memory: Uint8Array, segments: Range[]): CopyFinding[]
399
398
  }
400
399
 
401
400
  /** Splits `range` at the parts that `taken` covers, and keeps the pieces of at least `min` bytes. */
402
- function without(range: Range, taken: Range[], min: number): Range[] {
403
- const pieces: Range[] = [];
401
+ function without(range: AddressRange, taken: AddressRange[], min: number): AddressRange[] {
402
+ const pieces: AddressRange[] = [];
404
403
  let start = range.start;
405
404
  for (const part of taken.filter((item) => item.start <= range.end && item.end >= range.start).sort((a, b) => a.start - b.start)) {
406
405
  if (part.start > start) pieces.push({ start, end: part.start - 1 });
@@ -413,19 +412,19 @@ function without(range: Range, taken: Range[], min: number): Range[] {
413
412
  export function findLeftovers(input: LeftoversInput): Leftovers {
414
413
  const { memory, segments, separation } = input;
415
414
  const ranges = separation.ranges;
416
- const placement = (range: Range): Placement => {
415
+ const placement = (range: AddressRange): Placement => {
417
416
  const parts = ranges.filter((part) => part.start <= range.end && part.end >= range.start);
418
417
  const stages = [...new Set(parts.flatMap((part) => (part.stage === undefined ? [] : [part.stage])))].sort((a, b) => a - b);
419
418
  const access = [...new Set(parts.flatMap((part) => (part.access === undefined ? [] : [part.access])))];
420
419
  return { ...(stages.length === 0 ? {} : { stages }), ...(access.length === 0 ? {} : { access }) };
421
420
  };
422
- const placed = <T extends Range>(items: T[]) => items.map((item) => ({ ...item, ...placement(item) }));
421
+ const placed = <T extends AddressRange>(items: T[]) => items.map((item) => ({ ...item, ...placement(item) }));
423
422
 
424
423
  const text = findText(memory, segments, input.minText);
425
424
  const basic = findBasic(memory, segments, input.basicStart);
426
425
  const fill = findFills(memory, segments);
427
426
  // Untouched bytes that no other finding explains.
428
- const untouched: Range[] = [];
427
+ const untouched: AddressRange[] = [];
429
428
  for (const part of ranges) {
430
429
  if (part.access !== "untouched") continue;
431
430
  const last = untouched.at(-1);