@henols/c64-re-tools 0.1.12 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.mjs CHANGED
@@ -26,14 +26,57 @@ const HERE = dirname(fileURLToPath(import.meta.url)); // installer/bin (packed)
26
26
  const PKG_ROOT = dirname(HERE);
27
27
  const SKILLS_SRC = join(PKG_ROOT, "skills");
28
28
 
29
+ // The single version-resolution seam this repo maintains is
30
+ // `.claude/mcp/vice/version.ts` (quick-260819-tsz, D-5). This package
31
+ // deliberately does NOT import it: it ships without the seam file (its
32
+ // `files[]` is `bin/`, `skills/`, `README.md`) and targets node >= 18, which
33
+ // cannot type-strip the seam's `.ts` the way the vice-mcp package's own
34
+ // node >= 22.18 runtime can. What follows is exactly the seam's own
35
+ // precedence step 1 -- "read my own package.json's `.version`, trust it
36
+ // when it is a real published number" -- not a second, independent
37
+ // implementation of the resolution algorithm; there is no template/`-`
38
+ // handling here because this package is never resolved from `VERSION`,
39
+ // only ever published with a concrete version already stamped in. That
40
+ // number is PRODUCED elsewhere: `scripts/version.mjs stamp` writes the
41
+ // working-tree placeholder, and CI's `npm version` writes the real one at
42
+ // publish time. Do not reimplement D-2's template-resolution rules here.
29
43
  const SELF = readJson(join(PKG_ROOT, "package.json")) ?? {};
30
44
  const SELF_VERSION = typeof SELF.version === "string" ? SELF.version : "0.0.0";
31
45
  const MCP_PKG = "@henols/vice-mcp";
32
- // Wire the project to the exact vice-mcp version this installer was built against.
33
- const MCP_VERSION =
34
- (SELF.dependencies && typeof SELF.dependencies[MCP_PKG] === "string"
46
+ // The dev placeholder every derived, publishable version string carries in
47
+ // the working tree (R-2, quick-260819-tsz). Defined authoritatively as
48
+ // `DEV_PLACEHOLDER` in `.claude/mcp/vice/version.ts` -- repeated here as a
49
+ // literal, NOT imported, because this package deliberately ships without
50
+ // that seam file (see the comment above) and targets node >= 18, which
51
+ // cannot type-strip a `.ts` import the way the vice-mcp package's own
52
+ // node >= 22.18 runtime can. This is the same disclosed divergence as
53
+ // `SELF_VERSION` above: one literal, kept in sync by hand, documented here
54
+ // so a future edit to the seam's placeholder is not missed.
55
+ const MCP_DEV_PLACEHOLDER = "0.0.0-dev";
56
+ // Wire the project to the exact vice-mcp version this installer was built
57
+ // against -- EXCEPT when run from an unstamped dev checkout (MED-3):
58
+ // `installer/package.json`'s dependency pin is the permanent working-tree
59
+ // placeholder outside a CI-stamped publish job, and `@henols/vice-mcp` at
60
+ // that literal version will never exist on the npm registry. Silently
61
+ // writing it into a consumer's .mcp.json would 404 every time Claude Code
62
+ // tries to launch the server, with no error until the user actually tries
63
+ // to use it -- strictly worse than falling back to "latest". Fall back AND
64
+ // warn loudly, so a developer testing the installer locally knows their
65
+ // .mcp.json is unpinned rather than discovering it via a silent 404 later.
66
+ const MCP_VERSION_RAW =
67
+ SELF.dependencies && typeof SELF.dependencies[MCP_PKG] === "string"
35
68
  ? SELF.dependencies[MCP_PKG].replace(/^[\^~]/, "")
36
- : SELF_VERSION);
69
+ : SELF_VERSION;
70
+ if (MCP_VERSION_RAW === MCP_DEV_PLACEHOLDER) {
71
+ console.error(
72
+ `c64-re-tools: WARNING -- running from an unstamped dev checkout (installer/package.json pins ` +
73
+ `${MCP_PKG}@${MCP_DEV_PLACEHOLDER}, the dev placeholder, not a real published version). ` +
74
+ `Falling back to "${MCP_PKG}@latest" in the generated .mcp.json instead of writing an ` +
75
+ `unresolvable pin. If you intended to test against a specific version, pass --vendor or fix ` +
76
+ `installer/package.json's dependency pin before packaging a release.`
77
+ );
78
+ }
79
+ const MCP_VERSION = MCP_VERSION_RAW === MCP_DEV_PLACEHOLDER ? "latest" : MCP_VERSION_RAW;
37
80
 
38
81
  function readJson(path) {
39
82
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henols/c64-re-tools",
3
- "version": "0.1.12",
3
+ "version": "0.2.1",
4
4
  "description": "Installer that adds the C64 reverse-engineering skills and the VICE emulator MCP server (@henols/vice-mcp) to a project.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,7 +44,7 @@
44
44
  "installer"
45
45
  ],
46
46
  "dependencies": {
47
- "@henols/vice-mcp": "0.1.12"
47
+ "@henols/vice-mcp": "0.2.1"
48
48
  },
