@henols/c64-re-tools 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,37 +6,38 @@ description: Drive an existing C64 annotation store's backlog of undocumented ro
6
6
  # Walking the routine and symbol queue to closure
7
7
 
8
8
  **Do not start annotating whatever is in front of you.** The expensive failure
9
- here is not slow work — it is a pass that *looks* finished while a hundred
9
+ here is not slow work. It is a pass that *looks* finished while a hundred
10
10
  `p_XXXX` labels are still nameless and nobody wrote down which ones. Build the
11
11
  queue first, from data, then walk it to the end.
12
12
 
13
13
  This playbook assumes block classification has already happened and an
14
- annotation store already exists. If you do not yet know what the program is —
15
- where it starts, which vector is live, which regions are code — stop and run
16
- `c64-program-recon` first. That skill answers *what is this program*; this one
17
- answers *what is still undocumented in it, and how do I finish*.
14
+ annotation store already exists. You may not yet know what the program is —
15
+ where it starts, which vector is live, which regions are code. If so, stop
16
+ and run `c64-program-recon` first. That skill answers *what is this
17
+ program*. This one answers *what is still undocumented in it, and how do I
18
+ finish*.
18
19
 
19
20
  ## The one rule that makes this different from upstream's version
20
21
 
21
22
  **Work the queue one entry at a time.** Not as a throughput compromise — as an
22
- accurate model of the store underneath. One `.annostore` is one writer: every
23
+ accurate model of the store underneath. One `.annostore` is one writer. Every
23
24
  mutating call opens it, commits and closes inside the call, and every one of
24
- them accepts an optional `base_revision` compare-and-swap that REFUSES a write
25
- computed against a revision the store has already moved past. Fanning several
26
- writers at one store therefore buys **zero** extra throughput and costs
27
- correctness: the losers come back as named stale-revision refusals you then
28
- have to re-derive and replay. Reading fan-out — several agents *thinking* over
29
- already-fetched answers — is fine, and its value is reasoning bandwidth, never
30
- I/O.
31
-
32
- **Every call names its own store.** There is no ambient "current store" on this
33
- surface: pass `store` (a `.annostore` path) on every call, and pass `image` as
34
- well on every call that derives its answer from the program's bytes rather than
35
- from the annotations — `anno_get_binary_info`, `anno_read_region`,
36
- `anno_disassemble`, `anno_get_cross_references`, `anno_search` and
37
- `anno_get_address_details`. The store holds annotations and never bytes, so an
38
- omitted image would read as a plausible success against whatever was recorded
39
- last.
25
+ them accepts an optional `base_revision` compare-and-swap. That REFUSES a
26
+ write computed against a revision the store has already moved past. Fanning
27
+ several writers at one store therefore buys **zero** extra throughput and
28
+ costs correctness. The losers come back as named stale-revision refusals you
29
+ then have to re-derive and replay. Reading fan-out — several agents *thinking*
30
+ over answers they already got — is fine, and its value is reasoning
31
+ bandwidth, never I/O.
32
+
33
+ **Every call names its own store.** There is no ambient "current store" on
34
+ this surface: pass `store` (a `.annostore` path) on every call. Also pass
35
+ `image` on every call that derives its answer from the program's bytes rather
36
+ than from the annotations. Those calls are `anno_get_binary_info`,
37
+ `anno_read_region`, `anno_disassemble`, `anno_get_cross_references`,
38
+ `anno_search` and `anno_get_address_details`. The store holds annotations and
39
+ never bytes, so an omitted image would read as a plausible success against
40
+ whatever was recorded last.
40
41
 
41
42
  ## Phase 0 — context, and the packed-binary gate
42
43
 
@@ -45,22 +46,23 @@ last.
45
46
  quotes them.
46
47
  2. Read the returned `entropy` against the threshold of **7.5** carried in that
47
48
  tool's own description. At or above it, the bytes are very likely packed.
