@modernrelay/orbit-core 0.2.0 → 0.13.5

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/index.js CHANGED
@@ -1,5 +1,7 @@
1
- import { DIAGNOSTIC_SAMPLE_CAP, deriveClusters, resolveClusterCenters, clusterCentroids } from './chunk-Z7FOEASL.js';
2
- export { DEFAULT_CLUSTER_CENTER_RADIUS, DEFAULT_LAYOUT_SEED, DIAGNOSTIC_SAMPLE_CAP, clusterCentroids, deriveClusters, generateClusterCenters, resolveClusterCenters } from './chunk-Z7FOEASL.js';
1
+ import { deriveClusters, resolveClusterCenters, clusterCentroids } from './chunk-XAR262L3.js';
2
+ export { DEFAULT_CLUSTER_CENTER_RADIUS, DEFAULT_LAYOUT_SEED, clusterCentroids, deriveClusters, generateClusterCenters, resolveClusterCenters } from './chunk-XAR262L3.js';
3
+ import { DIAGNOSTIC_SAMPLE_CAP, encodeStringTable, collectTransfers, EnvelopeSequencer, RequestLedger } from './chunk-NIJX5NVJ.js';
4
+ export { DIAGNOSTIC_SAMPLE_CAP, acceptColumnar, collectTransfers, decodeStringTable, encodeStringTable, judgeEpoch } from './chunk-NIJX5NVJ.js';
3
5
  import { createStore } from 'zustand/vanilla';
4
6
 
5
7
  // src/errors.ts
@@ -1513,6 +1515,375 @@ function neighborsOf(adj, index) {
1513
1515
  }
1514
1516
  return adj.neighbors.subarray(adj.offsets[index], adj.offsets[index + 1]);
1515
1517
  }
1518
+ function buildIncidence(links, pointCount) {
1519
+ if (!Number.isInteger(pointCount) || pointCount < 0) {
1520
+ throw new RangeError(
1521
+ `buildIncidence: pointCount must be a non-negative integer, got ${pointCount}`
1522
+ );
1523
+ }
1524
+ if ((links.length & 1) !== 0) {
1525
+ throw new RangeError(
1526
+ `buildIncidence: links length must be even ([src, tgt] pairs), got ${links.length}`
1527
+ );
1528
+ }
1529
+ const n = links.length;
1530
+ const offsets = new Uint32Array(pointCount + 1);
1531
+ for (let i = 0; i < n; i++) {
1532
+ const p = links[i];
1533
+ if (p >= pointCount) {
1534
+ throw new RangeError(
1535
+ `buildIncidence: link endpoint ${p} out of range (pointCount ${pointCount})`
1536
+ );
1537
+ }
1538
+ offsets[p + 1] = offsets[p + 1] + 1;
1539
+ }
1540
+ for (let i = 1; i <= pointCount; i++) {
1541
+ offsets[i] = offsets[i] + offsets[i - 1];
1542
+ }
1543
+ const edgeSlots = new Uint32Array(n);
1544
+ const cursor = offsets.slice(0, pointCount);
1545
+ for (let i = 0; i < n; i += 2) {
1546
+ const edge = i >>> 1;
1547
+ const a = links[i];
1548
+ const b = links[i + 1];
1549
+ const ca = cursor[a];
1550
+ edgeSlots[ca] = edge;
1551
+ cursor[a] = ca + 1;
1552
+ const cb = cursor[b];
1553
+ edgeSlots[cb] = edge;
1554
+ cursor[b] = cb + 1;
1555
+ }
1556
+ return { offsets, edgeSlots };
1557
+ }
1558
+ function incidentEdgesOf(inc, index) {
1559
+ const pointCount = inc.offsets.length - 1;
1560
+ if (!Number.isInteger(index) || index < 0 || index >= pointCount) {
1561
+ throw new RangeError(`incidentEdgesOf: index ${index} out of range (pointCount ${pointCount})`);
1562
+ }
1563
+ return inc.edgeSlots.subarray(inc.offsets[index], inc.offsets[index + 1]);
1564
+ }
1565
+
1566
+ // src/alphaCompose.ts
1567
+ var IncrementalAlphaComposer = class {
1568
+ bufA = null;
1569
+ bufB = null;
1570
+ staleA = [];
1571
+ staleB = [];
1572
+ /** Which buffer the NEXT nextBuffer() call returns (0 = A, 1 = B). */
1573
+ next = 0;
1574
+ // --- seed key ---
1575
+ seededBase = null;
1576
+ seededCount = -1;
1577
+ seededDimAlpha = NaN;
1578
+ seededEpoch = void 0;
1579
+ /** Slots rewritten since the last resetStats (wave-5 gate instrument). */
1580
+ slotsRewritten = 0;
1581
+ /** Full reseeds performed (each is one O(n) masked pass over the pair). */
1582
+ reseeds = 0;
1583
+ resetStats() {
1584
+ this.slotsRewritten = 0;
1585
+ this.reseeds = 0;
1586
+ }
1587
+ /**
1588
+ * True when the composer is seeded for exactly this (base, count,
1589
+ * dimAlpha, extraEpoch) tuple; otherwise both buffers are rebuilt with a
1590
+ * full masked pass and the stale lists reset. Object.is on the alpha
1591
+ * handles the NaN sentinel.
1592
+ */
1593
+ ensureSeeded(base, count, dimAlpha, extraEpoch, alphaOf) {
1594
+ if (this.seededBase === base && this.seededCount === count && Object.is(this.seededDimAlpha, dimAlpha) && this.seededEpoch === extraEpoch && this.bufA !== null && this.bufB !== null) {
1595
+ return true;
1596
+ }
1597
+ this.reseeds += 1;
1598
+ this.bufA = this.seedOne(base, count, alphaOf, this.bufA);
1599
+ this.bufB = this.seedOne(base, count, alphaOf, this.bufB);
1600
+ this.staleA.length = 0;
1601
+ this.staleB.length = 0;
1602
+ this.seededBase = base;
1603
+ this.seededCount = count;
1604
+ this.seededDimAlpha = dimAlpha;
1605
+ this.seededEpoch = extraEpoch;
1606
+ return false;
1607
+ }
1608
+ /** Record changed slots from one mask drain (call once per drain — the
1609
+ * drain arrays are reused by the mask, so this copies them out). */
1610
+ note(slots) {
1611
+ for (let i = 0; i < slots.length; i += 1) {
1612
+ this.staleA.push(slots[i]);
1613
+ this.staleB.push(slots[i]);
1614
+ }
1615
+ }
1616
+ /**
1617
+ * Replay the target buffer's stale slots against the CURRENT mask state,
1618
+ * swap, and return it. Same output semantics as the naive composer: the
1619
+ * base RGB is untouched, alpha = base alpha × alphaOf(slot).
1620
+ */
1621
+ nextBuffer(base, alphaOf) {
1622
+ const useA = this.next === 0;
1623
+ const buf = useA ? this.bufA : this.bufB;
1624
+ const stale = useA ? this.staleA : this.staleB;
1625
+ for (let i = 0; i < stale.length; i += 1) {
1626
+ const slot = stale[i];
1627
+ buf[4 * slot + 3] = base[4 * slot + 3] * alphaOf(slot);
1628
+ this.slotsRewritten += 1;
1629
+ }
1630
+ stale.length = 0;
1631
+ this.next = useA ? 1 : 0;
1632
+ return buf;
1633
+ }
1634
+ /** Drop the buffers entirely (scene teardown). */
1635
+ reset() {
1636
+ this.bufA = null;
1637
+ this.bufB = null;
1638
+ this.staleA.length = 0;
1639
+ this.staleB.length = 0;
1640
+ this.seededBase = null;
1641
+ this.seededCount = -1;
1642
+ this.seededDimAlpha = NaN;
1643
+ this.seededEpoch = void 0;
1644
+ this.next = 0;
1645
+ }
1646
+ seedOne(base, count, alphaOf, reuse) {
1647
+ const out = reuse !== null && reuse.length === base.length ? reuse : new Float32Array(base.length);
1648
+ out.set(base);
1649
+ for (let i = 0; i < count; i += 1) {
1650
+ const a = alphaOf(i);
1651
+ if (a !== 1) out[4 * i + 3] = base[4 * i + 3] * a;
1652
+ }
1653
+ return out;
1654
+ }
1655
+ };
1656
+
1657
+ // src/perf.ts
1658
+ var PRESSURE_WINDOW_MS = 250;
1659
+ var DROPPED_FRAME_MS = 34;
1660
+ var SLEEP_GAP_MS = 250;
1661
+ var PressureSampler = class {
1662
+ lastFrameAt = NaN;
1663
+ windowStart = NaN;
1664
+ windowSum = 0;
1665
+ windowCount = 0;
1666
+ ewma = NaN;
1667
+ worst = 0;
1668
+ frames = 0;
1669
+ dropped = 0;
1670
+ windows = 0;
1671
+ idleWakeups = 0;
1672
+ /**
1673
+ * Record one onFrame tick. `settled` marks a tick that arrived while the
1674
+ * scene was at rest (sim settled, no pending commit work) — the idle-
1675
+ * wakeup counter, which reads 0 when the ADR-005 gated clock is honest.
1676
+ */
1677
+ noteFrame(timeMs, settled) {
1678
+ this.frames += 1;
1679
+ if (settled) this.idleWakeups += 1;
1680
+ const prev = this.lastFrameAt;
1681
+ this.lastFrameAt = timeMs;
1682
+ if (Number.isNaN(prev)) {
1683
+ this.windowStart = timeMs;
1684
+ return;
1685
+ }
1686
+ const delta = timeMs - prev;
1687
+ if (delta < 0) {
1688
+ this.windowStart = timeMs;
1689
+ this.windowSum = 0;
1690
+ this.windowCount = 0;
1691
+ return;
1692
+ }
1693
+ if (delta >= SLEEP_GAP_MS) {
1694
+ this.closeWindow();
1695
+ this.windowStart = timeMs;
1696
+ return;
1697
+ }
1698
+ if (delta > this.worst) this.worst = delta;
1699
+ if (delta >= DROPPED_FRAME_MS) this.dropped += 1;
1700
+ this.windowSum += delta;
1701
+ this.windowCount += 1;
1702
+ if (timeMs - this.windowStart >= PRESSURE_WINDOW_MS) {
1703
+ this.closeWindow();
1704
+ this.windowStart = timeMs;
1705
+ }
1706
+ }
1707
+ snapshot() {
1708
+ return {
1709
+ frameEwmaMs: this.ewma,
1710
+ worstFrameMs: this.worst,
1711
+ frames: this.frames,
1712
+ droppedFrames: this.dropped,
1713
+ windows: this.windows,
1714
+ idleWakeups: this.idleWakeups
1715
+ };
1716
+ }
1717
+ /** Zero the accumulated counters (EWMA and frame anchor survive — the
1718
+ * smoothing is continuous; the counters are per-sample-period). */
1719
+ resetCounters() {
1720
+ this.worst = 0;
1721
+ this.frames = 0;
1722
+ this.dropped = 0;
1723
+ this.windows = 0;
1724
+ this.idleWakeups = 0;
1725
+ }
1726
+ closeWindow() {
1727
+ if (this.windowCount === 0) return;
1728
+ const mean = this.windowSum / this.windowCount;
1729
+ this.windows += 1;
1730
+ this.ewma = Number.isNaN(this.ewma) ? mean : this.ewma * 0.7 + mean * 0.3;
1731
+ this.windowSum = 0;
1732
+ this.windowCount = 0;
1733
+ }
1734
+ };
1735
+
1736
+ // src/degrade.ts
1737
+ var SCALE_LIMITS_DEFAULTS = Object.freeze({
1738
+ domLabelNodes: 1e5,
1739
+ pickingLinks: 25e4,
1740
+ histogramBatchNodes: 5e5,
1741
+ hysteresis: 0.1,
1742
+ minimumDwellMs: 1e3,
1743
+ resourceDegradationOrder: Object.freeze(["disable-transitions", "defer-images"])
1744
+ });
1745
+ var RESOURCE_STEPS = /* @__PURE__ */ new Set([
1746
+ "disable-transitions",
1747
+ "defer-images",
1748
+ "uniform-link-style"
1749
+ ]);
1750
+ function resolveScaleLimits(input) {
1751
+ const warnings = [];
1752
+ const out = { ...SCALE_LIMITS_DEFAULTS };
1753
+ if (input === void 0) return { limits: out, warnings };
1754
+ const num3 = (key) => {
1755
+ const v = input[key];
1756
+ if (v === void 0) return;
1757
+ if (typeof v === "number" && Number.isFinite(v) && v >= 0) out[key] = v;
1758
+ else warnings.push(`limits.${key} must be a non-negative finite number (got ${String(v)})`);
1759
+ };
1760
+ num3("domLabelNodes");
1761
+ num3("pickingLinks");
1762
+ num3("histogramBatchNodes");
1763
+ num3("minimumDwellMs");
1764
+ if (input.hysteresis !== void 0) {
1765
+ const h = input.hysteresis;
1766
+ if (typeof h === "number" && Number.isFinite(h) && h >= 0 && h < 1) out.hysteresis = h;
1767
+ else warnings.push(`limits.hysteresis must be in [0, 1) (got ${String(h)})`);
1768
+ }
1769
+ if (input.resourceDegradationOrder !== void 0) {
1770
+ const order = input.resourceDegradationOrder;
1771
+ const valid = Array.isArray(order) && order.every((s) => RESOURCE_STEPS.has(s)) && new Set(order).size === order.length;
1772
+ if (valid) out.resourceDegradationOrder = Object.freeze([...order]);
1773
+ else {
1774
+ warnings.push(
1775
+ "limits.resourceDegradationOrder must be unique resource steps ('disable-transitions' | 'defer-images' | 'uniform-link-style')"
1776
+ );
1777
+ }
1778
+ }
1779
+ return { limits: Object.freeze(out), warnings };
1780
+ }
1781
+ var DegradeController = class {
1782
+ constructor(limits, now) {
1783
+ this.limits = limits;
1784
+ this.now = now;
1785
+ }
1786
+ limits;
1787
+ now;
1788
+ steps = /* @__PURE__ */ new Map();
1789
+ activeCache = Object.freeze([]);
1790
+ isEngaged(step) {
1791
+ return this.steps.get(step)?.engaged === true;
1792
+ }
1793
+ /** Stable frozen list for GraphPerfSnapshot.activeDegradations. */
1794
+ activeSteps() {
1795
+ return this.activeCache;
1796
+ }
1797
+ /**
1798
+ * Edge-triggered count evaluation over the CURRENT visible counts.
1799
+ * Returns the events to emit (empty when nothing crossed its band or a
1800
+ * dwell is holding). Count-engaged steps disengage here; pressure- or
1801
+ * resource-engaged steps do NOT auto-disengage on counts — their signal
1802
+ * owns them (`clearPressure` / `releaseResourceSteps`).
1803
+ */
1804
+ evaluateCounts(visible) {
1805
+ const events = [];
1806
+ const counts = [
1807
+ ["cap-dom-labels", visible.nodes, this.limits.domLabelNodes],
1808
+ ["defer-link-picking", visible.edges, this.limits.pickingLinks],
1809
+ ["batch-histograms", visible.nodes, this.limits.histogramBatchNodes]
1810
+ ];
1811
+ for (const [step, value, limit] of counts) {
1812
+ const state = this.stateOf(step);
1813
+ if (!state.engaged) {
1814
+ if (value > limit && this.dwellOver(state)) {
1815
+ events.push(this.transition(step, state, true, "count", visible));
1816
+ }
1817
+ } else if (state.reason === "count") {
1818
+ if (value < limit * (1 - this.limits.hysteresis) && this.dwellOver(state)) {
1819
+ events.push(this.transition(step, state, false, "count", visible));
1820
+ }
1821
+ }
1822
+ }
1823
+ return events;
1824
+ }
1825
+ /**
1826
+ * Pressure trigger (frame or input): engage a step EARLIER than its count
1827
+ * hint. No-op (null) when already engaged or dwell-held.
1828
+ */
1829
+ engageForPressure(step, reason, visible) {
1830
+ const state = this.stateOf(step);
1831
+ if (state.engaged || !this.dwellOver(state)) return null;
1832
+ return this.transition(step, state, true, reason, visible);
1833
+ }
1834
+ /** Release a pressure-engaged step once its signal normalizes. */
1835
+ clearPressure(step, visible) {
1836
+ const state = this.stateOf(step);
1837
+ if (!state.engaged) return null;
1838
+ if (state.reason !== "frame-pressure" && state.reason !== "input-pressure") return null;
1839
+ if (!this.dwellOver(state)) return null;
1840
+ return this.transition(step, state, false, state.reason, visible);
1841
+ }
1842
+ /**
1843
+ * Resource-admission trigger: engage the NEXT not-yet-engaged step in the
1844
+ * declared order. Null when the order is exhausted — the caller must then
1845
+ * REJECT before allocating (§17: never silently erase semantic styling).
1846
+ */
1847
+ engageNextResourceStep(visible) {
1848
+ for (const step of this.limits.resourceDegradationOrder) {
1849
+ const state = this.stateOf(step);
1850
+ if (state.engaged) continue;
1851
+ return this.transition(step, state, true, "resource-estimate", visible);
1852
+ }
1853
+ return null;
1854
+ }
1855
+ /** Release every resource-engaged step (pressure cleared / new budget). */
1856
+ releaseResourceSteps(visible) {
1857
+ const events = [];
1858
+ for (const step of this.limits.resourceDegradationOrder) {
1859
+ const state = this.stateOf(step);
1860
+ if (state.engaged && state.reason === "resource-estimate") {
1861
+ events.push(this.transition(step, state, false, "resource-estimate", visible));
1862
+ }
1863
+ }
1864
+ return events;
1865
+ }
1866
+ stateOf(step) {
1867
+ let state = this.steps.get(step);
1868
+ if (state === void 0) {
1869
+ state = { engaged: false, changedAt: -Infinity, reason: null };
1870
+ this.steps.set(step, state);
1871
+ }
1872
+ return state;
1873
+ }
1874
+ dwellOver(state) {
1875
+ return this.now() - state.changedAt >= this.limits.minimumDwellMs || state.changedAt === -Infinity;
1876
+ }
1877
+ transition(step, state, engaged, reason, visible) {
1878
+ state.engaged = engaged;
1879
+ state.changedAt = this.now();
1880
+ state.reason = engaged ? reason : null;
1881
+ const active = [];
1882
+ for (const [s, st] of this.steps) if (st.engaged) active.push(s);
1883
+ this.activeCache = Object.freeze(active);
1884
+ return { step, engaged, reason, visible: { nodes: visible.nodes, edges: visible.edges } };
1885
+ }
1886
+ };
1516
1887
 
