@henols/c64-re-tools 2.0.0-rc.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +98 -115
  2. package/package.json +1 -1
  3. package/skills/c64-assembler/SKILL.md +22 -6
  4. package/skills/c64-assembler/scripts/assemble.ts +42 -14
  5. package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
  6. package/skills/c64-basic/SKILL.md +10 -1
  7. package/skills/c64-basic/scripts/basic.ts +3 -3
  8. package/skills/c64-disk/SKILL.md +13 -4
  9. package/skills/c64-disk/scripts/disk.ts +7 -7
  10. package/skills/c64-disk/scripts/{containers.ts → lib/containers.ts} +2 -2
  11. package/skills/c64-docs/SKILL.md +138 -0
  12. package/skills/c64-docs/references/page-types.md +141 -0
  13. package/skills/c64-docs/references/style.md +43 -0
  14. package/skills/c64-docs/scripts/generate.ts +109 -0
  15. package/skills/c64-docs/scripts/lib/links.ts +167 -0
  16. package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
  17. package/skills/c64-docs/scripts/lib/reference.ts +406 -0
  18. package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
  19. package/skills/c64-docs/scripts/link.ts +239 -0
  20. package/skills/c64-emulator/SKILL.md +16 -5
  21. package/skills/c64-knowledge/SKILL.md +140 -18
  22. package/skills/c64-knowledge/scripts/knowledge.ts +737 -112
  23. package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
  24. package/skills/c64-memory-map/SKILL.md +11 -2
  25. package/skills/c64-memory-map/scripts/memmap.ts +2 -2
  26. package/skills/c64-provenance/SKILL.md +66 -11
  27. package/skills/c64-provenance/scripts/{compare.ts → lib/compare.ts} +3 -3
  28. package/skills/c64-provenance/scripts/lib/library.ts +584 -0
  29. package/skills/c64-provenance/scripts/{runtime.ts → lib/runtime.ts} +2 -2
  30. package/skills/c64-provenance/scripts/{separate.ts → lib/separate.ts} +3 -3
  31. package/skills/c64-provenance/scripts/provenance.ts +242 -40
  32. package/skills/c64-reverse-engineering/SKILL.md +51 -26
  33. package/skills/c64-source/SKILL.md +76 -28
  34. package/skills/c64-source/scripts/generate.ts +425 -127
  35. package/skills/c64-source/scripts/{loader.ts → lib/loader.ts} +84 -25
  36. package/skills/c64-source/scripts/lib/source.ts +1121 -0
  37. package/skills/c64-static-analysis/SKILL.md +35 -18
  38. package/skills/c64-static-analysis/scripts/analyze.ts +184 -55
  39. package/skills/c64-static-analysis/scripts/{coverage.ts → lib/coverage.ts} +19 -25
  40. package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +6 -2
  41. package/skills/c64-static-analysis/scripts/{idioms.ts → lib/idioms.ts} +21 -12
  42. package/skills/c64-static-analysis/scripts/{instructions.ts → lib/instructions.ts} +1 -1
  43. package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
  44. package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
  45. package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
  46. package/skills/c64-testing/SKILL.md +36 -7
  47. package/skills/c64-testing/references/scenario-format.md +12 -8
  48. package/skills/c64-testing/scripts/checklist.ts +3 -3
  49. package/skills/c64-testing/scripts/diff.ts +167 -0
  50. package/skills/c64-testing/scripts/lib/diff.ts +70 -0
  51. package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +45 -34
  52. package/skills/c64-testing/scripts/run.ts +73 -21
  53. package/skills/c64-testing/scripts/test.ts +5 -5
  54. package/skills/c64-unpacker/SKILL.md +23 -9
  55. package/skills/c64-unpacker/scripts/{evidence.ts → lib/evidence.ts} +1 -1
  56. package/skills/c64-unpacker/scripts/{loads.ts → lib/loads.ts} +4 -4
  57. package/skills/c64-unpacker/scripts/{machine.ts → lib/machine.ts} +4 -4
  58. package/skills/c64-unpacker/scripts/{stages.ts → lib/stages.ts} +17 -5
  59. package/skills/c64-unpacker/scripts/unpack.ts +41 -24
  60. package/src/c64.ts +103 -5
  61. package/src/cli/cleanup.ts +1 -1
  62. package/src/cli/main.ts +7 -4
  63. package/src/cli/status.ts +70 -3
  64. package/src/graphics.ts +158 -0
  65. package/src/host/main.ts +43 -8
  66. package/src/host/server.ts +10 -3
  67. package/src/host/tools/petcat.ts +1 -1
  68. package/src/host/tools/status.ts +1 -1
  69. package/src/host/vice/adapter.ts +4 -3
  70. package/src/host/vice/process.ts +2 -2
  71. package/src/host/vice/screen.ts +1 -42
  72. package/src/host/vice/session.ts +196 -23
  73. package/src/host-client/autostart.ts +1 -1
  74. package/src/host-client/connect.ts +1 -1
  75. package/src/host-client/coverage.ts +2 -29
  76. package/src/host-client/image.ts +66 -0
  77. package/src/host-client/steps.ts +9 -4
  78. package/src/host-client/tools.ts +1 -0
  79. package/src/host-client/transfer.ts +39 -11
  80. package/src/host-client/vice-session.ts +33 -16
  81. package/src/knowledge/conflicts.ts +161 -0
  82. package/src/knowledge/database.ts +150 -38
  83. package/src/knowledge/expression.ts +209 -0
  84. package/src/knowledge/graph.ts +104 -0
  85. package/src/knowledge/history.ts +70 -22
  86. package/src/knowledge/import.ts +64 -55
  87. package/src/knowledge/join.ts +211 -0
  88. package/src/knowledge/program.ts +266 -0
  89. package/src/knowledge/read.ts +258 -27
  90. package/src/knowledge/schema.ts +199 -75
  91. package/src/knowledge/text.ts +415 -0
  92. package/src/knowledge/write.ts +740 -57
  93. package/src/mcp/main.ts +1 -1
  94. package/src/mcp/output.ts +15 -8
  95. package/src/mcp/server.ts +3 -1
  96. package/src/mcp/tools/debug.ts +97 -16
  97. package/src/mcp/tools/execution.ts +38 -5
  98. package/src/mcp/tools/machine.ts +11 -2
  99. package/src/mcp/tools/media.ts +18 -13
  100. package/src/mcp/tools/memory.ts +17 -8
  101. package/src/mcp/tools/video.ts +9 -9
  102. package/src/native/discover.ts +2 -2
  103. package/src/native/ghidra/analyze.ts +2 -2
  104. package/src/native/ghidra/index.ts +1 -1
  105. package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
  106. package/src/native/ghidra/scripts/C64Export.java +1 -1
  107. package/src/native/ghidra/scripts/C64Prepare.java +7 -1
  108. package/src/native/types.ts +14 -8
  109. package/{skills/c64-source/scripts → src}/opcodes.ts +75 -3
  110. package/src/platform.ts +70 -7
  111. package/src/project.ts +180 -3
  112. package/src/protocol/messages.ts +1 -1
  113. package/src/protocol/tools.ts +2 -16
  114. package/src/protocol/vice.ts +124 -15
  115. package/src/script.ts +46 -7
  116. package/src/trace.ts +55 -23
  117. package/skills/c64-assembler/scripts/compare.ts +0 -84
  118. package/skills/c64-source/scripts/source.ts +0 -595
