@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,631 +0,0 @@
1
- ---
2
- name: c64-memory-mapping
3
- description: Look up what any C64 address means and turn raw 6502 disassembly into documented assembly, by resolving every address against the C64 memory map, KERNAL ROM routine list, canonical assembler symbols, and per-bit VIC-II/SID/CIA register tables. Use when asked to annotate or comment assembly against the published memory map, document a disassembly listing by resolving every address it touches, or look up an address like $D020, $EA24 or $FFD2.
4
- ---
5
-
6
- # C64 memory mapping & annotated disassembly
7
-
8
- Look up what a C64 address means, and document a 6502 listing by resolving every
9
- address it touches. One script does both, offline, anywhere Node ≥18 runs:
10
-
11
- ```bash
12
- D=src/skills/c64-memory-mapping/scripts/driver.mjs # relative to the repo root
13
-
14
- node $D lookup '$D011' '$FFD2' # what lives at an address
15
- node $D annotate --file game.asm # document a listing or .asm file
16
- … | node $D annotate # ... or one piped in on stdin
17
- node $D memmap # refresh the table from its sources (needs network)
18
- ```
19
-
20
- Write an address however the source you copied it from wrote it — hex `$D011`,
21
- `0xD011`, `D011h` or `D011`, binary `%1101000000010001`, or decimal `53265` all
22
- reach the same register, in either letter case.
23
-
24
- ## Look up an address
25
-
26
- `lookup` prints the full published prose for an address, most specific match
27
- first, each tagged with the source it came from and the memory region it sits in.
28
- Registers carry their per-bit breakdown, and the wider regions enclosing the
29
- address follow it:
30
-
31
- ```
32
- === $D011 ===
33
- $D011 [$D000-$D3FF, 53248-54271 VIC-II; video display] <sta>
34
- Screen control register #1. Bits: Bits #0-#2: Vertical raster scroll. Bit #3: Screen height; 0 =
35
- 24 rows; 1 = 25 rows. Bit #4: 0 = Screen off, complete screen is covered by border; 1 = Screen
36
- on, normal screen contents are visible. Bit #5: 0 = Text mode; 1 = Bitmap mode. Bit #6: 1 =
37
- Extended background mode on. Bit #7: Read: Current raster line (bit #8). Write: Raster line to
38
- generate interrupt at (bit #8). Default: $1B, %00011011.
39
- $D011 [MOS 6566 VIDEO INTERFACE CONTROLLER (VIC)] <io>
40
- VIC Control Register
41
- bit 7 Raster Compare: (Bit 8) See 53266
42
- bit 6 Extended Color Text Mode 1 = Enable
43
- bit 5 Bit Map Mode. 1 = Enable
44
- bit 4 Blank Screen to Border Color: O = Blank
45
- bit 3 Select 24/25 Row Text Display: 1 = 25 Rows
46
- bit 2-0 Smooth Scroll to Y Dot-Position (0-7)
47
- $D000-$D02E [C64 memory map (labelled)] <zim>
48
- 6566 Video Interface Chip, VIC II.
49
- …
50
- ```
51
-
52
- Reach for this whenever a bare address needs a meaning — a register you are about
53
- to write, a `JSR` target, or a symbol name to give a variable.
54
-
55
- ## Annotate a listing
56
-
57
- ```bash
58
- node $D annotate --file game.asm --out game.documented.asm
59
- ```
60
-
61
- The input is any text carrying 6502 mnemonics: hand-written source, or a listing
62
- from whichever disassembler produced it. Lines come back byte-for-byte —
63
- indentation, labels, directives, blank lines and existing comments intact — with
64
- a `; $addr (SYMBOL) = description` comment appended:
65
-
66
- ```
67
- * = $C000
68
- start lda #$00
69
- sta $d021 ; $D021 = Background color (only bits #0-#3)
70
- loop: ldx $dc01 ; $DC01 = Port B, keyboard matrix rows and joystick #1
71
- inc $d020 ; $D020 = Border color (only bits #0-#3)
72
- jsr $ffd2 ; $FFD2 = Output Vector, chrout
73
- lda ($fb),y ; $00FB-$00FE (FREKZP, pointer) = Unused (4 bytes)
74
- sta $0400,x ; $0400-$07E7 (VICSCN, indexed) = Default area of screen memory (1000 bytes)
75
- bne loop
76
- jmp ($0314) ; $0314-$0315 (CINV, pointer) = Execution address of interrupt service routine
77
- rts
78
- ```
79
-
80
- `annotate` prepends a header block that lists every referenced address with its
81
- full description, symbol and region (elided above). Measured on the two examples
82
- on this page, the eleven-line listing above grows a 23-line header and the
83
- nine-line IRQ excerpt below grows a 25-line one — a little over 2x the input
84
- either way. Pass
85
- `--no-header` to drop it and get the annotated body alone.
86
-
87
- Options:
88
-
89
- - `--out FILE` writes the result to a file instead of stdout.
90
- - `--max-span N` keeps comments to hits narrower than N bytes, for terser output.
91
- Default is 4096 bytes: at that width a hit as wide as screen RAM
92
- (`$0400-$07E7`, 1000 bytes) or the `$C000-$CFFF` block still earns an inline
93
- comment, while the 8 KB BASIC and KERNAL ROM blocks do not — a branch
94
- annotated "KERNAL ROM (8192 bytes)" teaches nobody anything. The cap applies
95
- only to non-flow instructions. `annotate` holds flow instructions (`JMP`,
96
- `JSR`, branches, `RTS`, `RTI`) to 2 bytes regardless of `--max-span`, so a jump or
97
- branch only earns a comment when it targets a specific vector such as
98
- `$0314`. `--max-span 2` gives register- and variable-level comments only.
99
- - `--no-header` suppresses the prepended header block, for piping annotated
100
- output straight into a file across a large listing set.
101
- - `--file -` reads stdin, identical to a bare `annotate`.
102
-
103
- Piping works the same way, to annotate straight from another command:
104
-
105
- ```bash
106
- node $D annotate --max-span 2 < irq.txt
107
- ```
108
-
109
- ```
110
- $EA31: 20 EA FF JSR $FFEA ; $FFEA = Increment Real-Time Clock
111
- $EA34: A5 CC LDA $CC ; $00CC (BLNSW) = Cursor visibility switch
112
- $EA36: D0 29 BNE $EA61
113
- $EA38: C6 CD DEC $CD ; $00CD (BLNCT) = Delay counter for changing cursor phase
114
- $EA3C: A9 14 LDA #$14
115
- $EA40: A4 D3 LDY $D3 ; $00D3 (PNTR) = Current cursor column
116
- $EA44: AE 87 02 LDX $0287 ; $0287 (GDCOL) = Color of character under cursor
117
- $EA47: B1 D1 LDA ($D1),Y ; $00D1-$00D2 (PNT, pointer) = Pointer to current line in screen memory
118
- $EA4F: 20 24 EA JSR $EA24 ; $EA24 = Syncronise Color Pointer
119
- ```
120
-
121
- ## Reading the annotations
122
-
123
- Weigh a comment by the region it describes:
124
-
125
- - **`$D000-$DFFF` is authoritative.** Hardware decides what these mean, so a VIC,
126
- SID or CIA comment holds for any program. `LDA $DC01 / AND #$10 / BNE` annotates
127
- as "Port B, keyboard matrix rows and joystick #1", bit 4 is the fire button, and
128
- that reading is sound because it rests on hardware. KERNAL entry points are
129
- equally solid wherever ROM is banked in — `$01` bits #0-#2 select it, and the
130
- vector at `$0314` shows whether the KERNAL IRQ path is in use.
131
- - **Zero page, `$0200-$07FF` and the BASIC area describe BASIC and KERNAL usage.**
132
- Read them as a strong hint and check against the program's own behaviour
133
- before adopting the name. A game that banks ROM out keeps its own variables
134
- there. A real case: at `$08E6` a game's `LDA $49` gets labelled `FORPNT`
135
- ("value of current variable during LET"), when `$49` is really one of that
136
- game's own variables. Take the address, check the meaning.
137
- - **A region-only answer is not an error.** An address that no source names
138
- specifically prints only the wider regions enclosing it — no error, no
139
- not-found line. `node $D lookup '$1234'` prints:
140
- ```
141
- === $1234 ===
142
- $0801-$9FFF [$0800-$9FFF, 2048-40959 BASIC area] <sta>
143
- Default BASIC area (38911 bytes).
144
- $0800-$9FFF [C64 memory map (labelled)] <zim>
145
- Normal BASIC Program space.
146
- ```
147
- This is the dominant case for any game's own code: a region-only answer
148
- means "the four tables do not name this exact address", not "this address is
149
- unmapped". It differs from the genuine zero-hit case, where `lookup` prints
150
- `(not in memory map)` because nothing at all covers the address
151
- (driver.mjs:485-487). Checked directly against the committed table (a scan of
152
- `memmap.json`, not a `memmap` rebuild): every address `$0000`-`$FFFF` is
153
- covered by at least one entry as of this build, so `(not in memory map)` is
154
- not reachable for any valid address today — the branch exists for a future
155
- table that loses coverage, not for anything the current one omits.
156
-
157
- For a game, the fastest route to a real name is to annotate with `--max-span 2`,
158
- trust the I/O lines immediately, and check the rest by watching what the code
159
- does with them.
160
-
161
- ## Where the data comes from
162
-
163
- `node $D memmap` rebuilds the table from four published sources, each covering
164
- what the others leave out:
165
-
166
- | Source | Contribution |
167
- |---|---|
168
- | [sta.c64.org/cbm64mem.html](https://sta.c64.org/cbm64mem.html) | richest prose for zero page, work areas, screen RAM, I/O |
169
- | [C64.MemoryMap.txt](https://www.zimmers.net/anonftp/pub/cbm/maps/C64.MemoryMap.txt) | canonical assembler symbols — `PNT`, `CINV`, `VICSCN`, `TXTTAB` |
170
- | [krnromma.htm](http://unusedino.de/ec64/technical/aay/c64/krnromma.htm) | every KERNAL ROM routine by name, so `JSR $EA24` reads as "Syncronise Color Pointer" |
171
- | [C64io.txt](https://www.zimmers.net/anonftp/pub/cbm/maps/C64io.txt) | VIC/SID/CIA registers broken down per bit |
172
-
173
- The built table is committed alongside the script, so `lookup` and `annotate` need
174
- only Node. Rebuild when a source publishes a correction. `lookup`'s `<src>` tags
175
- show which table any given claim came from.
176
-
177
- `memmap` overwrites the committed, git-tracked `memmap.json` (driver.mjs:270 —
178
- 235,925 bytes as of 2026-08-04, check with `wc -c`) **in place, with no backup
179
- and no diff.** `memmap` gets one of the four sources (`http://unusedino.de/…`,
180
- driver.mjs:33) over plain HTTP with no TLS. The only guard against a bad rebuild
181
- is a per-source emptiness check plus a 600-entry floor across all sources
182
- combined (driver.mjs:262, 268). A partially-reachable or partially-changed source
183
- set can therefore silently replace good tracked data with less of it. Rebuild, then
184
- run `git diff --stat` on `memmap.json` before accepting the result, and
185
- `git checkout` the file if the diff is not explainable as the correction you
186
- were expecting. **`memmap` mutates tracked files** — unlike `lookup` and
187
- `annotate`, which are read-only — so run it deliberately, on a clean working
188
- tree, and review the diff before you commit it.
189
-
190
- ## Feeding the enum generator
191
-
192
- `memmap.json`'s structured `bits` entries are the source of the curated register bit-name table used
193
- to generate program-specific enums for this project's annotation store: register
194
- writes disassemble as `lda #D011_YSCROLL3_ROW25_SCREENON_TEXT` instead of a bare `#$1b`. The
195
- generator is `src/mcp/vice/anno-regbits-gen.ts`. Its committed output is
196
- `src/mcp/vice/anno-regbits.json`. That output is **digest-pinned** to `memmap.json`. A
197
- `node anno-regbits-gen.ts` run compares its own fresh build against the committed file, and CI fails
198
- if `memmap.json` changed without a re-run.
199
-
200
- **The honest gap:** only 29 of this file's 959 entries carry a structured `bits` array. `$D015`,
201
- `$D017`, `$D01A` and `$D01B`–`$D01D` — the sprite-plane bitmask registers a real game writes
202
- constantly — are **not** among those 29, so the enum generator supplies them from its own curated
203
- override table (`OVERRIDES` in `anno-regbits-gen.ts`), not from this file. Widening `memmap.json`'s
204
- `io` parser (or correcting the OCR damage already present in some `bits` prose, e.g. a letter `O` for
205
- the digit `0`) so those registers get a real structured entry here is separate work belonging to this
206
- skill, not the generator.
207
-
208
- **Installing those bit names into a program's own disassembly — dated withdrawal, 2026-08-29.** The
209
- table above only builds `anno-regbits.json`. Turning a specific program's register *writes* into
210
- named enum variants was the `gen-enums` CLI verb, and **that verb is WITHDRAWN from this surface, and
211
- no phase currently owns its return.** This notice previously forecast that it would come
212
- back as a rebuild over the annotation store alongside the ACME export route. The ACME export route
213
- did come back on 2026-08-31, but the phase that rebuilt it covered that route only — no requirement
214
- and no success criterion of it mentioned `gen-enums`. This notice corrects that forecast rather than
215
- removing it. Do not invoke the verb — it does not exist, and the invocation fails with an unknown-verb
216
- error and nothing to explain it.
217
-
218
- What it did, so the rebuild has a specification and so a reader knows what is missing: it read the
219
- program's own disassembly, created one enum variant per DISTINCT value actually written at each
220
- matching immediate-load address (named from the curated table above), and printed
221
- total/paired/unpaired register-store counts plus a per-enum variant count.
222
-
223
- **The by-hand route is open in the meantime, and it is the same one this skill already documents
224
- for any other enum.** `anno_create_project_enum` defines the variants and `anno_apply_enum_usage`
225
- binds one to the accessing instruction's address — see "Name it" and "Document it" below. That is
226
- manual where `gen-enums` was bulk, but it writes exactly the same rows into the store.
227
-
228
- ## Reference material for annotating
229
-
230
- Two deeper jobs feed the annotate/lookup job above, rather than standing apart from it: knowing
231
- what a whole region actually is (code, or one of eight kinds of data) before it can be annotated
232
- at all, and knowing what one of a program's own addresses represents when no published table can
233
- say. Both exist to serve a documented listing. You do not invoke either one as a job on its own.
234
-
235
- ### Classifying every region of an annotation project
236
-
237
- Everything above answers *what does this published address mean*. This section
238
- answers a different question over the same map: **given a loaded binary in an
239
- annotation project, what is each region of it — code, or one of eight kinds of
240
- data?**
241
-
242
- **This is the static answer, taken from bytes on disk.** `c64-program-recon`
243
- answers the *live* code-versus-data question — its step 5 is "what the PC
244
- actually visits across full coverage", and an execution trace beats every
245
- static heuristic on this page. Run the live pass when you have a running
246
- machine. Run this one when all you have is a file, and treat a later trace as
247
- the thing that overrules it.
248
-
249
- When a binary is first loaded, the auto-analyzer traces reachable code from the
250
- entry point and marks it **Code**. Everything else is **Undefined** — not
251
- "data", just unexplored. The job here is to walk the Undefined regions, work
252
- out what each one actually is, and set it.
253
-
254
- #### The one mistake that matters more than the rest
255
-
256
- **Never disassemble a region without concrete proof that it executes.** Random
257
- data routinely disassembles into plausible-looking instruction sequences, and
258
- that is *not* evidence of code — it is a property of the 6502's dense opcode
259
- map. A region earns the Code type only when at least one of these holds:
260
-
261
- - **It is a `JSR`/`JMP` target.** Already-analysed code contains `JSR $addr` or
262
- `JMP $addr` landing in it. Check with `anno_get_cross_references` — it names
263
- the `image` as well as the `store`, takes a REQUIRED `max_results`, and unions
264
- three sources on every call without caching any of them: the instructions
265
- decoded fresh out of every range typed `code`, the typed split ADDRESS tables
266
- (the `_address` forms produce cross-references and the `_word` forms do not),
267
- and the stored rows, which exist because a computed dispatch cannot be
268
- recovered from bytes at all.
269
- - **It is a branch target** of an already-analysed `BNE`/`BEQ`/`BCC`/`BCS`/
270
- `BPL`/`BMI`/`BVC`/`BVS`.
271
- - **It is a vector or handler**: its address appears in a vector table
272
- (`$FFFA`–`$FFFF`, `$0314`–`$0319`), in an `address` or split-address block,
273
- or in a jump table reached by `JMP ($addr)`.
274
- - **A human says so explicitly.**
275
-
276
- None of those? Leave it **Undefined**, or classify it as data — even when the
277
- bytes disassemble cleanly. "It looked like code" is how a sprite sheet becomes
278
- four hundred lines of fiction.
279
-
280
- #### The order of the passes
281
-
282
- Work the Undefined blocks in four passes, in this order. Do not interleave
283
- them. Each pass makes the next one cheaper.
284
-
285
- 1. **Provably-reachable code** — read from the entry point of each region that
286
- meets the proof bar above with `anno_disassemble`, then type the range you
287
- actually checked as `code` with `anno_set_data_type`. **Reading and typing
288
- are two calls on this surface, and that is deliberate:** `anno_disassemble`
289
- decodes fresh from the image bytes and writes nothing at all, so nothing is
290
- ever classified as code by a decoder's guess — the boundary you record is
291
- the one you read and judged.
292
- 2. **Text** — PETSCII and screencode strings.
293
- 3. **Tables** — byte, word, address and split (lo/hi, hi/lo) tables.
294
- 4. **Whatever is left** — decide data, or leave it Undefined for a human.
295
- **Never** speculatively disassemble in this pass. By definition nothing here
296
- met the proof bar.
297
-
298
- #### Scope, and reading a region
299
-
300
- 1. `anno_get_binary_info` first. Keep `origin`, `size`, `system`, `filename`,
301
- `description` and `may_contain_undocumented_opcodes`.
302
- - `system` names the target machine. On a C64 the rest of this skill *is*
303
- the memory map you need — `lookup` any address a region touches before
304
- guessing at it.
305
- - `filename` and `description` are the software context. A known title, a
306
- known music driver or a known packer changes what a region is likely to
307
- be.
308
- - `may_contain_undocumented_opcodes: true` means illegal opcodes (`LAX`,
309
- `SAX`, `SLO`, `DCP`, `ISC`) may appear. **Do not misclassify those as
310
- data** — they are valid instructions. The flag is a human's hint, not a
311
- guarantee: some programs use them with the flag false.
312
- 2. `anno_get_blocks` to see what is already classified, and focus on the
313
- Undefined entries. `max_results` is REQUIRED with no default. Pass a ceiling
314
- above the range count you expect and compare the returned count against it.
315
- 3. Read each candidate region twice, through `anno_read_region` (which names
316
- both the `store` and the `image`): `view:
317
- "hexdump"` shows the byte patterns, and **omitting `view`** gives the
318
- disassembly view — that is the parameter's documented default — which shows
319
- how the region would decode. `anno_read_region` caps the combined byte count
320
- at **4096 bytes** per call (`ANNO_READ_REGION_MAX_BYTES`). It refuses a
321
- request above the cap by name rather than truncating it — so walk a large binary in consecutive ranges.
322
- Chunks of **256–512 bytes** are the practical working size for
323
- classification. A 4096-byte hexdump is more than can be read carefully in
324
- one pass.
325
-
326
- #### Applying the classification
327
-
328
- - **Code**: `anno_disassemble` from the entry-point address to READ, then
329
- `anno_set_data_type` with `"code"` to RECORD the range you checked.
330
- `anno_disassemble` performs no write, so it classifies nothing until you say
331
- so. Type only as far as you actually followed the flow — the end of a routine
332
- at its `RTS`/`RTI`/`JMP`, not "to the end of the region" — because the typed
333
- `code` ranges are what `anno_get_cross_references` and `anno_search` decode
334
- instructions out of later.
335
- - **Data**: batch the `anno_set_data_type` calls through
336
- `anno_batch_execute`. A real classification pass is dozens of ranges, and one
337
- batch is one open/commit/close rather than dozens.
338
- - **A wrong classification is not a disaster and does not need undoing.**
339
- `anno_set_data_type` is idempotent over a range: set the correct type again
340
- over the same range and the previous one is gone, and an identical repeat
341
- succeeds reporting `changed: false`. (Upstream reaches for an undo call here.
342
- This project does not expose one, and does not need to.)
343
- - **Read the disclosures on a successful retype. They are not errors and they
344
- are never dropped.** `contradictedComments` names comments whose recorded
345
- confidence now contradicts the type you just applied, and
346
- `reinterpretedSplitTables` names every split table the write FRAGMENTED, with
347
- the entry-address pairs it read before and the pairs each surviving remainder
348
- reads now. A split table's entries re-pair as a function of the row's start
349
- **and** its length, so a fragment decodes to different 16-bit values than the
350
- ones a human recorded.
351
- - A split layout REFUSES an odd byte count — the low half and the high half must
352
- be the same length.
353
- - Re-read `anno_get_blocks` after each batch to check what actually landed.
354
- `max_results` is REQUIRED on that read and has no default. That read returns
355
- the true match count beside the list.
356
-
357
- Example of a valid data-only batch, then the code regions separately:
358
-
359
- ```
360
- anno_batch_execute:
361
- - anno_set_data_type start=2304 end=2367 data_type="byte"
362
- - anno_set_data_type start=2368 end=2431 data_type="petscii"
363
- then: anno_disassemble address=2049 # read, writes nothing
364
- then: anno_set_data_type start=2049 end=2303 data_type="code"
365
- then: anno_get_blocks max_results=500 # refresh
366
- ```
367
-
368
- #### The block types
369
-
370
- | Block type | `data_type` | When |
371
- |---|---|---|
372
- | **Code** | `code` | Provably-executed instructions. Read the extent with `anno_disassemble` first — it writes nothing — then record exactly what you checked. |
373
- | Byte | `byte` | Raw 8-bit data: sprites, bitmaps, charsets, lookup tables, variables, unknowns |
374
- | Word | `word` | 16-bit little-endian values: 16-bit variables, math constants, SID frequencies |
375
- | Address | `address` | 16-bit LE pointers — jump tables, vector lists. Creates cross-references |
376
- | PETSCII text | `petscii` | PETSCII strings: messages, prompts, anything bound for `$FFD2` |
377
- | Screencode text | `screencode` | Text written straight to screen RAM (`$0400`–`$07E7`) |
378
- | Lo/Hi address | `lo_hi_address` | Split address table, low bytes first. Even byte count required |
379
- | Hi/Lo address | `hi_lo_address` | Split address table, high bytes first. Even byte count required |
380
- | Lo/Hi word | `lo_hi_word` | Split word table, low half first — e.g. a SID frequency table |
381
- | Hi/Lo word | `hi_lo_word` | Split word table, high half first |
382
- | External file | `external_file` | Large blobs to export as-is: SID tunes, bitmaps, charsets |
383
- | Undefined | `undefined` | Reset to unknown. The honest answer for a region you cannot place |
384
-
385
- #### Recognising each kind
386
-
387
- **Byte data** — regular patterns that form no valid instruction sequence.
388
- Addressed by `LDA addr,X` / `LDA addr,Y` table lookups. Sprite data in 63-byte
389
- (padded to 64) units, usually grouped. Bitmap data in 8-byte character cells.
390
- Colour data confined to `$00`–`$0F`. Or random-looking bytes between two code
391
- blocks whose disassembly is nonsense.
392
-
393
- **Word data** — byte pairs forming meaningful 16-bit values (screen addresses,
394
- timer values). Loaded low-then-high by adjacent `LDA addr` / `LDA addr+1`.
395
-
396
- **Address tables** — byte pairs that read as little-endian addresses landing
397
- *inside* the binary. Reached by `JMP ($addr)` or indexed indirect reads. Jump
398
- tables, dispatch tables and vector lists all live here.
399
-
400
- **Split lo/hi (or hi/lo) tables** — two equal halves, one of plausible low
401
- bytes and one of plausible high bytes, referenced separately:
402
- `LDA lo,X / STA ptr / LDA hi,X / STA ptr+1 / JMP (ptr)`. Recombine the halves
403
- and check the addresses are real. Lo/Hi (low half first) is the commoner form
404
- on the 6502. **The total byte count must be even and the halves equal** — an
405
- odd count means the boundary is in the wrong place.
406
-
407
- **PETSCII text** — bytes in `$20`–`$7E` (unshifted) or `$C0`–`$DF` (shifted),
408
- often recognisably English since PETSCII shares `$20`–`$5F` with ASCII.
409
- Terminated by `$00`, `$0D`, or a high-bit sentinel. Reached by `$FFD2` (CHROUT)
410
- or `$AB1E` (BASIC STROUT). `GAME OVER`, `PRESS FIRE`, menus, credits.
411
-
412
- **Screencode text** — bytes in `$00`–`$3F` where `$00` is `@` and `$01` is `A`.
413
- Copied directly to `$0400`–`$07E7`. `LDA data,X / STA $0400,X` is the
414
- give-away. A full screen dump is exactly 1000 bytes.
415
-
416
- **External file** — a large contiguous non-code block matching a known format:
417
- a `PSID`/`RSID` header, a 2048-byte charset (256 chars × 8 bytes), sprite data
418
- in multiples of 64, or a bitmap. Export it rather than annotate it.
419
-
420
- **PETSCII is not screencode.** If the program copies it to `$0400`, it is
421
- screencode. If the program passes it to CHROUT, it is PETSCII. Getting this backwards produces text
422
- that renders as garbage in exactly one of the two places.
423
-
424
- #### The adjacent-table limitation, and how it was closed
425
-
426
- **Dated limitation, recorded 2026-08-24. CLOSED 2026-08-29 when the store
427
- changed underneath it.** The old store auto-merged two adjacent regions of the
428
- *same* type into one block, so two byte tables side by side — or the two halves
429
- of a split table sitting next to each other — lost their boundary on read, and
430
- the answer upstream reached for was a `toggle_splitter` call this project never
431
- exposed.
432
-
433
- **The annotation store does not do that.** It never joins two rows of its own
434
- accord: adjacent same-type ranges stay two rows, and `anno_get_blocks` reports
435
- them as two. A range you name that *spans* several existing rows still collapses
436
- to the one row you asked for — that is you asking, not the store deciding — and
437
- partial overlaps split and preserve the addresses outside your range rather than
438
- swallowing them.
439
-
440
- So the working rules are now the ordinary ones:
441
-
442
- - **Do** rely on the block listing to tell two adjacent same-type tables apart.
443
- It can, provided you typed them as two calls rather than one spanning call.
444
- - **Do** still record the boundary in the annotations as well — an
445
- `anno_set_label_name` at the start of the second table and a line comment on
446
- both naming the extent you determined. A name and an evidence line survive a
447
- later retype. A row boundary does not.
448
- - **Do not** carry the old over-merge caveat into a report taken from this
449
- store. It was true of the retired one and is not true here.
450
-
451
- #### Labelling, and the report
452
-
453
- Name what you classified — `anno_set_label_name` on entry points, tables and
454
- strings — and comment it with `anno_set_comment` (`"line"` above,
455
- `"side"` beside). For the conventions to name things *by*, and for the
456
- comment-block format to use on a subroutine, follow the absorbed routine
457
- procedure in `src/skills/c64-program-recon/SKILL.md`.
458
-
459
- Then report, and mean it:
460
-
461
- - Total blocks by type.
462
- - Notable findings — "three PETSCII strings", "a lo/hi jump table at `$1200`".
463
- - **Every region still uncertain or still Undefined, by address.** This is the
464
- part that makes the pass reusable. A classification report with no uncertain
465
- regions on a real game is almost always a report that stopped looking.
466
- - The store revision, read with `anno_save_project`. That verb performs **no
467
- write** — every classification call above already committed and fsynced its
468
- own — so quoting the revision pins the report to an exact store
469
- state rather than to "after the pass".
470
-
471
- #### What goes wrong
472
-
473
- | Symptom | What it actually is |
474
- |---|---|
475
- | A region disassembles beautifully but has no incoming reference | Data. Decodability is not evidence. Leave it Undefined. |
476
- | Disassembly full of impossible branches or `BRK` (`$00`) floods | Data misread as code. |
477
- | Odd-looking instructions, but real `JSR`/`JMP` cross-references land here | Probably code using undocumented opcodes. Check the `may_contain_undocumented_opcodes` hint. |
478
- | A split table's addresses recombine to nonsense | The half boundary is misplaced, or the table is hi/lo rather than lo/hi. |
479
- | Two tables you classified separately show up as one block | The adjacent-table limitation above. Not your error. |
480
- | Text renders as garbage on screen but fine through CHROUT | It is PETSCII, typed as screencode — or the reverse. |
481
-
482
- ### What a symbol in the store actually represents
483
-
484
- `lookup` at the top of this page answers what a **published** address means —
485
- a hardware register, a KERNAL entry point, an OS variable. That answer comes
486
- from four tables and holds for every program.
487
-
488
- This section is the other half: **what a program's *own* address represents.**
489
- No table can tell you, because the program's code decided the meaning.
490
- When `lookup` returns a region-only answer — the dominant case for a game's own
491
- code and variables, as noted above — this is the procedure that gets you a
492
- name.
493
-
494
- #### 1. Target and context
495
-
496
- - **Always start from an explicit address**, `$XXXX` or its decimal
497
- equivalent. There is no editor cursor in this project's route, and upstream's
498
- own text forbids relying on one anyway. `anno_get_address_details` composes
499
- the symbol, comments, block type and cross-references for one explicit
500
- address in a single call.
501
- - `anno_get_binary_info` for `system`, `filename`, `description` and
502
- `may_contain_undocumented_opcodes`. `filename` and `description` are how a
503
- symbol gets a *domain* name — `lap_counter` in a racing game, `lives` in a
504
- platformer — instead of a generic one. With undocumented opcodes in play,
505
- remember that `LAX`, `SAX` and `DCP` have real read/write side effects that
506
- belong in the data-flow picture.
507
-
508
- #### 2. Gather the usage
509
-
510
- `anno_get_cross_references` on the address — naming the `store`, the `image` and
511
- a REQUIRED `max_results` — returns every site that touches it. Read the
512
- instruction at each site, because the instruction is the evidence:
513
-
514
- - **Writes**: `STA`, `STX`, `STY`
515
- - **Reads**: `LDA`, `LDX`, `LDY`, `BIT`, `CMP`, `CPX`, `CPY`, `ADC`, `SBC`
516
- - **Read-modify-write**: `INC`, `DEC`, `ASL`, `LSR`, `ROL`, `ROR`
517
-
518
- **Zero cross-references is a result, not a dead end.** Three explanations, in
519
- order of likelihood:
520
-
521
- 1. It is reached **indirectly**. Check whether it is in the zero page
522
- (`$00`–`$FF`) and whether nearby code uses `($addr),Y` or `($addr,X)`. An
523
- indirect pointer's *target* has no direct reference by construction.
524
- 2. It is a **well-known system address** the disassembler does not cross-
525
- reference. Run `lookup` on it — that is exactly the case the four tables
526
- above cover.
527
- 3. It is genuinely **dead**: unused variable, or code no longer reached. Say so
528
- in the report rather than inventing a purpose.
529
-
530
- #### 3. Place it
531
-
532
- **A hardware register?** `lookup` it. If one of the four tables names it, take
533
- the published name and the per-bit breakdown with it — that reading rests on
534
- hardware and holds for any program.
535
-
536
- **Inside a well-known global block?** Screen RAM (`$0400`–`$07E7`), colour RAM
537
- (`$D800`–`$DBFF`), a sprite pointer at VM+`$03F8`. **Do not skip these as
538
- "obvious".** Name them systematically from the base plus the offset —
539
- `SCREEN_ROW03_COL12` — so contiguous structures read as structures instead of a
540
- field of auto-generated offsets.
541
-
542
- **An external ROM or system routine?** An `e_` prefix, or an address in KERNAL
543
- space (`$E000`–`$FFFF`), or a standard shadow vector. `lookup` gives the
544
- routine's published name. Rename to the conventional form — `$FFD2` becomes
545
- `KERNAL_CHROUT`, `$EA31` becomes `SYSTEM_IRQ_HANDLER`.
546
-
547
- **A 16-bit pointer?** Below `$0100`, and used with indirect-indexed `($xx),Y`
548
- or indexed-indirect `($xx,X)`. Rename to `ptr_`/`vec_` form and comment what it
549
- points *to*, which is the thing the name cannot carry.
550
-
551
- **A flag or bitmask?** Only ever `$00`/`$01` or `$00`/`$FF`. Tested with `BIT`
552
- or `LDA`/`BEQ`. Name it as a predicate — `is_active`, `has_collided`. When the
553
- individual bits carry separate meanings, that is an enum: define it with
554
- `anno_create_project_enum` (`$01 = ACTIVE`, `$02 = COLLIDED`, `$04 =
555
- VISIBLE`) and apply it with `anno_apply_enum_usage` so every bitmask test
556
- reads as words rather than hex.
557
-
558
- **A counter or index?** `INC`/`DEC` inside a loop, compared against a limit
559
- with `CPX`/`CPY`/`CMP`. `loop_idx`, `sprite_count`, `delay_timer`.
560
-
561
- **A state variable?** Several distinct values, often feeding a dispatch
562
- (`ASL` / `TAX` / `JMP (table,X)`). Name it `game_state` or `current_mode` — and
563
- these are the best enum candidates of all. Look for an existing enum first.
564
- Define one (`0 = INIT`, `1 = TITLE`, `2 = GAMEPLAY`, `3 = GAME_OVER`) with a
565
- real `description` if none matches, then apply it to every instruction reading
566
- or writing the variable.
567
-
568
- #### 4. Name it
569
-
570
- | Symbol kind | Convention | Example |
571
- |---|---|---|
572
- | Zero-page variable | `zp_` prefix | `zp_player_lives`, `zp_delay_timer` |
573
- | Zero-page pointer | `zp_ptr_` prefix | `zp_ptr_screen`, `zp_ptr_dest` |
574
- | RAM variable | `snake_case` | `score_hi`, `current_level` |
575
- | Pointer / vector | `ptr_` / `vec_` prefix | `ptr_screen`, `vec_irq` |
576
- | Hardware register | `UPPER_SNAKE` | `VIC_SPR0_X`, `SID_FREQ_LO1` |
577
- | Constant / address | `UPPER_SNAKE` | `SCREEN_RAM`, `CHR_ROM_BASE` |
578
- | Routine entry point | `snake_case` | `init_screen`, `draw_sprite` |
579
- | External ROM call | `KERNAL_` / `OS_` | `KERNAL_CHROUT`, `KERNAL_CLRCHN` |
580
-
581
- > **The zero-page rule overrides all of the above.** An address at or below
582
- > `$FF` **must** carry the `zp_` prefix — a zero-page pointer becomes
583
- > `zp_ptr_`, a zero-page flag becomes `zp_is_active`, and a zero-page OS
584
- > variable becomes `zp_`-prefixed too. The prefix makes the addressing mode
585
- > visible at every use site, which is the whole point.
586
-
587
- Apply it with `anno_set_label_name`.
588
-
589
- #### 5. Document it
590
-
591
- - `anno_set_comment` `"line"` at the definition: the range it occupies, its
592
- purpose, its bitfield layout if it has one.
593
- - `anno_set_comment` `"side"` at the interesting *uses*: why this read, why
594
- this write. "Reset life counter" beside a `STA` is worth more than any name.
595
- - Define and apply enums where the values form a set (above).
596
-
597
- **The pointer-formatting step this project does not have.** Where a pointer is
598
- initialised by immediate loads of a target's low and high bytes — `LDA #<target
599
- / STA ptr / LDA #>target / STA ptr+1` — upstream calls `set_immediate_format`
600
- twice to turn both immediates into a single readable symbol reference. **That
601
- call is not exposed on this project's surface.** The rule it would serve — "every branch,
602
- `JSR`/`JMP` and data reference goes through a symbol, so code can move" — still
603
- supplies the criterion, and the per-call disposition is
604
- recorded in the manifest named in the attribution header above. Until then,
605
- reconstruct the target by hand and put it in a side comment on both
606
- instructions — `; low byte of ptr_sprite_table ($C240)` — so the pointer is
607
- still readable even though the store cannot format it.
608
-
609
- #### 6. Report
610
-
611
- - **Address** and its current label.
612
- - **Classification**: flag, counter, pointer, hardware register, state
613
- variable, dead.
614
- - **Evidence**: the specific cross-references or usage patterns that decided
615
- it. A classification with no evidence line is a guess wearing a name.
616
- - **Actions taken**: what you renamed, what you commented, which enums were
617
- defined or applied.
618
- - **Uncertainty**: if `anno_get_cross_references` returned nothing, say which
619
- of the three explanations in step 2 you could and could not rule out.
620
-
621
- ## Troubleshooting
622
-
623
- | Symptom | Correction |
624
- |---|---|
625
- | Comments land on regions too wide to be useful | `--max-span 2`. The default is 4096 bytes. |
626
- | A `JSR` or branch target got no comment at all | `annotate` caps flow instructions at 2 bytes regardless of `--max-span`. `lookup` the target directly for the ROM routine name. |
627
- | The output is mostly header | `--no-header`. |
628
- | `lookup` printed only wide region lines and no specific name | Nothing in the four tables names that address. This answer is normal for the game's own code. Take the region and name the address from what the code does with it. |
629
- | `lookup` printed `(not in memory map)` | Nothing covers it. Check the address parsed as intended, since a bare `1234` reads as decimal. |
630
- | `no memmap.json; run: node driver.mjs memmap` | Restore the committed table with `git checkout` rather than rebuilding. The rebuild needs network and overwrites tracked data. |
631
- | `memmap` rewrote `memmap.json` and the diff is large or negative | A source was unreachable or changed. `git checkout` the file. |