@hviana/sema 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/example/demo.d.ts +1 -0
  2. package/dist/example/demo.js +39 -0
  3. package/dist/example/train_base.d.ts +87 -0
  4. package/dist/example/train_base.js +2252 -0
  5. package/dist/src/alphabet.d.ts +7 -0
  6. package/dist/src/alphabet.js +33 -0
  7. package/dist/src/alu/src/alu.d.ts +185 -0
  8. package/dist/src/alu/src/alu.js +440 -0
  9. package/dist/src/alu/src/expr.d.ts +61 -0
  10. package/dist/src/alu/src/expr.js +318 -0
  11. package/dist/src/alu/src/index.d.ts +11 -0
  12. package/dist/src/alu/src/index.js +19 -0
  13. package/dist/src/alu/src/kernel-arith.d.ts +16 -0
  14. package/dist/src/alu/src/kernel-arith.js +264 -0
  15. package/dist/src/alu/src/kernel-bits.d.ts +19 -0
  16. package/dist/src/alu/src/kernel-bits.js +152 -0
  17. package/dist/src/alu/src/kernel-logic.d.ts +4 -0
  18. package/dist/src/alu/src/kernel-logic.js +60 -0
  19. package/dist/src/alu/src/kernel-nd.d.ts +3 -0
  20. package/dist/src/alu/src/kernel-nd.js +208 -0
  21. package/dist/src/alu/src/kernel-numeric.d.ts +54 -0
  22. package/dist/src/alu/src/kernel-numeric.js +366 -0
  23. package/dist/src/alu/src/operation.d.ts +168 -0
  24. package/dist/src/alu/src/operation.js +189 -0
  25. package/dist/src/alu/src/parser.d.ts +221 -0
  26. package/dist/src/alu/src/parser.js +577 -0
  27. package/dist/src/alu/src/resonance.d.ts +55 -0
  28. package/dist/src/alu/src/resonance.js +126 -0
  29. package/dist/src/alu/src/text.d.ts +31 -0
  30. package/dist/src/alu/src/text.js +73 -0
  31. package/dist/src/alu/src/value.d.ts +109 -0
  32. package/dist/src/alu/src/value.js +300 -0
  33. package/dist/src/alu/test/alu.test.d.ts +1 -0
  34. package/dist/src/alu/test/alu.test.js +764 -0
  35. package/dist/src/bytes.d.ts +14 -0
  36. package/dist/src/bytes.js +59 -0
  37. package/dist/src/canon.d.ts +26 -0
  38. package/dist/src/canon.js +57 -0
  39. package/dist/src/config.d.ts +111 -0
  40. package/dist/src/config.js +91 -0
  41. package/dist/src/derive/src/deduction.d.ts +125 -0
  42. package/dist/src/derive/src/deduction.js +155 -0
  43. package/dist/src/derive/src/index.d.ts +7 -0
  44. package/dist/src/derive/src/index.js +11 -0
  45. package/dist/src/derive/src/priority-queue.d.ts +20 -0
  46. package/dist/src/derive/src/priority-queue.js +73 -0
  47. package/dist/src/derive/src/rewrite.d.ts +56 -0
  48. package/dist/src/derive/src/rewrite.js +100 -0
  49. package/dist/src/derive/src/trie.d.ts +90 -0
  50. package/dist/src/derive/src/trie.js +217 -0
  51. package/dist/src/derive/test/derive.test.d.ts +1 -0
  52. package/dist/src/derive/test/derive.test.js +122 -0
  53. package/dist/src/extension.d.ts +37 -0
  54. package/dist/src/extension.js +7 -0
  55. package/dist/src/geometry.d.ts +202 -0
  56. package/dist/src/geometry.js +575 -0
  57. package/dist/src/index.d.ts +16 -0
  58. package/dist/src/index.js +18 -0
  59. package/dist/src/ingest-cache.d.ts +41 -0
  60. package/dist/src/ingest-cache.js +161 -0
  61. package/dist/src/mind/articulation.d.ts +6 -0
  62. package/dist/src/mind/articulation.js +99 -0
  63. package/dist/src/mind/attention.d.ts +405 -0
  64. package/dist/src/mind/attention.js +1829 -0
  65. package/dist/src/mind/bridge.d.ts +30 -0
  66. package/dist/src/mind/bridge.js +569 -0
  67. package/dist/src/mind/canonical.d.ts +29 -0
  68. package/dist/src/mind/canonical.js +88 -0
  69. package/dist/src/mind/graph-search.d.ts +285 -0
  70. package/dist/src/mind/graph-search.js +956 -0
  71. package/dist/src/mind/index.d.ts +7 -0
  72. package/dist/src/mind/index.js +5 -0
  73. package/dist/src/mind/junction.d.ts +124 -0
  74. package/dist/src/mind/junction.js +317 -0
  75. package/dist/src/mind/learning.d.ts +47 -0
  76. package/dist/src/mind/learning.js +248 -0
  77. package/dist/src/mind/match.d.ts +126 -0
  78. package/dist/src/mind/match.js +441 -0
  79. package/dist/src/mind/mechanisms/alu.d.ts +4 -0
  80. package/dist/src/mind/mechanisms/alu.js +36 -0
  81. package/dist/src/mind/mechanisms/cast.d.ts +89 -0
  82. package/dist/src/mind/mechanisms/cast.js +716 -0
  83. package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
  84. package/dist/src/mind/mechanisms/confluence.js +225 -0
  85. package/dist/src/mind/mechanisms/cover.d.ts +6 -0
  86. package/dist/src/mind/mechanisms/cover.js +203 -0
  87. package/dist/src/mind/mechanisms/extraction.d.ts +67 -0
  88. package/dist/src/mind/mechanisms/extraction.js +387 -0
  89. package/dist/src/mind/mechanisms/recall.d.ts +13 -0
  90. package/dist/src/mind/mechanisms/recall.js +325 -0
  91. package/dist/src/mind/mind.d.ts +276 -0
  92. package/dist/src/mind/mind.js +572 -0
  93. package/dist/src/mind/pipeline-mechanism.d.ts +142 -0
  94. package/dist/src/mind/pipeline-mechanism.js +213 -0
  95. package/dist/src/mind/pipeline.d.ts +20 -0
  96. package/dist/src/mind/pipeline.js +217 -0
  97. package/dist/src/mind/primitives.d.ts +70 -0
  98. package/dist/src/mind/primitives.js +287 -0
  99. package/dist/src/mind/rationale.d.ts +139 -0
  100. package/dist/src/mind/rationale.js +163 -0
  101. package/dist/src/mind/reasoning.d.ts +34 -0
  102. package/dist/src/mind/reasoning.js +240 -0
  103. package/dist/src/mind/recognition.d.ts +20 -0
  104. package/dist/src/mind/recognition.js +390 -0
  105. package/dist/src/mind/resonance.d.ts +23 -0
  106. package/dist/src/mind/resonance.js +0 -0
  107. package/dist/src/mind/trace.d.ts +15 -0
  108. package/dist/src/mind/trace.js +73 -0
  109. package/dist/src/mind/traverse.d.ts +113 -0
  110. package/dist/src/mind/traverse.js +568 -0
  111. package/dist/src/mind/types.d.ts +289 -0
  112. package/dist/src/mind/types.js +130 -0
  113. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  114. package/dist/src/rabitq-ivf/src/database.js +201 -0
  115. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  116. package/dist/src/rabitq-ivf/src/index.js +4 -0
  117. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  118. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  119. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  120. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  121. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  122. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  123. package/dist/src/sema.d.ts +31 -0
  124. package/dist/src/sema.js +63 -0
  125. package/dist/src/store-sqlite.d.ts +184 -0
  126. package/dist/src/store-sqlite.js +942 -0
  127. package/dist/src/store.d.ts +663 -0
  128. package/dist/src/store.js +1618 -0
  129. package/dist/src/vec.d.ts +31 -0
  130. package/dist/src/vec.js +109 -0
  131. package/package.json +1 -1
  132. package/src/mind/attention.ts +178 -63
  133. package/test/51-structural-resonance-ladder.test.mjs +6 -3
