@henols/c64-re-tools 2.0.0-rc.2 → 2.0.0-rc.21

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 (126) hide show
  1. package/README.md +28 -62
  2. package/distribution/package.ts +1 -16
  3. package/distribution/plugin.ts +5 -3
  4. package/package.json +1 -1
  5. package/skills/c64-assembler/SKILL.md +25 -6
  6. package/skills/c64-assembler/scripts/assemble.ts +45 -10
  7. package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
  8. package/skills/c64-basic/SKILL.md +15 -7
  9. package/skills/c64-basic/scripts/basic.ts +4 -4
  10. package/skills/c64-disk/SKILL.md +34 -11
  11. package/skills/c64-disk/scripts/disk.ts +65 -10
  12. package/skills/c64-disk/scripts/lib/containers.ts +124 -0
  13. package/skills/c64-docs/SKILL.md +138 -0
  14. package/skills/c64-docs/references/page-types.md +141 -0
  15. package/skills/c64-docs/references/style.md +43 -0
  16. package/skills/c64-docs/scripts/generate.ts +102 -0
  17. package/skills/c64-docs/scripts/lib/links.ts +155 -0
  18. package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
  19. package/skills/c64-docs/scripts/lib/reference.ts +406 -0
  20. package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
  21. package/skills/c64-docs/scripts/link.ts +207 -0
  22. package/skills/c64-emulator/SKILL.md +29 -8
  23. package/skills/c64-knowledge/SKILL.md +201 -15
  24. package/skills/c64-knowledge/scripts/knowledge.ts +927 -78
  25. package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
  26. package/skills/c64-memory-map/SKILL.md +14 -2
  27. package/skills/c64-memory-map/scripts/memmap.ts +15 -4
  28. package/skills/c64-provenance/SKILL.md +100 -11
  29. package/skills/c64-provenance/scripts/lib/compare.ts +152 -0
  30. package/skills/c64-provenance/scripts/lib/library.ts +584 -0
  31. package/skills/c64-provenance/scripts/lib/runtime.ts +191 -0
  32. package/skills/c64-provenance/scripts/lib/separate.ts +140 -0
  33. package/skills/c64-provenance/scripts/provenance.ts +477 -24
  34. package/skills/c64-reverse-engineering/SKILL.md +90 -11
  35. package/skills/c64-source/SKILL.md +174 -0
  36. package/skills/c64-source/scripts/generate.ts +788 -0
  37. package/skills/c64-source/scripts/lib/loader.ts +630 -0
  38. package/skills/c64-source/scripts/lib/source.ts +1121 -0
  39. package/skills/c64-static-analysis/SKILL.md +53 -13
  40. package/skills/c64-static-analysis/scripts/analyze.ts +260 -31
  41. package/skills/c64-static-analysis/scripts/lib/coverage.ts +72 -0
  42. package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +11 -5
  43. package/skills/c64-static-analysis/scripts/lib/idioms.ts +158 -0
  44. package/skills/c64-static-analysis/scripts/lib/instructions.ts +105 -0
  45. package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
  46. package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
  47. package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
  48. package/skills/c64-testing/SKILL.md +57 -4
  49. package/skills/c64-testing/references/scenario-format.md +50 -6
  50. package/skills/c64-testing/scripts/checklist.ts +3 -3
  51. package/skills/c64-testing/scripts/diff.ts +167 -0
  52. package/skills/c64-testing/scripts/lib/diff.ts +70 -0
  53. package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +183 -152
  54. package/skills/c64-testing/scripts/run.ts +241 -0
  55. package/skills/c64-testing/scripts/test.ts +20 -10
  56. package/skills/c64-unpacker/SKILL.md +88 -21
  57. package/skills/c64-unpacker/scripts/lib/evidence.ts +207 -0
  58. package/skills/c64-unpacker/scripts/lib/loads.ts +209 -0
  59. package/skills/c64-unpacker/scripts/lib/machine.ts +80 -0
  60. package/skills/c64-unpacker/scripts/lib/stages.ts +142 -0
  61. package/skills/c64-unpacker/scripts/unpack.ts +545 -61
  62. package/src/c64.ts +240 -12
  63. package/src/cli/cleanup.ts +1 -1
  64. package/src/cli/main.ts +14 -8
  65. package/src/cli/status.ts +105 -11
  66. package/src/graphics.ts +158 -0
  67. package/src/host/main.ts +57 -31
  68. package/src/host/server.ts +30 -47
  69. package/src/host/tools/c1541.ts +34 -5
  70. package/src/host/tools/index.ts +6 -2
  71. package/src/host/tools/petcat.ts +10 -3
  72. package/src/host/tools/status.ts +23 -6
  73. package/src/host/vice/adapter.ts +64 -20
  74. package/src/host/vice/chips.ts +11 -1
  75. package/src/host/vice/process.ts +19 -8
  76. package/src/host/vice/screen.ts +4 -44
  77. package/src/host/vice/session.ts +343 -60
  78. package/src/host-client/autostart.ts +55 -0
  79. package/src/host-client/connect.ts +14 -14
  80. package/src/host-client/coverage.ts +36 -0
  81. package/src/host-client/image.ts +63 -0
  82. package/src/host-client/steps.ts +175 -0
  83. package/src/host-client/tools.ts +7 -6
  84. package/src/host-client/transfer.ts +60 -10
  85. package/src/host-client/vice-session.ts +107 -28
  86. package/src/knowledge/conflicts.ts +161 -0
  87. package/src/knowledge/database.ts +150 -38
  88. package/src/knowledge/expression.ts +209 -0
  89. package/src/knowledge/graph.ts +104 -0
  90. package/src/knowledge/history.ts +105 -27
  91. package/src/knowledge/import.ts +141 -85
  92. package/src/knowledge/join.ts +211 -0
  93. package/src/knowledge/program.ts +258 -0
  94. package/src/knowledge/read.ts +445 -63
  95. package/src/knowledge/schema.ts +277 -65
  96. package/src/knowledge/text.ts +415 -0
  97. package/src/knowledge/write.ts +1145 -150
  98. package/src/mcp/main.ts +44 -19
  99. package/src/mcp/output.ts +39 -0
  100. package/src/mcp/server.ts +15 -4
  101. package/src/mcp/tools/debug.ts +202 -31
  102. package/src/mcp/tools/execution.ts +39 -5
  103. package/src/mcp/tools/input.ts +7 -4
  104. package/src/mcp/tools/machine.ts +14 -2
  105. package/src/mcp/tools/media.ts +44 -16
  106. package/src/mcp/tools/memory.ts +68 -12
  107. package/src/mcp/tools/video.ts +20 -9
  108. package/src/native/discover.ts +44 -6
  109. package/src/native/ghidra/analyze.ts +2 -2
  110. package/src/native/ghidra/index.ts +19 -3
  111. package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
  112. package/src/native/ghidra/scripts/C64Export.java +33 -1
  113. package/src/native/ghidra/scripts/C64Prepare.java +7 -1
  114. package/src/native/status.ts +15 -3
  115. package/src/native/types.ts +43 -8
  116. package/src/opcodes.ts +133 -0
  117. package/src/package.ts +21 -0
  118. package/{skills/c64-memory-map/scripts → src}/platform.ts +87 -7
  119. package/src/project.ts +166 -3
  120. package/src/protocol/messages.ts +17 -10
  121. package/src/protocol/tools.ts +27 -22
  122. package/src/protocol/vice.ts +255 -33
  123. package/src/script.ts +49 -9
  124. package/src/trace.ts +55 -23
  125. package/skills/c64-provenance/scripts/compare.ts +0 -92
  126. package/skills/c64-unpacker/scripts/evidence.ts +0 -135
