@henols/c64-re-tools 1.1.0 → 2.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +138 -37
  3. package/distribution/package.ts +36 -0
  4. package/distribution/plugin.ts +120 -0
  5. package/package.json +34 -40
  6. package/skills/c64-assembler/SKILL.md +73 -0
  7. package/skills/c64-assembler/scripts/assemble.ts +103 -0
  8. package/skills/c64-basic/SKILL.md +67 -0
  9. package/skills/c64-basic/scripts/basic.ts +46 -0
  10. package/skills/c64-disk/SKILL.md +79 -0
  11. package/skills/c64-disk/scripts/disk.ts +65 -0
  12. package/skills/c64-emulator/SKILL.md +124 -0
  13. package/skills/c64-knowledge/SKILL.md +102 -0
  14. package/skills/c64-knowledge/scripts/knowledge.ts +257 -0
  15. package/skills/c64-memory-map/SKILL.md +76 -0
  16. package/skills/c64-memory-map/scripts/memmap.ts +81 -0
  17. package/skills/c64-memory-map/scripts/platform.ts +424 -0
  18. package/skills/c64-provenance/SKILL.md +76 -0
  19. package/skills/c64-provenance/scripts/compare.ts +92 -0
  20. package/skills/c64-provenance/scripts/provenance.ts +83 -0
  21. package/skills/c64-reverse-engineering/SKILL.md +90 -0
  22. package/skills/c64-static-analysis/SKILL.md +96 -0
  23. package/skills/c64-static-analysis/scripts/analyze.ts +175 -0
  24. package/skills/c64-static-analysis/scripts/findings.ts +73 -0
  25. package/skills/c64-testing/SKILL.md +79 -0
  26. package/skills/c64-testing/references/scenario-format.md +76 -0
  27. package/skills/c64-testing/scripts/checklist.ts +51 -0
  28. package/skills/c64-testing/scripts/scenario.ts +473 -0
  29. package/skills/c64-testing/scripts/test.ts +55 -0
  30. package/skills/c64-unpacker/SKILL.md +82 -0
  31. package/skills/c64-unpacker/scripts/evidence.ts +135 -0
  32. package/skills/c64-unpacker/scripts/unpack.ts +216 -0
  33. package/src/c64.ts +281 -0
  34. package/src/cli/ap-sdk.ts +44 -0
  35. package/src/cli/cleanup.ts +194 -0
  36. package/src/cli/main.ts +122 -0
  37. package/src/cli/status.ts +34 -0
  38. package/src/host/main.ts +106 -0
  39. package/src/host/server.ts +376 -0
  40. package/src/host/tools/c1541.ts +255 -0
  41. package/src/host/tools/index.ts +31 -0
  42. package/src/host/tools/petcat.ts +70 -0
  43. package/src/host/tools/status.ts +30 -0
  44. package/src/host/tools/version.ts +41 -0
  45. package/src/host/vice/adapter.ts +761 -0
  46. package/src/host/vice/binary-monitor.ts +389 -0
  47. package/src/host/vice/chips.ts +108 -0
  48. package/src/host/vice/connect.ts +34 -0
  49. package/src/host/vice/process.ts +402 -0
  50. package/src/host/vice/screen.ts +183 -0
  51. package/src/host/vice/session.ts +1316 -0
  52. package/src/host/vice/text-monitor.ts +223 -0
  53. package/src/host-client/connect.ts +238 -0
  54. package/src/host-client/tools.ts +89 -0
  55. package/src/host-client/transfer.ts +100 -0
  56. package/src/host-client/vice-session.ts +254 -0
  57. package/src/knowledge/database.ts +132 -0
  58. package/src/knowledge/history.ts +128 -0
  59. package/src/knowledge/import.ts +344 -0
  60. package/src/knowledge/read.ts +191 -0
  61. package/src/knowledge/schema.ts +140 -0
  62. package/src/knowledge/write.ts +348 -0
  63. package/src/mcp/main.ts +106 -0
  64. package/src/mcp/server.ts +307 -0
  65. package/src/mcp/tools/debug.ts +237 -0
  66. package/src/mcp/tools/execution.ts +116 -0
  67. package/src/mcp/tools/input.ts +69 -0
  68. package/src/mcp/tools/machine.ts +97 -0
  69. package/src/mcp/tools/media.ts +106 -0
  70. package/src/mcp/tools/memory.ts +203 -0
  71. package/src/mcp/tools/video.ts +260 -0
  72. package/src/native/acme.ts +116 -0
  73. package/src/native/discover.ts +141 -0
  74. package/src/native/dxa.ts +166 -0
  75. package/src/native/ghidra/analyze.ts +86 -0
  76. package/src/native/ghidra/index.ts +157 -0
  77. package/src/native/ghidra/language/c64rt_6510.cspec +34 -0
  78. package/src/native/ghidra/language/c64rt_6510.ldefs +15 -0
  79. package/src/native/ghidra/language/c64rt_6510.pspec +15 -0
  80. package/src/native/ghidra/language/c64rt_6510.slaspec +596 -0
  81. package/src/native/ghidra/scripts/C64Export.java +242 -0
  82. package/src/native/ghidra/scripts/C64Prepare.java +60 -0
  83. package/src/native/local.ts +34 -0
  84. package/src/native/processes.ts +350 -0
  85. package/src/native/run.ts +172 -0
  86. package/src/native/staging.ts +83 -0
  87. package/src/native/status.ts +101 -0
  88. package/src/native/types.ts +228 -0
  89. package/src/native/watchdog.ts +78 -0
  90. package/src/project.ts +39 -0
  91. package/src/protocol/framing.ts +132 -0
  92. package/src/protocol/messages.ts +243 -0
  93. package/src/protocol/tools.ts +247 -0
  94. package/src/protocol/vice.ts +1235 -0
  95. package/src/script.ts +59 -0
  96. package/src/time.ts +15 -0
  97. package/src/trace.ts +162 -0
  98. package/THIRD-PARTY-NOTICES.md +0 -26
  99. package/bin/cli.mjs +0 -283
  100. package/skills/acme-build/SKILL.md +0 -260
  101. package/skills/acme-build/scripts/acme.mjs +0 -341
  102. package/skills/acme-build/template.a +0 -53
  103. package/skills/c64-disk-access/SKILL.md +0 -159
  104. package/skills/c64-disk-access/scripts/c1541.mjs +0 -569
  105. package/skills/c64-memory-mapping/SKILL.md +0 -631
  106. package/skills/c64-memory-mapping/memmap.json +0 -8800
  107. package/skills/c64-memory-mapping/scripts/driver.mjs +0 -553
  108. package/skills/c64-petcat/SKILL.md +0 -87
  109. package/skills/c64-petcat/scripts/petcat.mjs +0 -221
  110. package/skills/c64-program-recon/SKILL.md +0 -706
  111. package/skills/c64-program-recon/references/control-flow.md +0 -180
  112. package/skills/c64-program-recon/references/graphics.md +0 -79
  113. package/skills/c64-program-recon/references/observation-hazards.md +0 -143
  114. package/skills/c64-program-recon/references/reconstruction.md +0 -133
  115. package/skills/c64-program-recon/references/sound-and-input.md +0 -74
  116. package/skills/c64-program-recon/references/tool-selection.md +0 -78
  117. package/skills/c64-program-recon/scripts/derive.mjs +0 -364
  118. package/skills/c64-program-recon/scripts/packer-finding.mjs +0 -709
  119. package/skills/c64-program-recon/templates/memory-map.template.md +0 -101
  120. package/skills/c64-provenance-diff/SKILL.md +0 -292
  121. package/skills/c64-provenance-diff/scripts/diff-images.mjs +0 -984
  122. package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +0 -395
  123. package/skills/c64-ram-capture/RELEASES.json.example +0 -17
  124. package/skills/c64-ram-capture/SKILL.md +0 -474
  125. package/skills/c64-ram-capture/scripts/compare-cross-binary.mjs +0 -525
  126. package/skills/c64-ram-capture/scripts/compare.mjs +0 -258
  127. package/skills/c64-ram-capture/scripts/derive-transients.mjs +0 -575
  128. package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +0 -317
  129. package/skills/c64-ram-capture/scripts/mcp-module.mjs +0 -174
  130. package/skills/c64-ram-capture/scripts/project-paths.mjs +0 -81
  131. package/skills/c64-ram-capture/scripts/releases.mjs +0 -109
  132. package/skills/c64-ram-capture/scripts/vsf-slice.mjs +0 -147
  133. package/skills/c64-ram-capture/scripts/watch-loads.mjs +0 -581
  134. package/skills/c64-ram-capture/templates/capture-record.template.md +0 -99
  135. package/skills/c64-ram-capture/transients/README.md +0 -137
  136. package/skills/routine-queue-walker/SKILL.md +0 -394
  137. package/skills/routine-queue-walker/scripts/completeness-report.mjs +0 -465
  138. package/skills/vice-wedge-triage/SKILL.md +0 -269
