@hviana/sema 0.1.5 → 0.1.7

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 (170) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1700 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +29 -43
  51. package/dist/src/geometry.js +324 -268
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +754 -872
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +141 -185
  82. package/dist/src/mind/mind.d.ts +194 -155
  83. package/dist/src/mind/mind.js +392 -289
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +20 -33
  89. package/dist/src/mind/primitives.js +124 -120
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +198 -210
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +29 -58
  101. package/dist/src/mind/traverse.js +344 -356
  102. package/dist/src/mind/types.d.ts +122 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +171 -166
  117. package/dist/src/store-sqlite.js +768 -662
  118. package/dist/src/store.d.ts +597 -630
  119. package/dist/src/store.js +1434 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +33 -15
  123. package/index.html +65 -0
  124. package/package.json +1 -1
  125. package/src/alu/README.md +1 -1
  126. package/src/alu/src/index.ts +1 -1
  127. package/src/config.ts +19 -27
  128. package/src/geometry.ts +93 -1
  129. package/src/index.ts +6 -11
  130. package/src/mind/attention.ts +11 -6
  131. package/src/mind/mechanisms/recall.ts +29 -13
  132. package/src/mind/mind.ts +250 -19
  133. package/src/mind/primitives.ts +43 -6
  134. package/src/mind/recognition.ts +26 -8
  135. package/src/mind/traverse.ts +47 -0
  136. package/src/mind/types.ts +20 -21
  137. package/src/rabitq-ivf/README.md +56 -0
  138. package/src/rabitq-ivf/src/database.ts +276 -0
  139. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  140. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  141. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  142. package/src/store-sqlite.ts +204 -9
  143. package/src/store.ts +27 -34
  144. package/test/08-storage.test.mjs +3 -3
  145. package/test/13-conversation.test.mjs +190 -20
  146. package/test/14-scaling.test.mjs +2 -2
  147. package/test/35-ivf.test.mjs +263 -0
  148. package/test/36-bloom.test.mjs +123 -0
  149. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  150. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  151. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  152. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  153. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  154. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  155. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  156. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  157. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  158. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  159. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  160. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  161. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  162. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  163. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  164. package/src/rabitq-hnsw/README.md +0 -303
  165. package/src/rabitq-hnsw/src/database.ts +0 -492
  166. package/src/rabitq-hnsw/src/heap.ts +0 -90
  167. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  168. package/src/rabitq-hnsw/src/store.ts +0 -994
  169. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  170. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -9,57 +9,50 @@ import { cosine } from "../vec.js";
9
9
  import { gistOf, read } from "./primitives.js";
10
10
  const structCaches = new WeakMap();
11
11
  function getStructCache(ctx) {
12
- if (ctx.climbMemo === null) {
13
- return null;
14
- }
15
- let c = structCaches.get(ctx.climbMemo);
16
- if (c === undefined) {
17
- structCaches.set(
18
- ctx.climbMemo,
19
- c = {
20
- hasNext: new Map(),
21
- prevCount: new Map(),
22
- hasParents: new Map(),
23
- },
24
- );
25
- }
26
- return c;
12
+ if (ctx.climbMemo === null)
13
+ return null;
14
+ let c = structCaches.get(ctx.climbMemo);
15
+ if (c === undefined) {
16
+ structCaches.set(ctx.climbMemo, c = {
17
+ hasNext: new Map(),
18
+ prevCount: new Map(),
19
+ hasParents: new Map(),
20
+ });
21
+ }
22
+ return c;
27
23
  }
28
24
  /** Cached {@link Store.hasNext} — pure during one respond(). */
29
25
  function cachedHasNext(ctx, id, cache) {
30
- if (cache === null) {
31
- return ctx.store.hasNext(id);
32
- }
33
- let v = cache.hasNext.get(id);
34
- if (v === undefined) {
35
- v = ctx.store.hasNext(id);
36
- cache.hasNext.set(id, v);
37
- }
38
- return v;
26
+ if (cache === null)
27
+ return ctx.store.hasNext(id);
28
+ let v = cache.hasNext.get(id);
29
+ if (v === undefined) {
30
+ v = ctx.store.hasNext(id);
31
+ cache.hasNext.set(id, v);
32
+ }
33
+ return v;
39
34
  }
40
35
  /** Cached {@link Store.prevCount} — pure during one respond(). */
41
36
  function cachedPrevCount(ctx, id, cache) {
42
- if (cache === null) {
43
- return ctx.store.prevCount(id);
44
- }
45
- let v = cache.prevCount.get(id);
46
- if (v === undefined) {
47
- v = ctx.store.prevCount(id);
48
- cache.prevCount.set(id, v);
49
- }
50
- return v;
37
+ if (cache === null)
38
+ return ctx.store.prevCount(id);
39
+ let v = cache.prevCount.get(id);
40
+ if (v === undefined) {
41
+ v = ctx.store.prevCount(id);
42
+ cache.prevCount.set(id, v);
43
+ }
44
+ return v;
51
45
  }
52
46
  /** Cached {@link Store.hasParents} — pure during one respond(). */