package/README.md CHANGED
@@ -12,78 +12,51 @@ functional-equivalence tests.
12
12
  Architecture in one line: one MCP process owns one VICE instance and is
13
13
  VICE-only; a Host Runtime on the graphical host owns VICE and the tools that
14
14
  come with it (c1541, petcat); skill scripts call the Host Runtime for those,
15
- run ACME, DXA and Ghidra themselves, and keep project knowledge local in
16
- `.c64-re-tools/knowledge.db`.
15
+ run ACME, DXA and Ghidra themselves, and keep project knowledge local as
16
+ text in `.c64-re-tools/knowledge/`.
17
17
  The design is in [`docs/redesign/`](docs/redesign/README.md), and
18
18
  [`docs/plan.md`](docs/plan.md) tracks the implementation.
19
19
 
20
- ## Install and start
20
+ ## Install
21
21
 
22
- Requirements:
22
+ You need Node.js 24 or newer, and VICE 3.9 or later (`x64sc`, `c1541`, `petcat`) with its ROM files; use the latest VICE release. `status` and the host name the known flaws of an older VICE. ACME, DXA and Ghidra are needed only for the skills that use them: [docs/install-tools.md](docs/install-tools.md) has the steps for each system.
23
23
 
24
- - Node.js 24 or newer with npx.
25
- - On the machine with VICE: VICE 3.9 or later (`x64sc`, `c1541`, `petcat`) with its ROM files.
26
- - Where the agent runs: ACME, DXA and Ghidra, for the skills that use them.
27
-
28
- Everything runs through npx. Nothing is installed globally.
29
-
30
- ### Release or prerelease
31
-
32
- | Channel | Version |
33
- | --- | --- |
34
- | `latest` | The current release. |
35
- | `next` | The current prerelease, for example `2.0.0-rc.1`. |
36
-
37
- Until 2.0.0 is released, `latest` is the old 1.1.0. Use `next` until then. In the commands below, replace `<channel>` with `latest` or `next`. Use the same channel on both machines.
38
-
39
- ### On one machine
40
-
41
- Start the Host Runtime in its own terminal. Ctrl+C stops it and every emulator:
24
+ Start the Host Runtime and leave it running:
42
25
 
43
26
  ```
44
- npx -y --package=@henols/c64-re-tools@<channel> c64-re-tools-host
27
+ npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host
45
28
  ```
46
29
 
47
- Install into the project, check it, and start the agent:
30
+ The host logs each call from a skill script. Add `--log-console` to print also each MCP call and each c1541 or petcat run, or `--log-dir <directory>` to write them to a file. Both are off by default.
31
+
32
+ Install into your project and start the agent:
48
33
 
49
34
  ```
50
35
  cd my-c64-project
51
- npx -y @henols/c64-re-tools@<channel> install --target claude
52
- npx -y @henols/c64-re-tools@<channel> status
36
+ npx -y @henols/c64-re-tools@latest install --target claude
53
37
  claude
54
38
  ```
55
39
 
56
- ### In a dev container
40
+ Several programs in one repository: install once at the root, give each program its own folder, and start the agent at the root. A folder with `.c64-re-tools/` is a project; the agent names one with `--project <folder>`.
57
41
 
58
- On the host, start the Host Runtime on the container bridge with a shared secret:
42
+ `npx -y @henols/c64-re-tools@latest status` shows what works.
59
43
 
60
- ```
61
- export C64RT_HOST_TOKEN=$(openssl rand -hex 16)
62
- npx -y --package=@henols/c64-re-tools@<channel> c64-re-tools-host --listen 172.17.0.1
63
- ```
44
+ Until 2.0.0 is released, `@latest` is the old 1.1.0. Use the prerelease instead. For the prerelease, a dev container and debugging, see [docs/debug-install.md](docs/debug-install.md).
64
45
 
65
- In the container, set the same `C64RT_HOST_TOKEN`, then:
46
+ ### Dev container
66
47
 
