@henols/c64-re-tools 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/bin/cli.mjs +7 -4
  2. package/package.json +2 -2
  3. package/skills/acme-build/SKILL.md +39 -23
  4. package/skills/acme-build/scripts/acme.mjs +159 -64
  5. package/skills/acme-build/template.a +1 -1
  6. package/skills/c64-disk-access/SKILL.md +156 -0
  7. package/skills/c64-disk-access/scripts/c1541.mjs +569 -0
  8. package/skills/c64-memory-mapping/SKILL.md +30 -23
  9. package/skills/c64-memory-mapping/scripts/driver.mjs +1 -1
  10. package/skills/c64-petcat/SKILL.md +87 -0
  11. package/skills/c64-petcat/scripts/petcat.mjs +221 -0
  12. package/skills/c64-program-recon/SKILL.md +93 -39
  13. package/skills/c64-program-recon/references/control-flow.md +12 -15
  14. package/skills/c64-program-recon/references/graphics.md +1 -1
  15. package/skills/c64-program-recon/references/observation-hazards.md +18 -16
  16. package/skills/c64-program-recon/references/reconstruction.md +1 -2
  17. package/skills/c64-program-recon/references/sound-and-input.md +6 -8
  18. package/skills/c64-program-recon/references/tool-selection.md +36 -17
  19. package/skills/c64-program-recon/scripts/packer-finding.mjs +165 -87
  20. package/skills/c64-program-recon/templates/memory-map.template.md +2 -2
  21. package/skills/c64-provenance-diff/SKILL.md +40 -5
  22. package/skills/c64-provenance-diff/scripts/diff-images.mjs +8 -8
  23. package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +7 -5
  24. package/skills/c64-ram-capture/SKILL.md +112 -44
  25. package/skills/c64-ram-capture/scripts/compare.mjs +2 -2
  26. package/skills/c64-ram-capture/scripts/derive-transients.mjs +575 -0
  27. package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +3 -3
  28. package/skills/c64-ram-capture/scripts/mcp-module.mjs +174 -0
  29. package/skills/c64-ram-capture/scripts/releases.mjs +1 -1
  30. package/skills/c64-ram-capture/scripts/vsf-slice.mjs +147 -0
  31. package/skills/c64-ram-capture/scripts/watch-loads.mjs +15 -15
  32. package/skills/c64-ram-capture/templates/capture-record.template.md +44 -4
  33. package/skills/c64-ram-capture/transients/README.md +136 -0
  34. package/skills/routine-queue-walker/SKILL.md +114 -22
  35. package/skills/routine-queue-walker/scripts/completeness-report.mjs +465 -0
  36. package/skills/vice-wedge-triage/SKILL.md +96 -89
  37. package/skills/c64-ram-capture/scripts/d64-parse.mjs +0 -243
@@ -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
@@ -100,13 +111,20 @@ script measures it from the file. It prints one JSON object. Read the `verdict`:
100
111
  | `unknown` | No route produced an answer. `unavailableReason` always says why. | Continue, but record the unknown. Never write it up as "not packed". |
101
112
 
102
113
  **A name is reported only when an external oracle stated one, and this project does not guess.**
103
- No first-party route on this project's surface reports a packer name at all — the dated
104
- investigation that established this, four independent ways, is written out in
105
- `.planning/phases/19-absorbed-procedures-and-the-coverage-instrument/19-RESEARCH.md` §2, and the
106
- dated decision that fixes the acceptance bar and its re-open trigger is recorded under
107
- `19-DECISIONS.md` in that same directory (SURF-03). So there is no code path here that can write a packer name from entropy, from a
108
- decompression address, or from a byte pattern. If you want a name and the finding does not give
109
- you one, install an external identifier and point `UNP64` or `UNP64_PATH` at it do not infer it.
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.
110
128
 
111
129
  **The entropy gate answers packedness, not identity.** High entropy tells you the bytes are
112
130
  compressed (or encrypted, or genuinely random); it does not tell you by what. And the way a packed
@@ -154,7 +172,7 @@ The method reproduces a known-good result from a static image with no emulator r
154
172
  ## Writing findings into the annotation store
155
173
 
156
174
  Recon's findings are not memory-map prose written once and left to rot — they are entries in a
157
- queryable annotation store, and the Markdown memory map is a *generated view* of that store (D-24),
175
+ queryable annotation store, and the Markdown memory map is a *generated view* of that store,
158
176
  not something you hand-edit yourself.
159
177
 
160
178
  **There is no bootstrap step, and no bootstrap verb.** The store is created by the first write to
