@henols/c64-re-tools 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/skills/acme-build/SKILL.md +39 -23
- package/skills/acme-build/scripts/acme.mjs +159 -64
- package/skills/acme-build/template.a +1 -1
- package/skills/c64-disk-access/SKILL.md +156 -0
- package/skills/c64-disk-access/scripts/c1541.mjs +569 -0
- package/skills/c64-memory-mapping/SKILL.md +30 -23
- package/skills/c64-memory-mapping/scripts/driver.mjs +1 -1
- package/skills/c64-petcat/SKILL.md +87 -0
- package/skills/c64-petcat/scripts/petcat.mjs +221 -0
- package/skills/c64-program-recon/SKILL.md +93 -39
- package/skills/c64-program-recon/references/control-flow.md +12 -15
- package/skills/c64-program-recon/references/graphics.md +1 -1
- package/skills/c64-program-recon/references/observation-hazards.md +18 -16
- package/skills/c64-program-recon/references/reconstruction.md +1 -2
- package/skills/c64-program-recon/references/sound-and-input.md +6 -8
- package/skills/c64-program-recon/references/tool-selection.md +36 -17
- package/skills/c64-program-recon/scripts/packer-finding.mjs +165 -87
- package/skills/c64-program-recon/templates/memory-map.template.md +2 -2
- package/skills/c64-provenance-diff/SKILL.md +40 -5
- package/skills/c64-provenance-diff/scripts/diff-images.mjs +8 -8
- package/skills/c64-provenance-diff/scripts/recovery-schema.mjs +7 -5
- package/skills/c64-ram-capture/SKILL.md +112 -44
- package/skills/c64-ram-capture/scripts/compare.mjs +2 -2
- package/skills/c64-ram-capture/scripts/derive-transients.mjs +575 -0
- package/skills/c64-ram-capture/scripts/dump-artifacts.mjs +3 -3
- package/skills/c64-ram-capture/scripts/mcp-module.mjs +174 -0
- package/skills/c64-ram-capture/scripts/releases.mjs +1 -1
- package/skills/c64-ram-capture/scripts/vsf-slice.mjs +147 -0
- package/skills/c64-ram-capture/scripts/watch-loads.mjs +15 -15
- package/skills/c64-ram-capture/templates/capture-record.template.md +44 -4
- package/skills/c64-ram-capture/transients/README.md +136 -0
- package/skills/routine-queue-walker/SKILL.md +114 -22
- package/skills/routine-queue-walker/scripts/completeness-report.mjs +463 -0
- package/skills/vice-wedge-triage/SKILL.md +96 -89
- package/skills/c64-ram-capture/scripts/d64-parse.mjs +0 -243
|
@@ -23,6 +23,17 @@ node $D sprites --dd00 3E --d018 18 --d015 0F --ptrs 20,21,22,23,FF,FF,FF,FF
|
|
|
23
23
|
The script does only the arithmetic that a lookup table cannot — register bits to concrete
|
|
24
24
|
addresses — over values **you** fetched through `mcp__plugin_c64-re-tools_vice__*`. It contacts nothing.
|
|
25
25
|
|
|
26
|
+
## Before disassembling anything
|
|
27
|
+
|
|
28
|
+
Two adjacent jobs belong to other skills, and pointing at their owners is cheaper than re-deriving
|
|
29
|
+
either one here:
|
|
30
|
+
|
|
31
|
+
- **A file still sitting inside a `.d64` image** — its directory, block allocation map, sector
|
|
32
|
+
chain, or raw bytes — is `c64-disk-access`'s job. Get the file out of the image there first.
|
|
33
|
+
- **A tokenized BASIC stub rather than raw machine code** — detokenizing it and finding where it
|
|
34
|
+
hands over to machine code, including the named decline when no static handover address exists —
|
|
35
|
+
is `c64-petcat`'s job. See below for the one thing it does not also do.
|
|
36
|
+
|
|
26
37
|
## The order
|
|
27
38
|
|
|
28
39
|
Each step is a read whose answer rules something out. Do not skip ahead: step 6 is cheap once the
|
|
@@ -100,13 +111,20 @@ script measures it from the file. It prints one JSON object. Read the `verdict`:
|
|
|
100
111
|
| `unknown` | No route produced an answer. `unavailableReason` always says why. | Continue, but record the unknown. Never write it up as "not packed". |
|
|
101
112
|
|
|
102
113
|
**A name is reported only when an external oracle stated one, and this project does not guess.**
|
|
103
|
-
No first-party route on this project's surface reports a packer name at all —
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
No first-party route on this project's surface reports a packer name at all — that was
|
|
115
|
+
established four independent ways, and the acceptance bar a future first-party identifier would
|
|
116
|
+
have to clear was fixed at the same time. So there is no code path here that can write a packer
|
|
117
|
+
name from entropy, from a decompression address, or from a byte pattern. If you want a name and the finding does not give
|
|
118
|
+
you one, install an external identifier on the **host** and point `UNP64` or `UNP64_PATH` at it in
|
|
119
|
+
the environment the host broker process sees — do not infer it. `packer-finding.mjs` never spawns
|
|
120
|
+
that identifier itself: it reaches it only through the host-tool execution
|
|
121
|
+
seam (`src/mcp/vice/host-tool.mts`'s `oracle.probe`/`oracle.run` allowlist entries), because this
|
|
122
|
+
script runs container-side and there is no container PATH to a host binary on. **The container-side
|
|
123
|
+
environment is not consulted at all**: setting `UNP64`/`UNP64_PATH` in
|
|
124
|
+
this script's own (container-side) environment adds only a diagnostic hint to an absent result —
|
|
125
|
+
it can never select what the host executes. The oracle's location is host-side configuration
|
|
126
|
+
only, and the configured path's file name must be the oracle binary's own name (`unp64`) or the
|
|
127
|
+
seam treats it as absent.
|
|
110
128
|
|
|
111
129
|
**The entropy gate answers packedness, not identity.** High entropy tells you the bytes are
|
|
112
130
|
compressed (or encrypted, or genuinely random); it does not tell you by what. And the way a packed
|
|
@@ -154,7 +172,7 @@ The method reproduces a known-good result from a static image with no emulator r
|
|
|
154
172
|
## Writing findings into the annotation store
|
|
155
173
|
|
|
156
174
|
Recon's findings are not memory-map prose written once and left to rot — they are entries in a
|
|
157
|
-
queryable annotation store, and the Markdown memory map is a *generated view* of that store
|
|
175
|
+
queryable annotation store, and the Markdown memory map is a *generated view* of that store,
|
|
158
176
|
not something you hand-edit yourself.
|
|
159
177
|
|
|
160
178
|
**There is no bootstrap step, and no bootstrap verb.** The store is created by the first write to
|
|
@@ -163,7 +181,7 @@ it: name a `.annostore` path on any mutating call — `anno_set_label_name`, `an
|
|
|
163
181
|
A read-only call against a path that does not exist yet is REFUSED by name rather than answering
|
|
164
182
|
against an empty store, so "I read nothing" and "there is nothing to read" stay distinguishable.
|
|
165
183
|
|
|
166
|
-
Every `anno_*` tool takes an explicit `store` path
|
|
184
|
+
Every `anno_*` tool takes an explicit `store` path — there is no ambient session state
|
|
167
185
|
naming the store, so which store a call touched is always visible in the transcript. Every call
|
|
168
186
|
that derives its answer from the program's **bytes** rather than from the annotations takes an
|
|
169
187
|
`image` path as well — `anno_get_binary_info`, `anno_read_region`, `anno_disassemble`,
|
|
@@ -189,7 +207,7 @@ bracket tokens (quoted verbatim from `anno-confidence.ts`, the parser's own sour
|
|
|
189
207
|
(confirmed data), `[probable-data]` (probable data), `[unknown]` (unknown).
|
|
190
208
|
|
|
191
209
|
A typo in the bracket token — wrong case, an underscore, a plural, stray whitespace — **fails
|
|
192
|
-
loudly**; it does not silently degrade into an ungraded comment.
|
|
210
|
+
loudly**; it does not silently degrade into an ungraded comment. Do not
|
|
193
211
|
promote a row by editing its grade in place: re-verify and restate the evidence with a fresh
|
|
194
212
|
`anno_set_comment` call, so the record of when something stopped being a guess survives.
|
|
195
213
|
|
|
@@ -233,7 +251,7 @@ error and no explanation of why.
|
|
|
233
251
|
The **loop itself is not withdrawn**, only its two automated legs, and the discipline it encodes is
|
|
234
252
|
what to keep doing by hand for as long as they stay gone:
|
|
235
253
|
|
|
236
|
-
1. **The store is the merge point
|
|
254
|
+
1. **The store is the merge point, not your own notes.** A name discovered live —
|
|
237
255
|
disassembling the running machine, a checkpoint hit — is written into the store with
|
|
238
256
|
`anno_set_label_name` *first*, before it is carried anywhere else.
|
|
239
257
|
2. **`vice_symbols_load` REPLACES the machine's symbol table rather than merging into it.** Call it
|
|
@@ -273,7 +291,7 @@ other generated artifact in this repo and never hand-edit it.
|
|
|
273
291
|
existed.** Before gap-closure round 2 the banner recorded the store and the sidecar by their
|
|
274
292
|
ABSOLUTE paths, so the checkout's own location was a silent third cause: an identical store,
|
|
275
293
|
sidecar and rendered file reported `drifted` the moment the tree sat at a different absolute path,
|
|
276
|
-
while `render-memmap` printed the same `render_digest` in both.
|
|
294
|
+
while `render-memmap` printed the same `render_digest` in both. That cause was removed by
|
|
277
295
|
recording workspace-relative locations, so the cause set above is the one the shipped verb has. The
|
|
278
296
|
old two-cause wording is superseded rather than merely reworded, and this note says so because a
|
|
279
297
|
reader meeting it in history needs to know which claim was live when.
|
|
@@ -293,8 +311,8 @@ shipped tree run the sync before they scan it, so a change made only in the twin
|
|
|
293
311
|
is ever measured. A change made here is SHIPPED only once that sync has run.
|
|
294
312
|
|
|
295
313
|
**Dated correction, 2026-08-30 — `render-memmap` reads the annotation store directly, and the note
|
|
296
|
-
that used to stand here was WRONG when it shipped.**
|
|
297
|
-
|
|
314
|
+
that used to stand here was WRONG when it shipped.** This verb was rebuilt over the annotation
|
|
315
|
+
store: its positional is an EXISTING `.annostore`, opened
|
|
298
316
|
with `mustExist` — an absent store is refused by name rather than created — and nothing on the path
|
|
299
317
|
it reaches consults the retired external analyser. The pre-store project file the earlier note named
|
|
300
318
|
has no producer left in this repository, so there is no route back to the old spelling. That earlier
|
|
@@ -302,6 +320,29 @@ note asserted in the PRESENT TENSE that this verb still read a project file; it
|
|
|
302
320
|
when it shipped, and it is DELETED here rather than amended, so a reader comparing two dated claims
|
|
303
321
|
can tell which one to believe.
|
|
304
322
|
|
|
323
|
+
**Importing a Ghidra export, and the mechanical join that follows it.** When a Ghidra harness run
|
|
324
|
+
(a separate, host-side capability) has produced a transfer file, two calls land its findings in the
|
|
325
|
+
store — in this order, and each is one mechanical call, not an agent turn:
|
|
326
|
+
|
|
327
|
+
1. **`anno_import_ghidra_export`** reads the transfer file, writes one cross-reference row per
|
|
328
|
+
surviving reference, and DELETES the transfer file once every write has durably committed. It reports
|
|
329
|
+
`referencesSeen`, `xrefsWritten`, `xrefsAlreadyPresent` (a duplicate reference is deduplicated, not
|
|
330
|
+
double-counted) and `kindsSeenNotImported` — reference kinds outside this store's four-member
|
|
331
|
+
vocabulary, dropped and counted rather than guessed or refused. A malformed, truncated or
|
|
332
|
+
digest-mismatched export is refused by name, naming the section and the offending line, and writes
|
|
333
|
+
nothing.
|
|
334
|
+
2. **`anno_join_memmap`** then reads every cross-reference target the store already holds, skips
|
|
335
|
+
addresses inside the program's own loaded image (those are code/data addresses, not hardware
|
|
336
|
+
features), and annotates everything else with the narrowest `c64-memory-mapping/memmap.json` entry
|
|
337
|
+
containing it. It reports `addressesConsidered`, `annotated`, `skippedInImage`,
|
|
338
|
+
`skippedNoMapEntry`, `declined` and `commentsChanged`, plus a per-address decision naming the
|
|
339
|
+
outcome and, for every skip, why.
|
|
340
|
+
|
|
341
|
+
Both calls are **mechanical**: there is no agent invocation, no queue walk and no skill invocation
|
|
342
|
+
anywhere inside either one, checked structurally over the two modules' own source rather than
|
|
343
|
+
asserted in prose. Run the import call once per Ghidra export, then the join call once per updated
|
|
344
|
+
image; neither call takes an agent turn to complete.
|
|
345
|
+
|
|
305
346
|
## Static disassembly
|
|
306
347
|
|
|
307
348
|
**Dated withdrawal 2026-08-29, dated return 2026-08-31 — whole-program ACME export was WITHDRAWN
|
|
@@ -314,14 +355,17 @@ against the input** — never by an exit code and never by a string match on the
|
|
|
314
355
|
output.
|
|
315
356
|
|
|
316
357
|
```bash
|
|
317
|
-
npx -y @henols/vice-mcp anno export-asm game.prg --store game.annostore --out game
|
|
358
|
+
npx -y @henols/vice-mcp anno export-asm game.prg --store game.annostore --out game-src
|
|
318
359
|
node <plugin-root>/src/mcp/vice/vice-proxy.ts anno export-asm game.prg --store game.annostore
|
|
319
360
|
```
|
|
320
361
|
|
|
321
362
|
`<image>` and `--store` are **two separate arguments and neither is derived from the other**: the
|
|
322
|
-
image supplies the bytes, the store supplies the names, typed ranges and comments. `--out`
|
|
323
|
-
|
|
324
|
-
rather than
|
|
363
|
+
image supplies the bytes, the store supplies the names, typed ranges and comments. `--out` names a
|
|
364
|
+
**directory** the whole export is written into, defaulting to the image's basename stem beside the
|
|
365
|
+
**store** rather than beside the image, and a non-empty destination is refused rather than
|
|
366
|
+
overwritten unless you pass `--force`. The directory holds a root file that sources the rest, one
|
|
367
|
+
file per annotation scope, and an `unscoped.a` for any block that lies inside no scope — open the
|
|
368
|
+
root file first to see how the tree fits together.
|
|
325
369
|
|
|
326
370
|
**It writes source and runs no assembler**, and says so in its own second output line
|
|
327
371
|
(`this file has NOT been assembled`). The real-ACME byte-diff is a **test-only** oracle in this
|
|
@@ -347,7 +391,7 @@ The two are complementary — reach for the static reads before the emulator is
|
|
|
347
391
|
Extracting a program from a `.d64` image is a separate capability that this repository still does
|
|
348
392
|
not have, and — correcting an earlier note that assigned it to the same numbered phase as the ACME
|
|
349
393
|
export oracle — **no phase currently owns it**. Whenever it is built it must name the file inside
|
|
350
|
-
the image explicitly and refuse rather than guess
|
|
394
|
+
the image explicitly and refuse rather than guess, because a guess could analyse a cracktro
|
|
351
395
|
or loader stub instead of the game.
|
|
352
396
|
|
|
353
397
|
## Before you touch the emulator
|
|
@@ -474,9 +518,9 @@ up a pointer or a vector does it with a pair of immediate loads — `LDA #<targe
|
|
|
474
518
|
/ STA ptr`, `LDA #>target / STA ptr+1`. Upstream calls `set_immediate_format`
|
|
475
519
|
on each of the two instruction addresses, with `low_byte` / `high_byte` and the
|
|
476
520
|
target, so the pair renders as one symbol reference. **That call is not exposed
|
|
477
|
-
on this project's surface.**
|
|
478
|
-
reference goes through a symbol, so code can move"
|
|
479
|
-
|
|
521
|
+
on this project's surface.** The rule it would serve — "every branch, `JSR`/`JMP`
|
|
522
|
+
and data reference goes through a symbol, so code can move" — still supplies
|
|
523
|
+
its criterion, and the per-call disposition sits in the manifest named
|
|
480
524
|
in the attribution header above. Until then, recombine the two bytes yourself
|
|
481
525
|
and put the reconstructed target in a side comment on both instructions, so the
|
|
482
526
|
vector setup is readable even though the store cannot format it.
|
|
@@ -529,19 +573,26 @@ documented earlier on this page.
|
|
|
529
573
|
| Disassembly appears to break mid-routine | Undocumented opcodes. Check the binary-info hint and keep reading. |
|
|
530
574
|
| Zero-page usage contradicts the main program's | The routine runs from the IRQ. Its context is IRQ-relative. |
|
|
531
575
|
|
|
532
|
-
##
|
|
533
|
-
|
|
534
|
-
**
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
576
|
+
## A tokenized BASIC stub: detokenize with c64-petcat, don't hand-decode it
|
|
577
|
+
|
|
578
|
+
**Detokenizing a `SYS` stub and resolving its machine-code handover address is
|
|
579
|
+
`c64-petcat`'s job, not this skill's.** Reach for its `decode` verb before
|
|
580
|
+
reading tokenized bytes by hand: it wraps VICE's own `petcat` to produce the
|
|
581
|
+
readable listing and to report the handover address — or a *named decline*
|
|
582
|
+
(`entrypoint: null` with a reason) when the `SYS` argument is not a static
|
|
583
|
+
value, which is a resolved answer, never a guess to spend a disassembler on.
|
|
584
|
+
Corrected here: an earlier note claimed none of `c64-petcat`'s trigger phrases
|
|
585
|
+
overlapped this skill's frontmatter; that was true only because the skill did
|
|
586
|
+
not exist yet, and it is superseded now that it does.
|
|
587
|
+
|
|
588
|
+
The material below stays as **reference text only**, narrowed to what
|
|
589
|
+
`c64-petcat` does NOT do — write the tokenized bytes' typed ranges and
|
|
590
|
+
comments into this project's annotation store. That is rare in practice: a
|
|
591
|
+
commercial C64 title captured after its loader has run almost universally
|
|
592
|
+
reduces to a one-line `SYS` stub, so most sessions never reach this section at
|
|
593
|
+
all. When one does, use `c64-petcat`'s resolved listing and handover address
|
|
594
|
+
as the source of truth rather than re-deriving them by hand from the byte
|
|
595
|
+
layout below.
|
|
545
596
|
|
|
546
597
|
### Line anatomy
|
|
547
598
|
|
|
@@ -610,6 +661,8 @@ This one is the route between the stations. It does not restate what the others
|
|
|
610
661
|
|
|
611
662
|
| Need | Go to |
|
|
612
663
|
|---|---|
|
|
664
|
+
| A disk image's directory, BAM, sector chains, or a named file's raw bytes | `c64-disk-access` |
|
|
665
|
+
| Detokenizing a BASIC stub, or its machine-code handover address | `c64-petcat` |
|
|
613
666
|
| A verified 64K image, or comparing two captures | `c64-ram-capture` |
|
|
614
667
|
| What a specific address or bit means | `c64-memory-mapping` — `node … lookup '$D018'` |
|
|
615
668
|
| Assembling | `acme-build` |
|
|
@@ -630,9 +683,10 @@ This one is the route between the stations. It does not restate what the others
|
|
|
630
683
|
| `references/reconstruction.md` | Binary inclusion, behavioural-equivalence correctness bar, SMC labels, label vocabulary |
|
|
631
684
|
| `templates/memory-map.template.md` | `render-memmap`'s provenance sidecar schema and the confidence vocabulary — the rendered map itself is generated, not hand-authored |
|
|
632
685
|
|
|
633
|
-
|
|
634
|
-
graded with `Evidence:` and `Confidence:`. Promote by re-logging with the new
|
|
635
|
-
editing
|
|
686
|
+
Record findings that make RE faster in your own project notes **at the moment you find them**,
|
|
687
|
+
graded with `Evidence:` and `Confidence:`. Promote a finding by re-logging it with the new
|
|
688
|
+
evidence, never by editing an old grade in place — the grade is only worth anything if it says
|
|
689
|
+
what was actually known when it was written.
|
|
636
690
|
|
|
637
691
|
## Troubleshooting
|
|
638
692
|
|
|
@@ -644,5 +698,5 @@ editing a grade in place. File-changing work enters through a GSD command (`/gsd
|
|
|
644
698
|
| A sprite decodes as noise | Check `$D015` first; a disabled sprite's registers are stale. Then check MCM — multicolor decoded as hires comes out twice as wide. |
|
|
645
699
|
| Computed mode is "INVALID — screen goes black" | You caught the registers mid-update inside a raster split. Re-read. |
|
|
646
700
|
| The emulator looks dead | Enumerate armed checkpoints before anything else. See hazard 2. |
|
|
647
|
-
| `vice_keyboard_type` does nothing | The game polls `$DC00`/`$DC01` directly.
|
|
701
|
+
| `vice_keyboard_type` does nothing | The game polls `$DC00`/`$DC01` directly. `vice_keyboard_matrix` is **permanently unavailable** — see `references/observation-hazards.md` § 4 for the reason and the available alternative. |
|
|
648
702
|
| Two captures of the same checkpoint differ | Expected. Full-64K identity is impossible in principle; use `c64-ram-capture`'s drift rules. |
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Control flow: entry point → vectors → IRQ source → main loop → structure
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
doc-derived) except where a line says otherwise. The vector-table step was confirmed against
|
|
3
|
+
Graded 2026-08-01, **MEDIUM**, doc-derived, except where a line says otherwise. The vector-table step was confirmed against
|
|
5
4
|
this project's own captures on 2026-08-04 — see the bottom of this file.
|
|
6
5
|
|
|
7
6
|
## 1. Entry point — three routes, and post-depack is a different question
|
|
@@ -31,7 +30,7 @@ it prints the IRQ/BRK/NMI and hardware blocks by default and takes `--all` for t
|
|
|
31
30
|
| Hardware vectors | `$FFFA-$FFFF` | NMI, RESET, IRQ/BRK. Live when the KERNAL is banked out |
|
|
32
31
|
|
|
33
32
|
**The hardware pairs are only live when the ROMs are banked out via `$01`.** The deciding bit is
|
|
34
|
-
HIRAM, `$01` bit 1 (
|
|
33
|
+
HIRAM, `$01` bit 1 (observed 2026-08-02). HIRAM = 1 ⇒ KERNAL ROM is in and `$0314/$0315` is
|
|
35
34
|
live. HIRAM = 0 ⇒ RAM at `$E000-$FFFF` and `$FFFE/$FFFF` is live.
|
|
36
35
|
|
|
37
36
|
Why LOAD and STOP earn their own callout on this project: both releases use custom raw-sector
|
|
@@ -82,15 +81,13 @@ Run over all six committed captures of one title (two releases, runs 1-3 each):
|
|
|
82
81
|
|
|
83
82
|
NMI and RESET sharing one entry is the shape of an anti-tamper trap: RESTORE and reset are the two
|
|
84
83
|
ways a user perturbs a running game, and both land in the same place. `$1116` is therefore the
|
|
85
|
-
address to checkpoint when the emulator is next available
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
PETSCII text into the buffer) cannot produce it. Calling it on the stock backend returns an error
|
|
93
|
-
naming the reason and the fork backend, rather than pulsing RESTORE.
|
|
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.
|
|
94
91
|
|
|
95
92
|
**Evidence:** derived mechanically from six three-run-verified captures; every value identical
|
|
96
93
|
across all three runs of its release, so none of it is drift.
|
|
@@ -161,9 +158,9 @@ at a title screen and again in gameplay, diff the two captures, and look for a s
|
|
|
161
158
|
changed in zero page or low RAM. `vice_memory_compare` narrows this; `c64-ram-capture` § Compare
|
|
162
159
|
two captures gives the volatility rules that stop you chasing drift.
|
|
163
160
|
|
|
164
|
-
|
|
161
|
+
Only `mode: 'ranges'` is served — capture the two states at different points in time and
|
|
165
162
|
compare two live ranges. `mode: 'snapshot'` is refused with an explanatory message; there is no
|
|
166
|
-
memory-only snapshot producer
|
|
163
|
+
memory-only snapshot producer at all.
|
|
167
164
|
|
|
168
165
|
## Verified against this project — 2026-08-04
|
|
169
166
|
|
|
@@ -173,7 +170,7 @@ holding **`$1103`**, while `$0314/$0315` holds `$0101` — nothing meaningful, e
|
|
|
173
170
|
dormant-block rule predicts.
|
|
174
171
|
|
|
175
172
|
`$1103` is the same IRQ-handler entry that phase-01 live work independently established, with the
|
|
176
|
-
raster-split chain `$1103 → $1574 → $152C` (
|
|
173
|
+
raster-split chain `$1103 → $1574 → $152C` (observed 2026-08-02, on a checkpoint trap). The
|
|
177
174
|
method reproduces a known-good result from a static image with no emulator running. The widened
|
|
178
175
|
sweep's own results, including two facts this table never surfaced, are in §2 above.
|
|
179
176
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# VIC-II: locating every displayed byte
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Graded 2026-08-01, **MEDIUM**, doc-derived.
|
|
4
4
|
|
|
5
5
|
**Graphics data is computed, not searched.** Every pointer the VIC follows derives from two
|
|
6
6
|
registers plus a bank. Read the bank, read `$D018`, read the mode bits, read `$D015` and the
|
|
@@ -5,7 +5,8 @@ this project at real cost. A wrong answer from a register table is cheap; a wron
|
|
|
5
5
|
machine that changed *because you looked at it* discredits a whole session without announcing
|
|
6
6
|
itself.
|
|
7
7
|
|
|
8
|
-
|
|
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.
|
|
9
10
|
|
|
10
11
|
## 1. Agent think-time runs the emulator at full speed
|
|
11
12
|
|
|
@@ -59,8 +60,7 @@ bracket, and comes back `wedged`. **The response to `wedged` is recycle, and rec
|
|
|
59
60
|
instance is the exact loss both the tool and this hazard exist to prevent.** So when a `wedged`
|
|
60
61
|
verdict arrives with any checkpoint still armed, do the two reads by hand before recycling:
|
|
61
62
|
`vice_checkpoint_list`, then resolve the live handler (`$0314/$0315`, or `$FFFE/$FFFF` when `$01`
|
|
62
|
-
has the ROMs banked out).
|
|
63
|
-
`.planning/todos/pending/2026-08-04-vice-diagnose-checkpoint-trap-shapes-miss-mid-handler-arming.md`.
|
|
63
|
+
has the ROMs banked out). Observed 2026-08-04, and still open.
|
|
64
64
|
|
|
65
65
|
**Counter-evidence, and why this is MEDIUM:** in one incident, deleting the checkpoint did *not*
|
|
66
66
|
unfreeze the machine, and neither did a soft reset, a hard reset, nor a single step. A checkpoint
|
|
@@ -80,19 +80,20 @@ Prefer the whole-chip reads — `vice_vicii_get_state`, `vice_cia_get_state`, `v
|
|
|
80
80
|
— over raw register reads. Whether the VICE monitor's own read path is side-effect-free is
|
|
81
81
|
**unverified**: treat it as verify-don't-assume rather than taking it on faith.
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
`vice_vicii_get_state`/`vice_cia_get_state` reads are `sidefx: false` with
|
|
84
84
|
no argument able to override it — **VERIFIED**, asserted on the wire body by a regression test.
|
|
85
85
|
Whether the emulator's own `MEM_GET` read path actually honours that flag for
|
|
86
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
|
|
88
|
-
|
|
89
|
-
in hardware and the binary monitor has no SID command
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
93
94
|
`registerBank`/`dataBank`), read through the emulator's own `io`/`ram` banks, so it stays valid
|
|
94
95
|
even while the program has I/O banked out ($01 driving the RAM/ROM/I-O switch). An answer with
|
|
95
|
-
**no** bank field — an older transcript
|
|
96
|
+
**no** bank field — an older transcript — is suspect whenever `$01` may not
|
|
96
97
|
have been `$37`: those bytes may be the RAM underneath the I/O area, not registers.
|
|
97
98
|
|
|
98
99
|
## 4. The keyboard buffer is not how games read keys
|
|
@@ -103,13 +104,14 @@ Games and cracks poll the `$DC00`/`$DC01` matrix directly, bypassing the KERNAL
|
|
|
103
104
|
`vice_keyboard_type` is invisible to them. Use `vice_keyboard_matrix`, and hold a key across a
|
|
104
105
|
gate by releasing it at the trigger checkpoint, never earlier.
|
|
105
106
|
|
|
106
|
-
**`vice_keyboard_matrix`
|
|
107
|
+
**`vice_keyboard_matrix` is permanently unavailable.** The binary monitor's `KEYBOARD_FEED` (0x72)
|
|
107
108
|
only injects PETSCII text into the KERNAL keyboard buffer; the emulator recomputes CIA port B from
|
|
108
109
|
its own keyboard array on every read, so there is no wire command that can drive the raw matrix —
|
|
109
|
-
this is unrecoverable
|
|
110
|
-
`vice_keyboard_petscii` when the gate reads the KERNAL buffer, or
|
|
111
|
-
the matrix directly instead; either way, buffer injection is
|
|
112
|
-
`$DC00`/`$DC01` itself, so a matrix-polling gate must be driven by
|
|
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.
|
|
113
115
|
|
|
114
116
|
## 5. Most state reads pause the emulator and do not resume it
|
|
115
117
|
|
|
@@ -43,8 +43,7 @@ at a checkpoint is not verified** — the constraint's own conclusion, and the r
|
|
|
43
43
|
design is part of the reconstruction work rather than something to bolt on afterwards.
|
|
44
44
|
|
|
45
45
|
**That bar is what buys you the freedom to rename routines, reorganise files, replace constants
|
|
46
|
-
with symbols and add macros**
|
|
47
|
-
argument). **But** reorganising changes addresses, which breaks self-modifying code and
|
|
46
|
+
with symbols and add macros**. **But** reorganising changes addresses, which breaks self-modifying code and
|
|
48
47
|
timing-sensitive raster routines. Replay through the checkpoint set after EACH reorganisation, not
|
|
49
48
|
at the end of several: one changed address per failing replay is a short diagnosis, ten is a
|
|
50
49
|
bisect.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# SID and CIA: music, effects, the RNG, input, timing
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
**MEDIUM**, doc-derived) except where marked. Per-register bit detail lives in
|
|
3
|
+
Graded 2026-08-01, **MEDIUM**, doc-derived, except where marked. Per-register bit detail lives in
|
|
5
4
|
`c64-memory-mapping`; this file carries the idioms and the order.
|
|
6
5
|
|
|
7
6
|
## SID — separating the player from the game logic
|
|
@@ -61,12 +60,11 @@ One that programs `$DC04-$DC07` and enables timer A runs its own timebase.
|
|
|
61
60
|
- **Direct `$DC00`/`$DC01` polling is the norm, and it defeats `vice_keyboard_type`.**
|
|
62
61
|
**Evidence: live, established on this project during recovery work. Confidence: HIGH. Cost: an
|
|
63
62
|
afternoon.** Games and cracks bypass the KERNAL keyboard buffer and read the matrix directly.
|
|
64
|
-
Assume it until shown otherwise
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
program polling `$DC00`/`$DC01` itself.
|
|
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.
|
|
70
68
|
|
|
71
69
|
## Finding input handling from the observable side
|
|
72
70
|
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
call; read parameters off those. What the schemas cannot tell you is *which call to reach for
|
|
5
5
|
first*, and that ordering is the whole value here.
|
|
6
6
|
|
|
7
|
-
Curated
|
|
8
|
-
**Confidence: MEDIUM** — the mapping is reasoned from the tool surface and this project's own
|
|
7
|
+
Curated 2026-08-01, doc-derived, **Confidence: MEDIUM** — the mapping is reasoned from the tool surface and this project's own
|
|
9
8
|
usage, not measured). Individual rows that have since been exercised live are marked.
|
|
10
9
|
|
|
11
10
|
| Question | Call |
|
|
@@ -14,13 +13,13 @@ usage, not measured). Individual rows that have since been exercised live are ma
|
|
|
14
13
|
| What does the handler at this vector do? | `vice_disassemble` — the emulator's own decoder, not a dead listing |
|
|
15
14
|
| Is this really the main loop? | `vice_checkpoint_add` + `vice_run_until` + `vice_registers_get` — fires once per frame ⇒ proven |
|
|
16
15
|
| What code writes this? | `vice_watch_add` — finds **writers**. Best targets: `$D018`, VM+`$03F8`, `$D404` |
|
|
17
|
-
| Whole-chip VIC-II/CIA state without the read hazards | `vice_vicii_get_state` / `vice_cia_get_state`
|
|
18
|
-
| Whole-chip SID state without the read hazards | `vice_sid_get_state`
|
|
19
|
-
| Decode sprite data | `vice_sprite_get` / `vice_sprite_inspect`
|
|
20
|
-
| Find a known byte pattern | `vice_memory_search`
|
|
21
|
-
| Carry labels across sessions | `vice_symbols_load` / `vice_symbols_lookup`
|
|
22
|
-
| Is the machine wedged, or did it stop itself? | `vice_diagnose` — five-state verdict with its evidence (
|
|
23
|
-
| Replace a wedged instance | `vice_recycle` — destructive, requires a `reason`, and that reason is written into `.
|
|
16
|
+
| Whole-chip VIC-II/CIA state without the read hazards | `vice_vicii_get_state` / `vice_cia_get_state` — prefer these over raw register reads |
|
|
17
|
+
| Whole-chip SID state without the read hazards | `vice_sid_get_state` — **permanently unavailable**: SID `$D400-$D418` is write-only in hardware and the binary monitor has no SID command, so read-back cannot be recovered. Writes to those addresses still work fine over the memory-set primitive. See `docs/stock-hard-losses.md` |
|
|
18
|
+
| Decode sprite data | `vice_sprite_get` / `vice_sprite_inspect` |
|
|
19
|
+
| Find a known byte pattern | `vice_memory_search` |
|
|
20
|
+
| Carry labels across sessions | `vice_symbols_load` / `vice_symbols_lookup` — ACME `--vicelabels` emits the format they consume. The annotation store's own export into that format is **withdrawn as of 2026-08-29, and no phase currently owns its return** — an earlier forecast naming a numbered phase for it is superseded |
|
|
21
|
+
| Is the machine wedged, or did it stop itself? | `vice_diagnose` — five-state verdict with its evidence (one state is `monitor_held_elsewhere`, because this monitor serves exactly one client at a time). **Reachable and proxy-intercepted as of 2026-08-04** (verified live). Triage tree: `vice-wedge-triage` |
|
|
22
|
+
| Replace a wedged instance | `vice_recycle` — destructive, requires a `reason`, and that reason is written into `.c64-re-tools/incidents/` **before** anything is killed. The reason *is* the evidence record |
|
|
24
23
|
| Read the restart epoch | **No tool does.** The proxy compares it around every forwarded call and raises drift itself; a value comes from that error or from `vice_diagnose` |
|
|
25
24
|
|
|
26
25
|
## Delegate rather than restate
|
|
@@ -32,16 +31,36 @@ usage, not measured). Individual rows that have since been exercised live are ma
|
|
|
32
31
|
| A verified 64K image, or comparing two captures | the `c64-ram-capture` skill |
|
|
33
32
|
| Whole-program static disassembly with code/data separation | **`anno export-asm`** — withdrawn 2026-08-29, returned 2026-08-31, settled by assembling the output with a real ACME and diffing the bytes against the input. That oracle is test-only, so the verb itself writes source and runs no assembler. For a single routine, read one explicit range at a time with `anno_read_region` / `anno_disassemble` (4096-byte cap per call, refused rather than truncated above it) and record what you verified with `anno_set_data_type` |
|
|
34
33
|
|
|
34
|
+
## Runtime evidence versus the byte-derived guess
|
|
35
|
+
|
|
36
|
+
| Question | Call |
|
|
37
|
+
|---|---|
|
|
38
|
+
| Where the store's byte-derived block table (`anno_set_data_type`'s own ranges) disagrees with what the emulator was actually observed executing | **`anno evid-disagreements`** (also `anno_evid_disagreements`) — joins the typed ranges against the runtime-observed rows an `anno_evid_ingest` call already wrote, reporting disagreements first, agreement as a count only, and a never-observed count that is explicitly NOT evidence the address holds data |
|
|
39
|
+
| What evidence a store already holds, without re-running the program | `anno_evid_runs` — every run identity's observation count beside its denominator |
|
|
40
|
+
| Reset one run identity's evidence for a fresh re-measurement | `anno_evid_reset` — clears only that run identity's rows; pair it with `vice_memmap_zap` on the emulator side |
|
|
41
|
+
|
|
42
|
+
## What blocks this program's code from being moved
|
|
43
|
+
|
|
44
|
+
| Question | Call |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Which constructions block relocating, rebasing or stripping part of this program | **`anno hazard-report`** (also `anno_hazard_report`) — enumerates movement-hazard findings derived from decoded bytes alone (for example, a store or read-modify-write instruction whose literal target lands on another instruction's opcode or operand byte, changing what runs or what value is read on a later pass). Each finding carries its own detection mechanism and a detection-strength token. Reports and changes NOTHING: it never relocates, strips or rebases anything, and it never emits a flag a caller could act on as an automatic relocation |
|
|
47
|
+
|
|
48
|
+
**A region this report does not flag is undecided or unflagged, never certified safe to move.** Every checked
|
|
49
|
+
region reports one of exactly three outcomes, and only one of them means a construction was actually found
|
|
50
|
+
there — the other two both mean "nothing this report knows how to look for fired here," which is a fact about
|
|
51
|
+
the detectors, not a guarantee about the bytes. A store through a runtime-computed pointer into the code range
|
|
52
|
+
is a known, named miss: this report cannot see it, and its absence from the findings is not evidence that no
|
|
53
|
+
such construction exists. Use this alongside, never instead of, the runtime-evidence and provenance-diff
|
|
54
|
+
routes above and in the sibling skills — a region with no finding still needs a human decision before it is
|
|
55
|
+
moved.
|
|
56
|
+
|
|
35
57
|
## Three traps in this table
|
|
36
58
|
|
|
37
|
-
**`vice_run_until`'s
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
schema, not reproduced. On stock, `timeout_ms` (default 30000, ceiling 600000) bounds the wait, and
|
|
43
|
-
a timed-out answer says the machine is left halted rather than looking like a wedge — see
|
|
44
|
-
`vice-wedge-triage` for the full triage judgement and its live evidence; do not restate it here.
|
|
59
|
+
**`vice_run_until`'s `timeout_ms` bounds the wait.** `timeout_ms` (default 30000, ceiling 600000)
|
|
60
|
+
bounds a run to an address the program never reaches, and a timed-out answer says the machine is
|
|
61
|
+
left halted rather than looking like a wedged emulator; prefer `vice_checkpoint_add` + a bounded
|
|
62
|
+
poll when the address is a hypothesis rather than a certainty — see `vice-wedge-triage` for the
|
|
63
|
+
full triage judgement and its live evidence; do not restate it here.
|
|
45
64
|
|
|
46
65
|
**`vice_diagnose` leaves the machine paused.** When it measures a cycle bracket it resumes the
|
|
47
66
|
machine once or twice and then leaves it **paused** — resuming is your own next call. And a
|