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

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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -37
  3. package/distribution/package.ts +21 -0
  4. package/distribution/plugin.ts +122 -0
  5. package/package.json +34 -40
  6. package/skills/c64-assembler/SKILL.md +76 -0
  7. package/skills/c64-assembler/scripts/assemble.ts +110 -0
  8. package/skills/c64-assembler/scripts/compare.ts +84 -0
  9. package/skills/c64-basic/SKILL.md +66 -0
  10. package/skills/c64-basic/scripts/basic.ts +46 -0
  11. package/skills/c64-disk/SKILL.md +93 -0
  12. package/skills/c64-disk/scripts/containers.ts +124 -0
  13. package/skills/c64-disk/scripts/disk.ts +120 -0
  14. package/skills/c64-emulator/SKILL.md +134 -0
  15. package/skills/c64-knowledge/SKILL.md +166 -0
  16. package/skills/c64-knowledge/scripts/knowledge.ts +492 -0
  17. package/skills/c64-memory-map/SKILL.md +79 -0
  18. package/skills/c64-memory-map/scripts/memmap.ts +92 -0
  19. package/skills/c64-provenance/SKILL.md +110 -0
  20. package/skills/c64-provenance/scripts/compare.ts +152 -0
  21. package/skills/c64-provenance/scripts/provenance.ts +334 -0
  22. package/skills/c64-provenance/scripts/runtime.ts +191 -0
  23. package/skills/c64-provenance/scripts/separate.ts +140 -0
  24. package/skills/c64-reverse-engineering/SKILL.md +144 -0
  25. package/skills/c64-source/SKILL.md +126 -0
  26. package/skills/c64-source/scripts/generate.ts +490 -0
  27. package/skills/c64-source/scripts/loader.ts +571 -0
  28. package/skills/c64-source/scripts/opcodes.ts +61 -0
  29. package/skills/c64-source/scripts/source.ts +595 -0
  30. package/skills/c64-static-analysis/SKILL.md +119 -0
  31. package/skills/c64-static-analysis/scripts/analyze.ts +275 -0
  32. package/skills/c64-static-analysis/scripts/coverage.ts +78 -0
  33. package/skills/c64-static-analysis/scripts/findings.ts +75 -0
  34. package/skills/c64-static-analysis/scripts/idioms.ts +149 -0
  35. package/skills/c64-static-analysis/scripts/instructions.ts +105 -0
  36. package/skills/c64-testing/SKILL.md +103 -0
  37. package/skills/c64-testing/references/scenario-format.md +116 -0
  38. package/skills/c64-testing/scripts/checklist.ts +51 -0
  39. package/skills/c64-testing/scripts/run.ts +189 -0
  40. package/skills/c64-testing/scripts/scenario.ts +493 -0
  41. package/skills/c64-testing/scripts/test.ts +65 -0
  42. package/skills/c64-unpacker/SKILL.md +135 -0
  43. package/skills/c64-unpacker/scripts/evidence.ts +207 -0
  44. package/skills/c64-unpacker/scripts/loads.ts +209 -0
  45. package/skills/c64-unpacker/scripts/machine.ts +80 -0
  46. package/skills/c64-unpacker/scripts/stages.ts +130 -0
  47. package/skills/c64-unpacker/scripts/unpack.ts +683 -0
  48. package/src/c64.ts +411 -0
  49. package/src/cli/ap-sdk.ts +44 -0
  50. package/src/cli/cleanup.ts +194 -0
  51. package/src/cli/main.ts +125 -0
  52. package/src/cli/status.ts +61 -0
  53. package/src/host/main.ts +134 -0
  54. package/src/host/server.ts +392 -0
  55. package/src/host/tools/c1541.ts +284 -0
  56. package/src/host/tools/index.ts +35 -0
  57. package/src/host/tools/petcat.ts +77 -0
  58. package/src/host/tools/status.ts +47 -0
  59. package/src/host/tools/version.ts +41 -0
  60. package/src/host/vice/adapter.ts +804 -0
  61. package/src/host/vice/binary-monitor.ts +389 -0
  62. package/src/host/vice/chips.ts +118 -0
  63. package/src/host/vice/connect.ts +34 -0
  64. package/src/host/vice/process.ts +413 -0
  65. package/src/host/vice/screen.ts +184 -0
  66. package/src/host/vice/session.ts +1426 -0
  67. package/src/host/vice/text-monitor.ts +223 -0
  68. package/src/host-client/autostart.ts +55 -0
  69. package/src/host-client/connect.ts +241 -0
  70. package/src/host-client/coverage.ts +63 -0
  71. package/src/host-client/steps.ts +170 -0
  72. package/src/host-client/tools.ts +89 -0
  73. package/src/host-client/transfer.ts +126 -0
  74. package/src/host-client/vice-session.ts +316 -0
  75. package/src/knowledge/database.ts +132 -0
  76. package/src/knowledge/history.ts +163 -0
  77. package/src/knowledge/import.ts +391 -0
  78. package/src/knowledge/read.ts +342 -0
  79. package/src/knowledge/schema.ts +229 -0
  80. package/src/knowledge/write.ts +663 -0
  81. package/src/mcp/main.ts +134 -0
  82. package/src/mcp/output.ts +32 -0
  83. package/src/mcp/server.ts +316 -0
  84. package/src/mcp/tools/debug.ts +327 -0
  85. package/src/mcp/tools/execution.ts +117 -0
  86. package/src/mcp/tools/input.ts +72 -0
  87. package/src/mcp/tools/machine.ts +100 -0
  88. package/src/mcp/tools/media.ts +129 -0
  89. package/src/mcp/tools/memory.ts +250 -0
  90. package/src/mcp/tools/video.ts +271 -0
  91. package/src/native/acme.ts +116 -0
  92. package/src/native/discover.ts +179 -0
  93. package/src/native/dxa.ts +166 -0
  94. package/src/native/ghidra/analyze.ts +86 -0
  95. package/src/native/ghidra/index.ts +173 -0
  96. package/src/native/ghidra/language/c64rt_6510.cspec +34 -0
  97. package/src/native/ghidra/language/c64rt_6510.ldefs +15 -0
  98. package/src/native/ghidra/language/c64rt_6510.pspec +15 -0
  99. package/src/native/ghidra/language/c64rt_6510.slaspec +596 -0
  100. package/src/native/ghidra/scripts/C64Export.java +274 -0
  101. package/src/native/ghidra/scripts/C64Prepare.java +60 -0
  102. package/src/native/local.ts +34 -0
  103. package/src/native/processes.ts +350 -0
  104. package/src/native/run.ts +172 -0
  105. package/src/native/staging.ts +83 -0
  106. package/src/native/status.ts +113 -0
  107. package/src/native/types.ts +257 -0
  108. package/src/native/watchdog.ts +78 -0
  109. package/src/package.ts +21 -0
  110. package/src/platform.ts +441 -0
  111. package/src/project.ts +39 -0
  112. package/src/protocol/framing.ts +132 -0
  113. package/src/protocol/messages.ts +259 -0
  114. package/src/protocol/tools.ts +266 -0
  115. package/src/protocol/vice.ts +1348 -0
  116. package/src/script.ts +60 -0
  117. package/src/time.ts +15 -0
  118. package/src/trace.ts +194 -0
  119. package/THIRD-PARTY-NOTICES.md +0 -26
  120. package/bin/cli.mjs +0 -283
  121. package/skills/acme-build/SKILL.md +0 -260
  122. package/skills/acme-build/scripts/acme.mjs +0 -341
  123. package/skills/acme-build/template.a +0 -53
  124. package/skills/c64-disk-access/SKILL.md +0 -159
  125. package/skills/c64-disk-access/scripts/c1541.mjs +0 -569
  126. package/skills/c64-memory-mapping/SKILL.md +0 -631
  127. package/skills/c64-memory-mapping/memmap.json +0 -8800
  128. package/skills/c64-memory-mapping/scripts/driver.mjs +0 -553
  129. package/skills/c64-petcat/SKILL.md +0 -87
  130. package/skills/c64-petcat/scripts/petcat.mjs +0 -221
  131. package/skills/c64-program-recon/SKILL.md +0 -706
  132. package/skills/c64-program-recon/references/control-flow.md +0 -180
  133. package/skills/c64-program-recon/references/graphics.md +0 -79
  134. package/skills/c64-program-recon/references/observation-hazards.md +0 -143
  135. package/skills/c64-program-recon/references/reconstruction.md +0 -133
  136. package/skills/c64-program-recon/references/sound-and-input.md +0 -74
  137. package/skills/c64-program-recon/references/tool-selection.md +0 -78
  138. package/skills/c64-program-recon/scripts/derive.mjs +0 -364
  139. package/skills/c64-program-recon/scripts/packer-finding.mjs +0 -709
  140. package/skills/c64-program-recon/templates/memory-map.template.md +0 -101
  141. package/skills/c64-provenance-diff/SKILL.md +0 -292
  142. package/skills/c64-provenance-diff/scripts/diff-images.mjs +0 -984
  143. package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +0 -395
  144. package/skills/c64-ram-capture/RELEASES.json.example +0 -17
  145. package/skills/c64-ram-capture/SKILL.md +0 -474
  146. package/skills/c64-ram-capture/scripts/compare-cross-binary.mjs +0 -525
  147. package/skills/c64-ram-capture/scripts/compare.mjs +0 -258
  148. package/skills/c64-ram-capture/scripts/derive-transients.mjs +0 -575
  149. package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +0 -317
  150. package/skills/c64-ram-capture/scripts/mcp-module.mjs +0 -174
  151. package/skills/c64-ram-capture/scripts/project-paths.mjs +0 -81
  152. package/skills/c64-ram-capture/scripts/releases.mjs +0 -109
  153. package/skills/c64-ram-capture/scripts/vsf-slice.mjs +0 -147
  154. package/skills/c64-ram-capture/scripts/watch-loads.mjs +0 -581
  155. package/skills/c64-ram-capture/templates/capture-record.template.md +0 -99
  156. package/skills/c64-ram-capture/transients/README.md +0 -137
  157. package/skills/routine-queue-walker/SKILL.md +0 -394
  158. package/skills/routine-queue-walker/scripts/completeness-report.mjs +0 -465
  159. package/skills/vice-wedge-triage/SKILL.md +0 -269
