@henols/c64-re-tools 1.0.0 → 2.0.0-rc.2

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 +190 -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 +121 -0
  39. package/src/host/server.ts +409 -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 +241 -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 +109 -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 +252 -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,180 +0,0 @@
1
- # Control flow: entry point → vectors → IRQ source → main loop → structure
2
-
3
- Graded 2026-08-01, **MEDIUM**, doc-derived, except where a line says otherwise. This project
4
- checked the vector-table step against its own captures on 2026-08-04 — see the bottom of this file.
5
-
6
- ## 1. Entry point — three routes, and post-depack is a different question
7
-
8
- | Situation | Where the entry point is |
9
- |---|---|
10
- | BASIC stub at `$0801` | A tokenized `SYS <addr>` line. The address is plain PETSCII digits in the stub, so it reads straight out of the load image with no interpretation. |
11
- | Autostart / non-BASIC | The `.prg` load address (first two bytes), the RESET vector at `$FFFC/$FFFD`, or a cartridge CBM80 signature at `$8000`. |
12
- | **Post-depack — this project's case** | Wherever the PC sits when the decrunch checkpoint fires. A different question with a different answer. |
13
-
14
- For a depacked image there is no BASIC stub to find. Do not spend time looking for one.
15
- `c64-ram-capture` § Find an entry point gives the live procedure: step in batches until PC and
16
- SP settle into a repeating range across three consecutive batches.
17
-
18
- ## 2. Vectors — sweep every block, and `$01` decides which are live
19
-
20
- Six pairs is not "all vectors". `node derive.mjs vectors <image.bin>` sweeps all six blocks below.
21
- It prints the IRQ/BRK/NMI and hardware blocks by default and takes `--all` for the rest.
22
-
23
- | Block | Range | Why it matters |
24
- |---|---|---|
25
- | BASIC indirects | `$0300-$030B` | IERROR, IMAIN, ICRNCH, IQPLOP, IGONE, IEVAL — a program returning to a modified BASIC prompt hooks these |
26
- | KERNAL IRQ/BRK/NMI | `$0314-$0319` | CINV `$EA31`, CBINV `$FE66`, NMINV `$FE47`. The per-frame handler, and the pair music players retarget |
27
- | KERNAL I/O indirects | `$031A-$0333` | OPEN…SAVE. **`$0328` STOP and `$0330`/`$0332` LOAD/SAVE are where a cracker hooks** |
28
- | Autostart / cartridge | `$8000` cold, `$8002` warm, `$8004` `CBM80` | The standard survive-a-reset trick. Without the signature the KERNAL ignores both words |
29
- | BASIC ROM entry | `$A000/$A002` | Only meaningful with BASIC banked in — check `$01` LORAM first |
30
- | Hardware vectors | `$FFFA-$FFFF` | NMI, RESET, IRQ/BRK. Live when the KERNAL is banked out |
31
-
32
- **The hardware pairs are only live when the ROMs are banked out via `$01`.** The deciding bit is
33
- HIRAM, `$01` bit 1 (observed 2026-08-02). HIRAM = 1 ⇒ KERNAL ROM is in and `$0314/$0315` is
34
- live. HIRAM = 0 ⇒ RAM at `$E000-$FFFF` and `$FFFE/$FFFF` is live.
35
-
36
- Why LOAD and STOP earn their own callout on this project: both releases use custom raw-sector
37
- loaders that bypass the KERNAL, so a diverted `$0330` is a provenance signal, and a diverted
38
- `$0328` is anti-tamper. Neither was being looked for before 2026-08-04.
39
-
40
- The second tell is the handler's own first instruction: the KERNAL's register-save preamble means
41
- the KERNAL path is in use. A jump straight into game code means it is not.
42
-
43
- ### A non-default value in a dormant block is not a hook
44
-
45
- **A garbage-looking `$0314` is not a bug when HIRAM = 0.** With the KERNAL banked out, nothing
46
- maintains the RAM vectors and they hold whatever was last there — usually the KERNAL's own
47
- boot-time values, partly overwritten. `derive.mjs` labels each such block `DORMANT` and reports
48
- its non-default bytes as *residue*, never as a divert, because reading a hook into residue is the
49
- fastest way to a confidently-wrong structural claim.
50
-
51
- A residue byte that **differs between two releases** is still worth something — but it is a
52
- provenance question, not a structural one. Take it to `c64-provenance-diff`.
53
-
54
- ### A target in a ROM window is unresolved until you read it twice
55
-
56
- A vector target in `$A000-$BFFF`, `$D000-$DFFF` or `$E000-$FFFF` is *either* ROM *or* the RAM
57
- underneath, decided by `$01` at the moment the vector is taken. `derive.mjs` marks these
58
- `bank-ambiguous` and stops there, because a static image cannot settle it.
59
-
60
- Live, it is one extra call: read the target with `mcp__plugin_c64-re-tools_vice__vice_memory_read`'s default bank,
61
- then again with `bank: "ram"`, and compare (`vice_memory_banks` lists what is available). Agreeing
62
- with stock ROM bytes ⇒ the vector genuinely lands in ROM and the KERNAL path is in use. Differing
63
- ⇒ **the program has its own code hidden under ROM**, which is a large structural finding and is
64
- otherwise not looked for at all. Record the stock bytes you compared against, so the check is
65
- reproducible.
66
-
67
- This supersedes the weaker "check `$01` and read the handler's preamble" tell, which silently
68
- reads whichever bank happens to be mapped.
69
-
70
- ### What the widened sweep found here — 2026-08-04
71
-
72
- Run over all six committed captures of one title (two releases, runs 1-3 each):
73
-
74
- | Vector | release-a | release-b | Reading |
75
- |---|---|---|---|
76
- | `$FFFE/$FFFF` IRQ | `$1103` | `$1103` | Known. Matches the live-established handler chain |
77
- | `$FFFA/$FFFB` NMI | `$1116` | `$1116` | **New.** The game installs its own NMI handler under KERNAL ROM |
78
- | `$FFFC/$FFFD` RESET | `$1116` | `$1116` | **New.** RESET funnels to the *same* address as NMI |
79
- | `$0328/$0329` ISTOP | `$F6FC` | `$F6ED` (stock) | **New, and a divergence.** Residue — the block is dormant |
80
- | `$8004` `CBM80` | absent | absent | Nothing catches a reset via the cartridge route |
81
-
82
- NMI and RESET sharing one entry is the shape of an anti-tamper trap: RESTORE and reset are the two
83
- ways a user perturbs a running game, and both land in the same place. `$1116` is therefore the
84
- address to checkpoint when the emulator is next available. Testing the RESTORE half of that
85
- experiment is not currently possible: **`vice_keyboard_restore` is permanently unavailable.** The
86
- RESTORE key pulses the NMI line directly and is not part of the keyboard matrix, so `KEYBOARD_FEED`
87
- (which only injects PETSCII text into the buffer) cannot produce it. Calling the tool returns an
88
- error naming the reason, rather than pulsing RESTORE. No client-side substitute exists — see
89
- `docs/stock-hard-losses.md`. The reset half of the experiment remains testable: arm the checkpoint,
90
- call `vice_machine_reset` soft and hard, and record where the PC actually lands.
91
-
92
- **Evidence:** derived mechanically from six three-run-verified captures. Every value stays
93
- identical across all three runs of its release, so none of it is drift.
94
- **Confidence:** HIGH for the values and for the cross-release divergence. The *interpretation* of
95
- `$1116` as anti-tamper is **LOW** — unexercised, and the perturbation experiment above is exactly
96
- what would settle it.
97
-
98
- ## 3. IRQ source — two enable masks close the question
99
-
100
- - **Raster IRQ** — `$D012` (raster compare), `$D011` bit 7 (raster bit 8), `$D01A` (IRQ enable
101
- mask), `$D019` (latch, which the handler must acknowledge). **A handler that writes a new
102
- `$D012` on its way out is a split raster chain**, and each such write is one more IRQ position
103
- to enumerate.
104
- - **CIA timer IRQ** — `$DC0D` (CIA#1, drives IRQ) and `$DD0D` (CIA#2, drives NMI), with
105
- `$DC04-$DC07` / `$DD04-$DD07` for the periods.
106
-
107
- A game that never touches `$DC0D` is on a raster IRQ. One that programs `$DC04-$DC07` and enables
108
- timer A has its own timebase. Read the two enable registers before reading any handler code.
109
-
110
- ## 4. Main loop — decide the shape before hunting
111
-
112
- - **Real main loop** — an unconditional backward branch or `JMP` to a nearby earlier address that
113
- never returns, usually preceded by a frame-sync wait: polling `$D012` for a fixed raster line, or
114
- spinning on a flag the IRQ handler sets.
115
- - **IRQ-does-everything** — the main loop is a two-instruction spin and all logic hangs off the
116
- raster IRQ. Common enough that assuming the first shape wastes the search.
117
-
118
- **The honest test is live, not static.** The address that repeats exactly once per frame in an
119
- execution trace is the main loop, whatever the listing suggests.
120
-
121
- ## 5. Four structural features a linear disassembler gets wrong
122
-
123
- - **Jump tables and dispatch** — `JMP ($xxxx)` and `JSR` into an indexed table. This is where game
124
- state machines live, and exactly what a linear decoder mis-decodes.
125
- - **Self-modifying code** — writes into `$0800-$CFFF` from code that also executes there. Common
126
- for animation frame pointers.
127
- - **Zero page** — the game's hot variables. The highest-frequency ZP addresses in a trace are the
128
- state worth naming first.
129
- - **Code/data separation** — the project's standing rule (`.claude/CLAUDE.md` § Stack Patterns): a
130
- range never hit as an instruction stream across full gameplay coverage is data, regardless of
131
- what the tracer guessed. The provenance diff between the two cracked releases is the second check
132
- on the same question.
133
-
134
- ## Finding the state machine
135
-
136
- Most games have one even when it is not explicit. Two shapes:
137
-
138
- ```asm
139
- LDA GameState ; indexed dispatch — the common shape, and the one
140
- ASL ; a linear disassembler turns into nonsense
141
- TAX
142
- LDA StateTable,X
143
- STA JumpVector+1
144
- LDA StateTable+1,X
145
- STA JumpVector+2
146
- JumpVector:
147
- JMP $FFFF ; operand is patched at runtime
148
- ```
149
-
150
- ```asm
151
- LDA GameState ; compare chain — easier to read, easier to find
152
- CMP #STATE_TITLE
153
- BEQ TitleState
154
- ```
155
-
156
- Finding the state variable gives a high-level map of the whole program. A practical route: pause
157
- at a title screen and again in gameplay, diff the two captures, and look for a single byte that
158
- differs in zero page or low RAM. `vice_memory_compare` narrows this. `c64-ram-capture` § Compare
159
- two captures gives the volatility rules that stop you chasing drift.
160
-
161
- `vice_memory_compare` serves only `mode: 'ranges'`, which captures the two states at different
162
- points in time and compares two live ranges. It refuses `mode: 'snapshot'` with an explanatory
163
- message. There is no memory-only snapshot producer at all.
164
-
165
- ## Verified against this project — 2026-08-04
166
-
167
- Running `derive.mjs vectors` cold on both releases' `*-gameentry-run1.bin` returns `$01` = `$40`
168
- (LORAM 0, HIRAM 0, CHAREN 0 — KERNAL and BASIC banked out), so the live pair is `$FFFE/$FFFF`,
169
- holding **`$1103`**, while `$0314/$0315` holds `$0101` — nothing meaningful, exactly as the
170
- dormant-block rule predicts.
171
-
172
- `$1103` is the same IRQ-handler entry that phase-01 live work independently established, with the
173
- raster-split chain `$1103 → $1574 → $152C` (observed 2026-08-02, on a checkpoint trap). The
174
- method reproduces a known-good result from a static image with no emulator running. The widened
175
- sweep's own results, including two facts this table never surfaced, are in §2 above.
176
-
177
- **Evidence:** derived mechanically from three-run-verified captures, cross-checked against a live
178
- finding recorded independently.
179
- **Confidence:** HIGH for the vector-table step, the HIRAM rule, and the dormant-block rule. The
180
- remaining steps in this file stay MEDIUM until exercised the same way.
@@ -1,79 +0,0 @@
1
- # VIC-II: locating every displayed byte
2
-
3
- Graded 2026-08-01, **MEDIUM**, doc-derived.
4
-
5
- **Graphics data is computed, not searched.** Every pointer the VIC follows derives from two
6
- registers plus a bank. Read the bank, read `$D018`, read the mode bits, read `$D015` and the
7
- sprite pointer block, and you have located every byte of graphics on screen. Searching memory for
8
- something the hardware will tell you the address of is the single largest time sink in graphics RE.
9
-
10
- `node derive.mjs vic` and `node derive.mjs sprites` do the arithmetic. For what any individual bit
11
- *means*, use `c64-memory-mapping` — it carries the full `$D018` and `$DD00` value tables, and this
12
- file deliberately does not restate them.
13
-
14
- ## The order, and why it is this order
15
-
16
- 1. **`$DD00` bits 0-1 — the VIC bank. Read this first, every time.** The bits are **inverted**
17
- (`%11` → bank 0 at `$0000`, `%00` → bank 3 at `$C000`). Every other pointer is relative to this
18
- base, so getting it wrong corrupts the whole chain **silently, with no error to signal it**.
19
- This is the most common source of a wrong answer in C64 graphics RE.
20
- 2. **`$D018` — two pointers in one byte.** Bits 4-7 = VM (screen RAM = bank + VM × `$0400`).
21
- Bits 1-3 = CB (charset = bank + CB × `$0800`). In bitmap mode only bit 3 matters: which 8K half
22
- of the bank the bitmap occupies, and the video matrix then holds colour pairs rather than
23
- character codes.
24
- 3. **The mode bits** — `$D011` bit 6 (ECM), `$D011` bit 5 (BMM), `$D016` bit 4 (MCM). These decide
25
- what the bytes *mean*. Decoding a multicolor sprite as hires produces garbage twice as wide as
26
- it should be. ECM combined with BMM or MCM is invalid and blanks the screen — if you compute
27
- that, you probably caught the registers mid-update inside a raster split, so re-read.
28
- 4. **`$D015` — the sprite enable mask. Start here, not at the sprite data.** A disabled sprite's
29
- other registers are stale and decoding them yields noise.
30
- 5. **Sprite pointers at video matrix + `$03F8`**, 8 bytes. Each pointer × 64 = the sprite's data
31
- address *within the current bank*. 63 bytes used of the 64 allocated.
32
-
33
- ## The hazard that costs an hour
34
-
35
- **The character ROM shadow.** The VIC sees character ROM at `$1000-$1FFF` (bank 0) and
36
- `$9000-$9FFF` (bank 2) **regardless of the `$01` banking the CPU sees**. If CB resolves into
37
- either window, the game is using ROM characters and there is no charset in RAM to extract.
38
- `derive.mjs vic` flags this explicitly. Check it before dumping anything.
39
-
40
- ## Colour is not banked
41
-
42
- Colour RAM is fixed at `$D800-$DBFF` and does **not** move with the VIC bank. Only the low nybble
43
- of each byte exists. `$D020` is the border, `$D021-$D024` backgrounds 0-3 (2 and 3 used only in
44
- ECM). Looking for colour data at a bank-relative address finds something else entirely.
45
-
46
- ## Two watch targets that find the two highest-value routines
47
-
48
- `vice_watch_add` finds **writers**, and that is its real leverage in RE — it is under-used relative
49
- to reading memory.
50
-
51
- | Watch | Finds |
52
- |---|---|
53
- | `$D018` | The screen-setup routine. In a room- or level-based game this is usually the room loader — one of the highest-value routines to locate early. |
54
- | video matrix + `$03F8` | The animation driver. Rewriting sprite pointers frame to frame is exactly what it does. |
55
-
56
- ## Reading collision registers changes the game
57
-
58
- `$D01E` (sprite-sprite) and `$D01F` (sprite-background) **clear when read**. Reading them while the
59
- game runs steals the collision the game was about to act on — the worst class of observation bug,
60
- because it discredits the capture without announcing itself. Prefer `vice_vicii_get_state`.
61
-
62
- Whether the VICE monitor's own read is side-effect-free is **unverified** — treat it as
63
- check-don't-assume, not as a settled fact.
64
-
65
- Many games do software collision anyway: look for coordinate subtraction, comparisons against
66
- width and height, tile lookups, mask tables and bounding-box arithmetic before concluding the
67
- hardware registers are what the game uses.
68
-
69
- ## Sprite decoding
70
-
71
- `vice_sprite_get` / `vice_sprite_inspect` do the pointer arithmetic and the multicolor bit-pair
72
- unpacking. Check what they return once against a hand-resolved pointer — `derive.mjs sprites`
73
- gives you that hand resolution — then trust them.
74
-
75
- On stock, `vice_vicii_get_state`'s `$D018` pointers come back **bank-relative**.
76
- `vice_sprite_get` resolves the absolute `screenBase` and per-sprite `dataAddress` for you.
77
- `vice_sprite_inspect`'s ASCII grid is the sprite's native 24x21 (hi-res) or 12x21 (multicolour)
78
- **data block** — it is **not** scaled by the `$D017`/`$D01D` expansion bits, so a sprite shown
79
- double-size on screen still renders at its native resolution in the grid.
@@ -1,143 +0,0 @@
1
- # Hazards: how observing a running C64 gives you the wrong answer
2
-
3
- The entries here are graded higher than the method files, because most were established live on
4
- this project at real cost. A wrong answer from a register table is cheap. A wrong answer from a
5
- machine that changed *because you looked at it* discredits a whole session without announcing
6
- itself.
7
-
8
- Log a new hazard in your own project notes at the moment you hit one — the cost of rediscovering
9
- one is a whole discredited session.
10
-
11
- ## 1. Agent think-time runs the emulator at full speed
12
-
13
- **Evidence: live, 01-04 attempt 5. Confidence: HIGH (measured, then reproduced with a control).**
14
-
15
- Between one screenshot and the next — a handful of memory/register reads, **zero input sent** —
16
- a game ran through an interstitial to `GAME OVER`. `vice_cycles_stopwatch` read **258,504,308**
17
- cycles since the previous action: roughly **262 seconds of emulated PAL time**, all of it real
18
- unattended execution while the agent composed tool calls.
19
-
20
- This invalidated a previously recorded gameplay-hazard conclusion. A "counter that depletes per
21
- input event" was really a counter depleting during the agent's own reasoning latency, and the
22
- earlier finding had no way to rule that out because it never paused either.
23
-
24
- **The discipline:** call `vice_execution_pause` immediately after every observation that is not
25
- immediately followed by a deliberate scripted input. Resume only for the bounded duration of that
26
- input. **Never leave the machine running across a reasoning step.**
27
-
28
- Two confirmations from the same session: a pause takes effect only once actually processed (the
29
- counter climbed another ~20M cycles before settling), but once landed it holds solidly — two
30
- consecutive stopwatch reads returned an identical value. And with the discipline held from the
31
- first frame, a room previously judged impassable across six attempts was crossed on the next try.
32
-
33
- ## 2. An armed stopping checkpoint can look exactly like a dead emulator
34
-
35
- **Evidence: cross-read of three recorded incidents, 3/3 correlation. Confidence: MEDIUM — the
36
- mechanism is consistent with every symptom and testable in minutes, but has not been reproduced.**
37
-
38
- The common factor across three "silent stall" incidents was not an address. It was: **a stopping
39
- exec checkpoint was armed, and execution was resumed.**
40
-
41
- The signature is complete and self-consistent — the cycle bracket reads exactly `0`, `vice_ping`
42
- answers `execution: "running"` because VICE's flag flips before the trap fires, and
43
- `vice_registers_get` returns a byte-identical PC every time **because the machine genuinely never
44
- moved.** The strongest single tell: in one incident `vice_checkpoint_list` reported the checkpoint
45
- at `hit_count: 0` after multiple resume/poll cycles, on an IRQ-driven screen where its address
46
- *must* execute every frame.
47
-
48
- **`mcp__plugin_c64-re-tools_vice__vice_diagnose` now performs this check for you** (2026-08-04): it enumerates armed
49
- checkpoints, reads the PC, resolves the live IRQ handler through `$01`, and returns a
50
- `checkpoint_trap` verdict — with **no resume and no stopwatch call**, which matters because
51
- `vice_execution_run` is this project's leading crash suspect. Call it before anything else. The
52
- triage decision tree around it is `vice-wedge-triage`.
53
-
54
- **Know the two shapes it matches, because a trap outside them reads as a wedge.** It fires when an
55
- enabled stopping exec checkpoint sits *exactly* at the current PC, or at the resolved handler
56
- entry with `hit_count` exactly `0`. This hazard's own evidence describes the PC as pinned "at or
57
- just past" the checkpoint — *just past* is not *exactly at*. A checkpoint armed mid-handler rather
58
- than at its entry, with a non-zero hit count, matches neither shape, falls through to the cycle
59
- bracket, and comes back `wedged`. **The response to `wedged` is recycle, and recycling a healthy
60
- instance is the exact loss both the tool and this hazard exist to prevent.** So when a `wedged`
61
- verdict arrives with any checkpoint still armed, do the two reads by hand before recycling:
62
- `vice_checkpoint_list`, then resolve the live handler (`$0314/$0315`, or `$FFFE/$FFFF` when `$01`
63
- has the ROMs banked out). Observed 2026-08-04, and still open.
64
-
65
- **Counter-evidence, and why this is MEDIUM:** in one incident, deleting the checkpoint did *not*
66
- unfreeze the machine, and neither did a soft reset, a hard reset, nor a single step. A checkpoint
67
- trap may be the *onset* without being the whole story. Do not assume delete-and-resume recovers it.
68
- `vice_diagnose`'s own `checkpoint_trap` report states this too, and cites the same incident.
69
-
70
- **This is not a reason to stop arming checkpoints on IRQ handlers** — that is core technique. It
71
- is a reason to enumerate what you armed *first* whenever the machine looks frozen, before
72
- concluding the emulator died.
73
-
74
- ## 3. Registers that clear when you read them
75
-
76
- `$D01E`, `$D01F` (VIC collisions) and `$DC0D`, `$DD0D` (CIA interrupt flags) clear on read.
77
- Reading one while the game runs steals the event the game was about to service.
78
-
79
- Prefer the whole-chip reads — `vice_vicii_get_state`, `vice_cia_get_state`, `vice_sid_get_state`
80
- — over raw register reads. Whether the VICE monitor's own read path is side-effect-free is
81
- **unverified**: treat it as check-don't-assume rather than taking it on faith.
82
-
83
- `vice_vicii_get_state`/`vice_cia_get_state` reads are `sidefx: false` with
84
- no argument able to override it — **VERIFIED**, asserted on the wire body by a regression test.
85
- Whether the emulator's own `MEM_GET` read path actually honours that flag for
86
- `$D01E`/`$D01F`/`$DC0D`/`$DD0D` — i.e. whether it truly cannot clear them — is **ASSUMED**, with
87
- no probe recorded in this repo. Treat it as unverified, not as a proven guarantee.
88
- `vice_sid_get_state` read-back is **permanently unavailable**: SID `$D400-$D418` is write-only
89
- in hardware and the binary monitor has no SID command, so there is no route to recover it.
90
- Writes to those addresses still work fine. See `docs/stock-hard-losses.md`. Also: an internal
91
- field the register map cannot expose is marked `{ available: false, reason }` in the answer, never
92
- a bare `0` — do not record a `0` from one of these fields as a measurement. Check `available`
93
- first. A chip-state or sprite answer also **names the memory view it read** (`bank`, or
94
- `registerBank`/`dataBank`), read through the emulator's own `io`/`ram` banks, so it stays valid
95
- even while the program has I/O banked out ($01 driving the RAM/ROM/I-O switch). An answer with
96
- **no** bank field — an older transcript — is suspect whenever `$01` may not
97
- have been `$37`: those bytes may be the RAM underneath the I/O area, not registers.
98
-
99
- ## 4. The keyboard buffer is not how games read keys
100
-
101
- **Evidence: live. Confidence: HIGH. Cost: an afternoon.**
102
-
103
- Games and cracks poll the `$DC00`/`$DC01` matrix directly, bypassing the KERNAL buffer, so
104
- `vice_keyboard_type` is invisible to them. Use `vice_keyboard_matrix`, and hold a key across a
105
- gate by releasing it at the trigger checkpoint, never earlier.
106
-
107
- **`vice_keyboard_matrix` is permanently unavailable.** The binary monitor's `KEYBOARD_FEED` (0x72)
108
- only injects PETSCII text into the KERNAL keyboard buffer. The emulator recomputes CIA port B from
109
- its own keyboard array on every read, so there is no wire command that can drive the raw matrix —
110
- this is unrecoverable, not merely unbuilt. See `docs/stock-hard-losses.md`. Use
111
- `vice_keyboard_type` / `vice_keyboard_petscii` when the gate reads the KERNAL buffer, or
112
- `vice_joystick_set` when it polls the matrix directly instead. Either way, buffer injection is
113
- invisible to a program polling `$DC00`/`$DC01` itself, so a matrix-polling gate must be driven by
114
- the joystick or not at all.
115
-
116
- ## 5. Most state reads pause the emulator and do not resume it
117
-
118
- Read all state first, poll with `vice_ping` (the non-pausing poll), and resume **exactly once** at
119
- the end. `vice_ping`'s `execution` field is **not** a liveness signal — see hazard 2 for why it
120
- reports `running` on a machine that is not moving. The only trustworthy liveness test is a cycle
121
- bracket.
122
-
123
- ## 6. The machine can be replaced under you
124
-
125
- A crash-and-respawn mid-session means the run is void — `c64-ram-capture` § Void a run gives the
126
- procedure. A successful retry after an auto-restart may be talking to a blank machine.
127
-
128
- **You do not poll for this, and no exposed tool reads the epoch.** The proxy compares it before and
129
- *after* every forwarded call and raises a loud error naming both values, then re-baselines so the
130
- session stays usable. The two recorded incidents surfaced exactly that way and self-healed on the
131
- next call. Where an epoch value is needed for a record, it comes from that error text or from
132
- `vice_diagnose`'s `restarted` report — there is no `vice_epoch_get`.
133
-
134
- What is still yours to do: **treat every post-drift read as a fresh machine, never as a resume
135
- point.** Reboot from `vice_disk_attach`. Two crashes in ~20 minutes of continuous live work is a
136
- real measured rate, so plan a live session to tolerate re-deriving a boot sequence more than once.
137
-
138
- ## 7. Full-64K byte-identity is impossible in principle
139
-
140
- Never-written RAM drifts continuously, so two captures of the same checkpoint will not match
141
- across all 64K. The recovery procedure is deterministic **for the program image**, not for 64K.
142
- `c64-ram-capture` § Compare two captures carries the volatility regions and the drift
143
- discriminator. Use them rather than treating any difference as a divergence.
@@ -1,133 +0,0 @@
1
- # Turning findings into ACME source
2
-
3
- Do not wait until everything is understood. Stand up a buildable tree early, include the
4
- unidentified bulk as binary, and replace regions with real source as they are confirmed. The tree
5
- stays assemblable at every commit, so a regression has a small blast radius.
6
-
7
- `acme-build` covers assembling. This file covers the shape of the source and what makes a
8
- reconstruction right.
9
-
10
- ## Start with binary inclusion
11
-
12
- ```asm
13
- * = $4000
14
- !binary "unknown_4000_5fff.bin"
15
- ```
16
-
17
- Then replace piece by piece, keeping the addresses fixed:
18
-
19
- ```asm
20
- * = $4200
21
-
22
- UpdatePlayer:
23
- ; reconstructed code
24
- ```
25
-
26
- A layout that keeps this workable:
27
-
28
- ```
29
- src/
30
- main.a zeropage.a irq.a
31
- input.a player.a enemies.a
32
- collision.a graphics.a music.a
33
- data/
34
- sprites.a levels.a text.a
35
- ```
36
-
37
- ## Behavioural equivalence is this project's definition of correctness
38
-
39
- **Correctness here means scripted replay plus comparison at checkpoints — nothing else**
40
- (`.claude/CLAUDE.md` § Constraints). A reconstruction is right when driving it through the
41
- checkpoint set produces the same observable behaviour as the original. **Anything not observable
42
- at a checkpoint is not verified** — the constraint's own conclusion, and the reason checkpoint
43
- design is part of the reconstruction work rather than something to bolt on afterwards.
44
-
45
- **That bar is what buys you the freedom to rename routines, reorganise files, replace constants
46
- with symbols and add macros**. **But** reorganising shifts addresses, which breaks self-modifying code and
47
- timing-sensitive raster routines. Replay through the checkpoint set after EACH reorganisation, not
48
- at the end of several: one shifted address per failing replay is a short diagnosis, ten is a
49
- bisect.
50
-
51
- A full 64K RAM capture is not a comparison surface: never-written RAM drifts continuously, so
52
- full-64K identity is impossible in principle (`observation-hazards.md` § 7).
53
-
54
- ## Self-modifying code needs explicit labels
55
-
56
- A static disassembler sees `LDA $FFFF,X` and cannot know the operand is written at runtime:
57
-
58
- ```asm
59
- LDA SourceAddress
60
- STA CopyLoop+1
61
- LDA SourceAddress+1
62
- STA CopyLoop+2
63
- CopyLoop:
64
- LDA $FFFF,X
65
- STA $0400,X
66
- ```
67
-
68
- Name the patched location so the intent survives:
69
-
70
- ```asm
71
- CopySource = CopyLoop + 1
72
- ```
73
-
74
- Look for writes to the byte after an opcode, the two bytes after a `JMP`/`JSR`, branch operands
75
- and immediate constants — `STA Routine+1`, `STX Routine+2`, `INC Routine+1`.
76
-
77
- ## Label vocabulary: name from evidence, promote on confirmation
78
-
79
- Provisional names that carry their own uncertainty beat confident names that turn out wrong:
80
-
81
- | Prefix | Means |
82
- |---|---|
83
- | `ZP_` | unknown zero-page variable |
84
- | `State_` | state variable |
85
- | `Flag_` | boolean or bit field |
86
- | `Counter_` | counter |
87
- | `Ptr_` | pointer |
88
- | `Table_` | lookup table |
89
- | `IRQ_` | interrupt routine |
90
- | `Maybe_` | plausible but unconfirmed |
91
- | `Unknown_` | no reliable interpretation yet |
92
-
93
- `Routine_43A2` → `Maybe_UpdatePlayer` → `UpdatePlayerPosition`, promoted only when behaviour is
94
- confirmed. Avoid `AmazingCollisionRoutine`-style names entirely. They encode a guess as a fact.
95
-
96
- Record the confidence next to the thing, in the same grammar the project uses everywhere else:
97
-
98
- ```asm
99
- ; Confirmed: decremented once per frame while the player is invulnerable.
100
- ; Evidence: live, watch on $D015 during damage. Confidence: HIGH.
101
- PlayerInvulnerabilityTimer:
102
- !byte 0
103
- ```
104
-
105
- ## Data tables a linear disassembler destroys
106
-
107
- The split low/high address table is the classic:
108
-
109
- ```asm
110
- AddressLo: !byte <Room0, <Room1, <Room2
111
- AddressHi: !byte >Room0, >Room1, >Room2
112
- ```
113
-
114
- Without recognising the pair, a disassembler shows meaningless instructions. Other data tells:
115
- referenced through indexed loads, repeated byte patterns, valid PETSCII or screen codes,
116
- sprite-sized 64-byte blocks, values matching VIC-II coordinates.
117
-
118
- Code tells: reachable through `JSR`/`JMP`/branches/vectors, **executes during tracing**, plausible
119
- control flow. The project's standing rule settles ties — a range never hit as an instruction
120
- stream across full gameplay coverage is data, whatever the tracer guessed.
121
-
122
- ## Labels round-trip through VICE
123
-
124
- ACME's `--vicelabels` output is the `al C:xxxx .Name` format `vice_symbols_load` /
125
- `vice_symbols_lookup` consume, so labels flow source → build → debugger without translation.
126
- `acme-build` emits the `.vs` file on every build. Load it after each one and your checkpoints carry
127
- real names.
128
-
129
- **The other direction — exporting the annotation store into that same format — is withdrawn as of
130
- 2026-08-29, and no phase currently owns its return** (an earlier forecast naming a numbered phase
131
- for it is superseded: that phase covered the ACME export oracle only). So a name discovered live
132
- goes into the store with `anno_set_label_name` first (the store is the merge point) and reaches the
133
- emulator only through a `.lbl` you produce yourself.
@@ -1,74 +0,0 @@
1
- # SID and CIA: music, effects, the RNG, input, timing
2
-
3
- Graded 2026-08-01, **MEDIUM**, doc-derived, except where marked. Per-register bit detail lives in
4
- `c64-memory-mapping`. This file carries the idioms and the order.
5
-
6
- ## SID — separating the player from the game logic
7
-
8
- Voice 1 at `$D400`, voice 2 at `$D407`, voice 3 at `$D40E`, seven bytes each: frequency lo/hi,
9
- pulse width lo/hi, control (gate bit 0, then sync/ring/test and the waveform bits), attack/decay,
10
- sustain/release. Then `$D415`-`$D418` for filter and volume. Write-only but for the last four:
11
- `$D419`/`$D41A` paddles, `$D41B` voice 3 oscillator, `$D41C` voice 3 envelope.
12
-
13
- **Watch `$D404` to land directly on the play routine.** Voice 1's control register gates on every
14
- note, so a write watch there hits the player without reading the IRQ handler line by line.
15
- Separating the two entry points follows immediately:
16
-
17
- > `init` is called **once** from the main code. `play` is called **once per frame** from the IRQ.
18
-
19
- Pulling the music driver out early removes a large amount of apparent complexity from everything
20
- else — it is often the single biggest block of code that has nothing to do with gameplay.
21
-
22
- ### Two idioms worth recognising on sight
23
-
24
- - **`$D41B` read is the random number generator, not audio.** Reading voice 3's oscillator is *the*
25
- C64 RNG idiom. Code reading `$D41B` is almost never doing sound — it is enemy AI, spawn
26
- placement, or a title-screen effect. Filing it as sound code sends the AI hunt in the wrong
27
- direction. Corollary: `$D418` bit 7 (voice 3 disconnect) is often set **precisely because** voice
28
- 3 is the RNG rather than a voice.
29
- - **`$D418` hammered alone at high frequency is 4-bit sample playback**, not music. It is a
30
- separate subsystem from the player and usually runs off a **fast CIA timer** rather than the
31
- frame IRQ — so finding it also explains a CIA timer you could not otherwise account for.
32
-
33
- ## CIA — two chips, identical layouts, different jobs
34
-
35
- Confusing them is a frequent early error.
36
-
37
- | | CIA#1 `$DC00` — keyboard, joysticks, **IRQ** | CIA#2 `$DD00` — VIC bank, serial, user port, **NMI** |
38
- |---|---|---|
39
- | Port A | `$DC00` keyboard **column** select. Joystick port 2 | `$DD00` VIC bank (bits 0-1, inverted). Serial ATN/CLK/DATA |
40
- | Port B | `$DC01` keyboard **row** read. Joystick port 1 | `$DD01` user port / RS-232 |
41
- | Timers | `$DC04-$DC07`, control at `$DC0E`/`$DC0F` | `$DD04-$DD07`, `$DD0E`/`$DD0F` — these drive **NMI** |
42
- | Interrupt | `$DC0D` | `$DD0D`, same bit layout |
43
-
44
- **The timebase question closes in one read.** A game that never touches `$DC0D` is on a raster IRQ.
45
- One that programs `$DC04-$DC07` and enables timer A runs its own timebase.
46
-
47
- ## Three CIA hazards
48
-
49
- - **`$DD00` is dual-purpose.** The same register carries the VIC bank *and* the serial bus lines,
50
- so a write during disk access also moves the VIC's view of memory unless the code masks
51
- carefully. **Check the mask** before concluding a `$DD00` write is a bank switch — loader code
52
- writing `$DD00` is usually talking to the drive.
53
- - **`$DC0D`/`$DD0D` clear the interrupt flags on read**, the same shape as `$D01E`/`$D01F`. Reading
54
- one steals an interrupt the game was about to service. Prefer `vice_cia_get_state`. The VICE
55
- monitor's exact behaviour here is **unverified** — check, don't assume. On stock,
56
- `vice_cia_get_state` reports the **read** side of `$xx0D` as `interruptStatus` and marks the
57
- write-side enable mask `unavailable` — the two share one address with different meanings, so a
58
- reader looking for "which interrupts are enabled" is not silently handed the flags that have
59
- fired.
60
- - **Direct `$DC00`/`$DC01` polling is the norm, and it defeats `vice_keyboard_type`.**
61
- **Evidence: live, established on this project during recovery work. Confidence: HIGH. Cost: an
62
- afternoon.** Games and cracks bypass the KERNAL keyboard buffer and read the matrix directly.
63
- Assume it until shown otherwise. **`vice_keyboard_matrix` is permanently unavailable** — the
64
- binary monitor's `KEYBOARD_FEED` only injects PETSCII buffer text and cannot drive the raw
65
- matrix. See `docs/stock-hard-losses.md`. Use `vice_keyboard_type` / `vice_keyboard_petscii` when
66
- the gate reads the KERNAL buffer, or `vice_joystick_set` when it polls the matrix directly.
67
- Buffer injection stays invisible to a program polling `$DC00`/`$DC01` itself.
68
-
69
- ## Finding input handling from the observable side
70
-
71
- Watch reads of `$DC00`/`$DC01` to find the input routine, then trace forward to what it stores.
72
- The joystick bits are active-low: bit 4 is fire, bits 0-3 up/down/left/right. A routine that reads
73
- `$DC01`, masks one bit and branches is the input decoder. The variable it writes is the one to
74
- name first.