@@ -163,7 +181,7 @@ it: name a `.annostore` path on any mutating call — `anno_set_label_name`, `an
163
181
  A read-only call against a path that does not exist yet is REFUSED by name rather than answering
164
182
  against an empty store, so "I read nothing" and "there is nothing to read" stay distinguishable.
165
183
 
166
- Every `anno_*` tool takes an explicit `store` path (D-19) — there is no ambient session state
184
+ Every `anno_*` tool takes an explicit `store` path — there is no ambient session state
167
185
  naming the store, so which store a call touched is always visible in the transcript. Every call
168
186
  that derives its answer from the program's **bytes** rather than from the annotations takes an
169
187
  `image` path as well — `anno_get_binary_info`, `anno_read_region`, `anno_disassemble`,
@@ -189,7 +207,7 @@ bracket tokens (quoted verbatim from `anno-confidence.ts`, the parser's own sour
189
207
  (confirmed data), `[probable-data]` (probable data), `[unknown]` (unknown).
190
208
 
191
209
  A typo in the bracket token — wrong case, an underscore, a plural, stray whitespace — **fails
192
- loudly**; it does not silently degrade into an ungraded comment. As with `RE-FINDINGS.md`, do not
210
+ loudly**; it does not silently degrade into an ungraded comment. Do not
193
211
  promote a row by editing its grade in place: re-verify and restate the evidence with a fresh
194
212
  `anno_set_comment` call, so the record of when something stopped being a guess survives.
195
213
 
@@ -233,7 +251,7 @@ error and no explanation of why.
233
251
  The **loop itself is not withdrawn**, only its two automated legs, and the discipline it encodes is
234
252
  what to keep doing by hand for as long as they stay gone:
235
253
 
236
- 1. **The store is the merge point (D-29), not your own notes.** A name discovered live —
254
+ 1. **The store is the merge point, not your own notes.** A name discovered live —
237
255
  disassembling the running machine, a checkpoint hit — is written into the store with
238
256
  `anno_set_label_name` *first*, before it is carried anywhere else.
239
257
  2. **`vice_symbols_load` REPLACES the machine's symbol table rather than merging into it.** Call it
@@ -273,7 +291,7 @@ other generated artifact in this repo and never hand-edit it.
273
291
  existed.** Before gap-closure round 2 the banner recorded the store and the sidecar by their
274
292
  ABSOLUTE paths, so the checkout's own location was a silent third cause: an identical store,
275
293
  sidecar and rendered file reported `drifted` the moment the tree sat at a different absolute path,
276
- while `render-memmap` printed the same `render_digest` in both. Plan 29-18 removed that cause by
294
+ while `render-memmap` printed the same `render_digest` in both. That cause was removed by
277
295
  recording workspace-relative locations, so the cause set above is the one the shipped verb has. The
278
296
  old two-cause wording is superseded rather than merely reworded, and this note says so because a
279
297
  reader meeting it in history needs to know which claim was live when.
@@ -293,8 +311,8 @@ shipped tree run the sync before they scan it, so a change made only in the twin
293
311
  is ever measured. A change made here is SHIPPED only once that sync has run.
294
312
 
295
313
  **Dated correction, 2026-08-30 — `render-memmap` reads the annotation store directly, and the note
296
- that used to stand here was WRONG when it shipped.** Phase 29 plan 29-12 rebuilt this verb over the
297
- Phase 28 annotation store on `D-17`'s authority: its positional is an EXISTING `.annostore`, opened
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
298
316
  with `mustExist` — an absent store is refused by name rather than created — and nothing on the path
299
317
  it reaches consults the retired external analyser. The pre-store project file the earlier note named
300
318
  has no producer left in this repository, so there is no route back to the old spelling. That earlier
@@ -302,6 +320,29 @@ note asserted in the PRESENT TENSE that this verb still read a project file; it
302
320
  when it shipped, and it is DELETED here rather than amended, so a reader comparing two dated claims
303
321
  can tell which one to believe.
304
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.
345
+
305
346
  ## Static disassembly
306
347
 
307
348
  **Dated withdrawal 2026-08-29, dated return 2026-08-31 — whole-program ACME export was WITHDRAWN
@@ -314,14 +355,17 @@ against the input** — never by an exit code and never by a string match on the
314
355
  output.
315
356
 
316
357
  ```bash
317
- npx -y @henols/vice-mcp anno export-asm game.prg --store game.annostore --out game.a
358
+ npx -y @henols/vice-mcp anno export-asm game.prg --store game.annostore --out game-src
318
359
  node <plugin-root>/src/mcp/vice/vice-proxy.ts anno export-asm game.prg --store game.annostore