@@ -1,137 +0,0 @@
1
- # Per-release transient allow-lists
2
-
3
- One committed JSON artifact per release, written by
4
- `../scripts/derive-transients.mjs derive`. Nothing in this directory is
5
- inherited, hand-edited or carried across releases.
6
-
7
- **What carries forward is the method below and the script that implements it.
8
- Never an address set.** A real release's transients are its own frame counters,
9
- RNG state, sprite positions and music-player pointers. An allow-list borrowed
10
- from another release cannot be distinguished afterwards from one honestly
11
- derived, so a contaminated ledger has no cheap repair — it has to be re-derived
12
- from fresh captures.
13
-
14
- ## The committed derivation method
15
-
16
- Verbatim, because this is the part that is meant to survive:
17
-
18
- - **N >= 3 runs** of the **same release** under the **same protocol** at the
19
- **same stop**. Three runs is already this project's documented minimum for a
20
- verified capture. `derive` refuses fewer than three runs, naming the count
21
- and the minimum.
22
- - The allow-list is the **union of addresses differing across the pairwise
23
- comparisons** — every pairing, which is N(N-1)/2 for N images, not just the
24
- adjacent ones.
25
- - Each entry records **the address**, **which run pairs it differed in**, the
26
- distinct byte values seen, and **a one-line attribution where known** (left
27
- empty otherwise, so an unattributed transient is visibly unattributed rather
28
- than absent).
29
- - It is **re-derived per release**, and **no address set is ever inherited
30
- between releases**. `derive` refuses a re-derivation over an existing
31
- artifact without an explicit `--force`.
32
- - The list is **enumerated** — one entry per address, never a range, a span, a
33
- page or a region. `parseAllowList()` in
34
- `src/mcp/vice/capture-predicate.ts` refuses range-shaped keys by name.
35
- - There is **no bit-count tolerance** at any address. A one-bit difference
36
- outside the list fails.
37
-
38
- ```bash
39
- S=src/skills/c64-ram-capture/scripts # from the repo root
40
- T=src/skills/c64-ram-capture/transients
41
-
42
- node $S/derive-transients.mjs derive --release <id> \
43
- --out $T/<id>.json run1.bin run2.bin run3.bin
44
-
45
- node $S/derive-transients.mjs check --allow-list $T/<id>.json runA.bin runB.bin
46
- ```
47
-
48
- ## The artifact shape
49
-
50
- ```json
51
- {
52
- "schema_version": "1.0",
53
- "release": "<the registry id the runs came from>",
54
- "derived_from": ["run1.bin", "run2.bin", "run3.bin"],
55
- "pair_count": 3,
56
- "cap": 64,
57
- "method": "union of addresses differing across every pairwise comparison ...",
58
- "entries": [
59
- {
60
- "address": 164,
61
- "pairs": ["run1.bin vs run2.bin", "run2.bin vs run3.bin"],
62
- "values": ["$11", "$22"],
63
- "attribution": ""
64
- }
65
- ]
66
- }
67
- ```
68
-
69
- `entries` is ascending by address, so the artifact is diffable and an entry's
70
- position never depends on which pairing happened to observe it first. `address`
71
- is an integer. `values` are the distinct bytes seen at it, in hex, across every
72
- pairing it differed in. This is exactly the shape `parseAllowList()` accepts —
73
- there is no translation step between the derivation and the predicate, and the
74
- derivation's colocated test asserts the round-trip rather than assuming it.
75
-
76
- ## The cap is 64, and exceeding it VOIDS the derivation
77
-
78
- `TRANSIENT_ALLOW_LIST_CAP = 64` in `src/mcp/vice/capture-predicate.ts` is the
79
- one definition. A test asserts the script's default cap equal to it.
80
- `--cap` **only ever narrows**. `derive` refuses a value above the committed cap
81
- by name.
82
-
83
- **Over the cap is not "the list is a bit long".** It means **the stop is not
84
- frame-exact**, and that is a fact the gate must hear rather than a threshold to
85
- move. So the script exits non-zero, writes **no artifact at all** — not a
86
- partial one and not a truncated one — and says so. A list truncated to fit the
87
- cap would make every later comparison pass on bytes nobody vetted, which is the
88
- one failure this rule exists to remove. Raising the cap after seeing a
89
- derivation overflow converts a measurement into an excuse.
90
-
91
- ### What the cap actually distinguishes — four measured reference points
92
-
93
- The cap does not separate "a few transients" from "a lot of transients". It
94
- separates **a frame-exact stop** from **a stop that is not**. The numbers, all
95
- measured on this host:
96
-
97
- | Differing addresses | The stop that produced it | Verdict under the cap |
98
- |---|---|---|
99
- | **0** | frame-exact stop at the KERNAL `READY` prompt | derivation writes |
100
- | **66** | autostarted stop, frame anchor, pre-protocol jitter 4000 ms (48 for the reported jitter-0/2500 pair) | **VOID** — 2 over |
101
- | **300** | wall-clock autostarted stop on a real cracked release | **VOID** — 4.7× the cap |
102
- | **1242** | wall-clock `READY`-prompt stop with the determinism block fully applied | **VOID** — 19× the cap |
103
-
104
- Read the second row carefully: **overflow is an observed outcome, not a
105
- hypothetical.** A frame-anchored autostarted stop has already been measured
106
- over the cap at one jitter and under it at another. The answer to that is a
107
- better stop, recorded together with the jitter value — not a bigger cap.
108
-
109
- The 1242 row is also the reason the seed is not the whole story: that stop had
110
- the determinism block applied and still differed at 1242 bytes, because it was
111
- wall-clock-anchored. The residual is what the reset protocol and the frame
112
- anchor close, not what the seed does.
113
-
114
- ## `$0000` / `$0001` do not belong in a derived list by hand
115
-
116
- `normalisePorts()` normalises the 6510 port overlay **in code**, once, on
117
- the snapshot route. Spending two of the cap's 64 slots on those two addresses
118
- would hide a real difference behind a known one. They can legitimately appear in
119
- a derivation taken from un-normalised images — `derive` compares the bytes it is
120
- given — so if you see them in an artifact, that is what it is telling you about
121
- the images, not a rule to add.
122
-
123
- ## What this directory refuses, and why now
124
-
125
- `.gitignore` here refuses every image byte form — flat captures, disk and tape
126
- images, cartridges, `.vsf` snapshots, and the archives an image arrives inside —
127
- and this project committed it **before the first derivation existed**. A directory that
128
- starts refusing images after the first capture lands has already had one
129
- commit's worth of opportunity to leak one. JSON is deliberately not refused:
130
- the derived allow-lists are the artifacts this directory is for.
131
-
132
- **If you are reading this from an installed copy of the skill, that `.gitignore`
133
- did not come with it.** npm excludes a nested `.gitignore` from a published
134
- tarball, so the installed `transients/` directory has this README and nothing
135
- else. Add the same refusals to your own project's ignore rules before you take
136
- the first capture — the whole point of the timing is that it happens before,
137
- not after.
@@ -1,394 +0,0 @@
1
- ---
2
- name: routine-queue-walker
3
- description: Drive an existing C64 annotation store's backlog of undocumented routines and auto-named symbols to closure — build the candidate queue from labels and comments, work it one entry at a time against explicit addresses, rebuild it after every pass, and report every leftover. Use when asked to annotate every remaining routine in a project, document all undocumented subroutines left in an annotation project, rename the leftover auto-generated labels, clear a backlog of unnamed symbols, drive an annotation pass to completion, or list what is still unannotated after a pass.
4
- ---
5
-
6
- # Walking the routine and symbol queue to closure
7
-
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
10
- `p_XXXX` labels are still nameless and nobody wrote down which ones. Build the
11
- queue first, from data, then walk it to the end.
12
-
13
- This playbook assumes block classification has already happened and an
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*.
19
-
20
- ## The one rule that makes this different from upstream's version
21
-
22
- **Work the queue one entry at a time.** Not as a throughput compromise — as an
23
- accurate model of the store underneath. One `.annostore` is one writer. Every
24
- mutating call opens it, commits and closes inside the call, and every one of
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.
41
-
42
- ## Phase 0 — context, and the packed-binary gate
43
-
44
- 1. Call `anno_get_binary_info`. Keep `origin`, `size`, `system`, `filename`,
45
- `description` and `may_contain_undocumented_opcodes` — every later step
46
- quotes them.
47
- 2. Read the returned `entropy` against the threshold of **7.5** carried in that
48
- tool's own description. At or above it, the bytes are very likely packed.
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.
61
-
62
- ## Phase 1 — make sure blocks are classified
63
-
64
- Region classification is a prerequisite for everything below: a routine
65
- candidate is only meaningful once you know the bytes around it are code.
66
-
67
- 1. Follow `src/skills/c64-memory-mapping/SKILL.md` for the classification pass
68
- and for what each region type means.
69
- 2. Do that pass yourself, in one sitting. It is a single long walk over the
70
- whole binary, not a queue of independent items.
71
-
72
- ## Phase 2 — the routine queue
73
-
74
- ### 2.1 Build the candidate list
75
-
76
- A routine counts as **already documented** when its entry address carries a
77
- line comment. That is the only test. Do not guess from the label name.
78
-
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.
83
- 2. Call `anno_get_comments`, again with an explicit `max_results`. Keep that
84
- too. The true match count rides beside the list. So truncation is a fact
85
- you are told rather than one you infer.
86
- 3. **Candidate source A — cross-reference and block-derived, checked FIRST and
87
- independently of whatever `anno_get_symbols` returned.** Call
88
- `anno_get_blocks` with `block_type: "code"` for every code-typed range,
89
- read each one with `anno_disassemble`, and collect every `jsr` target
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.
102
- 4. **Candidate source B — the label-prefix path, for a store that DOES carry
103
- externally-imported auto-names.** Keep a label as a routine candidate when
104
- any of these holds:
105
- - its name starts with `s_` (an auto-generated subroutine label).
106
- - it sits in a code region and is the target of at least one `JSR`
107
- cross-reference (`anno_get_cross_references`).
108
- - it is a `p_XXXX` label sitting **inside a code region**. These come from
109
- split lo/hi immediate loads and from address tables. They are almost
110
- always chained raster-IRQ handlers, hardware- or shadow-vector handlers,
111
- or jump-table and callback targets. Treat every one of them as a
112
- candidate rather than pattern-matching specific vector addresses.
113
- - it is the label named exactly `start`.
114
- 5. **Union sources A and B by address** — a routine reachable both ways counts
115
- once. A store may carry either shape, or both, so neither source alone is
116
- sufficient.
117
- 6. Drop every candidate that already carries a line comment.
118
- 7. What remains is the routine queue.
119
- 8. **Order it with `start` first** when `start` is in it. The entry point sets
120
- the context you read every other routine against.
121
-
122
- ### 2.2 Walk it
123
-
124
- Take **one** entry at a time, to completion, before starting the next — the
125
- queue discipline this section owns. For each entry, run `c64-program-recon`
126
- `SKILL.md`'s **"Documenting one routine, end to end"** procedure (steps 1-7)
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.
133
-
134
- Record per entry, for Phase 4: the address, the old label, the new label, a
135
- one-line summary, and any uncertainty.
136
-
137
- ### 2.3 Refresh point
138
-
139
- When the queue is empty, read the store's revision with `anno_save_project`.
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
144
- `base_revision` a later compare-and-swap write would quote. Everything after
145
- this point re-reads the store, because Phase 2 has just changed the label
146
- names Phase 3 filters on.
147
-
148
- ## Phase 3 — the symbol queue
149
-
150
- ### 3.1 Build the candidate list
151
-
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).
155
-
156
- 1. Call `anno_get_symbols` **again** — Phase 2 renamed things.
157
- 2. **Candidate source A — cross-reference and block-derived, checked FIRST
158
- and independently of whatever label population exists.** Call
159
- `anno_get_blocks` (with `include: ["enum_usage"]` where useful) for every
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.
172
- 3. **Candidate source B — the label-prefix path, for a store that DOES carry
173
- externally-imported auto-names.** Keep every label whose name still
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).
182
- 5. **Union sources A and B by address** — a symbol reachable both ways counts
183
- once.
184
- 6. What remains is the symbol queue.
185
-
186
- ### 3.2 Walk it
187
-
188
- Same discipline as Phase 2: explicit address, one entry at a time, to
189
- completion. For each symbol, use `anno_get_cross_references` to find who
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.
193
-
194
- **No premature stopping.** The symbol queue is routinely far larger than the
195
- routine queue — fifty, a hundred entries is normal. Do not truncate it, do not
196
- skip "secondary" symbols, and do not stop early because it is long. Feeding the
197
- whole queue through is the job. Stopping early and labelling the remainder
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.
201
-
202
- For naming conventions and for what any given hardware or KERNAL address
203
- means, follow `src/skills/c64-memory-mapping/SKILL.md` rather than guessing.
204
-
205
- ### 3.3 Refresh point
206
-
207
- Read the revision again with `anno_save_project` and record it. No write is
208
- performed. The writes already landed.
209
-
210
- ## Phase 4 — save and report
211
-
212
- 1. Read the revision one last time with `anno_save_project` and quote it in
213
- the report. This makes the pass attributable to an exact store state.
214
- 2. Write the report. Four sections, all of them required:
215
-
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.
218
-
219
- **Routines.**
220
-
221
- | Address | Old label | New label | What it does |
222
- | ------- | --------- | --------- | ------------ |
223
- | `$C000` | `s_C000` | `init_screen` | Clears screen RAM, sets the border colour |
224
-
225
- **Symbols.**
226
-
227
- | Address | Old label | New label | Classification |
228
- | ------- | --------- | --------- | -------------- |
229
- | `$02` | `zpp_02` | `ptr_screen` | Zero-page indirect pointer |
230
-
231
- **Leftovers — uncertain, skipped, or still unannotated.** This section is not
232
- optional and it is not allowed to be empty when the queues were not emptied.
233
- List every routine and every symbol you left undone, with its address and
234
- the reason. Never report "no uncertain areas" or "nothing left" while a single
235
- `f_XXXX` or `a_XXXX` label is still auto-named or a queued routine is still
236
- uncommented. Those must be listed by name for a human to pick up.
237
-
238
- ## Phase 5 — measure the pass instead of asserting it finished
239
-
240
- A report that says "all routines documented" is a claim about the report, not
241
- about the program. Measure it. From the repository root:
242
-
243
- ```
244
- node src/mcp/vice/vice-proxy.ts anno coverage game.prg --store game.annostore
245
- ```
246
-
247
- **`--store` is REQUIRED and is a second path, not a spelling of the first.**
248
- `<program>` supplies the payload bytes and the load origin. `--store` names the
249
- annotation store holding the labels, comments and typed ranges. The store holds
250
- annotations and never bytes, so the verb refuses to guess either path from the
251
- other.
252
-
253
- **Dated note, 2026-08-30 — the positional is a program IMAGE, and the command
254
- above is now correct against the shipped verb.** `<program>` is a `.prg` (a
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.
269
-
270
- Add `--out coverage.json` to keep the machine-readable report, `--force` to
271
- overwrite one, and `--sample N` to widen the reproducibility sample. The verb
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.
276
-
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.
284
-
285
- **Read the three numbers against each other. Never quote one of them alone.**
286
- There is deliberately no single "percent documented" figure, because one
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.
296
- - **A large divergence means the store and the bytes disagree about what is
297
- code.** Bytes the census reached as instructions that the store does not call
298
- `Code` are places where Phase 1's classification is behind the actual control
299
- flow. The reverse direction (the store calls it `Code`, the census never
300
- reached it) is ordinary on an image with unreachable filler. Read it, do
301
- not chase it.
302
- - **A low distinct-comment ratio means the comments are filler.** Fifty
303
- addresses carrying the same sentence counts once, not fifty times. That is
304
- the number that catches a pass which renamed everything and explained
305
- nothing.
306
-
307
- Anything the per-measure findings list names belongs in Phase 4's leftovers
308
- table, by address. A finding is a named defect in one named measure — it is
309
- never a rating, and there is no number to report as "the coverage".
310
-
311
- ### The decomposition-completeness gate
312
-
313
- This is a DIFFERENT, non-overlapping measurement from the `anno coverage`
314
- call above — neither replaces the other. `anno coverage` is the byte-census
315
- and label-ratio instrument: a derived-from-bytes census this store's own
316
- block table cannot move. `anno decomp-completeness` is the
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.
326
-
327
- ```
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
329
- ```
330
-
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.
342
-
343
- **The stop condition is a measured exit code, not a belief.** The walk
344
- described in Phases 2-4 above finishes for a fixture when `node
345
- src/skills/routine-queue-walker/scripts/completeness-report.mjs --store
346
- <fixture>.annostore --disagreements <fixture>-disagreements.json --manifest
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.
359
-
360
- ## When something fails
361
-
362
- - A failed call is not a reason to drop a queue entry. Log the address, the
363
- call and the error. Put the entry back on the queue, and carry on with the
364
- next one. Report every one of those in the leftovers table.
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.
378
- - **A genuinely unresolvable target gets a `DECLINED:` comment, never a
379
- fabricated name.** When a referenced address's target is truly
380
- path-dependent or otherwise cannot be determined, record it with
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
387
- byte-derived block table calls `data` but the runtime evidence shows
388
- executing. Also, a review finds the runtime evidence correct. Or the
389
- disagreement is otherwise a reviewed, accepted fact rather than a
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.