@henols/c64-re-tools 1.1.0 → 2.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +190 -37
- package/distribution/package.ts +36 -0
- package/distribution/plugin.ts +120 -0
- package/package.json +34 -40
- package/skills/c64-assembler/SKILL.md +73 -0
- package/skills/c64-assembler/scripts/assemble.ts +103 -0
- package/skills/c64-basic/SKILL.md +67 -0
- package/skills/c64-basic/scripts/basic.ts +46 -0
- package/skills/c64-disk/SKILL.md +79 -0
- package/skills/c64-disk/scripts/disk.ts +65 -0
- package/skills/c64-emulator/SKILL.md +124 -0
- package/skills/c64-knowledge/SKILL.md +102 -0
- package/skills/c64-knowledge/scripts/knowledge.ts +257 -0
- package/skills/c64-memory-map/SKILL.md +76 -0
- package/skills/c64-memory-map/scripts/memmap.ts +81 -0
- package/skills/c64-memory-map/scripts/platform.ts +424 -0
- package/skills/c64-provenance/SKILL.md +76 -0
- package/skills/c64-provenance/scripts/compare.ts +92 -0
- package/skills/c64-provenance/scripts/provenance.ts +83 -0
- package/skills/c64-reverse-engineering/SKILL.md +90 -0
- package/skills/c64-static-analysis/SKILL.md +96 -0
- package/skills/c64-static-analysis/scripts/analyze.ts +175 -0
- package/skills/c64-static-analysis/scripts/findings.ts +73 -0
- package/skills/c64-testing/SKILL.md +79 -0
- package/skills/c64-testing/references/scenario-format.md +76 -0
- package/skills/c64-testing/scripts/checklist.ts +51 -0
- package/skills/c64-testing/scripts/scenario.ts +473 -0
- package/skills/c64-testing/scripts/test.ts +55 -0
- package/skills/c64-unpacker/SKILL.md +82 -0
- package/skills/c64-unpacker/scripts/evidence.ts +135 -0
- package/skills/c64-unpacker/scripts/unpack.ts +216 -0
- package/src/c64.ts +281 -0
- package/src/cli/ap-sdk.ts +44 -0
- package/src/cli/cleanup.ts +194 -0
- package/src/cli/main.ts +122 -0
- package/src/cli/status.ts +34 -0
- package/src/host/main.ts +121 -0
- package/src/host/server.ts +409 -0
- package/src/host/tools/c1541.ts +255 -0
- package/src/host/tools/index.ts +31 -0
- package/src/host/tools/petcat.ts +70 -0
- package/src/host/tools/status.ts +30 -0
- package/src/host/tools/version.ts +41 -0
- package/src/host/vice/adapter.ts +761 -0
- package/src/host/vice/binary-monitor.ts +389 -0
- package/src/host/vice/chips.ts +108 -0
- package/src/host/vice/connect.ts +34 -0
- package/src/host/vice/process.ts +402 -0
- package/src/host/vice/screen.ts +183 -0
- package/src/host/vice/session.ts +1316 -0
- package/src/host/vice/text-monitor.ts +223 -0
- package/src/host-client/connect.ts +241 -0
- package/src/host-client/tools.ts +89 -0
- package/src/host-client/transfer.ts +100 -0
- package/src/host-client/vice-session.ts +254 -0
- package/src/knowledge/database.ts +132 -0
- package/src/knowledge/history.ts +128 -0
- package/src/knowledge/import.ts +344 -0
- package/src/knowledge/read.ts +191 -0
- package/src/knowledge/schema.ts +140 -0
- package/src/knowledge/write.ts +348 -0
- package/src/mcp/main.ts +109 -0
- package/src/mcp/server.ts +307 -0
- package/src/mcp/tools/debug.ts +237 -0
- package/src/mcp/tools/execution.ts +116 -0
- package/src/mcp/tools/input.ts +69 -0
- package/src/mcp/tools/machine.ts +97 -0
- package/src/mcp/tools/media.ts +106 -0
- package/src/mcp/tools/memory.ts +203 -0
- package/src/mcp/tools/video.ts +260 -0
- package/src/native/acme.ts +116 -0
- package/src/native/discover.ts +141 -0
- package/src/native/dxa.ts +166 -0
- package/src/native/ghidra/analyze.ts +86 -0
- package/src/native/ghidra/index.ts +157 -0
- package/src/native/ghidra/language/c64rt_6510.cspec +34 -0
- package/src/native/ghidra/language/c64rt_6510.ldefs +15 -0
- package/src/native/ghidra/language/c64rt_6510.pspec +15 -0
- package/src/native/ghidra/language/c64rt_6510.slaspec +596 -0
- package/src/native/ghidra/scripts/C64Export.java +242 -0
- package/src/native/ghidra/scripts/C64Prepare.java +60 -0
- package/src/native/local.ts +34 -0
- package/src/native/processes.ts +350 -0
- package/src/native/run.ts +172 -0
- package/src/native/staging.ts +83 -0
- package/src/native/status.ts +101 -0
- package/src/native/types.ts +228 -0
- package/src/native/watchdog.ts +78 -0
- package/src/project.ts +39 -0
- package/src/protocol/framing.ts +132 -0
- package/src/protocol/messages.ts +252 -0
- package/src/protocol/tools.ts +247 -0
- package/src/protocol/vice.ts +1235 -0
- package/src/script.ts +59 -0
- package/src/time.ts +15 -0
- package/src/trace.ts +162 -0
- package/THIRD-PARTY-NOTICES.md +0 -26
- package/bin/cli.mjs +0 -283
- package/skills/acme-build/SKILL.md +0 -260
- package/skills/acme-build/scripts/acme.mjs +0 -341
- package/skills/acme-build/template.a +0 -53
- package/skills/c64-disk-access/SKILL.md +0 -159
- package/skills/c64-disk-access/scripts/c1541.mjs +0 -569
- package/skills/c64-memory-mapping/SKILL.md +0 -631
- package/skills/c64-memory-mapping/memmap.json +0 -8800
- package/skills/c64-memory-mapping/scripts/driver.mjs +0 -553
- package/skills/c64-petcat/SKILL.md +0 -87
- package/skills/c64-petcat/scripts/petcat.mjs +0 -221
- package/skills/c64-program-recon/SKILL.md +0 -706
- package/skills/c64-program-recon/references/control-flow.md +0 -180
- package/skills/c64-program-recon/references/graphics.md +0 -79
- package/skills/c64-program-recon/references/observation-hazards.md +0 -143
- package/skills/c64-program-recon/references/reconstruction.md +0 -133
- package/skills/c64-program-recon/references/sound-and-input.md +0 -74
- package/skills/c64-program-recon/references/tool-selection.md +0 -78
- package/skills/c64-program-recon/scripts/derive.mjs +0 -364
- package/skills/c64-program-recon/scripts/packer-finding.mjs +0 -709
- package/skills/c64-program-recon/templates/memory-map.template.md +0 -101
- package/skills/c64-provenance-diff/SKILL.md +0 -292
- package/skills/c64-provenance-diff/scripts/diff-images.mjs +0 -984
- package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +0 -395
- package/skills/c64-ram-capture/RELEASES.json.example +0 -17
- package/skills/c64-ram-capture/SKILL.md +0 -474
- package/skills/c64-ram-capture/scripts/compare-cross-binary.mjs +0 -525
- package/skills/c64-ram-capture/scripts/compare.mjs +0 -258
- package/skills/c64-ram-capture/scripts/derive-transients.mjs +0 -575
- package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +0 -317
- package/skills/c64-ram-capture/scripts/mcp-module.mjs +0 -174
- package/skills/c64-ram-capture/scripts/project-paths.mjs +0 -81
- package/skills/c64-ram-capture/scripts/releases.mjs +0 -109
- package/skills/c64-ram-capture/scripts/vsf-slice.mjs +0 -147
- package/skills/c64-ram-capture/scripts/watch-loads.mjs +0 -581
- package/skills/c64-ram-capture/templates/capture-record.template.md +0 -99
- package/skills/c64-ram-capture/transients/README.md +0 -137
- package/skills/routine-queue-walker/SKILL.md +0 -394
- package/skills/routine-queue-walker/scripts/completeness-report.mjs +0 -465
- package/skills/vice-wedge-triage/SKILL.md +0 -269
|
@@ -1,260 +0,0 @@
|
|
|
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, or list which symbols an assembled build actually used from its symbol file.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Assembling C64 source with ACME
|
|
7
|
-
|
|
8
|
-
Source in, `.prg` out. Everything goes through one script:
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
A=src/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
|
-
```
|
|
17
|
-
|
|
18
|
-
The script wraps `acme` and nothing else — **assembling only**. Running
|
|
19
|
-
the result on a C64 belongs to the emulator skills (`acme.mjs:3-4` says so, and the
|
|
20
|
-
absent `run` verb is not an omission). It contacts nothing.
|
|
21
|
-
|
|
22
|
-
Options: `-o FILE` `--out-dir DIR` `-f FORMAT` `--setpc ADDR` `-DSYM=VAL`
|
|
23
|
-
`-I DIR` `--no-report` `--json`.
|
|
24
|
-
|
|
25
|
-
The seam resolves `-I DIR` **workspace-relative** to the project root the host
|
|
26
|
-
broker starts with — the same resolution `source`/`--out-dir` already
|
|
27
|
-
go through — before it ever reaches the assembler. The seam refuses an absolute
|
|
28
|
-
or escaping `-I` directory rather than passing it to ACME. This is a
|
|
29
|
-
documented contract change from earlier releases, when an absolute include
|
|
30
|
-
reached the assembler unchecked.
|
|
31
|
-
|
|
32
|
-
## Build
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
node $A build game.asm
|
|
36
|
-
```
|
|
37
|
-
```
|
|
38
|
-
Saving 53 (0x35) bytes (0x801 - 0x836 exclusive).
|
|
39
|
-
built game.prg (55 bytes) load $0801-$0836 53 bytes of code
|
|
40
|
-
symbols: game.sym (4 used / 121 total)
|
|
41
|
-
debug labels: game.vs (4 addresses)
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
The indented line is ACME's own `-v1` note, passed straight through. Three side
|
|
45
|
-
files land next to the `.prg`, and `--no-report` drops the `.rep`:
|
|
46
|
-
|
|
47
|
-
| file | contents |
|
|
48
|
-
|---|---|
|
|
49
|
-
| `.prg` | the program, with its load address |
|
|
50
|
-
| `.sym` | every symbol, with the used ones marked |
|
|
51
|
-
| `.vs` | address labels, ready for a debugger or monitor |
|
|
52
|
-
| `.rep` | each source line with the address and bytes it produced |
|
|
53
|
-
|
|
54
|
-
Only address-typed *and* referenced symbols survive into the `.vs` — raw
|
|
55
|
-
`--vicelabels` output lists constants too, and a debugger reading
|
|
56
|
-
`viccolor_WHITE = $1` would relabel the 6510 processor port at `$0001`
|
|
57
|
-
(`curateLabels`, `scripts/acme.mjs`) — hence 4 addresses against 121 total
|
|
58
|
-
symbols above. Load it with `mcp__plugin_c64-re-tools_vice__vice_symbols_load` (format `vice`), this
|
|
59
|
-
project's only route to the emulator (`.claude/CLAUDE.md` § Version Compatibility
|
|
60
|
-
/ § Emulator Access).
|
|
61
|
-
|
|
62
|
-
Re-run `build` until it exits 0 — a clean exit means every symbol resolved.
|
|
63
|
-
|
|
64
|
-
Add `--json` to act on diagnostics programmatically:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
node $A build game.asm --json
|
|
68
|
-
```
|
|
69
|
-
```json
|
|
70
|
-
{ "ok": false,
|
|
71
|
-
"diags": [ { "file": "game.asm", "line": 26, "severity": "error",
|
|
72
|
-
"zone": "Zone <untitled>",
|
|
73
|
-
"message": "Number does not fit in 8 bits." } ] }
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Use the `.rep` listing to map source to memory:
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
16 0801 0b080a00 !word .eol, 10 ; link to next line, line number
|
|
80
|
-
26 080d a900 lda #viccolor_BLACK
|
|
81
|
-
27 080f 8d21d0 sta vic_cbg ; $d021 background
|
|
82
|
-
29 0814 8d20d0 sta vic_cborder ; $d020 border
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Build variants with `-D`, giving each its own `-o` so the symbol files stay
|
|
86
|
-
separate:
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
node $A build game.asm -DBORDER=2 -o v2.prg # -> v2.prg v2.sym v2.vs
|
|
90
|
-
node $A build game.asm -DBORDER=5 -o v5.prg # -> v5.prg v5.sym v5.vs
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Inspect the result with `od`:
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
od -An -tx1 game.prg | head -2
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
The first two bytes are the little-endian load address (`01 08` = `$0801`). Code
|
|
100
|
-
follows.
|
|
101
|
-
|
|
102
|
-
## Writing source
|
|
103
|
-
|
|
104
|
-
Start from the scaffold — it carries a BASIC stub that computes its `SYS` target
|
|
105
|
-
rather than hard-coding it, so the entry point stays correct as the program grows:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
node $A new game.asm
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Use the C64 symbol library instead of writing addresses by hand:
|
|
112
|
-
|
|
113
|
-
| `!source <...>` | gives you | example |
|
|
114
|
-
|---|---|---|
|
|
115
|
-
| `<cbm/c64/vic.a>` | `vic_*` registers, `viccolor_*` constants | `vic_cborder` = `$d020` |
|
|
116
|
-
| `<cbm/c64/kernal.a>` | `k_*` KERNAL entry points `$ff81`–`$fff5` | `k_chrout` = `$ffd2` |
|
|
117
|
-
| `<cbm/c64/cia1.a>` / `<cbm/c64/cia2.a>` | `cia1_*` / `cia2_*` | keyboard, joystick, timers |
|
|
118
|
-
| `<cbm/c64/sid.a>` | `sid_*` | sound |
|
|
119
|
-
|
|
120
|
-
KERNAL routines use the **`k_`** prefix — `k_chrout`, `k_getin`, `k_setnam`,
|
|
121
|
-
`k_plot`. Several have aliases (`k_bsout` and `k_basout` are also `$ffd2`).
|
|
122
|
-
Run `node $A sym game.asm` to see what a build resolved:
|
|
123
|
-
|
|
124
|
-
```
|
|
125
|
-
addr $80d entry
|
|
126
|
-
addr $ffd2 k_chrout
|
|
127
|
-
addr $d021 vic_cbg
|
|
128
|
-
addr $d020 vic_cborder
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Let `-o` name the output and leave `!to` out of the source, so the filename you
|
|
132
|
-
pass is the filename you get.
|
|
133
|
-
|
|
134
|
-
The 6510's illegal opcodes are always available: `lax dcp sax slo rla sre rra
|
|
135
|
-
isc anc alr arr sbx las tas sha shx shy jam`. Verified — `lax $fb / dcp $fc /
|
|
136
|
-
sax $fd / slo $02 / anc #$0f / sbx #$10` assembles to
|
|
137
|
-
`a7 fb c7 fc 87 fd 07 02 0b 0f cb 10`. Keep `!cpu 6510` at the top of any source
|
|
138
|
-
you also assemble by hand, so these stay recognised as mnemonics.
|
|
139
|
-
|
|
140
|
-
## Disassembly
|
|
141
|
-
|
|
142
|
-
This skill does not disassemble. The route that does is `anno export-asm`, and
|
|
143
|
-
it lives in the `anno` CLI rather than here.
|
|
144
|
-
|
|
145
|
-
**Dated withdrawal 2026-08-29, dated return 2026-08-31 — this document keeps
|
|
146
|
-
both halves, because the withdrawal records why the route has its current shape.**
|
|
147
|
-
Whole-program static disassembly was WITHDRAWN on 2026-08-29 rather than left standing on an unverified reassembly claim: the removed route
|
|
148
|
-
settled correctness with a transcript parser, and the recorded false pass that
|
|
149
|
-
discipline exists against read `ACME not found in PATH (skipped)` / `All
|
|
150
|
-
roundtrip verifications passed.` / `EXIT=0` — exit zero, an aggregate line
|
|
151
|
-
reading as a full pass, and the one assembler this project cares about never
|
|
152
|
-
having run. On **2026-08-31 the route returned**, rebuilt over the annotation
|
|
153
|
-
store as `anno export-asm`. It is not a rename of the removed route: its
|
|
154
|
-
correctness comes from **assembling the output with a real ACME and diffing
|
|
155
|
-
the bytes against the input** — never from an exit code, and never from a string
|
|
156
|
-
match on the exporter's own output.
|
|
157
|
-
|
|
158
|
-
**The live invocation:**
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
npx -y @henols/vice-mcp anno export-asm game.prg --store game.annostore --out game-src
|
|
162
|
-
node <plugin-root>/src/mcp/vice/vice-proxy.ts anno export-asm game.prg --store game.annostore
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
`<image>` and `--store` are **two separate arguments, and neither one derives from
|
|
166
|
-
the other** — the image supplies the bytes, the store supplies the names, ranges,
|
|
167
|
-
typed regions and comments, and naming one does not name the other. `--out` names
|
|
168
|
-
a **directory**: the verb writes the whole export into it, defaulting to the image's
|
|
169
|
-
basename stem beside the **store**, not beside the image, because the export is a
|
|
170
|
-
generated view of the annotations. The directory holds a root file that sources
|
|
171
|
-
the rest, one file per annotation scope, and an `unscoped.a` for anything inside
|
|
172
|
-
no scope. The verb refuses a non-empty destination rather than overwriting it, unless you
|
|
173
|
-
pass `--force`.
|
|
174
|
-
|
|
175
|
-
**The verb writes source text and runs no assembler.** Its own second output line
|
|
176
|
-
says so, in as many words:
|
|
177
|
-
|
|
178
|
-
```
|
|
179
|
-
export-asm: this file has NOT been assembled -- this command writes source text and runs no assembler.
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
The real-ACME byte-diff oracle is **test-only** — it lives in
|
|
183
|
-
`src/mcp/vice/acme-verify.ts`, `acme-verify.test.ts` exercises it (hard-failed
|
|
184
|
-
in CI with `VICE_REQUIRE_ACME=1`), and it stays deliberately absent from the published
|
|
185
|
-
package, so nothing on the runtime path can reach it. A clean `export-asm` run is
|
|
186
|
-
therefore evidence that the verb wrote source and nothing more. It is not an
|
|
187
|
-
assembler verdict. If you need to know the emitted source reassembles, assemble
|
|
188
|
-
it yourself — that is what this skill's own build route is for.
|
|
189
|
-
|
|
190
|
-
**Reading one range at a time is still the right move for a single routine.**
|
|
191
|
-
`anno_read_region` and `anno_disassemble` render an explicit inclusive range out
|
|
192
|
-
of the image on demand, capped at 4096 bytes per call and REFUSED by name above
|
|
193
|
-
the cap rather than truncated. `c64-program-recon` documents that route and this
|
|
194
|
-
history together. It is not restated there.
|
|
195
|
-
|
|
196
|
-
## Setup
|
|
197
|
-
|
|
198
|
-
`acme` on `$PATH` — **on the host**, never inside a container — is the **only**
|
|
199
|
-
requirement. The scaffold that `new` writes assembles against a bare install
|
|
200
|
-
with no standard hardware-register library — that's deliberate: neither a
|
|
201
|
-
plain `~/.local/bin/acme` build nor the Debian trixie `apt` candidate ships
|
|
202
|
-
one, so a scaffold that depended on it would fail to assemble on a fresh
|
|
203
|
-
install.
|
|
204
|
-
|
|
205
|
-
**Route:** `scripts/acme.mjs` never spawns `acme` itself
|
|
206
|
-
and never probes a container PATH for it — a container has no such PATH to
|
|
207
|
-
probe (the project owner's rule of 2026-08-28). The script reaches the
|
|
208
|
-
assembler only through the host-tool execution seam
|
|
209
|
-
(`src/mcp/vice/host-tool.mts`'s `acme.build` allowlist entry), which runs on
|
|
210
|
-
the HOST and probes the library there — the same handful of conventional
|
|
211
|
-
install locations `acme.mts`'s own `findAcmeLib()` names, none of them
|
|
212
|
-
documented a second time here. `$ACME` still matters for **your own** sources
|
|
213
|
-
that use angle-bracket includes (see "Writing source" above), not for the
|
|
214
|
-
scaffold — but set it in the environment the **host** broker process sees, not
|
|
215
|
-
this script's own environment, since the probe now runs host-side inside the
|
|
216
|
-
seam's executor. If you don't have that library, the scaffold doesn't need it.
|
|
217
|
-
|
|
218
|
-
Re-checked against ACME release 0.97 "Zem" (31 Jan 2021). CI now assembles
|
|
219
|
-
the shipped scaffold on every build with `$ACME` cleared (the "Assemble the
|
|
220
|
-
acme-build scaffold (library-free)" step in `.github/workflows/ci.yml`), so
|
|
221
|
-
this claim is re-checkable rather than a one-machine observation.
|
|
222
|
-
|
|
223
|
-
Copy `acme.mjs` into any project's `.claude/skills/acme-build/scripts/`, and
|
|
224
|
-
`template.a` into `.claude/skills/acme-build/`, to use this elsewhere (the path Claude Code auto-discovers and `installSkills()` deploys to).
|
|
225
|
-
|
|
226
|
-
## Which skill does what
|
|
227
|
-
|
|
228
|
-
This one turns source into bytes. It does not restate what the others carry.
|
|
229
|
-
|
|
230
|
-
| Need | Go to |
|
|
231
|
-
|---|---|
|
|
232
|
-
| Where to start on an unknown program, and which address to read next | `c64-program-recon` |
|
|
233
|
-
| What a specific address or bit means, or annotating a listing | `c64-memory-mapping` — `node … lookup '$D018'` |
|
|
234
|
-
| A verified 64K image, or comparing two captures | `c64-ram-capture` |
|
|
235
|
-
| Static disassembly of a `.prg` or flat image | **`anno export-asm`** — withdrawn 2026-08-29, returned 2026-08-31 behind a real-ACME byte-diff oracle that is test-only, so the verb writes source and assembles nothing (see Disassembly above) |
|
|
236
|
-
| **Source in, `.prg` out** | here |
|
|
237
|
-
|
|
238
|
-
## References
|
|
239
|
-
|
|
240
|
-
| File | Covers |
|
|
241
|
-
|---|---|
|
|
242
|
-
| `scripts/acme.mjs` | The driver. Its comments are the contract for every flag above |
|
|
243
|
-
| `template.a` | The scaffold `new` writes: BASIC stub with a computed `SYS`, five local hardware constants (no library needed), no `!to` |
|
|
244
|
-
|
|
245
|
-
Record findings that make RE faster in your own project notes **at the moment you
|
|
246
|
-
find them**, graded with `Evidence:` and `Confidence:`. Promote a finding by
|
|
247
|
-
re-logging it with the new evidence, never by editing an old grade in place — the
|
|
248
|
-
grade is only worth anything if it says what you actually knew when you wrote it.
|
|
249
|
-
|
|
250
|
-
## Troubleshooting
|
|
251
|
-
|
|
252
|
-
| Symptom | Correct |
|
|
253
|
-
|---|---|
|
|
254
|
-
| `install the ACME cross assembler and put acme on PATH` | Install ACME. |
|
|
255
|
-
| `for <...> includes, set $ACME to …` | `export ACME=<dir holding cbm/c64/vic.a>`. |
|
|
256
|
-
| `Value not defined (kernal_chrout)` | Use the `k_` prefix — `k_chrout`. `node $A sym` lists what resolved. |
|
|
257
|
-
| `Label name not in leftmost column` + `Syntax error` on a mnemonic | Add `!cpu 6510`. |
|
|
258
|
-
| `Output file already chosen` | Remove `!to` from the source and keep `-o`. |
|
|
259
|
-
| `Number does not fit in 8 bits` | Pass a value 0–255, or drop the `#` if you meant an address. |
|
|
260
|
-
| `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. 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`. |
|
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// ACME -> C64 assembler driver. Target is fixed: C64, 6510 CPU, cbm output.
|
|
3
|
-
// Scope is assembling only: source in, .prg + symbol files out. Running the
|
|
4
|
-
// result on a C64 belongs to the emulator skill.
|
|
5
|
-
//
|
|
6
|
-
// The assembler is reached ONLY through the host-tool execution seam -- the
|
|
7
|
-
// project owner's rule of 2026-08-28 is that this script runs container-side,
|
|
8
|
-
// `acme` lives host-side, and there is no container PATH to find it on. This file used to spawn `acme` directly (a synchronous
|
|
9
|
-
// `spawnSync("acme", args, { env })`) and probed FOUR fixed HOST paths
|
|
10
|
-
// (`/usr/local/share/acme`, `/usr/share/acme`, `/usr/lib/acme`, `~/.acme`)
|
|
11
|
-
// for its `<...>`-include library -- both are exactly what the owner's rule
|
|
12
|
-
// says cannot work from inside a container. The spawn and the library probe
|
|
13
|
-
// both moved to `src/mcp/vice/host-tool.mts`'s `acme.build` allowlist entry;
|
|
14
|
-
// this file now only constructs a TYPED request and reads the produced files
|
|
15
|
-
// back off the shared workspace tree.
|
|
16
|
-
//
|
|
17
|
-
// WHAT NOT TO DO: never reintroduce a local child-process call to the
|
|
18
|
-
// assembler as a fallback when the seam is unreachable -- a fallback that
|
|
19
|
-
// works on the developer's own host and silently fails inside a container is
|
|
20
|
-
// the exact failure this seam exists to remove. A seam refusal is reported
|
|
21
|
-
// and the build fails; it is never retried by spawning `acme` here.
|
|
22
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from "node:fs";
|
|
23
|
-
import { dirname, join, basename, relative, isAbsolute, resolve, sep } from "node:path";
|
|
24
|
-
import { fileURLToPath } from "node:url";
|
|
25
|
-
import { spawn } from "node:child_process";
|
|
26
|
-
|
|
27
|
-
import { resolveMcpModule, refusalMessage } from "../../c64-ram-capture/scripts/mcp-module.mjs";
|
|
28
|
-
|
|
29
|
-
const SELF = fileURLToPath(import.meta.url);
|
|
30
|
-
const HERE = dirname(SELF);
|
|
31
|
-
|
|
32
|
-
/** The MCP-side module this script reaches -- never imported statically
|
|
33
|
-
* (cross-package: this file ships in `@henols/c64-re-tools`, the seam client
|
|
34
|
-
* ships in `@henols/vice-mcp`), only located via the ladder and invoked with
|
|
35
|
-
* `process.execPath`, the interpreter already running this script, on an
|
|
36
|
-
* in-tree module -- not an external host binary. */
|
|
37
|
-
const HOST_TOOL_CLIENT_FILE = "host-tool-client.ts";
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Invokes the host-tool execution seam for `tool`/`args`, rooted at
|
|
41
|
-
* `repoRoot` for THIS invocation's workspace-relative path resolution.
|
|
42
|
-
* Never rejects: a resolution failure, a spawn failure, or unparseable
|
|
43
|
-
* output all resolve to `{ ok: false, message }` -- the same shape a tool's
|
|
44
|
-
* own refusal uses, so a caller never needs a try/catch.
|
|
45
|
-
*/
|
|
46
|
-
function invokeSeam(tool, args, repoRoot) {
|
|
47
|
-
return new Promise((resolvePromise) => {
|
|
48
|
-
const resolved = resolveMcpModule(HOST_TOOL_CLIENT_FILE);
|
|
49
|
-
if (!resolved.ok) {
|
|
50
|
-
resolvePromise({ ok: false, message: refusalMessage(HOST_TOOL_CLIENT_FILE, resolved.rungs) });
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const cliArgs = [resolved.path, "run", "--tool", tool, "--args", JSON.stringify(args), "--repo-root", repoRoot];
|
|
55
|
-
let child;
|
|
56
|
-
try {
|
|
57
|
-
child = spawn(process.execPath, cliArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
58
|
-
} catch (e) {
|
|
59
|
-
resolvePromise({ ok: false, message: e instanceof Error ? e.message : String(e) });
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
let stdout = "";
|
|
64
|
-
let stderr = "";
|
|
65
|
-
child.stdout.on("data", (chunk) => { stdout += chunk.toString("utf8"); });
|
|
66
|
-
child.stderr.on("data", (chunk) => { stderr += chunk.toString("utf8"); });
|
|
67
|
-
child.on("error", (err) => resolvePromise({ ok: false, message: err.message }));
|
|
68
|
-
child.on("close", () => {
|
|
69
|
-
const lines = stdout.split("\n").filter((line) => line.trim() !== "");
|
|
70
|
-
const last = lines[lines.length - 1];
|
|
71
|
-
if (last === undefined) {
|
|
72
|
-
resolvePromise({ ok: false, message: `host-tool-client.ts produced no output${stderr ? ` (stderr: ${stderr})` : ""}` });
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
resolvePromise(JSON.parse(last));
|
|
77
|
-
} catch {
|
|
78
|
-
resolvePromise({ ok: false, message: `host-tool-client.ts produced non-JSON output: ${last}` });
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** The smallest common ancestor directory of two absolute paths -- computed,
|
|
85
|
-
* never a fixed guess, so the request's `--repo-root` for THIS invocation is
|
|
86
|
-
* always exactly big enough to contain both the source and the output
|
|
87
|
-
* directory, and no bigger. This is what keeps a build entirely outside this
|
|
88
|
-
* project's own tree (this repo's `skill-acme-build-cli.test.ts`'s own
|
|
89
|
-
* scratch directories under the SYSTEM temp dir, and CI's own
|
|
90
|
-
* `RUNNER_TEMP`-rooted scaffold check) working after the migration: the
|
|
91
|
-
* seam's `resolveWorkspacePath()` refuses any path outside its given root, so
|
|
92
|
-
* the root for one invocation is chosen to be wherever that invocation's own
|
|
93
|
-
* files actually live, never a client-supplied absolute path sent as-is. */
|
|
94
|
-
function commonAncestorDir(a, b) {
|
|
95
|
-
const partsA = resolve(a).split(sep);
|
|
96
|
-
const partsB = resolve(b).split(sep);
|
|
97
|
-
const common = [];
|
|
98
|
-
for (let i = 0; i < Math.min(partsA.length, partsB.length); i++) {
|
|
99
|
-
if (partsA[i] === partsB[i]) common.push(partsA[i]);
|
|
100
|
-
else break;
|
|
101
|
-
}
|
|
102
|
-
const joined = common.join(sep);
|
|
103
|
-
return joined === "" ? sep : joined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** `path.relative()`, except the "same directory" case yields `"."` rather
|
|
107
|
-
* than `""` -- the seam's `resolveWorkspacePath()` refuses an empty string,
|
|
108
|
-
* but accepts `"."` as a no-op relative reference to its own root. */
|
|
109
|
-
function toRel(root, abs) {
|
|
110
|
-
const r = relative(root, abs);
|
|
111
|
-
return r === "" ? "." : r;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// How to refer to this script in hints, from wherever we were run.
|
|
115
|
-
function selfPath() {
|
|
116
|
-
const r = relative(process.cwd(), SELF);
|
|
117
|
-
return !r || r.startsWith("..") || isAbsolute(r) ? SELF : r;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const die = (m) => { console.error(`error: ${m}`); process.exit(1); };
|
|
121
|
-
|
|
122
|
-
// ------------------------------------------------------------------- build
|
|
123
|
-
|
|
124
|
-
// ACME's --msvc format: file(line) : Error (Zone <z>): message.
|
|
125
|
-
const MSVC = /^(.*?)\((\d+)\)\s*:\s*(Error|Warning|Serious error)\s*(?:\(([^)]*)\))?\s*:\s*(.*)$/;
|
|
126
|
-
|
|
127
|
-
function parseDiagnostics(text) {
|
|
128
|
-
const out = [];
|
|
129
|
-
for (const line of text.split("\n")) {
|
|
130
|
-
const m = line.match(MSVC);
|
|
131
|
-
if (m) {
|
|
132
|
-
out.push({
|
|
133
|
-
file: m[1], line: Number(m[2]),
|
|
134
|
-
severity: m[3].toLowerCase().replace(" ", "_"),
|
|
135
|
-
zone: m[4] || null, message: m[5].trim(),
|
|
136
|
-
});
|
|
137
|
-
} else if (line.trim()) {
|
|
138
|
-
out.push({ file: null, line: null, severity: "note", zone: null, message: line.trim() });
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return out;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// The symbol list marks address-typed symbols with a leading "!addr" and
|
|
145
|
-
// never-referenced ones with a trailing "; unused". Both matter downstream.
|
|
146
|
-
function parseSymbols(path) {
|
|
147
|
-
if (!existsSync(path)) return [];
|
|
148
|
-
return readFileSync(path, "utf8").split("\n").flatMap((raw) => {
|
|
149
|
-
const m = raw.match(/^(!addr\s+)?(\S+)\s*=\s*(\S+?)\s*(?:;\s*(.*))?$/);
|
|
150
|
-
if (!m) return [];
|
|
151
|
-
return [{
|
|
152
|
-
name: m[2], value: m[3],
|
|
153
|
-
isAddress: Boolean(m[1]),
|
|
154
|
-
used: !/^unused/.test(m[4] || ""),
|
|
155
|
-
}];
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ACME's label file lists every global symbol, constants included, in address
|
|
160
|
-
// form. A debugger reads `viccolor_WHITE = $1` as a name for address $0001 and
|
|
161
|
-
// relabels the 6510 processor port with it. Keep referenced addresses only, so
|
|
162
|
-
// the emitted file is safe to load anywhere.
|
|
163
|
-
function curateLabels(vsPath, symbols) {
|
|
164
|
-
if (!existsSync(vsPath)) return { kept: 0, dropped: 0 };
|
|
165
|
-
const addr = new Set(symbols.filter((s) => s.isAddress && s.used).map((s) => s.name));
|
|
166
|
-
const kept = [];
|
|
167
|
-
let dropped = 0;
|
|
168
|
-
for (const l of readFileSync(vsPath, "utf8").split("\n")) {
|
|
169
|
-
const m = l.match(/^al\s+C:[0-9a-f]+\s+\.(\S+)/i);
|
|
170
|
-
if (!m) continue;
|
|
171
|
-
if (addr.has(m[1])) kept.push(l); else dropped++;
|
|
172
|
-
}
|
|
173
|
-
writeFileSync(vsPath, kept.join("\n") + (kept.length ? "\n" : ""));
|
|
174
|
-
return { kept: kept.length, dropped };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async function build(src, opts) {
|
|
178
|
-
if (!existsSync(src)) die(`no such source file: ${src}`);
|
|
179
|
-
|
|
180
|
-
const srcAbs = resolve(src);
|
|
181
|
-
// Side files follow the .prg, not the source: two -DVARIANT builds of one
|
|
182
|
-
// source must not overwrite each other's symbol tables.
|
|
183
|
-
const desiredPrg = opts.out
|
|
184
|
-
? resolve(opts.out)
|
|
185
|
-
: join(resolve(opts.outDir || dirname(src)), basename(src).replace(/\.(a|asm|s)$/i, "") + ".prg");
|
|
186
|
-
const desiredOutDirAbs = dirname(desiredPrg);
|
|
187
|
-
if (!existsSync(desiredOutDirAbs)) mkdirSync(desiredOutDirAbs, { recursive: true });
|
|
188
|
-
const desiredStem = desiredPrg.replace(/\.prg$/i, "");
|
|
189
|
-
|
|
190
|
-
// Workspace-relative request construction (A-03): the root for THIS
|
|
191
|
-
// invocation is the smallest ancestor containing both the source and the
|
|
192
|
-
// output directory -- see commonAncestorDir()'s own header.
|
|
193
|
-
const repoRoot = commonAncestorDir(dirname(srcAbs), desiredOutDirAbs);
|
|
194
|
-
const autoOutDirAbs = dirname(srcAbs); // the executor's own default when outDir is omitted
|
|
195
|
-
|
|
196
|
-
const args = { source: toRel(repoRoot, srcAbs) };
|
|
197
|
-
if (desiredOutDirAbs !== autoOutDirAbs) args.outDir = toRel(repoRoot, desiredOutDirAbs);
|
|
198
|
-
if (opts.format) args.format = opts.format;
|
|
199
|
-
if (opts.setpc) args.setpc = opts.setpc;
|
|
200
|
-
if (opts.defines && opts.defines.length) args.defines = opts.defines;
|
|
201
|
-
if (opts.includes && opts.includes.length) args.includes = opts.includes;
|
|
202
|
-
if (opts.noReport) args.noReport = true;
|
|
203
|
-
|
|
204
|
-
const response = await invokeSeam("acme.build", args, repoRoot);
|
|
205
|
-
|
|
206
|
-
if (!response.ok) {
|
|
207
|
-
// A seam-level refusal (unresolvable seam, unreachable broker, a bad
|
|
208
|
-
// request) -- never a local fallback that spawns the assembler itself.
|
|
209
|
-
die(response.message);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// The executor always names outputs after the SOURCE's own basename (never
|
|
213
|
-
// a caller-chosen stem) -- see host-tool.mts's buildHostToolArgv(). When
|
|
214
|
-
// `-o`/`--out-dir` asked for a DIFFERENT stem (a rename, not just a
|
|
215
|
-
// different directory), the produced files are moved here to the exact
|
|
216
|
-
// requested names -- a workspace file operation, not a second copy of
|
|
217
|
-
// argv construction.
|
|
218
|
-
const autoStem = join(desiredOutDirAbs, basename(srcAbs).replace(/\.(a|asm|s)$/i, ""));
|
|
219
|
-
if (autoStem !== desiredStem) {
|
|
220
|
-
for (const ext of [".prg", ".sym", ".vs", ".rep"]) {
|
|
221
|
-
const from = `${autoStem}${ext}`;
|
|
222
|
-
const to = `${desiredStem}${ext}`;
|
|
223
|
-
if (existsSync(from) && from !== to) {
|
|
224
|
-
mkdirSync(dirname(to), { recursive: true });
|
|
225
|
-
renameSync(from, to);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const stem = desiredStem;
|
|
231
|
-
const prg = desiredPrg;
|
|
232
|
-
|
|
233
|
-
const diags = parseDiagnostics((response.stderrTail || "").trim());
|
|
234
|
-
const errors = diags.filter((d) => d.severity.endsWith("error"));
|
|
235
|
-
const ok = response.exitStatus === 0 && existsSync(prg);
|
|
236
|
-
|
|
237
|
-
let range = null, size = null, symbols = [], labels = null;
|
|
238
|
-
if (ok) {
|
|
239
|
-
symbols = parseSymbols(`${stem}.sym`);
|
|
240
|
-
labels = curateLabels(`${stem}.vs`, symbols);
|
|
241
|
-
const buf = readFileSync(prg);
|
|
242
|
-
size = buf.length;
|
|
243
|
-
if ((opts.format || "cbm") === "cbm" && buf.length >= 2) {
|
|
244
|
-
const load = buf[0] | (buf[1] << 8);
|
|
245
|
-
range = { load, end: load + buf.length - 2, bytes: buf.length - 2 };
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return { ok, prg, stem, diags, errors, symbols, labels, range, size };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const hex = (n, w = 4) => n.toString(16).padStart(w, "0");
|
|
252
|
-
|
|
253
|
-
function reportBuild(res, { json }) {
|
|
254
|
-
if (json) { console.log(JSON.stringify(res, null, 2)); return; }
|
|
255
|
-
for (const d of res.diags) {
|
|
256
|
-
if (d.file) console.log(`${d.file}:${d.line}: ${d.severity}: ${d.message}`);
|
|
257
|
-
else console.log(` ${d.message}`);
|
|
258
|
-
}
|
|
259
|
-
if (!res.ok) { console.error(`build FAILED (${res.errors.length} error(s))`); return; }
|
|
260
|
-
const r = res.range;
|
|
261
|
-
console.log(
|
|
262
|
-
`built ${res.prg} (${res.size} bytes)` +
|
|
263
|
-
(r ? ` load $${hex(r.load)}-$${hex(r.end)} ${r.bytes} bytes of code` : "")
|
|
264
|
-
);
|
|
265
|
-
const used = res.symbols.filter((s) => s.used).length;
|
|
266
|
-
console.log(`symbols: ${res.stem}.sym (${used} used / ${res.symbols.length} total)`);
|
|
267
|
-
if (res.labels) {
|
|
268
|
-
console.log(`debug labels: ${res.stem}.vs (${res.labels.kept} addresses)`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// -------------------------------------------------------------------- verbs
|
|
273
|
-
|
|
274
|
-
async function cmdBuild(argv) {
|
|
275
|
-
const o = parseOpts(argv);
|
|
276
|
-
const res = await build(o.src, o);
|
|
277
|
-
reportBuild(res, o);
|
|
278
|
-
process.exit(res.ok ? 0 : 1);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
async function cmdSym(argv) {
|
|
282
|
-
const o = parseOpts(argv);
|
|
283
|
-
const res = await build(o.src, { ...o, noReport: true });
|
|
284
|
-
if (!res.ok) { reportBuild(res, o); process.exit(1); }
|
|
285
|
-
const used = res.symbols.filter((s) => s.used).sort((a, b) => a.name.localeCompare(b.name));
|
|
286
|
-
if (o.json) { console.log(JSON.stringify(used, null, 2)); return; }
|
|
287
|
-
for (const s of used) console.log(`${s.isAddress ? "addr " : "const"} ${s.value.padStart(6)} ${s.name}`);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// A skeleton that is correct on the first try: BASIC stub with a computed SYS
|
|
291
|
-
// target, the C64 symbol libraries, and no !to (the CLI supplies -o).
|
|
292
|
-
function cmdNew(argv) {
|
|
293
|
-
const path = argv[0];
|
|
294
|
-
if (!path) die("usage: new <file.a>");
|
|
295
|
-
if (existsSync(path)) die(`${path} already exists`);
|
|
296
|
-
// template.a lives at the skill root, one level up from scripts/, by
|
|
297
|
-
// decision: only .mjs modules move into scripts/.
|
|
298
|
-
writeFileSync(path, readFileSync(join(HERE, "..", "template.a"), "utf8"));
|
|
299
|
-
console.log(`wrote ${path}`);
|
|
300
|
-
console.log(`next: node ${selfPath()} build ${path}`);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// ------------------------------------------------------------------ options
|
|
304
|
-
|
|
305
|
-
function parseOpts(argv) {
|
|
306
|
-
const o = { defines: [], includes: [], json: false };
|
|
307
|
-
const rest = [];
|
|
308
|
-
for (let i = 0; i < argv.length; i++) {
|
|
309
|
-
const a = argv[i];
|
|
310
|
-
if (a === "--json") o.json = true;
|
|
311
|
-
else if (a === "--no-report") o.noReport = true;
|
|
312
|
-
else if (a === "-o" || a === "--out") o.out = argv[++i];
|
|
313
|
-
else if (a === "--out-dir") o.outDir = argv[++i];
|
|
314
|
-
else if (a === "-f" || a === "--format") o.format = argv[++i];
|
|
315
|
-
else if (a === "--setpc") o.setpc = argv[++i];
|
|
316
|
-
else if (a === "-D") o.defines.push(argv[++i]);
|
|
317
|
-
else if (a.startsWith("-D")) o.defines.push(a.slice(2));
|
|
318
|
-
else if (a === "-I") o.includes.push(argv[++i]);
|
|
319
|
-
else rest.push(a);
|
|
320
|
-
}
|
|
321
|
-
o.src = rest[0];
|
|
322
|
-
if (!o.src) die("no source file given");
|
|
323
|
-
return o;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// --------------------------------------------------------------------- main
|
|
327
|
-
|
|
328
|
-
const [cmd, ...rest] = process.argv.slice(2);
|
|
329
|
-
const VERBS = { new: cmdNew, build: cmdBuild, sym: cmdSym };
|
|
330
|
-
if (!cmd || !VERBS[cmd]) {
|
|
331
|
-
console.log(`usage: node ${selfPath()} <command> [options]
|
|
332
|
-
|
|
333
|
-
new <file.a> scaffold a C64 program (BASIC stub, no libraries needed)
|
|
334
|
-
build <file.a> assemble -> .prg .sym .vs .rep
|
|
335
|
-
sym <file.a> list the symbols the program uses
|
|
336
|
-
|
|
337
|
-
options: -o FILE --out-dir DIR -f FORMAT --setpc ADDR -DSYM=VAL -I DIR
|
|
338
|
-
--no-report --json`);
|
|
339
|
-
process.exit(cmd ? 1 : 0);
|
|
340
|
-
}
|
|
341
|
-
await VERBS[cmd](rest);
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
; C64 / 6510 program skeleton.
|
|
2
|
-
; Build: node .claude/skills/acme-build/scripts/acme.mjs build THIS.a
|
|
3
|
-
; No !to here on purpose - the driver passes -o, and having both makes ACME
|
|
4
|
-
; warn "Output file already chosen" and silently ignore the !to.
|
|
5
|
-
;
|
|
6
|
-
; No angle-bracket library includes here on purpose. Neither documented ACME
|
|
7
|
-
; provisioning route ships the standard hardware-register library - a bare
|
|
8
|
-
; `~/.local/bin/acme` install and the Debian trixie `apt` candidate were both
|
|
9
|
-
; verified to lack it - so a scaffold that depends on
|
|
10
|
-
; it fails to assemble on a fresh install. The five constants below are
|
|
11
|
-
; exactly what this scaffold's body uses, defined locally instead. If you
|
|
12
|
-
; want the full library for your own sources' angle-bracket includes, set
|
|
13
|
-
; $ACME to a directory containing that library (with vic.a inside it);
|
|
14
|
-
; acme.mjs's findAcmeLib() already probes for it and passes it through to
|
|
15
|
-
; the child.
|
|
16
|
-
|
|
17
|
-
!cpu 6510 ; C64's CPU: legal 6502 + the illegal opcodes
|
|
18
|
-
|
|
19
|
-
!address vic_cborder = $d020 ; VIC-II border color register
|
|
20
|
-
!address vic_cbg = $d021 ; VIC-II background color register
|
|
21
|
-
viccolor_BLACK = 0
|
|
22
|
-
viccolor_GREEN = 5
|
|
23
|
-
!address k_chrout = $ffd2 ; KERNAL: print one PETSCII char
|
|
24
|
-
|
|
25
|
-
* = $0801 ; start of BASIC RAM
|
|
26
|
-
|
|
27
|
-
; --- BASIC stub: "10 SYS <entry>", so LOAD"...",8,1 + RUN reaches the code.
|
|
28
|
-
; The SYS digits are computed from `entry`, so inserting code above never
|
|
29
|
-
; breaks the stub - which is the classic way to lose an afternoon.
|
|
30
|
-
!word .eol, 10 ; link to next line, line number
|
|
31
|
-
!byte $9e ; SYS token
|
|
32
|
-
!byte '0' + entry % 10000 / 1000
|
|
33
|
-
!byte '0' + entry % 1000 / 100
|
|
34
|
-
!byte '0' + entry % 100 / 10
|
|
35
|
-
!byte '0' + entry % 10
|
|
36
|
-
!byte 0 ; end of BASIC line
|
|
37
|
-
.eol !word 0 ; end of BASIC program
|
|
38
|
-
|
|
39
|
-
entry
|
|
40
|
-
lda #viccolor_BLACK
|
|
41
|
-
sta vic_cbg ; $d021 background
|
|
42
|
-
lda #viccolor_GREEN
|
|
43
|
-
sta vic_cborder ; $d020 border
|
|
44
|
-
|
|
45
|
-
ldx #0
|
|
46
|
-
- lda .msg,x
|
|
47
|
-
beq +
|
|
48
|
-
jsr k_chrout ; $ffd2 print one PETSCII char
|
|
49
|
-
inx
|
|
50
|
-
bne -
|
|
51
|
-
+ rts
|
|
52
|
-
|
|
53
|
-
.msg !pet "hello from acme", 13, 0
|