@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,706 +0,0 @@
1
- ---
2
- name: c64-program-recon
3
- description: Work out how an unknown C64 program is structured at runtime — entry point, interrupt handlers, main loop, game states, graphics and sound — in a fixed order, before disassembling anything. Use when asked to reverse engineer a C64 game, find the main loop, entry point or IRQ handler, locate the player sprite, charset or music player, identify a game state machine, work out which memory regions are code versus data, or decide where to start on a depacked image.
4
- ---
5
-
6
- # Reconnaissance on an unknown C64 program
7
-
8
- **Do not disassemble the whole program first.** The structure hangs off a small set of well-known
9
- addresses: read them in order and the answer falls out. Treated as a search problem it costs an
10
- hour every session. Written down, it is minutes.
11
-
12
- Build a network of checked facts. Once you know the vectors, the IRQ handler, the main loop and
13
- the major tables, everything else classifies far more easily.
14
-
15
- ```bash
16
- D=src/skills/c64-program-recon/scripts/derive.mjs # from the repo root
17
-
18
- node $D vectors dump.bin # $01 + six vectors, which pair is live
19
- node $D vic --dd00 3E --d018 18 --d011 1B --d016 C8 # bank, screen, charset, mode
20
- node $D sprites --dd00 3E --d018 18 --d015 0F --ptrs 20,21,22,23,FF,FF,FF,FF
21
- ```
22
-
23
- The script does only the arithmetic that a lookup table cannot — register bits to concrete
24
- addresses — over values **you** got through `mcp__plugin_c64-re-tools_vice__*`. It contacts nothing.
25
-
26
- ## Before disassembling anything
27
-
28
- Two adjacent jobs belong to other skills, and pointing at their owners is cheaper than re-deriving
29
- either one here:
30
-
31
- - **A file still sitting inside a `.d64` image** — its directory, block allocation map, sector
32
- chain, or raw bytes — is `c64-disk-access`'s job. Get the file out of the image there first.
33
- - **A tokenized BASIC stub rather than raw machine code** — detokenizing it and finding where it
34
- hands over to machine code, including the named decline when no static handover address exists —
35
- is `c64-petcat`'s job. See below for the one thing it does not also do.
36
-
37
- ## The order
38
-
39
- Each step is a read whose answer rules something out. Do not skip ahead: step 6 is cheap once you
40
- know the handler, because that is where most chip writes happen.
41
-
42
- | # | Question | Read | What the answer settles |
43
- |---|---|---|---|
44
- | 0 | Which of these bytes are even the game? | The manifest's buckets — `c64-provenance-diff` | Tracing a depacker's IRQ handler wastes work. Scope before you trace |
45
- | 1 | Where does execution start? | Post-depack: wherever the PC sits at the decrunch checkpoint. There is no BASIC stub to find. | The one address everything else hangs off |
46
- | 2 | Which vector is live? | `$01`, then `$0314/$0315` **or** `$FFFE/$FFFF` | HIRAM (`$01` bit 1) decides. KERNAL out ⇒ the RAM vectors are meaningless |
47
- | 3 | What drives the frame? | `$D01A`, `$D012`, `$DC0D` | `$DC0D` untouched ⇒ raster IRQ. Programmed ⇒ the game runs its own timebase |
48
- | 4 | Where is the main loop? | Checkpoint a suspected loop head, run one frame | Two shapes only: a real loop, or a two-instruction spin with the IRQ doing everything |
49
- | 5 | Code or data? | What the PC actually visits across full coverage | A range never executed is data, whatever a tracer guessed |
50
- | 6 | Where is the graphics? | `$DD00` → `$D018` → mode bits → `$D015` → VM+`$03F8` | Every byte shown on screen, computed. Nothing to search for |
51
- | 7 | Where is the music? | Watch `$D404` | `init` runs once from main code. `play` runs once per frame from the IRQ |
52
- | 8 | Where is the input? | Reads of `$DC00`/`$DC01` | Games poll the matrix directly and ignore the KERNAL buffer |
53
-
54
- ## Step 0 in full: only the game is in scope
55
-
56
- Every image taken from a cracked release carries three layers that are **not the game**, and no
57
- original master exists to strip them for you:
58
-
59
- | Layer | What it is | Dead when |
60
- |---|---|---|
61
- | **loader** | The custom raw-sector routine that bypasses the KERNAL | The payload is in RAM |
62
- | **cruncher / depacker** | The decompression stub | It has run once |
63
- | **cracktro** | The group's intro, scroller, credits, and the keypress gate that dismisses it | Dismissed |
64
-
65
- **The rule: a byte with nothing to do with the game is out of scope — always, not case by case.**
66
- It is not annotated, not reconstructed, and not traced. This is a standing default, not a
67
- per-session decision.
68
-
69
- **The evidence bar runs both ways, and this project has been burned in both directions.**
70
-
71
- - Removal needs *positive* evidence of the bucket: a crack-credit vocabulary match, a
72
- `RELEASES.json` `loader_ranges` entry earned from live disassembly, or a depacker stub provably
73
- dead after first run. A bare printable-ASCII scan classified **the game's own title text** as
74
- cracktro credit and would have shipped a confidently-wrong `CRACKER-PATCH` verdict.
75
- - Absence of evidence records `UNKNOWN` and **keeps the bytes**. A real title-screen text
76
- divergence was found sitting in a region that is neither loader nor cracktro — a cracker edit
77
- inside the game's own data. Stripping only the obvious intro screen leaves crack residue behind.
78
-
79
- `c64-provenance-diff` owns the machinery and the five kinds. Do not re-derive them here. What
80
- belongs here is the ordering: **bucket first, then trace only what survives.**
81
-
82
- Then work **backwards from observable effects** rather than reading code sequentially — it is
83
- consistently faster. Watch writes to the sprite coordinates to find movement. Watch `$D018` to find
84
- the room loader. Watch VM+`$03F8` to find the animation driver. `vice_watch_add` finds *writers*,
85
- and that is its real leverage.
86
-
87
- Differential experiments close the loop: patch a routine to `RTS` and see what stops. If enemies
88
- freeze and nothing else does, that checks the routine's purpose — far stronger evidence than
89
- reading the listing.
90
-
91
- ## Step 0.5: is it packed, and by what?
92
-
93
- Runs **between step 0 and step 1** — after scoping, before you go looking for an entry point.
94
- Tracing a decruncher is the same wasted work as tracing a loader, and every label you write on a
95
- packed image is thrown away the moment the real image is recovered.
96
-
97
- ```bash
98
- node src/skills/c64-program-recon/scripts/packer-finding.mjs game.prg # from the repo root
99
- node src/skills/c64-program-recon/scripts/packer-finding.mjs game.prg --entropy 7.83
100
- ```
101
-
102
- Pass `--entropy` when you already have the number from `anno_get_binary_info`. Otherwise the
103
- script measures it from the file. It prints one JSON object. Read the `verdict`:
104
-
105
- | Verdict | What it means | What to do |
106
- | --- | --- | --- |
107
- | `identified` | An external oracle stated the packer name, verbatim. `packer` holds it and `confidence` is `HIGH`. | Record the name as a finding. Then depack: run it in the emulator and capture RAM past the decrunch (`c64-ram-capture`). |
108
- | `packed-unidentified` | Entropy is at or above the 7.5 packedness threshold and **no oracle named the packer**. `packer` is `null`. | Treat the image as packed. Depack the same way. Do not annotate these bytes and do not go hunting for a name. |
109
- | `unpacked` | Entropy is below the threshold. Still not an identity claim — it says nothing about which packer, only that these bytes do not look compressed. | Continue to step 1 on this image. |
110
- | `unknown` | No route produced an answer. `unavailableReason` always says why. | Continue, but record the unknown. Never write it up as "not packed". |
111
-
112
- **A name is reported only when an external oracle stated one, and this project does not guess.**
113
- No first-party route on this project's surface reports a packer name at all — that was
114
- established four independent ways, and the acceptance bar a future first-party identifier would
115
- have to clear was set at the same time. So there is no code path here that can write a packer
116
- name from entropy, from a decompression address, or from a byte pattern. If you want a name and the finding does not give
117
- you one, install an external identifier on the **host** and point `UNP64` or `UNP64_PATH` at it in
118
- the environment the host broker process sees — do not infer it. `packer-finding.mjs` never spawns
119
- that identifier itself: it reaches it only through the host-tool execution
120
- seam (`src/mcp/vice/host-tool.mts`'s `oracle.probe`/`oracle.run` allowlist entries), because this
121
- script runs container-side and there is no container PATH to a host binary on. **The container-side
122
- environment is not consulted at all**: setting `UNP64`/`UNP64_PATH` in
123
- this script's own (container-side) environment adds only a diagnostic hint to an absent result —
124
- it can never select what the host executes. The oracle's location is host-side configuration
125
- only, and the configured path's file name must be the oracle binary's own name (`unp64`) or the
126
- seam treats it as absent.
127
-
128
- **The entropy gate answers packedness, not identity.** High entropy tells you the bytes are
129
- compressed (or encrypted, or genuinely random). It does not tell you by what. And the way a packed
130
- image is actually opened up here is the run-and-capture route — run the program under the emulator
131
- and capture RAM at a checkpoint past the decrunch — not an in-place unpack, which would destroy
132
- the comments, labels and blocks the project already holds.
133
-
134
- ## Worked example — a real capture
135
-
136
- ```
137
- $ node $D vectors capture.bin
138
- $01 = $40 %01000000
139
- bit 0 LORAM = 0 BASIC ROM out (RAM at $A000-$BFFF)
140
- bit 1 HIRAM = 0 KERNAL ROM out (RAM at $E000-$FFFF)
141
- bit 2 CHAREN = 0 character ROM at $D000-$DFFF
142
-
143
- LIVE VECTOR PAIR: $FFFE/$FFFF (KERNAL banked OUT — the hardware vectors are live)
144
- CBM80 SIGNATURE: absent — nothing catches a reset here
145
-
146
- ## KERNAL IRQ/BRK/NMI ($0314-$0319)
147
- DORMANT: KERNAL ROM banked out — nothing maintains these. Read it, do not act on it.
148
- vector value default status
149
- $0314/$0315 $0101 $EA31 *** RETARGETED ***
150
- CINV — KERNAL IRQ (RAM, indirect)
151
- …
152
- ## Hardware vectors ($FFFA-$FFFF) — live when the KERNAL is banked out
153
- $FFFA/$FFFB $1116 -- no default
154
- $FFFC/$FFFD $1116 -- no default
155
- $FFFE/$FFFF $1103 -- no default
156
-
157
- Non-default bytes in DORMANT blocks: 17. These are NOT diverted
158
- vectors. …
159
- ```
160
-
161
- Read it as: the KERNAL is banked out, so the whole `$0300-$0333` range is **dormant** and `$0314`'s
162
- `$0101` is residue, not a retargeted vector — ignore it. The live handler is `$1103`, and that is
163
- where to arm the first checkpoint. `$FFFA` and `$FFFC` both holding `$1116` says the game installs
164
- its own NMI *and* RESET handlers, at one shared address.
165
-
166
- Both cracked releases give the same answers across all three of their captures, and `$1103` is the
167
- same IRQ entry that phase-01 live work established independently (chain `$1103 → $1574 → $152C`).
168
- The method reproduces a known-good result from a static image with no emulator running, and the
169
- `$1116` pair is new — see `references/control-flow.md` § 2. **Confidence: HIGH** for steps 1-2.
170
-
171
- ## Writing findings into the annotation store
172
-
173
- Recon's findings are not memory-map prose written once and left to rot — they are entries in a
174
- queryable annotation store, and the Markdown memory map is a *generated view* of that store,
175
- not something you hand-edit yourself.
176
-
177
- **There is no bootstrap step, and no bootstrap verb.** The store is created by the first write to
178
- it: name a `.annostore` path on any mutating call — `anno_set_label_name`, `anno_set_comment`,
179
- `anno_set_data_type`, `anno_add_scope` — and it is created, committed and closed inside that call.
180
- A read-only call against a path that does not exist yet is REFUSED by name rather than answering
181
- against an empty store, so "I read nothing" and "there is nothing to read" stay distinguishable.
182
-
183
- Every `anno_*` tool takes an explicit `store` path — there is no ambient session state
184
- naming the store, so which store a call touched is always visible in the transcript. Every call
185
- that derives its answer from the program's **bytes** rather than from the annotations takes an
186
- `image` path as well — `anno_get_binary_info`, `anno_read_region`, `anno_disassemble`,
187
- `anno_get_cross_references`, `anno_search` and `anno_get_address_details`. The store holds
188
- annotations and never bytes, so an omitted image would read as a plausible success against
189
- whatever was recorded last. `image` is a `.prg` (2-byte little-endian load address plus payload) or
190
- an exactly-65536-byte flat capture, dispatched **by extension first**, never by length.
191
-
192
- **Write findings with the named tools, not a Markdown row:**
193
-
194
- | Tool | Use for |
195
- |---|---|
196
- | `anno_set_label_name` | Naming a routine or table (`init_screen`, `sprite_table`) |
197
- | `anno_set_data_type` | Classifying a block (`code`, `byte`, `address`, `petscii`, …) |
198
- | `anno_add_scope` | Marking a handler's extent as a lexical scope |
199
- | `anno_set_comment` | Recording the evidence — the carrier for the confidence grade below |
200
- | `anno_batch_execute` | Bulk annotation, 5+ independent calls at once — a real memory map is dozens of labels, comments and block ranges. One batch is one open, commit, close instead of dozens. You name the store (and the image, when an inner call needs one) ONCE at the top level, and every inner call inherits it. A malformed payload, an empty `calls` array, an uncurated inner name at any depth, or an illegal label name refuses the **whole** batch by index. It executes nothing. Past that gate, execution runs to completion and each entry carries its own status. So an error entry inside a successful result means that one call did not work |
201
-
202
- **Grade with the confidence prefix.** Lead every evidence comment with exactly one of these five
203
- bracket tokens (quoted verbatim from `anno-confidence.ts`, the parser's own source of truth):
204
-
205
- `[confirmed-code]` (`confirmed code`), `[probable-code]` (`probable code`), `[confirmed-data]`
206
- (`confirmed data`), `[probable-data]` (`probable data`), `[unknown]` (`unknown`).
207
-
208
- A typo in the bracket token — wrong case, an underscore, a plural, stray whitespace — **fails
209
- loudly**. It does not silently degrade into an ungraded comment. Do not promote a row by editing
210
- its grade in place. Re-check and restate the evidence with a fresh `anno_set_comment` call, so the
211
- record of when something stopped being a guess survives.
212
-
213
- **Query instead of re-deriving.** `anno_get_symbols`, `anno_get_comments` and `anno_get_blocks`
214
- answer straight from the store. `anno_get_cross_references` and `anno_search` derive their answers
215
- from the image bytes plus the store's typed ranges, so they take `image` too. `anno_search` searches
216
- three corpora together — label names, comment text, and the instruction text rendered from every
217
- range typed `code` — **byte-exact and case-sensitive**, with each corpus named in the answer
218
- alongside how many entries it held, so a genuine zero over a real corpus stays distinguishable from
219
- a corpus this surface does not have.
220
-
221
- `max_results` is **REQUIRED, with no default,** on every one of those reads. That is deliberate: an
222
- implicit default silently truncates a full-program pass, and here the true match count rides beside
223
- the truncated list, so truncation is a fact you are told rather than one you infer. The query this
224
- whole workflow exists to make cheap:
225
-
226
- > "Show me everything still `[unknown]`" → `anno_search` with `query: "[unknown]"` and
227
- > an explicit `max_results` set above your program's comment count.
228
-
229
- `anno_get_blocks` is also the read route for the store's other structural annotations: pass
230
- `include: ["scopes", "enums", "enum_usage"]` to get scope spans (which `anno_remove_scope` must
231
- match exactly), every project enum with its variants, and every address-to-enum association.
232
-
233
- `anno_get_address_details` composes everything known about ONE address — the labels bound there,
234
- the comments there, the typed range covering it, and the cross-references reaching it. **The call
235
- discloses the composition:** the body carries `composed_client_side` and a `composed_from` list
236
- naming all four sources, so a composition is never mistaken for something the store held whole.
237
-
238
- ### Take names to the running machine, and bring live findings back
239
-
240
- **Dated withdrawal, 2026-08-29 — the `.lbl` round trip is WITHDRAWN, and as of 2026-08-31 no phase
241
- currently owns its return.** The two CLI verbs that carried it, `export-lbl` and `import-lbl`, are
242
- gone from this surface: both were delivery paths into the retired static analyser. This notice
243
- previously forecast that a numbered phase would rebuild them alongside the ACME export route. That
244
- forecast was **wrong, and this note fixes it here rather than removes it**. The phase that rebuilt the ACME
245
- export route covered that route only — no requirement and no success criterion of it mentioned the
246
- `.lbl` round trip — so the round trip still has no route and **no phase currently owns its return**.
247
- Do not reach for these verbs here: they do not exist, and an invocation fails with an unknown-verb
248
- error and no explanation of why.
249
-
250
- The **loop itself is not withdrawn**, only its two automated legs, and the discipline it encodes is
251
- what to keep doing by hand for as long as they stay gone:
252
-
253
- 1. **The store is the merge point, not your own notes.** Write a name discovered live —
254
- disassembling the running machine, a checkpoint hit — into the store with
255
- `anno_set_label_name` *first*, before you carry it anywhere else.
256
- 2. **`vice_symbols_load` REPLACES the machine's symbol table rather than merging into it.** Call it
257
- **exactly once** per generated `.lbl` file. Loading an older file a second time, after the store
258
- has moved on, silently discards the newer names.
259
- 3. **Regenerate whole, never patch incrementally.** The round trip regenerated the entire `.lbl`
260
- from the store, and any rebuild of it must do the same. A hand-written incremental patch
261
- reintroduces exactly the drift the single merge point exists to prevent.
262
-
263
- Two traps that survive the withdrawal and are part of the specification whoever eventually rebuilds
264
- this will read: the export carried **USER** labels only — auto-generated `a_D011`/`e_FFD2` externals
265
- never appeared in the written file — and neither direction ever created a store from a raw input.
266
-
267
- `gen-enums` — turning register writes into named enum variants — is **withdrawn on the same terms,
268
- and no phase currently owns its return either**. The same superseded forecast named a numbered phase
269
- for it. That phase's requirements covered the ACME export oracle only. `c64-memory-mapping`
270
- documents what `gen-enums` consumed — the `memmap.json` bit table — alongside the withdrawal and
271
- the by-hand route that stays open.
272
-
273
- **Generate the memory map. Do not hand-author it.** Fill in the provenance sidecar (schema and a
274
- filled example live in `templates/memory-map.template.md`), then:
275
-
276
- ```bash
277
- npx -y @henols/vice-mcp anno render-memmap game.annostore --provenance sidecar.json
278
- node <plugin-root>/src/mcp/vice/vice-proxy.ts anno render-memmap game.annostore --provenance sidecar.json
279
- ```
280
-
281
- Add `--check` to detect drift. `--check` reports drift when, and only when, one of these changed:
282
-
283
- - the rendered file itself (a hand edit)
284
- - a store row (a range, a label, a comment, or a comment's confidence grade)
285
- - the provenance sidecar's bytes
286
- - the location of the store or the sidecar **relative to the workspace root**
287
- - the renderer
288
-
289
- **Relocating the checkout is not drift** — the same tree at a different absolute path renders the
290
- same bytes, because the banner records workspace-relative locations. The rendered file carries a
291
- generated-file banner. Treat it like every other generated artifact in this repo and never
292
- hand-edit it.
293
-
294
- **Dated correction, 2026-08-30 — the paragraph above used to name TWO drift causes, and a third
295
- existed.** Before gap-closure round 2 the banner recorded the store and the sidecar by their
296
- ABSOLUTE paths, so the checkout's own location was a silent third cause: an identical store,
297
- sidecar and rendered file reported `drifted` the moment the tree sat at a different absolute path,
298
- while `render-memmap` printed the same `render_digest` in both. Recording workspace-relative
299
- locations removed that cause, so the cause set above is the one the shipped verb has. This note
300
- supersedes the old two-cause wording rather than merely rewording it, because a reader meeting it
301
- in history needs to know which claim was live when.
302
-
303
- **One-time drift after upgrading, 2026-08-30.** A memory map rendered *before* that change reports
304
- `drifted` on its first `--check` afterwards, exactly once, because the banner's recorded locations
305
- changed from absolute to workspace-relative spellings. Re-run the generator and commit the new
306
- banner. This repository has no committed rendered `memory-map.md` — only the template — so nothing
307
- here regresses. This sentence is meant for **consuming projects**, which do have one.
308
-
309
- **This playbook itself has a generated twin, and it is not the one to edit.**
310
- `installer/skills/c64-program-recon/` is a gitignored COPY of this directory, rebuilt from it by
311
- `installer/scripts/sync-skills.mjs` on the installer package's `prepack` and by
312
- `npm --prefix installer run sync-skills`. Edit THIS file. Never edit the twin. A hand-edit there is
313
- overwritten by the next sync and is not independently covered either — the gates that scan the
314
- shipped tree run the sync before they scan it, so the sync REMOVES a change made only in the twin
315
- before anything ever measures it. A change made here SHIPS only once that sync has run.
316
-
317
- **Dated correction, 2026-08-30 — `render-memmap` reads the annotation store directly, and the note
318
- that used to stand here was WRONG when it shipped.** This verb was rebuilt over the annotation
319
- store: its positional is an EXISTING `.annostore`, opened
320
- with `mustExist` — the call refuses an absent store by name rather than creating one — and nothing
321
- on the path it reaches consults the retired external analyser. The pre-store project file the
322
- earlier note named has no producer left in this repository, so there is no route back to the old
323
- spelling. That earlier note asserted in the PRESENT TENSE that this verb still read a project file.
324
- It was already false when it shipped, and this page REMOVES it here rather than amends it, so a
325
- reader comparing two dated claims can tell which one to believe.
326
-
327
- **Importing a Ghidra export, and the mechanical join that follows it.** When a Ghidra harness run
328
- (a separate, host-side capability) has produced a transfer file, two calls land its findings in the
329
- store — in this order, and each is one mechanical call, not an agent turn:
330
-
331
- 1. **`anno_import_ghidra_export`** reads the transfer file, writes one cross-reference row per
332
- surviving reference, and REMOVES the transfer file once every write has durably committed. It
333
- reports `referencesSeen`, `xrefsWritten`, `xrefsAlreadyPresent` (the call deduplicates a
334
- duplicate reference rather than double-counting it) and `kindsSeenNotImported` — reference kinds
335
- outside this store's four-member vocabulary, dropped and counted rather than guessed or refused.
336
- The call refuses a malformed, truncated or digest-mismatched export by name, naming the section
337
- and the offending line, and writes nothing.
338
- 2. **`anno_join_memmap`** then reads every cross-reference target the store already holds, skips
339
- addresses inside the program's own loaded image (those are code/data addresses, not hardware
340
- features), and annotates everything else with the narrowest `c64-memory-mapping/memmap.json` entry
341
- containing it. It reports `addressesConsidered`, `annotated`, `skippedInImage`,
342
- `skippedNoMapEntry`, `declined` and `commentsChanged`, plus a per-address decision naming the
343
- outcome and, for every skip, why.
344
-
345
- Both calls are **mechanical**: there is no agent invocation, no queue walk and no skill invocation
346
- anywhere inside either one, checked structurally over the two modules' own source rather than
347
- asserted in prose. Run the import call once per Ghidra export, then the join call once per updated
348
- image. Neither call takes an agent turn to complete.
349
-
350
- ## Static disassembly
351
-
352
- **Dated withdrawal 2026-08-29, dated return 2026-08-31 — whole-program ACME export was WITHDRAWN
353
- and has come back as `anno export-asm`, behind a real-ACME byte-diff oracle.** This page keeps the
354
- notice rather than removing it, because the withdrawal explains the shape of what returned. The
355
- removed verb turned a `.prg` or a flat 64K image into ACME source offline and settled its own
356
- correctness with a transcript parser. What returned is not a rename of it. This project rebuilds it
357
- over the **annotation store**, and settles its correctness by **assembling the output with a real
358
- ACME and diffing the bytes against the input** — never by an exit code and never by a string match
359
- on the exporter's own output.
360
-
361
- ```bash
362
- npx -y @henols/vice-mcp anno export-asm game.prg --store game.annostore --out game-src
363
- node <plugin-root>/src/mcp/vice/vice-proxy.ts anno export-asm game.prg --store game.annostore
364
- ```
365
-
366
- `<image>` and `--store` are **two separate arguments and neither derives from the other**: the
367
- image supplies the bytes, the store supplies the names, typed ranges and comments. `--out` names a
368
- **directory** for the whole export, defaulting to the image's basename stem beside the **store**
369
- rather than beside the image, and the export refuses a non-empty destination rather than
370
- overwriting it unless you pass `--force`. The directory holds a root file that sources the rest, one
371
- file per annotation scope, and an `unscoped.a` for any block that lies inside no scope — open the
372
- root file first to see how the tree fits together.
373
-
374
- **It writes source and runs no assembler**, and says so in its own second output line
375
- (`this file has NOT been assembled`). The real-ACME byte-diff is a **test-only** oracle in this
376
- repository's test suite, absent from the published package and unreachable at runtime — so a clean
377
- run is evidence that the export wrote source, not an assembler verdict. `acme-build` carries the
378
- full statement of that split.
379
-
380
- Two routes remain for reading a single routine, and they are the ones the rest of this playbook
381
- already uses:
382
-
383
- - **`anno_read_region`** and **`anno_disassemble`** render one routine or table at an **explicit**
384
- inclusive range, decoded fresh from the image bytes on every call and written nowhere. That is
385
- the static route, bounded on purpose: these calls cap the combined byte count at **4096 bytes**
386
- (`ANNO_READ_REGION_MAX_BYTES`), and they REFUSE a wider request by name rather than truncating it,
387
- because a full-64K disassembly dumped into an agent's context is exactly the hazard the cap
388
- exists to prevent.
389
- - **`vice_disassemble`** is the live-RAM route this skill's own table above uses: it reads a
390
- running emulator's RAM at a checkpoint.
391
-
392
- The two are complementary — reach for the static reads before the emulator is even running, and for
393
- `vice_disassemble` once you have a live checkpoint to decode from.
394
-
395
- Extracting a program from a `.d64` image is a separate capability that this repository still does
396
- not have, and — fixing an earlier note that assigned it to the same numbered phase as the ACME
397
- export oracle — **no phase currently owns it**. Whatever future tool builds it must name the file
398
- inside the image explicitly and refuse rather than guess, because a guess could analyse a cracktro
399
- or loader stub instead of the game.
400
-
401
- ## Before you touch the emulator
402
-
403
- Two hazards cost this project real sessions. Both are in `references/observation-hazards.md`. These
404
- two lines are the part you cannot afford to load lazily.
405
-
406
- - **Pause after every observation.** Agent think-time runs the emulator at full speed — 258 million
407
- cycles (~262 emulated seconds) elapsed across a handful of reads with zero input sent, which was
408
- enough to reach `GAME OVER` and to invalidate an earlier finding. Never leave the machine running
409
- across a reasoning step.
410
- - **When the machine looks frozen, enumerate your own checkpoints first.** An armed *stopping*
411
- checkpoint on the live IRQ path reproduces the entire "dead emulator" signature — zero cycles,
412
- `ping` still reporting `running`, an identical PC — because the machine genuinely never moved.
413
- Two cheap reads settle it, and neither needs `vice_execution_run`.
414
-
415
- ## Documenting one routine, end to end
416
-
417
- The table at the top of this page finds *where* the structure is. This section
418
- is what you do once you have picked one routine out of it and want it
419
- documented properly in the annotation store.
420
-
421
- **Scope, so three skills do not fight over the same job.** This procedure
422
- handles **one routine, at one explicit address**. Building the backlog of every
423
- undocumented routine in a project and draining it to closure is
424
- `routine-queue-walker`'s job — it calls into this procedure once per queue
425
- entry. Classifying the *regions* around the routine, and naming the data
426
- symbols it touches, is the absorbed pair in `c64-memory-mapping`.
427
-
428
- ### 1. Context first
429
-
430
- `anno_get_binary_info` for `system`, `filename`, `description` and
431
- `may_contain_undocumented_opcodes`.
432
-
433
- - `system` names the target machine and therefore which memory map, hardware
434
- registers and ROM entry points are in play.
435
- - `filename` and `description` identify the software. This is how you recognise
436
- a stock component instead of re-deriving it — a Hubbard-style music driver,
437
- an Exomizer decrunch stub — and how genre informs a guess
438
- (`check_collision` is a plausible routine in a shooter).
439
- - With `may_contain_undocumented_opcodes: true`, expect `LAX`, `SAX`, `SLO`,
440
- `DCP`, `ISC`. These are real instructions, not disassembly errors. Do not
441
- stop reading at one.
442
-
443
- ### 2. Bounds, from an explicit address
444
-
445
- **Always start from an address you were given or derived** — `$XXXX` or its
446
- decimal equivalent. There is no editor cursor in this project's route, and
447
- upstream's own text forbids relying on one in any case.
448
-
449
- Find the start (the entry point or its label) and the end (`RTS`, `RTI`, or a
450
- `JMP`). Two shapes to expect:
451
-
452
- - A routine ending in `JMP shared_epilogue` still **ends there** — that is a
453
- tail call, and the target's body is a different routine.
454
- - A routine with no return at all may **fall through** into the next one. Use
455
- the cross-references and the flow to decide where the boundary is, and say in
456
- the comment that it falls through.
457
-
458
- ### 3. Read the range
459
-
460
- `anno_read_region` over the routine's explicit range, naming the `store` and
461
- the `image`, with `view` **omitted** — the disassembly view is that parameter's
462
- documented default, so the call needs no `view` at all here.
463
-
464
- The combined byte count is capped at **4096 bytes** per call
465
- (`ANNO_READ_REGION_MAX_BYTES`) and a request above it is refused by name
466
- rather than silently truncated. A routine longer than that — rare, but real in
467
- a decruncher or a level builder — is read as **consecutive ranges**. Read them
468
- in order. Do not raise the cap to swallow the whole program, because the cap is
469
- what keeps a "read this routine" call from becoming a whole-program export.
470
-
471
- Then read the flow, not just the instructions:
472
-
473
- - Does it loop? Where does the loop stop?
474
- - Does it call other routines, or ROM entry points?
475
- - Does it touch hardware registers?
476
-
477
- Recurring shapes worth recognising on sight:
478
-
479
- | Pattern | Almost always |
480
- |---|---|
481
- | `SEI` … `CLI` bracketing | IRQ setup or teardown |
482
- | `LDA`/`STA` with `DEX`/`DEY`/`BNE` | Memory copy or fill |
483
- | Bit shifts plus `ADC`/`SBC` chains | Maths, or a decompressor |
484
- | Reads an I/O address then branches | Hardware polling |
485
- | Writes to `$0314`/`$FFFE` | Interrupt vector installation |
486
- | Writes to `$D400`–`$D418` | Music or SFX driver tick |
487
- | Reads `$DC00`/`$DC01` | Joystick or keyboard polling |
488
-
489
- ### 4. Who calls it
490
-
491
- `anno_get_cross_references` on the entry point. The caller is often more
492
- decisive than the body:
493
-
494
- - Called from an init block → a setup routine, runs once.
495
- - Called from the main loop → a per-frame update.
496
- - Called from the IRQ → must be fast. Likely a music tick or a raster update,
497
- and its zero-page usage is IRQ-relative.
498
- - **No callers at all** → not necessarily dead. It may be a dispatch target
499
- reached through a jump table. Check the nearby data blocks for an address
500
- table pointing at it.
501
-
502
- ### 5. What data it touches
503
-
504
- For every address the routine reads or writes:
505
-
506
- 1. `lookup` it first (see `c64-memory-mapping`). A hardware register or KERNAL
507
- entry point is answered outright and needs no further work.
508
- 2. Otherwise `anno_get_cross_references` on that address, and read the shape:
509
- - Written once, in init → a constant or a config value.
510
- - Written *and* read by several routines → shared state, a global.
511
- - In the zero page and used as `($addr),Y` → an indirect pointer.
512
- 3. **Enums.** If the accessed addresses or the immediate values form a logical
513
- set — state constants, joystick direction bits, colour codes — check for an
514
- existing project, global or system enum that matches, and apply it with
515
- `anno_apply_enum_usage` at the accessing instruction. If none matches but
516
- the set is clean, define one with `anno_create_project_enum` (give it a
517
- real `description`) and then apply it everywhere it fits. This is what turns
518
- `lda #$1b` into something a reader understands.
519
-
520
- **The pointer-formatting step this project does not have.** A routine that sets
521
- up a pointer or a vector does it with a pair of immediate loads — `LDA #<target
522
- / STA ptr`, `LDA #>target / STA ptr+1`. Upstream calls `set_immediate_format`
523
- on each of the two instruction addresses, with `low_byte` / `high_byte` and the
524
- target, so the pair renders as one symbol reference. **That call is not exposed
525
- on this project's surface.** The rule it would serve — "every branch, `JSR`/`JMP`
526
- and data reference goes through a symbol, so code can move" — still supplies
527
- its criterion, and the per-call disposition sits in the manifest named
528
- in the attribution header above. Until then, recombine the two bytes yourself
529
- and put the reconstructed target in a side comment on both instructions, so the
530
- vector setup is readable even though the store cannot format it.
531
-
532
- ### 6. Synthesise, then document
533
-
534
- Four things, and they are the four things the comment block carries: **purpose**
535
- (one sentence), **inputs** (registers and memory used as arguments), **outputs**
536
- (registers and memory modified), **side effects** (hardware, screen, sound).
537
-
538
- Rename the label with `anno_set_label_name`, then put a multi-line `"line"`
539
- comment above the first instruction with `anno_set_comment`, in this exact
540
- shape — the separator is both the first and the last line:
541
-
542
- ```
543
- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
544
- <what the routine does>
545
-
546
- Inputs: <registers or memory used as arguments, or "None">
547
- Outputs: <registers or memory modified, or "None">
548
- Side Effects: <hardware changes, screen updates, etc., or "None">
549
- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
550
- ```
551
-
552
- Then add `"side"` comments to the instructions that carry the meaning — what a
553
- register holds here, why this branch is taken, what this address represents.
554
- This is the part that makes the listing readable for the next person, and it is
555
- the part most often skipped. Grade evidence comments with the confidence prefix
556
- documented earlier on this page.
557
-
558
- ### 7. Report
559
-
560
- - **Purpose** in one sentence.
561
- - **Inputs / outputs / side effects** as determined above.
562
- - **Evidence** — the instructions or cross-references that decided it.
563
- - **Actions taken** — what was renamed, which line comment was added, which
564
- instructions got side comments, which enums were defined or applied.
565
- - **Uncertain areas** — every instruction or address whose purpose is still
566
- unclear, by address. A routine report with no uncertain areas on a real game
567
- is usually a report that stopped looking.
568
-
569
- ### What goes wrong
570
-
571
- | Symptom | What it actually is |
572
- |---|---|
573
- | No `RTS`/`JMP`/`RTI` at the apparent end | Deliberate fall-through. Check whether the next label is independently called. |
574
- | `JMP some_routine` as the last instruction | A tail call. This routine ends there. The target is a separate routine. |
575
- | Several routines converging on one `RTS` | A shared epilogue. It belongs to none of them. Note it in each comment. |
576
- | No callers, but the routine is clearly live | Reached through a jump table. Look for an address table pointing at it. |
577
- | Disassembly appears to break mid-routine | Undocumented opcodes. Check the binary-info hint and keep reading. |
578
- | Zero-page usage contradicts the main program's | The routine runs from the IRQ. Its context is IRQ-relative. |
579
-
580
- ## A tokenized BASIC stub: detokenize with c64-petcat, don't hand-decode it
581
-
582
- **Detokenizing a `SYS` stub and resolving its machine-code handover address is
583
- `c64-petcat`'s job, not this skill's.** Reach for its `decode` verb before
584
- reading tokenized bytes by hand: it wraps VICE's own `petcat` to produce the
585
- readable listing and to report the handover address — or a *named decline*
586
- (`entrypoint: null` with a reason) when the `SYS` argument is not a static
587
- value, which is a resolved answer, never a guess to spend a disassembler on.
588
- Fixed here: an earlier note claimed none of `c64-petcat`'s trigger phrases
589
- overlapped this skill's frontmatter. That was true only because the skill did
590
- not exist yet, and it is superseded now that it does.
591
-
592
- The material below stays as **reference text only**, narrowed to what
593
- `c64-petcat` does NOT do — write the tokenized bytes' typed ranges and
594
- comments into this project's annotation store. That is rare in practice: a
595
- commercial C64 title captured after its loader has run almost universally
596
- reduces to a one-line `SYS` stub, so most sessions never reach this section at
597
- all. When one does, use `c64-petcat`'s resolved listing and handover address
598
- as the source of truth rather than re-deriving them by hand from the byte
599
- layout below.
600
-
601
- ### Line anatomy
602
-
603
- A tokenised BASIC program is a linked list in memory. Each line is:
604
-
605
- 1. **Bytes 0–1 — next-line pointer.** The address where the *next* line
606
- starts, little-endian (`24 04` → `$0424`).
607
- 2. **Bytes 2–3 — line number**, 16-bit little-endian (`0A 00` → `10`).
608
- 3. **Bytes 4–N — the tokens**, running until a `$00` terminator.
609
- 4. **End of program** when a line's next-line pointer is `$00 $00`.
610
-
611
- Read the range with `anno_read_region`, `view: "hexdump"`, over an explicit
612
- start and end address — subject to the same 4096-byte ceiling as every other
613
- range read on this surface. Then walk the pointer chain from the first line
614
- until the pointer is `$00 $00`.
615
-
616
- ### Keyword tokens (BASIC V2)
617
-
618
- Bytes with the high bit set, `$80` through `$CB`, are keywords:
619
-
620
- | Hex | Keyword | Hex | Keyword | Hex | Keyword | Hex | Keyword |
621
- | --- | --- | --- | --- | --- | --- | --- | --- |
622
- | `$80` | `END` | `$93` | `LOAD` | `$A6` | `SPC(` | `$B9` | `POS` |
623
- | `$81` | `FOR` | `$94` | `SAVE` | `$A7` | `THEN` | `$BA` | `SQR` |
624
- | `$82` | `NEXT` | `$95` | `VERIFY` | `$A8` | `NOT` | `$BB` | `RND` |
625
- | `$83` | `DATA` | `$96` | `DEF` | `$A9` | `STEP` | `$BC` | `LOG` |
626
- | `$84` | `INPUT#` | `$97` | `POKE` | `$AA` | `+` | `$BD` | `EXP` |
627
- | `$85` | `INPUT` | `$98` | `PRINT#` | `$AB` | `-` | `$BE` | `COS` |
628
- | `$86` | `DIM` | `$99` | `PRINT` | `$AC` | `*` | `$BF` | `SIN` |
629
- | `$87` | `READ` | `$9A` | `CONT` | `$AD` | `/` | `$C0` | `TAN` |
630
- | `$88` | `LET` | `$9B` | `LIST` | `$AE` | `^` | `$C1` | `ATN` |
631
- | `$89` | `GOTO` | `$9C` | `CLR` | `$AF` | `AND` | `$C2` | `PEEK` |
632
- | `$8A` | `RUN` | `$9D` | `CMD` | `$B0` | `OR` | `$C3` | `LEN` |
633
- | `$8B` | `IF` | `$9E` | `SYS` | `$B1` | `>` | `$C4` | `STR$` |
634
- | `$8C` | `RESTORE` | `$9F` | `OPEN` | `$B2` | `=` | `$C5` | `VAL` |
635
- | `$8D` | `GOSUB` | `$A0` | `CLOSE` | `$B3` | `<` | `$C6` | `ASC` |
636
- | `$8E` | `RETURN` | `$A1` | `GET` | `$B4` | `SGN` | `$C7` | `CHR$` |
637
- | `$8F` | `REM` | `$A2` | `NEW` | `$B5` | `INT` | `$C8` | `LEFT$` |
638
- | `$90` | `STOP` | `$A3` | `TAB(` | `$B6` | `ABS` | `$C9` | `RIGHT$` |
639
- | `$91` | `ON` | `$A4` | `TO` | `$B7` | `USR` | `$CA` | `MID$` |
640
- | `$92` | `WAIT` | `$A5` | `FN` | `$B8` | `FRE` | `$CB` | `GO` |
641
-
642
- Bytes between `$20` and `$7F` are literal PETSCII characters — strings,
643
- variable names, numbers.
644
-
645
- ### What a decoding pass would write
646
-
647
- Per line, batched through `anno_batch_execute`:
648
-
649
- 1. `anno_set_data_type` `address` over bytes 0–1 (the next-line pointer).
650
- 2. `anno_set_data_type` `word` over bytes 2–3 (the line number).
651
- 3. `anno_set_data_type` `byte` from byte 4 through the `$00` terminator,
652
- inclusive.
653
- 4. `anno_set_comment` `"side"` at byte 0, carrying the reconstructed line —
654
- `10 REM LODE RUNNER`.
655
-
656
- Then jump to the next-line pointer and repeat until it reads `$00 $00`, and
657
- finally mark that `$00 $00` terminator itself as `word`. Nothing needs to be
658
- "saved": every one of those writes committed and fsynced inside its own call.
659
- `anno_save_project` performs **no write at all** — it reports the store's current
660
- revision, which is what to quote when you write the pass up.
661
-
662
- ## Which skill does what
663
-
664
- This one is the route between the stations. It does not restate what the others carry.
665
-
666
- | Need | Go to |
667
- |---|---|
668
- | A disk image's directory, BAM, sector chains, or a named file's raw bytes | `c64-disk-access` |
669
- | Detokenizing a BASIC stub, or its machine-code handover address | `c64-petcat` |
670
- | A verified 64K image, or comparing two captures | `c64-ram-capture` |
671
- | What a specific address or bit means | `c64-memory-mapping` — `node … lookup '$D018'` |
672
- | Assembling | `acme-build` |
673
- | Static disassembly of a `.prg` or flat image | **`anno export-asm`** — withdrawn 2026-08-29, returned 2026-08-31 behind a real-ACME byte-diff oracle that is test-only, so the verb writes source and assembles nothing. Read one range at a time with `anno_read_region` for a single routine (see above) |
674
- | Whether a byte is original or cracker-changed | `c64-provenance-diff` |
675
- | The emulator stopped moving — wedged, self-trapped, or respawned | `vice-wedge-triage` |
676
- | **Which address to read next, and what the answer rules out** | here |
677
-
678
- ## References
679
-
680
- | File | Covers |
681
- |---|---|
682
- | `references/control-flow.md` | Entry point, the six vectors, IRQ source, main-loop shapes, state machines |
683
- | `references/graphics.md` | The VIC derivation chain, the char-ROM shadow trap, sprites, watch targets |
684
- | `references/sound-and-input.md` | SID player vs `$D41B`-as-RNG vs digi. CIA#1 vs CIA#2 |
685
- | `references/observation-hazards.md` | Every way a live read gives a wrong answer. **Read before driving.** |
686
- | `references/tool-selection.md` | Which `mcp__plugin_c64-re-tools_vice__*` call answers which question, and what to delegate |
687
- | `references/reconstruction.md` | Binary inclusion, behavioural-equivalence correctness bar, SMC labels, label vocabulary |
688
- | `templates/memory-map.template.md` | `render-memmap`'s provenance sidecar schema and the confidence vocabulary — the rendered map itself is generated, not hand-authored |
689
-
690
- Record findings that make RE faster in your own project notes **at the moment you find them**,
691
- graded with `Evidence:` and `Confidence:`. Promote a finding by re-logging it with the new
692
- evidence, never by editing an old grade in place — the grade is only worth anything if it says
693
- what was actually known when it was written.
694
-
695
- ## Troubleshooting
696
-
697
- | Symptom | Remedy |
698
- |---|---|
699
- | `$0314` holds something that is not a plausible address | Check HIRAM. With the KERNAL banked out the RAM vectors are uninitialised. Read `$FFFE/$FFFF`. |
700
- | Every graphics pointer is wrong, with no error | `$DD00` bits 0-1 are **inverted**. Re-derive the bank first. Everything else hangs off it. |
701
- | The charset at the computed address is garbage | CB may resolve into the char-ROM shadow (`$1000`/`$9000`, banks 0/2). `derive.mjs vic` flags it — there is no charset in RAM to extract. |
702
- | A sprite decodes as noise | Check `$D015` first. A disabled sprite's registers are stale. Then check MCM — multicolor decoded as hires comes out twice as wide. |
703
- | Computed mode is "INVALID — screen goes black" | You caught the registers mid-update inside a raster split. Re-read. |
704
- | The emulator looks dead | Enumerate armed checkpoints before anything else. See hazard 2. |
705
- | `vice_keyboard_type` does nothing | The game polls `$DC00`/`$DC01` directly. `vice_keyboard_matrix` is **permanently unavailable** — see `references/observation-hazards.md` § 4 for the reason and the available alternative. |
706
- | Two captures of the same checkpoint differ | Expected. Full-64K identity is impossible in principle. Use `c64-ram-capture`'s drift rules. |