67
- ```
68
- npx -y @henols/c64-re-tools@<channel> install --target claude
69
- npx -y @henols/c64-re-tools@<channel> status
70
- claude
71
- ```
72
-
73
- The container reaches the host as `host.docker.internal`. `status` must show the Host Runtime as reachable.
48
+ The agent runs in the container, VICE and the Host Runtime on the host. In `.devcontainer/devcontainer.json`:
74
49
 
75
- ### Update and remove
76
-
77
- ```
78
- npx -y @henols/c64-re-tools@<channel> update # the newest skills into the project
79
- npx -y @henols/c64-re-tools@<channel> uninstall
50
+ ```json
51
+ "features": {
52
+ "https://github.com/henols/c64-re-tools/releases/latest/download/devcontainer-feature-c64-re-tools.tgz": { "target": "claude" }
53
+ },
54
+ "runArgs": ["--add-host=host.docker.internal:host-gateway"]
80
55
  ```
81
56
 
82
- Restart the Host Runtime after a new release. `--global` installs into your home directory.
57
+ Until 2.0.0 is released, the `latest` URL has no Feature. Use the prerelease: `https://github.com/henols/c64-re-tools/releases/download/v2.0.0-rc.4/devcontainer-feature-c64-re-tools.tgz` with `{ "target": "claude", "channel": "next" }`.
83
58
 
84
- ### Debugging
85
-
86
- See [docs/debugging.md](docs/debugging.md) for the trace and the field test.
59
+ The Feature installs ACME, dxa, Java 21 and Ghidra, puts the toolkit into the project when the container is created, and updates it at each start. `target` is a harness or a comma list. `channel` is `latest` (default) or `next`. Debian and Ubuntu images only. `runArgs` is for Docker on Linux. Start the Host Runtime on the host as above.
87
60
 
88
61
  ### Settings
89
62
 
@@ -94,9 +67,8 @@ See [docs/debugging.md](docs/debugging.md) for the trace and the field test.
94
67
  | `C64RT_ACME`, `C64RT_DXA` | skills, CLI | Full path of the tool when it is not on `PATH`. |
95
68
  | `C64RT_GHIDRA` | skills, CLI | The Ghidra installation directory, when `analyzeHeadless` is not on `PATH`. |
96
69
  | `C64RT_HOST` | MCP, skills, CLI | `host:port` of the Host Runtime when it is not on 127.0.0.1, `host.docker.internal` or `host.containers.internal` at 6464. |
97
- | `C64RT_HOST_TOKEN` | host, MCP, skills, CLI | The shared secret for a Host Runtime that listens beyond loopback. |
98
70
  | `C64RT_VIDEO` | MCP | `pal` (default) or `ntsc`, fixed for the life of the MCP process. |
99
- | `C64RT_TRACE` | host, MCP, skills, CLI | A directory for a trace of what the programs do, for a person who debugs a run. See [docs/debugging.md](docs/debugging.md). Nothing of the trace reaches the agent. |
71
+ | `C64RT_TRACE` | MCP, skills, CLI | A directory for a trace of what the programs do, for a person who debugs a run. See [docs/debug-install.md](docs/debug-install.md). Nothing of the trace reaches the agent. The host uses `--log-dir` instead. |
100
72
 
101
73
  Ghidra runs with its own settings directory for each request. The toolkit
102
74
  brings its own NMOS 6510 language (all 256 opcodes); your Ghidra installation
@@ -106,7 +78,7 @@ and settings stay unchanged.
106
78
 
107
79
  1. Install for your harness: `--target claude`, `codex`, `pi`, `opencode`, `gemini`, `copilot`, `cursor` or `windsurf`. Without `--target`, `install` installs for all of them.
108
80
  2. Start the Host Runtime and leave it running.
109
- 3. Start the harness in the project, for example `claude` or `codex`, or open the project in Cursor or Windsurf. The harness starts the MCP server and finds the skills by itself.
81
+ 3. Start the harness in the project (or at the root of a repository with several projects), for example `claude` or `codex`, or open the project in Cursor or Windsurf. The harness starts the MCP server and finds the skills by itself.
110
82
  4. Ask for the result you want, in plain words. The agent picks the skills and the `c64_*` tools.
111
83
 
112
84
  Examples:
@@ -123,7 +95,7 @@ Build it, run it in the emulator, and write a test for it.
123
95
  Show me the emulator window.
