@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 +47 -4
- package/package.json +2 -2
- package/skills/acme-build/SKILL.md +34 -48
- package/skills/acme-build/scripts/acme.mjs +2 -19
- package/skills/acme-build/template.a +17 -3
- package/skills/c64-memory-mapping/SKILL.md +35 -0
- package/skills/c64-program-recon/SKILL.md +128 -3
- package/skills/c64-program-recon/references/control-flow.md +9 -0
- package/skills/c64-program-recon/references/graphics.md +6 -0
- package/skills/c64-program-recon/references/observation-hazards.md +23 -0
- package/skills/c64-program-recon/references/sound-and-input.md +10 -2
- package/skills/c64-program-recon/references/tool-selection.md +15 -11
- package/skills/c64-program-recon/templates/memory-map.template.md +72 -51
- package/skills/c64-provenance-diff/SKILL.md +1 -1
- package/skills/c64-ram-capture/SKILL.md +17 -1
- package/skills/vice-wedge-triage/SKILL.md +129 -16
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
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
|
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`,
|
|
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
|
|
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
|
|
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
|
-
|
|
8
|
-
!
|
|
9
|
-
!
|
|
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
|
|
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` |
|
|
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` / `
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
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
|
-
|
|
|
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`
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
8
|
+
Run the generator once findings are in the store:
|
|
34
9
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
22
|
+
## The provenance sidecar
|
|
46
23
|
|
|
47
|
-
|
|
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
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|---|---|---|---|
|
|
58
|
-
| | `Maybe_` | | |
|
|
65
|
+
## Confidence vocabulary
|
|
59
66
|
|
|
60
|
-
|
|
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
|
|
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
|
|
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
|
-
**
|
|
9
|
-
one of them.** Work the order below. Do not start with
|
|
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
|
|
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`
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
| `
|
|
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
|
|
76
|
-
|
|
77
|
-
timeout to bound
|
|
78
|
-
|
|
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
|
|
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>
|