319
360
  ```
320
361
 
321
362
  `<image>` and `--store` are **two separate arguments and neither is derived from the other**: the
322
- image supplies the bytes, the store supplies the names, typed ranges and comments. `--out` defaults
323
- to a `.a` beside the **store** rather than beside the image, and an existing destination is refused
324
- rather than overwritten unless you pass `--force`.
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.
325
369
 
326
370
  **It writes source and runs no assembler**, and says so in its own second output line
327
371
  (`this file has NOT been assembled`). The real-ACME byte-diff is a **test-only** oracle in this
@@ -347,7 +391,7 @@ The two are complementary — reach for the static reads before the emulator is
347
391
  Extracting a program from a `.d64` image is a separate capability that this repository still does
348
392
  not have, and — correcting an earlier note that assigned it to the same numbered phase as the ACME
349
393
  export oracle — **no phase currently owns it**. Whenever it is built it must name the file inside
350
- the image explicitly and refuse rather than guess (D-02), because a guess could analyse a cracktro
394
+ the image explicitly and refuse rather than guess, because a guess could analyse a cracktro
351
395
  or loader stub instead of the game.
352
396
 
353
397
  ## Before you touch the emulator
@@ -474,9 +518,9 @@ up a pointer or a vector does it with a pair of immediate loads — `LDA #<targe
474
518
  / STA ptr`, `LDA #>target / STA ptr+1`. Upstream calls `set_immediate_format`
475
519
  on each of the two instruction addresses, with `low_byte` / `high_byte` and the
476
520
  target, so the pair renders as one symbol reference. **That call is not exposed
477
- on this project's surface.** `BUILD-03` ("every branch, `JSR`/`JMP` and data
478
- reference goes through a symbol, so code can move") is the requirement that
479
- supplies its criterion, and the per-call disposition sits in the manifest named
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
480
524
  in the attribution header above. Until then, recombine the two bytes yourself
481
525
  and put the reconstructed target in a side comment on both instructions, so the
482
526
  vector setup is readable even though the store cannot format it.
@@ -529,19 +573,26 @@ documented earlier on this page.
529
573
  | Disassembly appears to break mid-routine | Undocumented opcodes. Check the binary-info hint and keep reading. |
530
574
  | Zero-page usage contradicts the main program's | The routine runs from the IRQ. Its context is IRQ-relative. |
531
575
 
532
- ## REFERENCE-ONLY: decoding Commodore BASIC tokens
533
-
534
- **This capability is DEFERRED under `FUT-01` and this skill does not claim
535
- it.** The reason is empirical, not a shortage of effort: a commercial C64 title
536
- captured after its loader has run almost universally reduces to a one-line
537
- `SYS` stub, so a token decoder would spend its life decoding `10 SYS 2064` and
538
- nothing else. The material below is carried as **reference text only** read
539
- it if you hit the rare program that really does carry a tokenised BASIC
540
- program, and note that none of its trigger phrases appear in any skill's
541
- `description:` frontmatter, so it cannot fire on its own.
542
-
543
- If a future milestone lifts `FUT-01`, this section is the starting point rather
544
- than a fresh research task.
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.
545
596
 
546
597
  ### Line anatomy
547
598
 
@@ -610,6 +661,8 @@ This one is the route between the stations. It does not restate what the others
610
661
 
611
662
  | Need | Go to |
612
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` |
613
666
  | A verified 64K image, or comparing two captures | `c64-ram-capture` |
614
667
  | What a specific address or bit means | `c64-memory-mapping` — `node … lookup '$D018'` |
615
668
  | Assembling | `acme-build` |
@@ -630,9 +683,10 @@ This one is the route between the stations. It does not restate what the others
630
683
  | `references/reconstruction.md` | Binary inclusion, behavioural-equivalence correctness bar, SMC labels, label vocabulary |
631
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 |
632
685
 
633
- Findings that make RE faster go in `.planning/RE-FINDINGS.md` **at the moment you find them**,
634
- graded with `Evidence:` and `Confidence:`. Promote by re-logging with the new evidence, never by
635
- editing a grade in place. File-changing work enters through a GSD command (`/gsd-quick`).
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.
636
690
 
637
691
  ## Troubleshooting
638
692
 
@@ -644,5 +698,5 @@ editing a grade in place. File-changing work enters through a GSD command (`/gsd
644
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. |
645
699
  | Computed mode is "INVALID — screen goes black" | You caught the registers mid-update inside a raster split. Re-read. |
646
700
  | The emulator looks dead | Enumerate armed checkpoints before anything else. See hazard 2. |
647
- | `vice_keyboard_type` does nothing | The game polls `$DC00`/`$DC01` directly. Use `vice_keyboard_matrix` (**requires the fork backend** — see `references/observation-hazards.md` § 4 for the stock route). |
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. |
648
702
  | Two captures of the same checkpoint differ | Expected. Full-64K identity is impossible in principle; use `c64-ram-capture`'s drift rules. |