48
- 3. If the binary looks packed, **stop and say so.** Do not annotate a packed
49
- image: you would be documenting a decruncher, and every label you write is
50
- thrown away the moment the real image is recovered. This project's route to
51
- an unpacked image is `c64-ram-capture` — run the program in the emulator and
52
- capture RAM at a checkpoint past the decrunch — plus the packer-identity
53
- finding in `c64-program-recon`, which names the packer when an oracle can.
54
- Come back with the captured image and start again at Phase 0.
55
-
56
- Upstream's in-place `unpack_binary` step is deliberately not carried: it is
57
- destructive (it clears the comments, labels and blocks already in the store)
58
- and this project has a non-destructive route to the same answer.
49
+ 3. If the binary looks packed, **stop and say so.** Do not annotate a
50
+ packed image. You would be documenting a decruncher. Every label you write
51
+ is thrown away the moment you recover the real image. This project's
52
+ route to an unpacked image is `c64-ram-capture`. Run the program in the
53
+ emulator and capture RAM at a checkpoint past the decrunch. Also run the
54
+ packer-identity finding in `c64-program-recon`, which names the packer
55
+ when an oracle can. Come back with the captured image and start again at
56
+ Phase 0.
57
+
58
+ Upstream's in-place `unpack_binary` step is deliberately not carried. It is
59
+ destructive: it clears the comments, labels and blocks already in the store.
60
+ This project has a non-destructive route to the same answer.
59
61
 
60
62
  ## Phase 1 — make sure blocks are classified
61
63
 
62
64
  Region classification is a prerequisite for everything below: a routine
63
- candidate is only meaningful once the bytes around it are known to be code.
65
+ candidate is only meaningful once you know the bytes around it are code.
64
66
 
65
67
  1. Follow `src/skills/c64-memory-mapping/SKILL.md` for the classification pass
66
68
  and for what each region type means.
@@ -72,60 +74,62 @@ candidate is only meaningful once the bytes around it are known to be code.
72
74
  ### 2.1 Build the candidate list
73
75
 
74
76
  A routine counts as **already documented** when its entry address carries a
75
- line comment. That is the only test; do not guess from the label name.
77
+ line comment. That is the only test. Do not guess from the label name.
76
78
 
77
- 1. Call `anno_get_symbols` for all labels — user, system and external, with
78
- an explicit `max_results` above the program's label count (`max_results` is
79
- REQUIRED on this surface and has no default, so a truncated answer is always
80
- a ceiling you chose). Keep the answer; Phase 3 reuses it.
79
+ 1. Call `anno_get_symbols` for all labels — user, system and external. Use
80
+ an explicit `max_results` above the program's label count. `max_results`
81
+ is REQUIRED on this surface and has no default. So a truncated answer is
82
+ always a ceiling you chose. Keep the answer. Phase 3 reuses it.
81
83
  2. Call `anno_get_comments`, again with an explicit `max_results`. Keep that
82
- too — the true match count rides beside the list, so truncation is a fact
84
+ too. The true match count rides beside the list. So truncation is a fact
83
85
  you are told rather than one you infer.
84
86
  3. **Candidate source A — cross-reference and block-derived, checked FIRST and
85
87
  independently of whatever `anno_get_symbols` returned.** Call
86
88
  `anno_get_blocks` with `block_type: "code"` for every code-typed range,
87
89
  read each one with `anno_disassemble`, and collect every `jsr` target
88
- address. For each candidate target, confirm it and gather its full caller
89
- list with `anno_get_cross_references` (a generous `max_results` — this is
90
- also the call that fills in "called from" when the entry is written up in
91
- Phase 2.2). Every one of these targets is a routine candidate **regardless
92
- of whether it carries any label at all**. `docs/phase45-wave0-
93
- measurements.md`'s own MEASUREMENT A found that a purely dxa/Ghidra-derived
94
- store carries ZERO labels of any shape — derivation writes typed ranges and
95
- cross-references, never names — so a queue built only from Candidate source
96
- B below finds nothing to do on such a store and silently reports a clean,
97
- empty queue on a program nothing has been named in yet. Source A does not
98
- depend on step 1 having found anything.
90
+ address. For each candidate target, check that it is a real routine. Also
91
+ gather its full caller list with `anno_get_cross_references`. Use a
92
+ generous `max_results`. This is also the call that fills in "called from"
93
+ when you write up the entry in Phase 2.2. Every one of these targets is a
94
+ routine candidate **regardless of whether it carries any label at all**.
95
+ A real measured derivation run (dxa disassemble, then Ghidra import) found
96
+ this. A purely dxa/Ghidra-derived store carries ZERO labels of any shape.
97
+ Derivation writes typed ranges and cross-references, never names. So a
98
+ queue built only from Candidate source B below finds nothing to do on
99
+ such a store. It silently reports a clean, empty queue on a program
100
+ nothing has been named in yet. Source A does not depend on step 1 having
101
+ found anything.
99
102
  4. **Candidate source B — the label-prefix path, for a store that DOES carry
