@henols/c64-re-tools 0.2.3 → 0.2.5

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.
@@ -9,14 +9,15 @@ description: Capture a running C64's full 64K RAM as a verified flat image, and
9
9
  permitted route. Never open a connection by any other means.
10
10
 
11
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
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
14
  and name the offending address when it is wrong.
15
15
 
16
16
  ```bash
17
17
  S=src/skills/c64-ram-capture/scripts # from the repo root
18
18
  A=$S/dump-artifacts.mjs
19
19
  C=$S/compare.mjs L=$S/releases.mjs
20
+ CX=$S/compare-cross-binary.mjs # different-binary comparison (see below)
20
21
  T=$S/derive-transients.mjs V=$S/vsf-slice.mjs
21
22
  TD=src/skills/c64-ram-capture/transients # the derived allow-lists live here
22
23
 
@@ -29,6 +30,8 @@ node $C digest dump.bin # sha256 + size, for the captur
29
30
  node $C compare a.bin b.bin # classify every difference, exit 1 on FAIL
30
31
  node $C floor a.bin b.bin c.bin # drift floor across a capture set
31
32
 
33
+ node $CX cross original.bin rebuild.bin # DIFFERENT binaries -- see below
34
+
32
35
  node $T derive --release <id> --out $TD/<id>.json a.bin b.bin c.bin
33
36
  node $T check --allow-list $TD/<id>.json a.bin b.bin
34
37
 
@@ -40,7 +43,7 @@ Every module above reads only committed files and the JSON **you** wrote from
40
43
  your own `mcp__plugin_c64-re-tools_vice__*` calls. They contact nothing.
41
44
 
42
45
  **Prerequisite: a resolvable project root.** `scripts/project-paths.mjs` uses
43
- `C64RE_PROJECT_ROOT` when it is set, and otherwise walks up from the toolkit's
46
+ `C64RE_PROJECT_ROOT` when you set it, and otherwise walks up from the toolkit's
44
47
  own location for the nearest ancestor directory containing a `.git` entry. A
45
48
  scratch project has neither by default, so `git init` it first or set the
46
49
  variable — the thrown error names both when this fails.
@@ -50,7 +53,7 @@ variable — the thrown error names both when this fails.
50
53
  | # | Phase | Settles |
51
54
  |---|---|---|
52
55
  | 1 | Read the disk directory | Whether the release's entries are real or faked, before booting anything |
53
- | 2 | Boot, confirm the PC moved | That the loader is actually executing |
56
+ | 2 | Boot, check the PC moved | That the loader is actually executing |
54
57
  | 3 | Checkpoint, hit, read 64K + chip state | The capture itself — all reads in one paused window |
55
58
  | 4 | `write-set` | Assertions pass, four artifacts written, digest returned |
56
59
  | 5 | Disarm, enumerate, resume once | That you left no checkpoint armed and the machine running |
@@ -66,10 +69,10 @@ sector chain or raw bytes, and directory-fakery detection — is
66
69
  1. `mcp__plugin_c64-re-tools_vice__vice_disk_attach` with the disk image.
67
70
  2. `mcp__plugin_c64-re-tools_vice__vice_autostart` with the same image.
68
71
  3. `mcp__plugin_c64-re-tools_vice__vice_execution_run`.
69
- 4. `mcp__plugin_c64-re-tools_vice__vice_registers_get` and confirm the program counter has moved.
72
+ 4. `mcp__plugin_c64-re-tools_vice__vice_registers_get` and check the program counter has moved.
70
73
 
71
- The broker sets the drive type at launch for every stock instance, so no drive setup
72
- is needed before attaching.
74
+ Every time the broker starts a stock instance, it sets the drive type, so you do
75
+ not need drive setup before attaching.
73
76
 
74
77
  If the program counter has not moved, type `LOAD"*",8,1` with
75
78
  `mcp__plugin_c64-re-tools_vice__vice_keyboard_type`, run it, then type `RUN` and run it.
@@ -84,11 +87,11 @@ If the program counter has not moved, type `LOAD"*",8,1` with
84
87
  4096 bytes each. Write them to `chunks.json` as an array of
85
88
  `{ "address": "$0000", "hex": "..." }` records, one per call, hex only.
86
89
  5. Record the chip state in the **same paused window**, into `raw.json`. The keys
87
- are fixed, because `chip-state` derives from exactly these: `registers`,
88
- `sprites` and `cpu` pass through verbatim from
90
+ never change, because `chip-state` derives from exactly these fields.
91
+ `registers`, `sprites` and `cpu` pass through verbatim from
89
92
  `mcp__plugin_c64-re-tools_vice__vice_vicii_get_state` / `mcp__plugin_c64-re-tools_vice__vice_sprite_get` /
90
- `mcp__plugin_c64-re-tools_vice__vice_registers_get`; `port01_raw` is `$0001`; `dd00_raw` is
91
- `$DD00`; `d018_raw` is `$D018`; `sprite_pointers` is the eight bytes at
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
92
95
  `screen_base+$3F8`.
93
96
  6. Write all four artifacts in one call:
94
97
 
@@ -103,7 +106,7 @@ If the program counter has not moved, type `LOAD"*",8,1` with
103
106
  with the SHA-256. It also derives `vic_bank`, `screen_base`, `charset_base`
104
107
  and `sprite_data_addresses` for free — do not recompute them by hand.
105
108
  7. `mcp__plugin_c64-re-tools_vice__vice_checkpoint_delete` the checkpoint.
106
- 8. `mcp__plugin_c64-re-tools_vice__vice_checkpoint_list` and confirm it reports zero checkpoints.
109
+ 8. `mcp__plugin_c64-re-tools_vice__vice_checkpoint_list` and check it reports zero checkpoints.
107
110
  Accept only this enumeration as proof. Record the count.
108
111
  9. `mcp__plugin_c64-re-tools_vice__vice_execution_run` to leave the machine running.
109
112
 
@@ -160,20 +163,20 @@ only after the provenance diff partitions loader from cracktro from game — see
160
163
  ## Find an entry point
161
164
 
162
165
  1. Press past any "hit any key" gate. **`vice_keyboard_matrix` is permanently unavailable** — the
163
- binary monitor's `KEYBOARD_FEED` only injects PETSCII text into the KERNAL buffer and cannot
164
- drive the raw matrix; see `docs/stock-hard-losses.md`. Use `vice_keyboard_type` /
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` /
165
168
  `vice_keyboard_petscii` when the gate reads the KERNAL buffer, or `vice_joystick_set` when it