@@ -1,7 +1,6 @@
1
1
  # Control flow: entry point → vectors → IRQ source → main loop → structure
2
2
 
3
- Source: `.planning/RE-FINDINGS.md` § Control-flow discovery method (2026-08-01, **MEDIUM**,
4
- doc-derived) except where a line says otherwise. The vector-table step was confirmed against
3
+ Graded 2026-08-01, **MEDIUM**, doc-derived, except where a line says otherwise. The vector-table step was confirmed against
5
4
  this project's own captures on 2026-08-04 — see the bottom of this file.
6
5
 
7
6
  ## 1. Entry point — three routes, and post-depack is a different question
@@ -31,7 +30,7 @@ it prints the IRQ/BRK/NMI and hardware blocks by default and takes `--all` for t
31
30
  | Hardware vectors | `$FFFA-$FFFF` | NMI, RESET, IRQ/BRK. Live when the KERNAL is banked out |
32
31
 
33
32
  **The hardware pairs are only live when the ROMs are banked out via `$01`.** The deciding bit is
34
- HIRAM, `$01` bit 1 (RE-FINDINGS 2026-08-02). HIRAM = 1 ⇒ KERNAL ROM is in and `$0314/$0315` is
33
+ HIRAM, `$01` bit 1 (observed 2026-08-02). HIRAM = 1 ⇒ KERNAL ROM is in and `$0314/$0315` is
35
34
  live. HIRAM = 0 ⇒ RAM at `$E000-$FFFF` and `$FFFE/$FFFF` is live.
36
35
 
37
36
  Why LOAD and STOP earn their own callout on this project: both releases use custom raw-sector
@@ -82,15 +81,13 @@ Run over all six committed captures of one title (two releases, runs 1-3 each):
82
81
 
83
82
  NMI and RESET sharing one entry is the shape of an anti-tamper trap: RESTORE and reset are the two
84
83
  ways a user perturbs a running game, and both land in the same place. `$1116` is therefore the
85
- address to checkpoint when the emulator is next available press RESTORE with
86
- `mcp__plugin_c64-re-tools_vice__vice_keyboard_restore` (it is *not* in the keyboard matrix, and NMI will not retrigger
87
- until the line is released, so it is a press→release **edge**), then `vice_machine_reset` soft and
88
- hard, and record where the PC actually lands.
89
-
90
- **`vice_keyboard_restore` requires the fork backend.** The RESTORE key pulses the NMI line
91
- directly and is not part of the keyboard matrix, so stock's `KEYBOARD_FEED` (which only injects
92
- PETSCII text into the buffer) cannot produce it. Calling it on the stock backend returns an error
93
- naming the reason and the fork backend, rather than pulsing RESTORE.
84
+ address to checkpoint when the emulator is next available. Testing the RESTORE half of that
85
+ experiment is not currently possible: **`vice_keyboard_restore` is permanently unavailable.** The
86
+ RESTORE key pulses the NMI line directly and is not part of the keyboard matrix, so `KEYBOARD_FEED`
87
+ (which only injects PETSCII text into the buffer) cannot produce it; calling the tool returns an
88
+ error naming the reason, rather than pulsing RESTORE. No client-side substitute exists — see
89
+ `docs/stock-hard-losses.md`. The reset half of the experiment remains testable: arm the checkpoint,
90
+ call `vice_machine_reset` soft and hard, and record where the PC actually lands.
94
91
 
95
92
  **Evidence:** derived mechanically from six three-run-verified captures; every value identical
96
93
  across all three runs of its release, so none of it is drift.
@@ -161,9 +158,9 @@ at a title screen and again in gameplay, diff the two captures, and look for a s
161
158
  changed in zero page or low RAM. `vice_memory_compare` narrows this; `c64-ram-capture` § Compare
162
159
  two captures gives the volatility rules that stop you chasing drift.
163
160
 
164
- On stock, only `mode: 'ranges'` is served — capture the two states at different points in time and
161
+ Only `mode: 'ranges'` is served — capture the two states at different points in time and
165
162
  compare two live ranges. `mode: 'snapshot'` is refused with an explanatory message; there is no
166
- memory-only snapshot producer on either backend.
163
+ memory-only snapshot producer at all.
167
164
 
168
165
  ## Verified against this project — 2026-08-04
169
166
 
@@ -173,7 +170,7 @@ holding **`$1103`**, while `$0314/$0315` holds `$0101` — nothing meaningful, e
173
170
  dormant-block rule predicts.
174
171
 
