@henols/vice-mcp 0.2.3 → 0.2.4
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/anno-cli.ts +156 -158
- package/anno-confidence.ts +2 -2
- package/anno-derive.ts +6 -6
- package/anno-details.ts +4 -4
- package/anno-export-asm.ts +100 -101
- package/anno-graphics.ts +16 -16
- package/anno-hazard-report.ts +2 -2
- package/anno-import.ts +15 -15
- package/anno-index.ts +8 -8
- package/anno-join.ts +35 -35
- package/anno-memmap-render.ts +22 -21
- package/anno-provenance-ledger.ts +4 -4
- package/anno-regbits-gen.ts +13 -13
- package/anno-store-export.ts +11 -11
- package/anno-store.ts +139 -144
- package/anno-symbols.ts +7 -7
- package/anno-types.ts +55 -55
- package/package.json +1 -1
- package/resources/broker-control.mjs +85 -92
- package/resources/broker-epoch.mjs +6 -7
- package/resources/broker-kill.mjs +29 -30
- package/resources/broker-launch.mjs +352 -370
- package/resources/broker-state.mjs +9 -10
- package/resources/host-tool.mjs +636 -664
- package/resources/vice-broker.mjs +189 -191
- package/vice-broker-client.ts +98 -100
package/anno-graphics.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// anno-graphics.ts
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// Derives the VIC-II graphics areas -- screen
|
|
5
5
|
// matrix, character set or bitmap, and sprite POINTERS -- from RECOVERED VIC
|
|
6
6
|
// register VALUES, never from cross-references. The chip fetches its
|
|
7
7
|
// character/bitmap/screen data by direct memory access, so a character set
|
|
8
8
|
// may be referenced by NO INSTRUCTION ANYWHERE IN THE PROGRAM -- exactly the
|
|
9
9
|
// case a cross-reference-driven join structurally cannot find. That is
|
|
10
|
-
//
|
|
10
|
+
// This module's entire reason to exist: no existing code in
|
|
11
11
|
// this repository decodes these three registers for this purpose -- the
|
|
12
12
|
// stock-backend modules that name them read LIVE emulator state for a
|
|
13
|
-
// different question
|
|
13
|
+
// different question.
|
|
14
14
|
//
|
|
15
15
|
// THIS MODULE TAKES PLAIN REGISTER-WRITE VALUES AND RETURNS PLAIN RANGES --
|
|
16
|
-
// AND NOTHING ELSE
|
|
16
|
+
// AND NOTHING ELSE. It never receives a store handle, never opens
|
|
17
17
|
// or names `node:sqlite`, never imports `anno-store.ts`, `anno-join.ts`, or
|
|
18
18
|
// any module that reads the stored cross-reference graph, and never imports
|
|
19
19
|
// `hostpath.ts`/`containerpath.ts` or the emulator backend
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
// very case this derivation exists to cover -- so its import list is scanned
|
|
23
23
|
// structurally (`anno-graphics.test.ts`) rather than merely reviewed.
|
|
24
24
|
//
|
|
25
|
-
// SPRITE BITMAP LOCATIONS ARE OUT OF SCOPE
|
|
25
|
+
// SPRITE BITMAP LOCATIONS ARE OUT OF SCOPE: this module derives the
|
|
26
26
|
// eight-byte sprite POINTER TABLE range only. The pointer VALUES themselves
|
|
27
27
|
// are program data, usually written at run time, and are not register values
|
|
28
28
|
// a static analysis recovers -- this module must never appear to promise
|
|
29
29
|
// them.
|
|
30
30
|
//
|
|
31
|
-
// A MISSING REGISTER IS A STATED ABSENCE, NEVER A DEFAULT
|
|
31
|
+
// A MISSING REGISTER IS A STATED ABSENCE, NEVER A DEFAULT: where a
|
|
32
32
|
// required register value was never recovered, the ranges that depend on it
|
|
33
33
|
// are omitted from that map and the map names the missing register in
|
|
34
34
|
// `missingRegisters`. Power-on defaults exist and are well known, which is
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
// recovered value, and this phase exists to prevent exactly that class of
|
|
38
38
|
// confident wrong output.
|
|
39
39
|
//
|
|
40
|
-
// SEVERAL COMBINATIONS ARE SEVERAL MAPS, NEVER ONE MERGED MAP
|
|
40
|
+
// SEVERAL COMBINATIONS ARE SEVERAL MAPS, NEVER ONE MERGED MAP: a
|
|
41
41
|
// program that reprograms bank, screen or charset per raster split has
|
|
42
42
|
// several valid graphics maps, and a single derived map would be wrong for
|
|
43
|
-
// all but one -- the same path-dependence limit
|
|
43
|
+
// all but one -- the same path-dependence limit this project's own processor-port derivation names for the
|
|
44
44
|
// processor port. `deriveGraphicsRanges()` returns one map per DISTINCT
|
|
45
45
|
// combination of the three registers' own recovered values: the cross
|
|
46
46
|
// product of each register's own distinct-value set, with a register that
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
// tuple order (missing sorts first), so two runs over the same facts return
|
|
50
50
|
// deeply equal arrays.
|
|
51
51
|
//
|
|
52
|
-
// NO GRAPHICS-SPECIFIC DATA TYPE EXISTS
|
|
52
|
+
// NO GRAPHICS-SPECIFIC DATA TYPE EXISTS: the store's `DataType`
|
|
53
53
|
// vocabulary (`anno-types.ts`) is a frozen twelve with no graphics-specific
|
|
54
54
|
// member, and widening it is a schema decision no plan in this phase makes --
|
|
55
55
|
// widening it would require re-auditing every consumer of that frozen list,
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
// (READ-ONLY, NEVER IMPORTED): this module's bank/screen/charset/bitmap
|
|
62
62
|
// arithmetic was checked by hand against those two files' existing, tested
|
|
63
63
|
// decode of the SAME three registers for the live-emulator backend, and
|
|
64
|
-
// found to agree exactly
|
|
64
|
+
// found to agree exactly.
|
|
65
65
|
// This module REIMPLEMENTS that arithmetic rather than importing it, because
|
|
66
66
|
// importing either file would reach the emulator backend's own transport
|
|
67
67
|
// seam, which this module must never touch.
|
|
@@ -126,7 +126,7 @@ export const SPRITE_POINTER_OFFSET = 0x3f8;
|
|
|
126
126
|
const SPRITE_POINTER_SIZE = 8;
|
|
127
127
|
|
|
128
128
|
/** The existing, byte-shaped `DataType` every range this module emits
|
|
129
|
-
* carries
|
|
129
|
+
* carries. */
|
|
130
130
|
const GRAPHICS_DATA_TYPE: DataType = "byte";
|
|
131
131
|
|
|
132
132
|
/** One recovered register write, in the same shape `ConstWriteFact` in
|
|
@@ -212,7 +212,7 @@ function distinctValuesFor(facts: readonly GraphicsConstWriteFact[], targetAddre
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
/** One combination of the three registers' own recovered values --
|
|
215
|
-
* `undefined` names a register with zero recovered facts
|
|
215
|
+
* `undefined` names a register with zero recovered facts. */
|
|
216
216
|
interface RegisterCombo {
|
|
217
217
|
bankSelect: number | undefined;
|
|
218
218
|
memoryControl: number | undefined;
|
|
@@ -220,7 +220,7 @@ interface RegisterCombo {
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
/** Ascending, with `undefined` (missing) sorting first -- a stated,
|
|
223
|
-
* deterministic order over the tuple
|
|
223
|
+
* deterministic order over the tuple, not "whatever `Set`
|
|
224
224
|
* iteration happened to produce". */
|
|
225
225
|
function compareSlot(a: number | undefined, b: number | undefined): number {
|
|
226
226
|
const av = a ?? -1;
|
|
@@ -304,13 +304,13 @@ function buildGraphicsMap(combo: RegisterCombo): GraphicsMap {
|
|
|
304
304
|
|
|
305
305
|
/**
|
|
306
306
|
* Derives the graphics areas -- screen matrix, character set or bitmap, and
|
|
307
|
-
* sprite pointers -- from recovered VIC register values ALONE
|
|
307
|
+
* sprite pointers -- from recovered VIC register values ALONE.
|
|
308
308
|
* Returns one `GraphicsMap` per distinct combination of the three registers'
|
|
309
|
-
* own recovered values
|
|
309
|
+
* own recovered values, in ascending tuple order, so two calls
|
|
310
310
|
* with the same `facts` return deeply equal arrays. A register with zero
|
|
311
311
|
* recovered facts contributes a single "missing" combination slot rather
|
|
312
312
|
* than an axis; every range depending on it is omitted from every map, and
|
|
313
|
-
* the map names it in `missingRegisters
|
|
313
|
+
* the map names it in `missingRegisters`. `facts` carrying values
|
|
314
314
|
* for addresses other than the three this module watches (e.g. the
|
|
315
315
|
* processor port, $0001) are ignored -- this module only ever reads writes
|
|
316
316
|
* to `BANK_SELECT_ADDRESS`/`MEMORY_CONTROL_ADDRESS`/`CONTROL_REGISTER_1_ADDRESS`.
|
package/anno-hazard-report.ts
CHANGED
|
@@ -233,8 +233,8 @@ export interface HazardLimit {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
|
-
* The two limits
|
|
237
|
-
* remaining entries
|
|
236
|
+
* The two limits seeded here; later work adds the
|
|
237
|
+
* remaining entries -- this array only ever grows.
|
|
238
238
|
*/
|
|
239
239
|
export const HAZARD_LIMITS: readonly HazardLimit[] = Object.freeze([
|
|
240
240
|
{
|
package/anno-import.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// anno-import.ts
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// The container-side parser and importer for `GhidraStructExport.java`'s
|
|
5
|
+
// `## `-delimited transfer file.
|
|
6
6
|
//
|
|
7
7
|
// THIS MODULE RECEIVES AN ALREADY-OPEN STORE HANDLE. It never opens or closes
|
|
8
8
|
// a store itself -- there is no second store session anywhere in this file.
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
// WHAT THIS IS THE ONE AUTHORITATIVE PLACE FOR: parsing the export's fixed
|
|
19
19
|
// `## `-delimited section format, mapping Ghidra's `Reference.getReferenceType()`
|
|
20
20
|
// vocabulary onto the store's frozen four-member `XrefAccessKind`
|
|
21
|
-
// (`GHIDRA_REFTYPE_TO_ACCESS_KIND
|
|
21
|
+
// (`GHIDRA_REFTYPE_TO_ACCESS_KIND`), and the digest-then-delete
|
|
22
22
|
// discipline that makes the transfer file transient evidence rather than a
|
|
23
|
-
// second on-disk model
|
|
23
|
+
// second on-disk model.
|
|
24
24
|
//
|
|
25
25
|
// WHAT NOT TO DO:
|
|
26
26
|
// - Never write a row before the WHOLE document has parsed successfully.
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
// member of `XREF_ACCESS_KINDS`. It is dropped and COUNTED in
|
|
38
38
|
// `kindsSeenNotImported`, never silently absorbed and never refused --
|
|
39
39
|
// refusing on an ordinary `JSR`/`JMP` reference would make the importer
|
|
40
|
-
// unusable against a real corpus binary
|
|
40
|
+
// unusable against a real corpus binary.
|
|
41
41
|
|
|
42
42
|
import { createHash } from "node:crypto";
|
|
43
43
|
// Namespace import, deliberately: a later acceptance gate greps this file for
|
|
@@ -95,11 +95,11 @@ export interface GhidraExportDocument {
|
|
|
95
95
|
* hand-written test fixture using either convention keeps working.
|
|
96
96
|
*
|
|
97
97
|
* Discovered as a LIVE, previously-untested defect in the REFERENCES import
|
|
98
|
-
* path
|
|
98
|
+
* path: every prior test used a hand-written, `$`-prefixed
|
|
99
99
|
* transfer file, never a token shaped exactly as Ghidra itself renders one
|
|
100
100
|
* -- `importGhidraExport()` would refuse EVERY real captured export outright.
|
|
101
101
|
* Fixed here, in the SAME function `## CONST_WRITES`'s own tokens (which are
|
|
102
|
-
* ALSO bare hex
|
|
102
|
+
* ALSO bare hex) need the identical treatment for.
|
|
103
103
|
*/
|
|
104
104
|
function parseGhidraAddressToken(token: string, what: string): number {
|
|
105
105
|
if (token.startsWith("$") || /^0[xX]/.test(token)) {
|
|
@@ -245,7 +245,7 @@ export function parseGhidraExport(text: string): GhidraExportDocument {
|
|
|
245
245
|
};
|
|
246
246
|
checkTrailerCount("REFERENCES", "REFERENCE_COUNT");
|
|
247
247
|
checkTrailerCount("CLASSIFICATION", "CLASSIFICATION_LINES");
|
|
248
|
-
//
|
|
248
|
+
// The SAME generic trailer-count check, extended to
|
|
249
249
|
// `## CONST_WRITES` / `## CONST_WRITES_COUNT`. Note what this generic
|
|
250
250
|
// grammar already does with the exporter's own `## CONST_WRITES_NONE`
|
|
251
251
|
// marker line: because every `## `-prefixed line with no following space
|
|
@@ -262,7 +262,7 @@ export function parseGhidraExport(text: string): GhidraExportDocument {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
/**
|
|
265
|
-
*
|
|
265
|
+
* The frozen mapping from Ghidra's `Reference.getReferenceType()`
|
|
266
266
|
* vocabulary onto the store's four-member `XrefAccessKind`. A token absent
|
|
267
267
|
* from this table is dropped and COUNTED (`kindsSeenNotImported`), never
|
|
268
268
|
* refused and never guessed -- a real corpus binary carries ordinary jump and
|
|
@@ -277,7 +277,7 @@ export const GHIDRA_REFTYPE_TO_ACCESS_KIND: Readonly<Record<string, XrefAccessKi
|
|
|
277
277
|
COMPUTED_CALL: "COMPUTED_JUMP",
|
|
278
278
|
});
|
|
279
279
|
|
|
280
|
-
/**
|
|
280
|
+
/** The watched-address set, mirroring the Java constant
|
|
281
281
|
* `CONST_WRITE_WATCHED_ADDRESSES` in `GhidraStructExport.java` byte-for-byte
|
|
282
282
|
* -- the two MUST be kept in step. `parseConstWrites()` below does NOT
|
|
283
283
|
* filter its own output against this list: a `## CONST_WRITES` line for an
|
|
@@ -299,8 +299,8 @@ export interface ConstWriteFact {
|
|
|
299
299
|
|
|
300
300
|
/**
|
|
301
301
|
* Turns the `## CONST_WRITES` section `parseGhidraExport()` already parsed
|
|
302
|
-
* into `(storeAddress, targetAddress, value)` facts (
|
|
303
|
-
*
|
|
302
|
+
* into `(storeAddress, targetAddress, value)` facts. `parseGhidraExport()`
|
|
303
|
+
* has ALREADY refused, before
|
|
304
304
|
* this function is ever called, if a declared `## CONST_WRITES_COUNT`
|
|
305
305
|
* trailer disagrees with the section's own parsed body-line count -- the
|
|
306
306
|
* SAME generic check `## REFERENCE_COUNT`/`## CLASSIFICATION_LINES` already
|
|
@@ -357,10 +357,10 @@ export interface ImportCounts {
|
|
|
357
357
|
* still reported as a success with this reason attached, never as a
|
|
358
358
|
* failure after a durable write. */
|
|
359
359
|
transferDeleteError?: string;
|
|
360
|
-
/**
|
|
360
|
+
/** The `## CONST_WRITES` section's own facts, parsed by
|
|
361
361
|
* `parseConstWrites()` BEFORE the transfer file is deleted below -- the
|
|
362
362
|
* ONE artifact carrying them. `importGhidraExport()` never persists these
|
|
363
|
-
* facts in the store (the reserved `bank` column stays null
|
|
363
|
+
* facts in the store (the reserved `bank` column stays null);
|
|
364
364
|
* they ride on THIS return value instead, so a caller can hand the SAME
|
|
365
365
|
* array straight to `anno_join_memmap`'s own `const_writes` argument in a
|
|
366
366
|
* following call, closing the loop `anno-tools.ts`'s `dispatchJoinMemmap()`
|
|
@@ -429,7 +429,7 @@ export function importGhidraExport(handle: AnnoStoreHandle, args: ImportGhidraEx
|
|
|
429
429
|
|
|
430
430
|
const doc = parseGhidraExport(contents.toString("utf8"));
|
|
431
431
|
const referenceLines = doc.sections.get("REFERENCES") ?? [];
|
|
432
|
-
//
|
|
432
|
+
// Parsed here, from the SAME document, before the
|
|
433
433
|
// transfer file is deleted below -- `parseConstWrites()` was previously
|
|
434
434
|
// exercised only by test code (`anno-join.test.ts`/`ghidra-live.test.ts`),
|
|
435
435
|
// never by this, the only production entry point that reads a transfer
|
package/anno-index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// The ONE place that answers "which annotated range owns this address" --
|
|
5
5
|
// a pure, narrowest-range-wins paint index over the whole 64K address space,
|
|
6
|
-
// rebuilt from rows and never maintained incrementally
|
|
6
|
+
// rebuilt from rows and never maintained incrementally.
|
|
7
7
|
//
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
// WHY THIS FILE EXISTS
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
// an argument is the only shape that can be tested for it.
|
|
38
38
|
// 2. NEVER cache this index on disk. A cached index is a second truth that
|
|
39
39
|
// can disagree with the range table -- exactly the failure the
|
|
40
|
-
// derived-from-bytes coverage census
|
|
40
|
+
// derived-from-bytes coverage census exists to make impossible.
|
|
41
41
|
// Rebuild it; see the measurement above.
|
|
42
|
-
// 3. NEVER add an adjacency, coalescing or merge pass.
|
|
43
|
-
// ranges AS ranges and never merges them, so there is no
|
|
44
|
-
// primitive here to introduce. Merging is
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// documented as hazards to remember.
|
|
42
|
+
// 3. NEVER add an adjacency, coalescing or merge pass. This project's own
|
|
43
|
+
// store stores ranges AS ranges and never merges them, so there is no
|
|
44
|
+
// splitter primitive here to introduce. Merging is a named blocker for
|
|
45
|
+
// decomposition-to-closure work and the predicted over-merge bias
|
|
46
|
+
// behind the coverage census; all three are designed out by
|
|
47
|
+
// construction here, not documented as hazards to remember.
|
|
48
48
|
// 4. NEVER maintain the index incrementally. An incremental update has to
|
|
49
49
|
// know what the row it is removing was covering UNDERNEATH, which the
|
|
50
50
|
// index cannot say -- a painted cell records the winner, not the losers.
|
package/anno-join.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// anno-join.ts
|
|
3
3
|
//
|
|
4
|
-
//
|
|
4
|
+
// The mechanical join between stored
|
|
5
5
|
// cross-references and `memmap.json`, with no agent call, no queue walk and
|
|
6
6
|
// no skill invocation anywhere in the loop.
|
|
7
7
|
//
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
// WHAT THIS IS THE ONE AUTHORITATIVE PLACE FOR: reading `listXrefs()`'s
|
|
18
18
|
// distinct target addresses, classifying each one (inside the loaded image,
|
|
19
19
|
// no `memmap.json` entry, or annotated) and writing the resulting comment
|
|
20
|
-
// through `setComment()`.
|
|
20
|
+
// through `setComment()`. This module's own criterion -- no agent, no queue
|
|
21
21
|
// walk, no skill invocation -- is checked STRUCTURALLY over this module's own
|
|
22
|
-
// source in `anno-join.test.ts
|
|
22
|
+
// source in `anno-join.test.ts`, not merely asserted here in
|
|
23
23
|
// prose.
|
|
24
24
|
//
|
|
25
25
|
// WHAT NOT TO DO:
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
// that decodes to more than one region, DECLINES -- see the bank-state
|
|
34
34
|
// block below.
|
|
35
35
|
//
|
|
36
|
-
//
|
|
36
|
+
// `runMemmapJoin()` gains an
|
|
37
37
|
// OPTIONAL `constWrites` argument. When `undefined` (every pre-37-06 call
|
|
38
38
|
// site, and every existing test in this file), the bank-state machinery
|
|
39
39
|
// below is a complete no-op and every address resolves EXACTLY as it did
|
|
40
40
|
// before this plan -- the candidate-constraint argument must not change any
|
|
41
41
|
// unconstrained selection's answer. Only when a caller explicitly supplies
|
|
42
42
|
// an array (even an empty one) does the reaching-values/decline logic
|
|
43
|
-
// activate for addresses inside `BANK_CONDITIONAL_RANGES`.
|
|
43
|
+
// activate for addresses inside `BANK_CONDITIONAL_RANGES`. The
|
|
44
44
|
// reaching-values computation is deliberately conservative -- it is NOT a
|
|
45
45
|
// dataflow analysis. It uses only what the export gives: the recovered
|
|
46
46
|
// constant stores to the processor port (each with its own address), and
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
// `constWrites` whose OWN store address can reach that address, walked
|
|
50
50
|
// forward over that same graph. Where the graph does not connect a store to
|
|
51
51
|
// the address at all, that value is simply not in the reaching set --
|
|
52
|
-
//
|
|
52
|
+
// the decline-on-empty-or-disagreement rule (below) is what turns
|
|
53
53
|
// "nothing reaches this point" into a stated absence rather than a silent
|
|
54
54
|
// default.
|
|
55
55
|
|
|
@@ -66,30 +66,29 @@ import type { ConstWriteFact } from "./anno-import.ts";
|
|
|
66
66
|
import type { ContradictedComment, SplitTableReinterpretation, XrefRow } from "./anno-types.ts";
|
|
67
67
|
import { loadMemmap, memmapDigest, PROVENANCE_TOKEN_PREFIX, selectMemmapEntry } from "./memmap-lookup.ts";
|
|
68
68
|
import type { MemmapEntry, MemmapSelection } from "./memmap-lookup.ts";
|
|
69
|
-
//
|
|
69
|
+
// The graphics write-back. deriveGraphicsRanges()
|
|
70
70
|
// is structurally typed against ConstWriteFact -- GraphicsConstWriteFact's own
|
|
71
71
|
// shape is identical ({storeAddress, targetAddress, value}) -- so THIS module's
|
|
72
|
-
// own constWrites argument, already threaded for
|
|
73
|
-
// handed straight through with no translation layer
|
|
74
|
-
// "Next Phase Readiness" note names this directly).
|
|
72
|
+
// own constWrites argument, already threaded for the bank-state block, is
|
|
73
|
+
// handed straight through with no translation layer.
|
|
75
74
|
import { deriveGraphicsRanges } from "./anno-graphics.ts";
|
|
76
75
|
import type { GraphicsMap } from "./anno-graphics.ts";
|
|
77
76
|
|
|
78
|
-
/**
|
|
77
|
+
/** The axis-qualified provenance marker an annotated bank-conditional
|
|
79
78
|
* comment carries, ALWAYS before `PROVENANCE_TOKEN_PREFIX`'s own digest
|
|
80
|
-
* token (which stays last
|
|
79
|
+
* token (which stays last). Names the axis ("processor-port")
|
|
81
80
|
* explicitly, so a later phase adding the VIC banking axis is additive
|
|
82
81
|
* rather than ambiguous about which axis a given token names. */
|
|
83
82
|
const BANK_PROVENANCE_PREFIX = "[processor-port:";
|
|
84
83
|
|
|
85
84
|
/**
|
|
86
|
-
* The reaching-values computation's own result shape
|
|
85
|
+
* The reaching-values computation's own result shape: exactly one
|
|
87
86
|
* recovered value reaches the address; several do (an array -- length 0
|
|
88
87
|
* means none reach at all, length 2+ means genuine disagreement-or-agreement
|
|
89
88
|
* to resolve); or the computation cannot decide at all. Nothing in this
|
|
90
89
|
* project's current data model (a plain directed graph of already-resolved
|
|
91
90
|
* `XrefRow`s) can currently produce `"unknown"` -- there is no signal here
|
|
92
|
-
* for a dropped or unresolved reference (
|
|
91
|
+
* for a dropped or unresolved reference (a stated limit of this computation) -- but
|
|
93
92
|
* the shape is kept complete for a future importer that DOES emit such a
|
|
94
93
|
* signal, rather than silently folding that case into "empty".
|
|
95
94
|
*/
|
|
@@ -126,7 +125,7 @@ function canReach(from: number, target: number, adjacency: Map<number, number[]>
|
|
|
126
125
|
return false;
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
/**
|
|
128
|
+
/** The set of recovered processor-port values whose OWN store
|
|
130
129
|
* address can reach `targetAddress`, over `adjacency`. Never a dataflow
|
|
131
130
|
* analysis -- purely "does the stored cross-reference graph connect this
|
|
132
131
|
* store to this address". */
|
|
@@ -156,8 +155,8 @@ export class AnnoJoinError extends Error {
|
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
/** What one `runMemmapJoin()` call reports. `addressesConsidered` is always
|
|
159
|
-
* the sum of the next four fields. The three `graphics*` fields
|
|
160
|
-
*
|
|
158
|
+
* the sum of the next four fields. The three `graphics*` fields are always
|
|
159
|
+
* present and `0` when `constWrites` is omitted or when
|
|
161
160
|
* the selected map derives zero ranges -- never absent, so a caller reads
|
|
162
161
|
* them unconditionally instead of guarding on them, mirroring
|
|
163
162
|
* `SetDataTypeResult`'s own "always present, often empty" convention for
|
|
@@ -175,14 +174,14 @@ export interface JoinCounts {
|
|
|
175
174
|
}
|
|
176
175
|
|
|
177
176
|
/**
|
|
178
|
-
* What the graphics write-back
|
|
177
|
+
* What the graphics write-back reports, in full --
|
|
179
178
|
* `JoinCounts`'s own `graphics*` fields are the COUNTS of these same
|
|
180
179
|
* `contradictedComments`/`reinterpretedSplitTables` arrays; this record
|
|
181
180
|
* carries the disclosures themselves so neither is dropped (must_haves.truths:
|
|
182
181
|
* "the join's returned counts include the contradicted-comment and
|
|
183
182
|
* fragmented-split-table disclosures the range write reported; neither is
|
|
184
183
|
* dropped"). `mapIndex` records WHICH of `deriveGraphicsRanges()`'s several
|
|
185
|
-
* maps was written --
|
|
184
|
+
* maps was written -- this project's own rule (several valid combinations are
|
|
186
185
|
* several maps, never one merged map) means writing more than one would
|
|
187
186
|
* write mutually-contradicting ranges into the SAME store, so exactly one is
|
|
188
187
|
* ever written and this field is the record of which. */
|
|
@@ -206,7 +205,7 @@ export interface JoinDecision {
|
|
|
206
205
|
export interface RunMemmapJoinArgs {
|
|
207
206
|
imageOrigin: number;
|
|
208
207
|
imageByteLength: number;
|
|
209
|
-
/**
|
|
208
|
+
/** The recovered `$01` const-write facts this run
|
|
210
209
|
* has evidence for, typically `parseConstWrites()`'s own output over one
|
|
211
210
|
* imported export. `undefined` (every pre-37-06 call site) means "this run
|
|
212
211
|
* carries no bank-state evidence at all" -- the bank-state machinery is a
|
|
@@ -214,7 +213,7 @@ export interface RunMemmapJoinArgs {
|
|
|
214
213
|
* An explicit array (even `[]`) activates it for addresses inside
|
|
215
214
|
* `BANK_CONDITIONAL_RANGES`. */
|
|
216
215
|
constWrites?: readonly ConstWriteFact[];
|
|
217
|
-
/**
|
|
216
|
+
/** Which of `deriveGraphicsRanges()`'s
|
|
218
217
|
* several maps to write back, when `constWrites` derives more than one
|
|
219
218
|
* distinct register-value combination. Defaults to `0`. Consulted ONLY
|
|
220
219
|
* when `constWrites` is supplied AT ALL (the SAME gate that activates the
|
|
@@ -230,8 +229,9 @@ export interface RunMemmapJoinArgs {
|
|
|
230
229
|
* in ascending address order. An address inside the caller's own loaded
|
|
231
230
|
* image range is a program address, not a hardware/memory-map feature, and
|
|
232
231
|
* is skipped WITHOUT a `memmap.json` lookup -- the membership test runs
|
|
233
|
-
* BEFORE `selectEntry()` is called at all
|
|
234
|
-
* control-flow fact rather than a result-filtering one:
|
|
232
|
+
* BEFORE `selectEntry()` is called at all, so the guard is a
|
|
233
|
+
* control-flow fact rather than a result-filtering one: this file's own
|
|
234
|
+
* contract says an
|
|
235
235
|
* in-image address is "never looked up in memmap.json", a claim about what
|
|
236
236
|
* runs, not merely about what the caller sees back. An address with no
|
|
237
237
|
* containing `memmap.json` entry is skipped for that reason instead;
|
|
@@ -262,7 +262,7 @@ export function runMemmapJoin(
|
|
|
262
262
|
);
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
//
|
|
265
|
+
// Computed ONCE per join run and reused for every annotated row,
|
|
266
266
|
// never recomputed per row -- two comments written in the same run are
|
|
267
267
|
// therefore GUARANTEED to carry byte-identical tokens, not merely likely
|
|
268
268
|
// to (the file cannot change mid-run, but a per-row recompute would still
|
|
@@ -272,7 +272,7 @@ export function runMemmapJoin(
|
|
|
272
272
|
const xrefs = listXrefs(handle);
|
|
273
273
|
const targets = [...new Set(xrefs.map((xref) => xref.toAddress))].sort((a, b) => a - b);
|
|
274
274
|
|
|
275
|
-
//
|
|
275
|
+
// Built ONCE per run, over the SAME xref graph the unconstrained
|
|
276
276
|
// path already reads via `listXrefs()` above -- reused for every address's
|
|
277
277
|
// own reachability walk below. `undefined` `args.constWrites` means the
|
|
278
278
|
// bank-state block is never entered at all, so this adjacency map is built
|
|
@@ -282,7 +282,7 @@ export function runMemmapJoin(
|
|
|
282
282
|
const bankAdjacency = buildAdjacency(xrefs);
|
|
283
283
|
|
|
284
284
|
// The inclusive image range, computed ONCE from the LoadedImage's own body
|
|
285
|
-
// bytes
|
|
285
|
+
// bytes -- never from `totalBytes` (the file's own byte count,
|
|
286
286
|
// which on the .prg route includes the two-byte load-address header and
|
|
287
287
|
// would shift this whole range by two bytes) and never a caller-supplied
|
|
288
288
|
// number pair that could silently widen or narrow the program's own
|
|
@@ -300,7 +300,7 @@ export function runMemmapJoin(
|
|
|
300
300
|
for (const address of targets) {
|
|
301
301
|
// THE GUARD: one early-return, before selectEntry() is ever called. A
|
|
302
302
|
// single textual deletion of this block removes it cleanly -- that
|
|
303
|
-
// deletion is
|
|
303
|
+
// deletion is this file's own observed-red control.
|
|
304
304
|
if (address >= imageStart && address <= imageEnd) {
|
|
305
305
|
skippedInImage += 1;
|
|
306
306
|
decisions.push({
|
|
@@ -313,13 +313,13 @@ export function runMemmapJoin(
|
|
|
313
313
|
continue;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
// THE BANK-STATE BLOCK
|
|
316
|
+
// THE BANK-STATE BLOCK. Only
|
|
317
317
|
// entered when the caller supplied `constWrites` AT ALL (`undefined`
|
|
318
318
|
// skips this whole block, falling through to the unconstrained path
|
|
319
319
|
// below exactly as pre-37-06) AND the address is inside one of the
|
|
320
320
|
// three bank-conditional ranges -- outside them, bank state is
|
|
321
321
|
// irrelevant and the candidate set stays unconstrained regardless of
|
|
322
|
-
// `constWrites
|
|
322
|
+
// `constWrites`.
|
|
323
323
|
if (args.constWrites !== undefined && isBankConditionalAddress(address)) {
|
|
324
324
|
const reaching = computeReachingValues(address, args.constWrites, bankAdjacency);
|
|
325
325
|
|
|
@@ -335,7 +335,7 @@ export function runMemmapJoin(
|
|
|
335
335
|
|
|
336
336
|
// Resolves ONE region (or refuses) for a single reaching value, applies
|
|
337
337
|
// it as a candidate constraint BEFORE selection runs (never a
|
|
338
|
-
// post-filter
|
|
338
|
+
// post-filter), and pushes the matching decision. Shared by
|
|
339
339
|
// both the single-value and the several-values-same-region branches
|
|
340
340
|
// below, so the annotate path is written exactly once.
|
|
341
341
|
const annotateUnderRegion = (region: Exclude<BankedRegion, "not_applicable">, bankNote: string): void => {
|
|
@@ -412,7 +412,7 @@ export function runMemmapJoin(
|
|
|
412
412
|
|
|
413
413
|
// The full comment text: the selected entry's label, one space, the
|
|
414
414
|
// provenance prefix, then the full 64-character digest -- always LAST,
|
|
415
|
-
// never truncated
|
|
415
|
+
// never truncated. setComment() -> assertCommentText() refuses
|
|
416
416
|
// (never truncates) a text that overflows MAX_COMMENT_BYTES; that
|
|
417
417
|
// refusal is left to propagate here rather than being pre-checked and
|
|
418
418
|
// silently worked around, because a truncated provenance token would be
|
|
@@ -424,7 +424,7 @@ export function runMemmapJoin(
|
|
|
424
424
|
decisions.push({ address, outcome: "annotated", label: selection.entry.label });
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
// THE GRAPHICS WRITE-BACK
|
|
427
|
+
// THE GRAPHICS WRITE-BACK. Runs AFTER the main
|
|
428
428
|
// per-address loop above, as its own step -- graphics ranges are derived
|
|
429
429
|
// from register VALUES, never from the cross-reference targets the loop
|
|
430
430
|
// above walks, so there is no reason to interleave the two. Gated on the
|
|
@@ -443,10 +443,10 @@ export function runMemmapJoin(
|
|
|
443
443
|
);
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
-
//
|
|
446
|
+
// Write ONLY the selected map's own ranges -- never every map
|
|
447
447
|
// deriveGraphicsRanges() returned. Several distinct register-value
|
|
448
|
-
// combinations describe MUTUALLY CONTRADICTING layouts (
|
|
449
|
-
//
|
|
448
|
+
// combinations describe MUTUALLY CONTRADICTING layouts (the reason
|
|
449
|
+
// several maps exist at all); writing more than one into the
|
|
450
450
|
// same store would write ranges that disagree with each other by
|
|
451
451
|
// construction.
|
|
452
452
|
let rangesWritten = 0;
|