49
49
  "scripts": {
50
50
  "sync-skills": "node scripts/sync-skills.mjs",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: acme-build
3
- description: Assemble Commodore 64 6510 assembly with the ACME cross assembler. Use when asked to assemble, build, compile or link .a/.asm 6502/6510 source, produce a C64 .prg, scaffold a new C64 program, list the symbols a program uses, or turn a .prg back into ACME source.
3
+ description: Assemble Commodore 64 6510 assembly with the ACME cross assembler. Use when asked to assemble, build, compile or link .a/.asm 6502/6510 source, produce a C64 .prg, scaffold a new C64 program, or list the symbols a program uses.
4
4
  ---
5
5
 
6
6
  # Assembling C64 source with ACME
@@ -13,10 +13,9 @@ A=.claude/skills/acme-build/scripts/acme.mjs # from the repo root
13
13
  node $A new game.asm # scaffold a C64 program
14
14
  node $A build game.asm # assemble -> .prg .sym .vs .rep
15
15
  node $A sym game.asm # the symbols the program uses
16
- node $A disasm game.prg # object code back into ACME source
17
16
  ```
18
17
 
19
- The script wraps `acme` and `toacme` and nothing else — **assembling only**. Running
18
+ The script wraps `acme` and nothing else — **assembling only**. Running
20
19
  the result on a C64 belongs to the emulator skills (`acme.mjs:3-4` says so, and the
21
20
  absent `run` verb is not an omission). It contacts nothing.
22
21
 
@@ -133,57 +132,43 @@ you also assemble by hand, so these stay recognised as mnemonics.
133
132
 
134
133
  ## Disassembly
135
134
 
136
- ```bash
137
- node $A disasm game.prg
138
- ```
139
- ```
140
- game.dis.a: 28 lines
141
- Read it as a linear decode: trust the instruction stream, and
142
- treat strings, tables and the BASIC stub as data. To reassemble,
143
- define the out-of-range labels it emits (Ld020, Lffd2, ...) and
144
- indent its illegal-opcode lines to the operand column.
145
- ```
146
-
147
- The default output is `<stem>.dis.a` — one more `.a` file the agent's Read tool
148
- refuses (see Troubleshooting). Pass a second positional ending `.asm` for an
149
- agent-readable listing instead (same stdout shape, `game.dis.asm: 28 lines` in
150
- place of the first line):
135
+ This skill does not disassemble. Static disassembly of a `.prg` or flat 64K image
136
+ is a **required prerequisite** of this plugin, not an optional accelerator:
137
+ regenerator2000, reached through
151
138
 
152
139
  ```bash
153
- node $A disasm game.prg game.dis.asm
140
+ npx -y @henols/vice-mcp r2000 export-asm game.prg # npm installs
141
+ node <plugin-root>/.claude/mcp/vice/vice-proxy.ts r2000 export-asm game.prg # in-repo/plugin
154
142
  ```
155
143
 
156
- `game.dis.asm` itself then holds:
144
+ A recursive-descent disassembler with an auto-analyzer does not render strings,
145
+ tables and the BASIC stub as instructions, so there are no out-of-range labels
146
+ to hand-define and no illegal-opcode lines to re-indent — the caveats this
147
+ section used to carry were structural to a flat linear decoder and do not
148
+ apply here. The exported source is verified reassemblable by a real ACME
149
+ via `vice-mcp r2000 verify` (evidence:
150
+ `.planning/phases/10-adoption-boundaries-automated-bootstrap-and-the-removal/evidence/10-verify-transcript.txt`).
151
+ `c64-program-recon` points at this same route; it is not restated there.
157
152
 
158
- ```
159
- *=$0801
160
- L0801 !by$0b;ANC# <- BASIC stub, read as data
161
- L0802 php
162
- L0805 SHX L3032, y
163
- ...
164
- L080d lda #$00 <- code, decoded correctly
165
- L080f sta Ld021
166
- L0812 lda #$05
167
- L0814 sta Ld020
168
- L081e jsr Lffd2
169
- L0824 rts
170
- L0825 pha <- PETSCII string, read as data
171
- ```
153
+ ## Setup
172
154
 
173
- Read it as a linear decode: the instruction stream is accurate, and strings,
174
- tables and the BASIC stub appear as instructions interpret those regions as
175
- data. To reassemble the listing, define the out-of-range labels it emits
176
- (`Ld020`, `Lffd2`) and indent its illegal-opcode lines to the operand column.
155
+ `acme` on `$PATH` is the **only** requirement. The scaffold that `new` writes
156
+ assembles against a bare install with no standard hardware-register library
157
+ that's deliberate: neither a plain `~/.local/bin/acme` build nor the Debian
158
+ trixie `apt` candidate ships one, so a scaffold that depended on it would fail
159
+ to assemble on a fresh install (Phase 8.1 FINDING-A1).
177
160
 
178
- ## Setup
161
+ `$ACME` and the wrapper's auto-probe (`$ACME`, `/usr/local/share/acme`,
162
+ `/usr/share/acme`, `/usr/lib/acme`, `~/.acme`) still exist and still matter —
163
+ but only for **your own** sources that use angle-bracket includes (see
164
+ "Writing source" above), not for the scaffold. If you have that library
165
+ somewhere, point `$ACME` at its directory and angle-bracket includes work as
166
+ before; if you don't, the scaffold doesn't need it.
179
167
 
180
- Put `acme` and `toacme` on `$PATH`. The wrapper auto-probes `$ACME`,
181
- `/usr/local/share/acme`, `/usr/share/acme`, `/usr/lib/acme` and `~/.acme` itself,
182
- so `$ACME` only needs setting by hand when calling `acme` directly. Verified live
183
- in this container on 2026-08-04: the library resolves to `/usr/local/share/acme`
184
- (`/usr/share/acme` doesn't exist), ACME release 0.97 "Zem" (31 Jan 2021) at
185
- `/usr/bin/acme`. **Confidence: HIGH** — read off `acme --version` and the probe
186
- result, not off a package manifest.
168
+ Re-checked against ACME release 0.97 "Zem" (31 Jan 2021). CI now assembles
169
+ the shipped scaffold on every build with `$ACME` cleared (the "Assemble the
170
+ acme-build scaffold (library-free)" step in `.github/workflows/ci.yml`), so
171
+ this claim is re-checkable rather than a one-machine observation.
187
172
 
188
173
  Copy `acme.mjs` into any project's `.claude/skills/acme-build/scripts/`, and
189
174
  `template.a` into `.claude/skills/acme-build/`, to use this elsewhere.
@@ -197,14 +182,15 @@ This one turns source into bytes. It does not restate what the others carry.
197
182
  | Where to start on an unknown program, and which address to read next | `c64-program-recon` |
198
183
  | What a specific address or bit means, or annotating a listing | `c64-memory-mapping` — `node … lookup '$D018'` |
199
184
  | A verified 64K image, or comparing two captures | `c64-ram-capture` |
200
- | **Source in, `.prg` out and a first-pass dead listing back** | here |
185
+ | Static disassembly of a `.prg` or flat image | `vice-mcp r2000 export-asm` (see Disassembly above) |
186
+ | **Source in, `.prg` out** | here |
201
187
 
202
188
  ## References
203
189
 
204
190
  | File | Covers |
205
191
  |---|---|
206
192
  | `scripts/acme.mjs` | The driver. Its comments are the contract for every flag above |
207
- | `template.a` | The scaffold `new` writes: BASIC stub with a computed `SYS`, the three `!source` libraries, no `!to` |
193
+ | `template.a` | The scaffold `new` writes: BASIC stub with a computed `SYS`, five local hardware constants (no library needed), no `!to` |
208
194
 
209
195
  Findings that make RE faster go in `.planning/RE-FINDINGS.md` **at the moment you
210
196
  find them**, graded with `Evidence:` and `Confidence:`. Promote by re-logging with
@@ -205,22 +205,6 @@ function cmdNew(argv) {
205
205
  console.log(`next: node ${selfPath()} build ${path}`);
206
206
  }
207
207
 
208
- // `toacme` ships with ACME and turns object code back into ACME source.
209
- function cmdDisasm(argv) {
210
- const src = argv[0];
211
- if (!src) die("usage: disasm <file.prg> [out.a]");
212
- const out = argv[1] || src.replace(/\.prg$/i, "") + ".dis.a";
213
- const r = spawnSync("toacme", ["object", src, out], { encoding: "utf8" });
214
- if (r.error) die("install the ACME cross assembler and put `toacme` on PATH");
215
- if (r.status !== 0) die(`toacme: ${(r.stderr || r.stdout).trim()}`);
216
- const n = readFileSync(out, "utf8").split("\n").filter((l) => /^L[0-9a-f]{4}/.test(l)).length;
217
- console.log(`${out}: ${n} lines`);
218
- console.log("Read it as a linear decode: trust the instruction stream, and");
219
- console.log("treat strings, tables and the BASIC stub as data. To reassemble,");
220
- console.log("define the out-of-range labels it emits (Ld020, Lffd2, ...) and");
221
- console.log("indent its illegal-opcode lines to the operand column.");
222
- }
223
-
224
208
  // ------------------------------------------------------------------ options
225
209
 
226
210
  function parseOpts(argv) {
@@ -247,14 +231,13 @@ function parseOpts(argv) {
247
231
  // --------------------------------------------------------------------- main
248
232
 
249
233
  const [cmd, ...rest] = process.argv.slice(2);
250
- const VERBS = { new: cmdNew, build: cmdBuild, sym: cmdSym, disasm: cmdDisasm };
234
+ const VERBS = { new: cmdNew, build: cmdBuild, sym: cmdSym };
251
235
  if (!cmd || !VERBS[cmd]) {
252
236
  console.log(`usage: node ${selfPath()} <command> [options]
253
237
 
254
- new <file.a> scaffold a C64 program (BASIC stub + libs)
238
+ new <file.a> scaffold a C64 program (BASIC stub, no libraries needed)
255
239
  build <file.a> assemble -> .prg .sym .vs .rep
256
240
  sym <file.a> list the symbols the program uses
257
- disasm <file.prg> [out.a] turn object code back into ACME source
258
241
 
259
242
  options: -o FILE --out-dir DIR -f FORMAT --setpc ADDR -DSYM=VAL -I DIR
260
243
  --no-report --json`);
@@ -2,11 +2,25 @@
2
2
  ; Build: node .claude/skills/acme-build/scripts/acme.mjs build THIS.a
3
3
  ; No !to here on purpose - the driver passes -o, and having both makes ACME
4
4
  ; warn "Output file already chosen" and silently ignore the !to.
5
+ ;
6
+ ; No angle-bracket library includes here on purpose. Neither documented ACME
7
+ ; provisioning route ships the standard hardware-register library - a bare
8
+ ; `~/.local/bin/acme` install and the Debian trixie `apt` candidate were both
9
+ ; verified to lack it (Phase 8.1 FINDING-A1) - so a scaffold that depends on
10
+ ; it fails to assemble on a fresh install. The five constants below are
11
+ ; exactly what this scaffold's body uses, defined locally instead. If you
12
+ ; want the full library for your own sources' angle-bracket includes, set
13
+ ; $ACME to a directory containing that library (with vic.a inside it);
14
+ ; acme.mjs's findAcmeLib() already probes for it and passes it through to
15
+ ; the child.
5
16
 
6
17
  !cpu 6510 ; C64's CPU: legal 6502 + the illegal opcodes
7
- !source <cbm/c64/vic.a> ; vic_* registers, viccolor_* constants
8
- !source <cbm/c64/kernal.a> ; k_* KERNAL entry points ($ff81-$fff5)
9
- !source <cbm/c64/cia1.a> ; cia1_* keyboard / joystick 2
18
+
19
+ !address vic_cborder = $d020 ; VIC-II border color register
20
+ !address vic_cbg = $d021 ; VIC-II background color register
21
+ viccolor_BLACK = 0
22
+ viccolor_GREEN = 5
23
+ !address k_chrout = $ffd2 ; KERNAL: print one PETSCII char
10
24
 
11
25
  * = $0801 ; start of BASIC RAM
12
26
 
@@ -186,6 +186,41 @@ were expecting. Because it mutates the repo, `memmap` belongs behind a GSD
186
186
  command (`/gsd-quick`), per this project's GSD Workflow Enforcement rule — it is
187
187
  not a read-only lookup like `lookup` and `annotate`.
188
188
 
189
+ ## Feeding the enum generator
190
+
191
+ `memmap.json`'s structured `bits` entries are the source of the curated register bit-name table used
192
+ to generate program-specific enums for regenerator2000's annotation store (R2000-13): register
193
+ writes disassemble as `lda #D011_YSCROLL3_ROW25_SCREENON_TEXT` instead of a bare `#$1b`. The
194
+ generator is `.claude/mcp/vice/r2000-regbits-gen.ts`; its committed output is
195
+ `.claude/mcp/vice/r2000-regbits.json`; and that output is **digest-pinned** to `memmap.json` — a
196
+ `node r2000-regbits-gen.ts` run compares its own fresh build against the committed file, and CI fails
197
+ if `memmap.json` changed without a re-run.
198
+
199
+ **The honest gap:** only 29 of this file's 959 entries carry a structured `bits` array. `$D015`,
200
+ `$D017`, `$D01A` and `$D01B`–`$D01D` — the sprite-plane bitmask registers a real game writes
201
+ constantly — are **not** among those 29, so the enum generator supplies them from its own curated
202
+ override table (`OVERRIDES` in `r2000-regbits-gen.ts`), not from this file. Widening `memmap.json`'s
203
+ `io` parser (or repairing the OCR damage already present in some `bits` prose, e.g. a letter `O` for
204
+ the digit `0`) so those registers get a real structured entry here is separate work belonging to this
205
+ skill, not the generator.
206
+
207
+ **Installing those bit names into a project's own disassembly:** the table above only builds
208
+ `r2000-regbits.json` — turning a specific project's register *writes* into named enum variants is a
209
+ separate, later step, once a `.regen2000proj` already exists (`r2000 bootstrap`, see
210
+ `c64-program-recon`):
211
+
212
+ ```bash
213
+ npx -y @henols/vice-mcp r2000 gen-enums game.regen2000proj # npm install
214
+ node <plugin-root>/.claude/mcp/vice/vice-proxy.ts r2000 gen-enums game.regen2000proj # in-repo/plugin
215
+ ```
216
+
217
+ `r2000 gen-enums` requires an EXISTING `.regen2000proj` — it does not bootstrap one from a raw
218
+ input. It reads the project's own disassembly, creates one enum variant per DISTINCT value actually
219
+ written at each matching immediate-load address (named from the curated table above), and prints
220
+ total/paired/unpaired register-store counts plus a per-enum variant count. It exits non-zero, naming
221
+ the reason, when either of its two internal search passes hits its own 10000-row ceiling — pass
222
+ `--max-results` to raise that ceiling for a program whose store exceeds it.
223
+
189
224
  ## Troubleshooting
190
225
 
191
226
  | Symptom | Fix |
@@ -115,6 +115,130 @@ same IRQ entry that phase-01 live work established independently (chain `$1103
115
115
  The method reproduces a known-good result from a static image with no emulator running, and the
116
116
  `$1116` pair is new — see `references/control-flow.md` § 2. **Confidence: HIGH** for steps 1-2.
117
117
 
118
+ ## Writing findings into the annotation store
119
+
120
+ Recon's findings are not memory-map prose written once and left to rot — they are entries in a
121
+ queryable annotation store, and the Markdown memory map is a *generated view* of that store (D-24),
122
+ not something you hand-edit yourself.
123
+
124
+ **Open or bootstrap the store**, then hand its path to every call that follows:
125
+
126
+ ```bash
127
+ npx -y @henols/vice-mcp r2000 bootstrap game.prg # npm install
128
+ node <plugin-root>/.claude/mcp/vice/vice-proxy.ts r2000 bootstrap game.prg # in-repo/plugin
129
+ ```
130
+
131
+ Every `r2000_*` tool takes an explicit `project` path pointing at the resulting `.regen2000proj`
132
+ (D-19) — there is no ambient session state naming the store, so which project a call touched is
133
+ always visible in the transcript.
134
+
135
+ **Write findings with the named tools, not a Markdown row:**
136
+
137
+ | Tool | Use for |
138
+ |---|---|
139
+ | `r2000_set_label_name` | Naming a routine or table (`init_screen`, `sprite_table`) |
140
+ | `r2000_set_data_type` | Classifying a block (`code`, `byte`, `address`, `petscii`, …) |
141
+ | `r2000_add_scope` | Marking a handler's extent as a lexical scope |
142
+ | `r2000_set_comment` | Recording the evidence — the carrier for the confidence grade below |
143
+ | `r2000_batch_execute` | Bulk annotation, 5+ independent calls at once — a real memory map is dozens of labels/comments/block ranges, and batching is what makes that affordable under the per-call spawn-load-mutate-save-exit lifecycle |
144
+
145
+ **Grade with the confidence prefix.** Lead every evidence comment with exactly one of these five
146
+ bracket tokens (quoted verbatim from `r2000-confidence.ts`, the parser's own source of truth):
147
+
148
+ `[confirmed-code]` (confirmed code), `[probable-code]` (probable code), `[confirmed-data]`
149
+ (confirmed data), `[probable-data]` (probable data), `[unknown]` (unknown).
150
+
151
+ A typo in the bracket token — wrong case, an underscore, a plural, stray whitespace — **fails
152
+ loudly**; it does not silently degrade into an ungraded comment. As with `RE-FINDINGS.md`, do not
153
+ promote a row by editing its grade in place: re-verify and restate the evidence with a fresh
154
+ `r2000_set_comment` call, so the record of when something stopped being a guess survives.
155
+
156
+ **Query instead of re-deriving.** `r2000_get_symbols`, `r2000_get_comments`, `r2000_get_blocks` and
157
+ `r2000_get_cross_references` answer straight from the store. `r2000_search_disassembly` searches
158
+ labels, comments and instructions together — but `max_results` is **REQUIRED** on this surface,
159
+ because regenerator2000's own default is 50 and silently truncates a full-program pass. The query
160
+ this whole workflow exists to make cheap:
161
+
162
+ > "Show me everything still `[unknown]`" → `r2000_search_disassembly` with `query: "[unknown]"` and
163
+ > an explicit `max_results` set above your program's comment count.
164
+
165
+ (The composite address-details lookup is deliberately not on this surface — D-32, a 64K-project
166
+ defect filed upstream — its answer is reachable as a combination of the tools above.)
167
+
168
+ ### Take names to the running machine, and bring live findings back
169
+
170
+ The store and the running emulator are not two independent destinations for a name — writing one
171
+ into the store and discovering one live are two legs of **one loop**, in this order, matching how
172
+ `R2000-14`/`R2000-15` were actually proven (see Phase 11's live walkthrough,
173
+ `evidence/criterion4/WALKTHROUGH.md`):
174
+
175
+ 1. **Export what the store already knows.** `r2000 export-lbl <project>` writes `al C:xxxx .Name`
176
+ lines that `stock-symbols.ts`'s own parser accepts — the verb reads the written file back
177
+ through that same parser before it reports success, never trusting a regenerator2000 exit code
178
+ alone.
179
+
180
+ ```bash
181
+ npx -y @henols/vice-mcp r2000 export-lbl game.regen2000proj # npm install
182
+ node <plugin-root>/.claude/mcp/vice/vice-proxy.ts r2000 export-lbl game.regen2000proj # in-repo/plugin
183
+ ```
184
+
185
+ 2. **Load it into the running machine — `vice_symbols_load`, exactly once.** Load that `.lbl` file
186
+ into the live emulator with `vice_symbols_load`. Call it **exactly once** per regenerated file:
187
+ it REPLACES the machine's symbol table rather than merging into it, so loading an older export a
188
+ second time after the store has moved on would silently discard the newer names.
189
+ 3. **Discover something live the static pass could not, then write it to the store first.**
190
+ Disassembling or reading the running machine (`vice_disassemble`, a checkpoint hit, …) can turn
191
+ up a name the static store never had. Write it with `r2000_set_label_name` *before* regenerating
192
+ anything — the store is the merge point (D-29), not your own notes.
193
+ 4. **Regenerate the whole `.lbl` and bring it back with `import-lbl`, never an incremental patch.**
194
+ `r2000 import-lbl <project> <lbl>` imports an externally-produced `.lbl` file into the project,
195
+ and reports whether the import was **disk-verified** — re-read from disk in a fresh process,
196
+ never trusted from the child's own success text alone.
197
+
198
+ ```bash
199
+ npx -y @henols/vice-mcp r2000 import-lbl game.regen2000proj discovered.lbl # npm install
200
+ node <plugin-root>/.claude/mcp/vice/vice-proxy.ts r2000 import-lbl game.regen2000proj discovered.lbl # in-repo/plugin
201
+ ```
202
+
203
+ Two traps: `export-lbl` exports **USER** labels only — the auto-generated `a_D011`/`e_FFD2`
204
+ externals never appear in the written file. And both verbs require an EXISTING `.regen2000proj`;
205
+ neither one bootstraps a project from a raw input.
206
+
207
+ `r2000 gen-enums` — turning register writes into named enum variants — is documented in
208
+ `c64-memory-mapping`, alongside the `memmap.json` bit table it consumes.
209
+
210
+ **Generate the memory map; do not hand-author it.** Fill in the provenance sidecar (schema and a
211
+ filled example live in `templates/memory-map.template.md`), then:
212
+
213
+ ```bash
214
+ npx -y @henols/vice-mcp r2000 render-memmap game.regen2000proj --provenance sidecar.json
215
+ node <plugin-root>/.claude/mcp/vice/vice-proxy.ts r2000 render-memmap game.regen2000proj --provenance sidecar.json
216
+ ```
217
+
218
+ Add `--check` to detect drift — either a hand edit to the rendered file, or a store change since it
219
+ was last rendered. The rendered file carries a generated-file banner; treat it like every other
220
+ generated artifact in this repo and never hand-edit it.
221
+
222
+ ## Static disassembly
223
+
224
+ Turning a `.prg` or a flat 64K image into ACME source, offline, is not part of this
225
+ skill's own method — it is a separate route:
226
+
227
+ ```bash
228
+ npx -y @henols/vice-mcp r2000 export-asm game.prg # npm installs
229
+ node <plugin-root>/.claude/mcp/vice/vice-proxy.ts r2000 export-asm game.prg # in-repo/plugin
230
+ ```
231
+
232
+ This is **static**, over a file on disk — `vice_disassemble` (the live-RAM route
233
+ this skill's own table above uses) reads a running emulator's RAM at a checkpoint
234
+ instead. The two are complementary: reach for the static route before the emulator
235
+ is even running, and for `vice_disassemble` once you have a live checkpoint to
236
+ decode from.
237
+
238
+ Extracting from a `.d64` image: name the file inside the image explicitly. The
239
+ tool lists the directory and refuses rather than guess (D-02) — a guess could
240
+ analyse a cracktro or loader stub instead of the game.
241
+
118
242
  ## Before you touch the emulator
119
243
 
120
244
  Two hazards cost this project real sessions. Both are in `references/observation-hazards.md`; these
@@ -137,7 +261,8 @@ This one is the route between the stations. It does not restate what the others
137
261
  |---|---|
138
262
  | A verified 64K image, or comparing two captures | `c64-ram-capture` |
139
263
  | What a specific address or bit means | `c64-memory-mapping` — `node … lookup '$D018'` |
140
- | Assembling, or a first-pass dead listing | `acme-build` |
264
+ | Assembling | `acme-build` |
265
+ | Static disassembly of a `.prg` or flat image | `vice-mcp r2000 export-asm` (see above) |
141
266
  | Whether a byte is original or cracker-changed | `c64-provenance-diff` |
142
267
  | The emulator stopped moving — wedged, self-trapped, or respawned | `vice-wedge-triage` |
143
268
  | **Which address to read next, and what the answer rules out** | here |
@@ -152,7 +277,7 @@ This one is the route between the stations. It does not restate what the others
152
277
  | `references/observation-hazards.md` | Every way a live read gives a wrong answer. **Read before driving.** |
153
278
  | `references/tool-selection.md` | Which `mcp__plugin_c64-re-tools_vice__*` call answers which question, and what to delegate |
154
279
  | `references/reconstruction.md` | Binary inclusion, behavioural-equivalence correctness bar, SMC labels, label vocabulary |
155
- | `templates/memory-map.template.md` | Region map with per-row confidence grading |
280
+ | `templates/memory-map.template.md` | `render-memmap`'s provenance sidecar schema and the confidence vocabulary — the rendered map itself is generated, not hand-authored |
156
281
 
157
282
  Findings that make RE faster go in `.planning/RE-FINDINGS.md` **at the moment you find them**,
158
283
  graded with `Evidence:` and `Confidence:`. Promote by re-logging with the new evidence, never by
@@ -168,5 +293,5 @@ editing a grade in place. File-changing work enters through a GSD command (`/gsd
168
293
  | 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. |
169
294
  | Computed mode is "INVALID — screen goes black" | You caught the registers mid-update inside a raster split. Re-read. |
170
295
  | The emulator looks dead | Enumerate armed checkpoints before anything else. See hazard 2. |
171
- | `vice_keyboard_type` does nothing | The game polls `$DC00`/`$DC01` directly. Use `vice_keyboard_matrix`. |
296
+ | `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). |
172
297
  | Two captures of the same checkpoint differ | Expected. Full-64K identity is impossible in principle; use `c64-ram-capture`'s drift rules. |
@@ -87,6 +87,11 @@ address to checkpoint when the emulator is next available — press RESTORE with
87
87
  until the line is released, so it is a press→release **edge**), then `vice_machine_reset` soft and
88
88
  hard, and record where the PC actually lands.
89
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.
94
+
90
95
  **Evidence:** derived mechanically from six three-run-verified captures; every value identical
91
96
  across all three runs of its release, so none of it is drift.
92
97
  **Confidence:** HIGH for the values and for the cross-release divergence. The *interpretation* of
@@ -156,6 +161,10 @@ at a title screen and again in gameplay, diff the two captures, and look for a s
156
161
  changed in zero page or low RAM. `vice_memory_compare` narrows this; `c64-ram-capture` § Compare
157
162
  two captures gives the volatility rules that stop you chasing drift.
158
163
 
164
+ On stock, only `mode: 'ranges'` is served — capture the two states at different points in time and
165
+ compare two live ranges. `mode: 'snapshot'` is refused with an explanatory message; there is no
166
+ memory-only snapshot producer on either backend.
167
+
159
168
  ## Verified against this project — 2026-08-04
160
169
 
161
170
  Running `derive.mjs vectors` cold on both releases' `*-gameentry-run1.bin` returns `$01` = `$40`
@@ -71,3 +71,9 @@ hardware registers are what the game uses.
71
71
  `vice_sprite_get` / `vice_sprite_inspect` do the pointer arithmetic and the multicolor bit-pair
72
72
  unpacking. Verify what they return once against a hand-resolved pointer — `derive.mjs sprites`
73
73
  gives you that hand resolution — then trust them.
74
+
75
+ On stock, `vice_vicii_get_state`'s `$D018` pointers are reported **bank-relative**;
76
+ `vice_sprite_get` resolves the absolute `screenBase` and per-sprite `dataAddress` for you.
77
+ `vice_sprite_inspect`'s ASCII grid is the sprite's native 24x21 (hi-res) or 12x21 (multicolour)
78
+ **data block** — it is **not** scaled by the `$D017`/`$D01D` expansion bits, so a sprite shown
79
+ double-size on screen still renders at its native resolution in the grid.
@@ -80,6 +80,21 @@ 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
84
+ no argument able to override it — **VERIFIED**, asserted on the wire body by a regression test.
85
+ Whether the emulator's own `MEM_GET` read path actually honours that flag for
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
93
+ `registerBank`/`dataBank`), read through the emulator's own `io`/`ram` banks, so it stays valid
94
+ 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
+ have been `$37`: those bytes may be the RAM underneath the I/O area, not registers.
97
+
83
98
  ## 4. The keyboard buffer is not how games read keys
84
99
 
85
100
  **Evidence: live. Confidence: HIGH. Cost: an afternoon.**
@@ -88,6 +103,14 @@ Games and cracks poll the `$DC00`/`$DC01` matrix directly, bypassing the KERNAL
88
103
  `vice_keyboard_type` is invisible to them. Use `vice_keyboard_matrix`, and hold a key across a
89
104
  gate by releasing it at the trigger checkpoint, never earlier.
90
105
 
106
+ **`vice_keyboard_matrix` requires the fork backend.** The binary monitor's `KEYBOARD_FEED` (0x72)
107
+ only injects PETSCII text into the KERNAL keyboard buffer; the emulator recomputes CIA port B from
108
+ 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.
113
+
91
114
  ## 5. Most state reads pause the emulator and do not resume it
92
115
 
93
116
  Read all state first, poll with `vice_ping` (the non-pausing poll), and resume **exactly once** at
@@ -53,12 +53,20 @@ One that programs `$DC04-$DC07` and enables timer A runs its own timebase.
53
53
  writing `$DD00` is usually talking to the drive.
54
54
  - **`$DC0D`/`$DD0D` clear the interrupt flags on read**, the same shape as `$D01E`/`$D01F`. Reading
55
55
  one steals an interrupt the game was about to service. Prefer `vice_cia_get_state`. The VICE
56
- monitor's exact behaviour here is **unverified** — verify, don't assume.
56
+ monitor's exact behaviour here is **unverified** — verify, don't assume. On stock,
57
+ `vice_cia_get_state` reports the **read** side of `$xx0D` as `interruptStatus` and marks the
58
+ write-side enable mask `unavailable` — the two share one address with different meanings, so a
59
+ reader looking for "which interrupts are enabled" is not silently handed the flags that have
60
+ fired.
57
61
  - **Direct `$DC00`/`$DC01` polling is the norm, and it defeats `vice_keyboard_type`.**
58
62
  **Evidence: live, established on this project during recovery work. Confidence: HIGH. Cost: an
59
63
  afternoon.** Games and cracks bypass the KERNAL keyboard buffer and read the matrix directly.
60
64
  Assume it until shown otherwise, and drive input with `vice_keyboard_matrix` or the joystick
61
- tools instead.
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.
62
70
 
63
71
  ## Finding input handling from the observable side
64
72
 
@@ -14,11 +14,12 @@ usage, not measured). Individual rows that have since been exercised live are ma
14
14
  | What does the handler at this vector do? | `vice_disassemble` — the emulator's own decoder, not a dead listing |
15
15
  | Is this really the main loop? | `vice_checkpoint_add` + `vice_run_until` + `vice_registers_get` — fires once per frame ⇒ proven |
16
16
  | What code writes this? | `vice_watch_add` — finds **writers**. Best targets: `$D018`, VM+`$03F8`, `$D404` |
17
- | Whole-chip state without the read hazards | `vice_vicii_get_state` / `vice_sid_get_state` / `vice_cia_get_state`**prefer these over raw register reads** |
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` and regenerator2000 output share this channel |
21
- | Is the machine wedged, or did it stop itself? | `vice_diagnose` five-state verdict with its evidence. **Reachable and proxy-intercepted as of 2026-08-04** (verified live). Triage tree: `vice-wedge-triage` |
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` and regenerator2000 output share this channel |
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` |
22
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 |
23
24
  | 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` |
24
25
 
@@ -29,15 +30,18 @@ usage, not measured). Individual rows that have since been exercised live are ma
29
30
  | What does address X mean? | the `c64-memory-mapping` skill — `node … lookup '$D018'`. **Do not restate its tables.** |
30
31
  | Is this byte original or cracker-changed? | the `c64-provenance-diff` skill |
31
32
  | A verified 64K image, or comparing two captures | the `c64-ram-capture` skill |
32
- | Fast first-pass listing | `toacme` — decodes data as instructions; never the deliverable |
33
- | Traced disassembly with code/data separation | regenerator2000 — still MEDIUM per `STACK.md`; its first real run is its verification |
33
+ | Traced disassembly with code/data separation | regenerator2000, via `vice-mcp r2000 export-asm` — a recursive-descent disassembler with an auto-analyzer; verified live: its `--verify` run reassembled byte-identically through a real ACME for both a `.prg` and a flat 64K image (`.planning/phases/10-adoption-boundaries-automated-bootstrap-and-the-removal/evidence/10-verify-transcript.txt`) |
34
34
 
35
35
  ## Three traps in this table
36
36
 
37
- **`vice_run_until` has no working timeout.** Its schema documents `cycles` as *"not yet
38
- implemented"*, so a run to an address the program never reaches has nothing to bound it and looks
39
- exactly like a wedged emulator. Prefer `vice_checkpoint_add` + a bounded poll when the address is
40
- a hypothesis rather than a certainty. **Confidence: MEDIUM** read off the schema, not reproduced.
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.
41
45
 
42
46
  **`vice_diagnose` leaves the machine paused.** When it measures a cycle bracket it resumes the
43
47
  machine once or twice and then leaves it **paused** — resuming is your own next call. And a
@@ -1,62 +1,83 @@
1
- # Memory map — <program name> @ <checkpoint where this was captured>
2
-
3
- Capture: `<path to the 64K image>` · SHA-256 `<hash>`
4
- `$01` = `<value>` · VIC bank `<n>` (`$DD00` = `<value>`) · video standard `<PAL/NTSC>`
5
- Live vector pair: `<$0314/$0315 or $FFFE/$FFFF>` → `<handler>`
6
-
7
- Every row carries a confidence. Do not promote a row by editing its grade — re-verify and restate
8
- the evidence, so the record of when something stopped being a guess survives.
9
-
10
- | Range | Contents | Confidence | Evidence |
11
- |---|---|---|---|
12
- | `$0000-$00FF` | Zero page — game variables | | |
13
- | `$0100-$01FF` | Stack | CONFIRMED | hardware |
14
- | `$0200-$03FF` | KERNAL work area / vectors | | |
15
- | `$0400-$07E7` | Screen RAM (if VM resolves here) | | |
16
- | `$0801-$` | | | |
17
- | `$D800-$DBFF` | Colour RAM | CONFIRMED | hardware, not banked |
18
- | `$E000-$FFFF` | RAM under KERNAL (HIRAM=0) or KERNAL ROM | | |
19
-
20
- Confidence vocabulary — the project's HIGH / MEDIUM / LOW scale, applied to classification:
21
-
22
- | Grade | Means |
23
- |---|---|
24
- | **confirmed code** | Executed during tracing, PC observed inside it |
25
- | **probable code** | Reachable through a `JSR`/`JMP`/vector, not yet observed executing |
26
- | **confirmed data** | Never hit as an instruction stream across full gameplay coverage |
27
- | **probable data** | Indexed-load target, or matches a data shape (sprite blocks, PETSCII, address tables) |
28
- | **unknown** | No reliable interpretation yet |
1
+ # Memory map generation
29
2
 
30
- Do not force an unknown range through a disassembler and record the output as code. A linear
31
- decode of data is silently wrong and contaminates everything downstream.
3
+ **The memory map is GENERATED, not hand-authored (D-24).** The store labels, comments, block
4
+ types and scopes written through the `r2000_*` tools described in `../SKILL.md` — is canonical. This
5
+ file used to be a fill-in-the-rows document; it is now the schema for the one input the generator
6
+ needs beyond the store itself, plus the confidence vocabulary that store comments carry.
32
7
 
33
- ## Graphics chain
8
+ Run the generator once findings are in the store:
34
9
 
35
- | What | Address | Derived from |
36
- |---|---|---|
37
- | VIC bank | | `$DD00` bits 0-1, inverted |
38
- | Screen RAM (VM) | | `$D018` bits 4-7 |
39
- | Charset / bitmap (CB) | | `$D018` bits 1-3 |
40
- | Mode | | `$D011` bits 5-6, `$D016` bit 4 |
41
- | Sprite pointers | | VM + `$03F8` |
10
+ ```bash
11
+ npx -y @henols/vice-mcp r2000 render-memmap game.regen2000proj --provenance sidecar.json
12
+ node <plugin-root>/.claude/mcp/vice/vice-proxy.ts r2000 render-memmap game.regen2000proj --provenance sidecar.json
13
+ ```
42
14
 
43
- Character ROM shadow checked: <yes/no CB in `$1000`/`$9000` of bank 0/2 means no charset in RAM>
15
+ Add `--check` to compare the rendered file on disk against a fresh render it exits non-zero and
16
+ prints the first differing line on **either** a hand edit to the rendered file **or** a store change
17
+ since it was last rendered. There is no way to "fix" drift by editing the rendered file directly:
18
+ the fix is always to re-run the generator (or, if the sidecar itself is stale, correct it and
19
+ re-run). The generated file carries a banner naming the store, the sidecar and a content digest —
20
+ do not strip it.
44
21
 
45
- ## Interrupts
22
+ ## The provenance sidecar
46
23
 
47
- | | Address | Notes |
24
+ Some facts belong to the **run** (which capture, which `$01`, which video standard) rather than to
25
+ any address, and the store has no address-keyed shape for them (D-27). They are supplied to the
26
+ renderer as a small JSON sidecar, hand-authored from `c64-ram-capture`'s and `derive.mjs`'s own
27
+ outputs and validated by the renderer — a missing or malformed key is a named error listing every
28
+ problem at once, never a `<placeholder>` silently rendered into a published document.
29
+
30
+ | Key | Type | Where it comes from |
48
31
  |---|---|---|
49
- | Live IRQ handler | | |
50
- | Raster positions | | one per `$D012` write on the way out of a handler |
51
- | NMI handler | | `$0318/$0319` or `$FFFA/$FFFB` |
52
- | Timebase | | `$DC0D` untouched raster; programmed own timebase |
32
+ | `capturePath` | string | The path to the captured 64K image, as given to `c64-ram-capture` |
33
+ | `captureSha256` | string, 64 hex chars | `compare.mjs digest`'s `sha256` proves which image the map describes |
34
+ | `port01` | string | `derive.mjs vectors`' `$01` value |
35
+ | `dd00` | string | `derive.mjs vic`'s `--dd00` input, i.e. the observed `$DD00` |
36
+ | `vicBank` | string | `derive.mjs vic` — VIC bank derived from `$DD00` bits 0-1, inverted |
37
+ | `screenRam` | string | `derive.mjs vic` — screen RAM derived from `$D018` bits 4-7 |
38
+ | `charsetOrBitmap` | string | `derive.mjs vic` — charset/bitmap derived from `$D018` bits 1-3 (note the char-ROM shadow case) |
39
+ | `mode` | string | `derive.mjs vic` — graphics mode derived from `$D011` bits 5-6 and `$D016` bit 4 |
40
+ | `videoStandard` | `"PAL"` or `"NTSC"` | Known from the capture's origin/hardware context |
41
+ | `liveVectorPair` | string | `derive.mjs vectors` — the live vector pair (`$0314/$0315` or `$FFFE/$FFFF`) |
42
+ | `vectorHandler` | string | The address the live vector pair points at, confirmed live at a checkpoint |
43
+ | `rasterPositions` | string array, optional | One entry per observed `$D012` write on the way out of the live IRQ handler; `derive.mjs sprites` where sprite coordinates are relevant |
44
+
45
+ A fully-filled example, with plausible values in place of placeholders — copy this shape, never the
46
+ literal values:
53
47
 
54
- ## Routines
48
+ ```json
49
+ {
50
+ "capturePath": "captures/game.raw",
51
+ "captureSha256": "3f8a1c9e2b7d4a6f0c5e8b2d9a1f4c7e6b3d0a9c8f5e2b1d4a7c0f3e6b9d2a5c",
52
+ "port01": "$40",
53
+ "dd00": "$06",
54
+ "vicBank": "1 ($4000-$7FFF)",
55
+ "screenRam": "$0400",
56
+ "charsetOrBitmap": "$1000 (ROM shadow)",
57
+ "mode": "text, multicolor off",
58
+ "videoStandard": "PAL",
59
+ "liveVectorPair": "$FFFE/$FFFF",
60
+ "vectorHandler": "$1103",
61
+ "rasterPositions": ["$F8", "$00"]
62
+ }
63
+ ```
55
64
 
56
- | Address | Provisional name | Confirmed by | Confidence |
57
- |---|---|---|---|
58
- | | `Maybe_` | | |
65
+ ## Confidence vocabulary
59
66
 
60
- ## Open questions
67
+ Every comment written into the store through `r2000_set_comment` that grades a finding leads with
68
+ one of these five bracket tokens (the parser in `r2000-confidence.ts` throws on anything that is
69
+ close but not exact — a typo never silently degrades into an ungraded comment):
70
+
71
+ | Grade | Bracket token | Means |
72
+ |---|---|---|
73
+ | **confirmed code** | `[confirmed-code]` | Executed during tracing, PC observed inside it |
74
+ | **probable code** | `[probable-code]` | Reachable through a `JSR`/`JMP`/vector, not yet observed executing |
75
+ | **confirmed data** | `[confirmed-data]` | Never hit as an instruction stream across full gameplay coverage |
76
+ | **probable data** | `[probable-data]` | Indexed-load target, or matches a data shape (sprite blocks, PETSCII, address tables) |
77
+ | **unknown** | `[unknown]` | No reliable interpretation yet |
78
+
79
+ Do not force an unknown range through a disassembler and record the output as code. A linear
80
+ decode of data is silently wrong and contaminates everything downstream.
61
81
 
62
- -
82
+ **Do not promote a row by editing its grade.** Re-verify and restate the evidence with a fresh
83
+ `r2000_set_comment` call, so the record of when something stopped being a guess survives.
@@ -231,7 +231,7 @@ addresses.
231
231
  | A verified 64K image, or proving two captures equivalent | `c64-ram-capture` |
232
232
  | Which address to read next, and what the answer rules out | `c64-program-recon` |
233
233
  | What a specific address or bit means | `c64-memory-mapping` — `node … lookup '$D018'` |
234
- | Assembling, or a first-pass dead listing | `acme-build` |
234
+ | Assembling | `acme-build` |
235
235
  | **Whether a byte is original, cracker-changed, or unknown** | here |
236
236
 
237
237
  Findings that make RE faster go in `.planning/RE-FINDINGS.md` **at the moment you
@@ -156,6 +156,11 @@ only after the provenance diff partitions loader from cracktro from game — see
156
156
  ## Find an entry point
157
157
 
158
158
  1. Press past any "hit any key" gate with `mcp__plugin_c64-re-tools_vice__vice_keyboard_matrix`.
159
+ **This call requires the fork backend** — the binary monitor's `KEYBOARD_FEED` only injects
160
+ PETSCII text into the KERNAL buffer and cannot drive the raw matrix. On stock, use
161
+ `vice_keyboard_type` / `vice_keyboard_petscii` when the gate reads the KERNAL buffer, or
162
+ `vice_joystick_set` when it polls the matrix directly; buffer injection stays invisible to a
163
+ program polling `$DC00`/`$DC01` itself.
159
164
  2. Step forward in batches with `mcp__plugin_c64-re-tools_vice__vice_execution_step`, reading
160
165
  `mcp__plugin_c64-re-tools_vice__vice_registers_get` after each batch.
161
166
  3. Stop when the program counter and the stack pointer both settle into a
@@ -257,6 +262,17 @@ Capture the power-on image as the very first action against a fresh machine, the
257
262
  idle-capture twice more and run `floor` over the set. State the result as a
258
263
  floor, not a complete set — more captures can only widen it.
259
264
 
265
+ ## Feeding the memory map's provenance sidecar
266
+
267
+ `c64-program-recon`'s generated memory map (`vice-mcp r2000 render-memmap`) takes a small provenance
268
+ sidecar as input, and this skill supplies one of its fields: `scripts/compare.mjs digest`'s `sha256`
269
+ and `size` become the sidecar's `captureSha256`, proving which image the rendered map describes. The
270
+ sidecar's other run-scoped keys (`port01`, `dd00`, `vicBank`, `screenRam`, `charsetOrBitmap`, `mode`,
271
+ `liveVectorPair`, `vectorHandler`, `rasterPositions`) come from `c64-program-recon`'s own
272
+ `derive.mjs` — **this skill does not emit the sidecar itself.** The sidecar is hand-authored from
273
+ those two skills' outputs and validated by the renderer, which throws naming every missing or
274
+ malformed key at once rather than rendering a document that silently carries a placeholder.
275
+
260
276
  ## Which skill does what
261
277
 
262
278
  This one owns the image and its identity. It does not restate what the others carry.
@@ -266,7 +282,7 @@ This one owns the image and its identity. It does not restate what the others ca
266
282
  | Which address to read next, and what the answer rules out | `c64-program-recon` |
267
283
  | Every way a live read gives a wrong answer | `c64-program-recon` — `references/observation-hazards.md`. **Read before driving.** |
268
284
  | What a specific address or bit means | `c64-memory-mapping` — `node … lookup '$D018'` |
269
- | Assembling, or a first-pass dead listing | `acme-build` |
285
+ | Assembling | `acme-build` |
270
286
  | Whether a byte is original or cracker-changed, and what `bucketed` means | `c64-provenance-diff` |
271
287
  | Whether the emulator is wedged, and whether it is safe to recycle | `vice-wedge-triage` |
272
288
  | **A verified 64K image, or proving two captures equivalent** | here |
@@ -5,8 +5,9 @@ description: Decide whether a VICE emulator that has stopped responding is genui
5
5
 
6
6
  # Triage a VICE that stopped moving
7
7
 
8
- **Four states look identical from outside, and the intuitive fix destroys a healthy machine in
9
- one of them.** Work the order below. Do not start with a remedy.
8
+ **On the fork, four states look identical from outside; on stock, it is five, and the intuitive
9
+ fix destroys a healthy machine in more than one of them.** Work the order below. Do not start with
10
+ a remedy.
10
11
 
11
12
  | State | Cheap tell | Safe action |
12
13
  |---|---|---|
@@ -14,14 +15,22 @@ one of them.** Work the order below. Do not start with a remedy.
14
15
  | **Stopped itself at your checkpoint** | An armed *stopping* checkpoint on the live IRQ path | Delete/disable the checkpoint. **Never recycle** |
15
16
  | **Crashed and respawned** | The proxy raises epoch drift on the next forwarded call | Void the run, reboot from scratch. Already handled for you |
16
17
  | **Genuinely wedged** | Two consecutive cycle brackets read exactly `0` | `vice_recycle` with a reason, as a last resort |
18
+ | **Monitor held elsewhere (stock only)** | A second client already holds this instance's single binary-monitor socket | Find the other holder. **Never recycle** — the instance is healthy, just claimed elsewhere |
17
19
 
18
20
  ```
19
- mcp__plugin_c64-re-tools_vice__vice_diagnose # one call, no arguments, answers which of the five it is
21
+ mcp__plugin_c64-re-tools_vice__vice_diagnose # one call, no arguments, answers which state it is
20
22
  ```
21
23
 
22
- `vice_diagnose` returns a closed five-verdict vocabulary `restarted`, `checkpoint_trap`,
23
- `wedged`, `stale_read_path`, `live` with the evidence that produced it. Read its schema for the
24
- contract; this skill is the judgement around it.
24
+ `vice_diagnose`'s verdict vocabulary differs by backend, because stock VICE's binary monitor
25
+ services exactly one client and the fork's non-pausing `vice_ping` has no stock equivalent (see
26
+ `docs/stock-vice-parity.md` D-03 for the full reasoning). The fork answers `restarted`,
27
+ `checkpoint_trap`, `wedged`, `stale_read_path`, `live`; stock answers `restarted`,
28
+ `checkpoint_trap`, `wedged`, `monitor_held_elsewhere`, `live`. Read the tool's own schema for the
29
+ exact contract on whichever backend is active — as of **07-16 (WR-07)** this instruction is
30
+ finally sound: `tools/list`'s advertised stock schema is the corrected stock manifest entry, not
31
+ the fork's synthetic literal it was silently overwritten by before. Stock `vice_diagnose` can also
32
+ answer a `diagnosis_unavailable` outcome when no verdict could be established at all — that is
33
+ **not** a sixth verdict; see the table below.
25
34
 
26
35
  ## The order
27
36
 
@@ -31,7 +40,13 @@ contract; this skill is the judgement around it.
31
40
  2. **Read the verdict, not the vibe.** Each verdict has exactly one correct response — the table
32
41
  below. A verdict is not a suggestion to try things.
33
42
  3. **`diagnose` leaves the machine paused** when it ran a bracket. Resuming is your own next call.
34
- Do not treat "still paused afterwards" as a symptom.
43
+ Do not treat "still paused afterwards" as a symptom. Every established verdict also reports
44
+ `machinePaused` plus `machinePausedSource` (07-15), so you can tell an actual observation from
45
+ an inference: `observed` means a wire `stopped`/`resumed`/`jam` event directly reported the
46
+ state; `structural` means it was inferred from the fact that every stock read halts the machine
47
+ (D-05), not from a specific event; `no_session` means no session was ever obtained (e.g. the
48
+ `monitor_held_elsewhere` verdict, or a `diagnosis_unavailable` acquisition failure) so no claim
49
+ about pause state is being made at all.
35
50
  4. **If the verdict is `wedged`, capture evidence before recovering.** `vice_recycle` requires a
36
51
  `reason`, and that string is written verbatim into a permanent, repo-tracked incident record
37
52
  under `.planning/incidents/` **before anything is killed**. That record is the evidence
@@ -43,11 +58,52 @@ contract; this skill is the judgement around it.
43
58
 
44
59
  | Verdict | What it means | Do |
45
60
  |---|---|---|
46
- | `live` | Cycles advanced | Resume and carry on. Suspect your own checkpoint conditions, not the emulator |
61
+ | `live` | Cycles advanced | Resume and carry on. Suspect your own checkpoint conditions, not the emulator — **unless `evidence.jamObserved` is true** (below) |
47
62
  | `checkpoint_trap` | The machine stopped **itself** at an armed checkpoint | `vice_checkpoint_delete` or `vice_checkpoint_toggle` it, or `vice_execution_step` past it, then re-run `diagnose`. **Recycling here destroys a healthy instance** |
48
63
  | `restarted` | The epoch changed — a crash-and-respawn already happened | The run is void. `c64-ram-capture` § Void a run gives the artifact procedure. Reboot from `vice_disk_attach` |
49
- | `stale_read_path` | Some reads move while others do not | Do not trust any measurement taken across the boundary. Treat as void and re-derive |
50
- | `wedged` | Two brackets, zero cycles, no epoch change | Last resort: `vice_recycle` with a real reason |
64
+ | `stale_read_path` **(fork only)** | Some reads move while others do not | Do not trust any measurement taken across the boundary. Treat as void and re-derive |
65
+ | `monitor_held_elsewhere` **(stock only)** | A different client already holds this instance's single binary-monitor slot | Release or identify the other holder. **Never a reason to recycle** — recycling here destroys an instance that is not even wedged |
66
+ | `wedged` | Two brackets, zero cycles, no epoch change | Last resort: `vice_recycle` with a real reason — **but check `evidence.jamObserved` first** (below) |
67
+ | `diagnosis_unavailable` **(stock only, non-verdict outcome — not one of the five)** | No verdict could be established at all; the message starts `vice_diagnose: diagnosis_unavailable (<reason>)`. The machine's state is **UNKNOWN**, not any of the five above | **Do not recycle on this answer alone.** Read the reason class in the message and act on it — see below |
68
+
69
+ ### `evidence.jamObserved` — read it before acting on `wedged` *or* `live` (stock only, 07-REVIEW WR-04)
70
+
71
+ Every stock `vice_diagnose` verdict carries `evidence.jamObserved` (always present, never omitted).
72
+ It is `true` once a `JAM` (0x61) event has arrived on this instance's wire — the 6510 executed an
73
+ illegal opcode and **the CPU is dead regardless of the verdict above**. The flag latches: it stays
74
+ true for the rest of the session.
75
+
76
+ It cuts across two verdicts in opposite directions, which is exactly why it is separate evidence
77
+ rather than a sixth verdict:
78
+
79
+ | `jamaction` | What `vice_diagnose` answers | Why | What to actually do |
80
+ |---|---|---|---|
81
+ | `-jamaction 2` (Monitor) | `wedged` | The machine stopped, so both brackets read zero advance | **`vice_machine_reset`, not `vice_recycle`.** Recycling destroys an instance a reset recovers — the same trap as `checkpoint_trap` |
82
+ | default (continue) | `live` | The emulator keeps burning cycles refetching the same opcode, so both brackets **advance** | **`vice_machine_reset`.** "Cycles advanced" is true and irrelevant: the machine will never execute another instruction |
83
+
84
+ **`jamObserved: true` is never a reason to recycle.** A jam is recovered by a reset; the instance
85
+ itself is healthy. Treat a `live` verdict with `jamObserved: true` as a false negative on liveness,
86
+ and a `wedged` verdict with it as a false positive on wedging.
87
+
88
+ ### `diagnosis_unavailable` — reason classes and response (07-15)
89
+
90
+ `diagnosis_unavailable` is what `vice_diagnose` answers, on the `isError:true` channel, when it
91
+ could not reach any of the five verdicts above — including a CR-01-class decode failure. It is
92
+ never added to the verdict enum and is never grounds to `vice_recycle` by itself: the message says
93
+ so explicitly. **Every** `isError:true` answer this tool can produce carries this prefix — there is
94
+ no unclassified no-verdict path left (07-REVIEW.md WR-02). Eight reason classes exist, each with
95
+ its own next move:
96
+
97
+ | Reason | What it means | Do |
98
+ |---|---|---|
99
+ | `connection_lost` | The socket died mid-session | Retry once. If it recurs, treat as a real transport problem, not a wedge |
100
+ | `request_timeout` | The wire went silent past the request bound | Retry once. If it recurs, fall to the manual cycle bracket below |
101
+ | `monitor_acquisition_timeout` | Another client holds the monitor and the wait bound expired | Wait for the current holder to release, then retry — this is the bounded sibling of `monitor_held_elsewhere`, not a wedge. **The abandoned acquisition is not cancelled** (07-REVIEW WR-19): a session may be established moments after this answer, so a later-appearing held session is not a ghost. Its real outcome is written to stderr |
102
+ | `session_refused` | The broker/lease itself refused the session | Read the raw detail in the message; this is a broker-level problem, not an emulator state |
103
+ | `protocol_decode_failure` | This build answered a frame the client cannot decode | Report it as a tool defect — check `docs/stock-vice-parity.md`'s `CPUHISTORY_GET` history for a known class of this — and fall back to the manual cycle bracket below |
104
+ | `evidence_gathering_failed` | A session was obtained but a read needed to build the verdict failed | `vice_execution_run` may be needed to unstick a stalled read path, then retry |
105
+ | `liveness_unmeasurable` | The liveness bracket could not be **measured at all** — no `CPUHISTORY_GET` (needs VICE ≥ 3.10) and no `LIN`/`CYC` enumerated. **The expected outcome on a stock 3.9-class build**, e.g. every current Debian/Ubuntu package | **Not a wedge and not a tool defect.** A bracket that cannot measure is not one that measured zero. Judge liveness from outside the monitor (screenshot, process state), or use the fork backend. Retrying will produce the same answer |
106
+ | `unknown` | None of the above classified the failure | Read the raw detail in the message; retry once before escalating |
51
107
 
52
108
  ## What is not recoverable
53
109
 
@@ -72,10 +128,53 @@ at a checkpoint — VICE's flag flips before the trap fires. Checkpoint bookkeep
72
128
  (`vice_checkpoint_add`/`list`/`delete`) also keeps returning healthy, self-consistent responses
73
129
  throughout a real wedge, so "the tools respond" proves nothing.
74
130
 
75
- **A `vice_run_until` on an address that is never reached looks exactly like a wedge.** Its `cycles`
76
- parameter is documented in its own schema as *"not yet implemented"*, so there is no working
77
- timeout to bound it. Before concluding anything, check whether you asked the machine to run to an
78
- address it cannot reach. **Confidence: MEDIUM** read off the tool schema, not reproduced.
131
+ **A `vice_run_until` on an address that is never reached looks exactly like a wedge on the fork,
132
+ still without a bound.** Its `cycles` parameter is *"not yet implemented"* on both backends, and the
133
+ fork has no timeout to bound the wait for an address either an unreachable address there is
134
+ unbounded and indistinguishable from a wedge. **On stock, passing `cycles` is now REFUSED rather
135
+ than ignored** (07-REVIEW WR-18) — including alongside `address`, where it used to be silently
136
+ dropped while the answer still reported `reached: true`. Unexpected argument names are refused by
137
+ name too, so a `timeoutMs`/`timeout_ms` typo can no longer run with the default bound in silence.
138
+ **On stock, this is now bounded (D-02):** pass `timeout_ms` (default 30000, clamped to a ceiling of
139
+ 600000); an unreachable address returns an explicit, bounded `timedOut: true` answer — with the
140
+ temporary checkpoint already cleaned up — rather than looking like a wedge. **Two further
141
+ behaviours (07-14, closing WR-01/WR-02):** every non-error answer, hit or timeout, carries
142
+ `machineHalted` plus a `machineHaltedNote` naming the resume call — the tool halts the machine on
143
+ every read and says so explicitly. **`machineHalted` is `true` on a hit and on a timeout whose
144
+ cleanup delete was answered (`cleanup: "deleted"` / `"already_gone"`); it is `false` when
145
+ `cleanup: "delete_failed"` or the socket is already gone** and the run-state projection does not
146
+ say `"stopped"`. **Do not read `machineHalted: false` as "still running"** — it means nothing here
147
+ could establish the state, `machineHaltedNote` says so, and the next call should be
148
+ `vice_diagnose`, not `vice_execution_run` (which may not reach the instance at all). And a timeout whose cleanup
149
+ delete lands on an already-gone race no longer asserts `reached: false` outright: it reads the
150
+ program counter and resolves the race (`raceResolved: "pc_at_address"` / `"pc_elsewhere"`), or, if
151
+ the PC read itself fails, omits `reached` entirely and reports `reachedUnknown: true`
152
+ (`raceResolved: "unresolved"`). **An absent `reached` is not "false"** — check `reachedUnknown`
153
+ before assuming a miss. The underlying judgement is unchanged and still the right first question on
154
+ either backend: before concluding anything, check whether you asked the machine to run to an
155
+ address it cannot reach. **Confidence: HIGH on stock for the reach/timeout mechanism** —
156
+ live-confirmed against genuine, unmodified `/usr/bin/x64sc` (VICE 3.9) and `/usr/local/bin/x64sc`
157
+ (VICE 3.10): a real KERNAL address ($EA31) reached within its timeout, an unreached one ($C000)
158
+ timing out with the checkpoint deleted (07-10's live pass). **MEDIUM for the WR-01/WR-02 honesty
159
+ fields above** — unit-proven (`stock-run-until.test.ts`, 21/21, 07-14) but not independently
160
+ re-exercised against a real emulator by this gap-closure batch. **MEDIUM on the fork** — read off
161
+ the tool schema, not reproduced.
162
+
163
+ **A second binary-monitor client is contention, not a wedge, and it has a cheap tell — stock
164
+ only.** Stock VICE's binary monitor services exactly one client; a second `connect()` sits
165
+ unserviced in the backlog with no reply and no EOF. The discriminator: a socket that *accepts* the
166
+ connection but never answers is contention, not a hung emulator — and the broker itself already
167
+ knows whether it holds a lease on that port, which is the thing a human or agent can actually go
168
+ check instead of guessing. Named causes, so a reader knows where to look: a hand-run `nc` session
169
+ left open against the port, a second Claude Code session driving the same instance, VICE's own
170
+ `-remotemonitor`, and any other 6502 debugger that dials in — including regenerator2000's own
171
+ `--vice` flag. **This plugin's own regenerator2000 route can never be one of them:** the launch
172
+ path refuses `--vice` by construction (no caller-supplied argv passthrough exists to inject it in
173
+ the first place) *and* by a scan that throws if the flag is ever present in the final argv — not by
174
+ documentation alone (`R2000-01`, plan 10-01) — so a user chasing a silent emulator can rule this
175
+ project's own r2000 integration out immediately, rather than suspecting it. The standing advice
176
+ does not change: contention is **never** a reason to recycle — the instance is healthy, merely
177
+ claimed elsewhere.
79
178
 
80
179
  ## The manual fallback, when `vice_diagnose` cannot answer
81
180
 
@@ -84,7 +183,7 @@ exists, that is a **host action for a human** — say so and stop. Nothing conta
84
183
  the emulator by another route.
85
184
 
86
185
  When the broker is up but you want the raw measurement, the cycle bracket is the only trustworthy
87
- liveness test, and it is four calls:
186
+ liveness test. **On the fork**, it is four calls:
88
187
 
89
188
  1. `vice_cycles_stopwatch` `{action: "reset"}`
90
189
  2. `vice_execution_run`
@@ -96,6 +195,18 @@ liveness test, and it is four calls:
96
195
  thing — merely slow, a separate documented hazard measured at ~6,000/s when a loop polls without
97
196
  re-resuming. Read all state first, poll with `vice_ping`, resume exactly once at the end.
98
197
 
198
+ **On stock, there is no non-pausing call at all — any inbound byte halts the machine — so the
199
+ `vice_ping` ×3 poll measures nothing there and is fork-only.** The stock equivalent is the same
200
+ bracket shape with zero calls during the wait:
201
+
202
+ 1. `vice_cycles_stopwatch` `{action: "reset"}`
203
+ 2. `vice_execution_run`
204
+ 3. A real wall-clock wait, with **no calls at all** during it
205
+ 4. `vice_cycles_stopwatch` `{action: "read"}`
206
+
207
+ `vice_diagnose` already runs exactly this bracket internally on stock, so the manual fallback above
208
+ is only for when the broker itself is unreachable and `vice_diagnose` cannot be called at all.
209
+
99
210
  **Enumerate your own checkpoints before running any bracket.** `vice_checkpoint_list`, then
100
211
  resolve the live IRQ handler (`$0314/$0315`, or `$FFFE/$FFFF` when `$01` has the ROMs banked out).
101
212
  An armed stopping checkpoint at or inside the live IRQ path, with the PC pinned at or just past
@@ -114,7 +225,8 @@ session, the last three all on that call).
114
225
  | Checkpoint delete / reset / step can all fail to recover | One recorded incident, all four attempts in sequence | HIGH, single incident |
115
226
  | A checkpoint trap explains all three recorded "silent stalls" | Cross-read, 3/3 correlation, mechanism consistent with every symptom — **not reproduced** | MEDIUM |
116
227
  | `vice_diagnose`'s five-verdict path behaves as its schema says | Schema read, and cross-checked against the tracked implementation's own report builders. **Not exercised end to end** | MEDIUM |
117
- | `vice_run_until` has no working timeout | Its schema says `cycles` is "not yet implemented" | MEDIUM |
228
+ | `vice_run_until` has no working timeout **(fork only)** | Its schema says `cycles` is "not yet implemented"; the fork has no `timeout_ms` bound | MEDIUM |
229
+ | Stock's five-verdict path (`restarted`, `checkpoint_trap`, `wedged`, `monitor_held_elsewhere`, `live`) and its bounded `vice_run_until` | Unit-proven (40/40 `stock-diagnose.test.ts`, 21/21 `stock-run-until.test.ts`, 07-15/07-14). **Live-proven** against genuine `/usr/bin/x64sc` (VICE 3.9) and `/usr/local/bin/x64sc` (VICE 3.10) for `live` (07-10), `checkpoint_trap`, `wedged` (confirmed on both capability routes — `frame_position` on 3.9, `cpu_history` on 3.10) and `restarted` (07-17). `monitor_held_elsewhere`'s **socket-level** contention bound is live-proven (07-13, ~1501-1502ms against a 1500ms bound). **UPDATED 2026-08-18 (quick task 260818-obc, `stock-live-broker-monitor.test.ts`, command `VICE_LIVE_BROKER_BIN=/usr/bin/x64sc` (or `/usr/local/bin/x64sc`) `node --test stock-live-broker-monitor.test.ts`):** both remaining unit-only residuals are now ALSO live-proven, on both binaries, in one real run — the **broker-mediated** `monitor_held_elsewhere` verdict (a real second `claimMonitor()` refusal from a genuine host broker daemon, naming the other real grant's id, settling in 1ms against the 10000ms bound) and the **broker-supervised** (not test-performed) `restarted` respawn (the host broker's OWN crash supervision relaunched the killed instance; `vice_diagnose` answered `restarted` with `baselineEpoch:1`/`currentEpoch:2` at zero-to-minimal emulator cost). `vice_run_until`'s reach/timeout mechanism is live-proven against both binaries (07-10); its WR-01/WR-02 honesty fields (`machineHalted`, `raceResolved`, `reachedUnknown`) remain unit-proven only (07-14) — NOT re-exercised live by this task, no blanket claim made here | HIGH for the five verdicts (including both the broker-mediated `monitor_held_elsewhere` path and the broker-supervised `restarted` path, both now live-proven) and the run_until reach/timeout mechanism; MEDIUM for the run_until honesty fields only, which stay unit-only |
118
230
 
119
231
  Full provenance in `.planning/RE-FINDINGS.md`. **Log a new incident there at the moment you hit
120
232
  it**, graded with `Evidence:` and `Confidence:`; promote by re-logging, never by editing a grade.
@@ -146,4 +258,5 @@ others carry.
146
258
  | Zero cycles, nothing armed, epoch unchanged | A wedge. `vice_recycle` with a reason that names the evidence |
147
259
  | A run "survived a reset" | Distrust it. You cannot read the epoch to confirm — but an unintended respawn inside the bracket would have raised a drift error on the next forwarded call, so absence of that error is the only evidence available |
148
260
  | `vice_recycle` refused for a missing reason | It is required, by design — the reason *is* the incident record |
261
+ | `vice_diagnose` answers `monitor_held_elsewhere`, or a call hangs with no reply and no EOF (stock only) | Not a wedge. Find the other client holding this instance's single binary-monitor slot |
149
262
  </content>