175
172
  `$1103` is the same IRQ-handler entry that phase-01 live work independently established, with the
176
- raster-split chain `$1103 → $1574 → $152C` (RE-FINDINGS 2026-08-02, checkpoint-trap entry). The
173
+ raster-split chain `$1103 → $1574 → $152C` (observed 2026-08-02, on a checkpoint trap). The
177
174
  method reproduces a known-good result from a static image with no emulator running. The widened
178
175
  sweep's own results, including two facts this table never surfaced, are in §2 above.
179
176
 
@@ -1,6 +1,6 @@
1
1
  # VIC-II: locating every displayed byte
2
2
 
3
- Source: `.planning/RE-FINDINGS.md` § VIC-II discovery (2026-08-01, **MEDIUM**, doc-derived).
3
+ Graded 2026-08-01, **MEDIUM**, doc-derived.
4
4
 
5
5
  **Graphics data is computed, not searched.** Every pointer the VIC follows derives from two
6
6
  registers plus a bank. Read the bank, read `$D018`, read the mode bits, read `$D015` and the
@@ -5,7 +5,8 @@ this project at real cost. A wrong answer from a register table is cheap; a wron
5
5
  machine that changed *because you looked at it* discredits a whole session without announcing
6
6
  itself.
7
7
 
8
- Full provenance in `.planning/RE-FINDINGS.md`. Log new hazards there at the moment you hit one.
8
+ Log a new hazard in your own project notes at the moment you hit one — the cost of rediscovering
9
+ one is a whole discredited session.
9
10
 
10
11
  ## 1. Agent think-time runs the emulator at full speed
11
12
 
@@ -59,8 +60,7 @@ bracket, and comes back `wedged`. **The response to `wedged` is recycle, and rec
59
60
  instance is the exact loss both the tool and this hazard exist to prevent.** So when a `wedged`
60
61
  verdict arrives with any checkpoint still armed, do the two reads by hand before recycling:
61
62
  `vice_checkpoint_list`, then resolve the live handler (`$0314/$0315`, or `$FFFE/$FFFF` when `$01`
62
- has the ROMs banked out). Filed as
63
- `.planning/todos/pending/2026-08-04-vice-diagnose-checkpoint-trap-shapes-miss-mid-handler-arming.md`.
63
+ has the ROMs banked out). Observed 2026-08-04, and still open.
64
64
 
65
65
  **Counter-evidence, and why this is MEDIUM:** in one incident, deleting the checkpoint did *not*
66
66
  unfreeze the machine, and neither did a soft reset, a hard reset, nor a single step. A checkpoint