166
- polls the matrix directly; buffer injection stays invisible to a program polling
169
+ polls the matrix directly. Buffer injection stays invisible to a program that polls
167
170
  `$DC00`/`$DC01` itself.
168
171
  2. Step forward in batches with `mcp__plugin_c64-re-tools_vice__vice_execution_step`, reading
169
172
  `mcp__plugin_c64-re-tools_vice__vice_registers_get` after each batch.
170
173
  3. Stop when the program counter and the stack pointer both settle into a
171
174
  repeating range across three consecutive batches. That range is the
172
- dispatch loop; its lowest address is the entry point.
173
- 4. Confirm the address with `mcp__plugin_c64-re-tools_vice__vice_disassemble` before recording it.
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.
174
177
 
175
178
  Set a batch ceiling before you start. Report failure to stabilise as a finding
176
- with the batches spent; never extend the ceiling silently.
179
+ with the batches spent. Never extend the ceiling silently.
177
180
 
178
181
  ## Prove the machine did not change under you
179
182
 
@@ -181,7 +184,7 @@ with the batches spent; never extend the ceiling silently.
181
184
  not have to poll for one.** The proxy compares the restart epoch before *and*
182
185
  after every forwarded call, and refuses the call — or discards its result, if the
183
186
  change happened mid-call — with a loud error naming both epoch values. So the
184
- capture's identity is guarded continuously, not at two sampled points.
187
+ proxy guards the capture's identity continuously, not at two sampled points.
185
188
 
186
189
  What that leaves you:
187
190
 
@@ -202,7 +205,7 @@ stops answering.
202
205
  1. Rename each artifact to `<name>.VOID-<UTC timestamp>`.
203
206
  2. Write a sibling note recording the reason, the time, and — if a drift error is