124
96
  ```
125
97
 
126
- The agent keeps what it learns about the program in `.c64-re-tools/knowledge.db`. Commit that file with the project.
98
+ The agent keeps what it learns about the program as text in `.c64-re-tools/knowledge/`. Commit that folder with the project. After a `git pull` that joined the work of two computers, run `knowledge.ts merge` when a command reports conflicts.
127
99
 
128
100
  ## Skills
129
101
 
@@ -137,7 +109,8 @@ The agent keeps what it learns about the program in `.c64-re-tools/knowledge.db`
137
109
  | c64-basic | BASIC V2 listings and the SYS/USR handoff to machine code. |
138
110
  | c64-unpacker | Packing evidence, and capture of the unpacked program from the emulator. |
139
111
  | c64-memory-map | Platform meaning of addresses and register values. |
140
- | c64-provenance | Byte comparison of releases. |
112
+ | c64-provenance | Byte comparison of releases, and a library of routines that several programs share. |
113
+ | c64-source | ACME source from the image and the knowledge, checked byte by byte, and a loader for the entry state. |
141
114
  | c64-assembler | ACME builds with diagnostics and symbols. |
142
115
  | c64-testing | Original-against-rebuild scenarios with PASS, FAIL or INCONCLUSIVE. |
143
116
 
@@ -190,14 +163,7 @@ C64RT_LIVE_VICE=/usr/bin/x64sc C64RT_GHIDRA=/opt/ghidra pnpm test
190
163
 
191
164
  ACME, DXA, c1541 and petcat tests run when the tools are on `PATH`.
192
165
 
193
- To release, push a tag on a commit of `main`. The tag gives the version:
194
-
195
- ```
196
- git tag v2.0.0-rc.2 && git push origin v2.0.0-rc.2 # a prerelease, to npm next
197
- git tag v2.0.0 && git push origin v2.0.0 # a release, to npm latest
198
- ```
199
-
200
- CI checks and tests the tagged commit, publishes it to npm and makes a GitHub Release. The repository keeps the version `0.0.0-development`.
166
+ To release, push a tag. See [docs/releasing.md](docs/releasing.md).
201
167
 
202
168
  ## Layout
203
169
 
@@ -2,22 +2,7 @@
2
2
  // only reads package.json, so a caller that needs the declaration does not
3
3
  // build the plugin.
4
4
 
5
- import { readFileSync } from "node:fs";
6
- import { join } from "node:path";
7
-
8
- /**
9
- * The npm dist-tag that a version is published under: a prerelease
10
- * (2.0.0-rc.1) goes to "next", a release to "latest".
11
- */
12
- export function channelOf(version: string): "latest" | "next" {
13
- return version.includes("-") ? "next" : "latest";
14
- }
15
-
16
- /** The name, version and dist-tag of this npm package, from its package.json. */
17
- export const PACKAGE = (() => {
18
- const metadata = JSON.parse(readFileSync(join(import.meta.dirname, "..", "package.json"), "utf8")) as { name: string; version: string };
19
- return { name: metadata.name, version: metadata.version, tag: channelOf(metadata.version) };
20
- })();
5
+ import { PACKAGE } from "../src/package.ts";
21
6
 
22
7
  /**
23
8
  * The agent starts the MCP server through npx with the newest version of the
@@ -13,7 +13,8 @@ import { dirname, join, relative, resolve, sep } from "node:path";
13
13
 
14
14
  import { definePlugin, defineSkill, type Skill } from "@jalco/ap-sdk";
15
15
 
16
- import { MCP_SERVER, PACKAGE } from "./package.ts";
16
+ import { PACKAGE } from "../src/package.ts";
17
+ import { MCP_SERVER } from "./package.ts";
17
18
 
18
19
  const root = resolve(import.meta.dirname, "..");
19
20
  const SOURCE = join(root, "src");
@@ -27,9 +28,10 @@ const SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(?\s*|\bnew URL\(\s*)(["'])((?:\.\.?
27
28
 
28
29
  /**
29
30
  * The package.json of an installed skill: ES modules, #src/* resolved inside
30
- * the skill, and the version of c64-re-tools that the copy comes from.
31
+ * the skill, and the name and version of c64-re-tools that the copy comes
32
+ * from, which src/package.ts reads.
31
33
  */
32
- export const SKILL_PACKAGE = `${JSON.stringify({ type: "module", version: PACKAGE.version, imports: { "#src/*": "./src/*" } }, null, 2)}\n`;
34
+ export const SKILL_PACKAGE = `${JSON.stringify({ name: PACKAGE.name, type: "module", version: PACKAGE.version, imports: { "#src/*": "./src/*" } }, null, 2)}\n`;
33
35
 
34
36
  /** The scripts that a SKILL.md runs, by file name. */
35
37
  export function scriptsOf(skillMarkdown: string): string[] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henols/c64-re-tools",
3
- "version": "2.0.0-rc.2",
3
+ "version": "2.0.0-rc.21",
4
4
  "description": "Agent-friendly Commodore 64 engineering toolkit for reverse engineering, debugging, development and testing.",
5
5
  "license": "MIT",
6
6
  "author": "Henrik Olsson",
@@ -13,9 +13,9 @@ This skill does not run the program and does not change project knowledge.
13
13
 
14
14
  ## Inputs
15
15
 
16
- - The source root: the project directory that holds all source files of the program.
16
+ - The source root: the folder in the project that holds all source files of the program.
17
17
  - The entry source: the main file, relative to the source root.
18
- - The output path: where to write the PRG, relative to the project directory.
18
+ - The output path: where to write the PRG, relative to the project folder.
19
19
  - Optional include directories, symbol definitions and a start address.
20
20
 
21
21
  ## Tools / execution path
@@ -31,19 +31,36 @@ node <skill>/scripts/assemble.ts --source-root src --entry main.a --out build/ga
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. |
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
+ | `--all-symbols` | List all symbols. Without it, the list does not include the constants that the source does not use. `unusedConstants` gives their count. |
34
37
 
35
38
  The script copies the full source root. Put all files that the program includes under it. Version control directories stay out.
36
39
 
37
- The script prints one JSON object. The target CPU is the 6510 and the output is always a C64 PRG.
40
+ The script prints one JSON object. The target CPU is the 6510. The output is a C64 PRG, or the bytes only with `--plain`.
41
+
42
+ ### Project folder
43
+
44
+ - Paths are relative to the project folder. The project folder is the nearest folder at or above the working directory that holds `.c64-re-tools/`.
45
+ - If the working directory is not in a project and has projects below it, the script refuses and lists them. Then give `--project <folder>` to each script.
46
+ - Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
47
+ - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence` of c64-knowledge. `--evidence` refuses a file in `.c64-re-tools/local/`.
48
+ - Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
49
+ - A shell redirect (`>`) is relative to the working directory, not to the project folder. From the folder above the project, write `<folder>/.c64-re-tools/local/result.json`.
38
50
 
39
51
  ## Workflow
40
52
 
41
53
  1. Find the source root and the entry source. The entry source sets its own start address with `* = $0801` or similar.
42
54
  2. Run the script.
