@henols/c64-re-tools 2.0.0-rc.2 → 2.0.0-rc.20
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/README.md +28 -62
- package/distribution/package.ts +1 -16
- package/distribution/plugin.ts +5 -3
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +25 -6
- package/skills/c64-assembler/scripts/assemble.ts +45 -10
- package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
- package/skills/c64-basic/SKILL.md +15 -7
- package/skills/c64-basic/scripts/basic.ts +4 -4
- package/skills/c64-disk/SKILL.md +34 -11
- package/skills/c64-disk/scripts/disk.ts +65 -10
- package/skills/c64-disk/scripts/lib/containers.ts +124 -0
- package/skills/c64-docs/SKILL.md +137 -0
- package/skills/c64-docs/references/page-types.md +113 -0
- package/skills/c64-docs/references/style.md +43 -0
- package/skills/c64-docs/scripts/generate.ts +102 -0
- package/skills/c64-docs/scripts/lib/links.ts +155 -0
- package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
- package/skills/c64-docs/scripts/lib/reference.ts +406 -0
- package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
- package/skills/c64-docs/scripts/link.ts +193 -0
- package/skills/c64-emulator/SKILL.md +29 -8
- package/skills/c64-knowledge/SKILL.md +201 -15
- package/skills/c64-knowledge/scripts/knowledge.ts +927 -78
- package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
- package/skills/c64-memory-map/SKILL.md +14 -2
- package/skills/c64-memory-map/scripts/memmap.ts +15 -4
- package/skills/c64-provenance/SKILL.md +100 -11
- package/skills/c64-provenance/scripts/lib/compare.ts +152 -0
- package/skills/c64-provenance/scripts/lib/library.ts +584 -0
- package/skills/c64-provenance/scripts/lib/runtime.ts +191 -0
- package/skills/c64-provenance/scripts/lib/separate.ts +140 -0
- package/skills/c64-provenance/scripts/provenance.ts +477 -24
- package/skills/c64-reverse-engineering/SKILL.md +89 -11
- package/skills/c64-source/SKILL.md +174 -0
- package/skills/c64-source/scripts/generate.ts +788 -0
- package/skills/c64-source/scripts/lib/loader.ts +630 -0
- package/skills/c64-source/scripts/lib/source.ts +1121 -0
- package/skills/c64-static-analysis/SKILL.md +53 -13
- package/skills/c64-static-analysis/scripts/analyze.ts +260 -31
- package/skills/c64-static-analysis/scripts/lib/coverage.ts +72 -0
- package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +11 -5
- package/skills/c64-static-analysis/scripts/lib/idioms.ts +158 -0
- package/skills/c64-static-analysis/scripts/lib/instructions.ts +105 -0
- package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
- package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
- package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
- package/skills/c64-testing/SKILL.md +57 -4
- package/skills/c64-testing/references/scenario-format.md +50 -6
- package/skills/c64-testing/scripts/checklist.ts +3 -3
- package/skills/c64-testing/scripts/diff.ts +167 -0
- package/skills/c64-testing/scripts/lib/diff.ts +70 -0
- package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +183 -152
- package/skills/c64-testing/scripts/run.ts +241 -0
- package/skills/c64-testing/scripts/test.ts +20 -10
- package/skills/c64-unpacker/SKILL.md +88 -21
- package/skills/c64-unpacker/scripts/lib/evidence.ts +207 -0
- package/skills/c64-unpacker/scripts/lib/loads.ts +209 -0
- package/skills/c64-unpacker/scripts/lib/machine.ts +80 -0
- package/skills/c64-unpacker/scripts/lib/stages.ts +142 -0
- package/skills/c64-unpacker/scripts/unpack.ts +545 -60
- package/src/c64.ts +240 -12
- package/src/cli/cleanup.ts +1 -1
- package/src/cli/main.ts +14 -8
- package/src/cli/status.ts +105 -11
- package/src/graphics.ts +158 -0
- package/src/host/main.ts +57 -31
- package/src/host/server.ts +30 -47
- package/src/host/tools/c1541.ts +34 -5
- package/src/host/tools/index.ts +6 -2
- package/src/host/tools/petcat.ts +10 -3
- package/src/host/tools/status.ts +23 -6
- package/src/host/vice/adapter.ts +64 -20
- package/src/host/vice/chips.ts +11 -1
- package/src/host/vice/process.ts +19 -8
- package/src/host/vice/screen.ts +4 -44
- package/src/host/vice/session.ts +343 -60
- package/src/host-client/autostart.ts +55 -0
- package/src/host-client/connect.ts +14 -14
- package/src/host-client/coverage.ts +63 -0
- package/src/host-client/image.ts +66 -0
- package/src/host-client/steps.ts +175 -0
- package/src/host-client/tools.ts +7 -6
- package/src/host-client/transfer.ts +60 -10
- package/src/host-client/vice-session.ts +107 -28
- package/src/knowledge/conflicts.ts +161 -0
- package/src/knowledge/database.ts +150 -38
- package/src/knowledge/expression.ts +209 -0
- package/src/knowledge/graph.ts +104 -0
- package/src/knowledge/history.ts +105 -27
- package/src/knowledge/import.ts +141 -85
- package/src/knowledge/join.ts +211 -0
- package/src/knowledge/program.ts +258 -0
- package/src/knowledge/read.ts +445 -63
- package/src/knowledge/schema.ts +277 -65
- package/src/knowledge/text.ts +415 -0
- package/src/knowledge/write.ts +1145 -150
- package/src/mcp/main.ts +44 -19
- package/src/mcp/output.ts +39 -0
- package/src/mcp/server.ts +15 -4
- package/src/mcp/tools/debug.ts +203 -32
- package/src/mcp/tools/execution.ts +39 -5
- package/src/mcp/tools/input.ts +7 -4
- package/src/mcp/tools/machine.ts +14 -2
- package/src/mcp/tools/media.ts +44 -16
- package/src/mcp/tools/memory.ts +68 -12
- package/src/mcp/tools/video.ts +20 -9
- package/src/native/discover.ts +44 -6
- package/src/native/ghidra/analyze.ts +2 -2
- package/src/native/ghidra/index.ts +19 -3
- package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
- package/src/native/ghidra/scripts/C64Export.java +33 -1
- package/src/native/ghidra/scripts/C64Prepare.java +7 -1
- package/src/native/status.ts +15 -3
- package/src/native/types.ts +43 -8
- package/src/opcodes.ts +137 -0
- package/src/package.ts +21 -0
- package/{skills/c64-memory-map/scripts → src}/platform.ts +87 -7
- package/src/project.ts +166 -3
- package/src/protocol/messages.ts +17 -10
- package/src/protocol/tools.ts +27 -8
- package/src/protocol/vice.ts +255 -33
- package/src/script.ts +49 -9
- package/src/trace.ts +55 -23
- package/skills/c64-provenance/scripts/compare.ts +0 -92
- package/skills/c64-unpacker/scripts/evidence.ts +0 -135
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// The source map that c64-source writes next to the source (D80): the line of
|
|
2
|
+
// each name and of each address. The documentation links to these lines.
|
|
3
|
+
|
|
4
|
+
import { posix } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { parseC64Address } from "#src/c64.ts";
|
|
7
|
+
import { WireFailure } from "#src/host-client/tools.ts";
|
|
8
|
+
import { readProjectFile } from "#src/host-client/transfer.ts";
|
|
9
|
+
import type { Space } from "#src/knowledge/schema.ts";
|
|
10
|
+
|
|
11
|
+
export const SOURCE_MAP_FILE = "source-map.json";
|
|
12
|
+
|
|
13
|
+
export interface SourceLine {
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SourceMap {
|
|
19
|
+
/** The source directory, relative to the project folder. */
|
|
20
|
+
directory: string;
|
|
21
|
+
space: Space;
|
|
22
|
+
revision: string;
|
|
23
|
+
files: string[];
|
|
24
|
+
labels: Array<SourceLine & { address: number; name: string }>;
|
|
25
|
+
/** The label line of a name. */
|
|
26
|
+
labelOf: (name: string) => (SourceLine & { address: number }) | undefined;
|
|
27
|
+
/** The line that writes an address: an instruction, a macro use or a data line. */
|
|
28
|
+
lineOf: (address: number) => SourceLine | undefined;
|
|
29
|
+
/** The project path of a source file. */
|
|
30
|
+
pathOf: (file: string) => string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Reads <directory>/source-map.json. A missing map is a refusal with the remedy. */
|
|
34
|
+
export function readSourceMap(directory: string): SourceMap {
|
|
35
|
+
const path = posix.join(directory, SOURCE_MAP_FILE);
|
|
36
|
+
let text: string;
|
|
37
|
+
try {
|
|
38
|
+
text = readProjectFile(path).bytes.toString("utf8");
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error instanceof WireFailure && error.code === "not-found") {
|
|
41
|
+
throw new WireFailure("not-found", `There is no ${path}. Generate the source with c64-source generate.ts --out ${directory}: it writes the source map.`);
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
const fail = (what: string): never => {
|
|
46
|
+
throw new WireFailure("invalid-input", `${path} is not a source map of c64-source: ${what}. Generate the source again.`);
|
|
47
|
+
};
|
|
48
|
+
let raw: Record<string, unknown>;
|
|
49
|
+
try {
|
|
50
|
+
raw = JSON.parse(text) as Record<string, unknown>;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return fail((error as Error).message);
|
|
53
|
+
}
|
|
54
|
+
if (!Array.isArray(raw.labels) || !Array.isArray(raw.items) || !Array.isArray(raw.files)) fail("it has no labels, items or files");
|
|
55
|
+
const labels = (raw.labels as Array<Record<string, unknown>>).map((label) => ({ address: parseC64Address(String(label.address)), name: String(label.name), file: String(label.file), line: Number(label.line) }));
|
|
56
|
+
const items = (raw.items as Array<Record<string, unknown>>)
|
|
57
|
+
.map((item) => ({ start: parseC64Address(String(item.start)), end: parseC64Address(String(item.end)), file: String(item.file), line: Number(item.line) }))
|
|
58
|
+
.sort((a, b) => a.start - b.start);
|
|
59
|
+
const byName = new Map(labels.map((label) => [label.name, label]));
|
|
60
|
+
return {
|
|
61
|
+
directory,
|
|
62
|
+
space: String(raw.space ?? "c64") as Space,
|
|
63
|
+
revision: String(raw.revision),
|
|
64
|
+
files: (raw.files as unknown[]).map(String),
|
|
65
|
+
labels,
|
|
66
|
+
labelOf: (name) => byName.get(name),
|
|
67
|
+
lineOf: (address) => {
|
|
68
|
+
let low = 0;
|
|
69
|
+
let high = items.length - 1;
|
|
70
|
+
while (low <= high) {
|
|
71
|
+
const middle = (low + high) >> 1;
|
|
72
|
+
const item = items[middle]!;
|
|
73
|
+
if (address < item.start) high = middle - 1;
|
|
74
|
+
else if (address > item.end) low = middle + 1;
|
|
75
|
+
else return { file: item.file, line: item.line };
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
},
|
|
79
|
+
pathOf: (file) => posix.join(directory, file),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// c64-docs link.ts (D80): links the name mentions of the documentation to the
|
|
2
|
+
// source and the reference, and checks every link and image of the pages.
|
|
3
|
+
// With --check it writes nothing and fails when a page is not up to date.
|
|
4
|
+
|
|
5
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
6
|
+
import { join, posix } from "node:path";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
8
|
+
|
|
9
|
+
import { WireFailure } from "#src/host-client/tools.ts";
|
|
10
|
+
import { writeProjectFile } from "#src/host-client/transfer.ts";
|
|
11
|
+
import { KnowledgeError, openForRead } from "#src/knowledge/database.ts";
|
|
12
|
+
import { currentRevision, listSymbols } from "#src/knowledge/read.ts";
|
|
13
|
+
import { resolveProjectFile, resolveProjectPath } from "#src/project.ts";
|
|
14
|
+
import { runScript, UsageError } from "#src/script.ts";
|
|
15
|
+
import { checkLinks, linkPage, type Files, type Problem } from "./lib/links.ts";
|
|
16
|
+
import { GENERATED_MARK } from "./lib/reference.ts";
|
|
17
|
+
import { readSourceMap } from "./lib/source-map.ts";
|
|
18
|
+
|
|
19
|
+
const USAGE = `link.ts <docs> --source <dir> [--reference <dir>] [--check]
|
|
20
|
+
|
|
21
|
+
<docs> the documentation directory, relative to the project folder;
|
|
22
|
+
link.ts reads every .md file below it
|
|
23
|
+
--source <dir> the source directory of c64-source, with its source-map.json
|
|
24
|
+
--reference <dir> the reference pages of generate.ts; the default is
|
|
25
|
+
<docs>/reference when it exists
|
|
26
|
+
--check write nothing; fail when a link is not up to date or a page
|
|
27
|
+
has a problem
|
|
28
|
+
|
|
29
|
+
A name in backticks gets a link to its line in the source. An address in
|
|
30
|
+
backticks after the name gets a link to the entry of the name in the reference.
|
|
31
|
+
The result is one JSON object.`;
|
|
32
|
+
|
|
33
|
+
const MAX_LISTED = 50;
|
|
34
|
+
/** A name outside the image covers the addresses up to the next name, at most this many. */
|
|
35
|
+
const MAX_BODY = 256;
|
|
36
|
+
|
|
37
|
+
function markdownFiles(directory: string): string[] {
|
|
38
|
+
const root = resolveProjectPath(directory);
|
|
39
|
+
if (!existsSync(root) || !statSync(root).isDirectory()) throw new WireFailure("not-found", `There is no directory ${directory} in the project folder.`);
|
|
40
|
+
const found: string[] = [];
|
|
41
|
+
const walk = (relative: string) => {
|
|
42
|
+
for (const entry of readdirSync(join(root, relative), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
43
|
+
const path = posix.join(relative, entry.name);
|
|
44
|
+
if (entry.isDirectory()) walk(path);
|
|
45
|
+
else if (entry.name.endsWith(".md")) found.push(posix.join(directory, path));
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
walk("");
|
|
49
|
+
return found;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const files: Files = {
|
|
53
|
+
inside: (path) => {
|
|
54
|
+
try {
|
|
55
|
+
resolveProjectPath(path);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
exists: (path) => {
|
|
62
|
+
try {
|
|
63
|
+
resolveProjectFile(path);
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
text: (path) => {
|
|
70
|
+
try {
|
|
71
|
+
const real = resolveProjectFile(path);
|
|
72
|
+
return statSync(real).isFile() ? readFileSync(real, "utf8") : undefined;
|
|
73
|
+
} catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function run(argv: string[]): { output: Record<string, unknown>; failed: boolean } {
|
|
80
|
+
const { values, positionals } = parseArgs({ args: argv, strict: true, allowPositionals: true, options: { source: { type: "string" }, reference: { type: "string" }, check: { type: "boolean" }, help: { type: "boolean" } } });
|
|
81
|
+
if (values.help) throw new UsageError("");
|
|
82
|
+
if (positionals.length !== 1) throw new UsageError("give one documentation directory");
|
|
83
|
+
if (values.source === undefined) throw new UsageError("give --source <dir>: the source directory of c64-source");
|
|
84
|
+
const docs = posix.normalize(positionals[0]!);
|
|
85
|
+
for (const [option, path] of [["<docs>", docs], ["--source", values.source], ["--reference", values.reference]] as const) {
|
|
86
|
+
if (path === undefined) continue;
|
|
87
|
+
if (!files.inside(path)) throw new UsageError(`${option} must be a path relative to the project folder that stays inside it, not ${path}`);
|
|
88
|
+
}
|
|
89
|
+
const map = readSourceMap(values.source);
|
|
90
|
+
const reference = values.reference === undefined ? (existsSync(resolveProjectPath(posix.join(docs, "reference"))) ? posix.join(docs, "reference") : undefined) : posix.normalize(values.reference);
|
|
91
|
+
const problems: Problem[] = [];
|
|
92
|
+
const db = openForRead();
|
|
93
|
+
let symbols;
|
|
94
|
+
let revision;
|
|
95
|
+
try {
|
|
96
|
+
revision = currentRevision(db);
|
|
97
|
+
symbols = listSymbols(db, undefined, undefined, undefined, map.space);
|
|
98
|
+
} finally {
|
|
99
|
+
db?.close();
|
|
100
|
+
}
|
|
101
|
+
if (map.revision !== revision) {
|
|
102
|
+
problems.push({ page: map.pathOf("source-map.json"), line: 1, kind: "stale-reference", message: `The source is from knowledge revision ${map.revision}, and the knowledge is at ${revision}. Generate the source and the reference again, then run link.ts.` });
|
|
103
|
+
}
|
|
104
|
+
const addresses = new Map<string, number>();
|
|
105
|
+
for (const label of map.labels) addresses.set(label.name, label.address);
|
|
106
|
+
for (const symbol of symbols) addresses.set(symbol.name, symbol.address);
|
|
107
|
+
// A routine runs to the next routine; any other name to the next name of the knowledge, in its file, and outside the image at most MAX_BODY bytes.
|
|
108
|
+
const routines = symbols.filter((symbol) => symbol.kind === "routine").map((symbol) => symbol.address).sort((a, b) => a - b);
|
|
109
|
+
const named = [...new Set(symbols.map((symbol) => symbol.address))].sort((a, b) => a - b);
|
|
110
|
+
const kinds = new Map(symbols.map((symbol) => [symbol.name, symbol.kind]));
|
|
111
|
+
const bodyEnd = (name: string) => {
|
|
112
|
+
const address = addresses.get(name);
|
|
113
|
+
if (address === undefined) return undefined;
|
|
114
|
+
const next = ((kinds.get(name) === "routine" ? routines : named).find((other) => other > address) ?? 0x10000) - 1;
|
|
115
|
+
const file = map.lineOf(address)?.file;
|
|
116
|
+
if (file === undefined) return Math.min(next, address + MAX_BODY - 1);
|
|
117
|
+
let end = address;
|
|
118
|
+
while (end < next && map.lineOf(end + 1)?.file === file) end++;
|
|
119
|
+
return end;
|
|
120
|
+
};
|
|
121
|
+
const nameAt = new Map(symbols.map((symbol) => [symbol.address, symbol.name]));
|
|
122
|
+
/** The routine, else the name, whose body holds an address. */
|
|
123
|
+
const placeOf = (address: number) => {
|
|
124
|
+
for (const list of [routines, named]) {
|
|
125
|
+
let start: number | undefined;
|
|
126
|
+
for (const other of list) if (other <= address) start = other;
|
|
127
|
+
const name = start === undefined ? undefined : nameAt.get(start);
|
|
128
|
+
if (name !== undefined && (bodyEnd(name) ?? start!) >= address) return name;
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// The entries of the reference: each <a id> that is a name.
|
|
134
|
+
const entries = new Map<string, string>();
|
|
135
|
+
const referencePages = reference === undefined ? [] : markdownFiles(reference);
|
|
136
|
+
for (const page of referencePages) {
|
|
137
|
+
const text = files.text(page)!;
|
|
138
|
+
const stamp = /^<!-- Generated by c64-docs from knowledge revision (\S+) /.exec(text);
|
|
139
|
+
if (text.startsWith(GENERATED_MARK) && stamp !== null && stamp[1] !== map.revision) {
|
|
140
|
+
problems.push({ page, line: 1, kind: "stale-reference", message: `${page} is from knowledge revision ${stamp[1]}, and the source from ${map.revision}. Run generate.ts again.` });
|
|
141
|
+
}
|
|
142
|
+
for (const match of text.matchAll(/<a id="([^"]+)"><\/a>/g)) if (addresses.has(match[1]!) && !entries.has(match[1]!)) entries.set(match[1]!, `${page}#${match[1]}`);
|
|
143
|
+
}
|
|
144
|
+
const inside = (path: string, directory: string | undefined) => directory !== undefined && (path === directory || path.startsWith(`${directory}/`));
|
|
145
|
+
const context = {
|
|
146
|
+
addressOf: (name: string) => addresses.get(name),
|
|
147
|
+
codeOf: (name: string) => {
|
|
148
|
+
const label = map.labelOf(name);
|
|
149
|
+
return label === undefined ? undefined : `${map.pathOf(label.file)}#L${label.line}`;
|
|
150
|
+
},
|
|
151
|
+
entryOf: (name: string) => entries.get(name),
|
|
152
|
+
bodyEnd,
|
|
153
|
+
placeOf,
|
|
154
|
+
lineOf: (address: number) => {
|
|
155
|
+
const line = map.lineOf(address);
|
|
156
|
+
return line === undefined ? undefined : `${map.pathOf(line.file)}#L${line.line}`;
|
|
157
|
+
},
|
|
158
|
+
managed: (path: string) => inside(path, posix.normalize(map.directory)) || inside(path, reference),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const pages = markdownFiles(docs);
|
|
162
|
+
const changedPages: string[] = [];
|
|
163
|
+
let links = 0;
|
|
164
|
+
for (const page of pages) {
|
|
165
|
+
const text = files.text(page)!;
|
|
166
|
+
// A generated page is right by its making: it is only checked.
|
|
167
|
+
const generated = text.startsWith(GENERATED_MARK);
|
|
168
|
+
const linked = generated ? { text, changed: 0, problems: [] } : linkPage(text, { page, ...context });
|
|
169
|
+
problems.push(...linked.problems, ...checkLinks(page, linked.text, files));
|
|
170
|
+
if (linked.changed === 0) continue;
|
|
171
|
+
links += linked.changed;
|
|
172
|
+
changedPages.push(page);
|
|
173
|
+
if (!values.check) writeProjectFile(page, Buffer.from(linked.text, "utf8"));
|
|
174
|
+
}
|
|
175
|
+
const byKind: Record<string, number> = {};
|
|
176
|
+
for (const problem of problems) byKind[problem.kind] = (byKind[problem.kind] ?? 0) + 1;
|
|
177
|
+
const output: Record<string, unknown> = {
|
|
178
|
+
pages: pages.length,
|
|
179
|
+
...(reference === undefined ? { reference: "none: run generate.ts for links to the entries" } : { reference }),
|
|
180
|
+
...(values.check ? { notUpToDate: { links, pages: changedPages } } : { linked: { links, pages: changedPages } }),
|
|
181
|
+
problems: { count: problems.length, byKind, listed: problems.slice(0, MAX_LISTED).map((problem) => ({ at: `${problem.page}:${problem.line}`, kind: problem.kind, message: problem.message })) },
|
|
182
|
+
};
|
|
183
|
+
return { output, failed: problems.length > 0 || (values.check === true && links > 0) };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await runScript(
|
|
187
|
+
(argv) => {
|
|
188
|
+
const { output, failed } = run(argv);
|
|
189
|
+
if (failed) process.exitCode = 1;
|
|
190
|
+
return output;
|
|
191
|
+
},
|
|
192
|
+
{ usage: USAGE, refusals: [KnowledgeError] },
|
|
193
|
+
);
|
|
@@ -25,18 +25,21 @@ Use only the MCP tools. Each MCP session has its own C64. Nothing else can chang
|
|
|
25
25
|
|
|
26
26
|
| Need | Tool |
|
|
27
27
|
|---|---|
|
|
28
|
-
| Run state, video standard, warp mode, window | `c64_status` |
|
|
28
|
+
| Run state, video standard, warp mode, window, version of the MCP server | `c64_status` |
|
|
29
29
|
| Reset, power cycle | `c64_reset` |
|
|
30
30
|
| Fast emulation | `c64_warp` |
|
|
31
31
|
| Show the C64 to the user in a window, hide it again | `c64_window` |
|
|
32
32
|
| Stop, start, step, step over, step out, exact frames | `c64_execution` |
|
|
33
33
|
| Run until an address, a memory value or a raster position | `c64_run_until` |
|
|
34
|
+
| Run to a place and read the machine there in one call | `c64_run_until` with `observe` and `history` |
|
|
35
|
+
| Each write, read or routine entry over many frames, with the instruction and the raster line | `c64_trace` |
|
|
34
36
|
| Stop on an instruction address | `c64_breakpoint` |
|
|
35
37
|
| Stop on a memory read or write | `c64_watchpoint` |
|
|
36
38
|
| Memory | `c64_memory_read`, `c64_memory_write`, `c64_memory_search`, `c64_memory_compare` |
|
|
37
39
|
| CPU registers | `c64_registers` |
|
|
38
40
|
| Code as it is in memory now | `c64_disassemble` |
|
|
39
41
|
| Executed instructions, call chain | `c64_cpu_history`, `c64_backtrace` |
|
|
42
|
+
| Which instruction reads or writes an address | `c64_trace` with a `read` or `write` point |
|
|
40
43
|
| Where the CPU spends cycles, which memory it uses | `c64_profile`, `c64_memmap` |
|
|
41
44
|
| Cycle count between two points | `c64_timing` |
|
|
42
45
|
| Video chip, sprites, CIAs, sound chip | `c64_vicii`, `c64_sprite`, `c64_cia`, `c64_sid` |
|
|
@@ -44,27 +47,46 @@ Use only the MCP tools. Each MCP session has its own C64. Nothing else can chang
|
|
|
44
47
|
| Several of these from one moment | `c64_observe` |
|
|
45
48
|
| Keys, joystick | `c64_keyboard`, `c64_joystick` |
|
|
46
49
|
| Program and disk media | `c64_autostart`, `c64_program_load`, `c64_disk_attach` |
|
|
47
|
-
| Machine state to go back to | `c64_snapshot` |
|
|
50
|
+
| Machine state to go back to, also in a later session | `c64_snapshot` (with `out` and `file`) |
|
|
48
51
|
|
|
49
52
|
Each memory and register tool has a `space`. Use `c64` for the computer. Use `drive8` for the CPU of the 1541 disk drive. The memory `view` is `cpu` or `ram`. With `cpu`, you see what the CPU sees now, with ROM and I/O where the memory configuration puts them. With `ram`, you see the RAM under them.
|
|
50
53
|
|
|
54
|
+
For a large result, give `out` with a path in the project. `c64_memory_read`, `c64_disassemble`, `c64_cpu_history` and `c64_screen` then write the result to that file. The answer gives only the file, the number of bytes and a SHA-256. Use it for a memory dump of more than a few hundred bytes, a long listing, or a screen that the user wants to keep. `c64_memmap` action `read` and `c64_snapshot` action `save` also take `out`. Write a file that is only for your work in `.c64-re-tools/local/` of the project. Git ignores that folder.
|
|
55
|
+
|
|
56
|
+
`c64_memmap` keeps RAM, ROM and I/O apart. Use action `summary` to see which code ran. Use action `read` with `out` to keep a coverage run as a file, for example `.c64-re-tools/local/attract.bin`. Then add it to the knowledge with `coverage` of the c64-knowledge skill. Clear the map before each run that you want to keep apart.
|
|
57
|
+
|
|
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
|
+
- 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
|
+
|
|
61
|
+
`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
|
+
### Project folder
|
|
64
|
+
|
|
65
|
+
Paths (`path`, `file` and `out`) are relative to the project folder. A project folder holds `.c64-re-tools/`. The tools use the nearest project folder at or above the folder where the agent started. If that folder is not in a project and has projects below it, a tool with a path refuses and lists them. Then give `project` with the project folder, for example `"project": "game"`. Give the same folder as the `--project` of the skill scripts. A tool without a path does not need `project`.
|
|
66
|
+
|
|
51
67
|
## Workflow
|
|
52
68
|
|
|
53
69
|
1. Read the project knowledge for the addresses in the question. Use the c64-knowledge skill.
|
|
54
70
|
2. Get the machine state with `c64_status`.
|
|
55
71
|
3. Load the subject if it is not loaded. Use `c64_autostart` to start it as a user does. Use `c64_program_load` to put a PRG in memory with no start.
|
|
56
72
|
4. Set the stop condition. Use a breakpoint, a watchpoint or a `c64_run_until` target. Add a typed condition when the stop must occur only in one state. The `list` result shows the condition of each breakpoint and each watchpoint.
|
|
57
|
-
5. Run, step or give input. Prefer `c64_run_until` and `advance-frames` to waits. They stop at the same emulated time on every run.
|
|
73
|
+
5. Run, step or give input. Prefer `c64_run_until` and `advance-frames` to waits. They stop at the same emulated time on every run. To wait for a breakpoint or a watchpoint, use `advance-frames` with a large count. `stopReason` and `pointId` in the result tell what stopped the CPU. `c64_status` also gives them.
|
|
58
74
|
6. Read only the state that answers the question. To read several things from one moment, use `c64_observe`.
|
|
59
75
|
7. Interpret the evidence. Keep the observed facts apart from your conclusions.
|
|
60
76
|
|
|
61
77
|
Write operations need a stopped CPU. Stop the CPU with `c64_execution` action `pause` first.
|
|
62
78
|
|
|
63
|
-
`c64_reset` with `run` false stops the CPU at the first instruction of the reset routine. The result gives that address in `pc`.
|
|
79
|
+
`c64_reset` with `run` false stops the CPU at the first instruction of the reset routine. The result gives that address in `pc`. A reset and `c64_autostart` set `c64_timing` to zero.
|
|
80
|
+
|
|
81
|
+
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.
|
|
64
82
|
|
|
65
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.
|
|
66
84
|
|
|
67
|
-
`c64_keyboard` puts keys in the keyboard queue of the KERNAL. A program that reads the keyboard hardware directly does not see them. Many games do this.
|
|
85
|
+
`c64_keyboard` puts keys in the keyboard queue of the KERNAL. A program that reads the keyboard hardware directly does not see them. Many games do this. The emulator has no command that presses one key of the keyboard matrix. For such a program:
|
|
86
|
+
|
|
87
|
+
- VICE 3.10 and older have a known flaw: bits 5-7 of `$DC00` and `$DC01` are always 0. Then `emulatorFlaw` in the result of `c64_keyboard` and `c64_joystick` gives the flaw. With this flaw, a wait for any key that reads all keyboard columns at one time ends without a key. A wait for one specific key, or a compare of the full byte of a joystick port, never ends. Tell the user about the flaw. The next VICE release and the VICE nightly builds do not have it. Joystick port 1 uses the same lines as the keyboard.
|
|
88
|
+
- For one specific key, for example F1 or RUN/STOP, open the window and ask the user to press the key.
|
|
89
|
+
- Do not change the program to make it accept a key.
|
|
68
90
|
|
|
69
91
|
Before a risky experiment, save the state with `c64_snapshot`. Restore it to try again from the same point.
|
|
70
92
|
|
|
@@ -106,11 +128,10 @@ If you cannot get the machine into the necessary state, say which state you coul
|
|
|
106
128
|
|
|
107
129
|
- `machine-state-lost` and the message says that the VICE window closed: the window closed before `c64_window` action `close`. The emulator has no window again. The machine state is the same as at the time when the window opened. The breakpoints and the watchpoints stay. Do not use results from the window time. Read the machine state again, then continue.
|
|
108
130
|
- `machine-unavailable` and the message says that the VICE window closed: the session starts the emulator again. Wait a few seconds. Then do the operation again.
|
|
109
|
-
- `machine-state-lost` with a different message: the
|
|
131
|
+
- `machine-state-lost` with a different message: the connection to the host runtime was lost, and the machine state is gone. Do not continue with results from before. The next call starts a new machine when the host runtime runs. Load the program again, and set the breakpoints and the watchpoints again. If the next call gives `machine-unavailable` with a message that the host runtime does not run, obey the rule for that message.
|
|
110
132
|
- `machine-unavailable` and the message names a pause or a dialog in the emulator window: this occurs only while the window is open. Do not send more operations. Tell the user to resume the emulator (Pause, Alt+P) or to close the dialog in the VICE window. Then do the operation again. The machine state does not change.
|
|
111
|
-
- `machine-unavailable` with a different message: the host runtime does not run, or it does not answer. Tell the user to start the host runtime with `npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host`.
|
|
112
133
|
- `machine-running`: stop the CPU first, then try again.
|
|
113
|
-
- `installation-incomplete
|
|
134
|
+
- `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.
|
|
114
135
|
- `c64_run_until` with `stopReason` `timeout`: the target did not occur in the frame limit. This is evidence too. Report it.
|
|
115
136
|
- `c64_run_until` with `stopReason` `jam`: the CPU executed a JAM opcode and stopped. Report the address.
|
|
116
137
|
|