@modernrelay/orbit-core 0.13.6 → 0.14.0
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/dist/{chunk-FG6TQANI.js → chunk-KRCG2JFN.js} +3 -3
- package/dist/chunk-KRCG2JFN.js.map +1 -0
- package/dist/engine.d.ts +1 -1
- package/dist/{index-BoQfS8jd.d.ts → index-CPRTWpGA.d.ts} +175 -175
- package/dist/index.d.ts +611 -611
- package/dist/index.js +145 -146
- package/dist/index.js.map +1 -1
- package/dist/{lane-SZYhcrXT.d.ts → lane-_KPQ1deI.d.ts} +42 -42
- package/dist/testing.d.ts +22 -22
- package/dist/testing.js +15 -15
- package/dist/testing.js.map +1 -1
- package/dist/worker/entry.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-FG6TQANI.js.map +0 -1
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { N as NodeId, a as GraphDiagnostic, b as GraphNode } from './index-
|
|
1
|
+
import { N as NodeId, a as GraphDiagnostic, b as GraphNode } from './index-CPRTWpGA.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* stage-4 non-collapsing clusters — pure derivation, no
|
|
5
5
|
* engine, no DOM, no instance.
|
|
6
6
|
*
|
|
7
|
-
* Contract summary
|
|
7
|
+
* Contract summary:
|
|
8
8
|
* - Clusters are a categorical PARTITION of the current PHYSICAL scene, never
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* a rewrite: `deriveClusters` returns membership only. It synthesizes no
|
|
10
|
+
* super-nodes and no meta-edges, so node and edge counts are identical
|
|
11
|
+
* before and after a cluster spec lands — clusters coexist
|
|
12
|
+
* with the stage-3 group rewrite by construction.
|
|
13
13
|
* - `null` (and any non-string) from `by` means UNCLUSTERED: the slot carries
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* NaN in {@link ClusterDerivation.slotOrdinals} (the engine contract's
|
|
15
|
+
* "not in any cluster" value, mirroring the NaN-position convention).
|
|
16
16
|
* - Missing force centers generate deterministically from the ORDERED cluster
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* - Overlay anchoring must never scan members per frame
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
17
|
+
* keys plus the layout seed. {@link generateClusterCenters} uses
|
|
18
|
+
* only IEEE-754 correctly-rounded operations (+ - * / and integer ops) — no
|
|
19
|
+
* transcendentals — so the same ordered keys and seed produce BIT-IDENTICAL
|
|
20
|
+
* centers on any conforming engine, and a different seed produces different
|
|
21
|
+
* ones.
|
|
22
|
+
* - Overlay anchoring must never scan members per frame:
|
|
23
|
+
* {@link clusterCentroids} is the ONLY member-scanning anchor path and runs
|
|
24
|
+
* at settle (from the single permitted readback) or at commit under a
|
|
25
|
+
* fixed layout. {@link clusterProbe} counts every member visit so tests can
|
|
26
|
+
* pin "zero per-frame member iterations".
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -32,7 +32,7 @@ import { N as NodeId, a as GraphDiagnostic, b as GraphNode } from './index-BoQfS
|
|
|
32
32
|
* O(members) passes); `derivations` counts stage-4 recomputations. Both are
|
|
33
33
|
* unconditional O(1) increments so the instrumented and production paths are
|
|
34
34
|
* identical code. Tests snapshot, act, and compare: a per-frame overlay tick
|
|
35
|
-
* must add ZERO member visits
|
|
35
|
+
* must add ZERO member visits and a stage-5 soft-mask change must
|
|
36
36
|
* add ZERO derivations.
|
|
37
37
|
*/
|
|
38
38
|
declare const clusterProbe: {
|
|
@@ -44,15 +44,15 @@ declare function resetClusterProbe(): void;
|
|
|
44
44
|
/**
|
|
45
45
|
* The layout seed generated cluster centers key on.
|
|
46
46
|
*
|
|
47
|
-
* FINDING (
|
|
47
|
+
* FINDING (investigation, recorded here because it is a contract
|
|
48
48
|
* decision): the core owns NO numeric layout seed in v0.10. The only
|
|
49
|
-
* layout-seeding knob on the
|
|
49
|
+
* layout-seeding knob on the config contract is
|
|
50
50
|
* `EngineConfigUpdate.seedRadius` (the ring radius unknown/NaN positions seed
|
|
51
51
|
* onto) and `instance.ts` never sets it — every adapter defaults it itself
|
|
52
|
-
* (`CosmosEngine` → `spaceSize / 4`).
|
|
52
|
+
* (`CosmosEngine` → `spaceSize / 4`). `LayoutSpec.seed` (the
|
|
53
53
|
* `static` layout's reproducibility seed) is not implemented in v0.10 either.
|
|
54
54
|
* So this constant is the single named place the instance keys generated
|
|
55
|
-
* centers on until
|
|
55
|
+
* centers on until normalized layout object lands and can supply
|
|
56
56
|
* `LayoutSpec.seed` here.
|
|
57
57
|
*/
|
|
58
58
|
declare const DEFAULT_LAYOUT_SEED = 1592594996;
|
|
@@ -64,7 +64,7 @@ declare const DEFAULT_LAYOUT_SEED = 1592594996;
|
|
|
64
64
|
*/
|
|
65
65
|
declare const DEFAULT_CLUSTER_CENTER_RADIUS = 1024;
|
|
66
66
|
/**
|
|
67
|
-
* Deterministic force centers for ORDERED cluster keys
|
|
67
|
+
* Deterministic force centers for ORDERED cluster keys: a
|
|
68
68
|
* low-discrepancy (Halton base 2/3) spread offset per key by a seeded hash,
|
|
69
69
|
* mapped into `[-radius, radius]²`.
|
|
70
70
|
*
|
|
@@ -77,13 +77,13 @@ declare const DEFAULT_CLUSTER_CENTER_RADIUS = 1024;
|
|
|
77
77
|
declare function generateClusterCenters(keys: readonly string[], seed?: number, radius?: number): Float32Array;
|
|
78
78
|
/**
|
|
79
79
|
* Force centers for ordered keys with caller-supplied entries winning:
|
|
80
|
-
* ONLY the missing keys generate
|
|
80
|
+
* ONLY the missing keys generate. A non-finite explicit pair is
|
|
81
81
|
* treated as missing (D4 boundary hygiene — a NaN center would poison the
|
|
82
82
|
* engine's force field).
|
|
83
83
|
*/
|
|
84
84
|
declare function resolveClusterCenters(keys: readonly string[], explicit: ReadonlyMap<string, readonly [number, number]> | undefined, seed?: number, radius?: number): Float32Array;
|
|
85
85
|
interface ClusterDerivation {
|
|
86
|
-
/** Distinct keys in FIRST-ENCOUNTER order over the physical scene (the
|
|
86
|
+
/** Distinct keys in FIRST-ENCOUNTER order over the physical scene (the
|
|
87
87
|
* categorical-domain convention, shared with groupBy). Ordinal i ↔ keys[i]. */
|
|
88
88
|
keys: readonly string[];
|
|
89
89
|
/** key → ordinal (the reverse of `keys`). */
|
|
@@ -92,7 +92,7 @@ interface ClusterDerivation {
|
|
|
92
92
|
membersByKey: ReadonlyMap<string, readonly NodeId[]>;
|
|
93
93
|
/**
|
|
94
94
|
* PHYSICAL slot → cluster ordinal, NaN = unclustered. Length is the caller's
|
|
95
|
-
* `sceneCount`, so synthetic suffix slots
|
|
95
|
+
* `sceneCount`, so synthetic suffix slots are
|
|
96
96
|
* always NaN: aggregates are never members of a cluster. This IS the engine
|
|
97
97
|
* `config.cluster.pointClusters` payload — no copy at the sink.
|
|
98
98
|
*/
|
|
@@ -102,18 +102,18 @@ interface ClusterDerivation {
|
|
|
102
102
|
diagnostic: GraphDiagnostic | null;
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
|
-
*
|
|
105
|
+
* stage-4 derivation over the PHYSICAL prefix of the current scene.
|
|
106
106
|
*
|
|
107
107
|
* `nodes` are the physical rows (post-group-rewrite when a rewrite is live)
|
|
108
108
|
* aligned to scene slots `0..nodes.length-1`; `sceneCount` is the FULL scene
|
|
109
109
|
* point count so the returned `slotOrdinals` covers the synthetic suffix too.
|
|
110
|
-
* Preserves everything and synthesizes nothing
|
|
110
|
+
* Preserves everything and synthesizes nothing.
|
|
111
111
|
*/
|
|
112
112
|
declare function deriveClusters<N>(nodes: readonly GraphNode<N>[], by: (node: GraphNode<N>) => string | null, sceneCount?: number): ClusterDerivation;
|
|
113
113
|
/**
|
|
114
114
|
* Centroids of every cluster from a slot-aligned position buffer — the ONLY
|
|
115
|
-
* member-scanning anchor path
|
|
116
|
-
* permitted
|
|
115
|
+
* member-scanning anchor path. Called at settle (over the single
|
|
116
|
+
* permitted readback) and at commit under a FIXED layout; never per
|
|
117
117
|
* frame. Slots with unknown (NaN) positions are skipped; a cluster with no
|
|
118
118
|
* placeable member keeps its `fallback` entry (its force center), so a label
|
|
119
119
|
* never jumps to the origin.
|
|
@@ -121,7 +121,7 @@ declare function deriveClusters<N>(nodes: readonly GraphNode<N>[], by: (node: Gr
|
|
|
121
121
|
declare function clusterCentroids(slotOrdinals: Float32Array, positions: Float32Array, clusterCount: number, fallback: Float32Array): Float32Array;
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
124
|
+
* Worker-lane wire protocol: envelope codec, epoch guards,
|
|
125
125
|
* consolidated transfer lists, and request-class bookkeeping
|
|
126
126
|
* (guaranteed-vs-throttled). Pure functions and one small class; no Worker
|
|
127
127
|
* construction here — the runtime that OWNS a thread imports this, and the
|
|
@@ -130,17 +130,17 @@ declare function clusterCentroids(slotOrdinals: Float32Array, positions: Float32
|
|
|
130
130
|
*
|
|
131
131
|
* Contract invariants (each pinned by test):
|
|
132
132
|
* - `msgId` is monotonic PER DIRECTION; a reply names the request it answers
|
|
133
|
-
*
|
|
133
|
+
* via `inReplyTo`.
|
|
134
134
|
* - Every envelope carries the model acceptance `epoch` it derived from
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
135
|
+
* (I1 discipline: references do not cross threads — epochs replace them).
|
|
136
|
+
* Results for a superseded epoch are dropped AT THE BOUNDARY, before the
|
|
137
|
+
* acceptance queue ever sees them.
|
|
138
138
|
* - Transfers are CONSOLIDATED: one ArrayBuffer per channel per message,
|
|
139
|
-
*
|
|
139
|
+
* deduplicated (two views over one buffer transfer once).
|
|
140
140
|
* - Request classes: 'guaranteed' requests all complete (structural
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
141
|
+
* derivation); 'throttled' requests coalesce LATEST-WINS per lane key
|
|
142
|
+
* (styling reprojection) — superseding a pending throttled request aborts
|
|
143
|
+
* the old one.
|
|
144
144
|
*/
|
|
145
145
|
type WorkerEntity = 'nodes' | 'edges' | 'scene';
|
|
146
146
|
interface WorkerEnvelope {
|
|
@@ -186,7 +186,7 @@ declare function encodeStringTable(strings: readonly string[]): EncodedStringTab
|
|
|
186
186
|
declare function decodeStringTable(table: EncodedStringTable): string[];
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
|
-
*
|
|
189
|
+
* Main-side worker lane: transport ownership, the request
|
|
190
190
|
* ledger (guaranteed/throttled), and the tri-option factory with the
|
|
191
191
|
* fall-back-to-main contract (`worker-unavailable`, never a crash).
|
|
192
192
|
*
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a5 as GraphEngine, a7 as EngineCapabilities, a6 as EngineCommit, aJ as EngineHostEvents, aK as FitViewOptions, V as ViewportState, aL as EngineDiagnostic } from './index-
|
|
2
|
-
import { d as WorkerTransport, c as WorkerEnvelope } from './lane-
|
|
3
|
-
export { l as clusterProbe, m as resetClusterProbe } from './lane-
|
|
1
|
+
import { a5 as GraphEngine, a7 as EngineCapabilities, a6 as EngineCommit, aJ as EngineHostEvents, aK as FitViewOptions, V as ViewportState, aL as EngineDiagnostic } from './index-CPRTWpGA.js';
|
|
2
|
+
import { d as WorkerTransport, c as WorkerEnvelope } from './lane-_KPQ1deI.js';
|
|
3
|
+
export { l as clusterProbe, m as resetClusterProbe } from './lane-_KPQ1deI.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* FakeEngine — the public headless test seam
|
|
6
|
+
* FakeEngine — the public headless test seam.
|
|
7
7
|
*
|
|
8
8
|
* Implements the full GraphEngine adapter contract with zero DOM/WebGL access
|
|
9
9
|
* so consumer suites can run under node/jsdom. Every public method call is
|
|
@@ -23,17 +23,17 @@ interface FakeEngineOptions {
|
|
|
23
23
|
/** Overrides merged over the defaults (linkPicking/trackedPositions false, rangeUpdates [], simulation true). */
|
|
24
24
|
capabilities?: Partial<EngineCapabilities>;
|
|
25
25
|
/**
|
|
26
|
-
* When true, commits queue instead of applying immediately; `stepFrame
|
|
26
|
+
* When true, commits queue instead of applying immediately; `stepFrame`
|
|
27
27
|
* applies the oldest queued commit. Lets tests observe the lag between the
|
|
28
|
-
* desired-render revision and `appliedRevision
|
|
28
|
+
* desired-render revision and `appliedRevision`.
|
|
29
29
|
*/
|
|
30
30
|
manualFrames?: boolean;
|
|
31
31
|
/**
|
|
32
|
-
* When set, `mount
|
|
32
|
+
* When set, `mount` records the call, does NOT store the host events, and
|
|
33
33
|
* rejects with this error — simulates an engine that cannot initialize.
|
|
34
34
|
*/
|
|
35
35
|
mountError?: Error;
|
|
36
|
-
/** Resolved by `captureScreenshot
|
|
36
|
+
/** Resolved by `captureScreenshot`; default null (unsupported/not ready). */
|
|
37
37
|
screenshot?: Blob | null;
|
|
38
38
|
}
|
|
39
39
|
/** One recorded public method invocation. */
|
|
@@ -45,7 +45,7 @@ declare class FakeEngine implements GraphEngine {
|
|
|
45
45
|
readonly capabilities: EngineCapabilities;
|
|
46
46
|
/** Every public method call, in order. */
|
|
47
47
|
readonly calls: RecordedCall[];
|
|
48
|
-
/** Every commit passed to `commit
|
|
48
|
+
/** Every commit passed to `commit`, in call order (queued or applied). */
|
|
49
49
|
readonly commits: EngineCommit[];
|
|
50
50
|
/** The fitView/zoom/setViewport/zoomToIndex entries of `calls`, in order. */
|
|
51
51
|
readonly cameraCalls: RecordedCall[];
|
|
@@ -53,32 +53,32 @@ declare class FakeEngine implements GraphEngine {
|
|
|
53
53
|
private readonly mountError;
|
|
54
54
|
private readonly screenshot;
|
|
55
55
|
private events;
|
|
56
|
-
/** Auto-advancing activity clock for emitFrame
|
|
56
|
+
/** Auto-advancing activity clock for emitFrame/stepFrame (ms). */
|
|
57
57
|
private frameClock;
|
|
58
58
|
private destroyedFlag;
|
|
59
59
|
private viewport;
|
|
60
60
|
private applied;
|
|
61
61
|
private positions;
|
|
62
62
|
/** Links of the most recent structure-bearing commit (commit-time, even in
|
|
63
|
-
* manualFrames mode) — the adjacency source for neighborIndices
|
|
63
|
+
* manualFrames mode) — the adjacency source for neighborIndices. */
|
|
64
64
|
private lastLinks;
|
|
65
65
|
private pinned;
|
|
66
66
|
private selected;
|
|
67
|
-
/** Commits awaiting stepFrame
|
|
67
|
+
/** Commits awaiting stepFrame in manualFrames mode (oldest first). */
|
|
68
68
|
private readonly pendingFrames;
|
|
69
69
|
constructor(options?: FakeEngineOptions);
|
|
70
70
|
/** Most recent commit, if any. */
|
|
71
71
|
get lastCommit(): EngineCommit | undefined;
|
|
72
72
|
get destroyed(): boolean;
|
|
73
|
-
/** Last setPinnedIndices
|
|
73
|
+
/** Last setPinnedIndices value (null = unpinned / never pinned). */
|
|
74
74
|
get pinnedIndices(): readonly number[] | null;
|
|
75
|
-
/** Last setSelectedIndices
|
|
76
|
-
*
|
|
75
|
+
/** Last setSelectedIndices value (null = cleared / never selected) — the
|
|
76
|
+
* highlight mirror of {@link pinnedIndices}. */
|
|
77
77
|
get selectedIndices(): readonly number[] | null;
|
|
78
78
|
/**
|
|
79
79
|
* Structure of the most recent structure-bearing commit — the geometry the
|
|
80
|
-
* engine currently holds. Buffer channels commit independently of structure
|
|
81
|
-
*
|
|
80
|
+
* engine currently holds. Buffer channels commit independently of structure,
|
|
81
|
+
* so the newest commit often carries none.
|
|
82
82
|
*/
|
|
83
83
|
get lastStructure(): NonNullable<EngineCommit['structure']> | undefined;
|
|
84
84
|
/**
|
|
@@ -122,9 +122,9 @@ declare class FakeEngine implements GraphEngine {
|
|
|
122
122
|
destroy(): void;
|
|
123
123
|
/**
|
|
124
124
|
* manualFrames mode: apply the oldest queued commit, making its revision
|
|
125
|
-
* visible via appliedRevision
|
|
125
|
+
* visible via appliedRevision. No-op on the queue when empty. A drawn
|
|
126
126
|
* frame is also an activity-clock sample, so a mounted engine gets an
|
|
127
|
-
* `onFrame` tick
|
|
127
|
+
* `onFrame` tick.
|
|
128
128
|
*/
|
|
129
129
|
stepFrame(): void;
|
|
130
130
|
/**
|
|
@@ -162,7 +162,7 @@ declare class FakeEngine implements GraphEngine {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/**
|
|
165
|
-
*
|
|
165
|
+
* requestAnimationFrame audit — the quiescence instrument.
|
|
166
166
|
*
|
|
167
167
|
* Wraps a window's `requestAnimationFrame` and counts SCHEDULING calls
|
|
168
168
|
* (registrations) separately from delivered ticks: the stop-at-rest claim is
|
|
@@ -171,7 +171,7 @@ declare class FakeEngine implements GraphEngine {
|
|
|
171
171
|
* still re-arming itself.
|
|
172
172
|
*
|
|
173
173
|
* Used three ways: core/react quiescence tests (install, drive the instance,
|
|
174
|
-
* assert `registrations
|
|
174
|
+
* assert `registrations === 0` over a settled window), the demo e2e's
|
|
175
175
|
* page-side counterpart (same shape, injected via addInitScript), and the
|
|
176
176
|
* dev-mode runtime assertion in the instance. The M0 spike's instrument.ts
|
|
177
177
|
* pioneered the wrapper; this is its reusable, uninstallable form.
|
|
@@ -197,7 +197,7 @@ interface RafAudit {
|
|
|
197
197
|
declare function installRafAudit(win?: Window): RafAudit;
|
|
198
198
|
|
|
199
199
|
/**
|
|
200
|
-
* In-process worker transport double
|
|
200
|
+
* In-process worker transport double — drives the REAL
|
|
201
201
|
* runtime module through the REAL codec, asynchronously (microtask), with
|
|
202
202
|
* REAL transfer semantics: payloads round through `structuredClone` with
|
|
203
203
|
* the declared transfer list, so a wrong transfer set detaches something
|
package/dist/testing.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { EnvelopeSequencer, isWellFormedEnvelope, decodeStringTable, acceptColumnar, collectTransfers } from './chunk-
|
|
2
|
-
export { clusterProbe, resetClusterProbe } from './chunk-
|
|
1
|
+
import { EnvelopeSequencer, isWellFormedEnvelope, decodeStringTable, acceptColumnar, collectTransfers } from './chunk-KRCG2JFN.js';
|
|
2
|
+
export { clusterProbe, resetClusterProbe } from './chunk-KRCG2JFN.js';
|
|
3
3
|
|
|
4
4
|
// src/testing/FakeEngine.ts
|
|
5
5
|
var DEFAULT_CAPABILITIES = {
|
|
@@ -7,8 +7,8 @@ var DEFAULT_CAPABILITIES = {
|
|
|
7
7
|
rangeUpdates: [],
|
|
8
8
|
trackedPositions: false,
|
|
9
9
|
simulation: true,
|
|
10
|
-
//
|
|
11
|
-
// when a test calls stepFrame
|
|
10
|
+
// the FakeEngine schedules nothing on its own — frames exist only
|
|
11
|
+
// when a test calls stepFrame/emitFrame — so it is quiescent by
|
|
12
12
|
// construction, and those calls apply-then-emit, which is genuinely
|
|
13
13
|
// post-draw.
|
|
14
14
|
idleFrames: "stops",
|
|
@@ -37,7 +37,7 @@ var FakeEngine = class {
|
|
|
37
37
|
capabilities;
|
|
38
38
|
/** Every public method call, in order. */
|
|
39
39
|
calls = [];
|
|
40
|
-
/** Every commit passed to `commit
|
|
40
|
+
/** Every commit passed to `commit`, in call order (queued or applied). */
|
|
41
41
|
commits = [];
|
|
42
42
|
/** The fitView/zoom/setViewport/zoomToIndex entries of `calls`, in order. */
|
|
43
43
|
cameraCalls = [];
|
|
@@ -45,18 +45,18 @@ var FakeEngine = class {
|
|
|
45
45
|
mountError;
|
|
46
46
|
screenshot;
|
|
47
47
|
events = null;
|
|
48
|
-
/** Auto-advancing activity clock for emitFrame
|
|
48
|
+
/** Auto-advancing activity clock for emitFrame/stepFrame (ms). */
|
|
49
49
|
frameClock = 0;
|
|
50
50
|
destroyedFlag = false;
|
|
51
51
|
viewport = { x: 0, y: 0, zoom: 1 };
|
|
52
52
|
applied = null;
|
|
53
53
|
positions = null;
|
|
54
54
|
/** Links of the most recent structure-bearing commit (commit-time, even in
|
|
55
|
-
* manualFrames mode) — the adjacency source for neighborIndices
|
|
55
|
+
* manualFrames mode) — the adjacency source for neighborIndices. */
|
|
56
56
|
lastLinks = null;
|
|
57
57
|
pinned = null;
|
|
58
58
|
selected = null;
|
|
59
|
-
/** Commits awaiting stepFrame
|
|
59
|
+
/** Commits awaiting stepFrame in manualFrames mode (oldest first). */
|
|
60
60
|
pendingFrames = [];
|
|
61
61
|
constructor(options = {}) {
|
|
62
62
|
this.capabilities = { ...DEFAULT_CAPABILITIES, ...options.capabilities };
|
|
@@ -71,19 +71,19 @@ var FakeEngine = class {
|
|
|
71
71
|
get destroyed() {
|
|
72
72
|
return this.destroyedFlag;
|
|
73
73
|
}
|
|
74
|
-
/** Last setPinnedIndices
|
|
74
|
+
/** Last setPinnedIndices value (null = unpinned / never pinned). */
|
|
75
75
|
get pinnedIndices() {
|
|
76
76
|
return this.pinned;
|
|
77
77
|
}
|
|
78
|
-
/** Last setSelectedIndices
|
|
79
|
-
*
|
|
78
|
+
/** Last setSelectedIndices value (null = cleared / never selected) — the
|
|
79
|
+
* highlight mirror of {@link pinnedIndices}. */
|
|
80
80
|
get selectedIndices() {
|
|
81
81
|
return this.selected;
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
84
|
* Structure of the most recent structure-bearing commit — the geometry the
|
|
85
|
-
* engine currently holds. Buffer channels commit independently of structure
|
|
86
|
-
*
|
|
85
|
+
* engine currently holds. Buffer channels commit independently of structure,
|
|
86
|
+
* so the newest commit often carries none.
|
|
87
87
|
*/
|
|
88
88
|
get lastStructure() {
|
|
89
89
|
for (let i = this.commits.length - 1; i >= 0; i--) {
|
|
@@ -277,9 +277,9 @@ var FakeEngine = class {
|
|
|
277
277
|
// --- test helpers (not part of GraphEngine) ---
|
|
278
278
|
/**
|
|
279
279
|
* manualFrames mode: apply the oldest queued commit, making its revision
|
|
280
|
-
* visible via appliedRevision
|
|
280
|
+
* visible via appliedRevision. No-op on the queue when empty. A drawn
|
|
281
281
|
* frame is also an activity-clock sample, so a mounted engine gets an
|
|
282
|
-
* `onFrame` tick
|
|
282
|
+
* `onFrame` tick.
|
|
283
283
|
*/
|
|
284
284
|
stepFrame() {
|
|
285
285
|
this.record("stepFrame", []);
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing/FakeEngine.ts","../src/testing/rafAudit.ts","../src/worker/runtime.ts","../src/testing/workerDouble.ts"],"names":["reply"],"mappings":"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","file":"testing.js","sourcesContent":["/**\n * FakeEngine — the public headless test seam (§18 /testing, §19).\n *\n * Implements the full GraphEngine adapter contract with zero DOM/WebGL access\n * so consumer suites can run under node/jsdom. Every public method call is\n * recorded to `calls` in order; commits and camera calls get typed convenience\n * views. Test-only helpers (`stepFrame`, `nudgePositions`, `inject*`) let\n * suites observe applied-revision lag, simulate position drift, and drive\n * host events.\n *\n * The state MIRRORS (`pinnedIndices`, `selectedIndices`, `lastStructure`,\n * `lastBuffer`) answer \"what does the engine currently hold?\", which the raw\n * `calls`/`commits` logs cannot: pins, highlight, structure and each buffer\n * channel are committed on independent schedules, so the newest entry is\n * rarely the newest value for a given channel.\n */\n\nimport type {\n EngineCapabilities,\n EngineCommit,\n EngineDiagnostic,\n EngineHostEvents,\n FitViewOptions,\n GraphEngine,\n} from '../engine/index';\nimport type { ViewportState } from '../types';\n\nexport interface FakeEngineOptions {\n /** Overrides merged over the defaults (linkPicking/trackedPositions false, rangeUpdates [], simulation true). */\n capabilities?: Partial<EngineCapabilities>;\n /**\n * When true, commits queue instead of applying immediately; `stepFrame()`\n * applies the oldest queued commit. Lets tests observe the lag between the\n * desired-render revision and `appliedRevision()`.\n */\n manualFrames?: boolean;\n /**\n * When set, `mount()` records the call, does NOT store the host events, and\n * rejects with this error — simulates an engine that cannot initialize.\n */\n mountError?: Error;\n /** Resolved by `captureScreenshot()`; default null (unsupported/not ready). */\n screenshot?: Blob | null;\n}\n\n/** One recorded public method invocation. */\nexport interface RecordedCall {\n method: string;\n args: readonly unknown[];\n}\n\nconst DEFAULT_CAPABILITIES: EngineCapabilities = {\n linkPicking: false,\n rangeUpdates: [],\n trackedPositions: false,\n simulation: true,\n // §17: the FakeEngine schedules nothing on its own — frames exist only\n // when a test calls stepFrame()/emitFrame() — so it is quiescent by\n // construction, and those calls apply-then-emit, which is genuinely\n // post-draw.\n idleFrames: 'stops',\n postDrawFrames: true,\n};\n\nconst CAMERA_METHODS: ReadonlySet<string> = new Set([\n 'fitView',\n 'zoom',\n 'setViewport',\n 'zoomToIndex',\n]);\n\n/** Deterministic seed for an unknown (NaN) position: a 10px grid, 100 per row. */\nfunction seedPosition(index: number): readonly [number, number] {\n return [(index % 100) * 10, Math.floor(index / 100) * 10];\n}\n\n/** Standard even-odd ray cast (screen == space in the FakeEngine). */\nfunction pointInPolygon(x: number, y: number, polygon: readonly [number, number][]): boolean {\n let inside = false;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const [xi, yi] = polygon[i]!;\n const [xj, yj] = polygon[j]!;\n const crosses = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;\n if (crosses) inside = !inside;\n }\n return inside;\n}\n\nexport class FakeEngine implements GraphEngine {\n readonly capabilities: EngineCapabilities;\n\n /** Every public method call, in order. */\n readonly calls: RecordedCall[] = [];\n /** Every commit passed to `commit()`, in call order (queued or applied). */\n readonly commits: EngineCommit[] = [];\n /** The fitView/zoom/setViewport/zoomToIndex entries of `calls`, in order. */\n readonly cameraCalls: RecordedCall[] = [];\n\n private readonly manualFrames: boolean;\n private readonly mountError: Error | null;\n private readonly screenshot: Blob | null;\n private events: EngineHostEvents | null = null;\n /** Auto-advancing activity clock for emitFrame()/stepFrame() (ms). */\n private frameClock = 0;\n private destroyedFlag = false;\n private viewport: ViewportState = { x: 0, y: 0, zoom: 1 };\n private applied: number | null = null;\n private positions: Float32Array | null = null;\n /** Links of the most recent structure-bearing commit (commit-time, even in\n * manualFrames mode) — the adjacency source for neighborIndices(). */\n private lastLinks: Uint32Array | null = null;\n private pinned: readonly number[] | null = null;\n private selected: readonly number[] | null = null;\n /** Commits awaiting stepFrame() in manualFrames mode (oldest first). */\n private readonly pendingFrames: EngineCommit[] = [];\n\n constructor(options: FakeEngineOptions = {}) {\n this.capabilities = { ...DEFAULT_CAPABILITIES, ...options.capabilities };\n this.manualFrames = options.manualFrames ?? false;\n this.mountError = options.mountError ?? null;\n this.screenshot = options.screenshot ?? null;\n }\n\n /** Most recent commit, if any. */\n get lastCommit(): EngineCommit | undefined {\n return this.commits[this.commits.length - 1];\n }\n\n get destroyed(): boolean {\n return this.destroyedFlag;\n }\n\n /** Last setPinnedIndices() value (null = unpinned / never pinned). */\n get pinnedIndices(): readonly number[] | null {\n return this.pinned;\n }\n\n /** Last setSelectedIndices() value (null = cleared / never selected) — the\n * §9.1 highlight mirror of {@link pinnedIndices}. */\n get selectedIndices(): readonly number[] | null {\n return this.selected;\n }\n\n /**\n * Structure of the most recent structure-bearing commit — the geometry the\n * engine currently holds. Buffer channels commit independently of structure\n * (§9.1 mask fast path), so the newest commit often carries none.\n */\n get lastStructure(): NonNullable<EngineCommit['structure']> | undefined {\n for (let i = this.commits.length - 1; i >= 0; i--) {\n const structure = this.commits[i]!.structure;\n if (structure !== undefined) return structure;\n }\n return undefined;\n }\n\n /**\n * Most recently committed value of ONE buffer channel — the engine's current\n * content for it. Channels are committed independently (a mask drain may\n * carry `pointColor` alone), so scanning back per channel is the only way to\n * read composed state; the newest commit alone is not the whole picture.\n */\n lastBuffer(channel: keyof NonNullable<EngineCommit['buffers']>): Float32Array | undefined {\n // Newest FULL upload seeds the channel; any LATER ranged patches replay\n // over a copy in commit order (S13-T04) — the return is what the engine\n // currently holds either way.\n let baseIndex = -1;\n let base: Float32Array | undefined;\n for (let i = this.commits.length - 1; i >= 0; i--) {\n const value = this.commits[i]!.buffers?.[channel];\n if (value !== undefined) {\n baseIndex = i;\n base = value;\n break;\n }\n }\n if (base === undefined) return undefined;\n let out = base;\n let copied = false;\n for (let i = baseIndex + 1; i < this.commits.length; i++) {\n const patches = this.commits[i]!.bufferPatches?.[channel];\n if (patches === undefined) continue;\n if (!copied) {\n out = out.slice();\n copied = true;\n }\n for (const patch of patches) out.set(patch.data, patch.start);\n }\n return out;\n }\n\n // --- GraphEngine contract ---\n\n /** Headless: never touches `container` — a dummy cast works under node. */\n mount(container: HTMLElement, events: EngineHostEvents): Promise<void> {\n this.record('mount', [container, events]);\n if (this.mountError !== null) return Promise.reject(this.mountError);\n this.events = events;\n return Promise.resolve();\n }\n\n commit(update: EngineCommit): void {\n if (this.destroyedFlag) {\n throw new Error('FakeEngine: commit() called after destroy()');\n }\n // S13-T04 tripwires: ranged patches are legal only on DECLARED channels,\n // never alongside a full buffer for the same channel in one commit, and\n // only after a full upload seeded the channel. Each is a producer bug —\n // fail the test loudly instead of rendering stale state silently.\n if (update.bufferPatches !== undefined) {\n for (const [channel, patches] of Object.entries(update.bufferPatches)) {\n if (patches === undefined) continue;\n const ch = channel as keyof NonNullable<EngineCommit['buffers']>;\n if (!this.capabilities.rangeUpdates.includes(ch)) {\n throw new Error(`FakeEngine: bufferPatches.${channel} without the declared capability`);\n }\n if (update.buffers?.[ch] !== undefined) {\n throw new Error(\n `FakeEngine: commit carries BOTH buffers.${channel} and bufferPatches.${channel}`,\n );\n }\n const seeded = this.commits.some((c) => c.buffers?.[ch] !== undefined);\n if (!seeded) {\n throw new Error(\n `FakeEngine: bufferPatches.${channel} before any full upload seeded the channel`,\n );\n }\n }\n }\n this.record('commit', [update]);\n this.commits.push(update);\n if (update.structure !== undefined) this.lastLinks = update.structure.links;\n if (this.manualFrames) {\n this.pendingFrames.push(update);\n } else {\n this.apply(update);\n }\n }\n\n appliedRevision(): number | null {\n this.record('appliedRevision', []);\n return this.applied;\n }\n\n fitView(opts?: FitViewOptions): void {\n this.record('fitView', [opts]);\n }\n\n zoom(factor: number, durationMs?: number): void {\n this.record('zoom', [factor, durationMs]);\n }\n\n setViewport(v: Partial<ViewportState>, opts?: { durationMs?: number }): void {\n this.record('setViewport', [v, opts]);\n this.viewport = { ...this.viewport, ...v };\n }\n\n getViewport(): ViewportState | null {\n this.record('getViewport', []);\n return { ...this.viewport };\n }\n\n zoomToIndex(index: number, durationMs?: number): void {\n this.record('zoomToIndex', [index, durationMs]);\n }\n\n start(alpha?: number): void {\n this.record('start', [alpha]);\n }\n\n pause(): void {\n this.record('pause', []);\n }\n\n setSelectedIndices(indices: readonly number[] | null): void {\n this.record('setSelectedIndices', [indices]);\n this.selected = indices === null ? null : [...indices];\n }\n\n setFocusedIndex(index: number | null): void {\n this.record('setFocusedIndex', [index]);\n }\n\n /** Screen == space in the FakeEngine: ray-cast over current positions,\n * skipping NaN pairs (not-yet-applied structure in manualFrames mode). */\n pointsInPolygon(screenPolygon: readonly [number, number][]): number[] {\n this.record('pointsInPolygon', [screenPolygon]);\n const pos = this.positions;\n const out: number[] = [];\n if (pos === null || screenPolygon.length < 3) return out;\n for (let i = 0; i < pos.length / 2; i++) {\n const x = pos[2 * i]!;\n const y = pos[2 * i + 1]!;\n if (Number.isNaN(x) || Number.isNaN(y)) continue;\n if (pointInPolygon(x, y, screenPolygon)) out.push(i);\n }\n return out;\n }\n\n /** Screen == space: point indices inside `[x0, y0, x1, y1]` (bounds\n * normalized), skipping NaN pairs (not-yet-applied structure). */\n pointsInRect(screenRect: readonly [number, number, number, number]): number[] {\n this.record('pointsInRect', [screenRect]);\n const pos = this.positions;\n const out: number[] = [];\n if (pos === null) return out;\n const minX = Math.min(screenRect[0], screenRect[2]);\n const maxX = Math.max(screenRect[0], screenRect[2]);\n const minY = Math.min(screenRect[1], screenRect[3]);\n const maxY = Math.max(screenRect[1], screenRect[3]);\n for (let i = 0; i < pos.length / 2; i++) {\n const x = pos[2 * i]!;\n const y = pos[2 * i + 1]!;\n if (Number.isNaN(x) || Number.isNaN(y)) continue;\n if (x >= minX && x <= maxX && y >= minY && y <= maxY) out.push(i);\n }\n return out;\n }\n\n /** Resolves the constructor's `screenshot` option (null by default). */\n captureScreenshot(): Promise<Blob | null> {\n this.record('captureScreenshot', []);\n return Promise.resolve(this.screenshot);\n }\n\n /** 1-hop adjacency from the last committed links (self-loops excluded). */\n neighborIndices(index: number): number[] {\n this.record('neighborIndices', [index]);\n const links = this.lastLinks;\n if (links === null) return [];\n const out = new Set<number>();\n for (let k = 0; k < links.length; k += 2) {\n const s = links[k]!;\n const t = links[k + 1]!;\n if (s === index && t !== index) out.add(t);\n if (t === index && s !== index) out.add(s);\n }\n return [...out];\n }\n\n /** Identity transform: FakeEngine screen coordinates ARE space coordinates. */\n screenToSpace(p: readonly [number, number]): [number, number] | null {\n this.record('screenToSpace', [p]);\n return [p[0], p[1]];\n }\n\n /** Identity transform: FakeEngine screen coordinates ARE space coordinates. */\n spaceToScreen(p: readonly [number, number]): [number, number] | null {\n this.record('spaceToScreen', [p]);\n return [p[0], p[1]];\n }\n\n setPinnedIndices(indices: readonly number[] | null): void {\n this.record('setPinnedIndices', [indices]);\n this.pinned = indices === null ? null : [...indices];\n }\n\n getPositions(): Float32Array | null {\n this.record('getPositions', []);\n return this.positions === null ? null : new Float32Array(this.positions);\n }\n\n destroy(): void {\n this.record('destroy', []);\n this.destroyedFlag = true;\n this.events = null;\n }\n\n // --- test helpers (not part of GraphEngine) ---\n\n /**\n * manualFrames mode: apply the oldest queued commit, making its revision\n * visible via appliedRevision(). No-op on the queue when empty. A drawn\n * frame is also an activity-clock sample, so a mounted engine gets an\n * `onFrame` tick (§13 overlay/activity clock).\n */\n stepFrame(): void {\n this.record('stepFrame', []);\n const next = this.pendingFrames.shift();\n if (next !== undefined) this.apply(next);\n if (this.events !== null) {\n this.frameClock += 16;\n this.events.onFrame?.(this.frameClock);\n }\n }\n\n /**\n * Drive the host `onFrame` activity clock. `timeMs` sets the clock\n * explicitly; omitted, the clock auto-advances by 16ms per call.\n */\n emitFrame(timeMs?: number): void {\n const events = this.requireMounted('emitFrame');\n this.frameClock = timeMs ?? this.frameClock + 16;\n this.record('emitFrame', [this.frameClock]);\n events.onFrame?.(this.frameClock);\n }\n\n /** Simulate simulation drift: shift every current position by (dx, dy). */\n nudgePositions(dx: number, dy: number): void {\n this.record('nudgePositions', [dx, dy]);\n const pos = this.positions;\n if (pos === null) return;\n for (let i = 0; i < pos.length; i += 2) {\n pos[i] = pos[i]! + dx;\n pos[i + 1] = pos[i + 1]! + dy;\n }\n }\n\n injectPointClick(index: number | null, modifiers?: { metaKey: boolean; shiftKey: boolean }): void {\n const events = this.requireMounted('injectPointClick');\n this.record('injectPointClick', [index, modifiers]);\n events.onPointClick?.(index, modifiers);\n }\n\n injectPointHover(index: number | null): void {\n const events = this.requireMounted('injectPointHover');\n this.record('injectPointHover', [index]);\n events.onPointHover?.(index);\n }\n\n injectLinkClick(linkIndex: number): void {\n const events = this.requireMounted('injectLinkClick');\n this.record('injectLinkClick', [linkIndex]);\n events.onLinkClick?.(linkIndex);\n }\n\n injectLinkHover(linkIndex: number | null): void {\n const events = this.requireMounted('injectLinkHover');\n this.record('injectLinkHover', [linkIndex]);\n events.onLinkHover?.(linkIndex);\n }\n\n injectDragStart(index: number): void {\n const events = this.requireMounted('injectDragStart');\n this.record('injectDragStart', [index]);\n events.onDragStart?.(index);\n }\n\n injectDragEnd(index: number, x: number, y: number): void {\n const events = this.requireMounted('injectDragEnd');\n this.record('injectDragEnd', [index, x, y]);\n events.onDragEnd?.(index, x, y);\n }\n\n injectContextMenu(index: number | null, screen: readonly [number, number]): void {\n const events = this.requireMounted('injectContextMenu');\n this.record('injectContextMenu', [index, screen]);\n events.onContextMenu?.(index, screen);\n }\n\n injectViewportChange(v: ViewportState): void {\n const events = this.requireMounted('injectViewportChange');\n this.record('injectViewportChange', [v]);\n this.viewport = { ...v };\n events.onViewportChange?.(v);\n }\n\n injectSimulationEnd(): void {\n const events = this.requireMounted('injectSimulationEnd');\n this.record('injectSimulationEnd', []);\n events.onSimulationEnd?.();\n }\n\n injectError(error: Error): void {\n const events = this.requireMounted('injectError');\n this.record('injectError', [error]);\n events.onError?.(error);\n }\n\n injectContextLost(): void {\n const events = this.requireMounted('injectContextLost');\n this.record('injectContextLost', []);\n events.onContextEvent?.({ type: 'lost' });\n }\n\n injectContextRestored(): void {\n const events = this.requireMounted('injectContextRestored');\n this.record('injectContextRestored', []);\n events.onContextEvent?.({ type: 'restored' });\n }\n\n injectContextFailed(error: Error): void {\n const events = this.requireMounted('injectContextFailed');\n this.record('injectContextFailed', [error]);\n events.onContextEvent?.({ type: 'failed', error });\n }\n\n injectEngineDiagnostic(d: EngineDiagnostic): void {\n const events = this.requireMounted('injectEngineDiagnostic');\n this.record('injectEngineDiagnostic', [d]);\n events.onDiagnostic?.(d);\n }\n\n // --- internals ---\n\n private record(method: string, args: unknown[]): void {\n // Trim trailing omitted optionals so `args` matches the caller's shape.\n while (args.length > 0 && args[args.length - 1] === undefined) args.pop();\n const call: RecordedCall = { method, args };\n this.calls.push(call);\n if (CAMERA_METHODS.has(method)) this.cameraCalls.push(call);\n }\n\n private requireMounted(helper: string): EngineHostEvents {\n const events = this.events;\n if (events === null) {\n throw new Error(`FakeEngine: ${helper}() requires a mounted engine — call mount() first`);\n }\n return events;\n }\n\n /**\n * Make a commit visible: rebuild positions when structure is present (NaN\n * pairs seeded deterministically, known coordinates kept verbatim) and\n * advance the applied revision.\n */\n private apply(update: EngineCommit): void {\n const structure = update.structure;\n if (structure !== undefined) {\n const { pointCount, positions } = structure;\n const next = new Float32Array(2 * pointCount);\n for (let i = 0; i < pointCount; i++) {\n const x = positions[2 * i]!;\n const y = positions[2 * i + 1]!;\n if (Number.isNaN(x) || Number.isNaN(y)) {\n const [sx, sy] = seedPosition(i);\n next[2 * i] = sx;\n next[2 * i + 1] = sy;\n } else {\n next[2 * i] = x;\n next[2 * i + 1] = y;\n }\n }\n this.positions = next;\n }\n this.applied = update.revision;\n }\n}\n","/**\n * §17/S13-T06 requestAnimationFrame audit — the quiescence instrument.\n *\n * Wraps a window's `requestAnimationFrame` and counts SCHEDULING calls\n * (registrations) separately from delivered ticks: the stop-at-rest claim is\n * about registrations — a truly stopped loop schedules nothing at all, while\n * tick counts alone can read zero under fake timers even though a loop is\n * still re-arming itself.\n *\n * Used three ways: core/react quiescence tests (install, drive the instance,\n * assert `registrations() === 0` over a settled window), the demo e2e's\n * page-side counterpart (same shape, injected via addInitScript), and the\n * dev-mode runtime assertion in the instance. The M0 spike's instrument.ts\n * pioneered the wrapper; this is its reusable, uninstallable form.\n */\n\nexport interface RafAudit {\n /** requestAnimationFrame CALLS observed since install (scheduling). */\n registrations(): number;\n /** Callbacks actually delivered since install (ticks). */\n ticks(): number;\n /** Zero both counters without uninstalling. */\n reset(): void;\n /** Restore the original requestAnimationFrame. Idempotent. */\n uninstall(): void;\n}\n\n/**\n * Install the audit on `win` (default: globalThis). Under jsdom + vitest\n * fake timers, rAF is timer-backed, so `vi.advanceTimersByTime` drives\n * deliveries — registrations count regardless. In a rAF-less environment\n * (plain-Node core tests) a 16ms setTimeout shim delivers: without the\n * audit, any rAF call there would simply crash — with it, the call is\n * COUNTED, which is exactly the evidence a quiescence test wants.\n */\nexport function installRafAudit(win: Window = globalThis as unknown as Window): RafAudit {\n const target = win as Window & {\n requestAnimationFrame?: typeof requestAnimationFrame;\n };\n const original = target.requestAnimationFrame;\n const native: typeof requestAnimationFrame =\n original !== undefined\n ? original.bind(target)\n : (cb) => setTimeout(() => cb(performance.now()), 16) as unknown as number;\n let registrations = 0;\n let ticks = 0;\n let installed = true;\n\n const wrapper = (callback: FrameRequestCallback): number => {\n registrations += 1;\n return native((time) => {\n ticks += 1;\n callback(time);\n });\n };\n target.requestAnimationFrame = wrapper;\n\n return {\n registrations: () => registrations,\n ticks: () => ticks,\n reset: () => {\n registrations = 0;\n ticks = 0;\n },\n uninstall: () => {\n if (!installed) return;\n installed = false;\n // Only restore if nobody else re-wrapped after us — otherwise leave\n // the newer wrapper in place and simply stop counting (native is\n // already bound into it).\n if (target.requestAnimationFrame === wrapper) {\n if (original !== undefined) target.requestAnimationFrame = original;\n else Reflect.deleteProperty(target, 'requestAnimationFrame');\n }\n },\n };\n}\n","/**\n * ADR-006 — the worker-side request handler, as a PURE function over the D3\n * codec so the real thread entry (worker/entry.ts) and the in-process test\n * double drive IDENTICAL code (D2: one implementation, never duplicated).\n *\n * First cargo (PR-F): `derive-columnar` — the §5.1 acceptance rules over\n * transferred acceptance inputs. Dictionaries arrive as UTF-8 string tables\n * (transferables, decoded off-main); results leave as keep bitmaps, the\n * survivor remap, resolved links, and object-lane-identical diagnostics —\n * every heavy field a transferable.\n *\n * No thread machinery here: input envelope in, reply envelope + transfer\n * list out. Unknown ops and malformed payloads reply with an 'error' op\n * (never throw — a worker that dies on one bad message kills every pending\n * request behind it).\n */\n\nimport { acceptColumnar } from '../columnarValidate';\nimport type { ColumnarGraphSnapshot, GraphDiagnostic } from '../types';\nimport {\n EnvelopeSequencer,\n collectTransfers,\n decodeStringTable,\n isWellFormedEnvelope,\n} from '../workerProtocol';\nimport type { EncodedStringTable, WorkerEnvelope } from '../workerProtocol';\n\n/** Wire payload for 'derive-columnar' — the acceptance inputs ONLY (attr\n * columns never cross; materialization stays main-side this slice). */\nexport interface DeriveColumnarRequest {\n nodeIdTable: EncodedStringTable;\n nodeIdCodes: Uint32Array;\n nodeCount: number;\n edgeIdTable: EncodedStringTable;\n edgeIdCodes: Uint32Array;\n edgeSource: Uint32Array;\n edgeTarget: Uint32Array;\n edgeCount: number;\n}\n\nexport interface DeriveColumnarResult {\n keepNodes: Uint8Array;\n keepEdges: Uint8Array;\n acceptedNodeCount: number;\n acceptedEdgeCount: number;\n nodeAcceptedIndex: Int32Array;\n links: Uint32Array;\n diagnostics: GraphDiagnostic[];\n}\n\nexport interface HandledReply {\n reply: WorkerEnvelope;\n transfers: ArrayBuffer[];\n}\n\n/** Handle ONE request envelope. The sequencer is the worker's own outbound\n * direction (per-direction monotonic ids). */\nexport function handleWorkerRequest(\n request: unknown,\n sequencer: EnvelopeSequencer,\n): HandledReply {\n if (!isWellFormedEnvelope(request)) {\n const reply = sequencer.make(0, 'scene', 'error', {\n message: 'malformed envelope (protocol violation)',\n });\n return { reply, transfers: [] };\n }\n\n if (request.op === 'derive-columnar') {\n try {\n const p = request.payload as DeriveColumnarRequest;\n // Rebuild the snapshot SHAPE acceptColumnar expects — same module the\n // main lane uses (D2), fed decoded-off-main dictionaries.\n const snapshot: ColumnarGraphSnapshot<unknown, unknown> = {\n kind: 'columnar',\n datasetKey: 'worker', // acceptance rules never read the coordinate\n sourceRevision: 0,\n nodes: {\n ids: {\n kind: 'string',\n dictionary: decodeStringTable(p.nodeIdTable),\n codes: p.nodeIdCodes,\n },\n columns: {},\n length: p.nodeCount,\n },\n edges: {\n ids: {\n kind: 'string',\n dictionary: decodeStringTable(p.edgeIdTable),\n codes: p.edgeIdCodes,\n },\n source: p.edgeSource,\n target: p.edgeTarget,\n columns: {},\n length: p.edgeCount,\n },\n };\n const acceptance = acceptColumnar(snapshot);\n const result: DeriveColumnarResult = {\n keepNodes: acceptance.keepNodes,\n keepEdges: acceptance.keepEdges,\n acceptedNodeCount: acceptance.acceptedNodeCount,\n acceptedEdgeCount: acceptance.acceptedEdgeCount,\n nodeAcceptedIndex: acceptance.nodeAcceptedIndex,\n links: acceptance.links,\n diagnostics: acceptance.diagnostics,\n };\n const reply = sequencer.make(\n request.epoch,\n request.entity,\n 'result',\n result,\n request.msgId,\n );\n return {\n reply,\n transfers: collectTransfers([\n result.keepNodes,\n result.keepEdges,\n result.nodeAcceptedIndex,\n result.links,\n ]),\n };\n } catch (err) {\n const reply = sequencer.make(\n request.epoch,\n request.entity,\n 'error',\n { message: err instanceof Error ? err.message : String(err) },\n request.msgId,\n );\n return { reply, transfers: [] };\n }\n }\n\n const reply = sequencer.make(\n request.epoch,\n request.entity,\n 'error',\n { message: `unknown op '${request.op}'` },\n request.msgId,\n );\n return { reply, transfers: [] };\n}\n","/**\n * In-process worker transport double (ADR-006 D2/D5) — drives the REAL\n * runtime module through the REAL codec, asynchronously (microtask), with\n * REAL transfer semantics: payloads round through `structuredClone` with\n * the declared transfer list, so a wrong transfer set detaches something\n * the sender still needs and fails the suite loudly — exactly what a live\n * thread would do, minus the scheduling.\n */\n\nimport { EnvelopeSequencer } from '../workerProtocol';\nimport type { WorkerEnvelope } from '../workerProtocol';\nimport { handleWorkerRequest } from '../worker/runtime';\nimport type { WorkerTransport } from '../worker/lane';\n\nexport interface WorkerDouble extends WorkerTransport {\n /** Requests handled so far (post-clone shapes, in arrival order). */\n readonly handled: WorkerEnvelope[];\n}\n\nexport function createWorkerDouble(): WorkerDouble {\n const sequencer = new EnvelopeSequencer();\n const handled: WorkerEnvelope[] = [];\n let deliver: ((reply: WorkerEnvelope) => void) | null = null;\n return {\n handled,\n post(envelope, transfers) {\n // REAL transfer semantics: the sender's views detach here.\n const received = structuredClone(envelope, {\n transfer: [...transfers],\n }) as WorkerEnvelope;\n queueMicrotask(() => {\n handled.push(received);\n const { reply, transfers: replyTransfers } = handleWorkerRequest(received, sequencer);\n const delivered = structuredClone(reply, {\n transfer: [...replyTransfers],\n }) as WorkerEnvelope;\n deliver?.(delivered);\n });\n },\n onReply(cb) {\n deliver = cb;\n },\n terminate() {\n deliver = null;\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/testing/FakeEngine.ts","../src/testing/rafAudit.ts","../src/worker/runtime.ts","../src/testing/workerDouble.ts"],"names":["reply"],"mappings":"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","file":"testing.js","sourcesContent":["/**\n * FakeEngine — the public headless test seam.\n *\n * Implements the full GraphEngine adapter contract with zero DOM/WebGL access\n * so consumer suites can run under node/jsdom. Every public method call is\n * recorded to `calls` in order; commits and camera calls get typed convenience\n * views. Test-only helpers (`stepFrame`, `nudgePositions`, `inject*`) let\n * suites observe applied-revision lag, simulate position drift, and drive\n * host events.\n *\n * The state MIRRORS (`pinnedIndices`, `selectedIndices`, `lastStructure`,\n * `lastBuffer`) answer \"what does the engine currently hold?\", which the raw\n * `calls`/`commits` logs cannot: pins, highlight, structure and each buffer\n * channel are committed on independent schedules, so the newest entry is\n * rarely the newest value for a given channel.\n */\n\nimport type {\n EngineCapabilities,\n EngineCommit,\n EngineDiagnostic,\n EngineHostEvents,\n FitViewOptions,\n GraphEngine,\n} from '../engine/index';\nimport type { ViewportState } from '../types';\n\nexport interface FakeEngineOptions {\n /** Overrides merged over the defaults (linkPicking/trackedPositions false, rangeUpdates [], simulation true). */\n capabilities?: Partial<EngineCapabilities>;\n /**\n * When true, commits queue instead of applying immediately; `stepFrame`\n * applies the oldest queued commit. Lets tests observe the lag between the\n * desired-render revision and `appliedRevision`.\n */\n manualFrames?: boolean;\n /**\n * When set, `mount` records the call, does NOT store the host events, and\n * rejects with this error — simulates an engine that cannot initialize.\n */\n mountError?: Error;\n /** Resolved by `captureScreenshot`; default null (unsupported/not ready). */\n screenshot?: Blob | null;\n}\n\n/** One recorded public method invocation. */\nexport interface RecordedCall {\n method: string;\n args: readonly unknown[];\n}\n\nconst DEFAULT_CAPABILITIES: EngineCapabilities = {\n linkPicking: false,\n rangeUpdates: [],\n trackedPositions: false,\n simulation: true,\n // the FakeEngine schedules nothing on its own — frames exist only\n // when a test calls stepFrame/emitFrame — so it is quiescent by\n // construction, and those calls apply-then-emit, which is genuinely\n // post-draw.\n idleFrames: 'stops',\n postDrawFrames: true,\n};\n\nconst CAMERA_METHODS: ReadonlySet<string> = new Set([\n 'fitView',\n 'zoom',\n 'setViewport',\n 'zoomToIndex',\n]);\n\n/** Deterministic seed for an unknown (NaN) position: a 10px grid, 100 per row. */\nfunction seedPosition(index: number): readonly [number, number] {\n return [(index % 100) * 10, Math.floor(index / 100) * 10];\n}\n\n/** Standard even-odd ray cast (screen == space in the FakeEngine). */\nfunction pointInPolygon(x: number, y: number, polygon: readonly [number, number][]): boolean {\n let inside = false;\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const [xi, yi] = polygon[i]!;\n const [xj, yj] = polygon[j]!;\n const crosses = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;\n if (crosses) inside = !inside;\n }\n return inside;\n}\n\nexport class FakeEngine implements GraphEngine {\n readonly capabilities: EngineCapabilities;\n\n /** Every public method call, in order. */\n readonly calls: RecordedCall[] = [];\n /** Every commit passed to `commit`, in call order (queued or applied). */\n readonly commits: EngineCommit[] = [];\n /** The fitView/zoom/setViewport/zoomToIndex entries of `calls`, in order. */\n readonly cameraCalls: RecordedCall[] = [];\n\n private readonly manualFrames: boolean;\n private readonly mountError: Error | null;\n private readonly screenshot: Blob | null;\n private events: EngineHostEvents | null = null;\n /** Auto-advancing activity clock for emitFrame/stepFrame (ms). */\n private frameClock = 0;\n private destroyedFlag = false;\n private viewport: ViewportState = { x: 0, y: 0, zoom: 1 };\n private applied: number | null = null;\n private positions: Float32Array | null = null;\n /** Links of the most recent structure-bearing commit (commit-time, even in\n * manualFrames mode) — the adjacency source for neighborIndices. */\n private lastLinks: Uint32Array | null = null;\n private pinned: readonly number[] | null = null;\n private selected: readonly number[] | null = null;\n /** Commits awaiting stepFrame in manualFrames mode (oldest first). */\n private readonly pendingFrames: EngineCommit[] = [];\n\n constructor(options: FakeEngineOptions = {}) {\n this.capabilities = { ...DEFAULT_CAPABILITIES, ...options.capabilities };\n this.manualFrames = options.manualFrames ?? false;\n this.mountError = options.mountError ?? null;\n this.screenshot = options.screenshot ?? null;\n }\n\n /** Most recent commit, if any. */\n get lastCommit(): EngineCommit | undefined {\n return this.commits[this.commits.length - 1];\n }\n\n get destroyed(): boolean {\n return this.destroyedFlag;\n }\n\n /** Last setPinnedIndices value (null = unpinned / never pinned). */\n get pinnedIndices(): readonly number[] | null {\n return this.pinned;\n }\n\n /** Last setSelectedIndices value (null = cleared / never selected) — the\n * highlight mirror of {@link pinnedIndices}. */\n get selectedIndices(): readonly number[] | null {\n return this.selected;\n }\n\n /**\n * Structure of the most recent structure-bearing commit — the geometry the\n * engine currently holds. Buffer channels commit independently of structure,\n * so the newest commit often carries none.\n */\n get lastStructure(): NonNullable<EngineCommit['structure']> | undefined {\n for (let i = this.commits.length - 1; i >= 0; i--) {\n const structure = this.commits[i]!.structure;\n if (structure !== undefined) return structure;\n }\n return undefined;\n }\n\n /**\n * Most recently committed value of ONE buffer channel — the engine's current\n * content for it. Channels are committed independently (a mask drain may\n * carry `pointColor` alone), so scanning back per channel is the only way to\n * read composed state; the newest commit alone is not the whole picture.\n */\n lastBuffer(channel: keyof NonNullable<EngineCommit['buffers']>): Float32Array | undefined {\n // Newest FULL upload seeds the channel; any LATER ranged patches replay\n // over a copy in commit order — the return is what the engine\n // currently holds either way.\n let baseIndex = -1;\n let base: Float32Array | undefined;\n for (let i = this.commits.length - 1; i >= 0; i--) {\n const value = this.commits[i]!.buffers?.[channel];\n if (value !== undefined) {\n baseIndex = i;\n base = value;\n break;\n }\n }\n if (base === undefined) return undefined;\n let out = base;\n let copied = false;\n for (let i = baseIndex + 1; i < this.commits.length; i++) {\n const patches = this.commits[i]!.bufferPatches?.[channel];\n if (patches === undefined) continue;\n if (!copied) {\n out = out.slice();\n copied = true;\n }\n for (const patch of patches) out.set(patch.data, patch.start);\n }\n return out;\n }\n\n // --- GraphEngine contract ---\n\n /** Headless: never touches `container` — a dummy cast works under node. */\n mount(container: HTMLElement, events: EngineHostEvents): Promise<void> {\n this.record('mount', [container, events]);\n if (this.mountError !== null) return Promise.reject(this.mountError);\n this.events = events;\n return Promise.resolve();\n }\n\n commit(update: EngineCommit): void {\n if (this.destroyedFlag) {\n throw new Error('FakeEngine: commit() called after destroy()');\n }\n // tripwires: ranged patches are legal only on DECLARED channels,\n // never alongside a full buffer for the same channel in one commit, and\n // only after a full upload seeded the channel. Each is a producer bug\n // fail the test loudly instead of rendering stale state silently.\n if (update.bufferPatches !== undefined) {\n for (const [channel, patches] of Object.entries(update.bufferPatches)) {\n if (patches === undefined) continue;\n const ch = channel as keyof NonNullable<EngineCommit['buffers']>;\n if (!this.capabilities.rangeUpdates.includes(ch)) {\n throw new Error(`FakeEngine: bufferPatches.${channel} without the declared capability`);\n }\n if (update.buffers?.[ch] !== undefined) {\n throw new Error(\n `FakeEngine: commit carries BOTH buffers.${channel} and bufferPatches.${channel}`,\n );\n }\n const seeded = this.commits.some((c) => c.buffers?.[ch] !== undefined);\n if (!seeded) {\n throw new Error(\n `FakeEngine: bufferPatches.${channel} before any full upload seeded the channel`,\n );\n }\n }\n }\n this.record('commit', [update]);\n this.commits.push(update);\n if (update.structure !== undefined) this.lastLinks = update.structure.links;\n if (this.manualFrames) {\n this.pendingFrames.push(update);\n } else {\n this.apply(update);\n }\n }\n\n appliedRevision(): number | null {\n this.record('appliedRevision', []);\n return this.applied;\n }\n\n fitView(opts?: FitViewOptions): void {\n this.record('fitView', [opts]);\n }\n\n zoom(factor: number, durationMs?: number): void {\n this.record('zoom', [factor, durationMs]);\n }\n\n setViewport(v: Partial<ViewportState>, opts?: { durationMs?: number }): void {\n this.record('setViewport', [v, opts]);\n this.viewport = { ...this.viewport, ...v };\n }\n\n getViewport(): ViewportState | null {\n this.record('getViewport', []);\n return { ...this.viewport };\n }\n\n zoomToIndex(index: number, durationMs?: number): void {\n this.record('zoomToIndex', [index, durationMs]);\n }\n\n start(alpha?: number): void {\n this.record('start', [alpha]);\n }\n\n pause(): void {\n this.record('pause', []);\n }\n\n setSelectedIndices(indices: readonly number[] | null): void {\n this.record('setSelectedIndices', [indices]);\n this.selected = indices === null ? null : [...indices];\n }\n\n setFocusedIndex(index: number | null): void {\n this.record('setFocusedIndex', [index]);\n }\n\n /** Screen == space in the FakeEngine: ray-cast over current positions,\n * skipping NaN pairs (not-yet-applied structure in manualFrames mode). */\n pointsInPolygon(screenPolygon: readonly [number, number][]): number[] {\n this.record('pointsInPolygon', [screenPolygon]);\n const pos = this.positions;\n const out: number[] = [];\n if (pos === null || screenPolygon.length < 3) return out;\n for (let i = 0; i < pos.length / 2; i++) {\n const x = pos[2 * i]!;\n const y = pos[2 * i + 1]!;\n if (Number.isNaN(x) || Number.isNaN(y)) continue;\n if (pointInPolygon(x, y, screenPolygon)) out.push(i);\n }\n return out;\n }\n\n /** Screen == space: point indices inside `[x0, y0, x1, y1]` (bounds\n * normalized), skipping NaN pairs (not-yet-applied structure). */\n pointsInRect(screenRect: readonly [number, number, number, number]): number[] {\n this.record('pointsInRect', [screenRect]);\n const pos = this.positions;\n const out: number[] = [];\n if (pos === null) return out;\n const minX = Math.min(screenRect[0], screenRect[2]);\n const maxX = Math.max(screenRect[0], screenRect[2]);\n const minY = Math.min(screenRect[1], screenRect[3]);\n const maxY = Math.max(screenRect[1], screenRect[3]);\n for (let i = 0; i < pos.length / 2; i++) {\n const x = pos[2 * i]!;\n const y = pos[2 * i + 1]!;\n if (Number.isNaN(x) || Number.isNaN(y)) continue;\n if (x >= minX && x <= maxX && y >= minY && y <= maxY) out.push(i);\n }\n return out;\n }\n\n /** Resolves the constructor's `screenshot` option (null by default). */\n captureScreenshot(): Promise<Blob | null> {\n this.record('captureScreenshot', []);\n return Promise.resolve(this.screenshot);\n }\n\n /** 1-hop adjacency from the last committed links (self-loops excluded). */\n neighborIndices(index: number): number[] {\n this.record('neighborIndices', [index]);\n const links = this.lastLinks;\n if (links === null) return [];\n const out = new Set<number>();\n for (let k = 0; k < links.length; k += 2) {\n const s = links[k]!;\n const t = links[k + 1]!;\n if (s === index && t !== index) out.add(t);\n if (t === index && s !== index) out.add(s);\n }\n return [...out];\n }\n\n /** Identity transform: FakeEngine screen coordinates ARE space coordinates. */\n screenToSpace(p: readonly [number, number]): [number, number] | null {\n this.record('screenToSpace', [p]);\n return [p[0], p[1]];\n }\n\n /** Identity transform: FakeEngine screen coordinates ARE space coordinates. */\n spaceToScreen(p: readonly [number, number]): [number, number] | null {\n this.record('spaceToScreen', [p]);\n return [p[0], p[1]];\n }\n\n setPinnedIndices(indices: readonly number[] | null): void {\n this.record('setPinnedIndices', [indices]);\n this.pinned = indices === null ? null : [...indices];\n }\n\n getPositions(): Float32Array | null {\n this.record('getPositions', []);\n return this.positions === null ? null : new Float32Array(this.positions);\n }\n\n destroy(): void {\n this.record('destroy', []);\n this.destroyedFlag = true;\n this.events = null;\n }\n\n // --- test helpers (not part of GraphEngine) ---\n\n /**\n * manualFrames mode: apply the oldest queued commit, making its revision\n * visible via appliedRevision. No-op on the queue when empty. A drawn\n * frame is also an activity-clock sample, so a mounted engine gets an\n * `onFrame` tick.\n */\n stepFrame(): void {\n this.record('stepFrame', []);\n const next = this.pendingFrames.shift();\n if (next !== undefined) this.apply(next);\n if (this.events !== null) {\n this.frameClock += 16;\n this.events.onFrame?.(this.frameClock);\n }\n }\n\n /**\n * Drive the host `onFrame` activity clock. `timeMs` sets the clock\n * explicitly; omitted, the clock auto-advances by 16ms per call.\n */\n emitFrame(timeMs?: number): void {\n const events = this.requireMounted('emitFrame');\n this.frameClock = timeMs ?? this.frameClock + 16;\n this.record('emitFrame', [this.frameClock]);\n events.onFrame?.(this.frameClock);\n }\n\n /** Simulate simulation drift: shift every current position by (dx, dy). */\n nudgePositions(dx: number, dy: number): void {\n this.record('nudgePositions', [dx, dy]);\n const pos = this.positions;\n if (pos === null) return;\n for (let i = 0; i < pos.length; i += 2) {\n pos[i] = pos[i]! + dx;\n pos[i + 1] = pos[i + 1]! + dy;\n }\n }\n\n injectPointClick(index: number | null, modifiers?: { metaKey: boolean; shiftKey: boolean }): void {\n const events = this.requireMounted('injectPointClick');\n this.record('injectPointClick', [index, modifiers]);\n events.onPointClick?.(index, modifiers);\n }\n\n injectPointHover(index: number | null): void {\n const events = this.requireMounted('injectPointHover');\n this.record('injectPointHover', [index]);\n events.onPointHover?.(index);\n }\n\n injectLinkClick(linkIndex: number): void {\n const events = this.requireMounted('injectLinkClick');\n this.record('injectLinkClick', [linkIndex]);\n events.onLinkClick?.(linkIndex);\n }\n\n injectLinkHover(linkIndex: number | null): void {\n const events = this.requireMounted('injectLinkHover');\n this.record('injectLinkHover', [linkIndex]);\n events.onLinkHover?.(linkIndex);\n }\n\n injectDragStart(index: number): void {\n const events = this.requireMounted('injectDragStart');\n this.record('injectDragStart', [index]);\n events.onDragStart?.(index);\n }\n\n injectDragEnd(index: number, x: number, y: number): void {\n const events = this.requireMounted('injectDragEnd');\n this.record('injectDragEnd', [index, x, y]);\n events.onDragEnd?.(index, x, y);\n }\n\n injectContextMenu(index: number | null, screen: readonly [number, number]): void {\n const events = this.requireMounted('injectContextMenu');\n this.record('injectContextMenu', [index, screen]);\n events.onContextMenu?.(index, screen);\n }\n\n injectViewportChange(v: ViewportState): void {\n const events = this.requireMounted('injectViewportChange');\n this.record('injectViewportChange', [v]);\n this.viewport = { ...v };\n events.onViewportChange?.(v);\n }\n\n injectSimulationEnd(): void {\n const events = this.requireMounted('injectSimulationEnd');\n this.record('injectSimulationEnd', []);\n events.onSimulationEnd?.();\n }\n\n injectError(error: Error): void {\n const events = this.requireMounted('injectError');\n this.record('injectError', [error]);\n events.onError?.(error);\n }\n\n injectContextLost(): void {\n const events = this.requireMounted('injectContextLost');\n this.record('injectContextLost', []);\n events.onContextEvent?.({ type: 'lost' });\n }\n\n injectContextRestored(): void {\n const events = this.requireMounted('injectContextRestored');\n this.record('injectContextRestored', []);\n events.onContextEvent?.({ type: 'restored' });\n }\n\n injectContextFailed(error: Error): void {\n const events = this.requireMounted('injectContextFailed');\n this.record('injectContextFailed', [error]);\n events.onContextEvent?.({ type: 'failed', error });\n }\n\n injectEngineDiagnostic(d: EngineDiagnostic): void {\n const events = this.requireMounted('injectEngineDiagnostic');\n this.record('injectEngineDiagnostic', [d]);\n events.onDiagnostic?.(d);\n }\n\n // --- internals ---\n\n private record(method: string, args: unknown[]): void {\n // Trim trailing omitted optionals so `args` matches the caller's shape.\n while (args.length > 0 && args[args.length - 1] === undefined) args.pop();\n const call: RecordedCall = { method, args };\n this.calls.push(call);\n if (CAMERA_METHODS.has(method)) this.cameraCalls.push(call);\n }\n\n private requireMounted(helper: string): EngineHostEvents {\n const events = this.events;\n if (events === null) {\n throw new Error(`FakeEngine: ${helper}() requires a mounted engine — call mount() first`);\n }\n return events;\n }\n\n /**\n * Make a commit visible: rebuild positions when structure is present (NaN\n * pairs seeded deterministically, known coordinates kept verbatim) and\n * advance the applied revision.\n */\n private apply(update: EngineCommit): void {\n const structure = update.structure;\n if (structure !== undefined) {\n const { pointCount, positions } = structure;\n const next = new Float32Array(2 * pointCount);\n for (let i = 0; i < pointCount; i++) {\n const x = positions[2 * i]!;\n const y = positions[2 * i + 1]!;\n if (Number.isNaN(x) || Number.isNaN(y)) {\n const [sx, sy] = seedPosition(i);\n next[2 * i] = sx;\n next[2 * i + 1] = sy;\n } else {\n next[2 * i] = x;\n next[2 * i + 1] = y;\n }\n }\n this.positions = next;\n }\n this.applied = update.revision;\n }\n}\n","/**\n * requestAnimationFrame audit — the quiescence instrument.\n *\n * Wraps a window's `requestAnimationFrame` and counts SCHEDULING calls\n * (registrations) separately from delivered ticks: the stop-at-rest claim is\n * about registrations — a truly stopped loop schedules nothing at all, while\n * tick counts alone can read zero under fake timers even though a loop is\n * still re-arming itself.\n *\n * Used three ways: core/react quiescence tests (install, drive the instance,\n * assert `registrations === 0` over a settled window), the demo e2e's\n * page-side counterpart (same shape, injected via addInitScript), and the\n * dev-mode runtime assertion in the instance. The M0 spike's instrument.ts\n * pioneered the wrapper; this is its reusable, uninstallable form.\n */\n\nexport interface RafAudit {\n /** requestAnimationFrame CALLS observed since install (scheduling). */\n registrations(): number;\n /** Callbacks actually delivered since install (ticks). */\n ticks(): number;\n /** Zero both counters without uninstalling. */\n reset(): void;\n /** Restore the original requestAnimationFrame. Idempotent. */\n uninstall(): void;\n}\n\n/**\n * Install the audit on `win` (default: globalThis). Under jsdom + vitest\n * fake timers, rAF is timer-backed, so `vi.advanceTimersByTime` drives\n * deliveries — registrations count regardless. In a rAF-less environment\n * (plain-Node core tests) a 16ms setTimeout shim delivers: without the\n * audit, any rAF call there would simply crash — with it, the call is\n * COUNTED, which is exactly the evidence a quiescence test wants.\n */\nexport function installRafAudit(win: Window = globalThis as unknown as Window): RafAudit {\n const target = win as Window & {\n requestAnimationFrame?: typeof requestAnimationFrame;\n };\n const original = target.requestAnimationFrame;\n const native: typeof requestAnimationFrame =\n original !== undefined\n ? original.bind(target)\n : (cb) => setTimeout(() => cb(performance.now()), 16) as unknown as number;\n let registrations = 0;\n let ticks = 0;\n let installed = true;\n\n const wrapper = (callback: FrameRequestCallback): number => {\n registrations += 1;\n return native((time) => {\n ticks += 1;\n callback(time);\n });\n };\n target.requestAnimationFrame = wrapper;\n\n return {\n registrations: () => registrations,\n ticks: () => ticks,\n reset: () => {\n registrations = 0;\n ticks = 0;\n },\n uninstall: () => {\n if (!installed) return;\n installed = false;\n // Only restore if nobody else re-wrapped after us — otherwise leave\n // the newer wrapper in place and simply stop counting (native is\n // already bound into it).\n if (target.requestAnimationFrame === wrapper) {\n if (original !== undefined) target.requestAnimationFrame = original;\n else Reflect.deleteProperty(target, 'requestAnimationFrame');\n }\n },\n };\n}\n","/**\n * Worker-side request handler, implemented as a PURE function over the\n * codec so the real thread entry (worker/entry.ts) and the in-process test\n * double drive IDENTICAL code (D2: one implementation, never duplicated).\n *\n * First cargo: `derive-columnar` — the acceptance rules over\n * transferred acceptance inputs. Dictionaries arrive as UTF-8 string tables\n * (transferables, decoded off-main); results leave as keep bitmaps, the\n * survivor remap, resolved links, and object-lane-identical diagnostics\n * every heavy field a transferable.\n *\n * No thread machinery here: input envelope in, reply envelope + transfer\n * list out. Unknown ops and malformed payloads reply with an 'error' op\n * (never throw — a worker that dies on one bad message kills every pending\n * request behind it).\n */\n\nimport { acceptColumnar } from '../columnarValidate';\nimport type { ColumnarGraphSnapshot, GraphDiagnostic } from '../types';\nimport {\n EnvelopeSequencer,\n collectTransfers,\n decodeStringTable,\n isWellFormedEnvelope,\n} from '../workerProtocol';\nimport type { EncodedStringTable, WorkerEnvelope } from '../workerProtocol';\n\n/** Wire payload for 'derive-columnar' — the acceptance inputs ONLY (attr\n * columns never cross; materialization stays main-side this slice). */\nexport interface DeriveColumnarRequest {\n nodeIdTable: EncodedStringTable;\n nodeIdCodes: Uint32Array;\n nodeCount: number;\n edgeIdTable: EncodedStringTable;\n edgeIdCodes: Uint32Array;\n edgeSource: Uint32Array;\n edgeTarget: Uint32Array;\n edgeCount: number;\n}\n\nexport interface DeriveColumnarResult {\n keepNodes: Uint8Array;\n keepEdges: Uint8Array;\n acceptedNodeCount: number;\n acceptedEdgeCount: number;\n nodeAcceptedIndex: Int32Array;\n links: Uint32Array;\n diagnostics: GraphDiagnostic[];\n}\n\nexport interface HandledReply {\n reply: WorkerEnvelope;\n transfers: ArrayBuffer[];\n}\n\n/** Handle ONE request envelope. The sequencer is the worker's own outbound\n * direction (per-direction monotonic ids). */\nexport function handleWorkerRequest(\n request: unknown,\n sequencer: EnvelopeSequencer,\n): HandledReply {\n if (!isWellFormedEnvelope(request)) {\n const reply = sequencer.make(0, 'scene', 'error', {\n message: 'malformed envelope (protocol violation)',\n });\n return { reply, transfers: [] };\n }\n\n if (request.op === 'derive-columnar') {\n try {\n const p = request.payload as DeriveColumnarRequest;\n // Rebuild the snapshot SHAPE acceptColumnar expects — same module the\n // main lane uses (D2), fed decoded-off-main dictionaries.\n const snapshot: ColumnarGraphSnapshot<unknown, unknown> = {\n kind: 'columnar',\n datasetKey: 'worker', // acceptance rules never read the coordinate\n sourceRevision: 0,\n nodes: {\n ids: {\n kind: 'string',\n dictionary: decodeStringTable(p.nodeIdTable),\n codes: p.nodeIdCodes,\n },\n columns: {},\n length: p.nodeCount,\n },\n edges: {\n ids: {\n kind: 'string',\n dictionary: decodeStringTable(p.edgeIdTable),\n codes: p.edgeIdCodes,\n },\n source: p.edgeSource,\n target: p.edgeTarget,\n columns: {},\n length: p.edgeCount,\n },\n };\n const acceptance = acceptColumnar(snapshot);\n const result: DeriveColumnarResult = {\n keepNodes: acceptance.keepNodes,\n keepEdges: acceptance.keepEdges,\n acceptedNodeCount: acceptance.acceptedNodeCount,\n acceptedEdgeCount: acceptance.acceptedEdgeCount,\n nodeAcceptedIndex: acceptance.nodeAcceptedIndex,\n links: acceptance.links,\n diagnostics: acceptance.diagnostics,\n };\n const reply = sequencer.make(\n request.epoch,\n request.entity,\n 'result',\n result,\n request.msgId,\n );\n return {\n reply,\n transfers: collectTransfers([\n result.keepNodes,\n result.keepEdges,\n result.nodeAcceptedIndex,\n result.links,\n ]),\n };\n } catch (err) {\n const reply = sequencer.make(\n request.epoch,\n request.entity,\n 'error',\n { message: err instanceof Error ? err.message : String(err) },\n request.msgId,\n );\n return { reply, transfers: [] };\n }\n }\n\n const reply = sequencer.make(\n request.epoch,\n request.entity,\n 'error',\n { message: `unknown op '${request.op}'` },\n request.msgId,\n );\n return { reply, transfers: [] };\n}\n","/**\n * In-process worker transport double — drives the REAL\n * runtime module through the REAL codec, asynchronously (microtask), with\n * REAL transfer semantics: payloads round through `structuredClone` with\n * the declared transfer list, so a wrong transfer set detaches something\n * the sender still needs and fails the suite loudly — exactly what a live\n * thread would do, minus the scheduling.\n */\n\nimport { EnvelopeSequencer } from '../workerProtocol';\nimport type { WorkerEnvelope } from '../workerProtocol';\nimport { handleWorkerRequest } from '../worker/runtime';\nimport type { WorkerTransport } from '../worker/lane';\n\nexport interface WorkerDouble extends WorkerTransport {\n /** Requests handled so far (post-clone shapes, in arrival order). */\n readonly handled: WorkerEnvelope[];\n}\n\nexport function createWorkerDouble(): WorkerDouble {\n const sequencer = new EnvelopeSequencer();\n const handled: WorkerEnvelope[] = [];\n let deliver: ((reply: WorkerEnvelope) => void) | null = null;\n return {\n handled,\n post(envelope, transfers) {\n // REAL transfer semantics: the sender's views detach here.\n const received = structuredClone(envelope, {\n transfer: [...transfers],\n }) as WorkerEnvelope;\n queueMicrotask(() => {\n handled.push(received);\n const { reply, transfers: replyTransfers } = handleWorkerRequest(received, sequencer);\n const delivered = structuredClone(reply, {\n transfer: [...replyTransfers],\n }) as WorkerEnvelope;\n deliver?.(delivered);\n });\n },\n onReply(cb) {\n deliver = cb;\n },\n terminate() {\n deliver = null;\n },\n };\n}\n"]}
|