@@ -0,0 +1,81 @@
1
+ // The source map that c64-source writes next to the source: 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,239 @@
1
+ // c64-docs link.ts: 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 <pages>... --source <dir> [--reference <dir>] [--check]
20
+
21
+ <pages>... .md files and directories of the documentation, relative to
22
+ the project folder, for example README.md docs; link.ts reads
23
+ each file and every .md file below each directory
24
+ --source <dir> the source directory of c64-source, with its source-map.json
25
+ --reference <dir> the reference pages of generate.ts; the default is the
26
+ reference/ folder of the first directory that has one
27
+ --check write nothing; fail when a link is not up to date or a page
28
+ has a problem
29
+
30
+ A name in backticks gets a link to its line in the source. An address in
31
+ backticks after the name gets a link to the entry of the name in the reference.
32
+ The result is one JSON object.`;
33
+
34
+ const MAX_LISTED = 50;
35
+ /** A name outside the image covers the addresses up to the next name, at most this many. */
36
+ const MAX_BODY = 256;
37
+
38
+ const isDirectory = (path: string) => {
39
+ const real = resolveProjectPath(path);
40
+ return existsSync(real) && statSync(real).isDirectory();
41
+ };
42
+
43
+ function markdownFiles(directory: string): string[] {
44
+ if (!isDirectory(directory)) throw new WireFailure("not-found", `There is no directory ${directory} in the project folder.`);
45
+ let root: string;
46
+ try {
47
+ root = resolveProjectFile(directory);
48
+ } catch {
49
+ throw new WireFailure("invalid-input", `${directory} leads out of the project folder through a symbolic link.`);
50
+ }
51
+ const found: string[] = [];
52
+ const walk = (relative: string) => {
53
+ for (const entry of readdirSync(join(root, relative), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
54
+ const path = posix.join(relative, entry.name);
55
+ if (entry.isDirectory()) walk(path);
56
+ else if (entry.name.endsWith(".md")) found.push(posix.join(directory, path));
57
+ }
58
+ };
59
+ walk("");
60
+ return found;
61
+ }
62
+
63
+ /** The text of each file that the run read: a page, a source file or a reference page is read once. */
64
+ const texts = new Map<string, string | undefined>();
65
+
66
+ const files: Files = {
67
+ inside: (path) => {
68
+ try {
69
+ resolveProjectPath(path);
70
+ return true;
71
+ } catch {
72
+ return false;
73
+ }
74
+ },
75
+ exists: (path) => {
76
+ try {
77
+ resolveProjectFile(path);
78
+ return true;
79
+ } catch {
80
+ return false;
81
+ }
82
+ },
83
+ text: (path) => {
84
+ if (texts.has(path)) return texts.get(path);
85
+ let text: string | undefined;
86
+ try {
87
+ const real = resolveProjectFile(path);
88
+ text = statSync(real).isFile() ? readFileSync(real, "utf8") : undefined;
89
+ } catch {
90
+ text = undefined;
91
+ }
92
+ texts.set(path, text);
93
+ return text;
94
+ },
95
+ };
96
+
97
+ /** A .md entry of a directory that is a symbolic link to no file, or to a file outside the project. */
98
+ const unreadable = (page: string): Problem => ({ page, line: 1, kind: "missing-file", message: `${page} is a symbolic link to no file or to a file outside the project folder.` });
99
+
100
+ function run(argv: string[]): { output: Record<string, unknown>; failed: boolean } {
101
+ const { values, positionals } = parseArgs({ args: argv, strict: true, allowPositionals: true, options: { source: { type: "string" }, reference: { type: "string" }, check: { type: "boolean" }, help: { type: "boolean" } } });
102
+ if (values.help) throw new UsageError("");
103
+ if (positionals.length === 0) throw new UsageError("give the pages: .md files and directories of the documentation, for example README.md docs");
104
+ if (values.source === undefined) throw new UsageError("give --source <dir>: the source directory of c64-source");
105
+ const given = [...new Set(positionals.map((path) => posix.normalize(path)))];
106
+ for (const [option, path] of [...given.map((path) => ["<pages>", path] as const), ["--source", values.source], ["--reference", values.reference]] as const) {
107
+ if (path === undefined) continue;
108
+ if (!files.inside(path)) throw new UsageError(`${option} must be a path relative to the project folder that stays inside it, not ${path}`);
109
+ }
110
+ for (const path of given) {
111
+ if (!path.endsWith(".md") && files.exists(path) && !isDirectory(path)) throw new UsageError(`<pages> takes .md files and directories, not ${path}`);
112
+ }
113
+ const directories = given.filter((path) => !path.endsWith(".md") || isDirectory(path));
114
+ const map = readSourceMap(values.source);
115
+ const reference = values.reference === undefined ? directories.map((path) => posix.join(path, "reference")).find((path) => isDirectory(path)) : posix.normalize(values.reference);
116
+ const problems: Problem[] = [];
117
+ const db = openForRead();
118
+ let symbols;
119
+ let revision;
120
+ try {
121
+ revision = currentRevision(db);
122
+ symbols = listSymbols(db, undefined, undefined, undefined, map.space);
123
+ } finally {
124
+ db?.close();
125
+ }
126
+ if (map.revision !== revision) {
127
+ 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.` });
128
+ }
129
+ const addresses = new Map<string, number>();
130
+ for (const label of map.labels) addresses.set(label.name, label.address);
131
+ for (const symbol of symbols) addresses.set(symbol.name, symbol.address);
132
+ // 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.
133
+ const routines = symbols.filter((symbol) => symbol.kind === "routine").map((symbol) => symbol.address).sort((a, b) => a - b);
134
+ const named = [...new Set(symbols.map((symbol) => symbol.address))].sort((a, b) => a - b);
135
+ const kinds = new Map(symbols.map((symbol) => [symbol.name, symbol.kind]));
136
+ const bodyEnds = new Map<string, number | undefined>();
137
+ const bodyEnd = (name: string) => {
138
+ if (bodyEnds.has(name)) return bodyEnds.get(name);
139
+ const address = addresses.get(name);
140
+ let end: number | undefined;
141
+ if (address !== undefined) {
142
+ const next = ((kinds.get(name) === "routine" ? routines : named).find((other) => other > address) ?? 0x10000) - 1;
143
+ const file = map.lineOf(address)?.file;
144
+ if (file === undefined) end = Math.min(next, address + MAX_BODY - 1);
145
+ else {
146
+ end = address;
147
+ while (end < next && map.lineOf(end + 1)?.file === file) end++;
148
+ }
149
+ }
150
+ bodyEnds.set(name, end);
151
+ return end;
152
+ };
153
+ const nameAt = new Map(symbols.map((symbol) => [symbol.address, symbol.name]));
154
+ /** The routine, else the name, whose body holds an address. */
155
+ const placeOf = (address: number) => {
156
+ for (const list of [routines, named]) {
157
+ let start: number | undefined;
158
+ for (const other of list) if (other <= address) start = other;
159
+ const name = start === undefined ? undefined : nameAt.get(start);
160
+ if (name !== undefined && (bodyEnd(name) ?? start!) >= address) return name;
161
+ }
162
+ return undefined;
163
+ };
164
+
165
+ // The entries of the reference: each <a id> that is a name.
166
+ const entries = new Map<string, string>();
167
+ const referencePages = reference === undefined ? [] : markdownFiles(reference);
168
+ for (const page of referencePages) {
169
+ const text = files.text(page);
170
+ if (text === undefined) {
171
+ problems.push(unreadable(page));
172
+ continue;
173
+ }
174
+ const stamp = /^<!-- Generated by c64-docs from knowledge revision (\S+) /.exec(text);
175
+ if (text.startsWith(GENERATED_MARK) && stamp !== null && stamp[1] !== map.revision) {
176
+ 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.` });
177
+ }
178
+ 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]}`);
179
+ }
180
+ const inside = (path: string, directory: string | undefined) => directory !== undefined && (path === directory || path.startsWith(`${directory}/`));
181
+ const context = {
182
+ addressOf: (name: string) => addresses.get(name),
183
+ codeOf: (name: string) => {
184
+ const label = map.labelOf(name);
185
+ return label === undefined ? undefined : `${map.pathOf(label.file)}#L${label.line}`;
186
+ },
187
+ entryOf: (name: string) => entries.get(name),
188
+ bodyEnd,
189
+ placeOf,
190
+ lineOf: (address: number) => {
191
+ const line = map.lineOf(address);
192
+ return line === undefined ? undefined : `${map.pathOf(line.file)}#L${line.line}`;
193
+ },
194
+ managed: (path: string) => inside(path, posix.normalize(map.directory)) || inside(path, reference),
195
+ };
196
+
197
+ const pages = [...new Set(given.flatMap((path) => {
198
+ if (directories.includes(path)) return markdownFiles(path);
199
+ if (files.text(path) === undefined) throw new WireFailure("not-found", `There is no file ${path} in the project folder.`);
200
+ return [path];
201
+ }))];
202
+ const changedPages: string[] = [];
203
+ let links = 0;
204
+ for (const page of pages) {
205
+ const text = files.text(page);
206
+ if (text === undefined) {
207
+ problems.push(unreadable(page));
208
+ continue;
209
+ }
210
+ // A generated page is right by its making: it is only checked.
211
+ const generated = text.startsWith(GENERATED_MARK);
212
+ const linked = generated ? { text, changed: 0, problems: [] } : linkPage(text, { page, ...context });
213
+ problems.push(...linked.problems, ...checkLinks(page, linked.text, files));
214
+ if (linked.changed === 0) continue;
215
+ links += linked.changed;
216
+ changedPages.push(page);
217
+ if (values.check) continue;
218
+ writeProjectFile(page, Buffer.from(linked.text, "utf8"));
219
+ texts.set(page, linked.text);
220
+ }
221
+ const byKind: Record<string, number> = {};
222
+ for (const problem of problems) byKind[problem.kind] = (byKind[problem.kind] ?? 0) + 1;
223
+ const output: Record<string, unknown> = {
224
+ pages: pages.length,
225
+ ...(reference === undefined ? { reference: "none: run generate.ts for links to the entries" } : { reference }),
226
+ ...(values.check ? { notUpToDate: { links, pages: changedPages } } : { linked: { links, pages: changedPages } }),
227
+ problems: { count: problems.length, byKind, listed: problems.slice(0, MAX_LISTED).map((problem) => ({ at: `${problem.page}:${problem.line}`, kind: problem.kind, message: problem.message })) },
228
+ };
229
+ return { output, failed: problems.length > 0 || (values.check === true && links > 0) };
230
+ }
231
+
232
+ await runScript(
233
+ (argv) => {
234
+ const { output, failed } = run(argv);
235
+ if (failed) process.exitCode = 1;
236
+ return output;
237
+ },
238
+ { usage: USAGE, refusals: [KnowledgeError] },
239
+ );
@@ -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` |
@@ -48,9 +51,18 @@ Use only the MCP tools. Each MCP session has its own C64. Nothing else can chang
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
 
51
- 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.
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.
52
55
 
53
- `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 `coverage/attract.bin`. Clear the map before each run that you want to keep apart.
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`.
54
66
 
55
67
  ## Workflow
56
68
 
@@ -58,7 +70,7 @@ For a large result, give `out` with a path in the project. `c64_memory_read`, `c
58
70
  2. Get the machine state with `c64_status`.
59
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.
60
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.
61
- 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.
62
74
  6. Read only the state that answers the question. To read several things from one moment, use `c64_observe`.
63
75
  7. Interpret the evidence. Keep the observed facts apart from your conclusions.
64
76
 
@@ -72,7 +84,6 @@ Step, next and until-return work only in space `c64`. The emulator cannot stop t
72
84
 
73
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:
74
86
 
75
- - 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.
76
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.
77
88
  - For one specific key, for example F1 or RUN/STOP, open the window and ask the user to press the key.
78
89
  - Do not change the program to make it accept a key.