1517
1888
  // src/linkPick.ts
1518
1889
  var MEDIAN_SAMPLE_CAP = 1024;
@@ -2492,8 +2863,8 @@ function buildEntries(nodes, fields) {
2492
2863
  fieldValues = [];
2493
2864
  fieldLowers = [];
2494
2865
  const attrs = node.attrs;
2495
- for (const field of fields) {
2496
- const raw = attrs?.[field];
2866
+ for (const field2 of fields) {
2867
+ const raw = attrs?.[field2];
2497
2868
  if (raw === void 0 || raw === null) continue;
2498
2869
  const value = String(raw);
2499
2870
  fieldValues.push(value);
@@ -2595,11 +2966,392 @@ function createLocalSearchService(getBase) {
2595
2966
  };
2596
2967
  }
2597
2968
 
2969
+ // src/columnar.ts
2970
+ function isColumnarSnapshot(input) {
2971
+ return input.kind === "columnar";
2972
+ }
2973
+ var isTypedLength = (arr, n) => arr !== void 0 && arr.length === n;
2974
+ function checkStringColumn(col, rows, where, isIds, out) {
2975
+ if (col.kind !== "string" || !Array.isArray(col.dictionary) || !(col.codes instanceof Uint32Array)) {
2976
+ out.push({ where, problem: "not-a-string-column", detail: 'expected {kind:"string", dictionary, codes}' });
2977
+ return;
2978
+ }
2979
+ if (col.codes.length !== rows) {
2980
+ const detached = col.codes.length === 0 && col.codes.buffer.byteLength === 0;
2981
+ out.push({
2982
+ where,
2983
+ problem: "length-mismatch",
2984
+ detail: detached ? `codes buffer is DETACHED (a bufferOwnership:'transfer' snapshot is single-use)` : `codes.length ${col.codes.length} !== length ${rows}`
2985
+ });
2986
+ return;
2987
+ }
2988
+ if (col.nulls !== void 0) {
2989
+ if (col.nulls.length !== rows) {
2990
+ out.push({
2991
+ where,
2992
+ problem: "nulls-length-mismatch",
2993
+ detail: `nulls.length ${col.nulls.length} !== length ${rows}`
2994
+ });
2995
+ } else if (isIds) {
2996
+ for (let i = 0; i < rows; i++) {
2997
+ if (col.nulls[i] !== 0) {
2998
+ out.push({ where, problem: "null-id", detail: `row ${i}: ids may not be null` });
2999
+ break;
3000
+ }
3001
+ }
3002
+ }
3003
+ }
3004
+ const dictSize = col.dictionary.length;
3005
+ for (let i = 0; i < rows; i++) {
3006
+ if (col.codes[i] >= dictSize) {
3007
+ out.push({
3008
+ where,
3009
+ problem: "code-out-of-range",
3010
+ detail: `row ${i}: code ${col.codes[i]} >= dictionary size ${dictSize}`
3011
+ });
3012
+ break;
3013
+ }
3014
+ }
3015
+ }
3016
+ function checkColumn(col, rows, where, out) {
3017
+ switch (col.kind) {
3018
+ case "string":
3019
+ checkStringColumn(col, rows, where, false, out);
3020
+ return;
3021
+ case "f64":
3022
+ case "i32":
3023
+ case "u32":
3024
+ case "bool": {
3025
+ if (!isTypedLength(col.data, rows)) {
3026
+ out.push({
3027
+ where,
3028
+ problem: "length-mismatch",
3029
+ detail: `data.length ${col.data?.length ?? "missing"} !== length ${rows}`
3030
+ });
3031
+ }
3032
+ if (col.nulls !== void 0 && col.nulls.length !== rows) {
3033
+ out.push({
3034
+ where,
3035
+ problem: "nulls-length-mismatch",
3036
+ detail: `nulls.length ${col.nulls.length} !== length ${rows}`
3037
+ });
3038
+ }
3039
+ return;
3040
+ }
3041
+ default:
3042
+ out.push({
3043
+ where,
3044
+ problem: "bad-column-kind",
3045
+ detail: `unknown column kind '${String(col.kind)}'`
3046
+ });
3047
+ }
3048
+ }
3049
+ function validateColumnarStructure(snapshot) {
3050
+ const issues = [];
3051
+ const nodeRows = snapshot.nodes?.length;
3052
+ const edgeRows = snapshot.edges?.length;
3053
+ if (!Number.isInteger(nodeRows) || nodeRows < 0) {
3054
+ issues.push({ where: "nodes.length", problem: "bad-length", detail: String(nodeRows) });
3055
+ return issues;
3056
+ }
3057
+ if (!Number.isInteger(edgeRows) || edgeRows < 0) {
3058
+ issues.push({ where: "edges.length", problem: "bad-length", detail: String(edgeRows) });
3059
+ return issues;
3060
+ }
3061
+ checkStringColumn(snapshot.nodes.ids, nodeRows, "nodes.ids", true, issues);
3062
+ for (const [name, col] of Object.entries(snapshot.nodes.columns ?? {})) {
3063
+ checkColumn(col, nodeRows, `nodes.${name}`, issues);
3064
+ }
3065
+ checkStringColumn(snapshot.edges.ids, edgeRows, "edges.ids", true, issues);
3066
+ for (const [name, col] of Object.entries(snapshot.edges.columns ?? {})) {
3067
+ checkColumn(col, edgeRows, `edges.${name}`, issues);
3068
+ }
3069
+ const { source, target } = snapshot.edges;
3070
+ if (!(source instanceof Uint32Array) || source.length !== edgeRows) {
3071
+ issues.push({
3072
+ where: "edges.source",
3073
+ problem: "endpoint-length-mismatch",
3074
+ detail: `source.length ${source?.length ?? "missing"} !== length ${edgeRows}`
3075
+ });
3076
+ }
3077
+ if (!(target instanceof Uint32Array) || target.length !== edgeRows) {
3078
+ issues.push({
3079
+ where: "edges.target",
3080
+ problem: "endpoint-length-mismatch",
3081
+ detail: `target.length ${target?.length ?? "missing"} !== length ${edgeRows}`
3082
+ });
3083
+ }
3084
+ if (issues.length === 0) {
3085
+ for (let i = 0; i < edgeRows; i++) {
3086
+ if (source[i] >= nodeRows || target[i] >= nodeRows) {
3087
+ issues.push({
3088
+ where: "edges.endpoints",
3089
+ problem: "endpoint-out-of-range",
3090
+ detail: `row ${i}: (${source[i]}, ${target[i]}) with ${nodeRows} nodes`
3091
+ });
3092
+ break;
3093
+ }
3094
+ }
3095
+ }
3096
+ return issues;
3097
+ }
3098
+ function columnValueAt(col, i) {
3099
+ if (col.nulls !== void 0 && col.nulls[i] !== 0) return null;
3100
+ switch (col.kind) {
3101
+ case "string":
3102
+ return col.dictionary[col.codes[i]];
3103
+ case "bool":
3104
+ return col.data[i] !== 0;
3105
+ default:
3106
+ return col.data[i];
3107
+ }
3108
+ }
3109
+ function materializeColumnarSnapshot(snapshot) {
3110
+ const nodeCols = Object.entries(snapshot.nodes.columns ?? {});
3111
+ const nodeIds = snapshot.nodes.ids;
3112
+ const nodes = new Array(snapshot.nodes.length);
3113
+ for (let i = 0; i < snapshot.nodes.length; i++) {
3114
+ const attrs = {};
3115
+ for (const [name, col] of nodeCols) attrs[name] = columnValueAt(col, i);
3116
+ nodes[i] = { id: nodeIds.dictionary[nodeIds.codes[i]], attrs };
3117
+ }
3118
+ const edgeCols = Object.entries(snapshot.edges.columns ?? {});
3119
+ const edgeIds = snapshot.edges.ids;
3120
+ const { source, target } = snapshot.edges;
3121
+ const edges = new Array(snapshot.edges.length);
3122
+ for (let i = 0; i < snapshot.edges.length; i++) {
3123
+ const attrs = {};
3124
+ for (const [name, col] of edgeCols) attrs[name] = columnValueAt(col, i);
3125
+ edges[i] = {
3126
+ id: edgeIds.dictionary[edgeIds.codes[i]],
3127
+ source: nodes[source[i]].id,
3128
+ target: nodes[target[i]].id,
3129
+ attrs
3130
+ };
3131
+ }
3132
+ return {
3133
+ datasetKey: snapshot.datasetKey,
3134
+ sourceRevision: snapshot.sourceRevision,
3135
+ nodes,
3136
+ edges
3137
+ };
3138
+ }
3139
+ function buildAcceptedFromColumnar(snapshot, acceptance) {
3140
+ const nodeCols = Object.entries(snapshot.nodes.columns ?? {});
3141
+ const nodeIds = snapshot.nodes.ids;
3142
+ const nodes = new Array(acceptance.acceptedNodeCount);
3143
+ const nodeIndex = /* @__PURE__ */ new Map();
3144
+ let outN = 0;
3145
+ for (let i = 0; i < snapshot.nodes.length; i++) {
3146
+ if (acceptance.keepNodes[i] !== 1) continue;
3147
+ const attrs = {};
3148
+ for (const [name, col] of nodeCols) attrs[name] = columnValueAt(col, i);
3149
+ const id = nodeIds.dictionary[nodeIds.codes[i]];
3150
+ nodeIndex.set(id, outN);
3151
+ nodes[outN] = { id, attrs };
3152
+ outN += 1;
3153
+ }
3154
+ const edgeCols = Object.entries(snapshot.edges.columns ?? {});
3155
+ const edgeIds = snapshot.edges.ids;
3156
+ const { source, target } = snapshot.edges;
3157
+ const edges = new Array(acceptance.acceptedEdgeCount);
3158
+ let outE = 0;
3159
+ for (let e = 0; e < snapshot.edges.length; e++) {
3160
+ if (acceptance.keepEdges[e] !== 1) continue;
3161
+ const attrs = {};
3162
+ for (const [name, col] of edgeCols) attrs[name] = columnValueAt(col, e);
3163
+ edges[outE] = {
3164
+ id: edgeIds.dictionary[edgeIds.codes[e]],
3165
+ // Endpoint STRINGS resolve through the original row (a dropped
3166
+ // duplicate row shares its survivor's id string by construction).
3167
+ source: nodeIds.dictionary[nodeIds.codes[source[e]]],
3168
+ target: nodeIds.dictionary[nodeIds.codes[target[e]]],
3169
+ attrs
3170
+ };
3171
+ outE += 1;
3172
+ }
3173
+ return {
3174
+ datasetKey: snapshot.datasetKey,
3175
+ sourceRevision: snapshot.sourceRevision,
3176
+ nodes,
3177
+ edges,
3178
+ nodeIndex,
3179
+ diagnostics: acceptance.diagnostics
3180
+ };
3181
+ }
3182
+ function columnarArrayBuffers(snapshot) {
3183
+ const buffers = /* @__PURE__ */ new Set();
3184
+ const add = (view) => {
3185
+ if (view !== void 0 && view.buffer instanceof ArrayBuffer) buffers.add(view.buffer);
3186
+ };
3187
+ const addColumn = (col) => {
3188
+ if (col.kind === "string") add(col.codes);
3189
+ else add(col.data);
3190
+ add(col.nulls);
3191
+ };
3192
+ addColumn(snapshot.nodes.ids);
3193
+ for (const col of Object.values(snapshot.nodes.columns ?? {})) addColumn(col);
3194
+ addColumn(snapshot.edges.ids);
3195
+ for (const col of Object.values(snapshot.edges.columns ?? {})) addColumn(col);
3196
+ add(snapshot.edges.source);
3197
+ add(snapshot.edges.target);
3198
+ return [...buffers];
3199
+ }
3200
+ function detachColumnarBuffers(snapshot) {
3201
+ let detached = 0;
3202
+ for (const buffer of columnarArrayBuffers(snapshot)) {
3203
+ if (buffer.byteLength === 0) continue;
3204
+ const transferable = buffer;
3205
+ if (typeof transferable.transfer === "function") {
3206
+ transferable.transfer();
3207
+ } else {
3208
+ structuredClone(buffer, { transfer: [buffer] });
3209
+ }
3210
+ detached += 1;
3211
+ }
3212
+ return detached;
3213
+ }
3214
+
3215
+ // src/worker/lane.ts
3216
+ function defaultWorkerUrl() {
3217
+ return new URL("./worker/entry.js", import.meta.url);
3218
+ }
3219
+ function transportFromWorker(worker) {
3220
+ return {
3221
+ post: (envelope, transfers) => worker.postMessage(envelope, [...transfers]),
3222
+ onReply: (cb) => {
3223
+ worker.onmessage = (ev) => cb(ev.data);
3224
+ },
3225
+ onError: (cb) => {
3226
+ worker.onerror = (ev) => cb(ev.message !== "" ? ev.message : "worker error");
3227
+ worker.onmessageerror = () => cb("worker messageerror (unclonable reply)");
3228
+ },
3229
+ terminate: () => worker.terminate()
3230
+ };
3231
+ }
3232
+ var WorkerLane = class {
3233
+ options;
3234
+ sequencer = new EnvelopeSequencer();
3235
+ ledger = new RequestLedger();
3236
+ pending = /* @__PURE__ */ new Map();
3237
+ transport = null;
3238
+ /** null = not yet booted; false = boot failed (permanent for this lane). */
3239
+ availableState = null;
3240
+ constructor(options = {}) {
3241
+ this.options = options;
3242
+ }
3243
+ available() {
3244
+ return this.availableState;
3245
+ }
3246
+ /** Synchronous boot probe for callers that must pick a lane NOW (the
3247
+ * instance's sync applyHostUpdate cannot await a rejected request). */
3248
+ ensureBooted() {
3249
+ return this.boot();
3250
+ }
3251
+ /** Boot lazily on first use. A throwing factory (no Worker global, CSP,
3252
+ * missing asset) marks the lane unavailable FOREVER — one diagnostic,
3253
+ * then the caller's main path owns every subsequent request. */
3254
+ boot() {
3255
+ if (this.availableState !== null) return this.availableState;
3256
+ try {
3257
+ let transport = this.options.transport ?? null;
3258
+ if (transport === null) {
3259
+ const factory = this.options.factory;
3260
+ const worker = factory !== void 0 && "create" in factory ? factory.create() : new Worker(
3261
+ factory !== void 0 && "url" in factory ? factory.url : defaultWorkerUrl(),
3262
+ { type: "module" }
3263
+ );
3264
+ transport = transportFromWorker(worker);
3265
+ }
3266
+ transport.onReply((reply) => this.settle(reply));
3267
+ transport.onError?.((reason) => this.fail(reason));
3268
+ this.transport = transport;
3269
+ this.availableState = true;
3270
+ } catch (err) {
3271
+ this.availableState = false;
3272
+ this.options.onUnavailable?.(err instanceof Error ? err.message : String(err));
3273
+ }
3274
+ return this.availableState;
3275
+ }
3276
+ /** Async worker death (P1: a constructed-but-dead worker must strand
3277
+ * NOTHING): every pending request rejects as 'worker-failed' so callers
3278
+ * run their main-lane fallback, the lane goes permanently unavailable,
3279
+ * and the unavailability callback fires (the instance one-shots it). */
3280
+ fail(reason) {
3281
+ if (this.availableState === false) return;
3282
+ this.availableState = false;
3283
+ this.options.onUnavailable?.(reason);
3284
+ for (const [id, entry] of this.pending) {
3285
+ this.pending.delete(id);
3286
+ entry.reject(new Error("worker-failed"));
3287
+ }
3288
+ this.ledger.abortAll();
3289
+ this.transport?.terminate();
3290
+ this.transport = null;
3291
+ }
3292
+ settle(reply) {
3293
+ const original = this.ledger.settle(reply);
3294
+ if (reply.inReplyTo === void 0) return;
3295
+ const entry = this.pending.get(reply.inReplyTo);
3296
+ this.pending.delete(reply.inReplyTo);
3297
+ if (entry === void 0) return;
3298
+ if (original === null) {
3299
+ entry.reject(new Error("superseded"));
3300
+ return;
3301
+ }
3302
+ entry.resolve(reply);
3303
+ }
3304
+ /**
3305
+ * Send one request. Resolves with the reply envelope (op 'result' or
3306
+ * 'error' — protocol errors are DATA to the caller, not exceptions);
3307
+ * rejects only on supersession/abort/unavailability.
3308
+ */
3309
+ request(epoch, entity, op, payload, transfers, klass, lane) {
3310
+ if (!this.boot()) {
3311
+ return Promise.reject(new Error("worker-unavailable"));
3312
+ }
3313
+ const envelope = this.sequencer.make(epoch, entity, op, payload);
3314
+ const signal = this.ledger.track(envelope, klass, lane);
3315
+ return new Promise((resolve, reject) => {
3316
+ if (signal.aborted) {
3317
+ reject(new Error("superseded"));
3318
+ return;
3319
+ }
3320
+ signal.addEventListener(
3321
+ "abort",
3322
+ () => {
3323
+ if (this.pending.delete(envelope.msgId)) reject(new Error("superseded"));
3324
+ },
3325
+ { once: true }
3326
+ );
3327
+ this.pending.set(envelope.msgId, { resolve, reject });
3328
+ this.transport.post(envelope, transfers);
3329
+ });
3330
+ }
3331
+ /** Abort everything in flight (epoch advance / detach / dataset swap).
3332
+ * The ledger's controllers fire each pending promise's abort listener;
3333
+ * the sweep below catches anything tracked before a listener attached —
3334
+ * rejects stay idempotent through the delete guard. */
3335
+ abortAll() {
3336
+ this.ledger.abortAll();
3337
+ for (const [id, entry] of this.pending) {
3338
+ this.pending.delete(id);
3339
+ entry.reject(new Error("aborted"));
3340
+ }
3341
+ }
3342
+ terminate() {
3343
+ this.abortAll();
3344
+ this.transport?.terminate();
3345
+ this.transport = null;
3346
+ this.availableState = null;
3347
+ }
3348
+ };
3349
+
2598
3350
  // src/filter.ts
2599
- function resolveFilterField(item, field) {
2600
- if (field === "id") return item.id;
3351
+ function resolveFilterField(item, field2) {
3352
+ if (field2 === "id") return item.id;
2601
3353
  const attrs = item.attrs;
2602
- return attrs?.[field];
3354
+ return attrs?.[field2];
2603
3355
  }
2604
3356
  function filterValuesEqual(a, b) {
2605
3357
  if (typeof a === "number" && typeof b === "number") {
@@ -2769,7 +3521,7 @@ function compileSelector(selector) {
2769
3521
  };
2770
3522
  }
2771
3523
  let current = null;
2772
- const resolve = (field) => current === null ? void 0 : resolveFilterField(current, field);
3524
+ const resolve = (field2) => current === null ? void 0 : resolveFilterField(current, field2);
2773
3525
  return {
2774
3526
  errors,
2775
3527
  test(item) {
@@ -3047,6 +3799,16 @@ var MetricStore = class {
3047
3799
  /** Admitted async columns by metric name; NaN encodes null. */
3048
3800
  columns = /* @__PURE__ */ new Map();
3049
3801
  degreePasses = 0;
3802
+ /** §17 telemetry: estimated bytes of metric storage held (S13-T07). */
3803
+ estimatedBytes() {
3804
+ let bytes = 0;
3805
+ for (const col of this.columns.values()) bytes += col.byteLength;
3806
+ const dc = this.degreeCache;
3807
+ if (dc !== null) {
3808
+ bytes += dc.degree.byteLength + dc.inDegree.byteLength + dc.outDegree.byteLength;
3809
+ }
3810
+ return bytes;
3811
+ }
3050
3812
  /** Number of combined degree-family compute passes (test observability). */
3051
3813
  get degreeComputePasses() {
3052
3814
  return this.degreePasses;
@@ -3278,6 +4040,7 @@ function resolveEnginePolicy(capabilities, requested) {
3278
4040
  images: imagesNative ? "native" : "placeholder",
3279
4041
  linkPicking: capabilities.linkPicking ? "native" : "cpu-fallback",
3280
4042
  clusterForce: clusterForceNative ? "native" : "inert",
4043
+ quiescence: capabilities.idleFrames === "stops" ? "stops" : "free-running",
3281
4044
  // Defensive snapshot: the policy must not alias the caller's array.
3282
4045
  rangedChannels: new Set(capabilities.rangeUpdates),
3283
4046
  degradations: Object.freeze(degradations)
@@ -3302,12 +4065,27 @@ function assertCapabilityMethodParity(engine) {
3302
4065
  }
3303
4066
  function normalizeCommitForCapabilities(commit, capabilities) {
3304
4067
  const config = commit.config;
4068
+ const declaredRanged = new Set(capabilities.rangeUpdates);
4069
+ const undeclaredPatches = commit.bufferPatches !== void 0 ? Object.keys(commit.bufferPatches).filter(
4070
+ (ch) => !declaredRanged.has(ch)
4071
+ ) : [];
3305
4072
  const dropResources = capabilities.pointImages !== true && commit.resources !== void 0;
3306
4073
  const dropLinkArrows = capabilities.edgeArrows !== true && config !== void 0 && config.linkArrows !== void 0;
3307
4074
  const dropCluster = capabilities.clusterForce !== true && config !== void 0 && config.cluster !== void 0;
3308
- if (!dropResources && !dropLinkArrows && !dropCluster) return { commit, dropped: [] };
4075
+ if (!dropResources && !dropLinkArrows && !dropCluster && undeclaredPatches.length === 0) {
4076
+ return { commit, dropped: [] };
4077
+ }
3309
4078
  const dropped = [];
3310
4079
  const next = { ...commit };
4080
+ if (undeclaredPatches.length > 0) {
4081
+ const kept = { ...next.bufferPatches };
4082
+ for (const ch of undeclaredPatches) {
4083
+ delete kept[ch];
4084
+ dropped.push(`bufferPatches.${ch}`);
4085
+ }
4086
+ if (Object.keys(kept).length > 0) next.bufferPatches = kept;
4087
+ else delete next.bufferPatches;
4088
+ }
3311
4089
  if (dropResources) {
3312
4090
  delete next.resources;
3313
4091
  dropped.push("resources");
@@ -3660,7 +4438,7 @@ var Lane = class {
3660
4438
  }
3661
4439
  };
3662
4440
  function newMembership(capacity) {
3663
- return { flags: new Uint8Array(capacity), list: [] };
4441
+ return { flags: new Uint8Array(capacity), list: [], holes: 0 };
3664
4442
  }
3665
4443
  function growMembership(mem, capacity) {
3666
4444
  if (capacity <= mem.flags.length) return;
@@ -3686,6 +4464,12 @@ var SoftMask = class {
3686
4464
  overflowedFlag = false;
3687
4465
  /** Total memberships currently held across all sources and lanes. */
3688
4466
  totalHeld = 0;
4467
+ /** §17 O(Δ) op counters (F10-02 gate instrumentation). */
4468
+ statsBox = {
4469
+ slotsVisited: 0,
4470
+ zeroCrossings: 0,
4471
+ cascadeEdgesVisited: 0
4472
+ };
3689
4473
  constructor(nodeCapacity, edgeCapacity) {
3690
4474
  checkCapacity(nodeCapacity, "nodeCapacity");
3691
4475
  checkCapacity(edgeCapacity, "edgeCapacity");
@@ -3780,6 +4564,14 @@ var SoftMask = class {
3780
4564
  this.applyMembership(this.edgeDimLane, state.edgeDim, dimIdx);
3781
4565
  }
3782
4566
  },
4567
+ updateNodeFailures: (addHide, removeHide, crossings) => {
4568
+ ensureAlive();
4569
+ this.applyMembershipDelta(this.nodeHideLane, state.nodeHide, addHide, removeHide, crossings);
4570
+ },
4571
+ updateEdgeFailures: (addHide, removeHide) => {
4572
+ ensureAlive();
4573
+ this.applyMembershipDelta(this.edgeHideLane, state.edgeHide, addHide, removeHide);
4574
+ },
3783
4575
  clear: () => {
3784
4576
  ensureAlive();
3785
4577
  this.clearSource(state);
@@ -3833,6 +4625,34 @@ var SoftMask = class {
3833
4625
  this.cascadeSource ??= this.acquire("\xA79.1 node\u2192edge cascade");
3834
4626
  this.cascadeSource.setEdgeFailures(failing);
3835
4627
  }
4628
+ /**
4629
+ * O(incident-edges) delta form of the §9.1 cascade (F10-02): for each node
4630
+ * whose HIDE visibility crossed zero, recompute only its incident edges'
4631
+ * cascade state from the CURRENT node counters and apply the delta through
4632
+ * the same internal cascade source the full form uses — the two compose
4633
+ * freely (the full form re-baselines). Edges shared by two crossed nodes
4634
+ * are visited twice; the membership delta is idempotent, so the second
4635
+ * visit is an O(1) no-op. `incidence` must describe the SAME `links`
4636
+ * buffer (edge slot i ↔ links[2i]/[2i+1]).
4637
+ */
4638
+ applyNodeCascadeToEdgesDelta(links, incidence, crossedNodes) {
4639
+ if (crossedNodes.length === 0) return;
4640
+ const hide = this.nodeHideLane.counters;
4641
+ this.cascadeSource ??= this.acquire("\xA79.1 node\u2192edge cascade");
4642
+ const nowFailing = [];
4643
+ const nowClear = [];
4644
+ for (let k = 0; k < crossedNodes.length; k++) {
4645
+ const edges = incidentEdgesOf(incidence, crossedNodes[k]);
4646
+ for (let j = 0; j < edges.length; j++) {
4647
+ const edge = edges[j];
4648
+ this.statsBox.cascadeEdgesVisited += 1;
4649
+ const failing = hide[links[edge * 2]] !== 0 || hide[links[edge * 2 + 1]] !== 0;
4650
+ if (failing) nowFailing.push(edge);
4651
+ else nowClear.push(edge);
4652
+ }
4653
+ }
4654
+ this.cascadeSource.updateEdgeFailures(nowFailing, nowClear);
4655
+ }
3836
4656
  /**
3837
4657
  * Drains the zero-crossing dirty lists accumulated since the previous
3838
4658
  * drain. Only NET flips are emitted (state compared against the previous
@@ -3848,6 +4668,27 @@ var SoftMask = class {
3848
4668
  edgeVisibleCount: this.edgeHideLane.zeroCount
3849
4669
  };
3850
4670
  }
4671
+ /** §17 telemetry: estimated bytes of mask storage held (S13-T07):
4672
+ * four counter lanes (+pending trackers) and per-source flag columns. */
4673
+ estimatedBytes() {
4674
+ let bytes = 0;
4675
+ for (const lane of [this.nodeHideLane, this.nodeDimLane, this.edgeHideLane, this.edgeDimLane]) {
4676
+ bytes += lane.counters.byteLength + lane.pending.byteLength;
4677
+ }
4678
+ for (const src of this.sources) {
4679
+ bytes += src.nodeHide.flags.byteLength + src.nodeDim.flags.byteLength + src.edgeHide.flags.byteLength + src.edgeDim.flags.byteLength;
4680
+ }
4681
+ return bytes;
4682
+ }
4683
+ /** §17 O(Δ) op counters (live object — snapshot before comparing). */
4684
+ get stats() {
4685
+ return this.statsBox;
4686
+ }
4687
+ resetStats() {
4688
+ this.statsBox.slotsVisited = 0;
4689
+ this.statsBox.zeroCrossings = 0;
4690
+ this.statsBox.cascadeEdgesVisited = 0;
4691
+ }
3851
4692
  visibleNodeCount() {
3852
4693
  return this.nodeHideLane.zeroCount;
3853
4694
  }
@@ -3912,15 +4753,62 @@ var SoftMask = class {
3912
4753
  const prev = mem.list;
3913
4754
  for (let i = 0; i < prev.length; i++) {
3914
4755
  const slot = prev[i];
3915
- if ((flags[slot] & 2) === 0) {
3916
- flags[slot] = 0;
3917
- this.decrement(lane, slot);
3918
- }
4756
+ const f = flags[slot];
4757
+ if ((f & 2) !== 0) continue;
4758
+ if ((f & 1) !== 0) this.decrement(lane, slot);
4759
+ flags[slot] = 0;
3919
4760
  }
3920
4761
  for (let i = 0; i < next.length; i++) flags[next[i]] = 1;
3921
4762
  mem.list = next;
4763
+ mem.holes = 0;
3922
4764
  }
3923
- increment(lane, slot) {
4765
+ /**
4766
+ * O(Δ) delta ops on one lane membership (F10-02). Adds and removes are
4767
+ * idempotent per slot (adding a member / removing a non-member is a
4768
+ * no-op); removed slots leave HOLES in `list` (compacted past 50%), so
4769
+ * replace/clear passes must honor the bit0 guard above. `crossings`, when
4770
+ * given, is cleared and then receives this CALL's hide zero-crossings.
4771
+ */
4772
+ applyMembershipDelta(lane, mem, add, remove, crossings) {
4773
+ if (crossings !== void 0) {
4774
+ crossings.becameFailing.length = 0;
4775
+ crossings.becameClear.length = 0;
4776
+ }
4777
+ const capacity = lane.counters.length;
4778
+ const flags = mem.flags;
4779
+ const check = (slot) => {
4780
+ if (!Number.isInteger(slot) || slot < 0 || slot >= capacity) {
4781
+ throw new RangeError(`SoftMask: ${lane.label} slot ${slot} out of range [0, ${capacity})`);
4782
+ }
4783
+ };
4784
+ if (add !== null) for (let i = 0; i < add.length; i++) check(add[i]);
4785
+ if (remove !== null) for (let i = 0; i < remove.length; i++) check(remove[i]);
4786
+ if (add !== null) {
4787
+ for (let i = 0; i < add.length; i++) {
4788
+ const slot = add[i];
4789
+ this.statsBox.slotsVisited += 1;
4790
+ if ((flags[slot] & 1) !== 0) continue;
4791
+ flags[slot] = 1;
4792
+ mem.list.push(slot);
4793
+ this.increment(lane, slot, crossings);
4794
+ }
4795
+ }
4796
+ if (remove !== null) {
4797
+ for (let i = 0; i < remove.length; i++) {
4798
+ const slot = remove[i];
4799
+ this.statsBox.slotsVisited += 1;
4800
+ if ((flags[slot] & 1) === 0) continue;
4801
+ flags[slot] = 0;
4802
+ mem.holes += 1;
4803
+ this.decrement(lane, slot, crossings);
4804
+ }
4805
+ }
4806
+ if (mem.holes > mem.list.length >> 1) {
4807
+ mem.list = mem.list.filter((slot) => (flags[slot] & 1) !== 0);
4808
+ mem.holes = 0;
4809
+ }
4810
+ }
4811
+ increment(lane, slot, crossings) {
3924
4812
  this.totalHeld += 1;
3925
4813
  const before = lane.counters[slot];
3926
4814
  if (before === COUNTER_MAX) {
@@ -3930,16 +4818,20 @@ var SoftMask = class {
3930
4818
  lane.counters[slot] = before + 1;
3931
4819
  if (before === 0) {
3932
4820
  lane.zeroCount -= 1;
4821
+ this.statsBox.zeroCrossings += 1;
4822
+ crossings?.becameFailing.push(slot);
3933
4823
  this.markDirty(lane, slot, true);
3934
4824
  }
3935
4825
  }
3936
- decrement(lane, slot) {
4826
+ decrement(lane, slot, crossings) {
3937
4827
  this.totalHeld -= 1;
3938
4828
  const before = lane.counters[slot];
3939
4829
  if (before === 0) return;
3940
4830
  lane.counters[slot] = before - 1;
3941
4831
  if (before === 1) {
3942
4832
  lane.zeroCount += 1;
4833
+ this.statsBox.zeroCrossings += 1;
4834
+ crossings?.becameClear.push(slot);
3943
4835
  this.markDirty(lane, slot, false);
3944
4836
  }
3945
4837
  }
@@ -4355,7 +5247,15 @@ function upperBound(values, sorted, target) {
4355
5247
  }
4356
5248
  var TypedColumnCrossfilter = class {
4357
5249
  /** Test instrumentation; see CrossfilterStats. Reset with resetStats(). */
4358
- stats = { slotsWalked: 0, fullSorts: 0, permutationMerges: 0 };
5250
+ stats = {
5251
+ slotsWalked: 0,
5252
+ fullSorts: 0,
5253
+ permutationMerges: 0,
5254
+ binUpdates: 0,
5255
+ filteredRecomputes: 0
5256
+ };
5257
+ /** Count of dims whose filtered layer is live-maintained (F10-02). */
5258
+ liveDims = 0;
4359
5259
  dims = [];
4360
5260
  byKey = /* @__PURE__ */ new Map();
4361
5261
  specs = [];
@@ -4377,6 +5277,29 @@ var TypedColumnCrossfilter = class {
4377
5277
  this.stats.slotsWalked = 0;
4378
5278
  this.stats.fullSorts = 0;
4379
5279
  this.stats.permutationMerges = 0;
5280
+ this.stats.binUpdates = 0;
5281
+ this.stats.filteredRecomputes = 0;
5282
+ }
5283
+ /** §17 telemetry: estimated bytes of typed-column storage held (S13-T07).
5284
+ * Documented components: per-dim value/permutation/bin/code/pass arrays,
5285
+ * the global failure counter, and the external mask. */
5286
+ estimatedBytes() {
5287
+ let bytes = this.failCount.byteLength + (this.externalMask?.byteLength ?? 0);
5288
+ for (const d of this.dims) {
5289
+ bytes += d.pass.byteLength;
5290
+ if (d.kind === "categorical") {
5291
+ bytes += d.codes.byteLength;
5292
+ } else {
5293
+ bytes += d.values.byteLength + d.sorted.byteLength + d.slotBin.byteLength;
5294
+ }
5295
+ }
5296
+ return bytes;
5297
+ }
5298
+ /** F10-02 live-layer bookkeeping — the ONLY writer of `filteredLive`. */
5299
+ setFilteredLive(dim, live) {
5300
+ if (dim.filteredLive === live) return;
5301
+ dim.filteredLive = live;
5302
+ this.liveDims += live ? 1 : -1;
4380
5303
  }
4381
5304
  /**
4382
5305
  * (Re)initialize columns from scratch. Clears all brushes and the external
@@ -4426,7 +5349,9 @@ var TypedColumnCrossfilter = class {
4426
5349
  this.applyRangeTransition(dim, normalized.brush, delta);
4427
5350
  }
4428
5351
  dim.brush = normalized.brush;
4429
- for (const other of this.dims) if (other !== dim) other.filteredDirty = true;
5352
+ for (const other of this.dims) {
5353
+ if (other !== dim && !other.filteredLive) other.filteredDirty = true;
5354
+ }
4430
5355
  this.revision++;
4431
5356
  this.notify();
4432
5357
  return delta;
@@ -4452,7 +5377,10 @@ var TypedColumnCrossfilter = class {
4452
5377
  if (unchanged) return;
4453
5378
  this.externalMask = Uint8Array.from(passSlots);
4454
5379
  }
4455
- for (const d of this.dims) d.filteredDirty = true;
5380
+ for (const d of this.dims) {
5381
+ this.setFilteredLive(d, false);
5382
+ d.filteredDirty = true;
5383
+ }
4456
5384
  this.notify();
4457
5385
  }
4458
5386
  /**
@@ -4466,6 +5394,7 @@ var TypedColumnCrossfilter = class {
4466
5394
  if (dim.filteredDirty) {
4467
5395
  this.recomputeFiltered(dim);
4468
5396
  dim.filteredDirty = false;
5397
+ this.setFilteredLive(dim, true);
4469
5398
  }
4470
5399
  const bins = [];
4471
5400
  const categories = [];
@@ -4530,13 +5459,16 @@ var TypedColumnCrossfilter = class {
4530
5459
  this.externalMask = em;
4531
5460
  }
4532
5461
  this.n = newN;
5462
+ for (const dim of this.dims) {
5463
+ this.setFilteredLive(dim, false);
5464
+ dim.filteredDirty = true;
5465
+ }
4533
5466
  for (const dim of this.dims) {
4534
5467
  const pass = new Uint8Array(newN).fill(1);
4535
5468
  pass.set(dim.pass);
4536
5469
  dim.pass = pass;
4537
5470
  if (dim.kind === "categorical") this.appendCat(dim, newNodes, oldN);
4538
5471
  else this.appendRange(dim, newNodes, oldN);
4539
- dim.filteredDirty = true;
4540
5472
  }
4541
5473
  for (let s = oldN; s < newN; s++) {
4542
5474
  (this.failCount[s] === 0 ? delta.shown : delta.hidden).push(s);
@@ -4600,6 +5532,7 @@ var TypedColumnCrossfilter = class {
4600
5532
  return dim;
4601
5533
  }
4602
5534
  buildDims(nodes, specs) {
5535
+ this.liveDims = 0;
4603
5536
  const seen = /* @__PURE__ */ new Set();
4604
5537
  const dims = [];
4605
5538
  for (const spec of specs) {
@@ -4655,6 +5588,7 @@ var TypedColumnCrossfilter = class {
4655
5588
  invalidSlots,
4656
5589
  brush: null,
4657
5590
  filteredDirty: true,
5591
+ filteredLive: false,
4658
5592
  filteredBins: []
4659
5593
  };
4660
5594
  this.rebinRange(dim);
@@ -4696,6 +5630,7 @@ var TypedColumnCrossfilter = class {
4696
5630
  invalidSlots,
4697
5631
  brush: null,
4698
5632
  filteredDirty: true,
5633
+ filteredLive: false,
4699
5634
  filteredCats: []
4700
5635
  };
4701
5636
  }
@@ -4744,14 +5679,59 @@ var TypedColumnCrossfilter = class {
4744
5679
  const next = this.failCount[slot] - 1;
4745
5680
  this.failCount[slot] = next;
4746
5681
  if (next === 0) delta.shown.push(slot);
5682
+ if (this.liveDims > 0) this.maintainFiltered(dim, slot, next, 1);
4747
5683
  } else {
4748
5684
  if (dim.pass[slot] === 0) return;
4749
5685
  dim.pass[slot] = 0;
4750
5686
  const prev = this.failCount[slot];
4751
5687
  this.failCount[slot] = prev + 1;
4752
5688
  if (prev === 0) delta.hidden.push(slot);
5689
+ if (this.liveDims > 0) this.maintainFiltered(dim, slot, prev, -1);
4753
5690
  }
4754
5691
  }
5692
+ /**
5693
+ * F10-02 inline maintenance of LIVE filtered layers, dispatched from the
5694
+ * one place that knows the failCount transition. `boundary` is the
5695
+ * other-failures picture at the interesting side of the flip (after for
5696
+ * shown, before for hidden):
5697
+ * - 0 → the slot crossed the FULLY-VISIBLE boundary: every other live
5698
+ * layer counts it (own layers ignore the own-dim brush, so the brushed
5699
+ * dim's layer is provably unchanged by its own flip);
5700
+ * - 1 → exactly one OTHER dim still fails the slot: only that dim's
5701
+ * what-if-I-cleared-mine layer flips;
5702
+ * - ≥2 → no layer can change.
5703
+ * External-mask-excluded and hygiene-invalid slots contribute nothing
5704
+ * either way and are skipped.
5705
+ */
5706
+ maintainFiltered(brushed, slot, boundary, sign) {
5707
+ if (boundary > 1) return;
5708
+ const ext = this.externalMask;
5709
+ if (ext !== null && ext[slot] === 0) return;
5710
+ if (boundary === 0) {
5711
+ for (const d of this.dims) {
5712
+ if (d === brushed || !d.filteredLive) continue;
5713
+ this.adjustLayer(d, slot, sign);
5714
+ }
5715
+ return;
5716
+ }
5717
+ for (const d of this.dims) {
5718
+ if (d === brushed || d.pass[slot] !== 0) continue;
5719
+ if (d.filteredLive) this.adjustLayer(d, slot, sign);
5720
+ break;
5721
+ }
5722
+ }
5723
+ adjustLayer(d, slot, sign) {
5724
+ if (d.kind === "categorical") {
5725
+ const c = d.codes[slot];
5726
+ if (c < 0) return;
5727
+ d.filteredCats[c] = (d.filteredCats[c] ?? 0) + sign;
5728
+ } else {
5729
+ const b = d.slotBin[slot];
5730
+ if (b < 0) return;
5731
+ d.filteredBins[b] = (d.filteredBins[b] ?? 0) + sign;
5732
+ }
5733
+ this.stats.binUpdates += 1;
5734
+ }
4755
5735
  walkSorted(dim, from, to, nowPass, delta) {
4756
5736
  for (let i = from; i < to; i++) this.flip(dim, dim.sorted[i], nowPass, delta);
4757
5737
  }
@@ -4919,6 +5899,7 @@ var TypedColumnCrossfilter = class {
4919
5899
  }
4920
5900
  // --- lazy filtered layer -----------------------------------------------------
4921
5901
  recomputeFiltered(dim) {
5902
+ this.stats.filteredRecomputes += 1;
4922
5903
  const ext = this.externalMask;
4923
5904
  const fc = this.failCount;
4924
5905
  if (dim.kind === "categorical") {
@@ -5250,7 +6231,8 @@ var GRAPH_THEME_DARK = Object.freeze({
5250
6231
  edgeDefault: "rgba(255,255,255,0.15)",
5251
6232
  labelFg: "#e6e9f0",
5252
6233
  accent: "#3b82f6",
5253
- mutedAlpha: 0.15
6234
+ mutedAlpha: 0.15,
6235
+ emphasisRing: "#7aa2f7"
5254
6236
  });
5255
6237
  var GRAPH_THEME_LIGHT = Object.freeze({
5256
6238
  background: "#ffffff",
@@ -5258,7 +6240,8 @@ var GRAPH_THEME_LIGHT = Object.freeze({
5258
6240
  edgeDefault: "rgba(15,23,42,0.18)",
5259
6241
  labelFg: "#0f172a",
5260
6242
  accent: "#2563eb",
5261
- mutedAlpha: 0.2
6243
+ mutedAlpha: 0.2,
6244
+ emphasisRing: "#2563eb"
5262
6245
  });
5263
6246
  function resolveTheme(input) {
5264
6247
  const base = input !== void 0 && input.base === "light" ? GRAPH_THEME_LIGHT : GRAPH_THEME_DARK;
@@ -5270,10 +6253,11 @@ function resolveTheme(input) {
5270
6253
  if (input.labelFg !== void 0) out.labelFg = input.labelFg;
5271
6254
  if (input.accent !== void 0) out.accent = input.accent;
5272
6255
  if (input.mutedAlpha !== void 0) out.mutedAlpha = input.mutedAlpha;
6256
+ if (input.emphasisRing !== void 0) out.emphasisRing = input.emphasisRing;
5273
6257
  return out;
5274
6258
  }
5275
6259
  function sameTheme(a, b) {
5276
- return a.background === b.background && a.nodeDefault === b.nodeDefault && a.edgeDefault === b.edgeDefault && a.labelFg === b.labelFg && a.accent === b.accent && a.mutedAlpha === b.mutedAlpha;
6260
+ return a.background === b.background && a.nodeDefault === b.nodeDefault && a.edgeDefault === b.edgeDefault && a.labelFg === b.labelFg && a.accent === b.accent && a.mutedAlpha === b.mutedAlpha && a.emphasisRing === b.emphasisRing;
5277
6261
  }
5278
6262
  var ZOOM_STEP = 1.5;
5279
6263
  var EMPTY_IDS = [];
@@ -5300,6 +6284,9 @@ var TIMELINE_COALESCE_WINDOW_MS = Number.MAX_SAFE_INTEGER;
5300
6284
  var DEV = Boolean(import.meta.env?.DEV);
5301
6285
  var nowMs = () => Date.now();
5302
6286
  var DEFAULT_RGBA = [0.66, 0.66, 0.66, 1];
6287
+ var PERF_SAMPLE_THROTTLE_MS = 1e3;
6288
+ var FRAME_PRESSURE_ENGAGE_MS = 40;
6289
+ var FRAME_PRESSURE_CLEAR_MS = 28;
5303
6290
  var LABEL_RERANK_THROTTLE_MS = 100;
5304
6291
  var SIM_HOT_REFRESH_MS = 500;
5305
6292
  function sameIds(a, b) {
@@ -5372,6 +6359,7 @@ function createGraphInstance(opts) {
5372
6359
  let overlayIdSeq = 0;
5373
6360
  let edgeIndexById = EMPTY_INDEX2;
5374
6361
  let adjacency = null;
6362
+ let sceneIncidence = null;
5375
6363
  let lastLinkWidths = null;
5376
6364
  let scopeSpec = null;
5377
6365
  const scopeExtraIds = /* @__PURE__ */ new Set();
@@ -5505,6 +6493,8 @@ function createGraphInstance(opts) {
5505
6493
  let theme = resolveTheme(void 0);
5506
6494
  let edgeArrows = false;
5507
6495
  let showLinks = true;
6496
+ let emphasisRingOn = true;
6497
+ let emphasizedNodeId = null;
5508
6498
  let nodeImage;
5509
6499
  let labelsConfig;
5510
6500
  const metricStore = new MetricStore();
@@ -5534,11 +6524,127 @@ function createGraphInstance(opts) {
5534
6524
  const positionSubs = /* @__PURE__ */ new Set();
5535
6525
  let currentPlacements = [];
5536
6526
  let labelPositionCache = null;
6527
+ let frameCadence = 0;
6528
+ let quiescenceAsserted = false;
6529
+ let quiescenceTimer = null;
5537
6530
  let lastHotRefreshMs = null;
5538
6531
  let rerankTimer = null;
5539
6532
  let lastOverloadCount = 0;
5540
6533
  const projectScratch = [0, 0];
5541
6534
  let dataDiags = [];
6535
+ let columnarDiags = [];
6536
+ const executionMode = opts.execution ?? "main";
6537
+ let workerLane = null;
6538
+ let workerDiags = [];
6539
+ let workerUnavailableReported = false;
6540
+ let pendingDeriveToken = 0;
6541
+ function workerEligible() {
6542
+ if (executionMode === "main") return false;
6543
+ if (workerLane === null) {
6544
+ workerLane = new WorkerLane({
6545
+ ...opts.workerFactory !== void 0 ? { factory: opts.workerFactory } : {},
6546
+ onUnavailable: (reason) => {
6547
+ if (workerUnavailableReported) return;
6548
+ workerUnavailableReported = true;
6549
+ workerDiags = [
6550
+ {
6551
+ code: "worker-unavailable",
6552
+ severity: executionMode === "worker" ? "error" : "info",
6553
+ count: 1,
6554
+ sampleIds: [],
6555
+ message: `worker lane unavailable (${reason}) \u2014 columnar acceptance runs on the main lane`
6556
+ }
6557
+ ];
6558
+ }
6559
+ });
6560
+ }
6561
+ return workerLane.ensureBooted();
6562
+ }
6563
+ function scheduleWorkerAcceptance(columnar, deferredMetrics) {
6564
+ const lane = workerLane;
6565
+ pendingDeriveToken += 1;
6566
+ const token = pendingDeriveToken;
6567
+ const payload = {
6568
+ nodeIdTable: encodeStringTable(columnar.nodes.ids.dictionary),
6569
+ nodeIdCodes: columnar.nodes.ids.codes.slice(),
6570
+ nodeCount: columnar.nodes.length,
6571
+ edgeIdTable: encodeStringTable(columnar.edges.ids.dictionary),
6572
+ edgeIdCodes: columnar.edges.ids.codes.slice(),
6573
+ edgeSource: columnar.edges.source.slice(),
6574
+ edgeTarget: columnar.edges.target.slice(),
6575
+ edgeCount: columnar.edges.length
6576
+ };
6577
+ const transfers = collectTransfers([
6578
+ payload.nodeIdTable.offsets,
6579
+ payload.nodeIdTable.bytes,
6580
+ payload.nodeIdCodes,
6581
+ payload.edgeIdTable.offsets,
6582
+ payload.edgeIdTable.bytes,
6583
+ payload.edgeIdCodes,
6584
+ payload.edgeSource,
6585
+ payload.edgeTarget
6586
+ ]);
6587
+ void lane.request(token, "scene", "derive-columnar", payload, transfers, "guaranteed", "derive").then((reply) => {
6588
+ if (destroyed || token !== pendingDeriveToken) return;
6589
+ if (reply.op !== "result") {
6590
+ workerDiags = [
6591
+ {
6592
+ code: "worker-unavailable",
6593
+ severity: "error",
6594
+ count: 1,
6595
+ sampleIds: [],
6596
+ message: `worker derive failed (${String(reply.payload.message)}) \u2014 snapshot dropped`
6597
+ }
6598
+ ];
6599
+ publish({ diagnostics: composeDiagnostics() });
6600
+ return;
6601
+ }
6602
+ const acceptance = reply.payload;
6603
+ acceptanceQueue.admit(() => {
6604
+ if (destroyed || token !== pendingDeriveToken) return;
6605
+ const mutated = acceptance.keepNodes.length !== columnar.nodes.length || acceptance.keepEdges.length !== columnar.edges.length || validateColumnarStructure(columnar).length > 0;
6606
+ if (mutated) {
6607
+ columnarDiags = [
6608
+ {
6609
+ code: "invalid-columnar-snapshot",
6610
+ severity: "error",
6611
+ count: 1,
6612
+ sampleIds: [],
6613
+ message: "columnar snapshot mutated while worker acceptance was pending \u2014 rejected whole (\xA75: source coordinates are immutable; publish a new sourceRevision)"
6614
+ }
6615
+ ];
6616
+ publish({ diagnostics: composeDiagnostics() });
6617
+ return;
6618
+ }
6619
+ const preAccepted = buildAcceptedFromColumnar(columnar, acceptance);
6620
+ applyHostUpdateInner(
6621
+ {
6622
+ data: {
6623
+ datasetKey: columnar.datasetKey,
6624
+ sourceRevision: columnar.sourceRevision,
6625
+ nodes: [],
6626
+ edges: []
6627
+ },
6628
+ ...deferredMetrics !== void 0 ? { metrics: deferredMetrics } : {}
6629
+ },
6630
+ preAccepted
6631
+ );
6632
+ if (columnar.bufferOwnership === "transfer") detachColumnarBuffers(columnar);
6633
+ });
6634
+ }).catch((err) => {
6635
+ if (destroyed || token !== pendingDeriveToken) return;
6636
+ const message = err instanceof Error ? err.message : String(err);
6637
+ if (message !== "worker-unavailable" && message !== "worker-failed") return;
6638
+ acceptanceQueue.admit(() => {
6639
+ if (destroyed || token !== pendingDeriveToken) return;
6640
+ applyHostUpdateInner({
6641
+ data: materializeColumnarSnapshot(columnar),
6642
+ ...deferredMetrics !== void 0 ? { metrics: deferredMetrics } : {}
6643
+ });
6644
+ if (columnar.bufferOwnership === "transfer") detachColumnarBuffers(columnar);
6645
+ });
6646
+ });
6647
+ }
5542
6648
  let nodeColorDiags = [];
5543
6649
  let nodeSizeDiags = [];
5544
6650
  let linkColorDiags = [];
@@ -5602,6 +6708,46 @@ function createGraphInstance(opts) {
5602
6708
  }
5603
6709
  pendingHistoryDepths = null;
5604
6710
  store.setState((prev) => ({ ...prev, ...patch }));
6711
+ if (patch.visible !== void 0 || patch.nodeCount !== void 0) {
6712
+ evaluateLadderCounts();
6713
+ }
6714
+ }
6715
+ function evaluateLadderCounts() {
6716
+ if (destroyed) return;
6717
+ const vis = store.getState().visible;
6718
+ const events = degradeController.evaluateCounts({ nodes: vis.nodes, edges: vis.edges });
6719
+ for (const event of events) applyDegradeEvent(event);
6720
+ }
6721
+ function applyDegradeEvent(event) {
6722
+ if (event.step === "cap-dom-labels") {
6723
+ if (event.engaged && !capLabelsNudged) {
6724
+ capLabelsNudged = true;
6725
+ console.warn(
6726
+ "orbit: cap-dom-labels engaged \u2014 DOM labels are hard-capped at the label budget above limits.domLabelNodes visible nodes (\xA717). A GPU label lane (labelStrategy 'sdf') is the planned scale path (S13-T05, deferred)."
6727
+ );
6728
+ }
6729
+ scheduleViewportRerank();
6730
+ }
6731
+ if (event.step === "batch-histograms" && !event.engaged) {
6732
+ flushCrossfilterNotify();
6733
+ }
6734
+ if (event.step === "defer-images" && !event.engaged && imageRefsDeferred) {
6735
+ pushImageRefs();
6736
+ }
6737
+ if (event.step === "disable-transitions") {
6738
+ const eng = engineIfReady();
6739
+ if (eng !== null) {
6740
+ const revisions = { ...store.getState().revisions };
6741
+ revisions.render += 1;
6742
+ commitToEngine(eng, {
6743
+ revision: revisions.render,
6744
+ config: { transitionDurationMs: event.engaged ? 0 : null }
6745
+ });
6746
+ revisions.appliedRender = eng.appliedRevision();
6747
+ store.setState((prev) => ({ ...prev, revisions }));
6748
+ }
6749
+ }
6750
+ emit("degrade", event);
5605
6751
  }
5606
6752
  function emitInstanceError(detail, phase, cause) {
5607
6753
  engineDiags = [
@@ -6019,6 +7165,9 @@ function createGraphInstance(opts) {
6019
7165
  return k === void 0 ? void 0 : accepted.edges[k];
6020
7166
  }
6021
7167
  function applyEdgeHover(edge, transitionsOnly) {
7168
+ if (edge !== null && degradeController.isEngaged("defer-link-picking") && store.getState().simulationRunning) {
7169
+ return;
7170
+ }
6022
7171
  const edgeId = edge === null ? null : edge.id;
6023
7172
  const hover = store.getState().hover;
6024
7173
  const changed = hover.edgeId !== edgeId;
@@ -6061,6 +7210,8 @@ function createGraphInstance(opts) {
6061
7210
  function composeDiagnostics() {
6062
7211
  return [
6063
7212
  ...dataDiags,
7213
+ ...columnarDiags,
7214
+ ...workerDiags,
6064
7215
  ...ingestMergeDiags,
6065
7216
  ...ingestCommitDiags.map((e) => e.diag),
6066
7217
  ...nodeColorDiags,
@@ -6112,7 +7263,60 @@ function createGraphInstance(opts) {
6112
7263
  baseLinkColors = null;
6113
7264
  basePointColorsSynthesized = false;
6114
7265
  baseLinkColorsSynthesized = false;
6115
- }
7266
+ nodeAlphaComposer.reset();
7267
+ edgeAlphaComposer.reset();
7268
+ sceneIncidence = null;
7269
+ }
7270
+ const brushCrossings = { becameFailing: [], becameClear: [] };
7271
+ const brushSceneAdd = [];
7272
+ const brushSceneRemove = [];
7273
+ const nodeAlphaComposer = new IncrementalAlphaComposer();
7274
+ const edgeAlphaComposer = new IncrementalAlphaComposer();
7275
+ const PATCH_FULL_UPLOAD_RATIO = 0.5;
7276
+ function buildAlphaPatches(buf, slotLists, stride) {
7277
+ const slots = [];
7278
+ for (const list of slotLists) for (const slot of list) slots.push(slot);
7279
+ slots.sort((a, b) => a - b);
7280
+ const patches = [];
7281
+ let runStart = -1;
7282
+ let runEnd = -1;
7283
+ for (const slot of slots) {
7284
+ if (slot < runEnd) continue;
7285
+ if (slot === runEnd) {
7286
+ runEnd = slot + 1;
7287
+ continue;
7288
+ }
7289
+ if (runStart >= 0) {
7290
+ patches.push({
7291
+ start: runStart * stride,
7292
+ data: buf.subarray(runStart * stride, runEnd * stride)
7293
+ });
7294
+ }
7295
+ runStart = slot;
7296
+ runEnd = slot + 1;
7297
+ }
7298
+ if (runStart >= 0) {
7299
+ patches.push({
7300
+ start: runStart * stride,
7301
+ data: buf.subarray(runStart * stride, runEnd * stride)
7302
+ });
7303
+ }
7304
+ return patches;
7305
+ }
7306
+ const pressureSampler = new PressureSampler();
7307
+ const resolvedLimits = resolveScaleLimits(opts.limits);
7308
+ const degradeController = new DegradeController(resolvedLimits.limits, () => nowMs());
7309
+ let capLabelsNudged = false;
7310
+ let imageRefsDeferred = false;
7311
+ let lastCommitMs;
7312
+ let lastPerfSampleAt = -Infinity;
7313
+ const perfCounters = {
7314
+ brushSlotsTranslated: 0,
7315
+ fullBrushRefreshes: 0,
7316
+ fullCascades: 0,
7317
+ fullNodeRecomposes: 0,
7318
+ fullEdgeRecomposes: 0
7319
+ };
6116
7320
  function evaluateFilterMembership() {
6117
7321
  if (softMask === null || maskFilterSource === null) return;
6118
7322
  const model = sceneModel();
@@ -6190,6 +7394,7 @@ function createGraphInstance(opts) {
6190
7394
  }
6191
7395
  function refreshBrushMembership() {
6192
7396
  if (softMask === null || maskBrushSource === null || scene === null) return;
7397
+ perfCounters.fullBrushRefreshes += 1;
6193
7398
  const slots = [];
6194
7399
  for (const s of crossfilterHiddenBase) {
6195
7400
  const id = crossfilterRowIds[s];
@@ -6199,8 +7404,45 @@ function createGraphInstance(opts) {
6199
7404
  }
6200
7405
  maskBrushSource.setNodeFailures(slots, null);
6201
7406
  }
7407
+ function refreshBrushMembershipDelta(delta) {
7408
+ if (softMask === null || maskBrushSource === null || scene === null) return false;
7409
+ if (groupRewrite !== null) return false;
7410
+ const identity = accepted !== null && sceneModel() === accepted && crossfilterRowsRef === accepted.nodes;
7411
+ const sceneRef = scene;
7412
+ const translate = (baseSlots, out) => {
7413
+ out.length = 0;
7414
+ for (let i = 0; i < baseSlots.length; i++) {
7415
+ const s = baseSlots[i];
7416
+ perfCounters.brushSlotsTranslated += 1;
7417
+ if (identity) {
7418
+ if (s < sceneRef.count) out.push(s);
7419
+ continue;
7420
+ }
7421
+ const id = crossfilterRowIds[s];
7422
+ if (id === void 0) continue;
7423
+ const idx = sceneRef.indexById.get(id);
7424
+ if (idx !== void 0) out.push(idx);
7425
+ }
7426
+ };
7427
+ translate(delta.hidden, brushSceneAdd);
7428
+ translate(delta.shown, brushSceneRemove);
7429
+ maskBrushSource.updateNodeFailures(brushSceneAdd, brushSceneRemove, brushCrossings);
7430
+ return true;
7431
+ }
6202
7432
  function cascadeNodeMask() {
6203
- if (softMask !== null && scene !== null) softMask.applyNodeCascadeToEdges(scene.links);
7433
+ if (softMask !== null && scene !== null) {
7434
+ perfCounters.fullCascades += 1;
7435
+ softMask.applyNodeCascadeToEdges(scene.links);
7436
+ }
7437
+ }
7438
+ function cascadeNodeMaskDelta() {
7439
+ if (softMask === null || scene === null) return;
7440
+ const failing = brushCrossings.becameFailing;
7441
+ const cleared = brushCrossings.becameClear;
7442
+ if (failing.length === 0 && cleared.length === 0) return;
7443
+ sceneIncidence ??= buildIncidence(scene.links, scene.count);
7444
+ const crossed = failing.length === 0 ? cleared : cleared.length === 0 ? failing : [...failing, ...cleared];
7445
+ softMask.applyNodeCascadeToEdgesDelta(scene.links, sceneIncidence, crossed);
6204
7446
  }
6205
7447
  function refreshGroupMaskMembership(hidden) {
6206
7448
  if (softMask === null || maskGroupSource === null) return;
@@ -6289,6 +7531,8 @@ function createGraphInstance(opts) {
6289
7531
  refreshGroupMaskMembership(hidden);
6290
7532
  cascadeNodeMask();
6291
7533
  softMask.drainDirty();
7534
+ nodeAlphaComposer.reset();
7535
+ edgeAlphaComposer.reset();
6292
7536
  return filterDiags !== before;
6293
7537
  }
6294
7538
  function computeVisibleCounts() {
@@ -6332,6 +7576,7 @@ function createGraphInstance(opts) {
6332
7576
  }
6333
7577
  function composeNodeAlphaBuffer(base) {
6334
7578
  if (softMask === null || scene === null) return base;
7579
+ perfCounters.fullNodeRecomposes += 1;
6335
7580
  const out = new Float32Array(base);
6336
7581
  const n = scene.count;
6337
7582
  const dimAlpha = theme.mutedAlpha;
@@ -6343,6 +7588,7 @@ function createGraphInstance(opts) {
6343
7588
  }
6344
7589
  function composeEdgeAlphaBuffer(base) {
6345
7590
  if (softMask === null && pathDimEdges === null || scene === null) return base;
7591
+ perfCounters.fullEdgeRecomposes += 1;
6346
7592
  const out = new Float32Array(base);
6347
7593
  const n = scene.linkCount;
6348
7594
  const dimAlpha = theme.mutedAlpha;
@@ -6357,6 +7603,36 @@ function createGraphInstance(opts) {
6357
7603
  }
6358
7604
  return out;
6359
7605
  }
7606
+ function composeNodeAlphaIncremental(drain) {
7607
+ const base = basePointColorBuffer();
7608
+ if (softMask === null || scene === null) return base;
7609
+ const mask = softMask;
7610
+ const dimAlpha = theme.mutedAlpha;
7611
+ const alphaOf = (i) => mask.nodeAlpha(i, dimAlpha);
7612
+ const seeded = nodeAlphaComposer.ensureSeeded(base, scene.count, dimAlpha, null, alphaOf);
7613
+ if (seeded) {
7614
+ nodeAlphaComposer.note(drain.nodes);
7615
+ nodeAlphaComposer.note(drain.nodesAlpha);
7616
+ }
7617
+ return nodeAlphaComposer.nextBuffer(base, alphaOf);
7618
+ }
7619
+ function composeEdgeAlphaIncremental(drain) {
7620
+ const base = baseLinkColorBuffer();
7621
+ if (softMask === null || scene === null) return base;
7622
+ if (pathDimEdges !== null) {
7623
+ edgeAlphaComposer.reset();
7624
+ return composeEdgeAlphaBuffer(base);
7625
+ }
7626
+ const mask = softMask;
7627
+ const dimAlpha = theme.mutedAlpha;
7628
+ const alphaOf = (k) => mask.edgeAlpha(k, dimAlpha);
7629
+ const seeded = edgeAlphaComposer.ensureSeeded(base, scene.linkCount, dimAlpha, null, alphaOf);
7630
+ if (seeded) {
7631
+ edgeAlphaComposer.note(drain.edges);
7632
+ edgeAlphaComposer.note(drain.edgesAlpha);
7633
+ }
7634
+ return edgeAlphaComposer.nextBuffer(base, alphaOf);
7635
+ }
6360
7636
  function publishMaskFastPath(extraPatch, diagsChanged) {
6361
7637
  const prev = store.getState();
6362
7638
  const patch = { ...extraPatch };
@@ -6380,13 +7656,48 @@ function createGraphInstance(opts) {
6380
7656
  };
6381
7657
  const buffers = visDirty.nodeColor || visDirty.nodeSize ? { ...projectChannelBuffers(visDirty) ?? {} } : {};
6382
7658
  if (visDirty.nodeColor || visDirty.nodeSize) diagsChanged = true;
7659
+ const maskPhaseT0 = performance.now();
7660
+ const rangedChannels = session !== null && session.policy !== null ? session.policy.rangedChannels : null;
7661
+ const bufferPatches = {};
6383
7662
  if (nodesAffected && buffers.pointColor === void 0) {
6384
- buffers.pointColor = composeNodeAlphaBuffer(basePointColorBuffer());
7663
+ const reseedsBefore = nodeAlphaComposer.reseeds;
7664
+ const composed = composeNodeAlphaIncremental(drain);
7665
+ const changed = drain.nodes.length + drain.nodesAlpha.length;
7666
+ if (rangedChannels !== null && rangedChannels.has("pointColor") && nodeAlphaComposer.reseeds === reseedsBefore && changed < scene.count * PATCH_FULL_UPLOAD_RATIO) {
7667
+ bufferPatches.pointColor = buildAlphaPatches(
7668
+ composed,
7669
+ [drain.nodes, drain.nodesAlpha],
7670
+ 4
7671
+ );
7672
+ } else {
7673
+ buffers.pointColor = composed;
7674
+ }
6385
7675
  }
6386
7676
  if (edgesAffected && buffers.linkColor === void 0) {
6387
- buffers.linkColor = composeEdgeAlphaBuffer(baseLinkColorBuffer());
7677
+ const reseedsBefore = edgeAlphaComposer.reseeds;
7678
+ const composed = composeEdgeAlphaIncremental(drain);
7679
+ const changed = drain.edges.length + drain.edgesAlpha.length;
7680
+ if (rangedChannels !== null && rangedChannels.has("linkColor") && edgeAlphaComposer.reseeds === reseedsBefore && changed < scene.links.length / 2 * PATCH_FULL_UPLOAD_RATIO) {
7681
+ bufferPatches.linkColor = buildAlphaPatches(
7682
+ composed,
7683
+ [drain.edges, drain.edgesAlpha],
7684
+ 4
7685
+ );
7686
+ } else {
7687
+ buffers.linkColor = composed;
7688
+ }
6388
7689
  }
6389
- commitToEngine(eng, { revision: revisions.render, buffers });
7690
+ const maskUploadT0 = performance.now();
7691
+ const maskCommit = { revision: revisions.render, buffers };
7692
+ if (Object.keys(bufferPatches).length > 0) maskCommit.bufferPatches = bufferPatches;
7693
+ commitToEngine(eng, maskCommit);
7694
+ lastCommitMs = {
7695
+ kind: "mask",
7696
+ validate: 0,
7697
+ derive: 0,
7698
+ project: maskUploadT0 - maskPhaseT0,
7699
+ upload: performance.now() - maskUploadT0
7700
+ };
6390
7701
  revisions.appliedRender = eng.appliedRevision();
6391
7702
  }
6392
7703
  patch.revisions = revisions;
@@ -6414,7 +7725,17 @@ function createGraphInstance(opts) {
6414
7725
  crossfilterHiddenBase.clear();
6415
7726
  }
6416
7727
  const crossfilterFacadeListeners = /* @__PURE__ */ new Set();
7728
+ let crossfilterNotifyPending = false;
6417
7729
  function notifyCrossfilterFacade() {
7730
+ if (degradeController.isEngaged("batch-histograms") && engineIfReady() !== null) {
7731
+ crossfilterNotifyPending = true;
7732
+ return;
7733
+ }
7734
+ for (const cb of crossfilterFacadeListeners) cb();
7735
+ }
7736
+ function flushCrossfilterNotify() {
7737
+ if (!crossfilterNotifyPending) return;
7738
+ crossfilterNotifyPending = false;
6418
7739
  for (const cb of crossfilterFacadeListeners) cb();
6419
7740
  }
6420
7741
  function buildCrossfilterEngine() {
@@ -6540,9 +7861,13 @@ function createGraphInstance(opts) {
6540
7861
  for (const s of delta.shown) crossfilterHiddenBase.delete(s);
6541
7862
  if (membershipChanged && scene !== null) {
6542
7863
  ensureMask();
6543
- refreshBrushMembership();
6544
- refreshGroupMaskMembership(store.getState().hiddenNodeIds);
6545
- cascadeNodeMask();
7864
+ if (refreshBrushMembershipDelta(delta)) {
7865
+ cascadeNodeMaskDelta();
7866
+ } else {
7867
+ refreshBrushMembership();
7868
+ refreshGroupMaskMembership(store.getState().hiddenNodeIds);
7869
+ cascadeNodeMask();
7870
+ }
6546
7871
  }
6547
7872
  publishMaskFastPath(extraPatch, false);
6548
7873
  }
@@ -6781,6 +8106,7 @@ function createGraphInstance(opts) {
6781
8106
  const result = reconcileScene(scopedAccepted ?? accepted);
6782
8107
  labelPositionCache = null;
6783
8108
  adjacency = null;
8109
+ sceneIncidence = null;
6784
8110
  structuralChange = result.structuralChange;
6785
8111
  positionChange = result.positionChange;
6786
8112
  }
@@ -6853,6 +8179,8 @@ function createGraphInstance(opts) {
6853
8179
  revisions.scope += 1;
6854
8180
  revisions.render += 1;
6855
8181
  if (eng !== null) {
8182
+ nodeAlphaComposer.reset();
8183
+ edgeAlphaComposer.reset();
6856
8184
  const buffers = {};
6857
8185
  if (nodesAffected) buffers.pointColor = composeNodeAlphaBuffer(basePointColorBuffer());
6858
8186
  if (edgesAffected) buffers.linkColor = composeEdgeAlphaBuffer(baseLinkColorBuffer());
@@ -7047,9 +8375,10 @@ function createGraphInstance(opts) {
7047
8375
  if (eng !== null && scene !== null && labelModel !== null && cfg !== void 0 && cfg.enabled !== false) {
7048
8376
  const sceneRef = scene;
7049
8377
  const vp = store.getState().viewport ?? eng.getViewport() ?? { zoom: 1 };
8378
+ const hardCap = degradeController.isEngaged("cap-dom-labels") ? LABEL_MAX_VISIBLE_DEFAULT : LABEL_MAX_VISIBLE_CAP;
7050
8379
  const k = Math.max(
7051
8380
  0,
7052
- Math.min(Math.floor(cfg.maxVisible ?? LABEL_MAX_VISIBLE_DEFAULT), LABEL_MAX_VISIBLE_CAP)
8381
+ Math.min(Math.floor(cfg.maxVisible ?? LABEL_MAX_VISIBLE_DEFAULT), hardCap)
7053
8382
  );
7054
8383
  const clusterCandidates = clusterLabelCandidates(cfg, vp.zoom, k);
7055
8384
  const nodeLabelsSuppressed = cfg.maxZoom !== void 0 && vp.zoom <= cfg.maxZoom;
@@ -7518,7 +8847,8 @@ function createGraphInstance(opts) {
7518
8847
  defaultPointColor: theme.nodeDefault,
7519
8848
  defaultLinkColor: theme.edgeDefault,
7520
8849
  linkArrows: edgeArrows,
7521
- renderLinks: showLinks
8850
+ renderLinks: showLinks,
8851
+ emphasisRingColor: theme.emphasisRing
7522
8852
  };
7523
8853
  if (simulation !== void 0) c.simulation = simulation;
7524
8854
  if (clusterSpec !== null) {
@@ -7578,6 +8908,11 @@ function createGraphInstance(opts) {
7578
8908
  }
7579
8909
  function pushImageRefs() {
7580
8910
  if (session === null || session.policy === null || session.policy.images !== "native") return;
8911
+ if (degradeController.isEngaged("defer-images")) {
8912
+ imageRefsDeferred = true;
8913
+ return;
8914
+ }
8915
+ imageRefsDeferred = false;
7581
8916
  const model = sceneModel();
7582
8917
  if (model === null) return;
7583
8918
  if (nodeImage === void 0) {
@@ -7691,15 +9026,25 @@ function createGraphInstance(opts) {
7691
9026
  }
7692
9027
  eng.setPinnedIndices(indexSet.size > 0 ? [...indexSet] : null);
7693
9028
  }
9029
+ function applyEmphasis(eng, index) {
9030
+ if (!emphasisRingOn) return;
9031
+ eng.setFocusedIndex(index);
9032
+ }
7694
9033
  function reapplyInteractionState(eng) {
7695
9034
  const { selection, pins, pinnedNodeIds, hover } = store.getState();
7696
9035
  if (selection.nodeIds.length > 0 || selection.groupIds.length > 0) {
7697
9036
  pushSelectionToEngine(eng, selection.nodeIds);
7698
9037
  }
7699
9038
  if (pins.size > 0 || pinnedNodeIds.size > 0) pushPinsToEngine(eng, pins);
9039
+ if (emphasizedNodeId !== null && scene !== null && !scene.indexById.has(emphasizedNodeId)) {
9040
+ emphasizedNodeId = null;
9041
+ }
7700
9042
  if (hover.nodeId !== null && scene !== null) {
7701
9043
  const idx = scene.indexById.get(hover.nodeId);
7702
- if (idx !== void 0) eng.setFocusedIndex(idx);
9044
+ if (idx !== void 0) applyEmphasis(eng, idx);
9045
+ } else if (emphasizedNodeId !== null && scene !== null) {
9046
+ const idx = scene.indexById.get(emphasizedNodeId);
9047
+ if (idx !== void 0) applyEmphasis(eng, idx);
7703
9048
  }
7704
9049
  }
7705
9050
  function maybeFitView(eng) {
@@ -8441,6 +9786,7 @@ function createGraphInstance(opts) {
8441
9786
  invalidateExpansionRecords();
8442
9787
  edgeIndexById = new Map(p.merged.edges.map((e, k) => [e.id, k]));
8443
9788
  adjacency = null;
9789
+ sceneIncidence = null;
8444
9790
  acceptedAdjacency = null;
8445
9791
  acceptedModelSeq += 1;
8446
9792
  scopedAccepted = computeScopedAccepted();
@@ -8610,9 +9956,15 @@ function createGraphInstance(opts) {
8610
9956
  if (pinsChanged || pinnedChanged || structuralChange && (nextPins.size > 0 || nextPinned.size > 0)) {
8611
9957
  pushPinsToEngine(eng, nextPins);
8612
9958
  }
9959
+ if (structuralChange && emphasizedNodeId !== null && !ingestScene.indexById.has(emphasizedNodeId)) {
9960
+ emphasizedNodeId = null;
9961
+ }
8613
9962
  if (structuralChange && !hoverCleared && prev.hover.nodeId !== null) {
8614
9963
  const idx = ingestScene.indexById.get(prev.hover.nodeId);
8615
- if (idx !== void 0) eng.setFocusedIndex(idx);
9964
+ if (idx !== void 0) applyEmphasis(eng, idx);
9965
+ } else if (structuralChange && emphasizedNodeId !== null) {
9966
+ const idx = ingestScene.indexById.get(emphasizedNodeId);
9967
+ if (idx !== void 0) applyEmphasis(eng, idx);
8616
9968
  }
8617
9969
  if (commitNeeded) maybeFitView(eng);
8618
9970
  }
@@ -9373,6 +10725,7 @@ function createGraphInstance(opts) {
9373
10725
  const result = reconcileScene(scopedAccepted ?? accepted);
9374
10726
  labelPositionCache = null;
9375
10727
  adjacency = null;
10728
+ sceneIncidence = null;
9376
10729
  hardScopeGen += 1;
9377
10730
  visibleGen += 1;
9378
10731
  const filterDiagsChanged = rebuildMaskMemberships(prev.hiddenNodeIds);
@@ -9728,10 +11081,14 @@ function createGraphInstance(opts) {
9728
11081
  checkRestoreAcknowledgement(update);
9729
11082
  });
9730
11083
  }
9731
- function applyHostUpdateInner(update) {
11084
+ function applyHostUpdateInner(update, preAccepted) {
11085
+ const phaseT0 = performance.now();
11086
+ let phaseProjectStart = phaseT0;
9732
11087
  const issuedModelSeq = acceptedModelSeq;
9733
11088
  let searchIndexRejected = false;
9734
11089
  let crossfilterRejected = false;
11090
+ let columnarRejected = false;
11091
+ let metricsDeferredToWorker = false;
9735
11092
  const diagnosticsForced = pendingDiagnosticsRefresh;
9736
11093
  pendingDiagnosticsRefresh = false;
9737
11094
  if (update.filter !== void 0 && update.filter !== null) {
@@ -9772,11 +11129,42 @@ function createGraphInstance(opts) {
9772
11129
  let positionChange = false;
9773
11130
  let datasetKeyChanged = false;
9774
11131
  let overlayIdsCleared = false;
9775
- const data = update.data;
11132
+ let data;
11133
+ if (update.data !== void 0 && isColumnarSnapshot(update.data)) {
11134
+ const columnar = update.data;
11135
+ const isColumnarReplay = baseAccepted !== null && baseAccepted.datasetKey === columnar.datasetKey && baseAccepted.sourceRevision === columnar.sourceRevision;
11136
+ if (isColumnarReplay) {
11137
+ data = void 0;
11138
+ } else {
11139
+ const issues = validateColumnarStructure(columnar);
11140
+ if (issues.length > 0) {
11141
+ columnarDiags = [
11142
+ {
11143
+ code: "invalid-columnar-snapshot",
11144
+ severity: "error",
11145
+ count: issues.length,
11146
+ sampleIds: issues.slice(0, DIAGNOSTIC_SAMPLE_CAP).map((i) => i.where),
11147
+ message: `columnar snapshot rejected whole (\xA75.1): ${issues[0].where} \u2014 ${issues[0].detail}`
11148
+ }
11149
+ ];
11150
+ columnarRejected = true;
11151
+ data = void 0;
11152
+ } else if (workerEligible()) {
11153
+ scheduleWorkerAcceptance(columnar, update.metrics);
11154
+ metricsDeferredToWorker = true;
11155
+ data = void 0;
11156
+ } else {
11157
+ data = materializeColumnarSnapshot(columnar);
11158
+ if (columnar.bufferOwnership === "transfer") detachColumnarBuffers(columnar);
11159
+ }
11160
+ }
11161
+ } else {
11162
+ data = update.data;
11163
+ }
9776
11164
  if (data !== void 0) {
9777
11165
  const isReplay = baseAccepted !== null && baseAccepted.datasetKey === data.datasetKey && baseAccepted.sourceRevision === data.sourceRevision;
9778
11166
  if (!isReplay) {
9779
- const nextAccepted = validateSnapshot(data);
11167
+ const nextAccepted = preAccepted ?? validateSnapshot(data);
9780
11168
  abortAllSessions(null, "replaced: a declarative snapshot was applied (\xA77.5)");
9781
11169
  overlayIdsCleared = clearOverlayState();
9782
11170
  datasetKeyChanged = baseAccepted !== null && baseAccepted.datasetKey !== data.datasetKey;
@@ -9809,8 +11197,11 @@ function createGraphInstance(opts) {
9809
11197
  baseSource = "declarative";
9810
11198
  edgeIndexById = new Map(nextAccepted.edges.map((e, k) => [e.id, k]));
9811
11199
  adjacency = null;
11200
+ sceneIncidence = null;
9812
11201
  acceptedAdjacency = null;
9813
11202
  dataDiags = nextAccepted.diagnostics;
11203
+ columnarDiags = [];
11204
+ pendingDeriveToken += 1;
9814
11205
  dataChanged = true;
9815
11206
  }
9816
11207
  }
@@ -9953,6 +11344,7 @@ function createGraphInstance(opts) {
9953
11344
  const result = reconcileScene(scopedAccepted ?? accepted);
9954
11345
  labelPositionCache = null;
9955
11346
  adjacency = null;
11347
+ sceneIncidence = null;
9956
11348
  structuralChange = result.structuralChange;
9957
11349
  positionChange = result.positionChange;
9958
11350
  }
@@ -9966,6 +11358,7 @@ function createGraphInstance(opts) {
9966
11358
  const result = reconcileScene(scopedAccepted ?? accepted);
9967
11359
  labelPositionCache = null;
9968
11360
  adjacency = null;
11361
+ sceneIncidence = null;
9969
11362
  structuralChange = structuralChange || result.structuralChange;
9970
11363
  positionChange = positionChange || result.positionChange;
9971
11364
  }
@@ -10003,7 +11396,7 @@ function createGraphInstance(opts) {
10003
11396
  if (clusterDiags !== clusterDiagsBefore) groupsDiagsChanged = true;
10004
11397
  let metricsAdmitted = null;
10005
11398
  let metricsProcessed = false;
10006
- if (update.metrics !== void 0 && update.metrics.length > 0) {
11399
+ if (!metricsDeferredToWorker && update.metrics !== void 0 && update.metrics.length > 0) {
10007
11400
  metricsProcessed = true;
10008
11401
  if (accepted === null || !ensureMetricModel()) {
10009
11402
  metricDiags = [
@@ -10068,6 +11461,7 @@ function createGraphInstance(opts) {
10068
11461
  let configPatch;
10069
11462
  let themeChanged = false;
10070
11463
  let mutedAlphaChanged = false;
11464
+ let emphasisToggled = false;
10071
11465
  let nodeBaseInvalidated = false;
10072
11466
  let linkBaseInvalidated = false;
10073
11467
  {
@@ -10102,6 +11496,10 @@ function createGraphInstance(opts) {
10102
11496
  linkBaseInvalidated = true;
10103
11497
  }
10104
11498
  }
11499
+ if (nextTheme.emphasisRing !== theme.emphasisRing) {
11500
+ c.emphasisRingColor = nextTheme.emphasisRing;
11501
+ any = true;
11502
+ }
10105
11503
  mutedAlphaChanged = nextTheme.mutedAlpha !== theme.mutedAlpha;
10106
11504
  if (groupRewrite !== null && (nextTheme.accent !== theme.accent || nextTheme.nodeDefault !== theme.nodeDefault)) {
10107
11505
  dirtyNodeColor = true;
@@ -10124,6 +11522,10 @@ function createGraphInstance(opts) {
10124
11522
  c.renderLinks = showLinks;
10125
11523
  any = true;
10126
11524
  }
11525
+ if (update.emphasisRing !== void 0 && update.emphasisRing !== emphasisRingOn) {
11526
+ emphasisRingOn = update.emphasisRing;
11527
+ emphasisToggled = true;
11528
+ }
10127
11529
  if (clusterConfigDirty) {
10128
11530
  c.cluster = clusterConfigPayload();
10129
11531
  any = true;
@@ -10325,6 +11727,8 @@ function createGraphInstance(opts) {
10325
11727
  const edgesAffected = drain.edges.length > 0 || drain.edgesAlpha.length > 0;
10326
11728
  maskAffected = nodesAffected || edgesAffected;
10327
11729
  if (maskAffected) {
11730
+ nodeAlphaComposer.reset();
11731
+ edgeAlphaComposer.reset();
10328
11732
  maskBuffers = {};
10329
11733
  if (nodesAffected) maskBuffers.pointColor = composeNodeAlphaBuffer(basePointColorBuffer());
10330
11734
  if (edgesAffected) maskBuffers.linkColor = composeEdgeAlphaBuffer(baseLinkColorBuffer());
@@ -10344,6 +11748,7 @@ function createGraphInstance(opts) {
10344
11748
  maskBuffers.linkColor = composeEdgeAlphaBuffer(baseLinkColorBuffer());
10345
11749
  }
10346
11750
  }
11751
+ phaseProjectStart = performance.now();
10347
11752
  let buffers = projectChannelBuffers({
10348
11753
  nodeColor: dirtyNodeColor,
10349
11754
  nodeSize: dirtyNodeSize,
@@ -10393,7 +11798,15 @@ function createGraphInstance(opts) {
10393
11798
  simRestarted = true;
10394
11799
  }
10395
11800
  }
11801
+ const phaseUploadStart = performance.now();
10396
11802
  commitToEngine(eng, commit);
11803
+ lastCommitMs = {
11804
+ kind: structure !== void 0 ? scopeChanged && !dataChanged ? "scope" : "model" : buffers !== void 0 ? "mask" : "config",
11805
+ validate: 0,
11806
+ derive: phaseProjectStart - phaseT0,
11807
+ project: phaseUploadStart - phaseProjectStart,
11808
+ upload: performance.now() - phaseUploadStart
11809
+ };
10397
11810
  revisions.appliedRender = eng.appliedRevision();
10398
11811
  const facade = session !== null ? session.edgePicking : null;
10399
11812
  if (facade !== null) {
@@ -10433,7 +11846,7 @@ function createGraphInstance(opts) {
10433
11846
  patch.timeline = { playingKey: null };
10434
11847
  changed = true;
10435
11848
  }
10436
- if (dataChanged || datasetKeyChanged || buffers !== void 0 || labelRerank.diagsChanged || filterDiagsChanged || metricsProcessed || searchIndexRejected || crossfilterRejected || parallelRejected || groupsDiagsChanged || diagnosticsForced) {
11849
+ if (dataChanged || datasetKeyChanged || buffers !== void 0 || labelRerank.diagsChanged || filterDiagsChanged || metricsProcessed || searchIndexRejected || crossfilterRejected || columnarRejected || parallelRejected || groupsDiagsChanged || diagnosticsForced) {
10437
11850
  patch.diagnostics = composeDiagnostics();
10438
11851
  changed = true;
10439
11852
  }
@@ -10502,9 +11915,26 @@ function createGraphInstance(opts) {
10502
11915
  if (pinsChanged || pinnedChanged || structuralChange && (nextPins.size > 0 || nextPinned.size > 0)) {
10503
11916
  pushPinsToEngine(eng, nextPins);
10504
11917
  }
11918
+ if (structuralChange && emphasizedNodeId !== null && scene !== null && !scene.indexById.has(emphasizedNodeId)) {
11919
+ emphasizedNodeId = null;
11920
+ }
10505
11921
  if (structuralChange && !hoverCleared && prev.hover.nodeId !== null && scene !== null) {
10506
11922
  const idx = scene.indexById.get(prev.hover.nodeId);
10507
- if (idx !== void 0) eng.setFocusedIndex(idx);
11923
+ if (idx !== void 0) applyEmphasis(eng, idx);
11924
+ } else if (structuralChange && emphasizedNodeId !== null && scene !== null) {
11925
+ const idx = scene.indexById.get(emphasizedNodeId);
11926
+ if (idx !== void 0) applyEmphasis(eng, idx);
11927
+ }
11928
+ if (emphasisToggled) {
11929
+ if (!emphasisRingOn) {
11930
+ eng.setFocusedIndex(null);
11931
+ } else if (!hoverCleared && prev.hover.nodeId !== null && scene !== null) {
11932
+ const idx = scene.indexById.get(prev.hover.nodeId);
11933
+ if (idx !== void 0) applyEmphasis(eng, idx);
11934
+ } else if (emphasizedNodeId !== null && scene !== null) {
11935
+ const idx = scene.indexById.get(emphasizedNodeId);
11936
+ if (idx !== void 0) applyEmphasis(eng, idx);
11937
+ }
10508
11938
  }
10509
11939
  if (commitNeeded) maybeFitView(eng);
10510
11940
  }
@@ -10554,7 +11984,8 @@ function createGraphInstance(opts) {
10554
11984
  const nodeId = node === null ? null : node.id;
10555
11985
  const hover = store.getState().hover;
10556
11986
  if (hover.nodeId !== nodeId) publish({ hover: { nodeId, edgeId: hover.edgeId } });
10557
- s.engine.setFocusedIndex(node === null ? null : index);
11987
+ emphasizedNodeId = null;
11988
+ applyEmphasis(s.engine, node === null ? null : index);
10558
11989
  emit("nodeHover", { node });
10559
11990
  },
10560
11991
  // §13 native-route edge events (§7.4 mapping). Host onLink* events are
@@ -10575,6 +12006,9 @@ function createGraphInstance(opts) {
10575
12006
  emit("edgeClick", { edge });
10576
12007
  },
10577
12008
  onLinkHover(linkIndex) {
12009
+ if (linkIndex !== null && degradeController.isEngaged("defer-link-picking") && store.getState().simulationRunning) {
12010
+ return;
12011
+ }
10578
12012
  if (!active()) return;
10579
12013
  if (linkIndex !== null && !maskEdgeVisibleAt(linkIndex)) linkIndex = null;
10580
12014
  const edge = linkIndex === null ? null : edgeAtLinkIndex(linkIndex) ?? null;
@@ -10616,6 +12050,32 @@ function createGraphInstance(opts) {
10616
12050
  */
10617
12051
  onFrame(timeMs) {
10618
12052
  if (!active()) return;
12053
+ frameCadence += 1;
12054
+ pressureSampler.noteFrame(timeMs, !store.getState().simulationRunning);
12055
+ flushCrossfilterNotify();
12056
+ if (timeMs - lastPerfSampleAt >= PERF_SAMPLE_THROTTLE_MS) {
12057
+ lastPerfSampleAt = timeMs;
12058
+ const ewma = pressureSampler.snapshot().frameEwmaMs;
12059
+ if (Number.isFinite(ewma)) {
12060
+ const vis = store.getState().visible;
12061
+ const visible = { nodes: vis.nodes, edges: vis.edges };
12062
+ if (ewma > FRAME_PRESSURE_ENGAGE_MS) {
12063
+ for (const step of ["cap-dom-labels", "defer-link-picking"]) {
12064
+ const event = degradeController.engageForPressure(step, "frame-pressure", visible);
12065
+ if (event !== null) applyDegradeEvent(event);
12066
+ }
12067
+ } else if (ewma < FRAME_PRESSURE_CLEAR_MS) {
12068
+ for (const step of ["cap-dom-labels", "defer-link-picking"]) {
12069
+ const event = degradeController.clearPressure(step, visible);
12070
+ if (event !== null) applyDegradeEvent(event);
12071
+ }
12072
+ }
12073
+ }
12074
+ if ((listeners.get("perfSample")?.size ?? 0) > 0) {
12075
+ emit("perfSample", getPerfSnapshot());
12076
+ }
12077
+ pressureSampler.resetCounters();
12078
+ }
10619
12079
  if (store.getState().simulationRunning && labelsEnabled()) {
10620
12080
  if (lastHotRefreshMs === null || timeMs - lastHotRefreshMs >= SIM_HOT_REFRESH_MS || timeMs < lastHotRefreshMs) {
10621
12081
  lastHotRefreshMs = timeMs;
@@ -10662,6 +12122,7 @@ function createGraphInstance(opts) {
10662
12122
  notifyLabelSubs(positionSubs);
10663
12123
  }
10664
12124
  emit("simulationEnd", {});
12125
+ armQuiescenceAssertion();
10665
12126
  },
10666
12127
  onError(error) {
10667
12128
  if (!active()) return;
@@ -10739,11 +12200,13 @@ function createGraphInstance(opts) {
10739
12200
  }
10740
12201
  function buildAndCommitFullReplay(s, restartAlpha) {
10741
12202
  const eng = s.engine;
12203
+ const phaseT0 = performance.now();
10742
12204
  const renderRevision = store.getState().revisions.render;
10743
12205
  const replayModel = renderModel();
10744
12206
  if (replayModel !== null) {
10745
12207
  reconcileScene(replayModel);
10746
12208
  const replayScene = scene;
12209
+ const phaseProjectStart = performance.now();
10747
12210
  const commit = {
10748
12211
  revision: renderRevision,
10749
12212
  structure: {
@@ -10767,7 +12230,15 @@ function createGraphInstance(opts) {
10767
12230
  commit.resources = { ...commit.resources ?? {}, pointImageIndex: syncIndex };
10768
12231
  }
10769
12232
  if (restartAlpha !== null) commit.restart = { alpha: restartAlpha };
12233
+ const phaseUploadStart = performance.now();
10770
12234
  commitToEngine(eng, commit);
12235
+ lastCommitMs = {
12236
+ kind: "model",
12237
+ validate: 0,
12238
+ derive: phaseProjectStart - phaseT0,
12239
+ project: phaseUploadStart - phaseProjectStart,
12240
+ upload: performance.now() - phaseUploadStart
12241
+ };
10771
12242
  if (s.edgePicking !== null) {
10772
12243
  if (restartAlpha !== null) s.edgePicking.disarm();
10773
12244
  else s.edgePicking.arm(replayScene.positions, replayScene.links);
@@ -10859,6 +12330,11 @@ function createGraphInstance(opts) {
10859
12330
  session = null;
10860
12331
  mountPromise = null;
10861
12332
  cancelRerankTimer();
12333
+ flushCrossfilterNotify();
12334
+ if (quiescenceTimer !== null) {
12335
+ clearTimeout(quiescenceTimer);
12336
+ quiescenceTimer = null;
12337
+ }
10862
12338
  const timelinePatch = resetTimelineForPatch();
10863
12339
  s.edgePicking?.destroy();
10864
12340
  s.edgePicking = null;
@@ -10871,10 +12347,48 @@ function createGraphInstance(opts) {
10871
12347
  });
10872
12348
  rerankAndNotify();
10873
12349
  }
12350
+ function armQuiescenceAssertion() {
12351
+ if (quiescenceAsserted || quiescenceTimer !== null || destroyed) return;
12352
+ const env = globalThis.process?.env?.NODE_ENV;
12353
+ if (env !== "development") return;
12354
+ const g = globalThis;
12355
+ if (typeof g.requestAnimationFrame !== "function") return;
12356
+ const armedRender = store.getState().revisions.render;
12357
+ quiescenceTimer = setTimeout(() => {
12358
+ quiescenceTimer = null;
12359
+ const state = store.getState();
12360
+ if (destroyed || state.simulationRunning || state.revisions.render !== armedRender) {
12361
+ return;
12362
+ }
12363
+ quiescenceAsserted = true;
12364
+ const native = g.requestAnimationFrame;
12365
+ if (typeof native !== "function") return;
12366
+ let registrations = 0;
12367
+ const wrapper = (cb) => {
12368
+ registrations += 1;
12369
+ return native.call(globalThis, cb);
12370
+ };
12371
+ g.requestAnimationFrame = wrapper;
12372
+ setTimeout(() => {
12373
+ if (g.requestAnimationFrame === wrapper) g.requestAnimationFrame = native;
12374
+ if (registrations > 0 && !destroyed) {
12375
+ console.warn(
12376
+ `orbit: ${registrations} requestAnimationFrame registration(s) observed over 500ms while this instance is quiescent (\xA717/S13-T06 one-frame-loop). If no app-owned animation is running, a second frame loop is leaking.`
12377
+ );
12378
+ }
12379
+ }, 500);
12380
+ }, 2e3);
12381
+ }
10874
12382
  function destroy() {
10875
12383
  if (destroyed) return;
10876
12384
  destroyed = true;
12385
+ workerLane?.terminate();
12386
+ workerLane = null;
10877
12387
  stopTimelineTimer();
12388
+ if (quiescenceTimer !== null) {
12389
+ clearTimeout(quiescenceTimer);
12390
+ quiescenceTimer = null;
12391
+ }
10878
12392
  timelinePlayingKey = null;
10879
12393
  imagePipeline?.dispose();
10880
12394
  imagePipeline = null;
@@ -11622,7 +13136,7 @@ function createGraphInstance(opts) {
11622
13136
  if (eng === null || scene === null) return EMPTY_IDS;
11623
13137
  const idx = scene.indexById.get(id);
11624
13138
  if (idx === void 0) return EMPTY_IDS;
11625
- eng.setFocusedIndex(idx);
13139
+ applyEmphasis(eng, idx);
11626
13140
  if (effectiveReducedMotion()) eng.zoomToIndex?.(idx, 0);
11627
13141
  else eng.zoomToIndex?.(idx);
11628
13142
  const neighbors = neighborIdsOf(id);
@@ -11637,6 +13151,67 @@ function createGraphInstance(opts) {
11637
13151
  }
11638
13152
  return neighbors;
11639
13153
  }
13154
+ function emphasizeNode(id) {
13155
+ const eng = engineIfReady();
13156
+ if (eng === null) return;
13157
+ if (id === null) {
13158
+ emphasizedNodeId = null;
13159
+ applyEmphasis(eng, null);
13160
+ return;
13161
+ }
13162
+ if (scene === null) return;
13163
+ const idx = scene.indexById.get(id);
13164
+ if (idx !== void 0) {
13165
+ emphasizedNodeId = id;
13166
+ applyEmphasis(eng, idx);
13167
+ }
13168
+ }
13169
+ function getPerfSnapshot() {
13170
+ const st = store.getState();
13171
+ const p = pressureSampler.snapshot();
13172
+ const snap = {
13173
+ at: Date.now(),
13174
+ nodeCount: st.nodeCount,
13175
+ edgeCount: st.edgeCount,
13176
+ visibleNodeCount: st.visible.nodes,
13177
+ visibleEdgeCount: st.visible.edges,
13178
+ estimatedCpuBytes: estimateCpuBytes(),
13179
+ // The acceptance queue is synchronous (depth is 0 outside a job, and a
13180
+ // reader inside a job observes 1); async ingestion depth arrives with
13181
+ // the worker lane (PR-E).
13182
+ queueDepth: acceptanceQueue.active ? 1 : 0,
13183
+ modelRevision: st.revisions.model,
13184
+ scopeRevision: st.revisions.scope,
13185
+ renderRevision: st.revisions.render,
13186
+ appliedRenderRevision: st.revisions.appliedRender,
13187
+ activeDegradations: degradeController.activeSteps(),
13188
+ // 'worker' iff the lane actually boots — a configured-but-unavailable
13189
+ // lane reports the honest 'main' (plus its worker-unavailable diag).
13190
+ execution: workerLane !== null && workerLane.available() === true ? "worker" : "main",
13191
+ rangeUpdates: session !== null && session.policy !== null ? [...session.policy.rangedChannels] : [],
13192
+ pressure: {
13193
+ frameEwmaMs: p.frameEwmaMs,
13194
+ droppedFrames: p.droppedFrames,
13195
+ idleWakeups: p.idleWakeups
13196
+ }
13197
+ };
13198
+ if (lastCommitMs !== void 0) snap.lastCommitMs = { ...lastCommitMs };
13199
+ if (scene !== null) {
13200
+ snap.estimatedGpuBytes = scene.count * (2 + 4 + 1) * 4 + scene.linkCount * (4 + 1 + 2) * 4;
13201
+ }
13202
+ return snap;
13203
+ }
13204
+ function estimateCpuBytes() {
13205
+ let bytes = 0;
13206
+ if (scene !== null) bytes += scene.positions.byteLength + scene.links.byteLength;
13207
+ if (basePointColors !== null) bytes += basePointColors.byteLength;
13208
+ if (baseLinkColors !== null) bytes += baseLinkColors.byteLength;
13209
+ if (lastLinkWidths !== null) bytes += lastLinkWidths.byteLength;
13210
+ bytes += metricStore.estimatedBytes();
13211
+ if (crossfilterEngine !== null) bytes += crossfilterEngine.estimatedBytes();
13212
+ if (softMask !== null) bytes += softMask.estimatedBytes();
13213
+ return bytes;
13214
+ }
11640
13215
  function cameraZoom(factor) {
11641
13216
  const eng = engineIfReady();
11642
13217
  if (eng === null) return;
@@ -11709,6 +13284,7 @@ function createGraphInstance(opts) {
11709
13284
  else eng.setViewport(v);
11710
13285
  },
11711
13286
  focusNode,
13287
+ emphasizeNode,
11712
13288
  requestNodeContextMenu,
11713
13289
  getViewState,
11714
13290
  setViewState,
@@ -11729,6 +13305,9 @@ function createGraphInstance(opts) {
11729
13305
  pickEdgeAt,
11730
13306
  sampleEdgeHover,
11731
13307
  sampleEdgeClick,
13308
+ getFrameCadence: () => frameCadence,
13309
+ getPerfCounters: () => perfCounters,
13310
+ getPerfSnapshot,
11732
13311
  hideNodes,
11733
13312
  showNodes,
11734
13313
  showAll,
@@ -11933,6 +13512,73 @@ var OverviewController = class {
11933
13512
  }
11934
13513
  };
11935
13514
 
11936
- export { ATLAS_MAX_CONCURRENT_DEFAULT, ATLAS_MAX_ENTRIES_DEFAULT, ATLAS_MAX_RETRIES_DEFAULT, AcceptanceQueue, BASE_PENDING_KEY, BRUSH_HISTORY_COALESCE_MS, CATEGORICAL_PALETTE, DEFAULT_BIN_COUNT, DIM_ALPHA_DEFAULT, DIVERGING_RANGE_DEFAULT, DomainStore, EDGE_PICK_TOLERANCE_PX, EdgePickingFacade, GRAPH_THEME_DARK, GRAPH_THEME_LIGHT, HISTORY_LIMIT_DEFAULT, HistoryKernel, INGEST_MAX_FLUSH_LATENCY_MS_DEFAULT, INGEST_MAX_PENDING_BYTES_DEFAULT, INGEST_OVERFLOW_FACTOR, ImageAtlasPipeline, LABEL_MAX_VISIBLE_CAP, LABEL_MAX_VISIBLE_DEFAULT, LinkPickIndex, META_EDGE_MAX_WIDTH, MetricStore, OVERVIEW_HOT_INTERVAL_MS, OVERVIEW_IDLE_INTERVAL_MS, OVERVIEW_SIZE_DEFAULT, OrbitOperationError, OverviewController, PHYSICAL_DEFAULT_LINK_WIDTH, PHYSICAL_DEFAULT_POINT_SIZE, PendingExpansions, Reconciler, SEARCH_CACHE_LIMIT, SEARCH_LIMIT_DEFAULT, SEARCH_SCAN_CHUNK, SEARCH_SCORE_EXACT_ID, SEARCH_SCORE_ID_PREFIX, SEARCH_SCORE_SUBSTRING, SEARCH_SCORE_TOKEN_START_BONUS, SEQUENTIAL_RANGE_DEFAULT, SUPER_NODE_MAX_SIZE, SVG_MAX_ELEMENTS_DEFAULT, SoftMask, SvgBudgetError, TIMELINE_STEP_DEFAULT, TIMELINE_TICK_MS_DEFAULT, TypedColumnCrossfilter, VIEW_STATE_VERSION, admitServiceResult, assertCapabilityMethodParity, baseFromAccepted, baseFromContribution, buildAcceptedAdjacency, buildAdjacency, canonicalFilterKey, canonicalJson2 as canonicalJson, canonicalScaleKey, cascadeEdges, categoricalIndex, categoricalRows, coerceNumeric, coerceNumericInto, collapseParallelEdges, compileEdgeFilter, compileNodeFilter, computeNumericDomain, createGraphInstance, createLocalExpansionService, createLocalSearchService, createRequestContext, deriveGroupsByKey, divergingColor, escapeXml, estimateBatchBytes, evaluateFilterExpr, graphErrorToError, groupByDerivedId, groupSceneKey, interpolateColor, isFatalGraphError, medianLinkWidthPx, mergeDiagnostics, mergeModel, metaEdgePublicId, metaEdgeSceneKey, metaEdgeWidthFor, neighborsOf, newContribution, newStagingTallies, nextRequestId, normalizeCommitForCapabilities, parseColor, pointSegmentDistanceSquared, projectColors, projectSizes, renderSvg, resolveEnginePolicy, resolveFilterField, resolveManualGroups, resolveScope, resolveTheme, resourceLimitFatal, rewriteGroups, sameDataRef, sameGroupBySpec, sameGroupSpecArrays, sceneGroupsOf, sceneLinkRefAt, scenePointRefAt, selectLabelCandidates, sequentialColor, sequentialSize, serviceCacheKey, sessionCommitDiagnostics, stageBatch, superNodeSizeFor, validateFilterExpr, validateGroupBySpec, validateGroupSpecs, validateSnapshot, validateViewState };
13515
+ // src/descriptors.ts
13516
+ function field(path) {
13517
+ return path;
13518
+ }
13519
+ var TRANSFORM_OPS = /* @__PURE__ */ new Set(["identity", "number", "lowercase", "date-to-epoch-ms", "coalesce"]);
13520
+ function validateFieldAccessor(value) {
13521
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
13522
+ return { kind: "not-an-object" };
13523
+ }
13524
+ const candidate = value;
13525
+ if (typeof candidate.field !== "string") return { kind: "field-not-a-string" };
13526
+ if (candidate.transform !== void 0) {
13527
+ const t = candidate.transform;
13528
+ if (t === null || typeof t !== "object" || Array.isArray(t)) {
13529
+ return { kind: "transform-not-an-object" };
13530
+ }
13531
+ const op = t.op;
13532
+ if (typeof op !== "string" || !TRANSFORM_OPS.has(op)) {
13533
+ return { kind: "unknown-transform-op", op: String(op) };
13534
+ }
13535
+ if (op === "coalesce" && !("value" in t)) {
13536
+ return { kind: "coalesce-missing-value" };
13537
+ }
13538
+ }
13539
+ return null;
13540
+ }
13541
+ function isFieldAccessor(value) {
13542
+ return validateFieldAccessor(value) === null;
13543
+ }
13544
+ function descriptorKey(accessor) {
13545
+ const t = accessor.transform;
13546
+ if (t === void 0) return `f:${accessor.field}|identity`;
13547
+ if (t.op === "coalesce") return `f:${accessor.field}|coalesce:${JSON.stringify(t.value ?? null)}`;
13548
+ return `f:${accessor.field}|${t.op}`;
13549
+ }
13550
+ function coerceNumber(value) {
13551
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
13552
+ if (typeof value === "string" && value.trim() !== "") {
13553
+ const parsed = Number(value);
13554
+ return Number.isFinite(parsed) ? parsed : null;
13555
+ }
13556
+ return null;
13557
+ }
13558
+ function coerceEpochMs(value) {
13559
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
13560
+ if (typeof value === "string") {
13561
+ const parsed = Date.parse(value);
13562
+ return Number.isNaN(parsed) ? null : parsed;
13563
+ }
13564
+ return null;
13565
+ }
13566
+ function evaluateFieldAccessor(accessor, id, attrs) {
13567
+ const raw = accessor.field === "id" ? id : attrs?.[accessor.field];
13568
+ const t = accessor.transform;
13569
+ if (t === void 0 || t.op === "identity") return raw;
13570
+ switch (t.op) {
13571
+ case "number":
13572
+ return coerceNumber(raw);
13573
+ case "lowercase":
13574
+ return typeof raw === "string" ? raw.toLowerCase() : null;
13575
+ case "date-to-epoch-ms":
13576
+ return coerceEpochMs(raw);
13577
+ case "coalesce":
13578
+ return raw === null || raw === void 0 ? t.value : raw;
13579
+ }
13580
+ }
13581
+
13582
+ export { ATLAS_MAX_CONCURRENT_DEFAULT, ATLAS_MAX_ENTRIES_DEFAULT, ATLAS_MAX_RETRIES_DEFAULT, AcceptanceQueue, BASE_PENDING_KEY, BRUSH_HISTORY_COALESCE_MS, CATEGORICAL_PALETTE, DEFAULT_BIN_COUNT, DIM_ALPHA_DEFAULT, DIVERGING_RANGE_DEFAULT, DomainStore, EDGE_PICK_TOLERANCE_PX, EdgePickingFacade, GRAPH_THEME_DARK, GRAPH_THEME_LIGHT, HISTORY_LIMIT_DEFAULT, HistoryKernel, INGEST_MAX_FLUSH_LATENCY_MS_DEFAULT, INGEST_MAX_PENDING_BYTES_DEFAULT, INGEST_OVERFLOW_FACTOR, ImageAtlasPipeline, LABEL_MAX_VISIBLE_CAP, LABEL_MAX_VISIBLE_DEFAULT, LinkPickIndex, META_EDGE_MAX_WIDTH, MetricStore, OVERVIEW_HOT_INTERVAL_MS, OVERVIEW_IDLE_INTERVAL_MS, OVERVIEW_SIZE_DEFAULT, OrbitOperationError, OverviewController, PHYSICAL_DEFAULT_LINK_WIDTH, PHYSICAL_DEFAULT_POINT_SIZE, PendingExpansions, Reconciler, SEARCH_CACHE_LIMIT, SEARCH_LIMIT_DEFAULT, SEARCH_SCAN_CHUNK, SEARCH_SCORE_EXACT_ID, SEARCH_SCORE_ID_PREFIX, SEARCH_SCORE_SUBSTRING, SEARCH_SCORE_TOKEN_START_BONUS, SEQUENTIAL_RANGE_DEFAULT, SUPER_NODE_MAX_SIZE, SVG_MAX_ELEMENTS_DEFAULT, SoftMask, SvgBudgetError, TIMELINE_STEP_DEFAULT, TIMELINE_TICK_MS_DEFAULT, TypedColumnCrossfilter, VIEW_STATE_VERSION, admitServiceResult, assertCapabilityMethodParity, baseFromAccepted, baseFromContribution, buildAcceptedAdjacency, buildAcceptedFromColumnar, buildAdjacency, canonicalFilterKey, canonicalJson2 as canonicalJson, canonicalScaleKey, cascadeEdges, categoricalIndex, categoricalRows, coerceNumeric, coerceNumericInto, collapseParallelEdges, columnarArrayBuffers, compileEdgeFilter, compileNodeFilter, computeNumericDomain, createGraphInstance, createLocalExpansionService, createLocalSearchService, createRequestContext, deriveGroupsByKey, descriptorKey, detachColumnarBuffers, divergingColor, escapeXml, estimateBatchBytes, evaluateFieldAccessor, evaluateFilterExpr, field, graphErrorToError, groupByDerivedId, groupSceneKey, interpolateColor, isColumnarSnapshot, isFatalGraphError, isFieldAccessor, materializeColumnarSnapshot, medianLinkWidthPx, mergeDiagnostics, mergeModel, metaEdgePublicId, metaEdgeSceneKey, metaEdgeWidthFor, neighborsOf, newContribution, newStagingTallies, nextRequestId, normalizeCommitForCapabilities, parseColor, pointSegmentDistanceSquared, projectColors, projectSizes, renderSvg, resolveEnginePolicy, resolveFilterField, resolveManualGroups, resolveScope, resolveTheme, resourceLimitFatal, rewriteGroups, sameDataRef, sameGroupBySpec, sameGroupSpecArrays, sceneGroupsOf, sceneLinkRefAt, scenePointRefAt, selectLabelCandidates, sequentialColor, sequentialSize, serviceCacheKey, sessionCommitDiagnostics, stageBatch, superNodeSizeFor, validateColumnarStructure, validateFieldAccessor, validateFilterExpr, validateGroupBySpec, validateGroupSpecs, validateSnapshot, validateViewState };
11937
13583
  //# sourceMappingURL=index.js.map
11938
13584
  //# sourceMappingURL=index.js.map