43
- 3. If `assembled` is `true`, the PRG is at `output`. `loadRange` tells where it loads. `symbols` gives each symbol as an `address` or a `value`.
55
+ 3. If `assembled` is `true`, the PRG is at `output`. `loadRange` tells where it loads. `symbols` gives each symbol:
56
+ - Kind `address` has a value in `loadRange`. It gives the `address`.
57
+ - 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.
58
+ - `used` is `false` when the source does not use the symbol.
44
59
  4. If `assembled` is `false`, read `diagnostics`. Each one has a severity, a file, a line and a message. Correct the source and run the script again.
45
60
  5. To run the program, give the PRG path to the c64-emulator skill.
46
61
 
62
+ The source of the c64-source skill has switches. Each switch is 0 when the build does not define it. `--define CRACK=1` puts in the patches of the switch `CRACK`. With `--compare`, only the bytes of those patches are different.
63
+
47
64
  ## Knowledge
48
65
 
49
66
  Automatic write:
@@ -59,12 +76,14 @@ A symbol from the rebuilt source can give the address of a routine in the rebuil
59
76
 
60
77
  The PRG in the project at the output path, with its load range and symbols. Or the source diagnostics when the assembly failed.
61
78
 
62
- The exit status is 0 for a PRG. The exit status is 1 for `assembled: false` and for an error. The exit status is 2 for a wrong argument.
79
+ With `--compare`, `compare` gives `identical` and one check for each segment of the program in the knowledge. A check is `identical`, `different` with the first different address, or `not covered` when the program does not hold the segment.
80
+
81
+ The exit status is 0 for a PRG. The exit status is 1 for `assembled: false`, for a `--compare` that is not identical, and for an error. The exit status is 2 for a wrong argument.
63
82
 
64
83
  ## Failure and conflicts
65
84
 
66
85
  - `assembled: false`: the source has errors. This is a normal result. Correct the source.
67
- - `installation-incomplete`: ACME is not installed on this machine. Tell the user the message.
86
+ - `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.
68
87
  - `invalid-input`: an option or a path is wrong. Read the message and correct the call.
69
88
 
70
89
  ## Handoffs
@@ -12,17 +12,28 @@ import { localToolContext } from "#src/native/local.ts";
12
12
  import type { AcmeParams } from "#src/native/types.ts";
13
13
  import { resolveProjectPath } from "#src/project.ts";
14
14
  import { runScript, UsageError } from "#src/script.ts";
15
+ import { compareBuild } from "./lib/compare.ts";
16
+ import { KnowledgeError, openForRead } from "#src/knowledge/database.ts";
17
+ import { requireProgram, type ProgramImage } from "#src/knowledge/program.ts";
18
+ import { DEFAULT_SPACE, SPACE } from "#src/knowledge/schema.ts";
15
19
 
16
20
  const USAGE = `assemble.ts --source-root <dir> --entry <file> --out <file.prg> [options]
17
21
 
18
- --source-root <dir> project directory to assemble from; all of it is sent
22
+ --source-root <dir> folder in the project to assemble from; all of it is sent
19
23
  --entry <file> the main source file, relative to the source root
20
24
  --out <file.prg> where to write the program, relative to the project
21
25
  --include <dir> include directory relative to the source root (repeatable)
22
26
  --define NAME=VALUE assembler symbol: an integer ($hex allowed) or true/false (repeatable)
23
27
  --set-pc <address> start address when the source sets none, for example '$0801'
28
+ --compare check the program byte by byte against the program in the
29
+ knowledge (D83), each segment; put it in with knowledge.ts
30
+ image of the c64-knowledge skill
31
+ --space <space> with --compare: the knowledge space of the program, c64 (the
32
+ default), drive8, or bank0 to bank255
33
+ --plain write the bytes only, without the 2-byte load address of a PRG
34
+ --all-symbols list also the constants that the source does not use
24
35
 
25
- The result is one JSON object. Addresses are $ and four hex digits.`;
36
+ The result is one JSON object. Addresses and symbol values are $ and at least four hex digits.`;
26
37
 
27
38
  function parseDefine(text: string): [string, number | boolean] {
28
39
  const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.+)$/.exec(text);
@@ -45,6 +56,10 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
45
56
  include: { type: "string", multiple: true },
46
57
  define: { type: "string", multiple: true },
47
58
  "set-pc": { type: "string" },
59
+ compare: { type: "boolean" },
60
+ space: { type: "string" },
61
+ plain: { type: "boolean" },
62
+ "all-symbols": { type: "boolean" },
48
63
  help: { type: "boolean" },
49
64
  },
50
65
  });
@@ -57,7 +72,20 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
57
72
  try {
58
73
  outPath = resolveProjectPath(out);
59
74
  } catch {
60
- throw new UsageError(`--out must be a path relative to the project directory that stays inside it, not ${out}`);
75
+ throw new UsageError(`--out must be a path relative to the project folder that stays inside it, not ${out}`);
76
+ }
77
+ if (values.space !== undefined && !values.compare) throw new UsageError("--space works only with --compare");
78
+ const space = values.space ?? DEFAULT_SPACE;
79
+ if (!SPACE.test(space)) throw new UsageError("--space must be c64, drive8, or bank0 to bank255");
80
+ // Read the program before ACME runs, so a project without it stops at once.
81
+ let program: ProgramImage | undefined;
82
+ if (values.compare) {
83
+ const db = openForRead();
84
+ try {
85
+ program = requireProgram(db, space);
86
+ } finally {
87
+ db?.close();
88
+ }
61
89
  }
62
90
  const defines = Object.fromEntries((values.define ?? []).map(parseDefine));
63
91
  let setPc: number | undefined;
@@ -75,29 +103,36 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
75
103
  const { result, attachments } = await assemble(params, tree.contents, localToolContext());
76
104
  if (!result.assembled) return { output: { assembled: false, diagnostics: result.diagnostics }, failed: true };
77
105
  mkdirSync(dirname(outPath), { recursive: true });
78
- writeFileSync(outPath, attachments![0]!);
106
+ writeFileSync(outPath, values.plain ? attachments![0]!.subarray(2) : attachments![0]!);
79
107
  const range = result.loadRange!;