100
103
  externally-imported auto-names.** Keep a label as a routine candidate when
101
104
  any of these holds:
102
- - its name starts with `s_` (an auto-generated subroutine label);
105
+ - its name starts with `s_` (an auto-generated subroutine label).
103
106
  - it sits in a code region and is the target of at least one `JSR`
104
- cross-reference (`anno_get_cross_references`);
107
+ cross-reference (`anno_get_cross_references`).
105
108
  - it is a `p_XXXX` label sitting **inside a code region**. These come from
106
- split lo/hi immediate loads and from address tables, and they are almost
109
+ split lo/hi immediate loads and from address tables. They are almost
107
110
  always chained raster-IRQ handlers, hardware- or shadow-vector handlers,
108
111
  or jump-table and callback targets. Treat every one of them as a
109
- candidate rather than pattern-matching specific vector addresses;
112
+ candidate rather than pattern-matching specific vector addresses.
110
113
  - it is the label named exactly `start`.
111
114
  5. **Union sources A and B by address** — a routine reachable both ways counts
112
115
  once. A store may carry either shape, or both, so neither source alone is
113
116
  sufficient.
114
117
  6. Drop every candidate that already carries a line comment.
115
- 7. What is left is the routine queue.
118
+ 7. What remains is the routine queue.
116
119
  8. **Order it with `start` first** when `start` is in it. The entry point sets
117
- the context every other routine is read against.
120
+ the context you read every other routine against.
118
121
 
119
122
  ### 2.2 Walk it
120
123
 
121
124
  Take **one** entry at a time, to completion, before starting the next — the
122
125
  queue discipline this section owns. For each entry, run `c64-program-recon`
123
126
  `SKILL.md`'s **"Documenting one routine, end to end"** procedure (steps 1-7)
124
- against the entry's explicit address — including its 4096-byte
125
- `anno_read_region` cap (consecutive ranges above it, never a raised cap) and
126
- its tail-call / fall-through bounds rules (`JMP shared_epilogue` still ends
127
- the routine; no return may mean fall-through — say so). Do not re-derive or
128
- paraphrase that procedure here.
127
+ against the entry's explicit address. That procedure has a 4096-byte
128
+ `anno_read_region` cap. Above the cap, use consecutive ranges. Never use a
129
+ raised cap. That procedure also carries its tail-call and fall-through
130
+ bounds rules. A `JMP shared_epilogue` still ends the routine. No return may
131
+ mean fall-through — say so. Do not re-derive or paraphrase that procedure
132
+ here.
129
133
 
130
134
  Record per entry, for Phase 4: the address, the old label, the new label, a
131
135
  one-line summary, and any uncertainty.
@@ -133,62 +137,67 @@ one-line summary, and any uncertainty.
133
137
  ### 2.3 Refresh point
134
138
 
135
139
  When the queue is empty, read the store's revision with `anno_save_project`.
136
- **It performs no write, and it exists to say so:** every mutating verb on this
137
- surface has already committed and fsynced its own write by the time it
138
- returned, so there is nothing for an explicit save to flush. Record the
139
- revision — it is the checkpoint this pass is measured from, and the
140
+ **It performs no write, and it exists to say so.** Every mutating verb on
141
+ this surface has already committed and fsynced its own write by the time it
142
+ returned. So there is nothing for an explicit save to flush. Record the
143
+ revision — it is the checkpoint you measure this pass from. It is also the
140
144
  `base_revision` a later compare-and-swap write would quote. Everything after
141
- this point re-reads the store, because Phase 2 has just changed the label names
142
- Phase 3 filters on.
145
+ this point re-reads the store, because Phase 2 has just changed the label
146
+ names Phase 3 filters on.
143
147
 
144
148
  ## Phase 3 — the symbol queue
145
149
 
146
150
  ### 3.1 Build the candidate list
147
151
 
