@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.
- package/LICENSE +21 -0
- package/README.md +154 -37
- package/distribution/package.ts +21 -0
- package/distribution/plugin.ts +122 -0
- package/package.json +34 -40
- package/skills/c64-assembler/SKILL.md +76 -0
- package/skills/c64-assembler/scripts/assemble.ts +110 -0
- package/skills/c64-assembler/scripts/compare.ts +84 -0
- package/skills/c64-basic/SKILL.md +66 -0
- package/skills/c64-basic/scripts/basic.ts +46 -0
- package/skills/c64-disk/SKILL.md +93 -0
- package/skills/c64-disk/scripts/containers.ts +124 -0
- package/skills/c64-disk/scripts/disk.ts +120 -0
- package/skills/c64-emulator/SKILL.md +134 -0
- package/skills/c64-knowledge/SKILL.md +166 -0
- package/skills/c64-knowledge/scripts/knowledge.ts +492 -0
- package/skills/c64-memory-map/SKILL.md +79 -0
- package/skills/c64-memory-map/scripts/memmap.ts +92 -0
- package/skills/c64-provenance/SKILL.md +110 -0
- package/skills/c64-provenance/scripts/compare.ts +152 -0
- package/skills/c64-provenance/scripts/provenance.ts +334 -0
- package/skills/c64-provenance/scripts/runtime.ts +191 -0
- package/skills/c64-provenance/scripts/separate.ts +140 -0
- package/skills/c64-reverse-engineering/SKILL.md +144 -0
- package/skills/c64-source/SKILL.md +126 -0
- package/skills/c64-source/scripts/generate.ts +490 -0
- package/skills/c64-source/scripts/loader.ts +571 -0
- package/skills/c64-source/scripts/opcodes.ts +61 -0
- package/skills/c64-source/scripts/source.ts +595 -0
- package/skills/c64-static-analysis/SKILL.md +119 -0
- package/skills/c64-static-analysis/scripts/analyze.ts +275 -0
- package/skills/c64-static-analysis/scripts/coverage.ts +78 -0
- package/skills/c64-static-analysis/scripts/findings.ts +75 -0
- package/skills/c64-static-analysis/scripts/idioms.ts +149 -0
- package/skills/c64-static-analysis/scripts/instructions.ts +105 -0
- package/skills/c64-testing/SKILL.md +103 -0
- package/skills/c64-testing/references/scenario-format.md +116 -0
- package/skills/c64-testing/scripts/checklist.ts +51 -0
- package/skills/c64-testing/scripts/run.ts +189 -0
- package/skills/c64-testing/scripts/scenario.ts +493 -0
- package/skills/c64-testing/scripts/test.ts +65 -0
- package/skills/c64-unpacker/SKILL.md +135 -0
- package/skills/c64-unpacker/scripts/evidence.ts +207 -0
- package/skills/c64-unpacker/scripts/loads.ts +209 -0
- package/skills/c64-unpacker/scripts/machine.ts +80 -0
- package/skills/c64-unpacker/scripts/stages.ts +130 -0
- package/skills/c64-unpacker/scripts/unpack.ts +683 -0
- package/src/c64.ts +411 -0
- package/src/cli/ap-sdk.ts +44 -0
- package/src/cli/cleanup.ts +194 -0
- package/src/cli/main.ts +125 -0
- package/src/cli/status.ts +61 -0
- package/src/host/main.ts +134 -0
- package/src/host/server.ts +392 -0
- package/src/host/tools/c1541.ts +284 -0
- package/src/host/tools/index.ts +35 -0
- package/src/host/tools/petcat.ts +77 -0
- package/src/host/tools/status.ts +47 -0
- package/src/host/tools/version.ts +41 -0
- package/src/host/vice/adapter.ts +804 -0
- package/src/host/vice/binary-monitor.ts +389 -0
- package/src/host/vice/chips.ts +118 -0
- package/src/host/vice/connect.ts +34 -0
- package/src/host/vice/process.ts +413 -0
- package/src/host/vice/screen.ts +184 -0
- package/src/host/vice/session.ts +1426 -0
- package/src/host/vice/text-monitor.ts +223 -0
- package/src/host-client/autostart.ts +55 -0
- package/src/host-client/connect.ts +241 -0
- package/src/host-client/coverage.ts +63 -0
- package/src/host-client/steps.ts +170 -0
- package/src/host-client/tools.ts +89 -0
- package/src/host-client/transfer.ts +126 -0
- package/src/host-client/vice-session.ts +316 -0
- package/src/knowledge/database.ts +132 -0
- package/src/knowledge/history.ts +163 -0
- package/src/knowledge/import.ts +391 -0
- package/src/knowledge/read.ts +342 -0
- package/src/knowledge/schema.ts +229 -0
- package/src/knowledge/write.ts +663 -0
- package/src/mcp/main.ts +134 -0
- package/src/mcp/output.ts +32 -0
- package/src/mcp/server.ts +316 -0
- package/src/mcp/tools/debug.ts +327 -0
- package/src/mcp/tools/execution.ts +117 -0
- package/src/mcp/tools/input.ts +72 -0
- package/src/mcp/tools/machine.ts +100 -0
- package/src/mcp/tools/media.ts +129 -0
- package/src/mcp/tools/memory.ts +250 -0
- package/src/mcp/tools/video.ts +271 -0
- package/src/native/acme.ts +116 -0
- package/src/native/discover.ts +179 -0
- package/src/native/dxa.ts +166 -0
- package/src/native/ghidra/analyze.ts +86 -0
- package/src/native/ghidra/index.ts +173 -0
- package/src/native/ghidra/language/c64rt_6510.cspec +34 -0
- package/src/native/ghidra/language/c64rt_6510.ldefs +15 -0
- package/src/native/ghidra/language/c64rt_6510.pspec +15 -0
- package/src/native/ghidra/language/c64rt_6510.slaspec +596 -0
- package/src/native/ghidra/scripts/C64Export.java +274 -0
- package/src/native/ghidra/scripts/C64Prepare.java +60 -0
- package/src/native/local.ts +34 -0
- package/src/native/processes.ts +350 -0
- package/src/native/run.ts +172 -0
- package/src/native/staging.ts +83 -0
- package/src/native/status.ts +113 -0
- package/src/native/types.ts +257 -0
- package/src/native/watchdog.ts +78 -0
- package/src/package.ts +21 -0
- package/src/platform.ts +441 -0
- package/src/project.ts +39 -0
- package/src/protocol/framing.ts +132 -0
- package/src/protocol/messages.ts +259 -0
- package/src/protocol/tools.ts +266 -0
- package/src/protocol/vice.ts +1348 -0
- package/src/script.ts +60 -0
- package/src/time.ts +15 -0
- package/src/trace.ts +194 -0
- package/THIRD-PARTY-NOTICES.md +0 -26
- package/bin/cli.mjs +0 -283
- package/skills/acme-build/SKILL.md +0 -260
- package/skills/acme-build/scripts/acme.mjs +0 -341
- package/skills/acme-build/template.a +0 -53
- package/skills/c64-disk-access/SKILL.md +0 -159
- package/skills/c64-disk-access/scripts/c1541.mjs +0 -569
- package/skills/c64-memory-mapping/SKILL.md +0 -631
- package/skills/c64-memory-mapping/memmap.json +0 -8800
- package/skills/c64-memory-mapping/scripts/driver.mjs +0 -553
- package/skills/c64-petcat/SKILL.md +0 -87
- package/skills/c64-petcat/scripts/petcat.mjs +0 -221
- package/skills/c64-program-recon/SKILL.md +0 -706
- package/skills/c64-program-recon/references/control-flow.md +0 -180
- package/skills/c64-program-recon/references/graphics.md +0 -79
- package/skills/c64-program-recon/references/observation-hazards.md +0 -143
- package/skills/c64-program-recon/references/reconstruction.md +0 -133
- package/skills/c64-program-recon/references/sound-and-input.md +0 -74
- package/skills/c64-program-recon/references/tool-selection.md +0 -78
- package/skills/c64-program-recon/scripts/derive.mjs +0 -364
- package/skills/c64-program-recon/scripts/packer-finding.mjs +0 -709
- package/skills/c64-program-recon/templates/memory-map.template.md +0 -101
- package/skills/c64-provenance-diff/SKILL.md +0 -292
- package/skills/c64-provenance-diff/scripts/diff-images.mjs +0 -984
- package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +0 -395
- package/skills/c64-ram-capture/RELEASES.json.example +0 -17
- package/skills/c64-ram-capture/SKILL.md +0 -474
- package/skills/c64-ram-capture/scripts/compare-cross-binary.mjs +0 -525
- package/skills/c64-ram-capture/scripts/compare.mjs +0 -258
- package/skills/c64-ram-capture/scripts/derive-transients.mjs +0 -575
- package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +0 -317
- package/skills/c64-ram-capture/scripts/mcp-module.mjs +0 -174
- package/skills/c64-ram-capture/scripts/project-paths.mjs +0 -81
- package/skills/c64-ram-capture/scripts/releases.mjs +0 -109
- package/skills/c64-ram-capture/scripts/vsf-slice.mjs +0 -147
- package/skills/c64-ram-capture/scripts/watch-loads.mjs +0 -581
- package/skills/c64-ram-capture/templates/capture-record.template.md +0 -99
- package/skills/c64-ram-capture/transients/README.md +0 -137
- package/skills/routine-queue-walker/SKILL.md +0 -394
- package/skills/routine-queue-walker/scripts/completeness-report.mjs +0 -465
- 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. |
|