53
47
  function cachedHasParents(ctx, id, cache) {
54
- if (cache === null) {
55
- return ctx.store.hasParents(id);
56
- }
57
- let v = cache.hasParents.get(id);
58
- if (v === undefined) {
59
- v = ctx.store.hasParents(id);
60
- cache.hasParents.set(id, v);
61
- }
62
- return v;
48
+ if (cache === null)
49
+ return ctx.store.hasParents(id);
50
+ let v = cache.hasParents.get(id);
51
+ if (v === undefined) {
52
+ v = ctx.store.hasParents(id);
53
+ cache.hasParents.set(id, v);
54
+ }
55
+ return v;
63
56
  }
64
57
  // ── Graph climbing ───────────────────────────────────────────────────────
65
58
  /** Climb the structural DAG from a node to its edge-bearing ancestor contexts.
@@ -74,165 +67,193 @@ function cachedHasParents(ctx, id, cache) {
74
67
  * chunk's prefixes — so without the memo every repeat re-pays the full
75
68
  * fan-out reads. */
76
69
  export function edgeAncestors(ctx, id, contextCount, memo) {
77
- const hit = memo?.get(id);
78
- if (hit !== undefined) {
79
- return hit;
80
- }
81
- const bound = Math.ceil(Math.sqrt(contextCount));
82
- const roots = [];
83
- const seen = new Set([id]);
84
- const ctxSeen = new Set();
85
- let saturated = false;
86
- // EXPAND-UNTIL-DECIDED: a reach is consumed either as a VOTE (which needs
87
- // contextsReached exactly, and only while √N — beyond that the region is
88
- // non-discriminative) or as an ABSTENTION (saturated — whose roots and
89
- // counts no consumer reads). So the climb may STOP the moment the answer
90
- // is decided:
91
- // a node whose prev fan-in alone exceeds √N decides it (its
92
- // predecessors are √N+ distinct contexts) no read needed, prevCount
93
- // is an indexed O(1);
94
- // distinct contexts crossing √N decides it;
95
- // a node with more than √N parents decides its own expansion (the
96
- // classic hub guard; the walk aborts rather than continue, which no
97
- // consumer can distinguish saturated reaches are never voted).
98
- // Below every decision threshold the walk is EXACT — identical roots and
99
- // contexts to the unbounded climb because prevFirst(√N) IS the full prev
100
- // list and parentsFirst(√N+1) IS the full parent list whenever they do not
101
- // decide. Work is bounded by √N contexts × the climb's local structure,
102
- // never by the corpus.
103
- const structCache = getStructCache(ctx);
104
- // LATERAL-BRANCH ACCOUNT — the cumulative dual of the per-node hub guard.
105
- // Within one deposit the ascent is a CHAIN (each node's first parent);
106
- // every parent BEYOND a node's first is an entry into another containing
107
- // structure (hash-consing: a shared subtree's extra parents are other
108
- // deposits' chunks). The per-node guard already declares a node with more
109
- // than √N parents non-discriminative; a climb whose ACCUMULATED lateral
110
- // entries exceed √N has spread across just as many distinct containing
111
- // structures — the same commonness, distributed along the cone instead of
112
- // concentrated at one node — and is decided: saturated. A deep chain in
113
- // ONE structure accrues no laterals, so legitimate deep scaffolding (a
114
- // fragment far down a long cumulative context) still climbs to its root
115
- // at any depth; what dies is the cross-structure drift that visited tens
116
- // of thousands of edge-free interiors (profiled on a 17.7M-node store:
117
- // ~20K distinct nodes per climb family, >95% unique — not memoisable)
118
- // while the context account never decided.
119
- let lateral = 0;
120
- const visit = (x) => {
121
- const hasNx = cachedHasNext(ctx, x, structCache);
122
- const pc = cachedPrevCount(ctx, x, structCache);
123
- if (hasNx || pc > 0) {
124
- roots.push(x);
125
- if (hasNx) {
126
- ctxSeen.add(x);
127
- }
128
- if (pc > bound) {
129
- return false; // decided: ≥ pc > √N distinct contexts
130
- }
131
- for (const p of ctx.store.prevFirst(x, bound)) {
132
- ctxSeen.add(p);
133
- }
134
- if (ctxSeen.size > bound) {
135
- return false; // decided
136
- }
137
- }
138
- const parents = ctx.store.parentsFirst(x, bound + 1);
139
- if (parents.length > bound) {
140
- return false; // decided: hub
70
+ const hit = memo?.get(id);
71
+ if (hit !== undefined)
72
+ return hit;
73
+ // BYTE-ATOM COMMONALITY. A single-byte leaf (implicit negative id) has no
74
+ // structural parents BY CONSTRUCTION — atoms are never linked into the kid
75
+ // or contain tables — so this climb cannot observe its containment at all.
76
+ // The walk below would see only the atom's own direct edges and report
77
+ // contextsReached 1, turning the MOST common content in the store into
78
+ // the MOST discriminative voter (observed on a 325K-context store: every
79
+ // recognised single-letter site voted full ln N for the one fact whose
80
+ // continuation is that letter, and their pooled sum out-voted every
81
+ // genuine anchor). An unmeasurable containment must not default to
82
+ // "maximally rare": it is bounded below by the uniform expectation over
83
+ // the byte alphabet — N contexts, each at least one chunk of up to W of
84
+ // the 256 possible atoms, reach N·W/256 contexts per atom on average
85
+ // (see {@link atomReach}). When that floor itself exceeds the hub bound
86
+ // √N the atom is a hub at this corpus scale and the climb abstains
87
+ // (saturated) the atom's own edges remain fully traversable (tier-0
88
+ // exact recall, chooseNext, project); only its say as a consensus voter
89
+ // is withdrawn. On a small store the floor stays √N and the atom
90
+ // climbs exactly as before, so single-letter facts keep working.
91
+ if (id < 0 && atomIsHub(ctx, contextCount)) {
92
+ const reach = { roots: [], contextsReached: 0, saturated: true };
93
+ memo?.set(id, reach);
94
+ return reach;
141
95
  }
142
- let fresh = 0;
143
- for (const p of parents) {
144
- if (!seen.has(p)) {
145
- seen.add(p);
146
- stack.push(p);
147
- fresh++;
148
- }
149
- }
150
- if (fresh > 1) {
151
- lateral += fresh - 1;
152
- if (lateral > bound) {
153
- return false; // decided: cone-wide hub
154
- }
155
- }
156
- return true;
157
- };
158
- const stack = [];
159
- const containment = !cachedHasParents(ctx, id, structCache);
160
- if (!containment) {
161
- stack.push(id);
162
- }
163
- // The containment seed is STREAMED in pages of √N: a distinctive window's
164
- // containers (which converge on one or two contexts, however many chunks
165
- // of one deposit repeat it) are walked IN FULL exact — while a common
166
- // window's corpus-sized container list is abandoned at the first decision
167
- // above, after O(√N) pages at most (each page adds containers whose climbs
168
- // add contexts; √N distinct contexts decide).
169
- let containerOff = 0;
170
- let containersExhausted = !containment;
171
- climb: for (;;) {
172
- if (stack.length === 0) {
173
- if (containersExhausted) {
174
- break;
175
- }
176
- const page = ctx.store.containersSlice(id, containerOff, bound);
177
- containerOff += page.length;
178
- if (page.length < bound) {
179
- containersExhausted = true;
180
- }
181
- for (const c of page) {
182
- if (!seen.has(c)) {
183
- seen.add(c);
184
- stack.push(c);
96
+ const bound = Math.ceil(Math.sqrt(contextCount));
97
+ const roots = [];
98
+ const seen = new Set([id]);
99
+ const ctxSeen = new Set();
100
+ let saturated = false;
101
+ // EXPAND-UNTIL-DECIDED: a reach is consumed either as a VOTE (which needs
102
+ // contextsReached exactly, and only while ≤ √N — beyond that the region is
103
+ // non-discriminative) or as an ABSTENTION (saturated — whose roots and
104
+ // counts no consumer reads). So the climb may STOP the moment the answer
105
+ // is decided:
106
+ // • a node whose prev fan-in alone exceeds √N decides it (its
107
+ // predecessors are √N+ distinct contexts) — no read needed, prevCount
108
+ // is an indexed O(1);
109
+ // • distinct contexts crossing √N decides it;
110
+ // • a node with more than √N parents decides its own expansion (the
111
+ // classic hub guard; the walk aborts rather than continue, which no
112
+ // consumer can distinguish — saturated reaches are never voted).
113
+ // Below every decision threshold the walk is EXACT — identical roots and
114
+ // contexts to the unbounded climb — because prevFirst(√N) IS the full prev
115
+ // list and parentsFirst(√N+1) IS the full parent list whenever they do not
116
+ // decide. Work is bounded by √N contexts × the climb's local structure,
117
+ // never by the corpus.
118
+ const structCache = getStructCache(ctx);
119
+ // LATERAL-BRANCH ACCOUNT the cumulative dual of the per-node hub guard.
120
+ // Within one deposit the ascent is a CHAIN (each node's first parent);
121
+ // every parent BEYOND a node's first is an entry into another containing
122
+ // structure (hash-consing: a shared subtree's extra parents are other
123
+ // deposits' chunks). The per-node guard already declares a node with more
124
+ // than √N parents non-discriminative; a climb whose ACCUMULATED lateral
125
+ // entries exceed √N has spread across just as many distinct containing
126
+ // structures the same commonness, distributed along the cone instead of
127
+ // concentrated at one node — and is decided: saturated. A deep chain in
128
+ // ONE structure accrues no laterals, so legitimate deep scaffolding (a
129
+ // fragment far down a long cumulative context) still climbs to its root
130
+ // at any depth; what dies is the cross-structure drift that visited tens
131
+ // of thousands of edge-free interiors (profiled on a 17.7M-node store:
132
+ // ~20K distinct nodes per climb family, >95% unique — not memoisable)
133
+ // while the context account never decided.
134
+ let lateral = 0;
135
+ const visit = (x) => {
136
+ const hasNx = cachedHasNext(ctx, x, structCache);
137
+ const pc = cachedPrevCount(ctx, x, structCache);
138
+ if (hasNx || pc > 0) {
139
+ roots.push(x);
140
+ if (hasNx)
141
+ ctxSeen.add(x);
142
+ if (pc > bound)
143
+ return false; // decided: ≥ pc > √N distinct contexts
144
+ for (const p of ctx.store.prevFirst(x, bound))
145
+ ctxSeen.add(p);
146
+ if (ctxSeen.size > bound)
147
+ return false; // decided
185
148
  }
186
- }
187
- if (stack.length === 0) {
188
- if (containerOff === 0) {
189
- stack.push(id); // no containers at all
190
- } else {
191
- break;
149
+ const parents = ctx.store.parentsFirst(x, bound + 1);
150
+ if (parents.length > bound)
151
+ return false; // decided: hub
152
+ let fresh = 0;
153
+ for (const p of parents) {
154
+ if (!seen.has(p)) {
155
+ seen.add(p);
156
+ stack.push(p);
157
+ fresh++;
158
+ }
192
159
  }
193
- }
194
- }
195
- while (stack.length > 0) {
196
- let x = stack.pop();
197
- // TRANSPARENT-CHAIN HOP: a node with no edges in or out and exactly one
198
- // parent contributes nothing here — no root, no context, no lateral
199
- // entry — so the run to its first non-transparent ancestor is skipped
200
- // in ONE store read (Store.chainRun) instead of three probes per node.
201
- // The interior nodes still enter `seen`, exactly as a node-at-a-time
202
- // ascent would have recorded them at push time, so sibling entries into
203
- // the same chain keep identical fresh/lateral accounting; and if the
204
- // terminal was already seen (another chain merged into this one first),
205
- // it is not visited twice the same dedup the push-time seen-check
206
- // used to provide.
207
- const run = ctx.store.chainRun(x);
208
- if (run.length > 1) {
209
- const top = run[run.length - 1];
210
- const dup = seen.has(top);
211
- for (let i = 1; i < run.length; i++) {
212
- seen.add(run[i]);
160
+ if (fresh > 1) {
161
+ lateral += fresh - 1;
162
+ if (lateral > bound)
163
+ return false; // decided: cone-wide hub
164
+ }
165
+ return true;
166
+ };
167
+ const stack = [];
168
+ const containment = !cachedHasParents(ctx, id, structCache);
169
+ if (!containment)
170
+ stack.push(id);
171
+ // The containment seed is STREAMED in pages of √N: a distinctive window's
172
+ // containers (which converge on one or two contexts, however many chunks
173
+ // of one deposit repeat it) are walked IN FULL — exact — while a common
174
+ // window's corpus-sized container list is abandoned at the first decision
175
+ // above, after O(√N) pages at most (each page adds containers whose climbs
176
+ // add contexts; √N distinct contexts decide).
177
+ let containerOff = 0;
178
+ let containersExhausted = !containment;
179
+ climb: for (;;) {
180
+ if (stack.length === 0) {
181
+ if (containersExhausted)
182
+ break;
183
+ const page = ctx.store.containersSlice(id, containerOff, bound);
184
+ containerOff += page.length;
185
+ if (page.length < bound)
186
+ containersExhausted = true;
187
+ for (const c of page) {
188
+ if (!seen.has(c)) {
189
+ seen.add(c);
190
+ stack.push(c);
191
+ }
192
+ }
193
+ if (stack.length === 0) {
194
+ if (containerOff === 0)
195
+ stack.push(id); // no containers at all
196
+ else
197
+ break;
198
+ }
213
199
  }
214
- if (dup) {
215
- continue;
200
+ while (stack.length > 0) {
201
+ let x = stack.pop();
202
+ // TRANSPARENT-CHAIN HOP: a node with no edges in or out and exactly one
203
+ // parent contributes nothing here — no root, no context, no lateral
204
+ // entry — so the run to its first non-transparent ancestor is skipped
205
+ // in ONE store read (Store.chainRun) instead of three probes per node.
206
+ // The interior nodes still enter `seen`, exactly as a node-at-a-time
207
+ // ascent would have recorded them at push time, so sibling entries into
208
+ // the same chain keep identical fresh/lateral accounting; and if the
209
+ // terminal was already seen (another chain merged into this one first),
210
+ // it is not visited twice — the same dedup the push-time seen-check
211
+ // used to provide.
212
+ const run = ctx.store.chainRun(x);
213
+ if (run.length > 1) {
214
+ const top = run[run.length - 1];
215
+ const dup = seen.has(top);
216
+ for (let i = 1; i < run.length; i++)
217
+ seen.add(run[i]);
218
+ if (dup)
219
+ continue;
220
+ x = top;
221
+ }
222
+ if (!visit(x)) {
223
+ saturated = true;
224
+ break climb;
225
+ }
216
226
  }
217
- x = top;
218
- }
219
- if (!visit(x)) {
220
- saturated = true;
221
- break climb;
222
- }
223
227
  }
224
- }
225
- const reach = { roots, contextsReached: ctxSeen.size, saturated };
226
- memo?.set(id, reach);
227
- return reach;
228
+ const reach = { roots, contextsReached: ctxSeen.size, saturated };
229
+ memo?.set(id, reach);
230
+ return reach;
228
231
  }
229
232
  /** Convenience: forward edges of a node. */
230
233
  export function nextOf(ctx, id) {
231
- return ctx.store.next(id);
234
+ return ctx.store.next(id);
232
235
  }
233
236
  /** Convenience: reverse edges of a node. */
234
237
  export function prevOf(ctx, id) {
235
- return ctx.store.prev(id);
238
+ return ctx.store.prev(id);
239
+ }
240
+ /** The uniform-expectation floor on a byte atom's corpus commonality: N
241
+ * learnt contexts, each at least one perception chunk of up to W of the 256
242
+ * possible byte values, contain a given atom in ≥ N·W/256 contexts on
243
+ * average. An atom's TRUE containment is unmeasurable (atoms carry no
244
+ * kid/contain links by construction), so this floor is the honest stand-in:
245
+ * derived entirely from the corpus scale N, the perception window W, and
246
+ * the alphabet size — never tuned. */
247
+ export function atomReach(ctx, contextCount) {
248
+ return Math.max(1, Math.ceil((contextCount * ctx.space.maxGroup) / 256));
249
+ }
250
+ /** Whether a byte atom is a hub at this corpus scale — its commonality floor
251
+ * {@link atomReach} exceeds the hub bound √N. Below it (small stores) an
252
+ * atom votes and is recognised exactly as any stored form; above it the
253
+ * alphabet is scaffolding everywhere and abstains. */
254
+ export function atomIsHub(ctx, contextCount) {
255
+ return atomReach(ctx, contextCount) >
256
+ Math.ceil(Math.sqrt(Math.max(2, contextCount)));
236
257
  }
237
258
  /** Whether a node LEADS SOMEWHERE — it bears a continuation edge or a halo.
238
259
  * The admission predicate recognition filters sites with (HOW_IT_WORKS
@@ -243,11 +264,10 @@ export function prevOf(ctx, id) {
243
264
  * indexed point probe per candidate, and the candidates that reach this
244
265
  * check have already been filtered by hasNext above in edgeAncestors. */
245
266
  export function leadsSomewhere(ctx, id) {
246
- const memo = getStructCache(ctx);
247
- if (cachedHasNext(ctx, id, memo)) {
248
- return true;
249
- }
250
- return ctx.store.hasHalo(id);
267
+ const memo = getStructCache(ctx);
268
+ if (cachedHasNext(ctx, id, memo))
269
+ return true;
270
+ return ctx.store.hasHalo(id);
251
271
  }
252
272
  /** The structural IDF read of ONE node: how many distinct learnt contexts
253
273
  * its containment/edge climb reaches, or Infinity when it reaches none or
@@ -257,25 +277,24 @@ export function leadsSomewhere(ctx, id) {
257
277
  * reaching a corpus MINORITY of contexts discriminates (an entity, a
258
278
  * filler); content reaching a majority is frame scaffolding. */
259
279
  export function reachOf(ctx, id, contextCount, memo) {
260
- const r = edgeAncestors(ctx, id, contextCount, memo);
261
- if (r.saturated || r.roots.length === 0) {
262
- return Infinity;
263
- }
264
- return Math.max(1, r.contextsReached);
280
+ const r = edgeAncestors(ctx, id, contextCount, memo);
281
+ if (r.saturated || r.roots.length === 0)
282
+ return Infinity;
283
+ return Math.max(1, r.contextsReached);
265
284
  }
266
285
  /** The corpus scale N — the count of DISTINCT learnt contexts, floored at 2
267
286
  * so its derived readings (ln N in the consensus floor, √N in the hub bound)
268
287
  * stay meaningful on a near-empty store. The one definition every consumer
269
288
  * of "how big is this corpus?" reads. */
270
289
  export function corpusN(ctx) {
271
- return Math.max(2, ctx.store.edgeSourceCount());
290
+ return Math.max(2, ctx.store.edgeSourceCount());
272
291
  }
273
292
  /** The hub bound √N itself (≥ 2 always, since N is floored at 2) — for
274
293
  * consumers that pass it to the store's LIMITed reads instead of capping a
275
294
  * materialised list. {@link hubCap} is the list-side reading of the same
276
295
  * convention. */
277
296
  export function hubBound(ctx) {
278
- return Math.ceil(Math.sqrt(corpusN(ctx)));
297
+ return Math.ceil(Math.sqrt(corpusN(ctx)));
279
298
  }
280
299
  /** Cap a candidate list at the hub bound √N (insertion order) — the ONE
281
300
  * fan-out convention every walk and disambiguation uses (see HOW_IT_WORKS
@@ -283,35 +302,32 @@ export function hubBound(ctx) {
283
302
  * connections carry ~no discriminative information; materialising or scoring
284
303
  * them all would make single decisions scale with the corpus. */
285
304
  export function hubCap(ctx, ids) {
286
- const bound = hubBound(ctx);
287
- return ids.length > bound ? ids.slice(0, bound) : ids;
305
+ const bound = hubBound(ctx);
306
+ return ids.length > bound ? ids.slice(0, bound) : ids;
288
307
  }
289
308
  /** Whether `descendant` lies within `ancestor`'s subtree — a structural DAG
290
309
  * relation read off the hash-consed `kids` lists, by a bounded explicit-stack
291
310
  * descent. Used by articulation to keep a voice from revoicing a fragment
292
311
  * OF that voice. */
293
312
  export function contains(ctx, ancestor, descendant) {
294
- if (ancestor === descendant) {
295
- return true;
296
- }
297
- const seen = new Set([ancestor]);
298
- const stack = [ancestor];
299
- while (stack.length > 0) {
300
- const rec = ctx.store.get(stack.pop());
301
- if (!rec?.kids) {
302
- continue;
303
- }
304
- for (const k of rec.kids) {
305
- if (k === descendant) {
313
+ if (ancestor === descendant)
306
314
  return true;
307
- }
308
- if (!seen.has(k)) {
309
- seen.add(k);
310
- stack.push(k);
311
- }
315
+ const seen = new Set([ancestor]);
316
+ const stack = [ancestor];
317
+ while (stack.length > 0) {
318
+ const rec = ctx.store.get(stack.pop());
319
+ if (!rec?.kids)
320
+ continue;
321
+ for (const k of rec.kids) {
322
+ if (k === descendant)
323
+ return true;
324
+ if (!seen.has(k)) {
325
+ seen.add(k);
326
+ stack.push(k);
327
+ }
328
+ }
312
329
  }
313
- }
314
- return false;
330
+ return false;
315
331
  }
316
332
  // ── Edge disambiguation (Section 6) ──────────────────────────────────────
317
333
  /** The best-scoring item by cosine against `query`, among items scoring at
@@ -320,61 +336,51 @@ export function contains(ctx, ancestor, descendant) {
320
336
  * caller needs: `true` keeps the first-seen leader on a tie (`>`), the
321
337
  * default lets a later equal score take it (`>=`). */
322
338
  export function argmaxBy(items, scoreOf, threshold, strict = false) {
323
- let best = null;
324
- for (const item of items) {
325
- const score = scoreOf(item);
326
- const bar = best?.score ?? threshold;
327
- if (strict ? score > bar : score >= bar) {
328
- best = { item, score };
339
+ let best = null;
340
+ for (const item of items) {
341
+ const score = scoreOf(item);
342
+ const bar = best?.score ?? threshold;
343
+ if (strict ? score > bar : score >= bar)
344
+ best = { item, score };
329
345
  }
330
- }
331
- return best;
346
+ return best;
332
347
  }
333
348
  export function argmaxCosine(query, items, vecOf, threshold, strict = false) {
334
- return argmaxBy(
335
- items,
336
- (item) => {
337
- const v = vecOf(item);
338
- return v ? cosine(query, v) : -Infinity;
339
- },
340
- threshold,
341
- strict,
342
- );
349
+ return argmaxBy(items, (item) => {
350
+ const v = vecOf(item);
351
+ return v ? cosine(query, v) : -Infinity;
352
+ }, threshold, strict);
343
353
  }
344
354
  /** The guided-or-first continuation of a node, as answer-shaped bytes source:
345
355
  * chooseNext under the response guide, falling back to the FIRST-inserted
346
356
  * edge — the one no-guide convention chooseNext, project() and the search's
347
357
  * formRules all share. undefined when the node has no continuation. */
348
358
  export function guidedFirst(ctx, id) {
349
- const pick = guidedNext(ctx, id);
350
- if (pick !== undefined) {
351
- return pick;
352
- }
353
- // No guide in flight (or nothing chosen): the first-inserted edge, read
354
- // with LIMIT 1 — never the full fan-out.
355
- const nx = ctx.store.nextFirst(id, 1);
356
- return nx.length > 0 ? nx[0] : undefined;
359
+ const pick = guidedNext(ctx, id);
360
+ if (pick !== undefined)
361
+ return pick;
362
+ // No guide in flight (or nothing chosen): the first-inserted edge, read
363
+ // with LIMIT 1 never the full fan-out.
364
+ const nx = ctx.store.nextFirst(id, 1);
365
+ return nx.length > 0 ? nx[0] : undefined;
357
366
  }
358
367
  export function guidedNext(ctx, node) {
359
- if (ctx._edgeGuide === null) {
360
- return undefined;
361
- }
362
- // The pick memo is BYPASSED while a rationale trace is attached — the same
363
- // policy climbMemo and recogniseMemo follow (every mechanism must emit its
364
- // own steps; a memo hit would swallow the repeat's `disambiguate` step).
365
- // Consistency does not need the memo: chooseNext is a pure function of the
366
- // (read-only) store and the guide, so recomputation yields the same pick.
367
- if (!ctx.trace) {
368
- const memo = ctx._edgeChoice.get(node);
369
- if (memo !== undefined) {
370
- return memo === -1 ? undefined : memo;
368
+ if (ctx._edgeGuide === null)
369
+ return undefined;
370
+ // The pick memo is BYPASSED while a rationale trace is attached — the same
371
+ // policy climbMemo and recogniseMemo follow (every mechanism must emit its
372
+ // own steps; a memo hit would swallow the repeat's `disambiguate` step).
373
+ // Consistency does not need the memo: chooseNext is a pure function of the
374
+ // (read-only) store and the guide, so recomputation yields the same pick.
375
+ if (!ctx.trace) {
376
+ const memo = ctx._edgeChoice.get(node);
377
+ if (memo !== undefined)
378
+ return memo === -1 ? undefined : memo;
371
379
  }
372
- }
373
- const pick = chooseNext(ctx, node, ctx._edgeGuide);
374
- if (!ctx.trace) {
375
- ctx._edgeChoice.set(node, pick ?? -1);
376
- }
377
- return pick;
380
+ const pick = chooseNext(ctx, node, ctx._edgeGuide);
381
+ if (!ctx.trace)
382
+ ctx._edgeChoice.set(node, pick ?? -1);
383
+ return pick;
378
384
  }
379
385
  /** Disambiguate among a node's learnt continuations by distributional
380
386
  * support. NOTE the `guide` contract: its VALUE is deliberately unused —
@@ -389,71 +395,61 @@ export function guidedNext(ctx, node) {
389
395
  * therefore scores by guide cosine. The two directions consult different
390
396
  * halves of the evidence on purpose. */
391
397
  export function chooseNext(ctx, id, guide) {
392
- // CAPPED read: only the first √N continuations are ever candidates (the
393
- // documented hub trade), so only they are read — a hub context's full
394
- // fan-out is corpus-sized and must never be materialised. hubBound ≥ 2,
395
- // so the single-continuation fast path below stays exact.
396
- const nx = ctx.store.nextFirst(id, hubBound(ctx));
397
- if (nx.length === 0) {
398
- return undefined;
399
- }
400
- if (nx.length === 1 || !guide) {
401
- return nx[0];
402
- }
403
- // Cap candidates at √N the same bound the original chooseAmong used.
404
- // A hub context can accumulate thousands of continuations; the best-fit
405
- // one is among the first √N by insertion order (edges are never deleted,
406
- // so the oldest are the most established). A strongly-supported edge
407
- // inserted beyond the cap is invisible here the deliberate trade
408
- // against paying O(fan-out) count reads on every disambiguation.
409
- const capped = nx; // already the hub-capped prefix, by the read above
410
- // Distributional-evidence disambiguation, consulting BOTH read-outs of the
411
- // evidence the training poured:
412
- // 1. prevCount how many DISTINCT contexts predict this candidate (one
413
- // indexed COUNT; never a materialisation a common continuation's
414
- // reverse fan-in is corpus-sized). Diversity of independent evidence
415
- // is the primary signal: three different formulations agreeing beat
416
- // one formulation repeated.
417
- // 2. haloMass how many episode signatures were poured into the
418
- // candidate's halo (repetition counts). The tie-break among equally
419
- // diverse candidates: a fact reinforced across many episodes is more
420
- // corroborated than one seen once, and this is the DIRECT measure of
421
- // that consulting only the structural count would leave poured
422
- // evidence on the table.
423
- // When both are equal, first-inserted wins (backward compatible).
424
- let best = capped[0];
425
- let bestSupport = ctx.store.prevCount(best);
426
- let bestMass = ctx.store.haloMass(best);
427
- for (let i = 1; i < capped.length; i++) {
428
- const support = ctx.store.prevCount(capped[i]);
429
- if (support < bestSupport) {
430
- continue;
398
+ // CAPPED read: only the first √N continuations are ever candidates (the
399
+ // documented hub trade), so only they are read — a hub context's full
400
+ // fan-out is corpus-sized and must never be materialised. hubBound ≥ 2,
401
+ // so the single-continuation fast path below stays exact.
402
+ const nx = ctx.store.nextFirst(id, hubBound(ctx));
403
+ if (nx.length === 0)
404
+ return undefined;
405
+ if (nx.length === 1 || !guide)
406
+ return nx[0];
407
+ // Cap candidates at √N — the same bound the original chooseAmong used.
408
+ // A hub context can accumulate thousands of continuations; the best-fit
409
+ // one is among the first √N by insertion order (edges are never deleted,
410
+ // so the oldest are the most established). A strongly-supported edge
411
+ // inserted beyond the cap is invisible here the deliberate trade
412
+ // against paying O(fan-out) count reads on every disambiguation.
413
+ const capped = nx; // already the hub-capped prefix, by the read above
414
+ // Distributional-evidence disambiguation, consulting BOTH read-outs of the
415
+ // evidence the training poured:
416
+ // 1. prevCount how many DISTINCT contexts predict this candidate (one
417
+ // indexed COUNT; never a materialisation — a common continuation's
418
+ // reverse fan-in is corpus-sized). Diversity of independent evidence
419
+ // is the primary signal: three different formulations agreeing beat
420
+ // one formulation repeated.
421
+ // 2. haloMass how many episode signatures were poured into the
422
+ // candidate's halo (repetition counts). The tie-break among equally
423
+ // diverse candidates: a fact reinforced across many episodes is more
424
+ // corroborated than one seen once, and this is the DIRECT measure of
425
+ // that consulting only the structural count would leave poured
426
+ // evidence on the table.
427
+ // When both are equal, first-inserted wins (backward compatible).
428
+ let best = capped[0];
429
+ let bestSupport = ctx.store.prevCount(best);
430
+ let bestMass = ctx.store.haloMass(best);
431
+ for (let i = 1; i < capped.length; i++) {
432
+ const support = ctx.store.prevCount(capped[i]);
433
+ if (support < bestSupport)
434
+ continue;
435
+ const mass = ctx.store.haloMass(capped[i]);
436
+ if (support > bestSupport || mass > bestMass) {
437
+ best = capped[i];
438
+ bestSupport = support;
439
+ bestMass = mass;
440
+ }
431
441
  }
432
- const mass = ctx.store.haloMass(capped[i]);
433
- if (support > bestSupport || mass > bestMass) {
434
- best = capped[i];
435
- bestSupport = support;
436
- bestMass = mass;
442
+ // Trace is built lazily — the filter + map below only execute when a
443
+ // trace listener is attached, so the common (no-trace) path pays only
444
+ // for the prevCount calls in the loop above, never for extra rItemShort
445
+ // byte-reads.
446
+ if (ctx.trace) {
447
+ const others = capped.filter((c) => c !== best);
448
+ ctx.trace.step("disambiguate", [rItemShort(ctx, best, "halo-evidence", bestSupport)], others.map((c) => rItemShort(ctx, c, "candidate", ctx.store.prevCount(c))), `${capped.length} continuations — distributional evidence selects ` +
449
+ `the most corroborated (distinct contexts ${bestSupport}, ` +
450
+ `poured mass ${bestMass})`);
437
451
  }
438
- }
439
- // Trace is built lazily — the filter + map below only execute when a
440
- // trace listener is attached, so the common (no-trace) path pays only
441
- // for the prevCount calls in the loop above, never for extra rItemShort
442
- // byte-reads.
443
- if (ctx.trace) {
444
- const others = capped.filter((c) => c !== best);
445
- ctx.trace.step(
446
- "disambiguate",
447
- [rItemShort(ctx, best, "halo-evidence", bestSupport)],
448
- others.map((c) =>
449
- rItemShort(ctx, c, "candidate", ctx.store.prevCount(c))
450
- ),
451
- `${capped.length} continuations — distributional evidence selects ` +
452
- `the most corroborated (distinct contexts ${bestSupport}, ` +
453
- `poured mass ${bestMass})`,
454
- );
455
- }
456
- return best;
452
+ return best;
457
453
  }
458
454
  /** The perceived gist of a candidate node, through the session gist cache.
459
455
  * Re-gisting a candidate is a full river fold of its bytes — the measured
@@ -463,38 +459,30 @@ export function chooseNext(ctx, id, guide) {
463
459
  * every "score node ids against a guide" decision (chooseAmong here, the
464
460
  * bridge's junction pick) so they share ONE cache and one convention. */
465
461
  export function candidateGist(ctx, c) {
466
- const hit = ctx._gistCache.get(c);
467
- if (hit !== undefined) {
468
- return hit;
469
- }
470
- const b = read(ctx, c);
471
- if (b.length === 0) {
472
- return null;
473
- }
474
- const g = gistOf(ctx, b);
475
- ctx._gistCache.set(c, g);
476
- return g;
462
+ const hit = ctx._gistCache.get(c);
463
+ if (hit !== undefined)
464
+ return hit;
465
+ const b = read(ctx, c);
466
+ if (b.length === 0)
467
+ return null;
468
+ const g = gistOf(ctx, b);
469
+ ctx._gistCache.set(c, g);
470
+ return g;
477
471
  }
478
472
  export function chooseAmong(ctx, candidates, guide) {
479
- const capped = hubCap(ctx, candidates);
480
- const found = argmaxCosine(
481
- guide,
482
- capped,
483
- (c) => candidateGist(ctx, c),
484
- -Infinity,
485
- true,
486
- );
487
- return found
488
- ? { id: found.item, score: found.score }
489
- : { id: candidates[0], score: -Infinity };
473
+ const capped = hubCap(ctx, candidates);
474
+ const found = argmaxCosine(guide, capped, (c) => candidateGist(ctx, c), -Infinity, true);
475
+ return found
476
+ ? { id: found.item, score: found.score }
477
+ : { id: candidates[0], score: -Infinity };
490
478
  }
491
479
  // ── Trace shim (used by chooseNext before trace module is loaded) ────────
492
480
  import { decodeText } from "./rationale.js";
493
481
  function rItemShort(ctx, id, role, score) {
494
- return {
495
- text: decodeText(read(ctx, id)),
496
- node: id,
497
- role,
498
- score,
499
- };
482
+ return {
483
+ text: decodeText(read(ctx, id)),
484
+ node: id,
485
+ role,
486
+ score,
487
+ };
500
488
  }