@henols/c64-re-tools 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/THIRD-PARTY-NOTICES.md +26 -0
- package/bin/cli.mjs +18 -7
- package/package.json +6 -4
- package/skills/acme-build/SKILL.md +83 -33
- package/skills/acme-build/scripts/acme.mjs +159 -64
- package/skills/acme-build/template.a +1 -1
- package/skills/c64-disk-access/SKILL.md +156 -0
- package/skills/c64-disk-access/scripts/c1541.mjs +569 -0
- package/skills/c64-memory-mapping/SKILL.md +419 -23
- package/skills/c64-memory-mapping/scripts/driver.mjs +1 -1
- package/skills/c64-petcat/SKILL.md +87 -0
- package/skills/c64-petcat/scripts/petcat.mjs +221 -0
- package/skills/c64-program-recon/SKILL.md +497 -92
- package/skills/c64-program-recon/references/control-flow.md +12 -15
- package/skills/c64-program-recon/references/graphics.md +1 -1
- package/skills/c64-program-recon/references/observation-hazards.md +18 -16
- package/skills/c64-program-recon/references/reconstruction.md +11 -6
- package/skills/c64-program-recon/references/sound-and-input.md +6 -8
- package/skills/c64-program-recon/references/tool-selection.md +37 -18
- package/skills/c64-program-recon/scripts/packer-finding.mjs +709 -0
- package/skills/c64-program-recon/templates/memory-map.template.md +27 -13
- package/skills/c64-provenance-diff/SKILL.md +43 -8
- package/skills/c64-provenance-diff/scripts/diff-images.mjs +9 -6
- package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +20 -8
- package/skills/c64-ram-capture/RELEASES.json.example +17 -0
- package/skills/c64-ram-capture/SKILL.md +147 -46
- package/skills/c64-ram-capture/scripts/compare.mjs +2 -2
- package/skills/c64-ram-capture/scripts/derive-transients.mjs +575 -0
- package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +3 -3
- package/skills/c64-ram-capture/scripts/mcp-module.mjs +174 -0
- package/skills/c64-ram-capture/scripts/project-paths.mjs +1 -1
- package/skills/c64-ram-capture/scripts/releases.mjs +1 -1
- package/skills/c64-ram-capture/scripts/vsf-slice.mjs +147 -0
- package/skills/c64-ram-capture/scripts/watch-loads.mjs +19 -13
- package/skills/c64-ram-capture/templates/capture-record.template.md +44 -4
- package/skills/c64-ram-capture/transients/README.md +136 -0
- package/skills/routine-queue-walker/SKILL.md +365 -0
- package/skills/routine-queue-walker/scripts/completeness-report.mjs +463 -0
- package/skills/vice-wedge-triage/SKILL.md +104 -97
- package/skills/c64-provenance-diff/scripts/diff-images.test.mjs +0 -665
- package/skills/c64-ram-capture/scripts/d64-parse.mjs +0 -243
- package/skills/c64-ram-capture/scripts/d64-parse.test.mjs +0 -243
- package/skills/c64-ram-capture/scripts/dump-artifacts.test.mjs +0 -133
- package/skills/c64-ram-capture/scripts/test-corpus.mjs +0 -75
- package/skills/c64-ram-capture/scripts/watch-loads.test.mjs +0 -339
|
@@ -13,7 +13,7 @@ Build a network of confirmed facts. Once the vectors, the IRQ handler, the main
|
|
|
13
13
|
tables are known, everything else classifies far more easily.
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
D
|
|
16
|
+
D=src/skills/c64-program-recon/scripts/derive.mjs # from the repo root
|
|
17
17
|
|
|
18
18
|
node $D vectors dump.bin # $01 + six vectors, which pair is live
|
|
19
19
|
node $D vic --dd00 3E --d018 18 --d011 1B --d016 C8 # bank, screen, charset, mode
|
|
@@ -23,6 +23,17 @@ node $D sprites --dd00 3E --d018 18 --d015 0F --ptrs 20,21,22,23,FF,FF,FF,FF
|
|
|
23
23
|
The script does only the arithmetic that a lookup table cannot — register bits to concrete
|
|
24
24
|
addresses — over values **you** fetched through `mcp__plugin_c64-re-tools_vice__*`. It contacts nothing.
|
|
25
25
|
|
|
26
|
+
## Before disassembling anything
|
|
27
|
+
|
|
28
|
+
Two adjacent jobs belong to other skills, and pointing at their owners is cheaper than re-deriving
|
|
29
|
+
either one here:
|
|
30
|
+
|
|
31
|
+
- **A file still sitting inside a `.d64` image** — its directory, block allocation map, sector
|
|
32
|
+
chain, or raw bytes — is `c64-disk-access`'s job. Get the file out of the image there first.
|
|
33
|
+
- **A tokenized BASIC stub rather than raw machine code** — detokenizing it and finding where it
|
|
34
|
+
hands over to machine code, including the named decline when no static handover address exists —
|
|
35
|
+
is `c64-petcat`'s job. See below for the one thing it does not also do.
|
|
36
|
+
|
|
26
37
|
## The order
|
|
27
38
|
|
|
28
39
|
Each step is a read whose answer rules something out. Do not skip ahead: step 6 is cheap once the
|
|
@@ -78,6 +89,49 @@ Differential experiments close the loop: patch a routine to `RTS` and see what s
|
|
|
78
89
|
freeze and nothing else does, the routine's purpose is confirmed — far stronger evidence than
|
|
79
90
|
reading the listing.
|
|
80
91
|
|
|
92
|
+
## Step 0.5: is it packed, and by what?
|
|
93
|
+
|
|
94
|
+
Runs **between step 0 and step 1** — after scoping, before you go looking for an entry point.
|
|
95
|
+
Tracing a decruncher is the same wasted work as tracing a loader, and every label you write on a
|
|
96
|
+
packed image is thrown away the moment the real image is recovered.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
node src/skills/c64-program-recon/scripts/packer-finding.mjs game.prg # from the repo root
|
|
100
|
+
node src/skills/c64-program-recon/scripts/packer-finding.mjs game.prg --entropy 7.83
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Pass `--entropy` when you already have the number from `anno_get_binary_info`; otherwise the
|
|
104
|
+
script measures it from the file. It prints one JSON object. Read the `verdict`:
|
|
105
|
+
|
|
106
|
+
| Verdict | What it means | What to do |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `identified` | An external oracle stated the packer name, verbatim. `packer` holds it and `confidence` is `HIGH`. | Record the name as a finding. Then depack: run it in the emulator and capture RAM past the decrunch (`c64-ram-capture`). |
|
|
109
|
+
| `packed-unidentified` | Entropy is at or above the 7.5 packedness threshold and **no oracle named the packer**. `packer` is `null`. | Treat the image as packed. Depack the same way. Do not annotate these bytes and do not go hunting for a name. |
|
|
110
|
+
| `unpacked` | Entropy is below the threshold. Still not an identity claim — it says nothing about which packer, only that these bytes do not look compressed. | Continue to step 1 on this image. |
|
|
111
|
+
| `unknown` | No route produced an answer. `unavailableReason` always says why. | Continue, but record the unknown. Never write it up as "not packed". |
|
|
112
|
+
|
|
113
|
+
**A name is reported only when an external oracle stated one, and this project does not guess.**
|
|
114
|
+
No first-party route on this project's surface reports a packer name at all — that was
|
|
115
|
+
established four independent ways, and the acceptance bar a future first-party identifier would
|
|
116
|
+
have to clear was fixed at the same time. So there is no code path here that can write a packer
|
|
117
|
+
name from entropy, from a decompression address, or from a byte pattern. If you want a name and the finding does not give
|
|
118
|
+
you one, install an external identifier on the **host** and point `UNP64` or `UNP64_PATH` at it in
|
|
119
|
+
the environment the host broker process sees — do not infer it. `packer-finding.mjs` never spawns
|
|
120
|
+
that identifier itself: it reaches it only through the host-tool execution
|
|
121
|
+
seam (`src/mcp/vice/host-tool.mts`'s `oracle.probe`/`oracle.run` allowlist entries), because this
|
|
122
|
+
script runs container-side and there is no container PATH to a host binary on. **The container-side
|
|
123
|
+
environment is not consulted at all**: setting `UNP64`/`UNP64_PATH` in
|
|
124
|
+
this script's own (container-side) environment adds only a diagnostic hint to an absent result —
|
|
125
|
+
it can never select what the host executes. The oracle's location is host-side configuration
|
|
126
|
+
only, and the configured path's file name must be the oracle binary's own name (`unp64`) or the
|
|
127
|
+
seam treats it as absent.
|
|
128
|
+
|
|
129
|
+
**The entropy gate answers packedness, not identity.** High entropy tells you the bytes are
|
|
130
|
+
compressed (or encrypted, or genuinely random); it does not tell you by what. And the way a packed
|
|
131
|
+
image is actually opened up here is the run-and-capture route — run the program under the emulator
|
|
132
|
+
and capture RAM at a checkpoint past the decrunch — not an in-place unpack, which would destroy
|
|
133
|
+
the comments, labels and blocks the project already holds.
|
|
134
|
+
|
|
81
135
|
## Worked example — a real capture
|
|
82
136
|
|
|
83
137
|
```
|
|
@@ -118,126 +172,227 @@ The method reproduces a known-good result from a static image with no emulator r
|
|
|
118
172
|
## Writing findings into the annotation store
|
|
119
173
|
|
|
120
174
|
Recon's findings are not memory-map prose written once and left to rot — they are entries in a
|
|
121
|
-
queryable annotation store, and the Markdown memory map is a *generated view* of that store
|
|
175
|
+
queryable annotation store, and the Markdown memory map is a *generated view* of that store,
|
|
122
176
|
not something you hand-edit yourself.
|
|
123
177
|
|
|
124
|
-
**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
178
|
+
**There is no bootstrap step, and no bootstrap verb.** The store is created by the first write to
|
|
179
|
+
it: name a `.annostore` path on any mutating call — `anno_set_label_name`, `anno_set_comment`,
|
|
180
|
+
`anno_set_data_type`, `anno_add_scope` — and it is created, committed and closed inside that call.
|
|
181
|
+
A read-only call against a path that does not exist yet is REFUSED by name rather than answering
|
|
182
|
+
against an empty store, so "I read nothing" and "there is nothing to read" stay distinguishable.
|
|
183
|
+
|
|
184
|
+
Every `anno_*` tool takes an explicit `store` path — there is no ambient session state
|
|
185
|
+
naming the store, so which store a call touched is always visible in the transcript. Every call
|
|
186
|
+
that derives its answer from the program's **bytes** rather than from the annotations takes an
|
|
187
|
+
`image` path as well — `anno_get_binary_info`, `anno_read_region`, `anno_disassemble`,
|
|
188
|
+
`anno_get_cross_references`, `anno_search` and `anno_get_address_details`. The store holds
|
|
189
|
+
annotations and never bytes, so an omitted image would read as a plausible success against
|
|
190
|
+
whatever was recorded last. `image` is a `.prg` (2-byte little-endian load address plus payload) or
|
|
191
|
+
an exactly-65536-byte flat capture, dispatched **by extension first**, never by length.
|
|
134
192
|
|
|
135
193
|
**Write findings with the named tools, not a Markdown row:**
|
|
136
194
|
|
|
137
195
|
| Tool | Use for |
|
|
138
196
|
|---|---|
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
197
|
+
| `anno_set_label_name` | Naming a routine or table (`init_screen`, `sprite_table`) |
|
|
198
|
+
| `anno_set_data_type` | Classifying a block (`code`, `byte`, `address`, `petscii`, …) |
|
|
199
|
+
| `anno_add_scope` | Marking a handler's extent as a lexical scope |
|
|
200
|
+
| `anno_set_comment` | Recording the evidence — the carrier for the confidence grade below |
|
|
201
|
+
| `anno_batch_execute` | Bulk annotation, 5+ independent calls at once — a real memory map is dozens of labels/comments/block ranges, and one batch is one open/commit/close instead of dozens. The store (and the image, when an inner call needs one) is named ONCE at the top level and every inner call inherits it. A malformed payload, an empty `calls` array, an uncurated inner name at any depth or an illegal label name refuses the **whole** batch by index and executes nothing; past that gate, execution runs to completion and each entry carries its own status, so an error entry inside a successful result means that one call did not work |
|
|
144
202
|
|
|
145
203
|
**Grade with the confidence prefix.** Lead every evidence comment with exactly one of these five
|
|
146
|
-
bracket tokens (quoted verbatim from `
|
|
204
|
+
bracket tokens (quoted verbatim from `anno-confidence.ts`, the parser's own source of truth):
|
|
147
205
|
|
|
148
206
|
`[confirmed-code]` (confirmed code), `[probable-code]` (probable code), `[confirmed-data]`
|
|
149
207
|
(confirmed data), `[probable-data]` (probable data), `[unknown]` (unknown).
|
|
150
208
|
|
|
151
209
|
A typo in the bracket token — wrong case, an underscore, a plural, stray whitespace — **fails
|
|
152
|
-
loudly**; it does not silently degrade into an ungraded comment.
|
|
210
|
+
loudly**; it does not silently degrade into an ungraded comment. Do not
|
|
153
211
|
promote a row by editing its grade in place: re-verify and restate the evidence with a fresh
|
|
154
|
-
`
|
|
155
|
-
|
|
156
|
-
**Query instead of re-deriving.** `
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
212
|
+
`anno_set_comment` call, so the record of when something stopped being a guess survives.
|
|
213
|
+
|
|
214
|
+
**Query instead of re-deriving.** `anno_get_symbols`, `anno_get_comments` and `anno_get_blocks`
|
|
215
|
+
answer straight from the store; `anno_get_cross_references` and `anno_search` derive their answers
|
|
216
|
+
from the image bytes plus the store's typed ranges, so they take `image` too. `anno_search` searches
|
|
217
|
+
three corpora together — label names, comment text, and the instruction text rendered from every
|
|
218
|
+
range typed `code` — **byte-exact and case-sensitive**, with each corpus named in the answer
|
|
219
|
+
alongside how many entries it held, so a genuine zero over a real corpus stays distinguishable from
|
|
220
|
+
a corpus this surface does not have.
|
|
221
|
+
|
|
222
|
+
`max_results` is **REQUIRED, with no default,** on every one of those reads. That is deliberate: an
|
|
223
|
+
implicit default silently truncates a full-program pass, and here the true match count rides beside
|
|
224
|
+
the truncated list, so truncation is a fact you are told rather than one you infer. The query this
|
|
225
|
+
whole workflow exists to make cheap:
|
|
226
|
+
|
|
227
|
+
> "Show me everything still `[unknown]`" → `anno_search` with `query: "[unknown]"` and
|
|
163
228
|
> an explicit `max_results` set above your program's comment count.
|
|
164
229
|
|
|
165
|
-
|
|
166
|
-
|
|
230
|
+
`anno_get_blocks` is also the read route for the store's other structural annotations: pass
|
|
231
|
+
`include: ["scopes", "enums", "enum_usage"]` to get scope spans (which `anno_remove_scope` must
|
|
232
|
+
match exactly), every project enum with its variants, and every address-to-enum association.
|
|
233
|
+
|
|
234
|
+
`anno_get_address_details` composes everything known about ONE address — the labels bound there,
|
|
235
|
+
the comments there, the typed range covering it, and the cross-references reaching it. **The
|
|
236
|
+
composition is disclosed:** the body carries `composed_client_side` and a `composed_from` list
|
|
237
|
+
naming all four sources, so a composition is never mistaken for something the store held whole.
|
|
167
238
|
|
|
168
239
|
### Take names to the running machine, and bring live findings back
|
|
169
240
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
3. **
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
Two traps: `export-lbl` exports **USER** labels only — the auto-generated `a_D011`/`e_FFD2`
|
|
204
|
-
externals never appear in the written file. And both verbs require an EXISTING `.regen2000proj`;
|
|
205
|
-
neither one bootstraps a project from a raw input.
|
|
206
|
-
|
|
207
|
-
`r2000 gen-enums` — turning register writes into named enum variants — is documented in
|
|
208
|
-
`c64-memory-mapping`, alongside the `memmap.json` bit table it consumes.
|
|
241
|
+
**Dated withdrawal, 2026-08-29 — the `.lbl` round trip is WITHDRAWN, and as of 2026-08-31 no phase
|
|
242
|
+
currently owns its return.** The two CLI verbs that carried it, `export-lbl` and `import-lbl`, are
|
|
243
|
+
gone from this surface: both were delivery paths into the retired static analyser. This notice
|
|
244
|
+
previously forecast that a numbered phase would rebuild them alongside the ACME export route; that
|
|
245
|
+
forecast was **wrong and is corrected here rather than deleted**. The phase that rebuilt the ACME
|
|
246
|
+
export route covered that route only — no requirement and no success criterion of it mentioned the
|
|
247
|
+
`.lbl` round trip — so the round trip still has no route and **no phase currently owns its return**.
|
|
248
|
+
Do not reach for these verbs here: they do not exist, and an invocation fails with an unknown-verb
|
|
249
|
+
error and no explanation of why.
|
|
250
|
+
|
|
251
|
+
The **loop itself is not withdrawn**, only its two automated legs, and the discipline it encodes is
|
|
252
|
+
what to keep doing by hand for as long as they stay gone:
|
|
253
|
+
|
|
254
|
+
1. **The store is the merge point, not your own notes.** A name discovered live —
|
|
255
|
+
disassembling the running machine, a checkpoint hit — is written into the store with
|
|
256
|
+
`anno_set_label_name` *first*, before it is carried anywhere else.
|
|
257
|
+
2. **`vice_symbols_load` REPLACES the machine's symbol table rather than merging into it.** Call it
|
|
258
|
+
**exactly once** per generated `.lbl` file. Loading an older file a second time, after the store
|
|
259
|
+
has moved on, silently discards the newer names.
|
|
260
|
+
3. **Regenerate whole, never patch incrementally.** The round trip regenerated the entire `.lbl`
|
|
261
|
+
from the store, and any rebuild of it must do the same; a hand-written incremental patch
|
|
262
|
+
reintroduces exactly the drift the single merge point exists to prevent.
|
|
263
|
+
|
|
264
|
+
Two traps that survive the withdrawal and are part of the specification whoever eventually rebuilds
|
|
265
|
+
this will read: the export carried **USER** labels only — auto-generated `a_D011`/`e_FFD2` externals
|
|
266
|
+
never appeared in the written file — and neither direction ever created a store from a raw input.
|
|
267
|
+
|
|
268
|
+
`gen-enums` — turning register writes into named enum variants — is **withdrawn on the same terms,
|
|
269
|
+
and no phase currently owns its return either**. The same superseded forecast named a numbered phase
|
|
270
|
+
for it; that phase's requirements covered the ACME export oracle only. What `gen-enums` consumed,
|
|
271
|
+
the `memmap.json` bit table, is documented in `c64-memory-mapping` along with the withdrawal and the
|
|
272
|
+
by-hand route that stays open.
|
|
209
273
|
|
|
210
274
|
**Generate the memory map; do not hand-author it.** Fill in the provenance sidecar (schema and a
|
|
211
275
|
filled example live in `templates/memory-map.template.md`), then:
|
|
212
276
|
|
|
213
277
|
```bash
|
|
214
|
-
npx -y @henols/vice-mcp
|
|
215
|
-
node <plugin-root
|
|
278
|
+
npx -y @henols/vice-mcp anno render-memmap game.annostore --provenance sidecar.json
|
|
279
|
+
node <plugin-root>/src/mcp/vice/vice-proxy.ts anno render-memmap game.annostore --provenance sidecar.json
|
|
216
280
|
```
|
|
217
281
|
|
|
218
|
-
Add `--check` to detect drift
|
|
219
|
-
|
|
220
|
-
|
|
282
|
+
Add `--check` to detect drift. It is reported when, and only when, one of these changed: the
|
|
283
|
+
rendered file itself (a hand edit); a store row (a range, a label, a comment, or a comment's
|
|
284
|
+
confidence grade); the provenance sidecar's bytes; the location of the store or the sidecar
|
|
285
|
+
**relative to the workspace root**; or the renderer. **Relocating the checkout is not drift** — the
|
|
286
|
+
same tree at a different absolute path renders the same bytes, because the banner records
|
|
287
|
+
workspace-relative locations. The rendered file carries a generated-file banner; treat it like every
|
|
288
|
+
other generated artifact in this repo and never hand-edit it.
|
|
289
|
+
|
|
290
|
+
**Dated correction, 2026-08-30 — the paragraph above used to name TWO drift causes, and a third
|
|
291
|
+
existed.** Before gap-closure round 2 the banner recorded the store and the sidecar by their
|
|
292
|
+
ABSOLUTE paths, so the checkout's own location was a silent third cause: an identical store,
|
|
293
|
+
sidecar and rendered file reported `drifted` the moment the tree sat at a different absolute path,
|
|
294
|
+
while `render-memmap` printed the same `render_digest` in both. That cause was removed by
|
|
295
|
+
recording workspace-relative locations, so the cause set above is the one the shipped verb has. The
|
|
296
|
+
old two-cause wording is superseded rather than merely reworded, and this note says so because a
|
|
297
|
+
reader meeting it in history needs to know which claim was live when.
|
|
298
|
+
|
|
299
|
+
**One-time drift after upgrading, 2026-08-30.** A memory map rendered *before* that change reports
|
|
300
|
+
`drifted` on its first `--check` afterwards, exactly once, because the banner's recorded locations
|
|
301
|
+
changed from absolute to workspace-relative spellings. Re-run the generator and commit the new
|
|
302
|
+
banner. This repository has no committed rendered `memory-map.md` — only the template — so nothing
|
|
303
|
+
here regresses; the sentence is written for **consuming projects**, which do have one.
|
|
304
|
+
|
|
305
|
+
**This playbook itself has a generated twin, and it is not the one to edit.**
|
|
306
|
+
`installer/skills/c64-program-recon/` is a gitignored COPY of this directory, rebuilt from it by
|
|
307
|
+
`installer/scripts/sync-skills.mjs` on the installer package's `prepack` and by
|
|
308
|
+
`npm --prefix installer run sync-skills`. Edit THIS file; never edit the twin. A hand-edit there is
|
|
309
|
+
overwritten by the next sync and is not independently covered either — the gates that scan the
|
|
310
|
+
shipped tree run the sync before they scan it, so a change made only in the twin is erased before it
|
|
311
|
+
is ever measured. A change made here is SHIPPED only once that sync has run.
|
|
312
|
+
|
|
313
|
+
**Dated correction, 2026-08-30 — `render-memmap` reads the annotation store directly, and the note
|
|
314
|
+
that used to stand here was WRONG when it shipped.** This verb was rebuilt over the annotation
|
|
315
|
+
store: its positional is an EXISTING `.annostore`, opened
|
|
316
|
+
with `mustExist` — an absent store is refused by name rather than created — and nothing on the path
|
|
317
|
+
it reaches consults the retired external analyser. The pre-store project file the earlier note named
|
|
318
|
+
has no producer left in this repository, so there is no route back to the old spelling. That earlier
|
|
319
|
+
note asserted in the PRESENT TENSE that this verb still read a project file; it was already false
|
|
320
|
+
when it shipped, and it is DELETED here rather than amended, so a reader comparing two dated claims
|
|
321
|
+
can tell which one to believe.
|
|
322
|
+
|
|
323
|
+
**Importing a Ghidra export, and the mechanical join that follows it.** When a Ghidra harness run
|
|
324
|
+
(a separate, host-side capability) has produced a transfer file, two calls land its findings in the
|
|
325
|
+
store — in this order, and each is one mechanical call, not an agent turn:
|
|
326
|
+
|
|
327
|
+
1. **`anno_import_ghidra_export`** reads the transfer file, writes one cross-reference row per
|
|
328
|
+
surviving reference, and DELETES the transfer file once every write has durably committed. It reports
|
|
329
|
+
`referencesSeen`, `xrefsWritten`, `xrefsAlreadyPresent` (a duplicate reference is deduplicated, not
|
|
330
|
+
double-counted) and `kindsSeenNotImported` — reference kinds outside this store's four-member
|
|
331
|
+
vocabulary, dropped and counted rather than guessed or refused. A malformed, truncated or
|
|
332
|
+
digest-mismatched export is refused by name, naming the section and the offending line, and writes
|
|
333
|
+
nothing.
|
|
334
|
+
2. **`anno_join_memmap`** then reads every cross-reference target the store already holds, skips
|
|
335
|
+
addresses inside the program's own loaded image (those are code/data addresses, not hardware
|
|
336
|
+
features), and annotates everything else with the narrowest `c64-memory-mapping/memmap.json` entry
|
|
337
|
+
containing it. It reports `addressesConsidered`, `annotated`, `skippedInImage`,
|
|
338
|
+
`skippedNoMapEntry`, `declined` and `commentsChanged`, plus a per-address decision naming the
|
|
339
|
+
outcome and, for every skip, why.
|
|
340
|
+
|
|
341
|
+
Both calls are **mechanical**: there is no agent invocation, no queue walk and no skill invocation
|
|
342
|
+
anywhere inside either one, checked structurally over the two modules' own source rather than
|
|
343
|
+
asserted in prose. Run the import call once per Ghidra export, then the join call once per updated
|
|
344
|
+
image; neither call takes an agent turn to complete.
|
|
221
345
|
|
|
222
346
|
## Static disassembly
|
|
223
347
|
|
|
224
|
-
|
|
225
|
-
|
|
348
|
+
**Dated withdrawal 2026-08-29, dated return 2026-08-31 — whole-program ACME export was WITHDRAWN
|
|
349
|
+
and has come back as `anno export-asm`, behind a real-ACME byte-diff oracle.** The notice is kept
|
|
350
|
+
rather than deleted because the withdrawal explains the shape of what returned. The removed verb
|
|
351
|
+
turned a `.prg` or a flat 64K image into ACME source offline and settled its own correctness with a
|
|
352
|
+
transcript parser; what returned is not a rename of it. It is rebuilt over the **annotation store**,
|
|
353
|
+
and its correctness is settled by **assembling the output with a real ACME and diffing the bytes
|
|
354
|
+
against the input** — never by an exit code and never by a string match on the exporter's own
|
|
355
|
+
output.
|
|
226
356
|
|
|
227
357
|
```bash
|
|
228
|
-
npx -y @henols/vice-mcp
|
|
229
|
-
node <plugin-root
|
|
358
|
+
npx -y @henols/vice-mcp anno export-asm game.prg --store game.annostore --out game-src
|
|
359
|
+
node <plugin-root>/src/mcp/vice/vice-proxy.ts anno export-asm game.prg --store game.annostore
|
|
230
360
|
```
|
|
231
361
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
362
|
+
`<image>` and `--store` are **two separate arguments and neither is derived from the other**: the
|
|
363
|
+
image supplies the bytes, the store supplies the names, typed ranges and comments. `--out` names a
|
|
364
|
+
**directory** the whole export is written into, defaulting to the image's basename stem beside the
|
|
365
|
+
**store** rather than beside the image, and a non-empty destination is refused rather than
|
|
366
|
+
overwritten unless you pass `--force`. The directory holds a root file that sources the rest, one
|
|
367
|
+
file per annotation scope, and an `unscoped.a` for any block that lies inside no scope — open the
|
|
368
|
+
root file first to see how the tree fits together.
|
|
369
|
+
|
|
370
|
+
**It writes source and runs no assembler**, and says so in its own second output line
|
|
371
|
+
(`this file has NOT been assembled`). The real-ACME byte-diff is a **test-only** oracle in this
|
|
372
|
+
repository's test suite, absent from the published package and unreachable at runtime — so a clean
|
|
373
|
+
run is evidence that source was written, not an assembler verdict. `acme-build` carries the full
|
|
374
|
+
statement of that split.
|
|
375
|
+
|
|
376
|
+
Two routes remain for reading a single routine, and they are the ones the rest of this playbook
|
|
377
|
+
already uses:
|
|
378
|
+
|
|
379
|
+
- **`anno_read_region`** and **`anno_disassemble`** render one routine or table at an **explicit**
|
|
380
|
+
inclusive range, decoded fresh from the image bytes on every call and written nowhere. That is
|
|
381
|
+
the static route, bounded on purpose: the combined byte count is capped at **4096 bytes**
|
|
382
|
+
(`ANNO_READ_REGION_MAX_BYTES`), and a wider request is REFUSED by name rather than truncated,
|
|
383
|
+
because a full-64K disassembly dumped into an agent's context is exactly the hazard the cap
|
|
384
|
+
exists to prevent.
|
|
385
|
+
- **`vice_disassemble`** is the live-RAM route this skill's own table above uses: it reads a
|
|
386
|
+
running emulator's RAM at a checkpoint.
|
|
387
|
+
|
|
388
|
+
The two are complementary — reach for the static reads before the emulator is even running, and for
|
|
389
|
+
`vice_disassemble` once you have a live checkpoint to decode from.
|
|
390
|
+
|
|
391
|
+
Extracting a program from a `.d64` image is a separate capability that this repository still does
|
|
392
|
+
not have, and — correcting an earlier note that assigned it to the same numbered phase as the ACME
|
|
393
|
+
export oracle — **no phase currently owns it**. Whenever it is built it must name the file inside
|
|
394
|
+
the image explicitly and refuse rather than guess, because a guess could analyse a cracktro
|
|
395
|
+
or loader stub instead of the game.
|
|
241
396
|
|
|
242
397
|
## Before you touch the emulator
|
|
243
398
|
|
|
@@ -253,16 +408,265 @@ two lines are the part you cannot afford to load lazily.
|
|
|
253
408
|
`ping` still reporting `running`, an identical PC — because the machine genuinely never moved.
|
|
254
409
|
Two cheap reads settle it, and neither needs `vice_execution_run`.
|
|
255
410
|
|
|
411
|
+
## Documenting one routine, end to end
|
|
412
|
+
|
|
413
|
+
The table at the top of this page finds *where* the structure is. This section
|
|
414
|
+
is what you do once you have picked one routine out of it and want it
|
|
415
|
+
documented properly in the annotation store.
|
|
416
|
+
|
|
417
|
+
**Scope, so three skills do not fight over the same job.** This procedure
|
|
418
|
+
handles **one routine, at one explicit address**. Building the backlog of every
|
|
419
|
+
undocumented routine in a project and draining it to closure is
|
|
420
|
+
`routine-queue-walker`'s job — it calls into this procedure once per queue
|
|
421
|
+
entry. Classifying the *regions* around the routine, and naming the data
|
|
422
|
+
symbols it touches, is the absorbed pair in `c64-memory-mapping`.
|
|
423
|
+
|
|
424
|
+
### 1. Context first
|
|
425
|
+
|
|
426
|
+
`anno_get_binary_info` for `system`, `filename`, `description` and
|
|
427
|
+
`may_contain_undocumented_opcodes`.
|
|
428
|
+
|
|
429
|
+
- `system` names the target machine and therefore which memory map, hardware
|
|
430
|
+
registers and ROM entry points are in play.
|
|
431
|
+
- `filename` and `description` identify the software. This is how you recognise
|
|
432
|
+
a stock component instead of re-deriving it — a Hubbard-style music driver,
|
|
433
|
+
an Exomizer decrunch stub — and how genre informs a guess
|
|
434
|
+
(`check_collision` is a plausible routine in a shooter).
|
|
435
|
+
- With `may_contain_undocumented_opcodes: true`, expect `LAX`, `SAX`, `SLO`,
|
|
436
|
+
`DCP`, `ISC`. These are real instructions, not disassembly errors; do not
|
|
437
|
+
stop reading at one.
|
|
438
|
+
|
|
439
|
+
### 2. Bounds, from an explicit address
|
|
440
|
+
|
|
441
|
+
**Always start from an address you were given or derived** — `$XXXX` or its
|
|
442
|
+
decimal equivalent. There is no editor cursor in this project's route, and
|
|
443
|
+
upstream's own text forbids relying on one in any case.
|
|
444
|
+
|
|
445
|
+
Find the start (the entry point or its label) and the end (`RTS`, `RTI`, or a
|
|
446
|
+
`JMP`). Two shapes to expect:
|
|
447
|
+
|
|
448
|
+
- A routine ending in `JMP shared_epilogue` still **ends there** — that is a
|
|
449
|
+
tail call, and the target's body is a different routine.
|
|
450
|
+
- A routine with no return at all may **fall through** into the next one. Use
|
|
451
|
+
the cross-references and the flow to decide where the boundary is, and say in
|
|
452
|
+
the comment that it falls through.
|
|
453
|
+
|
|
454
|
+
### 3. Read the range
|
|
455
|
+
|
|
456
|
+
`anno_read_region` over the routine's explicit range, naming the `store` and
|
|
457
|
+
the `image`, with `view` **omitted** — the disassembly view is that parameter's
|
|
458
|
+
documented default, so the call needs no `view` at all here.
|
|
459
|
+
|
|
460
|
+
The combined byte count is capped at **4096 bytes** per call
|
|
461
|
+
(`ANNO_READ_REGION_MAX_BYTES`) and a request above it is refused by name
|
|
462
|
+
rather than silently truncated. A routine longer than that — rare, but real in
|
|
463
|
+
a decruncher or a level builder — is read as **consecutive ranges**. Read them
|
|
464
|
+
in order; do not raise the cap to swallow the whole program, because the cap is
|
|
465
|
+
what keeps a "read this routine" call from becoming a whole-program export.
|
|
466
|
+
|
|
467
|
+
Then read the flow, not just the instructions:
|
|
468
|
+
|
|
469
|
+
- Does it loop? Where does the loop terminate?
|
|
470
|
+
- Does it call other routines, or ROM entry points?
|
|
471
|
+
- Does it touch hardware registers?
|
|
472
|
+
|
|
473
|
+
Recurring shapes worth recognising on sight:
|
|
474
|
+
|
|
475
|
+
| Pattern | Almost always |
|
|
476
|
+
|---|---|
|
|
477
|
+
| `SEI` … `CLI` bracketing | IRQ setup or teardown |
|
|
478
|
+
| `LDA`/`STA` with `DEX`/`DEY`/`BNE` | Memory copy or fill |
|
|
479
|
+
| Bit shifts plus `ADC`/`SBC` chains | Maths, or a decompressor |
|
|
480
|
+
| Reads an I/O address then branches | Hardware polling |
|
|
481
|
+
| Writes to `$0314`/`$FFFE` | Interrupt vector installation |
|
|
482
|
+
| Writes to `$D400`–`$D418` | Music or SFX driver tick |
|
|
483
|
+
| Reads `$DC00`/`$DC01` | Joystick or keyboard polling |
|
|
484
|
+
|
|
485
|
+
### 4. Who calls it
|
|
486
|
+
|
|
487
|
+
`anno_get_cross_references` on the entry point. The caller is often more
|
|
488
|
+
decisive than the body:
|
|
489
|
+
|
|
490
|
+
- Called from an init block → a setup routine, runs once.
|
|
491
|
+
- Called from the main loop → a per-frame update.
|
|
492
|
+
- Called from the IRQ → must be fast; likely a music tick or a raster update,
|
|
493
|
+
and its zero-page usage is IRQ-relative.
|
|
494
|
+
- **No callers at all** → not necessarily dead. It may be a dispatch target
|
|
495
|
+
reached through a jump table; check the nearby data blocks for an address
|
|
496
|
+
table pointing at it.
|
|
497
|
+
|
|
498
|
+
### 5. What data it touches
|
|
499
|
+
|
|
500
|
+
For every address the routine reads or writes:
|
|
501
|
+
|
|
502
|
+
1. `lookup` it first (see `c64-memory-mapping`). A hardware register or KERNAL
|
|
503
|
+
entry point is answered outright and needs no further work.
|
|
504
|
+
2. Otherwise `anno_get_cross_references` on that address, and read the shape:
|
|
505
|
+
- Written once, in init → a constant or a config value.
|
|
506
|
+
- Written *and* read by several routines → shared state, a global.
|
|
507
|
+
- In the zero page and used as `($addr),Y` → an indirect pointer.
|
|
508
|
+
3. **Enums.** If the accessed addresses or the immediate values form a logical
|
|
509
|
+
set — state constants, joystick direction bits, colour codes — check for an
|
|
510
|
+
existing project, global or system enum that matches, and apply it with
|
|
511
|
+
`anno_apply_enum_usage` at the accessing instruction. If none matches but
|
|
512
|
+
the set is clean, define one with `anno_create_project_enum` (give it a
|
|
513
|
+
real `description`) and then apply it everywhere it fits. This is what turns
|
|
514
|
+
`lda #$1b` into something a reader understands.
|
|
515
|
+
|
|
516
|
+
**The pointer-formatting step this project does not have.** A routine that sets
|
|
517
|
+
up a pointer or a vector does it with a pair of immediate loads — `LDA #<target
|
|
518
|
+
/ STA ptr`, `LDA #>target / STA ptr+1`. Upstream calls `set_immediate_format`
|
|
519
|
+
on each of the two instruction addresses, with `low_byte` / `high_byte` and the
|
|
520
|
+
target, so the pair renders as one symbol reference. **That call is not exposed
|
|
521
|
+
on this project's surface.** The rule it would serve — "every branch, `JSR`/`JMP`
|
|
522
|
+
and data reference goes through a symbol, so code can move" — still supplies
|
|
523
|
+
its criterion, and the per-call disposition sits in the manifest named
|
|
524
|
+
in the attribution header above. Until then, recombine the two bytes yourself
|
|
525
|
+
and put the reconstructed target in a side comment on both instructions, so the
|
|
526
|
+
vector setup is readable even though the store cannot format it.
|
|
527
|
+
|
|
528
|
+
### 6. Synthesise, then document
|
|
529
|
+
|
|
530
|
+
Four things, and they are the four things the comment block carries: **purpose**
|
|
531
|
+
(one sentence), **inputs** (registers and memory used as arguments), **outputs**
|
|
532
|
+
(registers and memory modified), **side effects** (hardware, screen, sound).
|
|
533
|
+
|
|
534
|
+
Rename the label with `anno_set_label_name`, then put a multi-line `"line"`
|
|
535
|
+
comment above the first instruction with `anno_set_comment`, in this exact
|
|
536
|
+
shape — the separator is both the first and the last line:
|
|
537
|
+
|
|
538
|
+
```
|
|
539
|
+
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
540
|
+
<what the routine does>
|
|
541
|
+
|
|
542
|
+
Inputs: <registers or memory used as arguments, or "None">
|
|
543
|
+
Outputs: <registers or memory modified, or "None">
|
|
544
|
+
Side Effects: <hardware changes, screen updates, etc., or "None">
|
|
545
|
+
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Then add `"side"` comments to the instructions that carry the meaning — what a
|
|
549
|
+
register holds here, why this branch is taken, what this address represents.
|
|
550
|
+
This is the part that makes the listing readable for the next person, and it is
|
|
551
|
+
the part most often skipped. Grade evidence comments with the confidence prefix
|
|
552
|
+
documented earlier on this page.
|
|
553
|
+
|
|
554
|
+
### 7. Report
|
|
555
|
+
|
|
556
|
+
- **Purpose** in one sentence.
|
|
557
|
+
- **Inputs / outputs / side effects** as determined above.
|
|
558
|
+
- **Evidence** — the instructions or cross-references that decided it.
|
|
559
|
+
- **Actions taken** — what was renamed, which line comment was added, which
|
|
560
|
+
instructions got side comments, which enums were defined or applied.
|
|
561
|
+
- **Uncertain areas** — every instruction or address whose purpose is still
|
|
562
|
+
unclear, by address. A routine report with no uncertain areas on a real game
|
|
563
|
+
is usually a report that stopped looking.
|
|
564
|
+
|
|
565
|
+
### What goes wrong
|
|
566
|
+
|
|
567
|
+
| Symptom | What it actually is |
|
|
568
|
+
|---|---|
|
|
569
|
+
| No `RTS`/`JMP`/`RTI` at the apparent end | Deliberate fall-through. Check whether the next label is independently called. |
|
|
570
|
+
| `JMP some_routine` as the last instruction | A tail call. This routine ends there; the target is a separate routine. |
|
|
571
|
+
| Several routines converging on one `RTS` | A shared epilogue. It belongs to none of them; note it in each comment. |
|
|
572
|
+
| No callers, but the routine is clearly live | Reached through a jump table. Look for an address table pointing at it. |
|
|
573
|
+
| Disassembly appears to break mid-routine | Undocumented opcodes. Check the binary-info hint and keep reading. |
|
|
574
|
+
| Zero-page usage contradicts the main program's | The routine runs from the IRQ. Its context is IRQ-relative. |
|
|
575
|
+
|
|
576
|
+
## A tokenized BASIC stub: detokenize with c64-petcat, don't hand-decode it
|
|
577
|
+
|
|
578
|
+
**Detokenizing a `SYS` stub and resolving its machine-code handover address is
|
|
579
|
+
`c64-petcat`'s job, not this skill's.** Reach for its `decode` verb before
|
|
580
|
+
reading tokenized bytes by hand: it wraps VICE's own `petcat` to produce the
|
|
581
|
+
readable listing and to report the handover address — or a *named decline*
|
|
582
|
+
(`entrypoint: null` with a reason) when the `SYS` argument is not a static
|
|
583
|
+
value, which is a resolved answer, never a guess to spend a disassembler on.
|
|
584
|
+
Corrected here: an earlier note claimed none of `c64-petcat`'s trigger phrases
|
|
585
|
+
overlapped this skill's frontmatter; that was true only because the skill did
|
|
586
|
+
not exist yet, and it is superseded now that it does.
|
|
587
|
+
|
|
588
|
+
The material below stays as **reference text only**, narrowed to what
|
|
589
|
+
`c64-petcat` does NOT do — write the tokenized bytes' typed ranges and
|
|
590
|
+
comments into this project's annotation store. That is rare in practice: a
|
|
591
|
+
commercial C64 title captured after its loader has run almost universally
|
|
592
|
+
reduces to a one-line `SYS` stub, so most sessions never reach this section at
|
|
593
|
+
all. When one does, use `c64-petcat`'s resolved listing and handover address
|
|
594
|
+
as the source of truth rather than re-deriving them by hand from the byte
|
|
595
|
+
layout below.
|
|
596
|
+
|
|
597
|
+
### Line anatomy
|
|
598
|
+
|
|
599
|
+
A tokenised BASIC program is a linked list in memory. Each line is:
|
|
600
|
+
|
|
601
|
+
1. **Bytes 0–1 — next-line pointer.** The address where the *next* line
|
|
602
|
+
begins, little-endian (`24 04` → `$0424`).
|
|
603
|
+
2. **Bytes 2–3 — line number**, 16-bit little-endian (`0A 00` → `10`).
|
|
604
|
+
3. **Bytes 4–N — the tokens**, running until a `$00` terminator.
|
|
605
|
+
4. **End of program** when a line's next-line pointer is `$00 $00`.
|
|
606
|
+
|
|
607
|
+
Read the range with `anno_read_region`, `view: "hexdump"`, over an explicit
|
|
608
|
+
start and end address — subject to the same 4096-byte ceiling as every other
|
|
609
|
+
range read on this surface. Then walk the pointer chain from the first line
|
|
610
|
+
until the pointer is `$00 $00`.
|
|
611
|
+
|
|
612
|
+
### Keyword tokens (BASIC V2)
|
|
613
|
+
|
|
614
|
+
Bytes with the high bit set, `$80` through `$CB`, are keywords:
|
|
615
|
+
|
|
616
|
+
| Hex | Keyword | Hex | Keyword | Hex | Keyword | Hex | Keyword |
|
|
617
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
618
|
+
| `$80` | `END` | `$93` | `LOAD` | `$A6` | `SPC(` | `$B9` | `POS` |
|
|
619
|
+
| `$81` | `FOR` | `$94` | `SAVE` | `$A7` | `THEN` | `$BA` | `SQR` |
|
|
620
|
+
| `$82` | `NEXT` | `$95` | `VERIFY` | `$A8` | `NOT` | `$BB` | `RND` |
|
|
621
|
+
| `$83` | `DATA` | `$96` | `DEF` | `$A9` | `STEP` | `$BC` | `LOG` |
|
|
622
|
+
| `$84` | `INPUT#` | `$97` | `POKE` | `$AA` | `+` | `$BD` | `EXP` |
|
|
623
|
+
| `$85` | `INPUT` | `$98` | `PRINT#` | `$AB` | `-` | `$BE` | `COS` |
|
|
624
|
+
| `$86` | `DIM` | `$99` | `PRINT` | `$AC` | `*` | `$BF` | `SIN` |
|
|
625
|
+
| `$87` | `READ` | `$9A` | `CONT` | `$AD` | `/` | `$C0` | `TAN` |
|
|
626
|
+
| `$88` | `LET` | `$9B` | `LIST` | `$AE` | `^` | `$C1` | `ATN` |
|
|
627
|
+
| `$89` | `GOTO` | `$9C` | `CLR` | `$AF` | `AND` | `$C2` | `PEEK` |
|
|
628
|
+
| `$8A` | `RUN` | `$9D` | `CMD` | `$B0` | `OR` | `$C3` | `LEN` |
|
|
629
|
+
| `$8B` | `IF` | `$9E` | `SYS` | `$B1` | `>` | `$C4` | `STR$` |
|
|
630
|
+
| `$8C` | `RESTORE` | `$9F` | `OPEN` | `$B2` | `=` | `$C5` | `VAL` |
|
|
631
|
+
| `$8D` | `GOSUB` | `$A0` | `CLOSE` | `$B3` | `<` | `$C6` | `ASC` |
|
|
632
|
+
| `$8E` | `RETURN` | `$A1` | `GET` | `$B4` | `SGN` | `$C7` | `CHR$` |
|
|
633
|
+
| `$8F` | `REM` | `$A2` | `NEW` | `$B5` | `INT` | `$C8` | `LEFT$` |
|
|
634
|
+
| `$90` | `STOP` | `$A3` | `TAB(` | `$B6` | `ABS` | `$C9` | `RIGHT$` |
|
|
635
|
+
| `$91` | `ON` | `$A4` | `TO` | `$B7` | `USR` | `$CA` | `MID$` |
|
|
636
|
+
| `$92` | `WAIT` | `$A5` | `FN` | `$B8` | `FRE` | `$CB` | `GO` |
|
|
637
|
+
|
|
638
|
+
Bytes between `$20` and `$7F` are literal PETSCII characters — strings,
|
|
639
|
+
variable names, numbers.
|
|
640
|
+
|
|
641
|
+
### What a decoding pass would write
|
|
642
|
+
|
|
643
|
+
Per line, batched through `anno_batch_execute`:
|
|
644
|
+
|
|
645
|
+
1. `anno_set_data_type` `address` over bytes 0–1 (the next-line pointer).
|
|
646
|
+
2. `anno_set_data_type` `word` over bytes 2–3 (the line number).
|
|
647
|
+
3. `anno_set_data_type` `byte` from byte 4 through the `$00` terminator,
|
|
648
|
+
inclusive.
|
|
649
|
+
4. `anno_set_comment` `"side"` at byte 0, carrying the reconstructed line —
|
|
650
|
+
`10 REM LODE RUNNER`.
|
|
651
|
+
|
|
652
|
+
Then jump to the next-line pointer and repeat until it reads `$00 $00`, and
|
|
653
|
+
finally mark that `$00 $00` terminator itself as `word`. Nothing needs to be
|
|
654
|
+
"saved": every one of those writes committed and fsynced inside its own call.
|
|
655
|
+
`anno_save_project` performs **no write at all** — it reports the store's current
|
|
656
|
+
revision, which is what to quote when you write the pass up.
|
|
657
|
+
|
|
256
658
|
## Which skill does what
|
|
257
659
|
|
|
258
660
|
This one is the route between the stations. It does not restate what the others carry.
|
|
259
661
|
|
|
260
662
|
| Need | Go to |
|
|
261
663
|
|---|---|
|
|
664
|
+
| A disk image's directory, BAM, sector chains, or a named file's raw bytes | `c64-disk-access` |
|
|
665
|
+
| Detokenizing a BASIC stub, or its machine-code handover address | `c64-petcat` |
|
|
262
666
|
| A verified 64K image, or comparing two captures | `c64-ram-capture` |
|
|
263
667
|
| What a specific address or bit means | `c64-memory-mapping` — `node … lookup '$D018'` |
|
|
264
668
|
| Assembling | `acme-build` |
|
|
265
|
-
| Static disassembly of a `.prg` or flat image |
|
|
669
|
+
| 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. Read one range at a time with `anno_read_region` for a single routine (see above) |
|
|
266
670
|
| Whether a byte is original or cracker-changed | `c64-provenance-diff` |
|
|
267
671
|
| The emulator stopped moving — wedged, self-trapped, or respawned | `vice-wedge-triage` |
|
|
268
672
|
| **Which address to read next, and what the answer rules out** | here |
|
|
@@ -279,9 +683,10 @@ This one is the route between the stations. It does not restate what the others
|
|
|
279
683
|
| `references/reconstruction.md` | Binary inclusion, behavioural-equivalence correctness bar, SMC labels, label vocabulary |
|
|
280
684
|
| `templates/memory-map.template.md` | `render-memmap`'s provenance sidecar schema and the confidence vocabulary — the rendered map itself is generated, not hand-authored |
|
|
281
685
|
|
|
282
|
-
|
|
283
|
-
graded with `Evidence:` and `Confidence:`. Promote by re-logging with the new
|
|
284
|
-
editing
|
|
686
|
+
Record findings that make RE faster in your own project notes **at the moment you find them**,
|
|
687
|
+
graded with `Evidence:` and `Confidence:`. Promote a finding by re-logging it with the new
|
|
688
|
+
evidence, never by editing an old grade in place — the grade is only worth anything if it says
|
|
689
|
+
what was actually known when it was written.
|
|
285
690
|
|
|
286
691
|
## Troubleshooting
|
|
287
692
|
|
|
@@ -293,5 +698,5 @@ editing a grade in place. File-changing work enters through a GSD command (`/gsd
|
|
|
293
698
|
| A sprite decodes as noise | Check `$D015` first; a disabled sprite's registers are stale. Then check MCM — multicolor decoded as hires comes out twice as wide. |
|
|
294
699
|
| Computed mode is "INVALID — screen goes black" | You caught the registers mid-update inside a raster split. Re-read. |
|
|
295
700
|
| The emulator looks dead | Enumerate armed checkpoints before anything else. See hazard 2. |
|
|
296
|
-
| `vice_keyboard_type` does nothing | The game polls `$DC00`/`$DC01` directly.
|
|
701
|
+
| `vice_keyboard_type` does nothing | The game polls `$DC00`/`$DC01` directly. `vice_keyboard_matrix` is **permanently unavailable** — see `references/observation-hazards.md` § 4 for the reason and the available alternative. |
|
|
297
702
|
| Two captures of the same checkpoint differ | Expected. Full-64K identity is impossible in principle; use `c64-ram-capture`'s drift rules. |
|