@henols/vice-mcp 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.
Files changed (77) hide show
  1. package/README.md +2 -2
  2. package/THIRD-PARTY-NOTICES.md +422 -1
  3. package/anno-bank.ts +171 -0
  4. package/anno-cli.ts +1674 -99
  5. package/anno-enum-gen.ts +416 -30
  6. package/anno-export-asm.ts +1175 -89
  7. package/anno-graphics.ts +338 -0
  8. package/anno-hazard-report.ts +1367 -0
  9. package/anno-import.ts +495 -0
  10. package/anno-join.ts +480 -0
  11. package/anno-provenance-ledger.ts +472 -0
  12. package/anno-register.ts +159 -0
  13. package/anno-store-export.ts +661 -0
  14. package/anno-store.ts +518 -2
  15. package/anno-tools.ts +1169 -16
  16. package/anno-types.ts +275 -2
  17. package/backend-detect.mts +124 -312
  18. package/build.ts +3 -1
  19. package/capture-predicate.ts +597 -0
  20. package/channel-lock.ts +349 -0
  21. package/evid-ingest.ts +217 -0
  22. package/evid-reconcile.ts +316 -0
  23. package/host-tool-client.ts +430 -0
  24. package/incident-record.ts +23 -12
  25. package/install-resources.ts +29 -13
  26. package/memmap-lookup.ts +285 -0
  27. package/package.json +27 -8
  28. package/prg-image.ts +1 -2
  29. package/repo-root.ts +87 -3
  30. package/resources/backend-detect.mjs +98 -236
  31. package/resources/broker-control.mjs +189 -16
  32. package/resources/broker-epoch.mjs +1 -1
  33. package/resources/broker-kill.mjs +8 -2
  34. package/resources/broker-launch.mjs +365 -210
  35. package/resources/broker-state.mjs +64 -18
  36. package/resources/container-guard.mjs +1 -1
  37. package/resources/ghidra-project.mjs +790 -0
  38. package/resources/host-tool.mjs +2561 -0
  39. package/resources/vice-broker.mjs +330 -184
  40. package/resources/vice-launcher.sh +127 -9
  41. package/stock-address.ts +1 -1
  42. package/stock-condition.ts +1 -1
  43. package/stock-connect.ts +9 -5
  44. package/stock-derived.ts +29 -37
  45. package/stock-diagnose.ts +200 -36
  46. package/stock-dispatch.ts +179 -77
  47. package/stock-handler.ts +1 -1
  48. package/stock-paths.ts +18 -14
  49. package/stock-petscii.ts +1 -1
  50. package/stock-protocol.ts +1 -1
  51. package/stock-recycle.ts +83 -2
  52. package/stock-reproducible-run.ts +811 -0
  53. package/stock-run-until.ts +100 -1
  54. package/stock-symbols.ts +4 -4
  55. package/stock-timing.ts +1 -1
  56. package/stop-oracle.ts +167 -0
  57. package/text-capability-probe.ts +660 -0
  58. package/text-connect.ts +157 -0
  59. package/text-protocol.ts +810 -0
  60. package/text-tools.ts +778 -0
  61. package/textmon-backtrace.ts +385 -0
  62. package/textmon-cpuhistory.ts +335 -0
  63. package/textmon-memmap.ts +494 -0
  64. package/textmon-profile.ts +458 -0
  65. package/textmon-registers.ts +748 -0
  66. package/tools-manifest.stock.json +864 -3
  67. package/vice-broker-client.ts +189 -42
  68. package/vice-errors.ts +268 -0
  69. package/vice-proxy.ts +339 -2144
  70. package/vsf-slice.ts +640 -0
  71. package/anno-d64.ts +0 -310
  72. package/capability-registry.ts +0 -390
  73. package/refresh-manifest.ts +0 -124
  74. package/tools-manifest.json +0 -1223
  75. package/vice-probe.ts +0 -278
  76. package/vice-sync.ts +0 -336
  77. package/vice.ts +0 -772
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/env node
2
+ // anno-graphics.ts
3
+ //
4
+ // Phase 37, plan 37-07 (AUTO-06): derives the VIC-II graphics areas -- screen
5
+ // matrix, character set or bitmap, and sprite POINTERS -- from RECOVERED VIC
6
+ // register VALUES, never from cross-references. The chip fetches its
7
+ // character/bitmap/screen data by direct memory access, so a character set
8
+ // may be referenced by NO INSTRUCTION ANYWHERE IN THE PROGRAM -- exactly the
9
+ // case a cross-reference-driven join structurally cannot find. That is
10
+ // AUTO-06's entire reason to exist (`37-RESEARCH.md` SS E: no existing code in
11
+ // this repository decodes these three registers for this purpose -- the
12
+ // stock-backend modules that name them read LIVE emulator state for a
13
+ // different question).
14
+ //
15
+ // THIS MODULE TAKES PLAIN REGISTER-WRITE VALUES AND RETURNS PLAIN RANGES --
16
+ // AND NOTHING ELSE (D-37-26). It never receives a store handle, never opens
17
+ // or names `node:sqlite`, never imports `anno-store.ts`, `anno-join.ts`, or
18
+ // any module that reads the stored cross-reference graph, and never imports
19
+ // `hostpath.ts`/`containerpath.ts` or the emulator backend
20
+ // (`stock-vicii.ts`/`stock-sprites.ts`). A module that could see the
21
+ // reference graph would let an implementation quietly lean on it for the
22
+ // very case this derivation exists to cover -- so its import list is scanned
23
+ // structurally (`anno-graphics.test.ts`) rather than merely reviewed.
24
+ //
25
+ // SPRITE BITMAP LOCATIONS ARE OUT OF SCOPE (D-37-29): this module derives the
26
+ // eight-byte sprite POINTER TABLE range only. The pointer VALUES themselves
27
+ // are program data, usually written at run time, and are not register values
28
+ // a static analysis recovers -- this module must never appear to promise
29
+ // them.
30
+ //
31
+ // A MISSING REGISTER IS A STATED ABSENCE, NEVER A DEFAULT (D-37-28): where a
32
+ // required register value was never recovered, the ranges that depend on it
33
+ // are omitted from that map and the map names the missing register in
34
+ // `missingRegisters`. Power-on defaults exist and are well known, which is
35
+ // exactly why substituting one is tempting and wrong: a range built on an
36
+ // assumed value is indistinguishable in the output from one built on a
37
+ // recovered value, and this phase exists to prevent exactly that class of
38
+ // confident wrong output.
39
+ //
40
+ // SEVERAL COMBINATIONS ARE SEVERAL MAPS, NEVER ONE MERGED MAP (D-37-27): a
41
+ // program that reprograms bank, screen or charset per raster split has
42
+ // several valid graphics maps, and a single derived map would be wrong for
43
+ // all but one -- the same path-dependence limit AUTO-05 names for the
44
+ // processor port. `deriveGraphicsRanges()` returns one map per DISTINCT
45
+ // combination of the three registers' own recovered values: the cross
46
+ // product of each register's own distinct-value set, with a register that
47
+ // carries zero facts contributing a single "missing" slot rather than an
48
+ // axis (never a synthesized value). Combinations are produced in ascending
49
+ // tuple order (missing sorts first), so two runs over the same facts return
50
+ // deeply equal arrays.
51
+ //
52
+ // NO GRAPHICS-SPECIFIC DATA TYPE EXISTS (D-37-30): the store's `DataType`
53
+ // vocabulary (`anno-types.ts`) is a frozen twelve with no graphics-specific
54
+ // member, and widening it is a schema decision no plan in this phase makes --
55
+ // widening it would require re-auditing every consumer of that frozen list,
56
+ // including `dxa-blocks.ts`'s own derivation of which types are data-bearing.
57
+ // Every range this module emits therefore carries the closest existing
58
+ // member, `"byte"`.
59
+ //
60
+ // BIT ARITHMETIC CROSS-CHECKED AGAINST `stock-vicii.ts`/`stock-sprites.ts`
61
+ // (READ-ONLY, NEVER IMPORTED): this module's bank/screen/charset/bitmap
62
+ // arithmetic was checked by hand against those two files' existing, tested
63
+ // decode of the SAME three registers for the live-emulator backend, and
64
+ // found to agree exactly -- see the plan 37-07 SUMMARY for the comparison.
65
+ // This module REIMPLEMENTS that arithmetic rather than importing it, because
66
+ // importing either file would reach the emulator backend's own transport
67
+ // seam, which this module must never touch.
68
+ //
69
+ // WHAT NOT TO DO:
70
+ // - Never derive `kind` from a range's own size, or vice versa. The mode
71
+ // bit decides `kind`; `kind` then decides size. Deriving the kind from
72
+ // the size would make a wrong mode bit produce a plausible range of the
73
+ // wrong length rather than a visibly wrong kind.
74
+ // - Never merge two distinct register-value combinations into one map.
75
+ // Each combination this module observes gets its own entry in the
76
+ // returned array, even when two combinations' ranges happen to overlap.
77
+ // - Never widen `missingRegisters` handling to substitute a guessed value
78
+ // "just this once" -- every range depending on a missing register is
79
+ // omitted, with no exception.
80
+
81
+ import type { DataType } from "./anno-types.ts";
82
+
83
+ /** The address of the CIA2 Data Port A register (VIC bank select, bits 0-1)
84
+ * this module watches. Matches `CONST_WRITE_WATCHED_ADDRESSES` in
85
+ * `anno-import.ts` (kept in step there, not re-imported here -- see the
86
+ * module header on why this file imports nothing from that module). */
87
+ export const BANK_SELECT_ADDRESS = 0xdd00;
88
+ /** The address of the VIC-II memory setup register (screen/charset-or-bitmap
89
+ * pointers) this module watches. */
90
+ export const MEMORY_CONTROL_ADDRESS = 0xd018;
91
+ /** The address of VIC-II control register #1 (bit 5: bitmap mode) this
92
+ * module watches. */
93
+ export const CONTROL_REGISTER_1_ADDRESS = 0xd011;
94
+
95
+ /** The three watched registers' own stable names, used both as
96
+ * `GraphicsMap.registerValues`/`missingRegisters` keys and as
97
+ * `GraphicsRange.derivedFrom` keys -- one place names them so a caller sees
98
+ * the identical string whichever field it reads. */
99
+ const REGISTER_NAMES: Readonly<Record<number, string>> = Object.freeze({
100
+ [BANK_SELECT_ADDRESS]: "bank-select",
101
+ [MEMORY_CONTROL_ADDRESS]: "memory-control",
102
+ [CONTROL_REGISTER_1_ADDRESS]: "control-register-1",
103
+ });
104
+
105
+ const BANK_SELECT_NAME = REGISTER_NAMES[BANK_SELECT_ADDRESS]!;
106
+ const MEMORY_CONTROL_NAME = REGISTER_NAMES[MEMORY_CONTROL_ADDRESS]!;
107
+ const CONTROL_REGISTER_1_NAME = REGISTER_NAMES[CONTROL_REGISTER_1_ADDRESS]!;
108
+
109
+ /** 16 KB per VIC-II bank ($DD00 bits 0-1, four banks). */
110
+ const VIC_BANK_SIZE = 16384;
111
+ /** $D018's high nibble is a screen-pointer unit of this many bytes. */
112
+ const SCREEN_MATRIX_GRANULARITY = 1024;
113
+ /** The fixed size of the screen matrix itself. */
114
+ const SCREEN_MATRIX_SIZE = 1024;
115
+ /** $D018's low nibble's upper three bits are a character/bitmap-pointer unit
116
+ * of this many bytes. */
117
+ const CHARACTER_BASE_GRANULARITY = 2048;
118
+ /** The fixed size of a character set (256 characters, 8 bytes each). */
119
+ const CHARACTER_SET_SIZE = 2048;
120
+ /** The fixed size of a hi-res/multicolour bitmap. */
121
+ const BITMAP_SIZE = 8192;
122
+ /** The sprite pointer table's fixed offset from the screen matrix base. */
123
+ export const SPRITE_POINTER_OFFSET = 0x3f8;
124
+ /** The sprite pointer table's fixed size -- eight pointer bytes, one per
125
+ * sprite. */
126
+ const SPRITE_POINTER_SIZE = 8;
127
+
128
+ /** The existing, byte-shaped `DataType` every range this module emits
129
+ * carries (D-37-30). */
130
+ const GRAPHICS_DATA_TYPE: DataType = "byte";
131
+
132
+ /** One recovered register write, in the same shape `ConstWriteFact` in
133
+ * `anno-import.ts` exports (`{ storeAddress, targetAddress, value }`).
134
+ * Declared locally -- not imported -- so this module's own import list stays
135
+ * free of any dependency on `anno-import.ts` (see the module header); plain
136
+ * structural typing means a caller can still pass `ConstWriteFact[]`
137
+ * directly with no translation layer. */
138
+ export interface GraphicsConstWriteFact {
139
+ storeAddress: number;
140
+ targetAddress: number;
141
+ value: number;
142
+ }
143
+
144
+ /** One derived graphics range. Shaped to match `dxa-blocks.ts`'s
145
+ * `KnownDataRow` (`start`/`endInclusive`/`dataType`) so a caller can produce
146
+ * a `KnownDataRow` from one without a translation layer -- `kind` and
147
+ * `derivedFrom` are this module's own additions, carrying which of the four
148
+ * things the range is and which register values produced it. Both ends are
149
+ * INCLUSIVE. */
150
+ export interface GraphicsRange {
151
+ start: number;
152
+ endInclusive: number;
153
+ kind: string;
154
+ dataType: DataType;
155
+ derivedFrom: Record<string, number>;
156
+ }
157
+
158
+ /** One derived graphics map -- the ranges implied by one distinct
159
+ * combination of the three registers' own recovered values.
160
+ * `registerValues` carries every register value THIS map actually had;
161
+ * `missingRegisters` names, by `REGISTER_NAMES`'s own spelling, every
162
+ * register this map had no recovered value for at all. A non-empty
163
+ * `missingRegisters` map carries only the ranges that did not depend on the
164
+ * missing value(s) -- never nothing, and never a guess. */
165
+ export interface GraphicsMap {
166
+ ranges: GraphicsRange[];
167
+ registerValues: Record<string, number>;
168
+ missingRegisters: string[];
169
+ }
170
+
171
+ /** VIC-II bank base: $DD00 bits 0-1, INVERTED (the chip's own convention --
172
+ * the stored value is the complement of the bank number), times the 16 KB
173
+ * bank size. A value with both low bits set (`0b11`) selects bank 0 (base
174
+ * 0); a value with both low bits clear (`0b00`) selects bank 3 (the highest
175
+ * base, $C000). Matches `stock-sprites.ts`'s `vicBank()`/`vicBankBase()`
176
+ * exactly (read-only cross-check, never imported -- see module header). */
177
+ function vicBankBase(bankSelectValue: number): number {
178
+ const invertedBankBits = 3 - (bankSelectValue & 0x03);
179
+ return invertedBankBits * VIC_BANK_SIZE;
180
+ }
181
+
182
+ /** Screen matrix base: $D018's high nibble, scaled by the screen-matrix
183
+ * granularity, added to the bank base. Matches `stock-sprites.ts`'s
184
+ * `screenBase()`/`stock-vicii.ts`'s `memorySetup.screenOffset` exactly. */
185
+ function screenMatrixStart(bankBase: number, memoryControlValue: number): number {
186
+ return bankBase + ((memoryControlValue >> 4) & 0x0f) * SCREEN_MATRIX_GRANULARITY;
187
+ }
188
+
189
+ /** Character-or-bitmap base: $D018's low nibble's upper three bits (bits
190
+ * #1-#3), scaled by the character-base granularity, added to the bank base.
191
+ * Matches `stock-vicii.ts`'s `memorySetup.charsetOffset` exactly. */
192
+ function characterOrBitmapStart(bankBase: number, memoryControlValue: number): number {
193
+ return bankBase + ((memoryControlValue >> 1) & 0x07) * CHARACTER_BASE_GRANULARITY;
194
+ }
195
+
196
+ /** True iff $D011 bit 5 (bitmap mode) is set. Matches `stock-vicii.ts`'s
197
+ * `control1.bitmapMode` exactly. Do not derive `kind` from a range's size --
198
+ * this bit decides `kind`, and `kind` then decides size (module header). */
199
+ function isBitmapMode(controlRegister1Value: number): boolean {
200
+ return ((controlRegister1Value >> 5) & 1) === 1;
201
+ }
202
+
203
+ /** Every DISTINCT value `facts` records for a write to `targetAddress`,
204
+ * ascending. Two facts recording the SAME value are one combination axis
205
+ * value, not two. */
206
+ function distinctValuesFor(facts: readonly GraphicsConstWriteFact[], targetAddress: number): number[] {
207
+ const seen = new Set<number>();
208
+ for (const fact of facts) {
209
+ if (fact.targetAddress === targetAddress) seen.add(fact.value);
210
+ }
211
+ return [...seen].sort((a, b) => a - b);
212
+ }
213
+
214
+ /** One combination of the three registers' own recovered values --
215
+ * `undefined` names a register with zero recovered facts (D-37-28). */
216
+ interface RegisterCombo {
217
+ bankSelect: number | undefined;
218
+ memoryControl: number | undefined;
219
+ controlRegister1: number | undefined;
220
+ }
221
+
222
+ /** Ascending, with `undefined` (missing) sorting first -- a stated,
223
+ * deterministic order over the tuple (D-37-27), not "whatever `Set`
224
+ * iteration happened to produce". */
225
+ function compareSlot(a: number | undefined, b: number | undefined): number {
226
+ const av = a ?? -1;
227
+ const bv = b ?? -1;
228
+ return av - bv;
229
+ }
230
+
231
+ function compareCombo(a: RegisterCombo, b: RegisterCombo): number {
232
+ return (
233
+ compareSlot(a.bankSelect, b.bankSelect) ||
234
+ compareSlot(a.memoryControl, b.memoryControl) ||
235
+ compareSlot(a.controlRegister1, b.controlRegister1)
236
+ );
237
+ }
238
+
239
+ /** One map for one combination. Screen matrix and sprite pointers depend
240
+ * only on `bankSelect`/`memoryControl`; the character-or-bitmap range
241
+ * additionally depends on `controlRegister1` (the mode bit) and is omitted
242
+ * on its own when that register alone is missing -- the other two ranges
243
+ * still derive. A missing `bankSelect` OR `memoryControl` removes every
244
+ * range (all three ultimately need the bank base and the screen-matrix
245
+ * base). */
246
+ function buildGraphicsMap(combo: RegisterCombo): GraphicsMap {
247
+ const { bankSelect, memoryControl, controlRegister1 } = combo;
248
+
249
+ const registerValues: Record<string, number> = {};
250
+ const missingRegisters: string[] = [];
251
+ if (bankSelect === undefined) missingRegisters.push(BANK_SELECT_NAME);
252
+ else registerValues[BANK_SELECT_NAME] = bankSelect;
253
+ if (memoryControl === undefined) missingRegisters.push(MEMORY_CONTROL_NAME);
254
+ else registerValues[MEMORY_CONTROL_NAME] = memoryControl;
255
+ if (controlRegister1 === undefined) missingRegisters.push(CONTROL_REGISTER_1_NAME);
256
+ else registerValues[CONTROL_REGISTER_1_NAME] = controlRegister1;
257
+
258
+ const ranges: GraphicsRange[] = [];
259
+
260
+ if (bankSelect !== undefined && memoryControl !== undefined) {
261
+ const bankBase = vicBankBase(bankSelect);
262
+ const screenStart = screenMatrixStart(bankBase, memoryControl);
263
+ const screenAndSpriteDerivedFrom: Record<string, number> = {
264
+ [BANK_SELECT_NAME]: bankSelect,
265
+ [MEMORY_CONTROL_NAME]: memoryControl,
266
+ };
267
+
268
+ ranges.push({
269
+ start: screenStart,
270
+ endInclusive: screenStart + SCREEN_MATRIX_SIZE - 1,
271
+ kind: "screen-matrix",
272
+ dataType: GRAPHICS_DATA_TYPE,
273
+ derivedFrom: { ...screenAndSpriteDerivedFrom },
274
+ });
275
+
276
+ if (controlRegister1 !== undefined) {
277
+ const charOrBitmapStart = characterOrBitmapStart(bankBase, memoryControl);
278
+ const bitmap = isBitmapMode(controlRegister1);
279
+ ranges.push({
280
+ start: charOrBitmapStart,
281
+ endInclusive: charOrBitmapStart + (bitmap ? BITMAP_SIZE : CHARACTER_SET_SIZE) - 1,
282
+ kind: bitmap ? "bitmap" : "character-set",
283
+ dataType: GRAPHICS_DATA_TYPE,
284
+ derivedFrom: {
285
+ [BANK_SELECT_NAME]: bankSelect,
286
+ [MEMORY_CONTROL_NAME]: memoryControl,
287
+ [CONTROL_REGISTER_1_NAME]: controlRegister1,
288
+ },
289
+ });
290
+ }
291
+
292
+ const spriteStart = screenStart + SPRITE_POINTER_OFFSET;
293
+ ranges.push({
294
+ start: spriteStart,
295
+ endInclusive: spriteStart + SPRITE_POINTER_SIZE - 1,
296
+ kind: "sprite-pointers",
297
+ dataType: GRAPHICS_DATA_TYPE,
298
+ derivedFrom: { ...screenAndSpriteDerivedFrom },
299
+ });
300
+ }
301
+
302
+ return { ranges, registerValues, missingRegisters };
303
+ }
304
+
305
+ /**
306
+ * Derives the graphics areas -- screen matrix, character set or bitmap, and
307
+ * sprite pointers -- from recovered VIC register values ALONE (D-37-26).
308
+ * Returns one `GraphicsMap` per distinct combination of the three registers'
309
+ * own recovered values (D-37-27), in ascending tuple order, so two calls
310
+ * with the same `facts` return deeply equal arrays. A register with zero
311
+ * recovered facts contributes a single "missing" combination slot rather
312
+ * than an axis; every range depending on it is omitted from every map, and
313
+ * the map names it in `missingRegisters` (D-37-28). `facts` carrying values
314
+ * for addresses other than the three this module watches (e.g. the
315
+ * processor port, $0001) are ignored -- this module only ever reads writes
316
+ * to `BANK_SELECT_ADDRESS`/`MEMORY_CONTROL_ADDRESS`/`CONTROL_REGISTER_1_ADDRESS`.
317
+ */
318
+ export function deriveGraphicsRanges(facts: readonly GraphicsConstWriteFact[]): GraphicsMap[] {
319
+ const bankSelectValues = distinctValuesFor(facts, BANK_SELECT_ADDRESS);
320
+ const memoryControlValues = distinctValuesFor(facts, MEMORY_CONTROL_ADDRESS);
321
+ const controlRegister1Values = distinctValuesFor(facts, CONTROL_REGISTER_1_ADDRESS);
322
+
323
+ const bankSlots: (number | undefined)[] = bankSelectValues.length > 0 ? bankSelectValues : [undefined];
324
+ const memorySlots: (number | undefined)[] = memoryControlValues.length > 0 ? memoryControlValues : [undefined];
325
+ const controlSlots: (number | undefined)[] = controlRegister1Values.length > 0 ? controlRegister1Values : [undefined];
326
+
327
+ const combos: RegisterCombo[] = [];
328
+ for (const bankSelect of bankSlots) {
329
+ for (const memoryControl of memorySlots) {
330
+ for (const controlRegister1 of controlSlots) {
331
+ combos.push({ bankSelect, memoryControl, controlRegister1 });
332
+ }
333
+ }
334
+ }
335
+ combos.sort(compareCombo);
336
+
337
+ return combos.map(buildGraphicsMap);
338
+ }