108
+ const compared = program === undefined ? undefined : compareBuild(attachments![0]!, program);
109
+ // An include file of platform names gives many constants that the program does not use.
110
+ const listed = result.symbols!.filter((symbol) => values["all-symbols"] || symbol.kind === "address" || symbol.used);
80
111
  return {
81
112
  output: {
82
113
  assembled: true,
83
114
  output: out,
115
+ ...(values.plain ? { plain: true } : {}),
84
116
  loadRange: { start: formatC64Address(range.start), end: formatC64Address(range.end), bytes: range.bytes },
85
- symbols: result.symbols!.map((symbol) =>
117
+ symbols: listed.map((symbol) =>
86
118
  symbol.kind === "address"
87
119
  ? { name: symbol.name, kind: "address", address: formatC64Address(symbol.address), used: symbol.used }
88
- : { name: symbol.name, kind: "constant", value: symbol.value, used: symbol.used },
120
+ : { name: symbol.name, kind: "constant", value: `$${symbol.value.toString(16).padStart(4, "0")}`, used: symbol.used },
89
121
  ),
122
+ ...(values["all-symbols"] ? {} : { unusedConstants: result.symbols!.length - listed.length }),
90
123
  diagnostics: result.diagnostics,
124
+ ...(compared === undefined ? {} : { compare: compared }),
91
125
  },
92
- failed: false,
126
+ // A build that differs from its reference is a failed check.
127
+ failed: compared !== undefined && !compared.identical,
93
128
  };
94
129
  }
95
130
 