@@ -80,19 +80,20 @@ Prefer the whole-chip reads — `vice_vicii_get_state`, `vice_cia_get_state`, `v
80
80
  — over raw register reads. Whether the VICE monitor's own read path is side-effect-free is
81
81
  **unverified**: treat it as verify-don't-assume rather than taking it on faith.
82
82
 
83
- On the stock backend, `vice_vicii_get_state`/`vice_cia_get_state` reads are `sidefx: false` with
83
+ `vice_vicii_get_state`/`vice_cia_get_state` reads are `sidefx: false` with
84
84
  no argument able to override it — **VERIFIED**, asserted on the wire body by a regression test.
85
85
  Whether the emulator's own `MEM_GET` read path actually honours that flag for
86
86
  `$D01E`/`$D01F`/`$DC0D`/`$DD0D` — i.e. whether it truly cannot clear them — is **ASSUMED**, with
87
- no probe recorded in this repo; treat it as no worse than the fork's own unverified path, not as
88
- a proven guarantee. `vice_sid_get_state` is **fork-only**, since SID `$D400-$D418` is write-only
89
- in hardware and the binary monitor has no SID command. Also: on stock, an internal field the
90
- register map cannot expose is marked `{ available: false, reason }` in the answer, never a bare
91
- `0` do not record a stock `0` from one of these fields as a measurement; check `available`
92
- first. A stock chip-state or sprite answer also **names the memory view it read** (`bank`, or
87
+ no probe recorded in this repo; treat it as unverified, not as a proven guarantee.
88
+ `vice_sid_get_state` read-back is **permanently unavailable**: SID `$D400-$D418` is write-only
89
+ in hardware and the binary monitor has no SID command, so there is no route to recover it.
90
+ Writes to those addresses still work fine. See `docs/stock-hard-losses.md`. Also: an internal
91
+ field the register map cannot expose is marked `{ available: false, reason }` in the answer, never
92
+ a bare `0` do not record a `0` from one of these fields as a measurement; check `available`
93
+ first. A chip-state or sprite answer also **names the memory view it read** (`bank`, or
93
94
  `registerBank`/`dataBank`), read through the emulator's own `io`/`ram` banks, so it stays valid
94
95
  even while the program has I/O banked out ($01 driving the RAM/ROM/I-O switch). An answer with
95
- **no** bank field — an older transcript, or the fork backend — is suspect whenever `$01` may not
96
+ **no** bank field — an older transcript — is suspect whenever `$01` may not
96
97
  have been `$37`: those bytes may be the RAM underneath the I/O area, not registers.
97
98
 
98
99
  ## 4. The keyboard buffer is not how games read keys
@@ -103,13 +104,14 @@ Games and cracks poll the `$DC00`/`$DC01` matrix directly, bypassing the KERNAL
103
104
  `vice_keyboard_type` is invisible to them. Use `vice_keyboard_matrix`, and hold a key across a
104
105
  gate by releasing it at the trigger checkpoint, never earlier.
105
106
 
106
- **`vice_keyboard_matrix` requires the fork backend.** The binary monitor's `KEYBOARD_FEED` (0x72)
107
+ **`vice_keyboard_matrix` is permanently unavailable.** The binary monitor's `KEYBOARD_FEED` (0x72)
107
108
  only injects PETSCII text into the KERNAL keyboard buffer; the emulator recomputes CIA port B from
108
109
  its own keyboard array on every read, so there is no wire command that can drive the raw matrix —
109
- this is unrecoverable on stock, not merely unbuilt. On stock, use `vice_keyboard_type` /
110
- `vice_keyboard_petscii` when the gate reads the KERNAL buffer, or `vice_joystick_set` when it polls
111
- the matrix directly instead; either way, buffer injection is invisible to a program polling
112
- `$DC00`/`$DC01` itself, so a matrix-polling gate must be driven by the joystick or not at all.
110
+ this is unrecoverable, not merely unbuilt. See `docs/stock-hard-losses.md`. Use
111
+ `vice_keyboard_type` / `vice_keyboard_petscii` when the gate reads the KERNAL buffer, or
112
+ `vice_joystick_set` when it polls the matrix directly instead; either way, buffer injection is
113
+ invisible to a program polling `$DC00`/`$DC01` itself, so a matrix-polling gate must be driven by
114
+ the joystick or not at all.
113
115
 
114
116
  ## 5. Most state reads pause the emulator and do not resume it
115
117
 
@@ -43,8 +43,7 @@ at a checkpoint is not verified** — the constraint's own conclusion, and the r
43
43
  design is part of the reconstruction work rather than something to bolt on afterwards.
44
44
 
45
45
  **That bar is what buys you the freedom to rename routines, reorganise files, replace constants
46
- with symbols and add macros** (`.planning/REQUIREMENTS.md` § Out of Scope makes the same
47
- argument). **But** reorganising changes addresses, which breaks self-modifying code and
46
+ with symbols and add macros**. **But** reorganising changes addresses, which breaks self-modifying code and
48
47
  timing-sensitive raster routines. Replay through the checkpoint set after EACH reorganisation, not
49
48
  at the end of several: one changed address per failing replay is a short diagnosis, ten is a
50
49
  bisect.
@@ -1,7 +1,6 @@
1
1
  # SID and CIA: music, effects, the RNG, input, timing
2
2
 
3
- Source: `.planning/RE-FINDINGS.md` § SID discovery and § CIA 6526 discovery (2026-08-01,
4
- **MEDIUM**, doc-derived) except where marked. Per-register bit detail lives in
3
+ Graded 2026-08-01, **MEDIUM**, doc-derived, except where marked. Per-register bit detail lives in
5
4
  `c64-memory-mapping`; this file carries the idioms and the order.
6
5
 
7
6
  ## SID — separating the player from the game logic
@@ -61,12 +60,11 @@ One that programs `$DC04-$DC07` and enables timer A runs its own timebase.
61
60
  - **Direct `$DC00`/`$DC01` polling is the norm, and it defeats `vice_keyboard_type`.**
62
61
  **Evidence: live, established on this project during recovery work. Confidence: HIGH. Cost: an
63
62
  afternoon.** Games and cracks bypass the KERNAL keyboard buffer and read the matrix directly.
64
- Assume it until shown otherwise, and drive input with `vice_keyboard_matrix` or the joystick
65
- tools instead. **`vice_keyboard_matrix` requires the fork backend** the binary monitor's
66
- `KEYBOARD_FEED` only injects PETSCII buffer text and cannot drive the raw matrix. On stock, use
67
- `vice_keyboard_type` / `vice_keyboard_petscii` when the gate reads the KERNAL buffer, or
68
- `vice_joystick_set` when it polls the matrix directly; buffer injection stays invisible to a
69
- program polling `$DC00`/`$DC01` itself.
63
+ Assume it until shown otherwise. **`vice_keyboard_matrix` is permanently unavailable** the
64
+ binary monitor's `KEYBOARD_FEED` only injects PETSCII buffer text and cannot drive the raw
65
+ matrix; see `docs/stock-hard-losses.md`. Use `vice_keyboard_type` / `vice_keyboard_petscii` when
66
+ the gate reads the KERNAL buffer, or `vice_joystick_set` when it polls the matrix directly;
67
+ buffer injection stays invisible to a program polling `$DC00`/`$DC01` itself.
70
68
 
71
69
  ## Finding input handling from the observable side
72
70
 
@@ -4,8 +4,7 @@
4
4
  call; read parameters off those. What the schemas cannot tell you is *which call to reach for
5
5
  first*, and that ordering is the whole value here.
6
6
 
7
- Curated from `.planning/RE-FINDINGS.md`'s tool-to-question entry (2026-08-01, doc-derived,
8
- **Confidence: MEDIUM** — the mapping is reasoned from the tool surface and this project's own
7
+ Curated 2026-08-01, doc-derived, **Confidence: MEDIUM** — the mapping is reasoned from the tool surface and this project's own
9
8
  usage, not measured). Individual rows that have since been exercised live are marked.
10
9
 
11
10
  | Question | Call |
@@ -14,13 +13,13 @@ usage, not measured). Individual rows that have since been exercised live are ma
14
13
  | What does the handler at this vector do? | `vice_disassemble` — the emulator's own decoder, not a dead listing |
15
14
  | Is this really the main loop? | `vice_checkpoint_add` + `vice_run_until` + `vice_registers_get` — fires once per frame ⇒ proven |
16
15
  | What code writes this? | `vice_watch_add` — finds **writers**. Best targets: `$D018`, VM+`$03F8`, `$D404` |
17
- | Whole-chip VIC-II/CIA state without the read hazards | `vice_vicii_get_state` / `vice_cia_get_state` (**both backends**) — prefer these over raw register reads |
18
- | Whole-chip SID state without the read hazards | `vice_sid_get_state` (**requires the fork** SID `$D400-$D418` is write-only in hardware and the binary monitor has no SID command; unrecoverable on stock) |
19
- | Decode sprite data | `vice_sprite_get` / `vice_sprite_inspect` (**both backends**) |
20
- | Find a known byte pattern | `vice_memory_search` (**both backends**) |
21
- | Carry labels across sessions | `vice_symbols_load` / `vice_symbols_lookup` (**both backends**) — ACME `--vicelabels` emits the format they consume. The annotation store's own export into that format is **withdrawn as of 2026-08-29, and no phase currently owns its return** — an earlier forecast naming a numbered phase for it is superseded |
22
- | Is the machine wedged, or did it stop itself? | `vice_diagnose` — five-state verdict with its evidence (the two backends' verdict sets differ by one; see `docs/stock-vice-parity.md` D-03). **Reachable and proxy-intercepted as of 2026-08-04** (verified live). Triage tree: `vice-wedge-triage` |
23
- | Replace a wedged instance | `vice_recycle` — destructive, requires a `reason`, and that reason is written into `.planning/incidents/` **before** anything is killed. The reason *is* the evidence record |
16
+ | Whole-chip VIC-II/CIA state without the read hazards | `vice_vicii_get_state` / `vice_cia_get_state` — prefer these over raw register reads |
17
+ | Whole-chip SID state without the read hazards | `vice_sid_get_state` **permanently unavailable**: SID `$D400-$D418` is write-only in hardware and the binary monitor has no SID command, so read-back cannot be recovered. Writes to those addresses still work fine over the memory-set primitive. See `docs/stock-hard-losses.md` |
18
+ | Decode sprite data | `vice_sprite_get` / `vice_sprite_inspect` |
19
+ | Find a known byte pattern | `vice_memory_search` |
20
+ | Carry labels across sessions | `vice_symbols_load` / `vice_symbols_lookup` — ACME `--vicelabels` emits the format they consume. The annotation store's own export into that format is **withdrawn as of 2026-08-29, and no phase currently owns its return** — an earlier forecast naming a numbered phase for it is superseded |
21
+ | Is the machine wedged, or did it stop itself? | `vice_diagnose` — five-state verdict with its evidence (one state is `monitor_held_elsewhere`, because this monitor serves exactly one client at a time). **Reachable and proxy-intercepted as of 2026-08-04** (verified live). Triage tree: `vice-wedge-triage` |
22
+ | Replace a wedged instance | `vice_recycle` — destructive, requires a `reason`, and that reason is written into `.c64-re-tools/incidents/` **before** anything is killed. The reason *is* the evidence record |
24
23
  | Read the restart epoch | **No tool does.** The proxy compares it around every forwarded call and raises drift itself; a value comes from that error or from `vice_diagnose` |
25
24
 
26
25
  ## Delegate rather than restate
@@ -32,16 +31,36 @@ usage, not measured). Individual rows that have since been exercised live are ma
32
31
  | A verified 64K image, or comparing two captures | the `c64-ram-capture` skill |
33
32
  | Whole-program static disassembly with code/data separation | **`anno export-asm`** — withdrawn 2026-08-29, returned 2026-08-31, settled by assembling the output with a real ACME and diffing the bytes against the input. That oracle is test-only, so the verb itself writes source and runs no assembler. For a single routine, read one explicit range at a time with `anno_read_region` / `anno_disassemble` (4096-byte cap per call, refused rather than truncated above it) and record what you verified with `anno_set_data_type` |
34
33
 
34
+ ## Runtime evidence versus the byte-derived guess
35
+
36
+ | Question | Call |
37
+ |---|---|
38
+ | Where the store's byte-derived block table (`anno_set_data_type`'s own ranges) disagrees with what the emulator was actually observed executing | **`anno evid-disagreements`** (also `anno_evid_disagreements`) — joins the typed ranges against the runtime-observed rows an `anno_evid_ingest` call already wrote, reporting disagreements first, agreement as a count only, and a never-observed count that is explicitly NOT evidence the address holds data |
39
+ | What evidence a store already holds, without re-running the program | `anno_evid_runs` — every run identity's observation count beside its denominator |
40
+ | Reset one run identity's evidence for a fresh re-measurement | `anno_evid_reset` — clears only that run identity's rows; pair it with `vice_memmap_zap` on the emulator side |
41
+
42
+ ## What blocks this program's code from being moved
43
+
44
+ | Question | Call |
45
+ |---|---|
46
+ | Which constructions block relocating, rebasing or stripping part of this program | **`anno hazard-report`** (also `anno_hazard_report`) — enumerates movement-hazard findings derived from decoded bytes alone (for example, a store or read-modify-write instruction whose literal target lands on another instruction's opcode or operand byte, changing what runs or what value is read on a later pass). Each finding carries its own detection mechanism and a detection-strength token. Reports and changes NOTHING: it never relocates, strips or rebases anything, and it never emits a flag a caller could act on as an automatic relocation |
47
+
48
+ **A region this report does not flag is undecided or unflagged, never certified safe to move.** Every checked
49
+ region reports one of exactly three outcomes, and only one of them means a construction was actually found
50
+ there — the other two both mean "nothing this report knows how to look for fired here," which is a fact about
51
+ the detectors, not a guarantee about the bytes. A store through a runtime-computed pointer into the code range
52
+ is a known, named miss: this report cannot see it, and its absence from the findings is not evidence that no
53
+ such construction exists. Use this alongside, never instead of, the runtime-evidence and provenance-diff
54
+ routes above and in the sibling skills — a region with no finding still needs a human decision before it is
55
+ moved.
56
+
35
57
  ## Three traps in this table
36
58
 
37
- **`vice_run_until`'s timeout is backend-qualified it has none on the fork, but stock bounds it
38
- (Phase 7, D-02).** On the fork, `cycles` is documented as *"not yet implemented"* and there is no
39
- `timeout_ms` either, so a run to an address the program never reaches has nothing to bound it and
40
- looks exactly like a wedged emulator; prefer `vice_checkpoint_add` + a bounded poll when the
41
- address is a hypothesis rather than a certainty. **Confidence: MEDIUM on the fork** — read off the
42
- schema, not reproduced. On stock, `timeout_ms` (default 30000, ceiling 600000) bounds the wait, and
43
- a timed-out answer says the machine is left halted rather than looking like a wedge — see
44
- `vice-wedge-triage` for the full triage judgement and its live evidence; do not restate it here.
59
+ **`vice_run_until`'s `timeout_ms` bounds the wait.** `timeout_ms` (default 30000, ceiling 600000)
60
+ bounds a run to an address the program never reaches, and a timed-out answer says the machine is
61
+ left halted rather than looking like a wedged emulator; prefer `vice_checkpoint_add` + a bounded
62
+ poll when the address is a hypothesis rather than a certainty see `vice-wedge-triage` for the
63
+ full triage judgement and its live evidence; do not restate it here.
45
64
 
46
65
  **`vice_diagnose` leaves the machine paused.** When it measures a cycle bracket it resumes the
47
66
  machine once or twice and then leaves it **paused** — resuming is your own next call. And a