204
207
  what voided it — the two epoch values quoted from that error. Do not go looking
205
- for them; nothing reads the epoch on demand. Keep the voided artifacts on disk.
208
+ for them. Nothing reads the epoch on demand. Keep the voided artifacts on disk.
206
209
 
207
210
  ## Compare two captures
208
211
 
@@ -246,12 +249,12 @@ repeat every `$40` across `$D000-$D3FF` and the SID's across `$D400-$D7FF`, so
246
249
  reading that range samples live hardware and two captures can never agree there.
247
250
  Omitting it is what made the earlier hand-applied rule fail five of the six
248
251
  committed gameentry pairings on `$D344`, `$D625` and `$D628` — differences that
249
- were guaranteed. Region first, bit-count second.
252
+ the repeating registers guarantee. Region first, bit-count second.
250
253
 
251
254
  `$E000-$FFFF` (RAM under KERNAL ROM when HIRAM = 0) is deliberately **not**
252
255
  excluded. `$FAD8` and `$FC51` do differ across captures, but only two addresses
253
256
  out of 8192 — too few for power-on garbage, and unexplained. They still fail, and
254
- what writes them is an open question. Observed 2026-08-04; graded MEDIUM,
257
+ what writes them is an open question. Observed 2026-08-04. Graded MEDIUM,
255
258
  structural, not reproduced against a second release.
256
259
 
257
260
  **Establish a drift floor** with `floor` across every capture of one checkpoint.
@@ -266,10 +269,54 @@ Capture the power-on image as the very first action against a fresh machine, the
266
269
  idle-capture twice more and run `floor` over the set. State the result as a
267
270
  floor, not a complete set — more captures can only widen it.
268
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
+
269
316
  ## Derive a per-release transient allow-list
270
317
 
271
318
  `scripts/derive-transients.mjs` is the named, repeatable derivation. **The
272
- method is what carries forward between releases; no address set ever does.** Two
319
+ method is what carries forward between releases. No address set ever does.** Two
273
320
  verbs:
274
321
 
275
322
  ```bash
@@ -280,18 +327,18 @@ node $T check --allow-list $TD/<id>.json runA.bin runB.bin
280
327
  `derive` takes **N ≥ 3** runs of the same release under the same protocol at the
281
328
  same stop and writes the **union of addresses differing across every pairwise
282
329
  comparison** — one entry per address, carrying which pairings it differed in,
283
- the distinct bytes seen, and an empty attribution line for you to fill. Fewer
284
- than three images is refused naming the count and the minimum; an image that is
285
- not exactly 65536 bytes is refused naming the path and the length. It prints
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
286
333
  `TRANSIENT_COUNT: <n>` at column 0, so a measurement gets transcribed rather
287
334
  than paraphrased.
288
335
 
289
336
  **Over the cap of 64 addresses the derivation is VOID:** non-zero exit, **no
290
337
  artifact written**, and the message says what the overflow means — the stop is
291
338
  not frame-exact. That is a fact to record, not a threshold to raise. `--cap`
292
- only ever *narrows*; a value above 64 is refused by name, and the union is never
293
- truncated to fit, because a truncated list makes every later comparison pass on
294
- bytes nobody vetted. Overflow is a **measured** outcome: 0 differing addresses
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
295
342
  at a frame-exact `READY` stop, 66 at a frame-anchored autostarted stop at
296
343
  jitter 4000 ms, 300 at a wall-clock autostarted stop on a real release, 1242 at
297
344
  a wall-clock `READY` stop with the determinism block applied.
@@ -331,7 +378,7 @@ real difference. Do not carry the exclusion across from the memory-read route.
331
378
 
332
379
  A malformed snapshot is **refused**, by name, naming the offending value and the
333
380
  valid range — it is never truncated into a plausible short image. The `.vsf`
334
- byte layout lives in exactly one place, `vsf-slice.ts` on the MCP side; this
381
+ byte layout lives in exactly one place, `vsf-slice.ts` on the MCP side. This
335
382
  script resolves it via `VICE_MCP_DIR`, the in-repo path, or the
336
383
  `@henols/vice-mcp` package, and refuses naming every path it tried when no rung
337
384
  resolves rather than falling back to a second copy of the layout.
@@ -344,8 +391,8 @@ and `size` become the sidecar's `captureSha256`, proving which image the rendere
344
391
  sidecar's other run-scoped keys (`port01`, `dd00`, `vicBank`, `screenRam`, `charsetOrBitmap`, `mode`,
345
392
  `liveVectorPair`, `vectorHandler`, `rasterPositions`) come from `c64-program-recon`'s own
346
393
  `derive.mjs` — **this skill does not emit the sidecar itself.** The sidecar is hand-authored from
347
- those two skills' outputs and validated by the renderer, which throws naming every missing or
348
- malformed key at once rather than rendering a document that silently carries a placeholder.
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.
349
396
 
350
397
  ## Which skill does what
351
398
 
@@ -375,7 +422,7 @@ registry — every other module takes the id as an argument. Its shape:
375
422
  | `id` | per-release | yes | The `--release` argument every other script takes. |
376
423
  | `canonical` | per-release | — | A boolean on one entry, not "the canonical image" — there are N releases. |
377
424
  | `disk_image` | per-release | yes | Project-relative path to the release's `.d64`. |
378
- | `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. |
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. |
379
426
 
