@henols/c64-re-tools 2.1.0 → 2.1.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.
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +7 -3
- package/skills/c64-assembler/scripts/assembler.ts +29 -11
- package/skills/c64-assembler/scripts/lib/compare.ts +4 -3
- package/skills/c64-basic/SKILL.md +1 -0
- package/skills/c64-disk/SKILL.md +3 -2
- package/skills/c64-disk/scripts/disk.ts +26 -12
- package/skills/c64-disk/scripts/lib/containers.ts +10 -11
- package/skills/c64-docs/SKILL.md +2 -1
- package/skills/c64-docs/scripts/docs.ts +1 -1
- package/skills/c64-docs/scripts/lib/links.ts +5 -3
- package/skills/c64-docs/scripts/lib/reference.ts +2 -2
- package/skills/c64-emulator/SKILL.md +9 -2
- package/skills/c64-knowledge/SKILL.md +9 -5
- package/skills/c64-knowledge/scripts/knowledge.ts +91 -53
- package/skills/c64-knowledge/scripts/lib/listing.ts +8 -30
- package/skills/c64-memory-map/SKILL.md +2 -1
- package/skills/c64-memory-map/scripts/memory-map.ts +4 -3
- package/skills/c64-provenance/SKILL.md +6 -4
- package/skills/c64-provenance/scripts/lib/library.ts +18 -6
- package/skills/c64-provenance/scripts/lib/separate.ts +45 -6
- package/skills/c64-provenance/scripts/provenance.ts +14 -10
- package/skills/c64-source/SKILL.md +3 -0
- package/skills/c64-source/scripts/lib/source.ts +9 -31
- package/skills/c64-source/scripts/source.ts +14 -10
- package/skills/c64-static-analysis/SKILL.md +2 -0
- package/skills/c64-static-analysis/scripts/lib/idioms.ts +6 -2
- package/skills/c64-static-analysis/scripts/static-analysis.ts +18 -8
- package/skills/c64-testing/SKILL.md +14 -3
- package/skills/c64-testing/references/recording-format.md +2 -0
- package/skills/c64-testing/references/scenario-format.md +6 -0
- package/skills/c64-testing/scripts/lib/diff-files.ts +76 -14
- package/skills/c64-testing/scripts/lib/diff.ts +3 -3
- package/skills/c64-testing/scripts/lib/recording.ts +50 -15
- package/skills/c64-testing/scripts/lib/replay.ts +12 -1
- package/skills/c64-testing/scripts/lib/runs.ts +84 -4
- package/skills/c64-testing/scripts/lib/scenario.ts +43 -6
- package/skills/c64-testing/scripts/testing.ts +20 -7
- package/skills/c64-unpacker/SKILL.md +17 -10
- package/skills/c64-unpacker/scripts/lib/evidence.ts +4 -5
- package/skills/c64-unpacker/scripts/lib/loads.ts +14 -7
- package/skills/c64-unpacker/scripts/lib/machine.ts +30 -3
- package/skills/c64-unpacker/scripts/lib/stages.ts +15 -7
- package/skills/c64-unpacker/scripts/unpacker.ts +93 -42
- package/src/host/vice/adapter.ts +4 -1
- package/src/host/vice/session.ts +148 -13
- package/src/host-client/image.ts +40 -8
- package/src/host-client/steps.ts +145 -9
- package/src/host-client/tools.ts +1 -1
- package/src/host-client/transfer.ts +33 -18
- package/src/knowledge/expression.ts +1 -1
- package/src/knowledge/history.ts +74 -4
- package/src/knowledge/import.ts +79 -7
- package/src/knowledge/names.ts +2 -1
- package/src/knowledge/program.ts +32 -18
- package/src/knowledge/read.ts +46 -0
- package/src/knowledge/schema.ts +4 -2
- package/src/knowledge/text.ts +2 -0
- package/src/knowledge/write.ts +58 -16
- package/src/mcp/inputs.ts +6 -3
- package/src/mcp/output.ts +5 -0
- package/src/mcp/server.ts +20 -4
- package/src/mcp/tools/debug.ts +36 -9
- package/src/mcp/tools/execution.ts +18 -5
- package/src/mcp/tools/machine.ts +3 -1
- package/src/mcp/tools/media.ts +14 -2
- package/src/mcp/tools/memory.ts +11 -6
- package/src/mcp/tools/video.ts +5 -3
- package/src/media.ts +42 -0
- package/src/opcodes.ts +51 -18
- package/src/project.ts +111 -3
- package/src/protocol/vice.ts +8 -2
- package/src/script.ts +55 -3
package/package.json
CHANGED
|
@@ -31,9 +31,11 @@ node <skill>/scripts/assembler.ts build src main.a --out build/game.prg
|
|
|
31
31
|
| `--include lib` | An include directory, relative to the source root. Use it again for more directories. |
|
|
32
32
|
| `--define DEBUG=1` | An assembler symbol: an integer, also as `$` hex, or `true` or `false`. Use it again for more symbols. |
|
|
33
33
|
| `--set-pc '$0801'` | The start address when the source does not set one. |
|
|
34
|
-
| `--compare` | Check the program byte by byte against the program in the knowledge, each segment. Put it in with `image` of the c64-knowledge skill. `--space drive8` or `--space bank0` compares with the program of that space. |
|
|
34
|
+
| `--compare` | Check the program byte by byte against the program in the knowledge, each segment. Put it in with `image` of the c64-knowledge skill. `--space drive8` or `--space bank0` compares with the program of that space. The script refuses `--space` without `--compare`. |
|
|
35
35
|
| `--plain` | Write only the bytes of the program, without the 2-byte load address of a PRG. `loadRange` still tells where the bytes go. |
|
|
36
36
|
| `--all-symbols` | List all symbols. Without it, the list does not include the constants that the source does not use. `unusedConstants` gives their count. |
|
|
37
|
+
| `--max-listed 200` | Show at most 200 symbols in the result. The default is 50. `symbols.count` gives the count of all symbols. |
|
|
38
|
+
| `--out-symbols build/game.json` | Write all symbols to this file as `{"symbols": [...]}`. Give this file to the c64-testing skill as the symbols of the rebuild. |
|
|
37
39
|
|
|
38
40
|
The script copies the full source root. Put all files that the program includes under it. Version control directories stay out.
|
|
39
41
|
|
|
@@ -46,6 +48,7 @@ The target CPU is the 6510. The output is a C64 PRG, or the bytes only with `--p
|
|
|
46
48
|
- 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.
|
|
47
49
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
48
50
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
51
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
49
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.
|
|
50
53
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
51
54
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -64,7 +67,7 @@ The target CPU is the 6510. The output is a C64 PRG, or the bytes only with `--p
|
|
|
64
67
|
|
|
65
68
|
1. Find the source root and the entry source. The entry source sets its own start address with `* = $0801` or similar.
|
|
66
69
|
2. Run the script.
|
|
67
|
-
3. If `result` is `PASS`, the PRG is at `out`. `loadRange` tells where it loads. `symbols`
|
|
70
|
+
3. If `result` is `PASS`, the PRG is at `out`. `loadRange` tells where it loads. `symbols` is a list with `count` and `listed`. Each item in `listed` is one symbol:
|
|
68
71
|
- Kind `address` has a value in `loadRange`. It gives the `address`.
|
|
69
72
|
- Kind `constant` has a value outside `loadRange`. It gives the `value` as `$` and four or more hex digits. A label in a `!pseudopc` block can be a constant, because its value is the run address, not the load address.
|
|
70
73
|
- `used` is `false` when the source does not use the symbol.
|
|
@@ -92,7 +95,7 @@ A symbol from the rebuilt source can give the address of a routine in the rebuil
|
|
|
92
95
|
- `FAIL` with `assembled: false`: the source has errors. `diagnostics` gives them.
|
|
93
96
|
- `FAIL` with `assembled: true`: `--compare` found differences. The PRG is at `out`.
|
|
94
97
|
|
|
95
|
-
With `--compare`, `compare` gives `identical` and `segments`: one check for each segment of the program in the knowledge. The `result` of a check is `identical`, `different` with `firstDifference`, or `not covered` when the build does not hold the segment.
|
|
98
|
+
With `--compare`, `compare` gives `identical` and `segments`: one check for each segment of the program in the knowledge. The `result` of a check is `identical`, `different` with `firstDifference`, or `not covered` when the build does not hold the segment. `firstDifference` gives the `address`, and the byte of the build (`built`) and of the knowledge (`reference`), each as `$` and two hex digits.
|
|
96
99
|
|
|
97
100
|
## Failure and conflicts
|
|
98
101
|
|
|
@@ -100,6 +103,7 @@ With `--compare`, `compare` gives `identical` and `segments`: one check for each
|
|
|
100
103
|
- `FAIL` with `assembled: true`: the build is not identical to the program in the knowledge. Read `compare.segments`.
|
|
101
104
|
- `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 disk parser, disassembler or emulator script.
|
|
102
105
|
- `invalid-input`: an option or a path is wrong. Read the message and correct the call.
|
|
106
|
+
- `not-found`: the source root has no main source file with that name, or no folder with files for an `--include`. Give a path relative to the source root.
|
|
103
107
|
|
|
104
108
|
## Handoffs
|
|
105
109
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// The c64-assembler script: assembles project source with ACME, which it runs
|
|
2
2
|
// itself, and writes the program into the project.
|
|
3
3
|
|
|
4
|
+
import { posix } from "node:path";
|
|
5
|
+
|
|
4
6
|
import { formatC64Address } from "#src/c64.ts";
|
|
5
7
|
import { Refusal } from "#src/errors.ts";
|
|
6
8
|
import { readProjectTree, writeReported } from "#src/host-client/transfer.ts";
|
|
@@ -9,8 +11,8 @@ import { knowledgeNames } from "#src/knowledge/names.ts";
|
|
|
9
11
|
import { requireProgram, type ProgramImage } from "#src/knowledge/program.ts";
|
|
10
12
|
import { assemble } from "#src/native/acme.ts";
|
|
11
13
|
import { localToolContext } from "#src/native/local.ts";
|
|
12
|
-
import type { AcmeParams } from "#src/native/types.ts";
|
|
13
|
-
import { OPTIONS, runScript, UsageError } from "#src/script.ts";
|
|
14
|
+
import type { AcmeParams, AssembledSymbol } from "#src/native/types.ts";
|
|
15
|
+
import { listed as listedItems, OPTIONS, runScript, UsageError } from "#src/script.ts";
|
|
14
16
|
import { compareBuild } from "./lib/compare.ts";
|
|
15
17
|
|
|
16
18
|
function parseDefine(text: string): [string, number | boolean] {
|
|
@@ -23,6 +25,13 @@ function parseDefine(text: string): [string, number | boolean] {
|
|
|
23
25
|
throw new UsageError(`--define ${match[1]} needs an integer or true/false, not ${value}`);
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/** A symbol of the build: an address as $ and four hex digits, a constant as $ and at least four hex digits. */
|
|
29
|
+
function showSymbol(symbol: AssembledSymbol) {
|
|
30
|
+
return symbol.kind === "address"
|
|
31
|
+
? { name: symbol.name, kind: "address", address: formatC64Address(symbol.address), used: symbol.used }
|
|
32
|
+
: { name: symbol.name, kind: "constant", value: `$${symbol.value.toString(16).padStart(4, "0")}`, used: symbol.used };
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
await runScript({
|
|
27
36
|
name: "assembler",
|
|
28
37
|
summary: "Assembles project source with ACME and writes the program into the project.",
|
|
@@ -36,14 +45,16 @@ await runScript({
|
|
|
36
45
|
{ name: "<entry>", help: "the main source file, relative to the source root" },
|
|
37
46
|
],
|
|
38
47
|
options: {
|
|
39
|
-
out: { ...OPTIONS.out, value: "<file.prg>", help: "where to write the program, relative to the project folder", required: true },
|
|
48
|
+
out: { ...OPTIONS.out, output: "file", value: "<file.prg>", help: "where to write the program, relative to the project folder", required: true },
|
|
40
49
|
include: { kind: "text", value: "<dir>", multiple: true, help: "an include folder, relative to the source root" },
|
|
41
50
|
define: { kind: "text", value: "NAME=VALUE", multiple: true, help: "an assembler symbol: an integer ($hex allowed) or true/false" },
|
|
42
51
|
"set-pc": { kind: "address", value: "<address>", help: "the start address when the source sets none, for example '$0801'" },
|
|
43
52
|
compare: { kind: "flag", help: "check the program byte by byte against the program in the knowledge, each segment" },
|
|
44
|
-
space: {
|
|
53
|
+
space: { kind: "space", value: "<space>", help: "with --compare: the knowledge space of the program: c64 (the default), drive8, or bank0 to bank255" },
|
|
45
54
|
plain: { kind: "flag", help: "write the bytes only, without the 2-byte load address of a PRG" },
|
|
46
55
|
"all-symbols": { kind: "flag", help: "list also the constants that the source does not use" },
|
|
56
|
+
"max-listed": { ...OPTIONS["max-listed"], help: "list at most n symbols (default 50). The count is always given" },
|
|
57
|
+
"out-symbols": { kind: "path", output: "file", value: "<file.json>", help: "write all symbols to this project file as {\"symbols\": [...]}: the names of the rebuild for c64-testing" },
|
|
47
58
|
},
|
|
48
59
|
notes: "Symbol values are $ and at least four hex digits. Put the program into the knowledge with knowledge.ts image of the c64-knowledge skill before --compare.",
|
|
49
60
|
run: async (call) => {
|
|
@@ -51,6 +62,7 @@ await runScript({
|
|
|
51
62
|
const out = call.string("out")!;
|
|
52
63
|
// Read the program before ACME runs, so a project without it stops at once.
|
|
53
64
|
let program: ProgramImage | undefined;
|
|
65
|
+
if (call.string("space") !== undefined && !call.flag("compare")) throw new UsageError("--space works only with --compare: it names the knowledge space of the program to compare with");
|
|
54
66
|
if (call.flag("compare")) {
|
|
55
67
|
const db = openForRead();
|
|
56
68
|
try {
|
|
@@ -62,6 +74,14 @@ await runScript({
|
|
|
62
74
|
const setPc = call.address("set-pc");
|
|
63
75
|
const tree = readProjectTree(sourceRoot);
|
|
64
76
|
if (!tree.files.some((file) => file.path === entry)) throw new Refusal("not-found", `${entry} is not a file in ${sourceRoot}. Give the main source file relative to the source root.`);
|
|
77
|
+
// ACME sees only the files of the source root: an include folder holds some of them. A path out of the root is refused later.
|
|
78
|
+
for (const folder of call.strings("include")) {
|
|
79
|
+
const inside = posix.normalize(folder.replaceAll("\\", "/")).replace(/\/+$/, "");
|
|
80
|
+
if (inside === "." || inside === ".." || inside.startsWith("../") || posix.isAbsolute(inside)) continue;
|
|
81
|
+
if (!tree.files.some((file) => file.path.startsWith(`${inside}/`))) {
|
|
82
|
+
throw new Refusal("not-found", `There is no include folder ${folder} with files in the source root ${sourceRoot}. Give --include a folder relative to the source root.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
65
85
|
const params: AcmeParams = { files: tree.files, entrySource: entry, includeDirs: call.strings("include"), defines: Object.fromEntries(call.strings("define").map(parseDefine)), ...(setPc === undefined ? {} : { setPc }) };
|
|
66
86
|
|
|
67
87
|
const { result, attachments } = await assemble(params, tree.contents, localToolContext());
|
|
@@ -72,7 +92,8 @@ await runScript({
|
|
|
72
92
|
const compared = program === undefined ? undefined : compareBuild(attachments![0]!, program);
|
|
73
93
|
// An include file of platform names gives many constants that the program does not use.
|
|
74
94
|
const all = call.flag("all-symbols");
|
|
75
|
-
const
|
|
95
|
+
const shown = result.symbols!.filter((symbol) => all || symbol.kind === "address" || symbol.used);
|
|
96
|
+
const symbolsPath = call.string("out-symbols");
|
|
76
97
|
return {
|
|
77
98
|
// A build that differs from its reference is a failed check.
|
|
78
99
|
result: compared !== undefined && !compared.identical ? "FAIL" : "PASS",
|
|
@@ -80,12 +101,9 @@ await runScript({
|
|
|
80
101
|
...written,
|
|
81
102
|
...(plain ? { plain: true } : {}),
|
|
82
103
|
loadRange: { start: formatC64Address(range.start), end: formatC64Address(range.end), bytes: range.bytes },
|
|
83
|
-
symbols:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
: { name: symbol.name, kind: "constant", value: `$${symbol.value.toString(16).padStart(4, "0")}`, used: symbol.used },
|
|
87
|
-
),
|
|
88
|
-
...(all ? {} : { unusedConstants: result.symbols!.length - listed.length }),
|
|
104
|
+
symbols: listedItems(shown, call.count("max-listed")!, showSymbol, "a larger --max-listed, or --out-symbols <file.json> for all symbols"),
|
|
105
|
+
...(symbolsPath === undefined ? {} : { symbolsFile: writeReported(symbolsPath, `${JSON.stringify({ symbols: shown.map(showSymbol) }, null, 1)}\n`, { what: "--out-symbols" }) }),
|
|
106
|
+
...(all ? {} : { unusedConstants: result.symbols!.length - shown.length }),
|
|
89
107
|
diagnostics: result.diagnostics,
|
|
90
108
|
...(compared === undefined ? {} : { compare: compared }),
|
|
91
109
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The byte check of a build: the assembled program against the program
|
|
2
2
|
// in the knowledge, segment by segment.
|
|
3
3
|
|
|
4
|
-
import { formatC64Address, prgRange } from "#src/c64.ts";
|
|
4
|
+
import { formatC64Address, formatC64Byte, prgRange } from "#src/c64.ts";
|
|
5
5
|
|
|
6
6
|
export interface SegmentCheck {
|
|
7
7
|
start: string;
|
|
@@ -10,7 +10,8 @@ export interface SegmentCheck {
|
|
|
10
10
|
/** Bytes of the segment that the build does not hold. */
|
|
11
11
|
uncovered?: number;
|
|
12
12
|
differences?: number;
|
|
13
|
-
|
|
13
|
+
/** The first address that differs, with its byte in the build and in the knowledge, each as $ and two hex digits. */
|
|
14
|
+
firstDifference?: { address: string; built: string; reference: string };
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/** The program that the build is checked against: its segments and its bytes in them. */
|
|
@@ -34,7 +35,7 @@ export function compareBuild(prg: Uint8Array, program: Reference): { identical:
|
|
|
34
35
|
const referenceByte = program.byteAt(address);
|
|
35
36
|
if (builtByte === referenceByte) continue;
|
|
36
37
|
differences++;
|
|
37
|
-
first ??= { address: formatC64Address(address), built: builtByte, reference: referenceByte };
|
|
38
|
+
first ??= { address: formatC64Address(address), built: formatC64Byte(builtByte), reference: formatC64Byte(referenceByte) };
|
|
38
39
|
}
|
|
39
40
|
const uncovered = segment.end - segment.start + 1 - (to - from + 1);
|
|
40
41
|
return {
|
|
@@ -30,6 +30,7 @@ node <skill>/scripts/basic.ts list extracted/game.prg
|
|
|
30
30
|
- 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.
|
|
31
31
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
32
32
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
33
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
33
34
|
- 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.
|
|
34
35
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
35
36
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
package/skills/c64-disk/SKILL.md
CHANGED
|
@@ -47,6 +47,7 @@ File names show the characters that the C64 shows in a directory list. A byte th
|
|
|
47
47
|
- 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.
|
|
48
48
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
49
49
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
50
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
50
51
|
- 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.
|
|
51
52
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
52
53
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -97,9 +98,9 @@ The directory, the BAM, an entry or a sector chain as JSON.
|
|
|
97
98
|
## Failure and conflicts
|
|
98
99
|
|
|
99
100
|
- `not-found`: the image file is missing, or the image has no file with that name. Read the directory again and compare the names.
|
|
100
|
-
- `media-error`: the image has a defect or is not
|
|
101
|
+
- `media-error`: the image, the T64 file or the CRT file has a defect, or its content is not of its type. A CRT with a hardware type that the script does not read also gives `media-error`. The message tells the problem, for example a sector chain that loops. Tell the user. Do not change the file.
|
|
101
102
|
- `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 disk parser, disassembler or emulator script.
|
|
102
|
-
- `invalid-input`: an argument or a path is wrong. Read the message and correct the call.
|
|
103
|
+
- `invalid-input`: an argument or a path is wrong. A file with an extension that the command does not take is also wrong. The message gives each type that the command takes. Read the message and correct the call.
|
|
103
104
|
|
|
104
105
|
A file that is not `closed` was not written fully. Its data can be incomplete.
|
|
105
106
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { formatC64Address } from "#src/c64.ts";
|
|
6
6
|
import { isRefusal, Refusal } from "#src/errors.ts";
|
|
7
|
-
import { inspectDisk } from "#src/host-client/tools.ts";
|
|
7
|
+
import { DISK_IMAGE_TYPES, inspectDisk } from "#src/host-client/tools.ts";
|
|
8
8
|
import { readProjectFile, writeReported } from "#src/host-client/transfer.ts";
|
|
9
9
|
import { OPTIONS, runScript, type Call, type SubjectSpec } from "#src/script.ts";
|
|
10
10
|
import { parseCrt, parseT64, tapeFile } from "./lib/containers.ts";
|
|
@@ -13,14 +13,30 @@ const IMAGE: SubjectSpec = { name: "<image>", help: "a .d64, .d71, .d81 or .g64
|
|
|
13
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}" };
|
|
14
14
|
|
|
15
15
|
type Kind = "disk" | "t64" | "crt";
|
|
16
|
-
|
|
16
|
+
/** The kind of a file by its extension; undefined for a file that no command takes. */
|
|
17
|
+
function kindOf(image: string): Kind | undefined {
|
|
18
|
+
const extension = image.toLowerCase().split(".").at(-1) ?? "";
|
|
19
|
+
if (extension === "t64" || extension === "crt") return extension;
|
|
20
|
+
return (DISK_IMAGE_TYPES as readonly string[]).includes(extension) ? "disk" : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const KIND_TEXT: Record<Kind, string> = { disk: "a .d64, .d71, .d81 or .g64 disk image", t64: "a .t64 tape file", crt: "a .crt cartridge file" };
|
|
24
|
+
|
|
25
|
+
/** The kind of the image of a command; refuses a file of a kind that the command does not take, and names every kind that it takes. */
|
|
26
|
+
function kindFor(command: string, image: string, takes: readonly Kind[]): Kind {
|
|
27
|
+
const kind = kindOf(image);
|
|
28
|
+
if (kind !== undefined && takes.includes(kind)) return kind;
|
|
29
|
+
const kinds = takes.map((item) => KIND_TEXT[item]);
|
|
30
|
+
const list = kinds.length === 1 ? kinds[0]! : `${kinds.slice(0, -1).join(", ")} or ${kinds.at(-1)}`;
|
|
31
|
+
throw new Refusal("invalid-input", `${image} is not a file that ${command} takes. Give ${list}.`);
|
|
32
|
+
}
|
|
17
33
|
|
|
18
34
|
/** Runs `body` and names the file in a refusal of its content. */
|
|
19
35
|
function about<T>(image: string, body: () => T): T {
|
|
20
36
|
try {
|
|
21
37
|
return body();
|
|
22
38
|
} catch (error) {
|
|
23
|
-
if (isRefusal(error) && error.code === "
|
|
39
|
+
if (isRefusal(error) && error.code === "media-error") throw new Refusal(error.code, `${image}: ${error.message}`);
|
|
24
40
|
throw error;
|
|
25
41
|
}
|
|
26
42
|
}
|
|
@@ -55,9 +71,7 @@ function cartridge(image: string) {
|
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
/** 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
|
-
};
|
|
74
|
+
const diskOnly = (call: Call, action: string) => kindFor(action, call.subject(0), ["disk"]);
|
|
61
75
|
|
|
62
76
|
await runScript({
|
|
63
77
|
name: "disk",
|
|
@@ -69,7 +83,7 @@ await runScript({
|
|
|
69
83
|
subjects: [{ ...IMAGE, help: "a disk image, a .t64 tape file or a .crt cartridge file, relative to the project folder" }],
|
|
70
84
|
run: async (call) => {
|
|
71
85
|
const image = call.subject(0);
|
|
72
|
-
const kind =
|
|
86
|
+
const kind = kindFor("directory", image, ["disk", "t64", "crt"]);
|
|
73
87
|
if (kind === "t64") return tapeDirectory(image);
|
|
74
88
|
if (kind === "crt") {
|
|
75
89
|
const { header, chips } = cartridge(image);
|
|
@@ -109,12 +123,12 @@ await runScript({
|
|
|
109
123
|
name: "read",
|
|
110
124
|
summary: "Writes the bytes of one file into the project. It also reads a .t64 tape file.",
|
|
111
125
|
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 } },
|
|
126
|
+
options: { out: { ...OPTIONS.out, output: "file", value: "<file>", help: "the file to write, relative to the project folder", required: true } },
|
|
113
127
|
run: async (call) => {
|
|
114
128
|
const [image, name] = [call.subject(0), call.subject(1)];
|
|
115
129
|
const out = call.string("out")!;
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
if (kindOf(image) === "crt") throw new Refusal("invalid-input", `read takes a disk image or a .t64 tape file. Use the command crt for the cartridge file ${image}.`);
|
|
131
|
+
const kind = kindFor("read", image, ["disk", "t64"]);
|
|
118
132
|
if (kind === "t64") {
|
|
119
133
|
const { bytes } = readProjectFile(image);
|
|
120
134
|
const tape = about(image, () => parseT64(bytes));
|
|
@@ -131,10 +145,10 @@ await runScript({
|
|
|
131
145
|
name: "crt",
|
|
132
146
|
summary: "Writes each CHIP packet of a cartridge as a PRG, and crt.json with the header and the packets.",
|
|
133
147
|
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 } },
|
|
148
|
+
options: { out: { ...OPTIONS.out, output: "folder", value: "<dir>", help: "the folder for the files, relative to the project folder", required: true } },
|
|
135
149
|
run: (call) => {
|
|
136
150
|
const image = call.subject(0);
|
|
137
|
-
|
|
151
|
+
kindFor("crt", image, ["crt"]);
|
|
138
152
|
const out = call.string("out")!;
|
|
139
153
|
const { crt, header, chips } = cartridge(image);
|
|
140
154
|
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" }));
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { formatC64Address, petsciiNameToText } from "#src/c64.ts";
|
|
7
7
|
import { Refusal } from "#src/errors.ts";
|
|
8
|
+
import { hasSignature } from "#src/media.ts";
|
|
8
9
|
|
|
9
10
|
/** The CRT hardware types this toolkit reads, by type number. */
|
|
10
11
|
export const CRT_TYPES: Record<number, string> = { 0: "normal", 5: "Ocean", 19: "Magic Desk", 32: "EasyFlash" };
|
|
@@ -30,36 +31,34 @@ export interface Crt {
|
|
|
30
31
|
chips: CrtChip[];
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const SIGNATURE = "C64 CARTRIDGE ";
|
|
34
|
-
|
|
35
34
|
export function parseCrt(bytes: Uint8Array): Crt {
|
|
36
35
|
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
37
|
-
if (buffer.length < 0x40 ||
|
|
36
|
+
if (buffer.length < 0x40 || !hasSignature("crt", buffer)) throw new Refusal("media-error", "the file is not a CRT: it does not start with C64 CARTRIDGE");
|
|
38
37
|
const headerLength = buffer.readUInt32BE(0x10);
|
|
39
38
|
const type = buffer.readUInt16BE(0x16);
|
|
40
39
|
const exrom = buffer[0x18]!;
|
|
41
40
|
const game = buffer[0x19]!;
|
|
42
41
|
const typeName = CRT_TYPES[type];
|
|
43
42
|
if (typeName === undefined) {
|
|
44
|
-
throw new Refusal("
|
|
43
|
+
throw new Refusal("media-error", `the CRT has hardware type ${type}; this toolkit reads types ${Object.entries(CRT_TYPES).map(([number, name]) => `${number} (${name})`).join(", ")}`);
|
|
45
44
|
}
|
|
46
45
|
const name = buffer.toString("latin1", 0x20, 0x40).replace(/\0+$/, "");
|
|
47
46
|
const chips: CrtChip[] = [];
|
|
48
47
|
let offset = Math.max(headerLength, 0x40);
|
|
49
48
|
while (offset + 0x10 <= buffer.length) {
|
|
50
|
-
if (buffer.toString("latin1", offset, offset + 4) !== "CHIP") throw new Refusal("
|
|
49
|
+
if (buffer.toString("latin1", offset, offset + 4) !== "CHIP") throw new Refusal("media-error", `the CRT has no CHIP packet at offset ${offset}`);
|
|
51
50
|
const packetLength = buffer.readUInt32BE(offset + 4);
|
|
52
51
|
const chipType = buffer.readUInt16BE(offset + 8);
|
|
53
52
|
const bank = buffer.readUInt16BE(offset + 10);
|
|
54
53
|
const address = buffer.readUInt16BE(offset + 12);
|
|
55
54
|
const size = buffer.readUInt16BE(offset + 14);
|
|
56
|
-
if (packetLength < 0x10 || offset + packetLength > buffer.length) throw new Refusal("
|
|
57
|
-
if (bank > 255) throw new Refusal("
|
|
58
|
-
if (size > packetLength - 0x10 || address + size > 0x10000) throw new Refusal("
|
|
55
|
+
if (packetLength < 0x10 || offset + packetLength > buffer.length) throw new Refusal("media-error", `the CHIP packet at offset ${offset} runs past the end of the file`);
|
|
56
|
+
if (bank > 255) throw new Refusal("media-error", `the CHIP packet at offset ${offset} has bank ${bank}; banks go up to 255`);
|
|
57
|
+
if (size > packetLength - 0x10 || address + size > 0x10000) throw new Refusal("media-error", `the CHIP packet at offset ${offset} has a wrong size ${size} at ${formatC64Address(address)}`);
|
|
59
58
|
if (size > 0) chips.push({ bank, address, size, chipType, data: buffer.subarray(offset + 0x10, offset + 0x10 + size) });
|
|
60
59
|
offset += packetLength;
|
|
61
60
|
}
|
|
62
|
-
if (chips.length === 0) throw new Refusal("
|
|
61
|
+
if (chips.length === 0) throw new Refusal("media-error", "the CRT has no CHIP packet with data");
|
|
63
62
|
const crt: Crt = { name, type, typeName, exrom, game, chips };
|
|
64
63
|
if (type === 0) crt.mode = exrom === 0 && game === 1 ? "8k" : exrom === 0 && game === 0 ? "16k" : "ultimax";
|
|
65
64
|
return crt;
|
|
@@ -85,7 +84,7 @@ export interface Tape {
|
|
|
85
84
|
|
|
86
85
|
export function parseT64(bytes: Uint8Array): Tape {
|
|
87
86
|
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
88
|
-
if (buffer.length < 0x40 || !
|
|
87
|
+
if (buffer.length < 0x40 || !hasSignature("t64", buffer)) throw new Refusal("media-error", "the file is not a T64: it does not start with C64");
|
|
89
88
|
const maxEntries = buffer.readUInt16LE(0x22);
|
|
90
89
|
const name = buffer.toString("latin1", 0x28, 0x40).replace(/[\0 ]+$/, "");
|
|
91
90
|
const raw: Array<Omit<TapeEntry, "bytes" | "end"> & { end: number }> = [];
|
|
@@ -107,7 +106,7 @@ export function parseT64(bytes: Uint8Array): Tape {
|
|
|
107
106
|
// Many T64 files have a wrong end address: the next file or the end of the file bounds each one.
|
|
108
107
|
const offsets = [...raw.map((entry) => entry.offset), buffer.length].sort((a, b) => a - b);
|
|
109
108
|
const entries = raw.map((entry): TapeEntry => {
|
|
110
|
-
if (entry.offset >= buffer.length) throw new Refusal("
|
|
109
|
+
if (entry.offset >= buffer.length) throw new Refusal("media-error", `the T64 entry ${entry.name} starts past the end of the file`);
|
|
111
110
|
const available = offsets.find((offset) => offset > entry.offset)! - entry.offset;
|
|
112
111
|
const stated = entry.end - entry.start;
|
|
113
112
|
const bytes = stated > 0 && stated <= available ? stated : Math.min(available, 0x10000 - entry.start);
|
package/skills/c64-docs/SKILL.md
CHANGED
|
@@ -49,6 +49,7 @@ node <skill>/scripts/docs.ts link README.md docs --source src --check
|
|
|
49
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
50
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
51
51
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
52
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
52
53
|
- 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
54
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
54
55
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -144,7 +145,7 @@ Do not store:
|
|
|
144
145
|
- `invalid-input` that names an old revision: the knowledge changed after the source. Generate the source again, then run `docs.ts generate`.
|
|
145
146
|
- `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.
|
|
146
147
|
- `unknown-name`: the page uses a name that is not in the knowledge. Record the name with c64-knowledge, or correct the page.
|
|
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` ``.
|
|
148
|
+
- `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` ``. The script does not link such a mention until it is correct.
|
|
148
149
|
- `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.
|
|
149
150
|
- `stale-reference`: the source or the reference is older than the knowledge. Do steps 2 and 3 of the workflow again.
|
|
150
151
|
- `conflict`: the knowledge has conflicts from a git merge. Run `knowledge.ts merge` of c64-knowledge.
|
|
@@ -297,7 +297,7 @@ await runScript({
|
|
|
297
297
|
summary: "Writes the reference pages: a page for each module, and pages for the names, the variables, the tables, the memory map and the graphics.",
|
|
298
298
|
subjects: [{ name: "<source-dir>", help: SOURCE.help }],
|
|
299
299
|
options: {
|
|
300
|
-
out: { ...OPTIONS.out, value: "<dir>", help: "the folder for the reference pages, relative to the project folder, for example docs/reference. generate writes only there and refuses a page in it that it did not write", required: true },
|
|
300
|
+
out: { ...OPTIONS.out, output: "folder", value: "<dir>", help: "the folder for the reference pages, relative to the project folder, for example docs/reference. generate writes only there and refuses a page in it that it did not write", required: true },
|
|
301
301
|
"max-listed": OPTIONS["max-listed"],
|
|
302
302
|
},
|
|
303
303
|
notes: "When the knowledge has the program of the source (knowledge.ts image of the c64-knowledge skill), the reference decodes the tables and shows the graphics.",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// its line in the source; the address after it links to the entry of the name
|
|
3
3
|
// in the reference, or, for an address inside the routine or the data of the
|
|
4
4
|
// name ("draw_room $0AAF", a place in draw_room), to the line of that address. A link that points into the source or the reference is
|
|
5
|
-
// managed:
|
|
6
|
-
// person wrote them;
|
|
5
|
+
// managed: docs.ts link writes it again when a line moves. Other links stay as the
|
|
6
|
+
// person wrote them; docs.ts link only checks that their targets exist.
|
|
7
7
|
|
|
8
8
|
import { posix } from "node:path";
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ export interface LinkContext {
|
|
|
24
24
|
lineOf: (address: number) => string | undefined;
|
|
25
25
|
/** The name of the routine or data that holds an address, for a message. */
|
|
26
26
|
placeOf: (address: number) => string | undefined;
|
|
27
|
-
/** True for a project path that
|
|
27
|
+
/** True for a project path that docs.ts link manages: a file of the source or a page of the reference. */
|
|
28
28
|
managed: (path: string) => boolean;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -102,6 +102,8 @@ export function linkPage(text: string, context: LinkContext): { text: string; ch
|
|
|
102
102
|
const expected = `$${(address + delta).toString(16).toUpperCase().padStart(4, "0")}`;
|
|
103
103
|
const holder = context.placeOf(given!);
|
|
104
104
|
report("wrong-address", `\`${name}${offset ?? ""}\` is ${expected} in the knowledge, and the page gives ${addressText}${holder === undefined || holder === name ? "" : `, a place in \`${holder}\``}. Correct the name or the address.${digits!.length <= 2 ? ` For a value, write \`${name}\` = \`${addressText}\`.` : ""}`);
|
|
105
|
+
// A mention with a problem gets no link: the text stays as the person wrote it until it is correct.
|
|
106
|
+
return whole;
|
|
105
107
|
}
|
|
106
108
|
const shownName = `${TICK}${name}${offset ?? ""}${TICK}`;
|
|
107
109
|
const code = context.codeOf(name);
|
|
@@ -12,7 +12,7 @@ import type { CommentRow, ModuleRow, ReferenceRow, RegionRow, SymbolRow } from "
|
|
|
12
12
|
import type { RegionType } from "#src/knowledge/schema.ts";
|
|
13
13
|
import type { SourceMap } from "./source-map.ts";
|
|
14
14
|
|
|
15
|
-
/** The first line of every generated page. A page without it is a page of a person, and
|
|
15
|
+
/** The first line of every generated page. A page without it is a page of a person, and docs.ts generate never writes over it. */
|
|
16
16
|
export const GENERATED_MARK = "<!-- Generated by c64-docs";
|
|
17
17
|
|
|
18
18
|
export interface ReferenceInput {
|
|
@@ -183,7 +183,7 @@ export function buildReference(input: ReferenceInput): { files: ReferenceFile[];
|
|
|
183
183
|
}
|
|
184
184
|
const titleOf = (file: string) => input.modules.find((module) => module.file === file)?.title;
|
|
185
185
|
|
|
186
|
-
const header = `${GENERATED_MARK} from knowledge revision ${input.revision} and ${map.pathOf("source-map.json")}. Do not edit: change the knowledge, generate the source again, then run
|
|
186
|
+
const header = `${GENERATED_MARK} from knowledge revision ${input.revision} and ${map.pathOf("source-map.json")}. Do not edit: change the knowledge, generate the source again, then run docs.ts generate. -->`;
|
|
187
187
|
const files: ReferenceFile[] = [];
|
|
188
188
|
const write = (path: string, lines: string[]) => files.push({ path, bytes: Buffer.from(`${[header, ...lines].join("\n").replace(/\n{3,}/g, "\n\n")}\n`, "utf8") });
|
|
189
189
|
|
|
@@ -57,6 +57,7 @@ For a large result, give `out` with a path in the project. `c64_memory_read`, `c
|
|
|
57
57
|
|
|
58
58
|
- Only the first byte of an instruction gets execute. Thus `read` without `out` gives a new range at each instruction of code. For code, use `summary`, or `read` with `out`.
|
|
59
59
|
- A read on a byte right after executed code, or at a return address, can be a dummy read of the 6502. The CPU reads such a byte but does not use it.
|
|
60
|
+
- VICE has a known flaw: it uses only one bit of `$01` to find RAM or ROM. At `$A000-$BFFF` it gives `rom` when bit 0 is set, also when bit 1 is clear and the CPU used the RAM there. Many games set `$01` to `$35`. Then code that runs at `$A000-$BFFF` shows as ROM. `emulatorFlaw` in the result tells when this can occur. Every access at `$D000-$DFFF` shows as I/O.
|
|
60
61
|
|
|
61
62
|
`c64_profile` gives the routines that used the most cycles since the session started, the last `clear` or the last `c64_window`. Use action `clear` before the part of the program that you want to measure. The profiler gives the cycles to the routine that a JSR called last. Code that the CPU enters with JMP or a branch counts for that routine. After `c64_window` or a snapshot restore, the profiler of VICE keeps the routines that ran before the restore. Then `c64_profile` can give the cycles of the program to such a routine, for example `$fd50`. Do not trust the routine addresses of `c64_profile` after a restore.
|
|
62
63
|
|
|
@@ -80,7 +81,7 @@ Write operations need a stopped CPU. Stop the CPU with `c64_execution` action `p
|
|
|
80
81
|
|
|
81
82
|
A cartridge from `c64_autostart` stays in the C64 through every reset, as on a real C64. `c64_status` gives `cartridge: true` while the C64 has it, and `cartridge: false` when it does not. `c64_autostart` of a file that is not a cartridge removes the cartridge first. `c64_autostart` also sets both joysticks to the center first. `joysticksCentred` gives the ports that it changed.
|
|
82
83
|
|
|
83
|
-
Step, next and until-return work only in space `c64`. The emulator cannot stop the disk drive CPU after one instruction. To find when the drive executes an address, use `c64_run_until` with an address target in space `drive8`, or a breakpoint in space `drive8`. The drive can execute a few more instructions before it stops. The computer completes its current instruction. The `pc` in the result is the drive address where it stopped.
|
|
84
|
+
Step, next and until-return work only in space `c64`. The emulator cannot stop the disk drive CPU after one instruction. To find when the drive executes an address, use `c64_run_until` with an address target in space `drive8`, or a breakpoint in space `drive8`. The drive can execute a few more instructions before it stops. The computer completes its current instruction. The `pc` in the result is the drive address where it stopped. `space` in the result is `drive8`. A breakpoint or a watchpoint in space `drive8` that stops `advance-frames` gives the same `pc` and `space`. `c64_status` also gives them. A memory target or a memory condition tests only space `c64`.
|
|
84
85
|
|
|
85
86
|
`c64_keyboard` puts keys in the keyboard queue of the KERNAL. A program that reads the keyboard matrix directly does not see them. Many games do this. No MCP tool presses a key of the keyboard matrix. For such a program:
|
|
86
87
|
|
|
@@ -137,7 +138,13 @@ An error is `{"error": {"code": "...", "message": "..."}}`. The message tells wh
|
|
|
137
138
|
- `machine-running`: stop the CPU first, then try again.
|
|
138
139
|
- `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 disk parser, disassembler or emulator script. Do not try to repair the installation.
|
|
139
140
|
- `c64_run_until` with `stopReason` `timeout`: the target did not occur in the frame limit. This is evidence too. Report it.
|
|
140
|
-
- `
|
|
141
|
+
- `stopReason` `jam`: the CPU executed a JAM opcode and stopped. Report the address. The CPU stays stopped, as on a real C64. Each run gives `jam` again. Step, next, until-return and resume refuse with `operation-failed`. Only `c64_reset`, a snapshot restore or `c64_autostart` starts the CPU again.
|
|
142
|
+
- After a JAM, VICE records no CPU history until the next reset. If the CPU ran again after a snapshot restore, `c64_cpu_history` refuses with `operation-failed`. `c64_run_until` then gives `historyMissing` in place of `history`. Use `c64_reset`, or use `c64_trace`.
|
|
143
|
+
- `invalid-input`: the message names the field and the rule. Change the call to obey the rule.
|
|
144
|
+
- `not-found`: a file, a snapshot, a baseline or a point that the call names does not exist.
|
|
145
|
+
- `unsupported-in-space`: the action works only in space `c64`.
|
|
146
|
+
- `limit-exceeded`: the call goes past a limit of the session, for example the number of snapshots. The message names the limit.
|
|
147
|
+
- `operation-failed`: the emulator did not do the operation. The message tells why.
|
|
141
148
|
|
|
142
149
|
If the runtime evidence does not agree with the current knowledge, do not change the knowledge. Report the conflict and hand it to c64-knowledge.
|
|
143
150
|
|
|
@@ -27,7 +27,7 @@ One of these:
|
|
|
27
27
|
node <skill>/scripts/knowledge.ts <command> [subjects] [options]
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
A symbol is the name of an address from the user or the LLM. An analyzer name is a second name from DXA, Ghidra or an idiom, for example `FUN_17e2`. Each analyzer keeps its own name at an address. `at`, `search` and `export` show the analyzer names. The generated source uses only the symbols, unless c64-source gets `--analyzer-names`. To use an analyzer name in the source, `rename` the address with it.
|
|
30
|
+
A symbol is the name of an address from the user or the LLM. An analyzer name is a second name from DXA, Ghidra or an idiom, for example `FUN_17e2`. Each analyzer keeps its own name at an address. `at`, `search` and `export` show the analyzer names. The generated source uses only the symbols, unless c64-source gets `--analyzer-names`. To use an analyzer name in the source, `rename` the address with it. A symbol name is a label for ACME: a letter or underscore, then letters, digits or underscores. The knowledge refuses the name of a 6502 instruction, for example `lda`, in upper or lower case.
|
|
31
31
|
|
|
32
32
|
| Need | Command |
|
|
33
33
|
|---|---|
|
|
@@ -42,6 +42,7 @@ A symbol is the name of an address from the user or the LLM. An analyzer name is
|
|
|
42
42
|
| What the knowledge holds of the program | `program` |
|
|
43
43
|
| The code of a range with names, comments, access and references | `listing '$0400-$04ff'` |
|
|
44
44
|
| Name the symbol at an address | `rename '$2100' update_player --kind routine` |
|
|
45
|
+
| The rows that one revision added and closed, also memory, entry and coverage rows | `revision 20261015T2145` |
|
|
45
46
|
| Remove a symbol | `remove-symbol '$2100'` |
|
|
46
47
|
| Classify memory | `classify '$3000-$303f' sprite` |
|
|
47
48
|
| Classify a table of records | `classify '$3000-$303b' bytes --element 6` |
|
|
@@ -62,12 +63,12 @@ A symbol is the name of an address from the user or the LLM. An analyzer name is
|
|
|
62
63
|
| Files of generated source | `modules` |
|
|
63
64
|
| The address spaces that hold knowledge | `spaces` |
|
|
64
65
|
| History | `history '$2100'`, `history --name update_player`, `history --macro inc16`, `revisions`, `revision 20261015T2145` |
|
|
65
|
-
| Go back to an earlier state | `revert 20261015T2145` |
|
|
66
|
+
| Go back to an earlier state | `revert 20261015T2145` for all spaces, or `revert 20261015T2145 --space bank3` for one space |
|
|
66
67
|
| Decide the conflicts after a git merge | `merge --reason 'merge of two computers'` |
|
|
67
68
|
|
|
68
69
|
A revision has an id such as `20261015T214501.123-k3f9qz`. The id starts with the date and time. A command takes the full id, or a start of the id that only one revision has. The revision `0` is the knowledge before the first revision.
|
|
69
70
|
|
|
70
|
-
Every command takes `--space`: `c64` (the default), `drive8` for code in the 1541 drive, or `bank0` to `bank255` for cartridge banks. Drive code and banks use the same addresses as the C64, so each space has its own knowledge. A name is unique inside one space. In a batch, a write can name its own `space`.
|
|
71
|
+
Every command takes `--space`: `c64` (the default), `drive8` for code in the 1541 drive, or `bank0` to `bank255` for cartridge banks. `revert` is different: without `--space`, it reverts all spaces. With `--space`, it reverts only that space. Drive code and banks use the same addresses as the C64, so each space has its own knowledge. A name is unique inside one space. In a batch, a write can name its own `space`.
|
|
71
72
|
|
|
72
73
|
Symbol kinds are `label`, `routine`, `variable`, `data` and `vector`. Reference kinds are `call`, `jump`, `read`, `write`, `reference` and `pointer`. Only an operand expression writes a `pointer` reference.
|
|
73
74
|
|
|
@@ -94,6 +95,7 @@ Region types:
|
|
|
94
95
|
- 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.
|
|
95
96
|
- A range is `<start>-<end>`, for example `'$c000-$cfff'`. In a JSON file, a range is `start` and `end`.
|
|
96
97
|
- Bytes in a JSON file are hex in a field `data`, for example `"data": "a900"`.
|
|
98
|
+
- The script examines each `--out` path before it starts work. It refuses a path in `.c64-re-tools/knowledge/` and the index of the knowledge, because only c64-knowledge writes them. It refuses a folder where the command writes a file, and a file where the command writes a folder.
|
|
97
99
|
- 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.
|
|
98
100
|
- An error is `{"error": {"code": "...", "message": "..."}}`. The message tells what is wrong and what to do.
|
|
99
101
|
- The exit status is 0 for done or PASS, 1 for FAIL, 2 for an error and 3 for INCONCLUSIVE.
|
|
@@ -139,6 +141,7 @@ The knowledge holds the program, so a project needs no image or coverage files.
|
|
|
139
141
|
- `image` writes only the bytes that changed. The same image again writes nothing. A new image replaces the program of the space. The history keeps the old bytes.
|
|
140
142
|
- `coverage` joins the maps. It keeps each address that a run executed, read or wrote. It also keeps each address that one run wrote and executed: self-modified code. `coverage` puts each map into the space of `--space`. You can add the same map to each space. A map that the space has is not added again.
|
|
141
143
|
- The capture and the maps are not necessary after the import. Keep them in `.c64-re-tools/local/`.
|
|
144
|
+
- A `revert` also gives back the coverage maps of that revision. A `revert` to a revision before an import removes the map and its ranges. Then you can import the map again, and `--evidence coverage:<map>` refuses it until you do.
|
|
142
145
|
|
|
143
146
|
For more than a few writes, use `apply -` and give the writes on standard input as one JSON list. Each write has `op` and the arguments of the command with the same name, for example:
|
|
144
147
|
|
|
@@ -171,7 +174,7 @@ The ops and their fields:
|
|
|
171
174
|
| `unmacro` | `name` |
|
|
172
175
|
| `use` | `start`, `end`, `macro`, `args` (a list or a comma list) |
|
|
173
176
|
|
|
174
|
-
A `line` comment is a line of its own above the address. A `side` comment is at the end of the line of the address. The script checks an `operand`, `patch` or `use` write against the program of the knowledge in the space of `--space`. All writes become one revision. If the knowledge refuses one write, the script writes nothing, and the error names the write.
|
|
177
|
+
Each write can also have `space`. A `line` comment is a line of its own above the address. A `side` comment is at the end of the line of the address. The script checks an `operand`, `patch` or `use` write against the program of the knowledge in the space of `--space`. All writes become one revision. If the knowledge refuses one write, the script writes nothing, and the error names the write.
|
|
175
178
|
|
|
176
179
|
The c64-provenance skill writes a batch with `match --out`. The batch holds the names of library routines at their addresses in this program. Read the `batch.leftOut` of its result first. Then apply the batch with the command in its `apply`.
|
|
177
180
|
|
|
@@ -213,6 +216,7 @@ Give one selector:
|
|
|
213
216
|
| `--value <symbol>` | The symbol plus the offset | `lda bar_colours-1,x` |
|
|
214
217
|
| `--expr <text>` | ACME expression text with names and numbers | `lda #(sprite_bank + 5*64) / 64` |
|
|
215
218
|
|
|
219
|
+
- The address is the start of an instruction in a code region. The script decodes the instructions from the start of the region, as `listing` does. It refuses an address in no code region and an operand byte of an instruction. Classify the code first.
|
|
216
220
|
- `--offset` adds a whole number to the symbol. Write a negative offset as `--offset=-1`.
|
|
217
221
|
- A name is a symbol of the knowledge or a platform name of the c64-memory-map skill.
|
|
218
222
|
- `--expr` takes `$`, `%` and decimal numbers, names, brackets, the operators `+ - * / % << >> >>> & | XOR`, and `<`, `>`, `-` and `!` before an operand.
|
|
@@ -277,7 +281,7 @@ The script records the origin `llm`. Use `--by user` only when the user decides
|
|
|
277
281
|
|
|
278
282
|
## Result
|
|
279
283
|
|
|
280
|
-
For a read: the current or historical knowledge in compact JSON.
|
|
284
|
+
For a read: the current or historical knowledge in compact JSON. Each list of a list command has `count` and `listed`. `--max-listed` limits `listed`. The default is 50. When the list is longer, `rest` tells how to get the other items. For `revisions`, give `--before` with the oldest revision in `listed`.
|
|
281
285
|
|
|
282
286
|
For a write: the new revision and the current value after the write. If the write replaces a symbol, a region or an operand expression of a different origin, `previous` gives that row as it was before the write. For example, an `llm` write over a `user` name gives `previous`. Read `previous` and tell the user about the change. For `apply`, `replaced` lists each write that replaced a row. Each item has the number of the write, its `op` and its `previous`.
|
|
283
287
|
|