@@ -1,101 +0,0 @@
1
- # Memory map generation
2
-
3
- **A generator writes the memory map. Nobody hand-authors it.** The store — labels, comments, block
4
- types and scopes written through the `anno_*` 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.
7
-
8
- Run the generator once findings are in the store:
9
-
10
- ```bash
11
- npx -y @henols/vice-mcp anno render-memmap game.annostore --provenance sidecar.json
12
- node <plugin-root>/src/mcp/vice/vice-proxy.ts anno render-memmap game.annostore --provenance sidecar.json
13
- ```
14
-
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. `--check` reports drift when, and only when, one of these changed:
17
-
18
- - the rendered file itself (a hand edit)
19
- - a store row (a range, a label, a comment, or a comment's confidence grade)
20
- - the provenance sidecar's bytes
21
- - the location of the store or the sidecar **relative to the workspace root**
22
- - the renderer
23
-
24
- **Relocating the checkout is not drift** — the same tree at a different absolute path renders the
25
- same bytes, because the banner records workspace-relative locations rather than absolute ones.
26
-
27
- There is no way to "fix" drift by editing the rendered file directly: the fix is always to re-run
28
- the generator (or, if the sidecar itself is stale, fix it and re-run). That remedy is safe to
29
- follow on any machine — because the banner records workspace-relative locations, re-running in a
30
- different checkout or in a worktree does not rewrite the file with that machine's absolute paths.
31
- The generated file carries a banner naming the store, the sidecar and a content digest — do not
32
- strip it.
33
-
34
- **One-time drift after upgrading, 2026-08-30.** A memory map rendered *before* 2026-08-30 will
35
- report `drifted` on its first `--check` after this change, exactly once: the banner's `store:` and
36
- `sidecar:` lines moved from absolute to workspace-relative spellings. Re-run the generator once and
37
- commit the new banner, after which the file is stable across machines and worktrees. This is a
38
- one-time, self-clearing banner correction — not a bug, and not a migration.
39
-
40
- ## The provenance sidecar
41
-
42
- Some facts belong to the **run** (which capture, which `$01`, which video standard) rather than to
43
- any address, and the store has no address-keyed shape for them. They are supplied to the
44
- renderer as a small JSON sidecar, hand-authored from `c64-ram-capture`'s and `derive.mjs`'s own
45
- outputs and confirmed by the renderer — a missing or malformed key is a named error listing every
46
- problem at once, never a `<placeholder>` silently rendered into a published document.
47
-
48
- | Key | Type | Where it comes from |
49
- |---|---|---|
50
- | `capturePath` | string | The path to the captured 64K image, as given to `c64-ram-capture` |
51
- | `captureSha256` | string, 64 hex chars | `compare.mjs digest`'s `sha256` — proves which image the map describes |
52
- | `port01` | string | `derive.mjs vectors`' `$01` value |
53
- | `dd00` | string | `derive.mjs vic`'s `--dd00` input, i.e. the observed `$DD00` |
54
- | `vicBank` | string | `derive.mjs vic` — VIC bank derived from `$DD00` bits 0-1, inverted |
55
- | `screenRam` | string | `derive.mjs vic` — screen RAM derived from `$D018` bits 4-7 |
56
- | `charsetOrBitmap` | string | `derive.mjs vic` — charset/bitmap derived from `$D018` bits 1-3 (note the char-ROM shadow case) |
57
- | `mode` | string | `derive.mjs vic` — graphics mode derived from `$D011` bits 5-6 and `$D016` bit 4 |
58
- | `videoStandard` | `"PAL"` or `"NTSC"` | Known from the capture's origin/hardware context |
59
- | `liveVectorPair` | string | `derive.mjs vectors` — the live vector pair (`$0314/$0315` or `$FFFE/$FFFF`) |
60
- | `vectorHandler` | string | The address the live vector pair points at, confirmed live at a checkpoint |
61
- | `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 |
62
-
63
- A fully-filled example, with plausible values in place of placeholders — copy this shape, never the
64
- literal values:
65
-
66
- ```json
67
- {
68
- "capturePath": "captures/game.raw",
69
- "captureSha256": "3f8a1c9e2b7d4a6f0c5e8b2d9a1f4c7e6b3d0a9c8f5e2b1d4a7c0f3e6b9d2a5c",
70
- "port01": "$40",
71
- "dd00": "$06",
72
- "vicBank": "1 ($4000-$7FFF)",
73
- "screenRam": "$0400",
74
- "charsetOrBitmap": "$1000 (ROM shadow)",
75
- "mode": "text, multicolor off",
76
- "videoStandard": "PAL",
77
- "liveVectorPair": "$FFFE/$FFFF",
78
- "vectorHandler": "$1103",
79
- "rasterPositions": ["$F8", "$00"]
80
- }
81
- ```
82
-
83
- ## Confidence vocabulary
84
-
85
- Every comment written into the store through `anno_set_comment` that grades a finding leads with
86
- one of these five bracket tokens (the parser in `anno-confidence.ts` throws on anything that is
87
- close but not exact — a typo never silently degrades into an ungraded comment):
88
-
89
- | Grade | Bracket token | Means |
90
- |---|---|---|
91
- | **confirmed code** | `[confirmed-code]` | Executed during tracing, PC observed inside it |
92
- | **probable code** | `[probable-code]` | Reachable through a `JSR`/`JMP`/vector, not yet observed executing |
93
- | **confirmed data** | `[confirmed-data]` | Never hit as an instruction stream across full gameplay coverage |
94
- | **probable data** | `[probable-data]` | Indexed-load target, or matches a data shape (sprite blocks, PETSCII, address tables) |
95
- | **unknown** | `[unknown]` | No reliable interpretation yet |
96
-
97
- Do not force an unknown range through a disassembler and record the output as code. A linear
98
- decode of data is silently wrong and contaminates everything downstream.
99
-
100
- **Do not promote a row by editing its grade.** Re-confirm and restate the evidence with a fresh
101
- `anno_set_comment` call, so the record of when something stopped being a guess survives.
@@ -1,292 +0,0 @@
1
- ---
2
- name: c64-provenance-diff
3
- description: Decide whether a byte in a cracked C64 release is original game code or something a cracker changed, by diffing two or more independently-cracked releases at an anchor-proven offset. Use when asked to diff two releases or disk images, work out which bytes the cracker patched, tell loader or cracktro code from game code, prove a byte is original, establish provenance or confidence for a memory range, regenerate the provenance ledger, or run anchor-search, count-patches or diff-images. Also use when asked whether a crack added a trainer or cheat, whether a patch changes gameplay rather than loading, whether a rebuild would inherit a cracker's gameplay alteration, or whether two releases are genuinely independent rather than sharing an ancestor.
4
- ---
5
-
6
- # Deciding what a cracker changed
7
-
8
- **A byte that differs between two releases is not a cracker patch.** It is a byte
9
- that differs. This pipeline exists because the gap between those two statements is
10
- where confident nonsense gets manufactured — `scripts/diff-images.mjs`'s own header
11
- calls it "the step most able to produce confident nonsense". Every stage below
12
- either proves its own precondition or refuses to emit.
13
-
14
- **Run the four verbs in order.** `diff` is meaningless without a proven offset, and
15
- `ledger` will not write a verdict the earlier stages did not earn.
16
-
17
- ```bash
18
- D=src/skills/c64-provenance-diff/scripts/diff-images.mjs # from the repo root
19
-
20
- node $D anchor-search # 1. prove the per-release offset [WRITES]
21
- node $D diff # 2. N-way byte diff at that offset
22
- node $D count-patches # 3. CRACKER-PATCH addresses in game code
23
- node $D ledger # 4. regenerate recovery/PROVENANCE.md [WRITES]
24
-
25
- node $D diff --json # machine-readable, with per-range reasons
26
- node $D diff --gap-tolerance 16 # coalescing width (default shown)
27
- node $D anchor-search --reference <id> # pick the reference release
28
- node src/skills/c64-provenance-diff/scripts/releases.mjs list # the release ids in play
29
- ```
30
-
31
- Pure Node over committed files — the `.bin` dumps, their `.map.json` manifests, and
32
- `recovery/RELEASES.json`. It contacts nothing.
33
-
34
- ## The order
35
-
36
- | # | Verb | Proves | Refuses to |
37
- |---|---|---|---|
38
- | 1 | `anchor-search` | A single global offset per release, from long distinctive byte runs located with `Buffer.indexOf` | Accept a **majority** vote — every usable anchor must agree, or there is no offset |
39
- | 2 | `diff` | Which ranges differ, coalesced on verdict continuity | Diff at an assumed offset |
40
- | 3 | `count-patches` | How many addresses are `CRACKER-PATCH` **and** `game`-kind | Count a patch outside game code |
41
- | 4 | `ledger` | The generated tier of `recovery/PROVENANCE.md` | Emit rather than launder an assumption |
42
-
43
- ## Two verbs write to tracked files
44
-
45
- `anchor-search` updates `recovery/RELEASES.json`. `ledger` rewrites
46
- `recovery/PROVENANCE.md` and touches `RELEASES.json` too. So `git status` is
47
- **expected** to be dirty after a run.
48
-
49
- What matters is *what* changed. A clean re-run produces a **timestamp-only** diff —
50
- `proven_at` and `generated_at`. Anything else is a real change:
51
-
52
- ```bash
53
- git diff -- recovery/RELEASES.json recovery/PROVENANCE.md
54
- ```
55
-
56
- If the only `-`/`+` pairs are those two fields, revert the churn and move on. If
57
- `offset`, `anchor_count`, `anchors_agreeing` or `generated_tier_sha256` moved, stop
58
- and find out why before committing — that is the pipeline telling you the evidence
59
- changed.
60
-
61
- ## Worked example — the real corpus
62
-
63
- Two independently-cracked releases of one title, both captured at the same
64
- post-loader entry trigger. **This example shows the release ids below as
65
- `release-a` and `release-b`.** **Every number is real output from a live run
66
- against a two-release corpus, with only the ids renamed** — the tool has no
67
- opinion about what you call a release.
68
-
69
- ```
70
- $ node $D anchor-search
71
- release-a -> release-b: ok=true offset=0 (all 7 usable anchor(s) agree on offset 0)
72
-
73
- $ node $D diff
74
- diff: 204 range(s), gap_tolerance=16, coalesced=260
75
-
76
- $ node $D count-patches
77
- release-a: 0
78
- release-b: 0
79
- ```
80
-
81
- **204 differing ranges and zero cracker patches.** The verdict tally from
82
- `diff --json` is `{"UNKNOWN": 102, "ORIGINAL": 102}` — nothing reached
83
- `CRACKER-PATCH` at all. That is the pipeline working, not failing.
84
-
85
- Read it as: 102 ranges are identical across two independently-cracked releases, so
86
- they are `ORIGINAL` with real evidence behind the word. The other 102 differ, match
87
- no cracker signature, and are therefore `UNKNOWN` — and each carries a `reason`
88
- naming the alternatives it ruled out:
89
-
90
- > differs across 2 release(s) (release-a, release-b) with no recognised cracker
91
- > signature … not a revision difference … not a `.d64` read error … not a packer
92
- > artifact … not relocation (the anchor-proven offset for this pair is recorded
93
- > above and used here).
94
-
95
- `UNKNOWN` with a rule-out list is the honest answer. Do not upgrade it to
96
- `CRACKER-PATCH` because a byte differs. **Confidence: HIGH** — run live against the
97
- committed corpus. `ledger` reproduced the committed
98
- `generated_tier_sha256 dc7eb080…` byte-identically, so the classification is
99
- deterministic.
100
-
101
- **One qualifier on the 102, and it is the example's premise rather than its output:**
102
- This example asserts "independently-cracked" at the top, and does not prove it.
103
- The determinism is HIGH. The `ORIGINAL` verdicts inherit whatever confidence
104
- that independence claim carries. See § *The independence precondition* below
105
- before quoting an `ORIGINAL` count as settled.
106
-
107
- ## The five kinds and the three verdicts
108
-
109
- `bucketManifest` promotes a manifest from `ranges-only` to `bucketed`, assigning
110
- `game` / `loader` / `cracktro` / `io` / `unused`. Verdicts are `ORIGINAL`,
111
- `CRACKER-PATCH`, `UNKNOWN`, carrying `HIGH` or `MEDIUM-HIGH` confidence.
112
-
113
- The two seeds are where this goes wrong, and both failure modes are on record:
114
-
115
- - **`bucketManifest` seeds `loader` from `RELEASES.json`'s earned `loader_ranges`**
116
- — live disassembly evidence — **never from `NOTES.md` prose.** Reading a loader
117
- range out of prose once classified `$08F5`, a permanent joystick-poll
118
- instruction, as loader code — the documented root cause.
119
- - **`bucketManifest` seeds `cracktro` from a crack-credit *vocabulary* scan**, not
120
- a bare printable-ASCII scan. A bare scan produced a real false positive against
121
- a real corpus: **the game's own title-screen text** is printable ASCII
122
- too, and it differed between the two releases. A bare scan called that cracker
123
- credit. It is not — a differing string is not a cracker string, and it is
124
- correctly left `UNKNOWN`.
125
-
126
- `io` (`$D000-$DFFF`) and `unused` (contiguous `$00`/`$FF` power-on runs) are
127
- assigned at capture time and kept verbatim. Everything the trace reaches is `game`.
128
-
129
- The `.bin` files are **never** edited or zeroed. Classification lives in
130
- the manifests. The bytes stay verbatim evidence.
131
-
132
- ## Carrying the verdict into the rebuild
133
-
134
- A verdict recorded here does not stay here. `anno export-asm`'s `--ledger` flag
135
- reads THIS skill's generated `recovery/PROVENANCE.md` — never
136
- re-deriving anything — and carries every covered range's Verdict and Confidence
137
- into the exported ACME source as an inline comment on the block that range
138
- overlaps:
139
-
140
- ```
141
- anno export-asm game.prg --store game.annostore --ledger recovery/PROVENANCE.md
142
- ```
143
-
144
- The `<image>` positional wants one of `.prg`/`.raw`/`.bin`, `--store` an
145
- `.annostore`/`.store` file, and `--ledger` the `.md` this skill's own `ledger`
146
- verb writes.
147
-
148
- **The flag makes the verdict VISIBLE and decides nothing.** Every byte in
149
- scope is still emitted, whatever the verdict says — a `CRACKER-PATCH` row does
150
- not drop, filter or change a single byte, it only makes the evidence readable
151
- at the point of use. What gets reversed, kept or left out remains the
152
- end-user's decision, never the tool's. `--ledger` is optional: omitting it
153
- exports exactly as before, with no provenance comment anywhere in the output.
154
-
155
- **When the operator, reading the ledger, decides a range genuinely should be
156
- left out of the rebuild output** — a trainer patch, a cracktro block, anything
157
- they choose — the round trip runs entirely on the `anno_*` MCP surface, never
158
- by editing the ledger or the export: `anno_exclude_range` records the span
159
- WITH the reason the operator gave, and `anno_include_range` takes the record
160
- back if the decision changes. Recording an exclusion changes nothing about
161
- which bytes the export emits — the exported block still carries every byte
162
- of that span, now with a visible marker naming the exclusion and its reason,
163
- so the export removes nothing and no gap appears in the output. The ledger's
164
- verdict is information the operator reads at this point. It is never wired as
165
- an input to an automatic exclusion, here or anywhere else on this surface.
166
-
167
- ## A `CRACKER-PATCH` in `game` code is a trainer until proven otherwise
168
-
169
- `count-patches` counts exactly one intersection — verdict `CRACKER-PATCH`, kind
170
- `game`. That intersection has a name the pipeline never says out loud: a **trainer**.
171
- A cracker who changes bytes *inside game code* changes gameplay, and unlimited
172
- lives, disabled collision or a frozen timer is the usual reason.
173
-
174
- This matters because the three verdicts answer **who wrote a range**, not **what it
175
- does**. A relocated loader stub and a life-decrement patched to a `NOP` both come
176
- back `CRACKER-PATCH`. So give every patch a **function verdict** alongside its origin
177
- verdict:
178
-
179
- | Function | Means | Why it matters |
180
- |---|---|---|
181
- | `loader` | raw-sector loading, decrunch, relocation, drive code | an obstacle to get past, not a subject |
182
- | `cracktro` | intro, scroller, music, the crack's own presentation | not the object of study |
183
- | `gameplay` | reads or writes game state — **a trainer** | any rebuild copying these bytes inherits it |
184
- | `unknown` | not yet attributed | scrutinise before trusting |
185
-
186
- **A rebuild reconstructed from these bytes inherits a `gameplay` patch silently**, and
187
- behaviour-only verification will not catch it: the baselines come from the same
188
- cracked image, so the rebuild and its reference agree *while both differ from the
189
- game as it shipped*.
190
-
191
- ### The independence precondition — this skill's own premise
192
-
193
- `ORIGINAL` means "identical across two **independently**-cracked releases". Remove the
194
- word *independently* and the verdict is worthless: two releases sharing an ancestor
195
- are identical everywhere the ancestor was, **including everywhere the ancestor's
196
- cracker patched**. Establish the independence. Do not infer it from two releases
197
- carrying different group names, different loaders, or different cracktros — those are
198
- the cheapest things for a re-cracker to replace.
199
-
200
- Until you establish it, the diff is directional, and the direction is the trap:
201
-
202
- - A diff **hit** is informative — something was patched.
203
- - A diff **miss** is not, and it is the miss that reads as reassurance.
204
-
205
- So `count-patches` reporting `0` is not evidence that no trainer exists. It is
206
- evidence that no trainer exists **in one release and not the other**. With unproven
207
- ancestry those are different claims, and the diff tested only the second one.
208
-
209
- ### The detector that does not depend on the diff
210
-
211
- A signature hunt over the canonical image, read against the coverage map rather than
212
- against another release. None of it needs a second release at all, which is precisely
213
- why it survives the shared-ancestor case:
214
-
215
- 1. **Writes to a consequence counter from an unexpected site.** Once the memory map
216
- names what the game decrements on failure — lives, timer, health — every writer
217
- that is not the game's own is a candidate. Search **every addressing form that can
218
- reach the address**, indexed included. An absolute-mode-only search is the standard
219
- way this hunt returns a false negative.
220
- 2. **Armed but never reached.** Code jumped to from a patched region that never
221
- executes across full gameplay coverage is either dead crack scaffolding or a
222
- trainer waiting on a trigger. Both need a verdict. Neither should be reproduced
223
- without one.
224
- 3. **Trigger scanners.** Reads of the keyboard or joystick registers in code that is
225
- not the game's own input handler, and comparisons against key codes inside a range
226
- already marked `CRACKER-PATCH`.
227
- 4. **`NOP` sleds and inverted branches.** The cheapest trainer is a patched-out check
228
- — `EA EA EA` where a `JSR` or a decrement was, or a `BEQ`↔`BNE` flip on a collision
229
- or life test. These show as a few bytes inside otherwise-original code: the pattern
230
- most easily dismissed as noise, and the one that matters most.
231
-
232
- **A negative is a result, and must state its own limits.** "No trainer found, by these
233
- four signatures, at this coverage level" is an answer. "The diff was clean" is not.
234
-
235
- ## Before you trust a verdict
236
-
237
- - **Coverage is incomplete, and the ledger says so out loud.**
238
- a load-coverage record is not a finished coverage claim until you have actually
239
- visited every game state. An on-demand-loaded
240
- region — bytes that only appear after reaching a room or state nobody visited —
241
- is by construction **absent from the primary dumps this diffs**. Every verdict is
242
- scoped to "the addresses visible at the post-loader game-entry point", not to the
243
- whole running game. This is exactly why the ledger is regenerable: a more
244
- complete `LOADING.md` reopens it.
245
- - **Never resolve a range's `kind` from its `start` address.** Coalescing groups on
246
- *verdict* continuity, not *kind* continuity, so one range can span several kind
247
- zones. `splitRangeByManifestKind` exists for this. This bug appeared in a live
248
- run: a wide `ORIGINAL` range ran straight through a `loader` sub-range nested
249
- inside it. Resolving from `start` silently mislabels every address after
250
- the first boundary.
251
- - **`--gap-tolerance` is off-by-one sensitive by design.** A gap of identical bytes
252
- *strictly shorter* than N coalesces. A run of *exactly* N stays its own row.
253
- - **More agreeing independent releases is the only thing that raises confidence.**
254
- Two releases can establish `ORIGINAL`. They cannot establish intent. And *agreeing*
255
- only counts once *independent* is proven — releases sharing an ancestor agree on
256
- the ancestor's patches too, so unproven ancestry makes every `ORIGINAL` verdict
257
- conditional rather than earned.
258
-
259
- ## Which skill does what
260
-
261
- This one answers "is this byte original?". It does not capture images or read
262
- addresses.
263
-
264
- | Need | Go to |
265
- |---|---|
266
- | A verified 64K image, or proving two captures equivalent | `c64-ram-capture` |
267
- | Which address to read next, and what the answer rules out | `c64-program-recon` |
268
- | What a specific address or bit means | `c64-memory-mapping` — `node … lookup '$D018'` |
269
- | Assembling | `acme-build` |
270
- | **Whether a byte is original, cracker-changed, or unknown** | here |
271
-
272
- Record findings that make RE faster in your own project notes **at the moment you
273
- find them**, graded with `Evidence:` and `Confidence:`. Promote a finding by
274
- re-logging it with the new evidence, never by editing an old grade in place — the
275
- grade is only worth anything if it says what you actually knew when you wrote it.
276
-
277
- ## Troubleshooting
278
-
279
- | Symptom | What to do |
280
- |---|---|
281
- | `anchor-search` reports `ok=false` | Anchors disagreed, so there is no single offset. Do **not** pick the majority — the images are not the same fully-loaded state, or one capture is bad. Re-capture rather than force it. |
282
- | `git status` dirty after a run | Expected — two verbs write. Diff the two files. If only `proven_at`/`generated_at` moved, `git checkout --` them. |
283
- | `generated_tier_sha256` changed | The classification changed, not just a timestamp. Find the cause before committing. The digest is the determinism check. |
284
- | `count-patches` reports 0 | Usually accurate. It counts `CRACKER-PATCH` **and** `game`-kind addresses. With two releases and no signature match, nothing qualifies. Check the `diff --json` tally before treating it as a bug. But do not read it as "no trainer" — see the next two rows. |
285
- | Asked whether the crack added a trainer | `count-patches` is the diff-side answer: `CRACKER-PATCH` ∧ `game` **is** the trainer count. It is necessary and not sufficient — it cannot see a trainer both releases carry. Run the signature hunt too. |
286
- | Asked to prove a release has no trainer | You cannot prove that from a diff alone, and saying so is the answer. A clean diff only rules out a trainer in *one* release and not the other. With unproven ancestry that is a weaker claim than it sounds. Report the signature-hunt result with its coverage limits. |
287
- | Two releases agree everywhere suspicious | Suspect shared ancestry before concluding `ORIGINAL`. Different group names, loaders and cracktros are the cheapest things for a re-cracker to swap and prove nothing about independence. |
288
- | Everything is `UNKNOWN` | Also usually accurate. `UNKNOWN` means "differs, no recognised signature, alternatives ruled out". Read the range's `reason` field. |
289
- | A range's `kind` looks wrong past its start | You resolved `kind` from `start`. Use `splitRangeByManifestKind`. Coalescing does not respect kind boundaries. |
290
- | A loader range disagrees with `NOTES.md` | `RELEASES.json`'s `loader_ranges` wins because live disassembly evidence earns it. Prose is how `$08F5` got misclassified. |
291
- | Title-screen text shows up as cracktro | You used a bare printable-run scan. The vocabulary scan exists because `$4771-$4779` is the game's own text. |
292
- | `unknown release "x" -- known releases: …` | `node src/skills/c64-provenance-diff/scripts/releases.mjs list` for the valid ids. |