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