@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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -37
  3. package/distribution/package.ts +36 -0
  4. package/distribution/plugin.ts +120 -0
  5. package/package.json +34 -40
  6. package/skills/c64-assembler/SKILL.md +73 -0
  7. package/skills/c64-assembler/scripts/assemble.ts +103 -0
  8. package/skills/c64-basic/SKILL.md +67 -0
  9. package/skills/c64-basic/scripts/basic.ts +46 -0
  10. package/skills/c64-disk/SKILL.md +79 -0
  11. package/skills/c64-disk/scripts/disk.ts +65 -0
  12. package/skills/c64-emulator/SKILL.md +124 -0
  13. package/skills/c64-knowledge/SKILL.md +102 -0
  14. package/skills/c64-knowledge/scripts/knowledge.ts +257 -0
  15. package/skills/c64-memory-map/SKILL.md +76 -0
  16. package/skills/c64-memory-map/scripts/memmap.ts +81 -0
  17. package/skills/c64-memory-map/scripts/platform.ts +424 -0
  18. package/skills/c64-provenance/SKILL.md +76 -0
  19. package/skills/c64-provenance/scripts/compare.ts +92 -0
  20. package/skills/c64-provenance/scripts/provenance.ts +83 -0
  21. package/skills/c64-reverse-engineering/SKILL.md +90 -0
  22. package/skills/c64-static-analysis/SKILL.md +96 -0
  23. package/skills/c64-static-analysis/scripts/analyze.ts +175 -0
  24. package/skills/c64-static-analysis/scripts/findings.ts +73 -0
  25. package/skills/c64-testing/SKILL.md +79 -0
  26. package/skills/c64-testing/references/scenario-format.md +76 -0
  27. package/skills/c64-testing/scripts/checklist.ts +51 -0
  28. package/skills/c64-testing/scripts/scenario.ts +473 -0
  29. package/skills/c64-testing/scripts/test.ts +55 -0
  30. package/skills/c64-unpacker/SKILL.md +82 -0
  31. package/skills/c64-unpacker/scripts/evidence.ts +135 -0
  32. package/skills/c64-unpacker/scripts/unpack.ts +216 -0
  33. package/src/c64.ts +281 -0
  34. package/src/cli/ap-sdk.ts +44 -0
  35. package/src/cli/cleanup.ts +194 -0
  36. package/src/cli/main.ts +122 -0
  37. package/src/cli/status.ts +34 -0
  38. package/src/host/main.ts +121 -0
  39. package/src/host/server.ts +409 -0
  40. package/src/host/tools/c1541.ts +255 -0
  41. package/src/host/tools/index.ts +31 -0
  42. package/src/host/tools/petcat.ts +70 -0
  43. package/src/host/tools/status.ts +30 -0
  44. package/src/host/tools/version.ts +41 -0
  45. package/src/host/vice/adapter.ts +761 -0
  46. package/src/host/vice/binary-monitor.ts +389 -0
  47. package/src/host/vice/chips.ts +108 -0
  48. package/src/host/vice/connect.ts +34 -0
  49. package/src/host/vice/process.ts +402 -0
  50. package/src/host/vice/screen.ts +183 -0
  51. package/src/host/vice/session.ts +1316 -0
  52. package/src/host/vice/text-monitor.ts +223 -0
  53. package/src/host-client/connect.ts +241 -0
  54. package/src/host-client/tools.ts +89 -0
  55. package/src/host-client/transfer.ts +100 -0
  56. package/src/host-client/vice-session.ts +254 -0
  57. package/src/knowledge/database.ts +132 -0
  58. package/src/knowledge/history.ts +128 -0
  59. package/src/knowledge/import.ts +344 -0
  60. package/src/knowledge/read.ts +191 -0
  61. package/src/knowledge/schema.ts +140 -0
  62. package/src/knowledge/write.ts +348 -0
  63. package/src/mcp/main.ts +109 -0
  64. package/src/mcp/server.ts +307 -0
  65. package/src/mcp/tools/debug.ts +237 -0
  66. package/src/mcp/tools/execution.ts +116 -0
  67. package/src/mcp/tools/input.ts +69 -0
  68. package/src/mcp/tools/machine.ts +97 -0
  69. package/src/mcp/tools/media.ts +106 -0
  70. package/src/mcp/tools/memory.ts +203 -0
  71. package/src/mcp/tools/video.ts +260 -0
  72. package/src/native/acme.ts +116 -0
  73. package/src/native/discover.ts +141 -0
  74. package/src/native/dxa.ts +166 -0
  75. package/src/native/ghidra/analyze.ts +86 -0
  76. package/src/native/ghidra/index.ts +157 -0
  77. package/src/native/ghidra/language/c64rt_6510.cspec +34 -0
  78. package/src/native/ghidra/language/c64rt_6510.ldefs +15 -0
  79. package/src/native/ghidra/language/c64rt_6510.pspec +15 -0
  80. package/src/native/ghidra/language/c64rt_6510.slaspec +596 -0
  81. package/src/native/ghidra/scripts/C64Export.java +242 -0
  82. package/src/native/ghidra/scripts/C64Prepare.java +60 -0
  83. package/src/native/local.ts +34 -0
  84. package/src/native/processes.ts +350 -0
  85. package/src/native/run.ts +172 -0
  86. package/src/native/staging.ts +83 -0
  87. package/src/native/status.ts +101 -0
  88. package/src/native/types.ts +228 -0
  89. package/src/native/watchdog.ts +78 -0
  90. package/src/project.ts +39 -0
  91. package/src/protocol/framing.ts +132 -0
  92. package/src/protocol/messages.ts +252 -0
  93. package/src/protocol/tools.ts +247 -0
  94. package/src/protocol/vice.ts +1235 -0
  95. package/src/script.ts +59 -0
  96. package/src/time.ts +15 -0
  97. package/src/trace.ts +162 -0
  98. package/THIRD-PARTY-NOTICES.md +0 -26
  99. package/bin/cli.mjs +0 -283
  100. package/skills/acme-build/SKILL.md +0 -260
  101. package/skills/acme-build/scripts/acme.mjs +0 -341
  102. package/skills/acme-build/template.a +0 -53
  103. package/skills/c64-disk-access/SKILL.md +0 -159
  104. package/skills/c64-disk-access/scripts/c1541.mjs +0 -569
  105. package/skills/c64-memory-mapping/SKILL.md +0 -631
  106. package/skills/c64-memory-mapping/memmap.json +0 -8800
  107. package/skills/c64-memory-mapping/scripts/driver.mjs +0 -553
  108. package/skills/c64-petcat/SKILL.md +0 -87
  109. package/skills/c64-petcat/scripts/petcat.mjs +0 -221
  110. package/skills/c64-program-recon/SKILL.md +0 -706
  111. package/skills/c64-program-recon/references/control-flow.md +0 -180
  112. package/skills/c64-program-recon/references/graphics.md +0 -79
  113. package/skills/c64-program-recon/references/observation-hazards.md +0 -143
  114. package/skills/c64-program-recon/references/reconstruction.md +0 -133
  115. package/skills/c64-program-recon/references/sound-and-input.md +0 -74
  116. package/skills/c64-program-recon/references/tool-selection.md +0 -78
  117. package/skills/c64-program-recon/scripts/derive.mjs +0 -364
  118. package/skills/c64-program-recon/scripts/packer-finding.mjs +0 -709
  119. package/skills/c64-program-recon/templates/memory-map.template.md +0 -101
  120. package/skills/c64-provenance-diff/SKILL.md +0 -292
  121. package/skills/c64-provenance-diff/scripts/diff-images.mjs +0 -984
  122. package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +0 -395
  123. package/skills/c64-ram-capture/RELEASES.json.example +0 -17
  124. package/skills/c64-ram-capture/SKILL.md +0 -474
  125. package/skills/c64-ram-capture/scripts/compare-cross-binary.mjs +0 -525
  126. package/skills/c64-ram-capture/scripts/compare.mjs +0 -258
  127. package/skills/c64-ram-capture/scripts/derive-transients.mjs +0 -575
  128. package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +0 -317
  129. package/skills/c64-ram-capture/scripts/mcp-module.mjs +0 -174
  130. package/skills/c64-ram-capture/scripts/project-paths.mjs +0 -81
  131. package/skills/c64-ram-capture/scripts/releases.mjs +0 -109
  132. package/skills/c64-ram-capture/scripts/vsf-slice.mjs +0 -147
  133. package/skills/c64-ram-capture/scripts/watch-loads.mjs +0 -581
  134. package/skills/c64-ram-capture/templates/capture-record.template.md +0 -99
  135. package/skills/c64-ram-capture/transients/README.md +0 -137
  136. package/skills/routine-queue-walker/SKILL.md +0 -394
  137. package/skills/routine-queue-walker/scripts/completeness-report.mjs +0 -465
  138. package/skills/vice-wedge-triage/SKILL.md +0 -269