@@ -0,0 +1,31 @@
1
+ export type Vec = Float32Array;
2
+ /** Deterministic PRNG (32-bit mixer). */
3
+ export declare function rng(seed: number): () => number;
4
+ export declare const zeros: (D: number) => Vec;
5
+ export declare const copy: (v: Vec) => Vec;
6
+ /** Random point on the unit sphere. */
7
+ export declare function randomUnit(D: number, rand: () => number): Vec;
8
+ /** target += src * scale (in place). */
9
+ export declare function addInto(target: Vec, src: Vec, scale?: number): Vec;
10
+ export declare function dot(a: Vec, b: Vec): number;
11
+ /** In-place normalization. */
12
+ export declare function normalize(v: Vec): Vec;
13
+ /** Resonance: 1 = same, 0 = unrelated. */
14
+ export declare function cosine(a: Vec, b: Vec): number;
15
+ /** Set vector epsilon thresholds. Called once by Mind at construction. */
16
+ export declare function setVecConfig(cfg: {
17
+ normalizeEpsilon?: number;
18
+ cosineEpsilon?: number;
19
+ }): void;
20
+ export interface Permutation {
21
+ fwd: Uint32Array;
22
+ inv: Uint32Array;
23
+ }
24
+ /** The keyring: one independent permutation per seat.
25
+ * Independent keys do not commute, so an address in a tree is the
26
+ * path itself — "seat 2 inside seat 1" ≠ "seat 1 inside seat 2". */
27
+ export declare function makeKeyring(D: number, seats: number, rand: () => number): Permutation[];
28
+ /** Apply permutation: out[i] = v[table[i]]. */
29
+ export declare function permute(v: Vec, table: Uint32Array): Vec;
30
+ /** Permute into existing buffer — zero allocation. */
31
+ export declare function permuteInto(out: Vec, v: Vec, table: Uint32Array): Vec;
@@ -0,0 +1,109 @@
1
+ // vec.ts — vector primitives.
2
+ // Superposition (add), normalization (stay on the sphere),
3
+ // resonance (cosine), and seat binding (a keyring of fixed permutations).
4
+ // No weights. No gradients.
5
+ /** Deterministic PRNG (32-bit mixer). */
6
+ export function rng(seed) {
7
+ let s = seed >>> 0;
8
+ return () => {
9
+ s = (s + 0x6d2b79f5) >>> 0;
10
+ let t = s;
11
+ t = Math.imul(t ^ (t >>> 15), t | 1);
12
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
13
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
14
+ };
15
+ }
16
+ /** Standard normal sample (Box–Muller). */
17
+ function gaussian(rand) {
18
+ let u = 0, v = 0;
19
+ while (u === 0)
20
+ u = rand();
21
+ while (v === 0)
22
+ v = rand();
23
+ return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
24
+ }
25
+ export const zeros = (D) => new Float32Array(D);
26
+ export const copy = (v) => new Float32Array(v);
27
+ /** Random point on the unit sphere. */
28
+ export function randomUnit(D, rand) {
29
+ const v = zeros(D);
30
+ for (let i = 0; i < D; i++)
31
+ v[i] = gaussian(rand);
32
+ return normalize(v);
33
+ }
34
+ /** target += src * scale (in place). */
35
+ export function addInto(target, src, scale = 1) {
36
+ for (let i = 0; i < target.length; i++)
37
+ target[i] += src[i] * scale;
38
+ return target;
39
+ }
40
+ export function dot(a, b) {
41
+ let s = 0;
42
+ for (let i = 0; i < a.length; i++)
43
+ s += a[i] * b[i];
44
+ return s;
45
+ }
46
+ const norm = (v) => Math.sqrt(dot(v, v));
47
+ // Epsilon thresholds (settable once via setVecConfig).
48
+ let _normalizeEpsilon = 1e-12;
49
+ let _cosineEpsilon = 1e-12;
50
+ /** In-place normalization. */
51
+ export function normalize(v) {
52
+ const n = norm(v);
53
+ if (n > _normalizeEpsilon) {
54
+ for (let i = 0; i < v.length; i++)
55
+ v[i] /= n;
56
+ }
57
+ return v;
58
+ }
59
+ /** Resonance: 1 = same, 0 = unrelated. */
60
+ export function cosine(a, b) {
61
+ const na = norm(a), nb = norm(b);
62
+ return na > _cosineEpsilon && nb > _cosineEpsilon ? dot(a, b) / (na * nb) : 0;
63
+ }
64
+ /** Set vector epsilon thresholds. Called once by Mind at construction. */
65
+ export function setVecConfig(cfg) {
66
+ if (cfg.normalizeEpsilon !== undefined) {
67
+ _normalizeEpsilon = cfg.normalizeEpsilon;
68
+ }
69
+ if (cfg.cosineEpsilon !== undefined)
70
+ _cosineEpsilon = cfg.cosineEpsilon;
71
+ }
72
+ /** One fixed random permutation (Fisher–Yates). */
73
+ function makePermutation(D, rand) {
74
+ const fwd = new Uint32Array(D);
75
+ for (let i = 0; i < D; i++)
76
+ fwd[i] = i;
77
+ for (let i = D - 1; i > 0; i--) {
78
+ const j = Math.floor(rand() * (i + 1));
79
+ const t = fwd[i];
80
+ fwd[i] = fwd[j];
81
+ fwd[j] = t;
82
+ }
83
+ const inv = new Uint32Array(D);
84
+ for (let i = 0; i < D; i++)
85
+ inv[fwd[i]] = i;
86
+ return { fwd, inv };
87
+ }
88
+ /** The keyring: one independent permutation per seat.
89
+ * Independent keys do not commute, so an address in a tree is the
90
+ * path itself — "seat 2 inside seat 1" ≠ "seat 1 inside seat 2". */
91
+ export function makeKeyring(D, seats, rand) {
92
+ const ring = [];
93
+ for (let s = 0; s < seats; s++)
94
+ ring.push(makePermutation(D, rand));
95
+ return ring;
96
+ }
97
+ /** Apply permutation: out[i] = v[table[i]]. */
98
+ export function permute(v, table) {
99
+ const out = new Float32Array(v.length);
100
+ for (let i = 0; i < v.length; i++)
101
+ out[i] = v[table[i]];
102
+ return out;
103
+ }
104
+ /** Permute into existing buffer — zero allocation. */
105
+ export function permuteInto(out, v, table) {
106
+ for (let i = 0; i < v.length; i++)
107
+ out[i] = v[table[i]];
108
+ return out;
109
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hviana/sema",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Sema: a non-parametric, instance-based reasoning system.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -1549,86 +1549,137 @@ const VARIANT_KIND_ORDER: Record<StructuralVariant["kind"], number> = {
1549
1549
  "double-synonym": 2,
1550
1550
  };