380
427
  The registry lives at `<project root>/recovery/RELEASES.json` by default —
381
428
  override the whole path with `C64RE_REGISTRY`, or just the containing
@@ -392,32 +439,36 @@ split: the workflow fits in one file, which is the right call when it does.
392
439
  | Path | Covers |
393
440
  |---|---|
394
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. |
395
- | `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`. |
396
- | `scripts/derive-transients.mjs` | `derive` / `check` — the per-release transient allow-list, derived from N ≥ 3 runs as the pairwise union, under a committed cap of 64 that **voids** rather than warns. Covered by `scripts/derive-transients.test.mjs`. |
397
- | `transients/README.md` | The committed derivation method, the artifact shape, the cap's reasoning with its four measured reference points, and the rule that no address set is inherited between releases. Its `.gitignore` refuses every image byte form. |
398
- | `templates/capture-record.template.md` | The per-capture record: identity including the three-row reproducibility key and the `capture route` row, machine state read in the same paused window, the void checklist, and the per-pairing comparison table. |
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. |
399
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. |
400
448
  | `RELEASES.json.example` | A copyable release-registry shape — see `## Release registry shape` above. |
401
449
 
402
450
  Record findings that make RE faster in your own project notes **at the moment you
403
451
  find them**, graded with `Evidence:` and `Confidence:`. Promote a finding by
404
452
  re-logging it with the new evidence, never by editing an old grade in place — the
405
- grade is only worth anything if it says what was actually known when it was written.
453
+ grade is only worth anything if it says what you actually knew when you wrote it.
406
454
 
407
455
  ## Troubleshooting
408
456
 
409
- | Symptom | Fix |
457
+ | Symptom | Correction |
410
458
  |---|---|
411
- | `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. |
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. |
412
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. |
413
461
  | `assembleImage: assembled 65534 bytes ending at $FFFE, expected exactly 65536` | A read returned short. Re-read the final window. |
414
- | `unknown release "x" -- known releases: …` | The `--release` id is not in the registry. The error names the valid ids; it throws before writing anything. |
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. |
415
463
  | A fresh `.map.json` says `classification_state: "bucketed"` | Wrong — a fresh capture is `"ranges-only"`. The provenance diff sets `"bucketed"`, nothing else. |
416
464
  | The checkpoint never fired | Most state reads pause the emulator. Resume exactly once, at the end, after every read. |
417
- | 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. |
418
- | `compare` fails on an address in `$D000`-`$DFFF` | It cannot — that range is volatile. If you are seeing this, you applied the rules by hand; use `scripts/compare.mjs`. |
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. |
419
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. |
420
- | `--limit 0` printed nothing | Fixed 2026-08-04 — it now means unlimited. Re-pull the script if you see the old behaviour. |
421
- | 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. |
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. |
422
473
  | The emulator looks dead | `vice-wedge-triage` — and enumerate your own armed checkpoints before concluding anything. |
423
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. |