@henols/c64-re-tools 0.1.4

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 (35) hide show
  1. package/README.md +61 -0
  2. package/bin/cli.mjs +226 -0
  3. package/package.json +53 -0
  4. package/skills/acme-build/SKILL.md +224 -0
  5. package/skills/acme-build/scripts/acme.mjs +263 -0
  6. package/skills/acme-build/template.a +39 -0
  7. package/skills/c64-memory-mapping/SKILL.md +199 -0
  8. package/skills/c64-memory-mapping/memmap.json +8800 -0
  9. package/skills/c64-memory-mapping/scripts/driver.mjs +553 -0
  10. package/skills/c64-program-recon/SKILL.md +172 -0
  11. package/skills/c64-program-recon/references/control-flow.md +174 -0
  12. package/skills/c64-program-recon/references/graphics.md +73 -0
  13. package/skills/c64-program-recon/references/observation-hazards.md +118 -0
  14. package/skills/c64-program-recon/references/reconstruction.md +128 -0
  15. package/skills/c64-program-recon/references/sound-and-input.md +68 -0
  16. package/skills/c64-program-recon/references/tool-selection.md +55 -0
  17. package/skills/c64-program-recon/scripts/derive.mjs +364 -0
  18. package/skills/c64-program-recon/templates/memory-map.template.md +62 -0
  19. package/skills/c64-provenance-diff/SKILL.md +257 -0
  20. package/skills/c64-provenance-diff/scripts/diff-images.mjs +981 -0
  21. package/skills/c64-provenance-diff/scripts/diff-images.test.mjs +665 -0
  22. package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +383 -0
  23. package/skills/c64-ram-capture/SKILL.md +306 -0
  24. package/skills/c64-ram-capture/scripts/compare.mjs +258 -0
  25. package/skills/c64-ram-capture/scripts/d64-parse.mjs +243 -0
  26. package/skills/c64-ram-capture/scripts/d64-parse.test.mjs +243 -0
  27. package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +317 -0
  28. package/skills/c64-ram-capture/scripts/dump-artifacts.test.mjs +133 -0
  29. package/skills/c64-ram-capture/scripts/project-paths.mjs +81 -0
  30. package/skills/c64-ram-capture/scripts/releases.mjs +109 -0
  31. package/skills/c64-ram-capture/scripts/test-corpus.mjs +75 -0
  32. package/skills/c64-ram-capture/scripts/watch-loads.mjs +575 -0
  33. package/skills/c64-ram-capture/scripts/watch-loads.test.mjs +339 -0
  34. package/skills/c64-ram-capture/templates/capture-record.template.md +59 -0
  35. package/skills/vice-wedge-triage/SKILL.md +149 -0
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @henols/c64-re-tools
2
+
3
+ One-command installer that adds the **C64 reverse-engineering skills** and the
4
+ **VICE emulator MCP server** ([`@henols/vice-mcp`](https://www.npmjs.com/package/@henols/vice-mcp))
5
+ to a project, so an MCP client such as Claude Code can drive a Commodore 64
6
+ emulator for reverse-engineering work.
7
+
8
+ ## Usage
9
+
10
+ From the project you want to set up:
11
+
12
+ ```sh
13
+ npx @henols/c64-re-tools
14
+ ```
15
+
16
+ That will:
17
+
18
+ 1. Copy the bundled skills into `<project>/.claude/skills/`
19
+ 2. Add a `vice` server to `<project>/.mcp.json` (existing servers are preserved),
20
+ launched via `npx -y @henols/vice-mcp`
21
+
22
+ Then restart Claude Code in that project.
23
+
24
+ ### Options
25
+
26
+ | Option | Effect |
27
+ | --- | --- |
28
+ | `[targetDir]` | Install into this directory instead of the current one. |
29
+ | `--force` | Overwrite existing skills and an existing `vice` MCP entry. |
30
+ | `--vendor` | Also `npm install -D @henols/vice-mcp` into the project and wire `.mcp.json` to the local copy (pinned / offline), instead of `npx`. |
31
+ | `--dry-run`, `-n` | Show what would change without writing anything. |
32
+ | `--help`, `-h` | Show help. |
33
+
34
+ Re-running is safe: existing skills and an existing `vice` entry are kept unless
35
+ you pass `--force`.
36
+
37
+ ## What gets installed
38
+
39
+ - **Skills** — `acme-build`, `c64-memory-mapping`, `c64-program-recon`,
40
+ `c64-provenance-diff`, `c64-ram-capture`, `vice-wedge-triage`.
41
+ - **MCP server** — `@henols/vice-mcp`, exposing the `vice` tools.
42
+
43
+ ## Requirements
44
+
45
+ - **This installer** runs on Node ≥ 18.
46
+ - **The VICE MCP server** it wires up requires **Node ≥ 22.18** (or ≥ 23.6), and a
47
+ host with VICE (`x64sc`) reachable from the MCP client. See the
48
+ [`@henols/vice-mcp`](https://www.npmjs.com/package/@henols/vice-mcp) readme.
49
+
50
+ ## Alternative: Claude Code plugin
51
+
52
+ If you use Claude Code, you can instead install everything as a plugin:
53
+
54
+ ```
55
+ /plugin marketplace add henols/c64-re-tools
56
+ /plugin install c64-re-tools@c64-re-tools
57
+ ```
58
+
59
+ ## License
60
+
61
+ MIT © Henrik Olsson
package/bin/cli.mjs ADDED
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ // @henols/c64-re-tools installer.
3
+ //
4
+ // Installs the C64 reverse-engineering skills and wires the VICE MCP server into
5
+ // a target project:
6
+ // npx @henols/c64-re-tools [targetDir] [--force] [--dry-run] [--vendor]
7
+ //
8
+ // * copies the bundled skills into <target>/.claude/skills/
9
+ // * merges a `vice` server entry into <target>/.mcp.json (never clobbering
10
+ // other servers), launching it via `npx -y @henols/vice-mcp`
11
+ // * with --vendor, also `npm install`s @henols/vice-mcp into the project and
12
+ // wires .mcp.json to the local copy (pinned/offline use)
13
+ import { spawnSync } from "node:child_process";
14
+ import { fileURLToPath } from "node:url";
15
+ import { dirname, join, resolve } from "node:path";
16
+ import {
17
+ existsSync,
18
+ readFileSync,
19
+ writeFileSync,
20
+ mkdirSync,
21
+ readdirSync,
22
+ cpSync,
23
+ } from "node:fs";
24
+
25
+ const HERE = dirname(fileURLToPath(import.meta.url)); // installer/bin (packed) or repo installer/bin (dev)
26
+ const PKG_ROOT = dirname(HERE);
27
+ const SKILLS_SRC = join(PKG_ROOT, "skills");
28
+
29
+ const SELF = readJson(join(PKG_ROOT, "package.json")) ?? {};
30
+ const SELF_VERSION = typeof SELF.version === "string" ? SELF.version : "0.0.0";
31
+ const MCP_PKG = "@henols/vice-mcp";
32
+ // Wire the project to the exact vice-mcp version this installer was built against.
33
+ const MCP_VERSION =
34
+ (SELF.dependencies && typeof SELF.dependencies[MCP_PKG] === "string"
35
+ ? SELF.dependencies[MCP_PKG].replace(/^[\^~]/, "")
36
+ : SELF_VERSION);
37
+
38
+ function readJson(path) {
39
+ try {
40
+ return JSON.parse(readFileSync(path, "utf8"));
41
+ } catch {
42
+ return undefined;
43
+ }
44
+ }
45
+
46
+ function parseArgs(argv) {
47
+ const opts = { force: false, dryRun: false, vendor: false, help: false, target: undefined };
48
+ for (const arg of argv) {
49
+ if (arg === "--force") opts.force = true;
50
+ else if (arg === "--dry-run" || arg === "-n") opts.dryRun = true;
51
+ else if (arg === "--vendor") opts.vendor = true;
52
+ else if (arg === "--help" || arg === "-h") opts.help = true;
53
+ else if (arg.startsWith("-")) {
54
+ console.error(`c64-re-tools: unknown flag ${JSON.stringify(arg)} (try --help)`);
55
+ process.exit(2);
56
+ } else if (opts.target === undefined) opts.target = arg;
57
+ else {
58
+ console.error(`c64-re-tools: unexpected extra argument ${JSON.stringify(arg)} (try --help)`);
59
+ process.exit(2);
60
+ }
61
+ }
62
+ return opts;
63
+ }
64
+
65
+ const HELP = `c64-re-tools -- install the C64 reverse-engineering skills + VICE MCP server into a project
66
+
67
+ Usage:
68
+ npx @henols/c64-re-tools [targetDir] [options]
69
+
70
+ Arguments:
71
+ targetDir Project to install into (default: current directory)
72
+
73
+ Options:
74
+ --force Overwrite existing skills and an existing 'vice' MCP entry
75
+ --vendor Also 'npm install -D ${MCP_PKG}' into the project and wire
76
+ .mcp.json to the local copy (pinned/offline), instead of npx
77
+ --dry-run, -n Show what would change without writing anything
78
+ --help, -h Show this help
79
+
80
+ What it does:
81
+ 1. Copies bundled skills into <target>/.claude/skills/
82
+ 2. Adds a 'vice' server to <target>/.mcp.json (other servers are preserved)
83
+
84
+ Requires Node >= 22.18 to RUN the vice MCP server (this installer runs on Node >= 18).`;
85
+
86
+ function viceServerEntry(vendor) {
87
+ return {
88
+ command: "npx",
89
+ args: vendor ? [MCP_PKG] : ["-y", `${MCP_PKG}@${MCP_VERSION}`],
90
+ timeout: 150000,
91
+ env: { MASTRA_TELEMETRY_DISABLED: "1" },
92
+ };
93
+ }
94
+
95
+ function installSkills(target, { force, dryRun }) {
96
+ if (!existsSync(SKILLS_SRC)) {
97
+ console.error(
98
+ `c64-re-tools: FAIL -- bundled skills not found at ${SKILLS_SRC}. ` +
99
+ `(In a dev checkout, run 'node scripts/sync-skills.mjs' first.)`
100
+ );
101
+ process.exit(1);
102
+ }
103
+ const names = readdirSync(SKILLS_SRC, { withFileTypes: true })
104
+ .filter((d) => d.isDirectory())
105
+ .map((d) => d.name)
106
+ .sort();
107
+ const destRoot = join(target, ".claude", "skills");
108
+ const installed = [];
109
+ const skipped = [];
110
+ for (const name of names) {
111
+ const dest = join(destRoot, name);
112
+ if (existsSync(dest) && !force) {
113
+ skipped.push(name);
114
+ continue;
115
+ }
116
+ if (!dryRun) {
117
+ mkdirSync(destRoot, { recursive: true });
118
+ cpSync(join(SKILLS_SRC, name), dest, { recursive: true, force: true });
119
+ }
120
+ installed.push(name);
121
+ }
122
+ return { destRoot, installed, skipped, total: names.length };
123
+ }
124
+
125
+ function wireMcp(target, { force, dryRun, vendor }) {
126
+ const mcpPath = join(target, ".mcp.json");
127
+ let config = { mcpServers: {} };
128
+ if (existsSync(mcpPath)) {
129
+ const parsed = readJson(mcpPath);
130
+ if (parsed === undefined) {
131
+ console.error(
132
+ `c64-re-tools: FAIL -- ${mcpPath} exists but is not valid JSON. ` +
133
+ `Refusing to overwrite it; fix or remove it and re-run.`
134
+ );
135
+ process.exit(1);
136
+ }
137
+ config = parsed;
138
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
139
+ console.error(`c64-re-tools: FAIL -- ${mcpPath} is not a JSON object.`);
140
+ process.exit(1);
141
+ }
142
+ if (typeof config.mcpServers !== "object" || config.mcpServers === null) {
143
+ config.mcpServers = {};
144
+ }
145
+ }
146
+ const existed = Object.prototype.hasOwnProperty.call(config.mcpServers, "vice");
147
+ let action;
148
+ if (existed && !force) {
149
+ action = "kept"; // leave the user's existing entry alone
150
+ } else {
151
+ action = existed ? "updated" : "added";
152
+ if (!dryRun) {
153
+ config.mcpServers.vice = viceServerEntry(vendor);
154
+ mkdirSync(dirname(mcpPath), { recursive: true });
155
+ writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
156
+ }
157
+ }
158
+ return { mcpPath, action };
159
+ }
160
+
161
+ function vendorInstall(target, { dryRun }) {
162
+ const spec = `${MCP_PKG}@${MCP_VERSION}`;
163
+ if (dryRun) return { ran: false, spec };
164
+ const res = spawnSync("npm", ["install", "--save-dev", spec], {
165
+ cwd: target,
166
+ stdio: "inherit",
167
+ });
168
+ if (res.status !== 0) {
169
+ console.error(
170
+ `c64-re-tools: WARN -- 'npm install --save-dev ${spec}' exited ${res.status}. ` +
171
+ `Skills and .mcp.json were still written; install the package manually if needed.`
172
+ );
173
+ return { ran: true, ok: false, spec };
174
+ }
175
+ return { ran: true, ok: true, spec };
176
+ }
177
+
178
+ function main() {
179
+ const opts = parseArgs(process.argv.slice(2));
180
+ if (opts.help) {
181
+ console.log(HELP);
182
+ return;
183
+ }
184
+ const target = resolve(opts.target ?? process.cwd());
185
+ if (!existsSync(target)) {
186
+ console.error(`c64-re-tools: FAIL -- target directory does not exist: ${target}`);
187
+ process.exit(1);
188
+ }
189
+
190
+ console.error(`c64-re-tools ${SELF_VERSION} -> ${target}${opts.dryRun ? " (dry run)" : ""}`);
191
+
192
+ const skills = installSkills(target, opts);
193
+ if (opts.vendor) vendorInstall(target, opts);
194
+ const mcp = wireMcp(target, opts);
195
+
196
+ // Summary
197
+ console.error("");
198
+ console.error(` skills -> ${skills.destRoot}`);
199
+ console.error(
200
+ ` ${skills.installed.length} installed${
201
+ opts.force ? "" : `, ${skills.skipped.length} already present (use --force to overwrite)`
202
+ } of ${skills.total}`
203
+ );
204
+ if (skills.installed.length) console.error(` + ${skills.installed.join(", ")}`);
205
+ if (skills.skipped.length && !opts.force)
206
+ console.error(` = ${skills.skipped.join(", ")} (kept)`);
207
+ console.error(` mcp -> ${mcp.mcpPath}`);
208
+ if (mcp.action === "kept") {
209
+ console.error(` 'vice' already configured -- kept (use --force to overwrite)`);
210
+ } else {
211
+ console.error(
212
+ ` 'vice' ${mcp.action}${
213
+ opts.vendor ? ` (local ${MCP_PKG})` : ` (npx -y ${MCP_PKG}@${MCP_VERSION})`
214
+ }`
215
+ );
216
+ }
217
+ console.error("");
218
+ if (opts.dryRun) {
219
+ console.error("Dry run -- nothing was written.");
220
+ } else {
221
+ console.error("Done. Restart Claude Code in this project so it picks up the skills and MCP server.");
222
+ console.error("Note: running the vice MCP server requires Node >= 22.18 (or >= 23.6).");
223
+ }
224
+ }
225
+
226
+ main();
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@henols/c64-re-tools",
3
+ "version": "0.1.4",
4
+ "description": "Installer that adds the C64 reverse-engineering skills and the VICE emulator MCP server (@henols/vice-mcp) to a project.",
5
+ "type": "module",
6
+ "bin": {
7
+ "c64-re-tools": "bin/cli.mjs"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "skills/",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "license": "MIT",
21
+ "author": {
22
+ "name": "Henrik Olsson",
23
+ "url": "https://github.com/henols"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/henols/c64-re-tools.git",
28
+ "directory": "installer"
29
+ },
30
+ "homepage": "https://github.com/henols/c64-re-tools#readme",
31
+ "bugs": {
32
+ "url": "https://github.com/henols/c64-re-tools/issues"
33
+ },
34
+ "keywords": [
35
+ "c64",
36
+ "commodore-64",
37
+ "vice",
38
+ "6502",
39
+ "6510",
40
+ "acme",
41
+ "reverse-engineering",
42
+ "mcp",
43
+ "claude",
44
+ "installer"
45
+ ],
46
+ "dependencies": {
47
+ "@henols/vice-mcp": "0.1.4"
48
+ },
49
+ "scripts": {
50
+ "sync-skills": "node scripts/sync-skills.mjs",
51
+ "prepack": "node scripts/sync-skills.mjs"
52
+ }
53
+ }
@@ -0,0 +1,224 @@
1
+ ---
2
+ name: acme-build
3
+ description: Assemble Commodore 64 6510 assembly with the ACME cross assembler. Use when asked to assemble, build, compile or link .a/.asm 6502/6510 source, produce a C64 .prg, scaffold a new C64 program, list the symbols a program uses, or turn a .prg back into ACME source.
4
+ ---
5
+
6
+ # Assembling C64 source with ACME
7
+
8
+ Source in, `.prg` out. Everything goes through one script:
9
+
10
+ ```bash
11
+ A=.claude/skills/acme-build/scripts/acme.mjs # from the repo root
12
+
13
+ node $A new game.asm # scaffold a C64 program
14
+ node $A build game.asm # assemble -> .prg .sym .vs .rep
15
+ node $A sym game.asm # the symbols the program uses
16
+ node $A disasm game.prg # object code back into ACME source
17
+ ```
18
+
19
+ The script wraps `acme` and `toacme` and nothing else — **assembling only**. Running
20
+ the result on a C64 belongs to the emulator skills (`acme.mjs:3-4` says so, and the
21
+ absent `run` verb is not an omission). It contacts nothing.
22
+
23
+ Options: `-o FILE` `--out-dir DIR` `-f FORMAT` `--setpc ADDR` `-DSYM=VAL`
24
+ `-I DIR` `--no-report` `--json`.
25
+
26
+ ## Build
27
+
28
+ ```bash
29
+ node $A build game.asm
30
+ ```
31
+ ```
32
+ Saving 53 (0x35) bytes (0x801 - 0x836 exclusive).
33
+ built game.prg (55 bytes) load $0801-$0836 53 bytes of code
34
+ symbols: game.sym (4 used / 121 total)
35
+ debug labels: game.vs (4 addresses)
36
+ ```
37
+
38
+ The indented line is ACME's own `-v1` note, passed straight through. Three side
39
+ files land next to the `.prg`, and `--no-report` drops the `.rep`:
40
+
41
+ | file | contents |
42
+ |---|---|
43
+ | `.prg` | the program, with its load address |
44
+ | `.sym` | every symbol, with the used ones marked |
45
+ | `.vs` | address labels, ready for a debugger or monitor |
46
+ | `.rep` | each source line with the address and bytes it produced |
47
+
48
+ Only address-typed *and* referenced symbols survive into the `.vs` — raw
49
+ `--vicelabels` output lists constants too, and a debugger reading
50
+ `viccolor_WHITE = $1` would relabel the 6510 processor port at `$0001`
51
+ (`curateLabels`, `scripts/acme.mjs`) — hence 4 addresses against 121 total
52
+ symbols above. Load it with `mcp__plugin_c64-re-tools_vice__vice_symbols_load` (format `vice`), this
53
+ project's only route to the emulator (`.claude/CLAUDE.md` § Version Compatibility
54
+ / § Emulator Access).
55
+
56
+ Re-run `build` until it exits 0 — a clean exit means every symbol resolved.
57
+
58
+ Add `--json` to act on diagnostics programmatically:
59
+
60
+ ```bash
61
+ node $A build game.asm --json
62
+ ```
63
+ ```json
64
+ { "ok": false,
65
+ "diags": [ { "file": "game.asm", "line": 26, "severity": "error",
66
+ "zone": "Zone <untitled>",
67
+ "message": "Number does not fit in 8 bits." } ] }
68
+ ```
69
+
70
+ Use the `.rep` listing to map source to memory:
71
+
72
+ ```
73
+ 16 0801 0b080a00 !word .eol, 10 ; link to next line, line number
74
+ 26 080d a900 lda #viccolor_BLACK
75
+ 27 080f 8d21d0 sta vic_cbg ; $d021 background
76
+ 29 0814 8d20d0 sta vic_cborder ; $d020 border
77
+ ```
78
+
79
+ Build variants with `-D`, giving each its own `-o` so the symbol files stay
80
+ separate:
81
+
82
+ ```bash
83
+ node $A build game.asm -DBORDER=2 -o v2.prg # -> v2.prg v2.sym v2.vs
84
+ node $A build game.asm -DBORDER=5 -o v5.prg # -> v5.prg v5.sym v5.vs
85
+ ```
86
+
87
+ Inspect the result with `od`:
88
+
89
+ ```bash
90
+ od -An -tx1 game.prg | head -2
91
+ ```
92
+
93
+ The first two bytes are the little-endian load address (`01 08` = `$0801`); code
94
+ follows.
95
+
96
+ ## Writing source
97
+
98
+ Start from the scaffold — it carries a BASIC stub whose `SYS` target is computed,
99
+ so the entry point stays correct as the program grows:
100
+
101
+ ```bash
102
+ node $A new game.asm
103
+ ```
104
+
105
+ Use the C64 symbol library instead of writing addresses by hand:
106
+
107
+ | `!source <...>` | gives you | example |
108
+ |---|---|---|
109
+ | `<cbm/c64/vic.a>` | `vic_*` registers, `viccolor_*` constants | `vic_cborder` = `$d020` |
110
+ | `<cbm/c64/kernal.a>` | `k_*` KERNAL entry points `$ff81`–`$fff5` | `k_chrout` = `$ffd2` |
111
+ | `<cbm/c64/cia1.a>` / `<cbm/c64/cia2.a>` | `cia1_*` / `cia2_*` | keyboard, joystick, timers |
112
+ | `<cbm/c64/sid.a>` | `sid_*` | sound |
113
+
114
+ KERNAL routines use the **`k_`** prefix — `k_chrout`, `k_getin`, `k_setnam`,
115
+ `k_plot`. Several have aliases (`k_bsout` and `k_basout` are also `$ffd2`).
116
+ Run `node $A sym game.asm` to see what a build resolved:
117
+
118
+ ```
119
+ addr $80d entry
120
+ addr $ffd2 k_chrout
121
+ addr $d021 vic_cbg
122
+ addr $d020 vic_cborder
123
+ ```
124
+
125
+ Let `-o` name the output and leave `!to` out of the source, so the filename you
126
+ pass is the filename you get.
127
+
128
+ The 6510's illegal opcodes are always available: `lax dcp sax slo rla sre rra
129
+ isc anc alr arr sbx las tas sha shx shy jam`. Verified — `lax $fb / dcp $fc /
130
+ sax $fd / slo $02 / anc #$0f / sbx #$10` assembles to
131
+ `a7 fb c7 fc 87 fd 07 02 0b 0f cb 10`. Keep `!cpu 6510` at the top of any source
132
+ you also assemble by hand, so these stay recognised as mnemonics.
133
+
134
+ ## Disassembly
135
+
136
+ ```bash
137
+ node $A disasm game.prg
138
+ ```
139
+ ```
140
+ game.dis.a: 28 lines
141
+ Read it as a linear decode: trust the instruction stream, and
142
+ treat strings, tables and the BASIC stub as data. To reassemble,
143
+ define the out-of-range labels it emits (Ld020, Lffd2, ...) and
144
+ indent its illegal-opcode lines to the operand column.
145
+ ```
146
+
147
+ The default output is `<stem>.dis.a` — one more `.a` file the agent's Read tool
148
+ refuses (see Troubleshooting). Pass a second positional ending `.asm` for an
149
+ agent-readable listing instead (same stdout shape, `game.dis.asm: 28 lines` in
150
+ place of the first line):
151
+
152
+ ```bash
153
+ node $A disasm game.prg game.dis.asm
154
+ ```
155
+
156
+ `game.dis.asm` itself then holds:
157
+
158
+ ```
159
+ *=$0801
160
+ L0801 !by$0b;ANC# <- BASIC stub, read as data
161
+ L0802 php
162
+ L0805 SHX L3032, y
163
+ ...
164
+ L080d lda #$00 <- code, decoded correctly
165
+ L080f sta Ld021
166
+ L0812 lda #$05
167
+ L0814 sta Ld020
168
+ L081e jsr Lffd2
169
+ L0824 rts
170
+ L0825 pha <- PETSCII string, read as data
171
+ ```
172
+
173
+ Read it as a linear decode: the instruction stream is accurate, and strings,
174
+ tables and the BASIC stub appear as instructions — interpret those regions as
175
+ data. To reassemble the listing, define the out-of-range labels it emits
176
+ (`Ld020`, `Lffd2`) and indent its illegal-opcode lines to the operand column.
177
+
178
+ ## Setup
179
+
180
+ Put `acme` and `toacme` on `$PATH`. The wrapper auto-probes `$ACME`,
181
+ `/usr/local/share/acme`, `/usr/share/acme`, `/usr/lib/acme` and `~/.acme` itself,
182
+ so `$ACME` only needs setting by hand when calling `acme` directly. Verified live
183
+ in this container on 2026-08-04: the library resolves to `/usr/local/share/acme`
184
+ (`/usr/share/acme` doesn't exist), ACME release 0.97 "Zem" (31 Jan 2021) at
185
+ `/usr/bin/acme`. **Confidence: HIGH** — read off `acme --version` and the probe
186
+ result, not off a package manifest.
187
+
188
+ Copy `acme.mjs` into any project's `.claude/skills/acme-build/scripts/`, and
189
+ `template.a` into `.claude/skills/acme-build/`, to use this elsewhere.
190
+
191
+ ## Which skill does what
192
+
193
+ This one turns source into bytes. It does not restate what the others carry.
194
+
195
+ | Need | Go to |
196
+ |---|---|
197
+ | Where to start on an unknown program, and which address to read next | `c64-program-recon` |
198
+ | What a specific address or bit means, or annotating a listing | `c64-memory-mapping` — `node … lookup '$D018'` |
199
+ | A verified 64K image, or comparing two captures | `c64-ram-capture` |
200
+ | **Source in, `.prg` out — and a first-pass dead listing back** | here |
201
+
202
+ ## References
203
+
204
+ | File | Covers |
205
+ |---|---|
206
+ | `scripts/acme.mjs` | The driver. Its comments are the contract for every flag above |
207
+ | `template.a` | The scaffold `new` writes: BASIC stub with a computed `SYS`, the three `!source` libraries, no `!to` |
208
+
209
+ Findings that make RE faster go in `.planning/RE-FINDINGS.md` **at the moment you
210
+ find them**, graded with `Evidence:` and `Confidence:`. Promote by re-logging with
211
+ the new evidence, never by editing a grade in place. File-changing work enters
212
+ through a GSD command (`/gsd-quick`).
213
+
214
+ ## Troubleshooting
215
+
216
+ | Symptom | Fix |
217
+ |---|---|
218
+ | `install the ACME cross assembler and put acme on PATH` | Install ACME. |
219
+ | `for <...> includes, set $ACME to …` | `export ACME=<dir holding cbm/c64/vic.a>`. |
220
+ | `Value not defined (kernal_chrout)` | Use the `k_` prefix — `k_chrout`. `node $A sym` lists what resolved. |
221
+ | `Label name not in leftmost column` + `Syntax error` on a mnemonic | Add `!cpu 6510`. |
222
+ | `Output file already chosen` | Remove `!to` from the source and keep `-o`. |
223
+ | `Number does not fit in 8 bits` | Pass a value 0–255, or drop the `#` if you meant an address. |
224
+ | `This tool cannot read binary files. The file appears to be a binary .a file.` | The file is fine — ACME source is plain text; the agent's Read tool refuses the `.a` extension regardless of content, and Edit needs a prior successful Read. Scaffold and write source as `.asm` instead — the driver accepts `.a`/`.asm`/`.s`, all three assemble byte-identically (verified both directions in this container, 2026-08-04). To read an existing `.a` file, use `sed -n '1,60p' file.a`. |