1551
1551
 
1552
- /** A node's structural gist, read directly from its own stored bytes the
1553
- * repository's node gist accessor: content is immutable and perception is
1554
- * a pure function of bytes, so re-perceiving a node's full stored bytes
1555
- * reproduces exactly the gist it was interned with. Never concatenates
1556
- * the sibling's bytes with anything else. */
1557
- function storedNodeGist(ctx: MindContext, id: number): Vec {
1558
- return gistOf(ctx, read(ctx, id));
1552
+ /** A lightweight, cost-free descriptor of one candidate structural variant
1553
+ * enough to rank it, but with no bytes read and no vector materialized. */
1554
+ interface StructuralVariantSpec {
1555
+ kind: "left-synonym" | "right-synonym" | "double-synonym";
1556
+ semanticConfidence: number;
1557
+ leftSiblingId?: number;
1558
+ rightSiblingId?: number;
1559
1559
  }
1560
1560
 
1561
- /** A halo sibling's structural part, occupying the ORIGINAL query-region
1562
- * slot length the sibling replaces only the DIRECTION, never the query's
1563
- * own bytes, position, or gap (see §6 of the spec this implements). */
1564
- function siblingPart(
1561
+ /** Same deterministic ordering the old implementation applied to already-
1562
+ * materialized variants (§8): semantic confidence desc, then kind
1563
+ * (left-synonym, right-synonym, double-synonym), then sibling ids asc. */
1564
+ function compareStructuralVariantSpecs(
1565
+ a: StructuralVariantSpec,
1566
+ b: StructuralVariantSpec,
1567
+ ): number {
1568
+ return b.semanticConfidence - a.semanticConfidence ||
1569
+ VARIANT_KIND_ORDER[a.kind] - VARIANT_KIND_ORDER[b.kind] ||
1570
+ (a.leftSiblingId ?? -1) - (b.leftSiblingId ?? -1) ||
1571
+ (a.rightSiblingId ?? -1) - (b.rightSiblingId ?? -1);
1572
+ }
1573
+
1574
+ /** Every single- and double-synonym combination, as cost-free descriptors —
1575
+ * no `read`, `gistOf`, `perceive` or `StructuralPart` allocation. Both
1576
+ * sibling lists are already bounded by `haloQueryK`, so the O(haloQueryK²)
1577
+ * cross-product here is cheap; only the SELECTED specs go on to pay for
1578
+ * sibling reconstruction. */
1579
+ function buildStructuralVariantSpecs(
1580
+ sides: JunctionSynonymSides,
1581
+ ): StructuralVariantSpec[] {
1582
+ const specs: StructuralVariantSpec[] = [];
1583
+
1584
+ for (const left of sides.leftSiblings) {
1585
+ specs.push({
1586
+ kind: "left-synonym",
1587
+ semanticConfidence: left.score,
1588
+ leftSiblingId: left.id,
1589
+ });
1590
+ }
1591
+
1592
+ for (const right of sides.rightSiblings) {
1593
+ specs.push({
1594
+ kind: "right-synonym",
1595
+ semanticConfidence: right.score,
1596
+ rightSiblingId: right.id,
1597
+ });
1598
+ }
1599
+
1600
+ for (const left of sides.leftSiblings) {
1601
+ for (const right of sides.rightSiblings) {
1602
+ specs.push({
1603
+ kind: "double-synonym",
1604
+ semanticConfidence: Math.min(left.score, right.score),
1605
+ leftSiblingId: left.id,
1606
+ rightSiblingId: right.id,
1607
+ });
1608
+ }
1609
+ }
1610
+
1611
+ specs.sort(compareStructuralVariantSpecs);
1612
+
1613
+ return specs;
1614
+ }
1615
+
1616
+ /** A halo sibling's structural gist, bounded to `maxBytes` of stored content
1617
+ * and reused across the whole climb. `positiveMemo` (shared across every
1618
+ * probe in the climb, passed in by the caller) remembers only successfully
1619
+ * reconstructed complete gists — a sibling rejected here for being too
1620
+ * large for THIS pair's phrase-scale bound may still be admissible for a
1621
+ * larger-spanning pair later, so a rejection is never memoized globally.
1622
+ * `localMemo` is scoped to one `buildStructuralVariants` call, where every
1623
+ * variant shares the same bound, so a `null` there is safe to reuse. */
1624
+ function loadBoundedSiblingGist(
1565
1625
  ctx: MindContext,
1566
- sibling: Hit,
1567
- originalRegion: { start: number; end: number },
1568
- ): StructuralPart {
1569
- return {
1570
- v: storedNodeGist(ctx, sibling.id),
1571
- len: originalRegion.end - originalRegion.start,
1572
- };
1626
+ id: number,
1627
+ maxBytes: number,
1628
+ positiveMemo: Map<number, Vec>,
1629
+ localMemo: Map<number, Vec | null>,
1630
+ ): Vec | null {
1631
+ if (localMemo.has(id)) {
1632
+ return localMemo.get(id) ?? null;
1633
+ }
1634
+
1635
+ const cached = positiveMemo.get(id);
1636
+ if (cached !== undefined) {
1637
+ localMemo.set(id, cached);
1638
+ return cached;
1639
+ }
1640
+
1641
+ const length = ctx.store.contentLen(id, maxBytes + 1);
1642
+ if (length <= 0 || length > maxBytes) {
1643
+ localMemo.set(id, null);
1644
+ return null;
1645
+ }
1646
+
1647
+ const bytes = read(ctx, id, maxBytes + 1);
1648
+ if (bytes.length === 0 || bytes.length > maxBytes) {
1649
+ localMemo.set(id, null);
1650
+ return null;
1651
+ }
1652
+
1653
+ const gist = gistOf(ctx, bytes);
1654
+ positiveMemo.set(id, gist);
1655
+ localMemo.set(id, gist);
1656
+ return gist;
1573
1657
  }
1574
1658
 
1575
1659
  /** Build, bound and order every mandatory structural variant (§7-8): the
1576
1660
  * exact/exact composition is always kept; up to `ctx.cfg.haloQueryK`
1577
1661
  * synonym variants (single- and double-synonym combined, one shared
1578
- * budget) are appended, ordered by confidence, then kind, then sibling id. */
1662
+ * budget) are appended, ordered by confidence, then kind, then sibling id.
1663
+ * Variant selection is entirely lightweight (see {@link
1664
+ * buildStructuralVariantSpecs}); a sibling's bytes are read and perceived
1665
+ * only for specs actually retained, and at most once per sibling id per
1666
+ * climb via `siblingGistMemo`. */
1579
1667
  export function buildStructuralVariants(
1580
1668
  ctx: MindContext,
1581
1669
  ra: Region,
1582
1670
  rb: Region,
1583
1671
  sides: JunctionSynonymSides,
1672
+ siblingGistMemo: Map<number, Vec>,
1584
1673
  ): {
1585
1674
  variants: StructuralVariant[];
1586
1675
  exactLeft: StructuralPart;
1587
1676
  exactRight: StructuralPart;
1588
1677
  } {
1589
- const exactLeft: StructuralPart = { v: ra.v, len: ra.end - ra.start };
1590
- const exactRight: StructuralPart = { v: rb.v, len: rb.end - rb.start };
1591
-
1592
- const synonymVariants: StructuralVariant[] = [];
1593
- for (const ls of sides.leftSiblings) {
1594
- synonymVariants.push({
1595
- left: siblingPart(ctx, ls, ra),
1596
- right: exactRight,
1597
- kind: "left-synonym",
1598
- semanticConfidence: ls.score,
1599
- leftSiblingId: ls.id,
1600
- });
1601
- }
1602
- for (const rs of sides.rightSiblings) {
1603
- synonymVariants.push({
1604
- left: exactLeft,
1605
- right: siblingPart(ctx, rs, rb),
1606
- kind: "right-synonym",
1607
- semanticConfidence: rs.score,
1608
- rightSiblingId: rs.id,
1609
- });
1610
- }
1611
- for (const ls of sides.leftSiblings) {
1612
- for (const rs of sides.rightSiblings) {
1613
- synonymVariants.push({
1614
- left: siblingPart(ctx, ls, ra),
1615
- right: siblingPart(ctx, rs, rb),
1616
- kind: "double-synonym",
1617
- semanticConfidence: Math.min(ls.score, rs.score),
1618
- leftSiblingId: ls.id,
1619
- rightSiblingId: rs.id,
1620
- });
1621
- }
1622
- }
1678
+ const leftLen = ra.end - ra.start;
1679
+ const rightLen = rb.end - rb.start;
1623
1680
 
1624
- // §8: semantic confidence desc, then kind (left-synonym, right-synonym,
1625
- // double-synonym), then left sibling id asc, then right sibling id asc.
1626
- synonymVariants.sort((a, b) =>
1627
- b.semanticConfidence - a.semanticConfidence ||
1628
- VARIANT_KIND_ORDER[a.kind] - VARIANT_KIND_ORDER[b.kind] ||
1629
- (a.leftSiblingId ?? -1) - (b.leftSiblingId ?? -1) ||
1630
- (a.rightSiblingId ?? -1) - (b.rightSiblingId ?? -1)
1631
- );
1681
+ const exactLeft: StructuralPart = { v: ra.v, len: leftLen };
1682
+ const exactRight: StructuralPart = { v: rb.v, len: rightLen };
1632
1683
 
1633
1684
  const variants: StructuralVariant[] = [
1634
1685
  {
@@ -1637,8 +1688,60 @@ export function buildStructuralVariants(
1637
1688
  kind: "exact-exact",
1638
1689
  semanticConfidence: 1,
1639
1690
  },
1640
- ...synonymVariants.slice(0, ctx.cfg.haloQueryK),
1641
1691
  ];
1692
+
1693
+ // Same phrase-scale bound the cross-region junction ladder uses
1694
+ // (`maxInterior`): a sibling whose complete stored content exceeds it is
1695
+ // not materialized as a structural-resonance endpoint, keeping sibling
1696
+ // reconstruction phrase-scale even for a large deposit or conversation
1697
+ // root that merely appeared in a halo result.
1698
+ const maxSiblingBytes = (leftLen + rightLen) * ctx.space.maxGroup;
1699
+
1700
+ const specs = buildStructuralVariantSpecs(sides);
1701
+ const localGistMemo = new Map<number, Vec | null>();
1702
+ let retainedSynonyms = 0;
1703
+
1704
+ for (const spec of specs) {
1705
+ if (retainedSynonyms >= ctx.cfg.haloQueryK) break;
1706
+
1707
+ let left = exactLeft;
1708
+ let right = exactRight;
1709
+
1710
+ if (spec.leftSiblingId !== undefined) {
1711
+ const gist = loadBoundedSiblingGist(
1712
+ ctx,
1713
+ spec.leftSiblingId,
1714
+ maxSiblingBytes,
1715
+ siblingGistMemo,
1716
+ localGistMemo,
1717
+ );
1718
+ if (gist === null) continue;
1719
+ left = { v: gist, len: leftLen };
1720
+ }
1721
+
1722
+ if (spec.rightSiblingId !== undefined) {
1723
+ const gist = loadBoundedSiblingGist(
1724
+ ctx,
1725
+ spec.rightSiblingId,
1726
+ maxSiblingBytes,
1727
+ siblingGistMemo,
1728
+ localGistMemo,
1729
+ );
1730
+ if (gist === null) continue;
1731
+ right = { v: gist, len: rightLen };
1732
+ }
1733
+
1734
+ variants.push({
1735
+ left,
1736
+ right,
1737
+ kind: spec.kind,
1738
+ semanticConfidence: spec.semanticConfidence,
1739
+ leftSiblingId: spec.leftSiblingId,
1740
+ rightSiblingId: spec.rightSiblingId,
1741
+ });
1742
+ retainedSynonyms++;
1743
+ }
1744
+
1642
1745
  return { variants, exactLeft, exactRight };
1643
1746
  }
1644
1747
 
@@ -1675,6 +1778,7 @@ export async function structuralResonance(
1675
1778
  ra: Region,
1676
1779
  rb: Region,
1677
1780
  sides: JunctionSynonymSides,
1781
+ siblingGistMemo: Map<number, Vec>,
1678
1782
  k: number,
1679
1783
  N: number,
1680
1784
  reachMemo: Map<number, AncestorReach>,
@@ -1692,7 +1796,13 @@ export async function structuralResonance(
1692
1796
  | { proposal: StructuralResonanceProposal; reach: AncestorReach; idf: number }
1693
1797
  | null
1694
1798
  > {
1695
- const { variants } = buildStructuralVariants(ctx, ra, rb, sides);
1799
+ const { variants } = buildStructuralVariants(
1800
+ ctx,
1801
+ ra,
1802
+ rb,
1803
+ sides,
1804
+ siblingGistMemo,
1805
+ );
1696
1806
  if (trace) trace.variantBudget = ctx.cfg.haloQueryK;
1697
1807
 
1698
1808
  const middleBytes = query.subarray(ra.end, rb.start);
@@ -1864,6 +1974,10 @@ async function crossRegionVotes(
1864
1974
  //
1865
1975
  // Only MAXIMAL spans compose: a span contained in another candidate is a
1866
1976
  // fragment of that candidate's evidence, never independent of it.
1977
+ // Shared across every cross-region probe in this climb: a sibling
1978
+ // successfully reconstructed while probing one pair must not be read and
1979
+ // perceived again while probing another pair in the same climb.
1980
+ const siblingGistMemo = new Map<number, Vec>();
1867
1981
  const votedSpans = new Set<string>();
1868
1982
  for (const rv of rvs.votes) votedSpans.add(`${rv.start},${rv.end}`);
1869
1983
  const seen = new Set<string>();
@@ -2155,6 +2269,7 @@ async function crossRegionVotes(
2155
2269
  ra,
2156
2270
  rb,
2157
2271
  sides,
2272
+ siblingGistMemo,
2158
2273
  k,
2159
2274
  N,
2160
2275
  reachMemo,
@@ -381,6 +381,7 @@ test("7/8/9. buildStructuralVariants always includes exact-exact, left-synonym,
381
381
  ra,
382
382
  rb,
383
383
  sides,
384
+ new Map(),
384
385
  );
385
386
  assert.equal(variants[0].kind, "exact-exact");
386
387
  assert.equal(variants[0].semanticConfidence, 1);
@@ -410,7 +411,7 @@ test("11. synonym variants are bounded to haloQueryK, in addition to the always-
410
411
  enc("crimson"),
411
412
  enc("square"),
412
413
  );
413
- const { variants } = buildStructuralVariants(m, ra, rb, sides);
414
+ const { variants } = buildStructuralVariants(m, ra, rb, sides, new Map());
414
415
  assert.ok(variants.length <= 1 + m.cfg.haloQueryK);
415
416
  await m.store.close();
416
417
  });
@@ -428,7 +429,7 @@ test("12. a sibling's structural part keeps the ORIGINAL query-region slot lengt
428
429
  enc("crimson"),
429
430
  enc("square"),
430
431
  );
431
- const { variants } = buildStructuralVariants(m, ra, rb, sides);
432
+ const { variants } = buildStructuralVariants(m, ra, rb, sides, new Map());
432
433
  for (const v of variants) {
433
434
  assert.equal(
434
435
  v.left.len,
@@ -483,6 +484,7 @@ test("13/18. structuralResonance composes the real middle query bytes and applie
483
484
  ra,
484
485
  rb,
485
486
  sides,
487
+ new Map(),
486
488
  12,
487
489
  N,
488
490
  new Map(),
@@ -520,7 +522,7 @@ test("15. two variants proposing the SAME candidate keep only the higher-effecti
520
522
  enc("crimson"),
521
523
  enc("square"),
522
524
  );
523
- const { variants } = buildStructuralVariants(m, ra, rb, sides);
525
+ const { variants } = buildStructuralVariants(m, ra, rb, sides, new Map());
524
526
  // Directly exercise the merge logic's contract: build two synthetic
525
527
  // proposals for the SAME id with different effectiveScore and confirm the
526
528
  // higher one is what a caller keeps (mirrors betterProposal's tie-break,
@@ -533,6 +535,7 @@ test("15. two variants proposing the SAME candidate keep only the higher-effecti
533
535
  ra,
534
536
  rb,
535
537
  sides,
538
+ new Map(),
536
539
  12,
537
540
  N,
538
541
  new Map(),