@@ -1,474 +0,0 @@
1
- ---
2
- name: c64-ram-capture
3
- description: Capture a running C64's full 64K RAM as a verified flat image, and prove two captures are equivalent. Use when asked to dump RAM, depack a program by running it, capture a memory image at a checkpoint, or compare two captures for reproducibility.
4
- ---
5
-
6
- # Capturing and comparing C64 RAM
7
-
8
- **Reach the emulator only through the `mcp__plugin_c64-re-tools_vice__*` tools.** They are the one
9
- permitted route. Never open a connection by any other means.
10
-
11
- **Never hand-assemble a capture.** Sixteen `vice_memory_read` calls have to land
12
- contiguously and total exactly 65536 bytes. A dropped or short read is the normal
13
- failure, and it is invisible in a hex dump. Two committed modules do that byte work
14
- and name the offending address when it is wrong.
15
-
16
- ```bash
17
- S=src/skills/c64-ram-capture/scripts # from the repo root
18
- A=$S/dump-artifacts.mjs
19
- C=$S/compare.mjs L=$S/releases.mjs
20
- CX=$S/compare-cross-binary.mjs # different-binary comparison (see below)
21
- T=$S/derive-transients.mjs V=$S/vsf-slice.mjs
22
- TD=src/skills/c64-ram-capture/transients # the derived allow-lists live here
23
-
24
- node $A assemble --chunks chunks.json # size + digest, writes nothing
25
- node $A write-set --release <id> --label <label> \
26
- --chunks chunks.json --raw raw.json # the four committed artifacts
27
- node $L list # the valid --release ids
28
-
29
- node $C digest dump.bin # sha256 + size, for the capture record
30
- node $C compare a.bin b.bin # classify every difference, exit 1 on FAIL
31
- node $C floor a.bin b.bin c.bin # drift floor across a capture set
32
-
33
- node $CX cross original.bin rebuild.bin # DIFFERENT binaries -- see below
34
-
35
- node $T derive --release <id> --out $TD/<id>.json a.bin b.bin c.bin
36
- node $T check --allow-list $TD/<id>.json a.bin b.bin
37
-
38
- node $V slice run1.vsf --out run1.bin # flat 64K image from a .vsf snapshot
39
- node $V digest run1.vsf # sha256 + size, writing no file
40
- ```
41
-
42
- Every module above reads only committed files and the JSON **you** wrote from
43
- your own `mcp__plugin_c64-re-tools_vice__*` calls. They contact nothing.
44
-
45
- **Prerequisite: a resolvable project root.** `scripts/project-paths.mjs` uses
46
- `C64RE_PROJECT_ROOT` when you set it, and otherwise walks up from the toolkit's
47
- own location for the nearest ancestor directory containing a `.git` entry. A
48
- scratch project has neither by default, so `git init` it first or set the
49
- variable — the thrown error names both when this fails.
50
-
51
- ## The order
52
-
53
- | # | Phase | Settles |
54
- |---|---|---|
55
- | 1 | Read the disk directory | Whether the release's entries are real or faked, before booting anything |
56
- | 2 | Boot, check the PC moved | That the loader is actually executing |
57
- | 3 | Checkpoint, hit, read 64K + chip state | The capture itself — all reads in one paused window |
58
- | 4 | `write-set` | Assertions pass, four artifacts written, digest returned |
59
- | 5 | Disarm, enumerate, resume once | That you left no checkpoint armed and the machine running |
60
-
61
- ## Read the disk first
62
-
63
- Disk-image structure — the directory, the block allocation map, a named file's
64
- sector chain or raw bytes, and directory-fakery detection — is
65
- `c64-disk-access`'s job now. Read the disk with it before booting anything.
66
-
67
- ## Boot a disk
68
-
69
- 1. `mcp__plugin_c64-re-tools_vice__vice_disk_attach` with the disk image.
70
- 2. `mcp__plugin_c64-re-tools_vice__vice_autostart` with the same image.
71
- 3. `mcp__plugin_c64-re-tools_vice__vice_execution_run`.
72
- 4. `mcp__plugin_c64-re-tools_vice__vice_registers_get` and check the program counter has moved.
73
-
74
- Every time the broker starts a stock instance, it sets the drive type, so you do
75
- not need drive setup before attaching.
76
-
77
- If the program counter has not moved, type `LOAD"*",8,1` with
78
- `mcp__plugin_c64-re-tools_vice__vice_keyboard_type`, run it, then type `RUN` and run it.
79
-
80
- ## Capture at a trigger address
81
-
82
- 1. `mcp__plugin_c64-re-tools_vice__vice_checkpoint_add` at the trigger address, with execution
83
- breaking and stopping enabled.
84
- 2. `mcp__plugin_c64-re-tools_vice__vice_execution_run`.
85
- 3. Poll `mcp__plugin_c64-re-tools_vice__vice_ping` until the checkpoint reports a hit.
86
- 4. Read `$0000`–`$FFFF` with repeated `mcp__plugin_c64-re-tools_vice__vice_memory_read` calls of
87
- 4096 bytes each. Write them to `chunks.json` as an array of
88
- `{ "address": "$0000", "hex": "..." }` records, one per call, hex only.
89
- 5. Record the chip state in the **same paused window**, into `raw.json`. The keys
90
- never change, because `chip-state` derives from exactly these fields.
91
- `registers`, `sprites` and `cpu` pass through verbatim from
92
- `mcp__plugin_c64-re-tools_vice__vice_vicii_get_state` / `mcp__plugin_c64-re-tools_vice__vice_sprite_get` /
93
- `mcp__plugin_c64-re-tools_vice__vice_registers_get`. `port01_raw` is `$0001`. `dd00_raw` is
94
- `$DD00`. `d018_raw` is `$D018`. `sprite_pointers` is the eight bytes at
95
- `screen_base+$3F8`.
96
- 6. Write all four artifacts in one call:
97
-
98
- ```bash
99
- node $A write-set --release <id> --label <label> \
100
- --chunks chunks.json --raw raw.json
101
- ```
102
-
103
- It asserts exactly 65536 bytes with no gap and no overlap *before* writing
104
- anything, then emits `<release>-<label>.bin`, `.state.json`, `.map.json` and
105
- `.capture.json` under `recovery/<release>/dumps/`, and returns their paths
106
- with the SHA-256. It also derives `vic_bank`, `screen_base`, `charset_base`
107
- and `sprite_data_addresses` for free — do not recompute them by hand.
108
- 7. `mcp__plugin_c64-re-tools_vice__vice_checkpoint_delete` the checkpoint.
109
- 8. `mcp__plugin_c64-re-tools_vice__vice_checkpoint_list` and check it reports zero checkpoints.
110
- Accept only this enumeration as proof. Record the count.
111
- 9. `mcp__plugin_c64-re-tools_vice__vice_execution_run` to leave the machine running.
112
-
113
- Read state before you resume, and resume exactly once at the end.
114
-
115
- Hold keys down across a gate by releasing them at the trigger checkpoint in
116
- step 3, never earlier.
117
-
118
- **Fill the record's reproducibility key in the same step.**
119
- `templates/capture-record.template.md`'s Identity table carries three rows that
120
- are one key, not three facts: `binary sha256`, `argv digest` and `seed`. The
121
- seed alone is **not** the key — measured, the same seed with a reordered argv
122
- yielded a 76-byte-different image, so two captures whose argv digests differ are
123
- different keys and must not be compared as a pair. A record with any of those
124
- three blank is not a reproducible capture and the void protocol applies. The
125
- table also carries a `capture route` row (`memory-read` or `snapshot`), and
126
- **on the snapshot route the `$D000-$DFFF` volatility rule below does not
127
- apply** — a difference there is a real difference.
128
-
129
- `assemble` runs the same assertions and writes nothing, so it is the cheap check
130
- on a set of chunks before committing them.
131
-
132
- ## Worked example — a real capture
133
-
134
- Chunks derived from a committed image, fed back through `assemble`:
135
-
136
- ```
137
- $ node $A assemble --chunks chunks.json
138
- 65536 bytes, sha256 e1b8428c55bc7606b7e77846e8928bff23e9cf0c8241da479aadc1bc092faa26
139
- ```
140
-
141
- That digest is byte-identical to the `sha256` field committed in
142
- that capture's own committed `.capture.json` sidecar, so the assembly path
143
- reproduces a known-good artifact rather than merely producing 65536 bytes.
144
- **Confidence: HIGH** (reproduced against the committed sidecar).
145
-
146
- Then break it deliberately, to see what the guards say:
147
-
148
- ```
149
- $ node $A assemble --chunks gap.json # one chunk removed
150
- Error: assembleImage: gap before address $3000 -- next chunk starts at $4000
151
-
152
- $ node $A assemble --chunks short.json # last chunk truncated by 2 bytes
153
- Error: assembleImage: assembled 65534 bytes ending at $FFFE, expected exactly 65536
154
- ```
155
-
156
- Read those as addresses to re-read, not as sizes to pad.
157
-
158
- `manifest` on a fresh capture reports `classification_state: "ranges-only"` with
159
- every range `unclassified`. That is correct and transient — it becomes `"bucketed"`
160
- only after the provenance diff partitions loader from cracktro from game — see
161
- `c64-provenance-diff`. A fresh capture already claiming `"bucketed"` is the anomaly.
162
-
163
- ## Find an entry point
164
-
165
- 1. Press past any "hit any key" gate. **`vice_keyboard_matrix` is permanently unavailable** — the
166
- binary monitor's `KEYBOARD_FEED` only injects PETSCII text into the KERNAL buffer. It cannot
167
- drive the raw matrix. See `docs/stock-hard-losses.md`. Use `vice_keyboard_type` /
168
- `vice_keyboard_petscii` when the gate reads the KERNAL buffer, or `vice_joystick_set` when it
169
- polls the matrix directly. Buffer injection stays invisible to a program that polls
170
- `$DC00`/`$DC01` itself.
171
- 2. Step forward in batches with `mcp__plugin_c64-re-tools_vice__vice_execution_step`, reading
172
- `mcp__plugin_c64-re-tools_vice__vice_registers_get` after each batch.
173
- 3. Stop when the program counter and the stack pointer both settle into a
174
- repeating range across three consecutive batches. That range is the
175
- dispatch loop. Its lowest address is the entry point.
176
- 4. Check the address with `mcp__plugin_c64-re-tools_vice__vice_disassemble` before recording it.
177
-
178
- Set a batch ceiling before you start. Report failure to stabilise as a finding
179
- with the batches spent. Never extend the ceiling silently.
180
-
181
- ## Prove the machine did not change under you
182
-
183
- **Corrected 2026-08-04: there is no exposed tool that reads the epoch, and you do
184
- not have to poll for one.** The proxy compares the restart epoch before *and*
185
- after every forwarded call, and refuses the call — or discards its result, if the
186
- change happened mid-call — with a loud error naming both epoch values. So the
187
- proxy guards the capture's identity continuously, not at two sampled points.
188
-
189
- What that leaves you:
190
-
191
- - **A clean capture is one during which no epoch-drift error appeared.** Record
192
- that, not a pair of hand-read numbers.
193
- - **When you need the numbers,** they come from the drift error's own text, or
194
- from `mcp__plugin_c64-re-tools_vice__vice_diagnose`'s `restarted` report. Both name the before and
195
- after value.
196
- - **A drift error voids the run** even if the very next call succeeds. It will —
197
- the proxy re-baselines so the session stays usable — and a successful retry
198
- after a respawn is talking to a freshly-booted machine.
199
-
200
- `vice-wedge-triage` carries the decision tree for the other three ways a machine
201
- stops answering.
202
-
203
- **Void a run** whose machine identity you could not prove unchanged:
204
-
205
- 1. Rename each artifact to `<name>.VOID-<UTC timestamp>`.
206
- 2. Write a sibling note recording the reason, the time, and — if a drift error is
207
- what voided it — the two epoch values quoted from that error. Do not go looking
208
- for them. Nothing reads the epoch on demand. Keep the voided artifacts on disk.
209
-
210
- ## Compare two captures
211
-
212
- Do not classify differences by hand — `scripts/compare.mjs` applies the rules
213
- identically every time, and exits 1 on a FAIL so a script can gate on it:
214
-
215
- ```bash
216
- node $C compare capture-a.bin capture-b.bin
217
- ```
218
-
219
- ```
220
- A capture-a.bin sha256 741213dcd1beb548b8896737f9f07e867c718dabeb56238862b9f3020e4902d2
221
- B capture-b.bin sha256 ee3813322127b7bedf97abf3dd6ffcebb80c937f8b75dfe471c886fb36975573
222
-
223
- volatile (excluded from the verdict): 100
224
- $020A $9E %10011110 -> $8E %10001110 1 bit
225
- … 99 more (--limit 0 for all)
226
-
227
- drift — exactly one bit, reported as candidates: 61
228
- $CC03 $00 %00000000 -> $20 %00100000 1 bit
229
- … 60 more (--limit 0 for all)
230
-
231
- DIVERGENCE — two or more bits, fails the comparison: 0
232
-
233
- total differing addresses: 161 of 65536
234
-
235
- VERDICT: PASS
236
- Drift candidates present — pass, but record them with the capture.
237
- ```
238
-
239
- The three classes:
240
-
241
- | Class | Rule | Effect on the verdict |
242
- |---|---|---|
243
- | volatile | `$0000-$0001`, `$0100-$01FF`, `$0200-$03FF`, **`$D000-$DFFF`** | counted and listed, never fails |
244
- | drift | exactly one bit differs | listed as a candidate, passes |
245
- | divergence | two or more bits differ | listed, **fails** |
246
-
247
- **`$D000-$DFFF` is volatile because it is I/O, not RAM.** The VIC's registers
248
- repeat every `$40` across `$D000-$D3FF` and the SID's across `$D400-$D7FF`, so
249
- reading that range samples live hardware and two captures can never agree there.
250
- Omitting it is what made the earlier hand-applied rule fail five of the six
251
- committed gameentry pairings on `$D344`, `$D625` and `$D628` — differences that
252
- the repeating registers guarantee. Region first, bit-count second.
253
-
254
- `$E000-$FFFF` (RAM under KERNAL ROM when HIRAM = 0) is deliberately **not**
255
- excluded. `$FAD8` and `$FC51` do differ across captures, but only two addresses
256
- out of 8192 — too few for power-on garbage, and unexplained. They still fail, and
257
- what writes them is an open question. Observed 2026-08-04. Graded MEDIUM,
258
- structural, not reproduced against a second release.
259
-
260
- **Establish a drift floor** with `floor` across every capture of one checkpoint.
261
- It reports each address that differed in any pairing, with the distinct values
262
- seen:
263
-
264
- ```bash
265
- node $C floor run1.bin run2.bin run3.bin
266
- ```
267
-
268
- Capture the power-on image as the very first action against a fresh machine, then
269
- idle-capture twice more and run `floor` over the set. State the result as a
270
- floor, not a complete set — more captures can only widen it.
271
-
272
- ## Compare two different binaries
273
-
274
- `compare.mjs` is for two captures of the **same** binary. When the two
275
- captures are of **different** binaries — original versus rebuilt, original
276
- versus modified — use its sibling instead, `scripts/compare-cross-binary.mjs`:
277
-
278
- ```bash
279
- node $CX cross original.bin rebuild.bin \
280
- --state original.state.json rebuild.state.json \
281
- --allowlist allow.json --checkpoint hazard_raster_entry
282
- ```
283
-
284
- Three rules depart from `compare.mjs`, each because the same-binary
285
- assumptions above are wrong across two different binaries:
286
-
287
- - **No drift bucket.** A one-bit difference is a real difference here, not
288
- sampling noise — `drift` exists to absorb two runs of the *same* binary,
289
- and applied across binaries it would absorb a real one-bit regression
290
- (e.g. `lda #$02` becoming `lda #$03`) whole.
291
- - **Narrowed I/O mask.** `compare.mjs` masks the whole of `$D000`-`$DFFF`.
292
- `compare-cross-binary.mjs` masks only the specific registers and ranges
293
- that genuinely cannot be stable — `$D011`, `$D012`, `$D019`, `$D01E`-`$D01F`,
294
- `$D400`-`$D7FF`, `$D800`-`$DBFF`, `$DC00`-`$DCFF`, `$DD00`-`$DDFF`,
295
- `$DE00`-`$DFFF` — so `$D015`, `$D018` and `$D020` stay visible to the
296
- verdict.
297
- - **Route awareness.** A capture declares its `route` (`snapshot` or
298
- `memory-read`) in its state sidecar. `compare-cross-binary.mjs` refuses to
299
- compare a snapshot-route capture against a memory-read-route capture — see
300
- "Slice the image out of a snapshot instead of transcribing it" above for why
301
- those two routes disagree about what `$D000`-`$DFFF` even is.
302
-
303
- You resolve a real difference with the `--allowlist` document, never by widening
304
- the mask: each entry names a `start`/`endInclusive` range, a `domain`
305
- (`image` or `register`), and a non-empty `why`. `compare-cross-binary.mjs`
306
- refuses by name an entry overlapping a masked span, or one lacking a `why`.
307
- `--no-allowlist` is the allowlist's own red control — the same pair must fail
308
- without it, or the allowlist is doing no work.
309
-
310
- `compare-cross-binary.mjs` prints byte-identity as `BYTE_IDENTICAL: yes`/`no`,
311
- but it is a recorded extra, never the verdict — it always runs the full
312
- classification, even when the two images are byte-for-byte equal, because a
313
- chip-state-only regression (e.g. a differing `$D020`) can exist under
314
- byte-identical images. The `VERDICT:` line is the only line to gate on.
315
-
316
- ## Derive a per-release transient allow-list
317
-
318
- `scripts/derive-transients.mjs` is the named, repeatable derivation. **The
319
- method is what carries forward between releases. No address set ever does.** Two
320
- verbs:
321
-
322
- ```bash
323
- node $T derive --release <id> --out $TD/<id>.json run1.bin run2.bin run3.bin
324
- node $T check --allow-list $TD/<id>.json runA.bin runB.bin
325
- ```
326
-
327
- `derive` takes **N ≥ 3** runs of the same release under the same protocol at the
328
- same stop and writes the **union of addresses differing across every pairwise
329
- comparison** — one entry per address, carrying which pairings it differed in,
330
- the distinct bytes seen, and an empty attribution line for you to fill. `derive`
331
- refuses fewer than three images, naming the count and the minimum. It refuses an
332
- image that is not exactly 65536 bytes, naming the path and the length. It prints
333
- `TRANSIENT_COUNT: <n>` at column 0, so a measurement gets transcribed rather
334
- than paraphrased.
335
-
336
- **Over the cap of 64 addresses the derivation is VOID:** non-zero exit, **no
337
- artifact written**, and the message says what the overflow means — the stop is
338
- not frame-exact. That is a fact to record, not a threshold to raise. `--cap`
339
- only ever *narrows*. `derive` refuses a value above 64 by name, and it never
340
- truncates the union to fit, because a truncated list makes every later
341
- comparison pass on bytes nobody vetted. Overflow is a **measured** outcome: 0 differing addresses
342
- at a frame-exact `READY` stop, 66 at a frame-anchored autostarted stop at
343
- jitter 4000 ms, 300 at a wall-clock autostarted stop on a real release, 1242 at
344
- a wall-clock `READY` stop with the determinism block applied.
345
-
346
- Re-deriving over an existing artifact is **refused without `--force`**, with the
347
- no-inheritance rule in the message: an inherited list cannot be distinguished
348
- afterwards from an honestly derived one.
349
-
350
- `check` re-checks one pair against an already-committed derivation without
351
- re-deriving it, printing `CHECK_VERDICT: equivalent | not-equivalent` and exiting
352
- 1 when not equivalent. Note what it does **not** carry: no address range is a
353
- volatile span, and there is no bit-count tolerance at any address — a one-bit
354
- difference outside the list fails. Those two rules belong to `compare.mjs` and
355
- are deliberately not inherited. `src/skills/c64-ram-capture/transients/README.md`
356
- holds the artifact shape, the committed method and the cap's reasoning.
357
-
358
- ## Slice the image out of a snapshot instead of transcribing it
359
-
360
- `scripts/vsf-slice.mjs` produces the flat 64K image by slicing a VICE `.vsf`
361
- snapshot's memory module body. Two verbs:
362
-
363
- ```bash
364
- node $V slice run1.vsf --out run1.bin # writes exactly 65536 bytes
365
- node $V digest run1.vsf # sha256 + size, writing no file
366
- ```
367
-
368
- Add `--json` to either for the same summary as one JSON object — it carries the
369
- snapshot's memory-module minor, the observed body length, and the three CPU port
370
- read-back values.
371
-
372
- **The one thing that matters operationally: this route has no transcription
373
- step, so the `$D000-$DFFF` volatility rule above does *not* apply to an image
374
- produced this way.** That rule exists because `vice_memory_read` samples live
375
- I/O. The snapshot array is RAM *under* I/O, not the register read view, so those
376
- 4096 addresses are ordinary RAM in a sliced image and a difference there is a
377
- real difference. Do not carry the exclusion across from the memory-read route.
378
-
379
- A malformed snapshot is **refused**, by name, naming the offending value and the
380
- valid range — it is never truncated into a plausible short image. The `.vsf`
381
- byte layout lives in exactly one place, `vsf-slice.ts` on the MCP side. This
382
- script resolves it via `VICE_MCP_DIR`, the in-repo path, or the
383
- `@henols/vice-mcp` package, and refuses naming every path it tried when no rung
384
- resolves rather than falling back to a second copy of the layout.
385
-
386
- ## Feeding the memory map's provenance sidecar
387
-
388
- `c64-program-recon`'s generated memory map (`vice-mcp anno render-memmap`) takes a small provenance
389
- sidecar as input, and this skill supplies one of its fields: `scripts/compare.mjs digest`'s `sha256`
390
- and `size` become the sidecar's `captureSha256`, proving which image the rendered map describes. The
391
- sidecar's other run-scoped keys (`port01`, `dd00`, `vicBank`, `screenRam`, `charsetOrBitmap`, `mode`,
392
- `liveVectorPair`, `vectorHandler`, `rasterPositions`) come from `c64-program-recon`'s own
393
- `derive.mjs` — **this skill does not emit the sidecar itself.** The sidecar is hand-authored from
394
- those two skills' outputs. The renderer checks it and throws, naming every missing or
395
- malformed key at once, rather than rendering a document that silently carries a placeholder.
396
-
397
- ## Which skill does what
398
-
399
- This one owns the image and its identity. It does not restate what the others carry.
400
-
401
- | Need | Go to |
402
- |---|---|
403
- | A disk image's directory, BAM, sector chains, or directory-fakery detection | `c64-disk-access` |
404
- | Which address to read next, and what the answer rules out | `c64-program-recon` |
405
- | Every way a live read gives a wrong answer | `c64-program-recon` — `references/observation-hazards.md`. **Read before driving.** |
406
- | What a specific address or bit means | `c64-memory-mapping` — `node … lookup '$D018'` |
407
- | Assembling | `acme-build` |
408
- | Whether a byte is original or cracker-changed, and what `bucketed` means | `c64-provenance-diff` |
409
- | Whether the emulator is wedged, and whether it is safe to recycle | `vice-wedge-triage` |
410
- | **A verified 64K image, or proving two captures equivalent** | here |
411
-
412
- ## Release registry shape
413
-
414
- `scripts/releases.mjs` is the only module that reads a release id out of the
415
- registry — every other module takes the id as an argument. Its shape:
416
-
417
- | Field | Level | Required | For |
418
- |---|---|---|---|
419
- | `schema_version` | top-level | — | The registry format version. |
420
- | `schema_notes` | top-level | — | Free-text stating the registry's N-readiness claim (`node $L schema-notes`). |
421
- | `releases` | top-level | yes | The array of release entries below. |
422
- | `id` | per-release | yes | The `--release` argument every other script takes. |
423
- | `canonical` | per-release | — | A boolean on one entry, not "the canonical image" — there are N releases. |
424
- | `disk_image` | per-release | yes | Project-relative path to the release's `.d64`. |
425
- | `dumps` | per-release | **yes, as an array** | Per-capture records written by `write-set`. **Must be an array, never omitted** — `releases.mjs`'s `list` command reads `r.dumps.length` with no guard (`releases.mjs:98`), so a missing `dumps` throws `TypeError: Cannot read properties of undefined` instead of listing anything. An empty array (`[]`) is fine. An absent key is not. `releases.mjs` itself imposes no per-entry shape. `scripts/watch-loads.mjs` (a different reader) looks up an entry by `label` and reads its `range_manifest`, which the example below follows. |
426
-
427
- The registry lives at `<project root>/recovery/RELEASES.json` by default —
428
- override the whole path with `C64RE_REGISTRY`, or just the containing
429
- directory with `C64RE_DATA_DIR`.
430
-
431
- `src/skills/c64-ram-capture/RELEASES.json.example` is a copyable starting
432
- point with every field above populated with placeholder values.
433
-
434
- ## References
435
-
436
- What this skill ships, and the committed modules it leans on. No `references/`
437
- split: the workflow fits in one file, which is the right call when it does.
438
-
439
- | Path | Covers |
440
- |---|---|
441
- | `scripts/compare.mjs` | Difference classification and the drift floor. Pure logic over captures you already have — `node $C` with no arguments prints the rules. |
442
- | `scripts/compare-cross-binary.mjs` | `cross` — for two DIFFERENT binaries. No drift bucket. A narrowed `$Dxxx` mask. Route awareness, an intentional-difference allowlist, and per-binary logical checkpoints. Covered by `scripts/compare-cross-binary.test.mjs`. |
443
- | `scripts/vsf-slice.mjs` | `slice` / `digest` — the flat 64K image sliced out of a `.vsf` snapshot with no transcription step. The layout lives in `vsf-slice.ts` on the MCP side. This wrapper resolves it and refuses by name when it cannot. Covered by `scripts/vsf-slice.test.mjs`. |
444
- | `scripts/derive-transients.mjs` | `derive` / `check` — the per-release transient allow-list, derived from N ≥ 3 runs as the pairwise union. A committed cap of 64 **voids** it rather than warns. Covered by `scripts/derive-transients.test.mjs`. |
445
- | `transients/README.md` | The committed derivation method, the artifact shape, and the cap's reasoning with its four measured reference points. No address set is inherited between releases. Its `.gitignore` refuses every image byte form. |
446
- | `templates/capture-record.template.md` | The per-capture record: identity, including the three-row reproducibility key and the `capture route` row. Also the machine state read in the same paused window, the void checklist, and the per-pairing comparison table. |
447
- | `scripts/dump-artifacts.mjs` | `assemble` / `chip-state` / `manifest` / `write-set` — the guarded byte work, and the source of every `assembleImage:` message in the table below. |
448
- | `RELEASES.json.example` | A copyable release-registry shape — see `## Release registry shape` above. |
449
-
450
- Record findings that make RE faster in your own project notes **at the moment you
451
- find them**, graded with `Evidence:` and `Confidence:`. Promote a finding by
452
- re-logging it with the new evidence, never by editing an old grade in place — the
453
- grade is only worth anything if it says what you actually knew when you wrote it.
454
-
455
- ## Troubleshooting
456
-
457
- | Symptom | Correction |
458
- |---|---|
459
- | `assembleImage: gap before address $3000 -- next chunk starts at $4000` | A `vice_memory_read` never landed. Re-read that 4096-byte window. Do not pad it. |
460
- | `assembleImage: overlap at address $8000 -- a previous chunk already covered up to $8003` | Two chunks cover the same window, usually a duplicated call after a retry. Drop the duplicate. |
461
- | `assembleImage: assembled 65534 bytes ending at $FFFE, expected exactly 65536` | A read returned short. Re-read the final window. |
462
- | `unknown release "x" -- known releases: …` | The `--release` id is not in the registry. The error names the valid ids. It throws before writing anything. |
463
- | A fresh `.map.json` says `classification_state: "bucketed"` | Wrong — a fresh capture is `"ranges-only"`. The provenance diff sets `"bucketed"`, nothing else. |
464
- | The checkpoint never fired | Most state reads pause the emulator. Resume exactly once, at the end, after every read. |
465
- | Two captures of the same checkpoint differ | Expected. Full-64K identity is impossible in principle. Run `compare` and read the verdict rather than judging by eye. |
466
- | `compare.mjs`'s `compare` fails on an address in `$D000`-`$DFFF` | It cannot — `compare.mjs` masks that whole range for its own same-binary job. If you are seeing this, you applied the rules by hand. Use `scripts/compare.mjs`. |
467
- | `compare-cross-binary.mjs`'s `cross` fails on an address in `$D000`-`$DFFF` | Expected — `compare-cross-binary.mjs` narrows the mask on purpose (see "Compare two different binaries" above). A failure here is a real finding, not a misapplied rule. |
468
- | `cross` refuses with "capture routes differ" | The two `--state` sidecars declare different `route` values. Re-capture both the same way — both `snapshot` or both `memory-read` — or pass the correct `--route`. |
469
- | `cross` refuses with "logical checkpoints differ" | The two `--state` sidecars declare different `checkpoint_name` values. Re-capture both at the same named checkpoint, or correct the sidecar. |
470
- | `compare` fails on `$FAD8` or `$FC51` only | Known and unexplained: RAM under KERNAL ROM, two addresses out of 8192. Record it with the capture rather than voiding a set that is otherwise clean. |
471
- | `--limit 0` printed nothing | Corrected 2026-08-04 — it now means unlimited. Re-pull the script if you see the old behaviour. |
472
- | An epoch-drift error appeared mid-capture | The machine restarted under you. Void the run. Do not salvage the artifacts. The next call succeeding does not undo it. |
473
- | The emulator looks dead | `vice-wedge-triage` — and enumerate your own armed checkpoints before concluding anything. |
474
- | `` `project-paths: could not locate the project root -- no `.git` found above ...` `` | No `.git` ancestor and no `C64RE_PROJECT_ROOT`. `git init` the project, or set the variable to its root. |