96
131
  await runScript(
97
- async () => {
98
- const { output, failed } = await run(process.argv.slice(2));
132
+ async (argv) => {
133
+ const { output, failed } = await run(argv);
99
134
  if (failed) process.exitCode = 1;
100
135
  return output;
101
136
  },
102
- { usage: USAGE },
137
+ { usage: USAGE, refusals: [KnowledgeError] },
103
138
  );
@@ -0,0 +1,48 @@
1
+ // The byte check of a build (D47): the assembled program against the program
2
+ // in the knowledge (D83), segment by segment.
3
+
4
+ import { formatC64Address, prgRange } from "#src/c64.ts";
5
+
6
+ export interface SegmentCheck {
7
+ start: string;
8
+ end: string;
9
+ result: "identical" | "different" | "not covered";
10
+ /** Bytes of the segment that the build does not hold. */
11
+ uncovered?: number;
12
+ differences?: number;
13
+ firstDifference?: { address: string; built: number; reference: number };
14
+ }
15
+
16
+ /** The program that the build is checked against: its segments and its bytes in them. */
17
+ export interface Reference {
18
+ segments: ReadonlyArray<{ start: number; end: number }>;
19
+ byteAt: (address: number) => number;
20
+ }
21
+
22
+ /** Compares the assembled PRG with the reference, segment by segment. */
23
+ export function compareBuild(prg: Uint8Array, program: Reference): { identical: boolean; segments: SegmentCheck[] } {
24
+ const built = prgRange(prg);
25
+ const checks = program.segments.map((segment): SegmentCheck => {
26
+ const from = Math.max(segment.start, built.start);
27
+ const to = Math.min(segment.end, built.end);
28
+ const shown = { start: formatC64Address(segment.start), end: formatC64Address(segment.end) };
29
+ if (from > to) return { ...shown, result: "not covered", uncovered: segment.end - segment.start + 1 };
30
+ let differences = 0;
31
+ let first: SegmentCheck["firstDifference"];
32
+ for (let address = from; address <= to; address++) {
33
+ const builtByte = built.body[address - built.start]!;
34
+ const referenceByte = program.byteAt(address);
35
+ if (builtByte === referenceByte) continue;
36
+ differences++;
37
+ first ??= { address: formatC64Address(address), built: builtByte, reference: referenceByte };
38
+ }
39
+ const uncovered = segment.end - segment.start + 1 - (to - from + 1);
40
+ return {
41
+ ...shown,
42
+ result: differences > 0 ? "different" : uncovered > 0 ? "not covered" : "identical",
43
+ ...(uncovered > 0 ? { uncovered } : {}),
44
+ ...(differences > 0 ? { differences, firstDifference: first! } : {}),
45
+ };
46
+ });
47
+ return { identical: checks.every((check) => check.result === "identical"), segments: checks };
48
+ }
@@ -13,7 +13,7 @@ This skill does not run the program and does not change project knowledge.
13
13
 
14
14
  ## Inputs
15
15
 
16
- - The program: a PRG file, relative to the project directory.
16
+ - The program: a PRG file, relative to the project folder.
17
17
 
18
18
  ## Tools / execution path
19
19
 
@@ -25,14 +25,23 @@ node <skill>/scripts/basic.ts extracted/game.prg
25
25
 
26
26
  The script prints one JSON object. Addresses are `$` and four hex digits.
27
27
 
28
+ ### Project folder
29
+
30
+ - Paths are relative to the project folder. The project folder is the nearest folder at or above the working directory that holds `.c64-re-tools/`.
31
+ - If the working directory is not in a project and has projects below it, the script refuses and lists them. Then give `--project <folder>` to each script.
32
+ - Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
33
+ - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence` of c64-knowledge. `--evidence` refuses a file in `.c64-re-tools/local/`.
34
+ - Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
35
+ - A shell redirect (`>`) is relative to the working directory, not to the project folder. From the folder above the project, write `<folder>/.c64-re-tools/local/result.json`.
36
+
28
37
  ## Workflow
29
38
 
30
39
  1. Run the script on the program.
31
40
  2. Read `listing`. Each line starts with its BASIC line number. Keywords show in lower case. Control characters in strings show in braces, for example `{clr}`.
32
41
  3. Read `handoffs`. Each handoff has a `kind` (`sys` or `usr`) and the BASIC `line`.
33
- - A `sys` handoff with an `address`: BASIC calls the machine code at that address.
34
- - A handoff with `computed: true`: the address comes from a calculation at run time. Use the c64-emulator skill to find the address.
35
- 4. Compare the address with `loadRange` and `basicEnd`. From `basicEnd` to the end of `loadRange`, the file holds bytes that are not BASIC. Usually this is the machine code that the handoff calls.
42
+ - A `sys` handoff with an `address`: BASIC calls the machine code at that address. The script calculates a SYS argument with numbers, π, `+`, `-`, `*`, `/` and brackets. For example, `SYS π*656` calls `$080c`.
43
+ - A handoff with `computed: true`: the address comes from a calculation at run time, for example with `PEEK`. A `sys` handoff gives its argument in `expression`. Use the c64-emulator skill to find the address.
44
+ 4. Compare the address with `loadRange` and `basicEnd`. From `basicEnd` to the end of `loadRange`, the file holds bytes that are not BASIC. Usually this is the machine code that the handoff calls. Some programs have no end marker (two zero bytes) after the last line. The machine code starts directly after the last line. Then `basicEnd` is the first byte of that machine code.
36
45
 
37
46
  The address ranges are for a load to the load address of the file. A load without `,1` puts a BASIC program at `$0801`. Most loaders have `$0801` as their load address.
38
47
 
@@ -54,10 +63,9 @@ The exit status is 0 for a listing. The exit status is 1 for `decoded: false` an
54
63
 
55
64
  ## Failure and conflicts
56
65
 
57
- - `decoded: false`: the file is not a BASIC program. This is a normal result. Usually the file is machine code. Give it to the c64-static-analysis skill.
66
+ - `decoded: false`: the file is not a BASIC program. This is a normal result. Usually the file is machine code. Give it to the c64-static-analysis skill. A memory dump also gives `decoded: false`: its first two bytes are not a load address, and its bytes go past `$ffff`.
58
67
  - An empty `handoffs` list: the BASIC does not start machine code with SYS or USR. Do not make up a handoff. Look for a POKE into a vector or a LOAD of another file in the listing.
59
- - `installation-incomplete`: petcat is not on the host. Tell the user the message.
60
- - `operation-failed` with a message about the host runtime: the host runtime does not run. Tell the user to start the host runtime with `npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host`.
68
+ - `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.
61
69
  - `not-found` or `invalid-input`: the path or an argument is wrong. Read the message and correct the call.
62
70
 
63
71
  ## Handoffs
@@ -11,7 +11,7 @@ import { runScript, UsageError } from "#src/script.ts";
11
11
 
12
12
  const USAGE = `basic.ts <program>
13
13
 
14
- <program> is a PRG file relative to the project directory. The result is one
14
+ <program> is a PRG file relative to the project folder. The result is one
15
15
  JSON object. Addresses are $ and four hex digits.`;
16
16
 
17
17
  async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }> {
@@ -22,7 +22,7 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
22
22
  const result = await decodeBasic({ program });
23
23
  if (!result.decoded) return { output: { decoded: false, reason: result.reason }, failed: true };
24
24
  const size = statSync(resolveProjectPath(program)).size - 2;
25
- const end = Math.min(0xffff, result.loadAddress + size - 1);
25
+ const end = result.loadAddress + size - 1;
26
26
  return {
27
27
  output: {
28
28
  decoded: true,
@@ -37,8 +37,8 @@ async function run(argv: string[]): Promise<{ output: unknown; failed: boolean }
37
37
  }
38
38
 
39
39
  await runScript(
40
- async () => {
41
- const { output, failed } = await run(process.argv.slice(2));
40
+ async (argv) => {
41
+ const { output, failed } = await run(argv);
42
42
  if (failed) process.exitCode = 1;
43
43
  return output;
44
44
  },
@@ -1,25 +1,26 @@
1
1
  ---
2
2
  name: c64-disk
3
- description: Use this skill to examine a C64 disk image (D64, D71, D81 or G64) and to extract files from it into the project. It shows the directory, the BAM, the directory entry of a file and the sector chain of a file. Do not use it to run a program or to decode BASIC. Give an extracted file to the c64-basic, c64-unpacker or c64-static-analysis skill.
3
+ description: Use this skill to examine a C64 disk image (D64, D71, D81 or G64), a T64 tape file or a CRT cartridge file. It extracts files into the project. It shows the directory, the BAM, the directory entry of a file and the sector chain of a file. It writes each bank of a cartridge as a PRG. Do not use it to run a program or to decode BASIC. Give an extracted file to the c64-basic, c64-unpacker or c64-static-analysis skill.
4
4
  ---
5
5
 
6
6
  # c64-disk
7
7
 
8
8
  ## Purpose
9
9
 
10
- Examine a C64 disk image. Extract a file from the image into the project.
10
+ Examine a C64 disk image, a T64 tape file or a CRT cartridge file. Extract a file from it into the project.
11
11
 
12
12
  This skill does not change the disk image and does not change project knowledge.
13
13
 
14
14
  ## Inputs
15
15
 
16
- - The disk image: a `.d64`, `.d71`, `.d81` or `.g64` file, relative to the project directory.
16
+ - The disk image: a `.d64`, `.d71`, `.d81` or `.g64` file, relative to the project folder.
17
17
  - For one file: the file name as the directory shows it.
18
- - To extract a file: the output path, relative to the project directory.
18
+ - To extract a file: the output path, relative to the project folder.
19
+ - A T64 tape file or a CRT cartridge file, relative to the project folder.
19
20
 
20
21
  ## Tools / execution path
21
22
 
22
- The script sends the image to the host runtime. The host runs c1541 and sends the result back. The script writes an extracted file into the project.
23
+ The script sends a disk image to the host runtime. The host runs c1541 and sends the result back. The script writes an extracted file into the project. It reads T64 and CRT files itself.
23
24
 
24
25
  ```
25
26
  node <skill>/scripts/disk.ts <action> <image> [name] [--out <file>]
@@ -28,21 +29,34 @@ node <skill>/scripts/disk.ts <action> <image> [name] [--out <file>]
28
29
  | Need | Command |
29
30
  |---|---|
30
31
  | The disk name, the free blocks and the files | `directory original/game.d64` |
31
- | The free and used sectors of each track | `bam original/game.d64` |
32
+ | The free and used sectors of each track, and the used sectors that no file uses | `bam original/game.d64` |
32
33
  | The type, size and first block of one file | `entry original/game.d64 GAME` |
33
34
  | The track and sector of each block of one file | `chain original/game.d64 GAME` |
34
35
  | Extract one file | `read original/game.d64 GAME --out extracted/game.prg` |
36
+ | The files of a tape | `directory original/game.t64` |
37
+ | Extract one file of a tape | `read original/game.t64 GAME --out extracted/game.prg` |
38
+ | The banks of a cartridge | `directory original/game.crt` |
39
+ | Write each bank of a cartridge as a PRG | `crt original/game.crt --out cart` |
35
40
 
36
41
  The script prints one JSON object.
37
42
 
38
- File names show the characters that the C64 shows in a directory list. A byte that has no character shows as `{$xx}`, for example `{$c1}`. Write the name in the same form. The letter case of a name is not important. Put the name in single quotes in a shell, for example `'GAME {$c1}'`.
43
+ File names show the characters that the C64 shows in a directory list. A byte that has no character shows as `{$xx}`, for example `{$c1}`. Write the name in the same form. The letter case of a name is not important. Put the name in single quotes in a shell, for example `'GAME {$c1}'`. A name that starts with `-` needs `--` before it, after all options, for example `read d.d64 --out x.prg -- '-----'`.
44
+
45
+ ### Project folder
46
+
47
+ - Paths are relative to the project folder. The project folder is the nearest folder at or above the working directory that holds `.c64-re-tools/`.
48
+ - If the working directory is not in a project and has projects below it, the script refuses and lists them. Then give `--project <folder>` to each script.
49
+ - Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
50
+ - Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence` of c64-knowledge. `--evidence` refuses a file in `.c64-re-tools/local/`.
51
+ - Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
52
+ - A shell redirect (`>`) is relative to the working directory, not to the project folder. From the folder above the project, write `<folder>/.c64-re-tools/local/result.json`.
39
53
 
40
54
  ## Workflow
41
55
 
42
56
  1. Run `directory`. Find the file in `entries`. Each entry has a `name`, a `type`, a size in `blocks` and the `closed` and `locked` flags.
43
57
  2. Run `read` with an output path to extract the file. A PRG file keeps its 2-byte load address at the start.
44
58
  3. Run `entry` or `chain` only when the disk structure is important, for example for a loader that reads sectors directly.
45
- 4. Run `bam` only to find used sectors that no file uses.
59
+ 4. Run `bam` only to find used sectors that no file uses. `unreferenced` lists them. The list does not include the header, the directory and the BAM blocks. These sectors can hold data that a loader reads directly.
46
60
 
47
61
  ## Knowledge
48
62
 
@@ -55,6 +69,15 @@ Do not store:
55
69
 
56
70
  A downstream skill records the meaning of the extracted content.
57
71
 
72
+ ## Tapes and cartridges
73
+
74
+ The script reads T64 and CRT files itself. The host runtime is not necessary for them.
75
+
76
+ - T64: `directory` and `read`. Many T64 files have a wrong end address. Then the entry has `fixedSize: true`, and the size comes from the position of the next file.
77
+ - CRT: `directory` and `crt`. The script reads hardware types 0 (normal 8K, 16K or Ultimax), 5 (Ocean), 19 (Magic Desk) and 32 (EasyFlash). It refuses other types by name. `crt` writes `crt.json` and one PRG for each CHIP packet, for example `bank3-8000.prg`.
78
+ - A cartridge bank uses the same addresses as other banks. Analyze each bank PRG with c64-static-analysis and `--space bank3`, and record its knowledge in space `bank3`.
79
+ - TAP: a TAP file holds tape signals, and turbo loaders have their own formats. The script does not read it. Start it with the c64-emulator skill, or capture its parts with `loads` of the c64-unpacker skill.
80
+
58
81
  ## Result
59
82
 
60
83
  The directory, the BAM, an entry or a sector chain as JSON. Or the extracted file in the project at the output path.
@@ -65,8 +88,7 @@ The exit status is 0 for a result with the file. The exit status is 1 for `found
65
88
 
66
89
  - `found: false`: the image has no file with that name. This is a normal result. Read the directory again and compare the names.
67
90
  - `media-error`: the image has a defect or is not a disk image. The message tells the problem, for example a sector chain that loops. Tell the user. Do not change the image.
68
- - `installation-incomplete`: c1541 is not on the host. Tell the user the message.
69
- - `operation-failed` with a message about the host runtime: the host runtime does not run. Tell the user to start the host runtime with `npx -y --package=@henols/c64-re-tools@latest c64-re-tools-host`.
91
+ - `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.
70
92
  - `invalid-input`: an argument or a path is wrong. Read the message and correct the call.
71
93
 
72
94
  A file that is not `closed` was not written fully. Its data can be incomplete.
@@ -75,5 +97,6 @@ A file that is not `closed` was not written fully. Its data can be incomplete.
75
97
 
76
98
  - BASIC program → c64-basic
77
99
  - Packed program → c64-unpacker
78
- - Machine code → c64-static-analysis
100
+ - Machine code, also a cartridge bank → c64-static-analysis
101
+ - A TAP file → c64-unpacker
79
102
  - Run the program → c64-emulator