148
- A symbol counts as **already documented** when it has a name a human chose, or
149
- when it is a well-known system address (hardware register, KERNAL entry point,
150
- OS variable).
152
+ A symbol counts as **already documented** when it has a name a human chose.
153
+ It also counts as documented when it is a well-known system address (hardware
154
+ register, KERNAL entry point, OS variable).
151
155
 
152
156
  1. Call `anno_get_symbols` **again** — Phase 2 renamed things.
153
157
  2. **Candidate source A — cross-reference and block-derived, checked FIRST
154
158
  and independently of whatever label population exists.** Call
155
159
  `anno_get_blocks` (with `include: ["enum_usage"]` where useful) for every
156
- typed range, then use `anno_get_cross_references` to find every address
157
- that is: referenced by one half of a split lo/hi pair or by an address
158
- table (a `lo_hi_address`/`hi_lo_address`/`lo_hi_word`/`hi_lo_word` range —
159
- the `_address` forms produce cross-references, the `_word` forms do not,
160
- per that data type's own schema distinction), OR referenced from a code
161
- range while NOT itself sitting inside one. Every one of these is a symbol
162
- candidate **regardless of whether it carries any label at all**.
163
- `docs/phase45-wave0-measurements.md`'s own MEASUREMENT A found that a
164
- purely dxa/Ghidra-derived store carries ZERO labels of any shape, so
165
- Candidate source B below finds nothing to do on such a store and silently
166
- reports a clean, empty queue on a program nothing has been named in yet.
160
+ typed range. Then use `anno_get_cross_references` to find every address
161
+ referenced by one half of a split lo/hi pair. Also find any address
162
+ referenced by an address table. An address table is a
163
+ `lo_hi_address`/`hi_lo_address`/`lo_hi_word`/`hi_lo_word` range. The
164
+ `_address` forms produce cross-references, the `_word` forms do not, per
165
+ that data type's own schema distinction. Also include any address
166
+ referenced from a code range while it does not sit inside one. Every one
167
+ of these is a symbol candidate **regardless of whether it carries any
168
+ label at all**. The same measured derivation run found this. A purely
169
+ dxa/Ghidra-derived store carries ZERO labels of any shape. So Candidate
170
+ source B below finds nothing to do on such a store. It silently reports
171
+ a clean, empty queue on a program nothing has been named in yet.
167
172
  3. **Candidate source B — the label-prefix path, for a store that DOES carry
168
173
  externally-imported auto-names.** Keep every label whose name still
169
- matches an auto-generated pattern: `zpp_XX`, `zpf_XX`, `zpa_XX` in the zero
170
- page; `p_XXXX`, `f_XXXX`, `a_XXXX` and `e_XXXX` outside it.
171
- 4. Exclude, from BOTH sources: `s_XXXX` (Phase 2 handled those), `b_XXXX`
172
- (branch targets, not data symbols), and any `p_XXXX`-shaped or
173
- xref-derived candidate inside a code region (also Phase 2's).
174
+ matches an auto-generated pattern. In the zero page, that pattern is
175
+ `zpp_XX`, `zpf_XX` or `zpa_XX`. Outside the zero page, that pattern is
176
+ `p_XXXX`, `f_XXXX`, `a_XXXX` or `e_XXXX`.
177
+ 4. Exclude, from BOTH sources:
178
+ - `s_XXXX` (Phase 2 handled those).
179
+ - `b_XXXX` (branch targets, not data symbols).
180
+ - any `p_XXXX`-shaped or xref-derived candidate inside a code region
181
+ (also Phase 2's).
174
182
  5. **Union sources A and B by address** — a symbol reachable both ways counts
175
183
  once.
176
- 6. What is left is the symbol queue.
184
+ 6. What remains is the symbol queue.
177
185
 
178
186
  ### 3.2 Walk it
179
187
 
180
188
  Same discipline as Phase 2: explicit address, one entry at a time, to
181
189
  completion. For each symbol, use `anno_get_cross_references` to find who
182
- touches it — a symbol's meaning is what its callers do with it — then rename it
183
- and comment it. Classify it plainly: flag, counter, pointer, state variable,
184
- buffer, table.
190
+ touches it. A symbol's meaning is what its callers do with it. Then rename
191
+ it and comment it. Classify it plainly: flag, counter, pointer, state
192
+ variable, buffer, table.
185
193
 
186
- **No premature halting.** The symbol queue is routinely far larger than the
194
+ **No premature stopping.** The symbol queue is routinely far larger than the
187
195
  routine queue — fifty, a hundred entries is normal. Do not truncate it, do not
188
196
  skip "secondary" symbols, and do not stop early because it is long. Feeding the
189
197
  whole queue through is the job. Stopping early and labelling the remainder
190
- "skipped for review" is a failed pass, not a completed one — unless the
191
- remainder is reported explicitly, in full, under Phase 4's leftovers table.
198
+ "skipped for review" is a failed pass, not a completed one. The only
199
+ exception is a remainder you report explicitly, in full, under Phase 4's
200
+ leftovers table.
192
201
 
193
202
  For naming conventions and for what any given hardware or KERNAL address
194
203
  means, follow `src/skills/c64-memory-mapping/SKILL.md` rather than guessing.
@@ -196,16 +205,16 @@ means, follow `src/skills/c64-memory-mapping/SKILL.md` rather than guessing.
196
205
  ### 3.3 Refresh point
197
206
 
198
207
  Read the revision again with `anno_save_project` and record it. No write is
199
- performed; the writes already landed.
208
+ performed. The writes already landed.
200
209
 
201
210
  ## Phase 4 — save and report
202
211
 
203
212
  1. Read the revision one last time with `anno_save_project` and quote it in
204
- the report, so the pass is attributable to an exact store state.
213
+ the report. This makes the pass attributable to an exact store state.
205
214
  2. Write the report. Four sections, all of them required:
206
215
 
207
- **Regions.** How many regions are classified, grouped by type, plus anything
208
- notable — text at a fixed address, a jump table, a sprite block.
216
+ **Regions.** How many regions you classified, grouped by type, plus anything
217
+ notable — text at a constant address, a jump table, a sprite block.
209
218
 
210
219
  **Routines.**
211
220
 
@@ -221,10 +230,10 @@ notable — text at a fixed address, a jump table, a sprite block.
221
230
 
222
231
  **Leftovers — uncertain, skipped, or still unannotated.** This section is not
223
232
  optional and it is not allowed to be empty when the queues were not emptied.
224
- List every routine and every symbol that was left undone, with its address and
233
+ List every routine and every symbol you left undone, with its address and
225
234
  the reason. Never report "no uncertain areas" or "nothing left" while a single
226
235
  `f_XXXX` or `a_XXXX` label is still auto-named or a queued routine is still
227
- uncommented — those must be listed by name for a human to pick up.
236
+ uncommented. Those must be listed by name for a human to pick up.
228
237
 
229
238
  ## Phase 5 — measure the pass instead of asserting it finished
230
239
 
@@ -236,55 +245,60 @@ node src/mcp/vice/vice-proxy.ts anno coverage game.prg --store game.annostore
236
245
  ```
237
246
 
238
247
  **`--store` is REQUIRED and is a second path, not a spelling of the first.**
239
- `<program>` supplies the payload bytes and the load origin; `--store` names the
248
+ `<program>` supplies the payload bytes and the load origin. `--store` names the
240
249
  annotation store holding the labels, comments and typed ranges. The store holds
241
250
  annotations and never bytes, so the verb refuses to guess either path from the
242
251
  other.
243
252
 
244
253
  **Dated note, 2026-08-30 — the positional is a program IMAGE, and the command
245
254
  above is now correct against the shipped verb.** `<program>` is a `.prg` (a
246
- 2-byte little-endian load address followed by the payload) or an
247
- **exactly-65536-byte** flat capture with a `.raw` or `.bin` extension — the two
248
- forms every other verb and tool on this surface already reads, and the two
249
- `c64-ram-capture` produces. The intermediate project-file format this verb
250
- previously required has **no producer left in this repo**; it is still accepted
251
- so an existing project file keeps working, but nothing here writes one, so do
252
- not go looking for a step that produces it.
253
-
254
- Dispatch is by **file extension first, length second**. A short flat capture is
255
- therefore refused by name — `a flat 64K capture must be exactly 65536 bytes` —
256
- rather than misread as a `.prg` whose first two payload bytes become the load
257
- address. If you get that refusal, the capture is truncated; re-capture it, do
258
- not rename it.
255
+ 2-byte little-endian load address followed by the payload). Or it is an
256
+ **exactly-65536-byte** flat capture with a `.raw` or `.bin` extension. Every
257
+ other verb and tool on this surface already reads these two forms.
258
+ `c64-ram-capture` also produces these same two forms. The intermediate
259
+ project-file format this verb previously required has **no producer left in
260
+ this repo**. It is still accepted so an existing project file keeps working.
261
+ But nothing here writes one, so do not go looking for a step that produces
262
+ it.
263
+
264
+ Dispatch is by **file extension first, length second**. A short flat capture
265
+ is therefore refused by name: `a flat 64K capture must be exactly 65536
266
+ bytes`. It is not misread as a `.prg` whose first two payload bytes become
267
+ the load address. If you get that refusal, the capture is truncated.
268
+ Re-capture it, do not rename it.
259
269
 
260
270
  Add `--out coverage.json` to keep the machine-readable report, `--force` to
261
271
  overwrite one, and `--sample N` to widen the reproducibility sample. The verb
262
- reads the same store every call in this playbook writes to, and exits **0 even
263
- when the numbers are bad** — a low measurement is a result, not a failure.
264
- Non-zero means a caller error, an image it could not read, or a store it could
265
- not read at all.
272
+ reads the same store every call in this playbook writes to. It exits **0
273
+ even when the numbers are bad** — a low measurement is a result, not a
274
+ failure. Non-zero means a caller error, an image it could not read, or a
275
+ store it could not read at all.
266
276
 
267
- **Run it three times:** once before Phase 2, so the pass has a starting point
268
- to be compared against; once at Phase 2.3's refresh point; and once at the end,
269
- after Phase 4's final save. The last run is what goes in the report.
277
+ **Run it three times:**
278
+ - once before Phase 2, so the pass has a starting point to be compared
279
+ against.
280
+ - once at Phase 2.3's refresh point.
281
+ - once at the end, after Phase 4's final save.
282
+
283
+ The last run is what goes in the report.
270
284
 
271
285
  **Read the three numbers against each other. Never quote one of them alone.**
272
286
  There is deliberately no single "percent documented" figure, because one
273
- combined number lets a weak measure hide behind a strong one and makes the
274
- claim unfalsifiable:
275
-
276
- - **A high user fraction beside a large unreached count means the wrong things
277
- were named.** Every label got a human name, but most of the image was never
278
- reached by the descent walk from any seed — the queue was worked over the
279
- easily-visible part of the program and the rest was never entered. Go back to
280
- Phase 0 and find more entry points (chained IRQ vectors, dispatch tables),
281
- not more labels.
287
+ combined number lets a weak measure hide behind a strong one. That combined
288
+ number also makes the claim unfalsifiable:
289
+
290
+ - **A high user fraction beside a large unreached count means you named the
291
+ wrong things.** Every label got a human name, but most of the image was
292
+ never reached by the descent walk from any seed. You worked the queue over
293
+ the easily-visible part of the program. The rest was never entered. Go back
294
+ to Phase 0 and find more entry points (chained IRQ vectors, dispatch
295
+ tables), not more labels.
282
296
  - **A large divergence means the store and the bytes disagree about what is
283
297
  code.** Bytes the census reached as instructions that the store does not call
284
298
  `Code` are places where Phase 1's classification is behind the actual control
285
299
  flow. The reverse direction (the store calls it `Code`, the census never
286
- reached it) is ordinary on an image with unreachable filler — read it, do not
287
- chase it.
300
+ reached it) is ordinary on an image with unreachable filler. Read it, do
301
+ not chase it.
288
302
  - **A low distinct-comment ratio means the comments are filler.** Fifty
289
303
  addresses carrying the same sentence counts once, not fifty times. That is
290
304
  the number that catches a pass which renamed everything and explained
@@ -300,66 +314,81 @@ This is a DIFFERENT, non-overlapping measurement from the `anno coverage`
300
314
  call above — neither replaces the other. `anno coverage` is the byte-census
301
315
  and label-ratio instrument: a derived-from-bytes census this store's own
302
316
  block table cannot move. `anno decomp-completeness` is the
303
- disagreement-gated closure gate: whether this fixture's byte-derived block
304
- classification and its own real, observed-execution evidence agree, every
305
- code entry point carries a name and a complete purpose comment, every
306
- referenced non-hardware address resolves to a name or a decline, and no
307
- auto-named survivor remains in a code region — with the disagreement query
308
- itself a required, non-defaultable input rather than an optional
309
- cross-check.
317
+ disagreement-gated closure gate. It checks four things:
318
+ - this fixture's byte-derived block classification and its own real,
319
+ observed-execution evidence agree.
320
+ - every code entry point carries a name and a complete purpose comment.
321
+ - every referenced non-hardware address resolves to a name or a decline.
322
+ - no auto-named survivor remains in a code region.
323
+
324
+ The disagreement query itself is a required, non-defaultable input, not an
325
+ optional cross-check.
310
326
 
311
327
  ```
312
328
  node src/mcp/vice/vice-proxy.ts anno decomp-completeness --store <fixture>.annostore --disagreements <fixture>-disagreements.json --manifest src/mcp/vice/fixtures/decomp-execution-manifest.json
313
329
  ```
314
330
 
315
- All three arguments are REQUIRED, and none is derived from another: `--store`
316
- names the annotation store; `--disagreements` names the JSON `anno
317
- evid-disagreements --store <same store> --json` wrote for THIS store's own
318
- run; `--manifest` names the committed execution manifest recording which of
319
- the nine fixtures were actually run under the reproducible-run protocol, and
320
- which were declared not-executed and why. Omitting any of the three refuses
321
- by name rather than rendering an empty-disagreement report — "the query was
322
- never run" and "the query found nothing" must never read the same.
331
+ All three arguments are REQUIRED, and none is derived from another:
332
+ - `--store` names the annotation store.
333
+ - `--disagreements` names the JSON `anno evid-disagreements --store <same
334
+ store> --json` wrote for THIS store's own run.
335
+ - `--manifest` names the committed execution manifest recording which of the
336
+ nine fixtures were actually run under the reproducible-run protocol. It also
337
+ records which fixtures were declared not-executed, and why.
338
+
339
+ Omitting any of the three refuses by name rather than rendering an
340
+ empty-disagreement report. "The query was never run" and "the query found
341
+ nothing" must never read the same.
323
342
 
324
343
  **The stop condition is a measured exit code, not a belief.** The walk
325
- described in Phases 2-4 above is finished for a fixture when `node
344
+ described in Phases 2-4 above finishes for a fixture when `node
326
345
  src/skills/routine-queue-walker/scripts/completeness-report.mjs --store
327
346
  <fixture>.annostore --disagreements <fixture>-disagreements.json --manifest
328
- src/mcp/vice/fixtures/decomp-execution-manifest.json` **exits 0** — never when
329
- the agent believes the queue is empty. A non-zero exit names, by address,
330
- exactly which measure still fails (an Undefined byte, a surviving auto-name,
331
- an entry point missing a name or a purpose-comment element, an unresolved
332
- referenced address, or an unresolved disagreement); go back to the
333
- corresponding phase and close it, then re-run the gate. Do not report a pass
334
- from reading the rendered text alone — read the process exit code.
347
+ src/mcp/vice/fixtures/decomp-execution-manifest.json` **exits 0**. It never
348
+ finishes just because the agent believes the queue is empty. A non-zero exit
349
+ names, by address, exactly which measure still fails:
350
+ - an Undefined byte.
351
+ - a surviving auto-name.
352
+ - an entry point missing a name or a purpose-comment element.
353
+ - an unresolved referenced address.
354
+ - an unresolved disagreement.
355
+
356
+ Go back to the corresponding phase and close it, then re-run the gate. Do
357
+ not report a pass from reading the rendered text alone — read the process
358
+ exit code.
335
359
 
336
360
  ## When something fails
337
361
 
338
362
  - A failed call is not a reason to drop a queue entry. Log the address, the
339
- call and the error, put the entry back on the queue, and carry on with the
363
+ call and the error. Put the entry back on the queue, and carry on with the
340
364
  next one. Report every one of those in the leftovers table.
341
- - A refused write is not silent and must not be treated as one. A
342
- stale-revision refusal (a `base_revision` that the store has moved past), an
343
- illegal label name, or a scope that overlaps an existing one all come back
344
- REFUSED and named, with nothing written. Re-read, re-derive and replay that
345
- one entry; never widen the range or drop the `base_revision` to make the
346
- refusal go away.
347
- - Never invent an answer to make a queue entry go away. An honest "this looks
348
- like a table, callers unclear" in the leftovers table is worth more than a
349
- confident wrong label that the next reader has to un-learn.
365
+ - A refused write is not silent and must not be treated as one. Three
366
+ things all come back REFUSED and named, with nothing written:
367
+ - a stale-revision refusal (a `base_revision` that the store has moved
368
+ past).
369
+ - an illegal label name.
370
+ - a scope that overlaps an existing one.
371
+
372
+ Re-read, re-derive and replay that one entry. Never widen the range or drop
373
+ the `base_revision` to make the refusal go away.
374
+ - Never invent an answer to make a queue entry go away. In the leftovers
375
+ table, an honest "this looks like a table, callers unclear" beats a
376
+ confident wrong label. The next reader would have to un-learn a wrong
377
+ label like that.
350
378
  - **A genuinely unresolvable target gets a `DECLINED:` comment, never a
351
379
  fabricated name.** When a referenced address's target is truly
352
380
  path-dependent or otherwise cannot be determined, record it with
353
- `anno_set_comment` using the literal prefix `DECLINED:` naming what is
354
- unknown and why — the same convention `.annostore`'s own importer already
355
- uses for bank-state declines, never a second mechanism. A confident wrong
356
- label is worse than an absent one.
357
- - **An accepted disagreement gets a `DISAGREEMENT-ACCEPTED:` comment.** When
358
- the decomposition-completeness gate's disagreement census flags a byte the
381
+ `anno_set_comment`. Use the literal prefix `DECLINED:` naming what is
382
+ unknown and why. `.annostore`'s own importer already uses this same
383
+ convention for bank-state declines. This is never a second mechanism. A
384
+ confident wrong label is worse than an absent one.
385
+ - **An accepted disagreement gets a `DISAGREEMENT-ACCEPTED:` comment.** The
386
+ decomposition-completeness gate's disagreement census flags a byte the
359
387
  byte-derived block table calls `data` but the runtime evidence shows
360
- executing, and review confirms the runtime evidence is correct (or the
388
+ executing. Also, a review finds the runtime evidence correct. Or the
361
389
  disagreement is otherwise a reviewed, accepted fact rather than a
362
- classification bug), record it with `anno_set_comment` using the literal
363
- prefix `DISAGREEMENT-ACCEPTED:` naming why — greppable, and read by the gate
364
- itself as the resolution for that address. Both conventions ride the
365
- existing `anno_set_comment` tool; neither is a new mechanism.
390
+ classification bug. When either holds, record it with `anno_set_comment`.
391
+ Use the literal prefix `DISAGREEMENT-ACCEPTED:` naming why. This is
392
+ greppable, and the gate itself reads it as the resolution for that
393
+ address. Both conventions ride the existing `anno_set_comment` tool.
394
+ Neither is a new mechanism.
@@ -75,13 +75,15 @@ export class MissingDisagreementInputError extends Error {
75
75
 
76
76
  /**
77
77
  * The frozen survivor prefix set, MIRRORED from
78
- * `src/mcp/vice/anno-cli.ts`'s own frozen set -- see
79
- * docs/phase45-wave0-measurements.md for the MEASURED label population this
80
- * was frozen against. Exported here, separately from the verb's own copy,
81
- * because this script's own tests must be able to assert on the predicate in
82
- * isolation, without a live store or a subprocess -- and because this
83
- * script's own header forbids it from reading a store directly, so it
84
- * cannot import the verb's copy through anything but a duplicate literal.
78
+ * `src/mcp/vice/anno-cli.ts`'s own frozen set -- frozen from a real
79
+ * derivation run (dxa disassemble, then Ghidra import) that measured ZERO
80
+ * labels written by import alone, confirming the eleven prefixes had
81
+ * nothing populated to positively test against rather than contradicting
82
+ * them. Exported here, separately from the verb's own copy, because this
83
+ * script's own tests must be able to assert on the predicate in isolation,
84
+ * without a live store or a subprocess -- and because this script's own
85
+ * header forbids it from reading a store directly, so it cannot import the
86
+ * verb's copy through anything but a duplicate literal.
85
87
  *
86
88
  * WHAT NOT TO DO: if the frozen set in `anno-cli.ts` ever changes, this copy
87
89
  * moves in the SAME commit, or the two renderers silently disagree about