@hviana/sema 0.1.7 → 0.1.9

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 (145) hide show
  1. package/example/train_base.ts +29 -4
  2. package/package.json +1 -1
  3. package/src/alu/src/parser.ts +105 -4
  4. package/src/canon.ts +65 -0
  5. package/src/geometry.ts +26 -30
  6. package/src/index.ts +1 -0
  7. package/src/mind/learning.ts +39 -1
  8. package/src/mind/match.ts +29 -6
  9. package/src/mind/mechanisms/cast.ts +33 -13
  10. package/src/mind/mechanisms/confluence.ts +15 -1
  11. package/src/mind/mechanisms/recall.ts +116 -41
  12. package/src/mind/mind.ts +172 -29
  13. package/src/mind/primitives.ts +66 -1
  14. package/src/mind/recognition.ts +10 -0
  15. package/src/mind/types.ts +10 -0
  16. package/src/store-sqlite.ts +68 -0
  17. package/src/store.ts +27 -0
  18. package/test/13-conversation.test.mjs +77 -27
  19. package/test/35-prefix-edge.test.mjs +86 -0
  20. package/dist/example/demo.d.ts +0 -1
  21. package/dist/example/demo.js +0 -39
  22. package/dist/example/train_base.d.ts +0 -87
  23. package/dist/example/train_base.js +0 -2232
  24. package/dist/src/alphabet.d.ts +0 -7
  25. package/dist/src/alphabet.js +0 -33
  26. package/dist/src/alu/src/alu.d.ts +0 -185
  27. package/dist/src/alu/src/alu.js +0 -440
  28. package/dist/src/alu/src/expr.d.ts +0 -61
  29. package/dist/src/alu/src/expr.js +0 -318
  30. package/dist/src/alu/src/index.d.ts +0 -11
  31. package/dist/src/alu/src/index.js +0 -19
  32. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  33. package/dist/src/alu/src/kernel-arith.js +0 -264
  34. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  35. package/dist/src/alu/src/kernel-bits.js +0 -152
  36. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  37. package/dist/src/alu/src/kernel-logic.js +0 -60
  38. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  39. package/dist/src/alu/src/kernel-nd.js +0 -208
  40. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  41. package/dist/src/alu/src/kernel-numeric.js +0 -366
  42. package/dist/src/alu/src/operation.d.ts +0 -168
  43. package/dist/src/alu/src/operation.js +0 -189
  44. package/dist/src/alu/src/parser.d.ts +0 -212
  45. package/dist/src/alu/src/parser.js +0 -469
  46. package/dist/src/alu/src/resonance.d.ts +0 -55
  47. package/dist/src/alu/src/resonance.js +0 -126
  48. package/dist/src/alu/src/text.d.ts +0 -31
  49. package/dist/src/alu/src/text.js +0 -73
  50. package/dist/src/alu/src/value.d.ts +0 -109
  51. package/dist/src/alu/src/value.js +0 -300
  52. package/dist/src/alu/test/alu.test.d.ts +0 -1
  53. package/dist/src/alu/test/alu.test.js +0 -764
  54. package/dist/src/bytes.d.ts +0 -14
  55. package/dist/src/bytes.js +0 -59
  56. package/dist/src/config.d.ts +0 -111
  57. package/dist/src/config.js +0 -91
  58. package/dist/src/derive/src/deduction.d.ts +0 -125
  59. package/dist/src/derive/src/deduction.js +0 -155
  60. package/dist/src/derive/src/index.d.ts +0 -7
  61. package/dist/src/derive/src/index.js +0 -11
  62. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  63. package/dist/src/derive/src/priority-queue.js +0 -73
  64. package/dist/src/derive/src/rewrite.d.ts +0 -56
  65. package/dist/src/derive/src/rewrite.js +0 -100
  66. package/dist/src/derive/src/trie.d.ts +0 -90
  67. package/dist/src/derive/src/trie.js +0 -217
  68. package/dist/src/derive/test/derive.test.d.ts +0 -1
  69. package/dist/src/derive/test/derive.test.js +0 -122
  70. package/dist/src/extension.d.ts +0 -37
  71. package/dist/src/extension.js +0 -7
  72. package/dist/src/geometry.d.ts +0 -148
  73. package/dist/src/geometry.js +0 -517
  74. package/dist/src/index.d.ts +0 -15
  75. package/dist/src/index.js +0 -17
  76. package/dist/src/ingest-cache.d.ts +0 -41
  77. package/dist/src/ingest-cache.js +0 -161
  78. package/dist/src/mind/articulation.d.ts +0 -6
  79. package/dist/src/mind/articulation.js +0 -99
  80. package/dist/src/mind/attention.d.ts +0 -72
  81. package/dist/src/mind/attention.js +0 -898
  82. package/dist/src/mind/canonical.d.ts +0 -29
  83. package/dist/src/mind/canonical.js +0 -88
  84. package/dist/src/mind/graph-search.d.ts +0 -270
  85. package/dist/src/mind/graph-search.js +0 -847
  86. package/dist/src/mind/index.d.ts +0 -5
  87. package/dist/src/mind/index.js +0 -5
  88. package/dist/src/mind/junction.d.ts +0 -95
  89. package/dist/src/mind/junction.js +0 -262
  90. package/dist/src/mind/learning.d.ts +0 -47
  91. package/dist/src/mind/learning.js +0 -201
  92. package/dist/src/mind/match.d.ts +0 -111
  93. package/dist/src/mind/match.js +0 -422
  94. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  95. package/dist/src/mind/mechanisms/alu.js +0 -29
  96. package/dist/src/mind/mechanisms/cast.d.ts +0 -35
  97. package/dist/src/mind/mechanisms/cast.js +0 -447
  98. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  99. package/dist/src/mind/mechanisms/confluence.js +0 -213
  100. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  101. package/dist/src/mind/mechanisms/cover.js +0 -179
  102. package/dist/src/mind/mechanisms/extraction.d.ts +0 -67
  103. package/dist/src/mind/mechanisms/extraction.js +0 -342
  104. package/dist/src/mind/mechanisms/recall.d.ts +0 -13
  105. package/dist/src/mind/mechanisms/recall.js +0 -166
  106. package/dist/src/mind/mind.d.ts +0 -211
  107. package/dist/src/mind/mind.js +0 -433
  108. package/dist/src/mind/pipeline-mechanism.d.ts +0 -142
  109. package/dist/src/mind/pipeline-mechanism.js +0 -213
  110. package/dist/src/mind/pipeline.d.ts +0 -20
  111. package/dist/src/mind/pipeline.js +0 -185
  112. package/dist/src/mind/primitives.d.ts +0 -54
  113. package/dist/src/mind/primitives.js +0 -184
  114. package/dist/src/mind/rationale.d.ts +0 -134
  115. package/dist/src/mind/rationale.js +0 -162
  116. package/dist/src/mind/reasoning.d.ts +0 -15
  117. package/dist/src/mind/reasoning.js +0 -162
  118. package/dist/src/mind/recognition.d.ts +0 -20
  119. package/dist/src/mind/recognition.js +0 -236
  120. package/dist/src/mind/resonance.d.ts +0 -23
  121. package/dist/src/mind/resonance.js +0 -0
  122. package/dist/src/mind/trace.d.ts +0 -15
  123. package/dist/src/mind/trace.js +0 -73
  124. package/dist/src/mind/traverse.d.ts +0 -113
  125. package/dist/src/mind/traverse.js +0 -488
  126. package/dist/src/mind/types.d.ts +0 -176
  127. package/dist/src/mind/types.js +0 -84
  128. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  129. package/dist/src/rabitq-ivf/src/database.js +0 -201
  130. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  131. package/dist/src/rabitq-ivf/src/index.js +0 -4
  132. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  133. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  134. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  135. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  136. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  137. package/dist/src/rabitq-ivf/src/rabitq.js +0 -283
  138. package/dist/src/sema.d.ts +0 -31
  139. package/dist/src/sema.js +0 -63
  140. package/dist/src/store-sqlite.d.ts +0 -176
  141. package/dist/src/store-sqlite.js +0 -890
  142. package/dist/src/store.d.ts +0 -642
  143. package/dist/src/store.js +0 -1612
  144. package/dist/src/vec.d.ts +0 -31
  145. package/dist/src/vec.js +0 -109
@@ -1,148 +0,0 @@
1
- import { Sema, Space } from "./sema.js";
2
- import { Alphabet } from "./alphabet.js";
3
- /** The store's geometric identity bar: cosine ≥ 1 − 1/√D is the similarity at
4
- * which `intern` already treats two gists as the SAME node. Recall reuses it
5
- * to accept a near-identical query, and the climb to accept a containing form —
6
- * one derived constant, never a tuned threshold. NOTE: this fixed bar is
7
- * the ESTIMATOR floor of an identity claim; a whole-span claim over a span
8
- * longer than the perception quantum must use the scale-aware
9
- * {@link identityBar}, which converts the tolerated fraction into bytes. */
10
- export declare function mergeThreshold(D: number): number;
11
- /** The scale-aware IDENTITY bar for a whole-span resonance claim over a span
12
- * of `len` bytes. Under the linear fold a cosine reads "fraction of aligned
13
- * shared bytes", so a FIXED cosine bar admits a byte budget that grows with
14
- * the span: 1 − 1/√D over a 4·√D-byte span tolerates four whole river
15
- * windows of foreign content while still claiming "near-identical". An
16
- * identity claim may tolerate at most ONE river window W — the perception
17
- * quantum, the same single-window budget near-dedup's differsByOneWindow
18
- * grants — so the bar is 1 − W/len, floored at mergeThreshold(D), below
19
- * which the RaBitQ estimator cannot certify identity anyway. This is the
20
- * angle+magnitude form of the identity test: the ANGLE carries the shared
21
- * fraction, the span's MAGNITUDE (√len, the linear fold's own norm) converts
22
- * the tolerated fraction into tolerated bytes. Derived from W, D and the
23
- * span; never tuned. */
24
- export declare function identityBar(D: number, maxGroup: number, len: number): number;
25
- /** The reach bar: half a river quantum, derived from the fold's own geometry.
26
- * A branch folds up to `maxGroup` children, so two forms that differ in ONE
27
- * whole child — the smallest distinction perception can mean — sit at cosine
28
- * ≈ 1 − 1/maxGroup. Half that quantum, 1 − 1/(2·maxGroup), is closer than any
29
- * single-child difference can be: a positional echo of the same content.
30
- *
31
- * Recall uses this as its confidence floor: a query whose nearest resonant
32
- * form sits below this bar is structurally unrelated to everything in the store
33
- * — further than any single-child variant — and the system returns null rather
34
- * than fabricate an answer from an unrelated form. Derived, never tuned. */
35
- export declare function reachThreshold(maxGroup: number): number;
36
- /** The estimator's own noise floor: 1/√D — ONE standard deviation of the
37
- * cosine between two independent random vectors in D dimensions (the same σ
38
- * {@link significanceBar} takes three of). It is the smallest difference in
39
- * cosine that is distinguishable from the rotation-uniformised RaBitQ
40
- * estimation error (see the MEASUREMENT CAVEAT above): a contrastive margin
41
- * below it is quantisation noise, not evidence. The consensus climb gates a
42
- * region's vote on its discriminative margin clearing this floor — the
43
- * minimal "above noise" bar, one σ, not the stricter 3σ relatedness bar.
44
- * Derived, never tuned. */
45
- export declare function estimatorNoise(D: number): number;
46
- /** The statistical-significance bar for whole-query resonance: 3/√D.
47
- * In D dimensions the expected cosine of two independent random vectors is 0
48
- * with standard deviation 1/√D. A cosine ≥ 3/√D is three standard deviations
49
- * above chance — the query is statistically related to the store, not merely
50
- * sharing random byte noise. Below this bar the consensus climb (which trusts
51
- * sub-region resonance) is skipped: there is no evidence the query belongs to
52
- * the same distribution as the stored content. Derived, never tuned. */
53
- export declare function significanceBar(D: number): number;
54
- /** The concept (halo) threshold: the cosine above which two nodes share a
55
- * distributional concept. A halo is a superposition of episode signatures in
56
- * D-dimensional space, so the expected cosine between two unrelated halos is 0
57
- * with standard deviation 1/√D. The structural midpoint 0.5 separates "more
58
- * similar than not" from noise; the +0.5/√D term adds one half-sigma margin
59
- * that vanishes as D → ∞, accounting for the wider noise band at lower D
60
- * without inventing a tuned constant. At D=1024 this gives 0.516, within
61
- * 3% of 0.5 — existing behavior is preserved while threshold and D move
62
- * together. Derived, never tuned. */
63
- export declare function conceptThreshold(D: number): number;
64
- /** The HALF-DOMINANCE predicate: whether a part covering `partLen` of a
65
- * whole of `wholeLen` covers STRICTLY more than half of it. A span that
66
- * dominates its whole can no longer discriminate the whole's own content —
67
- * the one test behind liftAnswer's keep-the-frame rule, collectRegions'
68
- * wrapper exclusion, and CAST's frame-depth majority (each cites this).
69
- * CAST's frame-FRACTION gate is the deliberately CLOSED variant (≥ ½ is
70
- * already unusable there) and stays inline where it is documented.
71
- * Derived from the structural midpoint, never tuned. */
72
- export declare function dominates(partLen: number, wholeLen: number): boolean;
73
- /** The consensus-vote significance floor: ln(N) + 1/2, where N is the number
74
- * of learnt contexts (edge sources). A single region's IDF-weighted vote for
75
- * an anchor reached through c contexts is at most ln(N/c) ≤ ln(N); the +1/2
76
- * demands the pooled vote exceed what ONE maximally-specific region could
77
- * contribute by half a unit — i.e. genuine corroboration beyond a lone
78
- * region's echo at this corpus scale. The ONE floor both consumers gate on:
79
- * recallByResonance trusting a climb anchor, and commitVotes admitting a
80
- * further point of attention. Defined once here so the two can never
81
- * drift apart. Derived from N, never tuned. */
82
- export declare function consensusFloor(N: number): number;
83
- /** The coverage bar for the reach (interior) index, when vector-similarity
84
- * gating is used. Returns the concept threshold — the structural midpoint
85
- * (~0.5 at D=1024) where two forms are "more similar than not."
86
- *
87
- * Currently UNUSED in the hot training path: interior nodes are indexed
88
- * unconditionally (hash-cons dedup bounds the index naturally).
89
- * Post-hoc structural compaction ({@link Store.compactContentIndex})
90
- * replaces runtime coverage gating with a batch pass that removes
91
- * structurally-isolated entries. Derived, never tuned. */
92
- export declare function coverageBar(_maxGroup: number, D: number): number;
93
- export interface Grid {
94
- width: number;
95
- height: number;
96
- channels: number;
97
- data: Uint8Array;
98
- dims?: number[];
99
- }
100
- /** Find the longest prefix of `bytes` whose leaf-id signature matches a
101
- * known branch via `lookup`. Returns the byte-length of that prefix, or 0. */
102
- export declare function knownPrefixLength(bytes: Uint8Array, leafAt: (i: number) => number | null, lookup: (leafIds: number[]) => number | null): number;
103
- /** Bytes → Sema tree. `leafAt` and `lookup` are store capabilities for
104
- * detecting previously-stored prefixes so the river can split at the
105
- * correct boundary. Pass them through from `perceive`; the geometry
106
- * computes the stable prefix internally.
107
- *
108
- * `boundaries` is the CALLER-computed stable-prefix boundary set (§10.3):
109
- * strictly-increasing proper byte offsets, each the length of a prefix that
110
- * is already a stored whole-stream form. When given, the fold splits into
111
- * the segments between consecutive boundaries — each folded independently,
112
- * exactly as it folded when it was learned — and the segment roots join
113
- * LEFT-NESTED (((s₀·s₁)·s₂)…), so every learnt cumulative-context root
114
- * reappears as an identical subtree (and, by hash-consing, the very same
115
- * node) inside the grown stream. This is what lets a conversation's next
116
- * turn extend perception instead of refolding it: identical prefixes
117
- * produce identical subtrees regardless of what follows them. */
118
- export declare function bytesToTree(space: Space, alphabet: Alphabet, bytes: Uint8Array, leafAt?: (i: number) => number | null, lookup?: (leafIds: number[]) => number | null, boundaries?: readonly number[]): Sema;
119
- /** The PLAIN fold's full level pyramid — every level's item list, bottom
120
- * (leaves) to top (root). Left-grouped folding is RADIX-ALIGNED: the item
121
- * at level L, index i, covers exactly bytes [i·mg^L, (i+1)·mg^L) whenever
122
- * it is a FULL block, and a full block folds bit-identically in ANY byte
123
- * string that contains it at that offset. So a string extended by a
124
- * suffix (a conversation's accumulated context) reuses every full block of
125
- * its prefix's pyramid and refolds only the right edge of each level —
126
- * O(suffix + depth·mg) per extension instead of O(whole), with the
127
- * produced tree BIT-IDENTICAL to a from-scratch plain fold (same nodes,
128
- * same FP ops; reused subtrees are shared objects, and Sema nodes are
129
- * never mutated). Purely an implementation cache: structure and numerics
130
- * never depend on whether a pyramid was available. */
131
- export interface FoldPyramid {
132
- levels: Array<Array<{
133
- tree: Sema;
134
- len: number;
135
- }>>;
136
- bytes: number;
137
- }
138
- /** Plain bytes→tree (identical to capability-less {@link bytesToTree}) that
139
- * also RETURNS its pyramid, reusing `prev` — the pyramid of a PROPER
140
- * prefix of `bytes` (caller guarantees content match and
141
- * prev.bytes < bytes.length). */
142
- export declare function bytesToTreePyramid(space: Space, alphabet: Alphabet, bytes: Uint8Array, prev?: FoldPyramid): {
143
- tree: Sema;
144
- pyramid: FoldPyramid;
145
- };
146
- export declare function hilbertBytes(grid: Grid): Uint8Array;
147
- export declare function gridToTree(space: Space, alphabet: Alphabet, grid: Grid): Sema;
148
- export declare function stackGrids(frames: Grid[]): Grid;
@@ -1,517 +0,0 @@
1
- // geometry.ts — every modality is a stream; geometry is only a reading order.
2
- //
3
- // 1. Each byte is a leaf — an atom carrying its own vector straight from
4
- // the alphabet.
5
- // 2. The river folds leaves upward in fixed-size groups (maxGroup). Items
6
- // that cross the stable-prefix boundary are split so the prefix folds
7
- // identically regardless of what follows — pure structural stability.
8
- // 3. The same rule recurses level after level until one root remains.
9
- import { normalize } from "./vec.js";
10
- import { sema } from "./sema.js";
11
- // ---- geometric constants ----
12
- //
13
- // Every threshold below is a derived function of the fold's own geometry —
14
- // dimension D, maxGroup, etc. — never a tuned magic number. They live here
15
- // (not in a config file) because they follow from the structure itself.
16
- //
17
- // MEASUREMENT CAVEAT: these thresholds are compared against RaBitQ-ESTIMATED
18
- // cosines (1-bit stored codes scored against a 4-bit-quantized query; the
19
- // index never reranks with exact vectors). The derivations assume an exact
20
- // cosine; the estimator adds a small, rotation-uniformised error the bars do
21
- // not model. This is benign for the inequality thresholds (they gate broad
22
- // regions), but it means NO decision may treat an estimated score as exact —
23
- // identity in particular is decided by content-addressed resolve(), never by
24
- // `score >= 1` (see recallByResonance tier 0).
25
- /** The store's geometric identity bar: cosine ≥ 1 − 1/√D is the similarity at
26
- * which `intern` already treats two gists as the SAME node. Recall reuses it
27
- * to accept a near-identical query, and the climb to accept a containing form —
28
- * one derived constant, never a tuned threshold. NOTE: this fixed bar is
29
- * the ESTIMATOR floor of an identity claim; a whole-span claim over a span
30
- * longer than the perception quantum must use the scale-aware
31
- * {@link identityBar}, which converts the tolerated fraction into bytes. */
32
- export function mergeThreshold(D) {
33
- return 1 - 1 / Math.sqrt(D);
34
- }
35
- /** The scale-aware IDENTITY bar for a whole-span resonance claim over a span
36
- * of `len` bytes. Under the linear fold a cosine reads "fraction of aligned
37
- * shared bytes", so a FIXED cosine bar admits a byte budget that grows with
38
- * the span: 1 − 1/√D over a 4·√D-byte span tolerates four whole river
39
- * windows of foreign content while still claiming "near-identical". An
40
- * identity claim may tolerate at most ONE river window W — the perception
41
- * quantum, the same single-window budget near-dedup's differsByOneWindow
42
- * grants — so the bar is 1 − W/len, floored at mergeThreshold(D), below
43
- * which the RaBitQ estimator cannot certify identity anyway. This is the
44
- * angle+magnitude form of the identity test: the ANGLE carries the shared
45
- * fraction, the span's MAGNITUDE (√len, the linear fold's own norm) converts
46
- * the tolerated fraction into tolerated bytes. Derived from W, D and the
47
- * span; never tuned. */
48
- export function identityBar(D, maxGroup, len) {
49
- return Math.max(mergeThreshold(D), 1 - maxGroup / Math.max(1, len));
50
- }
51
- /** The reach bar: half a river quantum, derived from the fold's own geometry.
52
- * A branch folds up to `maxGroup` children, so two forms that differ in ONE
53
- * whole child — the smallest distinction perception can mean — sit at cosine
54
- * ≈ 1 − 1/maxGroup. Half that quantum, 1 − 1/(2·maxGroup), is closer than any
55
- * single-child difference can be: a positional echo of the same content.
56
- *
57
- * Recall uses this as its confidence floor: a query whose nearest resonant
58
- * form sits below this bar is structurally unrelated to everything in the store
59
- * — further than any single-child variant — and the system returns null rather
60
- * than fabricate an answer from an unrelated form. Derived, never tuned. */
61
- export function reachThreshold(maxGroup) {
62
- return 1 - 1 / (2 * maxGroup);
63
- }
64
- /** The estimator's own noise floor: 1/√D — ONE standard deviation of the
65
- * cosine between two independent random vectors in D dimensions (the same σ
66
- * {@link significanceBar} takes three of). It is the smallest difference in
67
- * cosine that is distinguishable from the rotation-uniformised RaBitQ
68
- * estimation error (see the MEASUREMENT CAVEAT above): a contrastive margin
69
- * below it is quantisation noise, not evidence. The consensus climb gates a
70
- * region's vote on its discriminative margin clearing this floor — the
71
- * minimal "above noise" bar, one σ, not the stricter 3σ relatedness bar.
72
- * Derived, never tuned. */
73
- export function estimatorNoise(D) {
74
- return 1 / Math.sqrt(D);
75
- }
76
- /** The statistical-significance bar for whole-query resonance: 3/√D.
77
- * In D dimensions the expected cosine of two independent random vectors is 0
78
- * with standard deviation 1/√D. A cosine ≥ 3/√D is three standard deviations
79
- * above chance — the query is statistically related to the store, not merely
80
- * sharing random byte noise. Below this bar the consensus climb (which trusts
81
- * sub-region resonance) is skipped: there is no evidence the query belongs to
82
- * the same distribution as the stored content. Derived, never tuned. */
83
- export function significanceBar(D) {
84
- return 3 / Math.sqrt(D);
85
- }
86
- /** The concept (halo) threshold: the cosine above which two nodes share a
87
- * distributional concept. A halo is a superposition of episode signatures in
88
- * D-dimensional space, so the expected cosine between two unrelated halos is 0
89
- * with standard deviation 1/√D. The structural midpoint 0.5 separates "more
90
- * similar than not" from noise; the +0.5/√D term adds one half-sigma margin
91
- * that vanishes as D → ∞, accounting for the wider noise band at lower D
92
- * without inventing a tuned constant. At D=1024 this gives 0.516, within
93
- * 3% of 0.5 — existing behavior is preserved while threshold and D move
94
- * together. Derived, never tuned. */
95
- export function conceptThreshold(D) {
96
- return 0.5 + 0.5 / Math.sqrt(D);
97
- }
98
- /** The HALF-DOMINANCE predicate: whether a part covering `partLen` of a
99
- * whole of `wholeLen` covers STRICTLY more than half of it. A span that
100
- * dominates its whole can no longer discriminate the whole's own content —
101
- * the one test behind liftAnswer's keep-the-frame rule, collectRegions'
102
- * wrapper exclusion, and CAST's frame-depth majority (each cites this).
103
- * CAST's frame-FRACTION gate is the deliberately CLOSED variant (≥ ½ is
104
- * already unusable there) and stays inline where it is documented.
105
- * Derived from the structural midpoint, never tuned. */
106
- export function dominates(partLen, wholeLen) {
107
- return partLen * 2 > wholeLen;
108
- }
109
- /** The consensus-vote significance floor: ln(N) + 1/2, where N is the number
110
- * of learnt contexts (edge sources). A single region's IDF-weighted vote for
111
- * an anchor reached through c contexts is at most ln(N/c) ≤ ln(N); the +1/2
112
- * demands the pooled vote exceed what ONE maximally-specific region could
113
- * contribute by half a unit — i.e. genuine corroboration beyond a lone
114
- * region's echo at this corpus scale. The ONE floor both consumers gate on:
115
- * recallByResonance trusting a climb anchor, and commitVotes admitting a
116
- * further point of attention. Defined once here so the two can never
117
- * drift apart. Derived from N, never tuned. */
118
- export function consensusFloor(N) {
119
- return Math.log(N) + 1 / 2;
120
- }
121
- /** The coverage bar for the reach (interior) index, when vector-similarity
122
- * gating is used. Returns the concept threshold — the structural midpoint
123
- * (~0.5 at D=1024) where two forms are "more similar than not."
124
- *
125
- * Currently UNUSED in the hot training path: interior nodes are indexed
126
- * unconditionally (hash-cons dedup bounds the index naturally).
127
- * Post-hoc structural compaction ({@link Store.compactContentIndex})
128
- * replaces runtime coverage gating with a batch pass that removes
129
- * structurally-isolated entries. Derived, never tuned. */
130
- export function coverageBar(_maxGroup, D) {
131
- return conceptThreshold(D);
132
- }
133
- // ---- folding ----
134
- //
135
- // The river fold is a hierarchical prefix network: each level contracts
136
- // groups of `maxGroup` adjacent items into one via permute-then-add
137
- // (positional seat binding), recursing until one root remains.
138
- //
139
- // FLAT per-level fold — one inline loop per level (foldSlice): no per-group
140
- // function calls, no Array.slice per group, the permute and add FUSED
141
- // (`gist[d] += v[seat[d]]`, no scratch buffer), and subtree byte lengths
142
- // carried incrementally on Folded (the old boundary scan re-walked subtrees
143
- // every level — O(n log n)). The per-level SUPERPOSITION is byte-identical
144
- // to the original recursive foldGroup: the same FP additions in the same
145
- // order.
146
- //
147
- // LINEAR fold — intermediate gists are NOT normalized; only the final root is
148
- // (riverFold's single normalize). This is a deliberate change of similarity
149
- // semantics from the original per-group normalize, not a cached optimization:
150
- // the fold is now a pure linear operator — a superposition of positionally-
151
- // bound leaf vectors — so an interior node carries its span's natural
152
- // magnitude and a resonance score reads as byte-proportional overlap rather
153
- // than a scale-free cosine. The mechanisms that depend on that reading
154
- // compensate for it EXPLICITLY, never silently: the contrastive margin on
155
- // approximate votes (voteRegions), company signatures decoupling halo company
156
- // from gist content (sema.ts), and the shared-frame analogy tier (match.ts).
157
- /** Fold `items[start .. start+count)` in groups of `mg` into `out`.
158
- *
159
- * With `force`, the trailing incomplete group (2..mg-1 items) is folded as
160
- * well — only a lone singleton passes through — guaranteeing progress when
161
- * the caller detected a stall. */
162
- function foldSlice(space, items, start, count, out, force) {
163
- const mg = space.maxGroup;
164
- const D = space.D;
165
- const complete = count - (count % mg);
166
- const foldAt = (at, size) => {
167
- const gist = new Float32Array(D);
168
- const kids = new Array(size);
169
- let len = 0;
170
- for (let k = 0; k < size; k++) {
171
- const f = items[at + k];
172
- const seat = space.seats[k].fwd;
173
- const v = f.tree.v;
174
- // Fused permute-and-accumulate — same FP ops, same order as the old
175
- // permuteInto + addInto pair, with no scratch buffer.
176
- for (let d = 0; d < D; d++)
177
- gist[d] += v[seat[d]];
178
- kids[k] = f.tree;
179
- len += f.len;
180
- }
181
- out.push({ tree: sema(gist, null, kids), len });
182
- };
183
- for (let i = 0; i < complete; i += mg)
184
- foldAt(start + i, mg);
185
- const leftover = count - complete;
186
- if (leftover === 0)
187
- return;
188
- if (force && leftover >= 2)
189
- foldAt(start + complete, leftover);
190
- else
191
- for (let i = complete; i < count; i++)
192
- out.push(items[start + i]);
193
- }
194
- function riverFold(space, row, stableBytes) {
195
- if (row.length === 0) {
196
- const z = new Float32Array(space.D);
197
- return { tree: sema(z, new Uint8Array(0), null), len: 0 };
198
- }
199
- let level = row;
200
- while (level.length > 1) {
201
- // Find the item index where accumulated bytes reaches stableBytes.
202
- let boundary = level.length;
203
- if (stableBytes > 0) {
204
- let acc = 0;
205
- for (let i = 0; i < level.length; i++) {
206
- acc += level[i].len;
207
- if (acc >= stableBytes) {
208
- boundary = i + 1;
209
- break;
210
- }
211
- }
212
- }
213
- const next = [];
214
- if (boundary < level.length) {
215
- // Prefix folds independently of the suffix — structural stability.
216
- foldSlice(space, level, 0, boundary, next, false);
217
- foldSlice(space, level, boundary, level.length - boundary, next, false);
218
- }
219
- else {
220
- foldSlice(space, level, 0, level.length, next, false);
221
- }
222
- if (next.length === level.length) {
223
- // Stuck — every group was incomplete. Force-fold to break the stall.
224
- const forced = [];
225
- foldSlice(space, next, 0, next.length, forced, true);
226
- level = forced;
227
- }
228
- else {
229
- level = next;
230
- }
231
- }
232
- // LINEAR fold — this root normalize is the ONLY normalize of the entire
233
- // fold; every intermediate gist stays unnormalized (see the folding
234
- // header). Skipped for a single-leaf input: that root IS the shared
235
- // alphabet vector (already unit), and normalizing in place would mutate the
236
- // alphabet itself.
237
- if (row.length > 1)
238
- normalize(level[0].tree.v);
239
- return level[0];
240
- }
241
- // ---- public API ----
242
- function bytesToLeaves(alphabet, bytes) {
243
- return Array.from(bytes, (b, i) => {
244
- const v = alphabet.vecs[b];
245
- return { tree: sema(v, bytes.slice(i, i + 1), null), len: 1 };
246
- });
247
- }
248
- /** Find the longest prefix of `bytes` whose leaf-id signature matches a
249
- * known branch via `lookup`. Returns the byte-length of that prefix, or 0. */
250
- export function knownPrefixLength(bytes, leafAt, lookup) {
251
- const leafIds = [];
252
- for (let i = 0; i < bytes.length; i++) {
253
- const lid = leafAt(i);
254
- if (lid === null)
255
- break;
256
- leafIds.push(lid);
257
- }
258
- // Match the longest PROPER prefix — a full-length match means the entire
259
- // input already exists as a stored form (e.g. the flat leaf-id branch
260
- // stored alongside the structural root). That would hide the true split
261
- // point and prevent the river from producing the same tree it folded
262
- // during training, so the structural recognition cannot find the right
263
- // forms. A proper prefix guarantees at least two regions.
264
- for (let len = bytes.length - 1; len >= 2; len--) {
265
- if (lookup(leafIds.slice(0, len)) !== null)
266
- return len;
267
- }
268
- return 0;
269
- }
270
- /** Bytes → Sema tree. `leafAt` and `lookup` are store capabilities for
271
- * detecting previously-stored prefixes so the river can split at the
272
- * correct boundary. Pass them through from `perceive`; the geometry
273
- * computes the stable prefix internally.
274
- *
275
- * `boundaries` is the CALLER-computed stable-prefix boundary set (§10.3):
276
- * strictly-increasing proper byte offsets, each the length of a prefix that
277
- * is already a stored whole-stream form. When given, the fold splits into
278
- * the segments between consecutive boundaries — each folded independently,
279
- * exactly as it folded when it was learned — and the segment roots join
280
- * LEFT-NESTED (((s₀·s₁)·s₂)…), so every learnt cumulative-context root
281
- * reappears as an identical subtree (and, by hash-consing, the very same
282
- * node) inside the grown stream. This is what lets a conversation's next
283
- * turn extend perception instead of refolding it: identical prefixes
284
- * produce identical subtrees regardless of what follows them. */
285
- export function bytesToTree(space, alphabet, bytes, leafAt, lookup, boundaries) {
286
- if (bytes.length === 0) {
287
- return sema(alphabet.vecs[0], new Uint8Array(0), null);
288
- }
289
- if (boundaries !== undefined && boundaries.length > 0) {
290
- return stablePrefixFold(space, alphabet, bytes, boundaries);
291
- }
292
- const sb = (leafAt && lookup) ? knownPrefixLength(bytes, leafAt, lookup) : 0;
293
- return riverFold(space, bytesToLeaves(alphabet, bytes), sb > 0 ? sb : bytes.length).tree;
294
- }
295
- /** The stable-prefix segmented fold (§10.3). Each segment between
296
- * consecutive boundaries folds PLAINLY and independently; segment roots
297
- * join left-nested, and only the final root is normalized (the linear-fold
298
- * contract: one normalize per perception). A segment's own inner splits
299
- * need no recursion here: a nested learnt prefix is itself an earlier
300
- * boundary, so the left-nested join reproduces every intermediate learnt
301
- * root ((s₀·s₁) IS the root the store learnt for the first two segments'
302
- * bytes, and so on). */
303
- function stablePrefixFold(space, alphabet, bytes, boundaries) {
304
- const cuts = [];
305
- let prev = 0;
306
- for (const b of boundaries) {
307
- if (b > prev && b < bytes.length) {
308
- cuts.push(b);
309
- prev = b;
310
- }
311
- }
312
- if (cuts.length === 0) {
313
- return riverFold(space, bytesToLeaves(alphabet, bytes), bytes.length).tree;
314
- }
315
- const edges = [0, ...cuts, bytes.length];
316
- const segs = [];
317
- for (let i = 0; i + 1 < edges.length; i++) {
318
- const seg = bytes.subarray(edges[i], edges[i + 1]);
319
- segs.push(riverFoldRaw(space, bytesToLeaves(alphabet, seg)));
320
- }
321
- let cur = segs[0];
322
- for (let i = 1; i < segs.length; i++)
323
- cur = fold2(space, cur, segs[i]);
324
- normalize(cur.tree.v);
325
- return cur.tree;
326
- }
327
- /** Join two folded items as one 2-kid branch — the top-level join of the
328
- * stable-prefix fold, identical FP ops to foldSlice's seat-bound
329
- * accumulation over a group of two. Unnormalized (interior). */
330
- function fold2(space, a, b) {
331
- const D = space.D;
332
- const gist = new Float32Array(D);
333
- const kids = [a.tree, b.tree];
334
- for (let k = 0; k < 2; k++) {
335
- const seat = space.seats[k].fwd;
336
- const v = kids[k].v;
337
- for (let d = 0; d < D; d++)
338
- gist[d] += v[seat[d]];
339
- }
340
- return { tree: sema(gist, null, kids), len: a.len + b.len };
341
- }
342
- /** Plain river fold WITHOUT the final root normalize — the segment-level
343
- * building block of {@link stablePrefixFold} (interiors must keep their
344
- * byte-proportional magnitude; only the whole perception's root is ever
345
- * normalized). */
346
- function riverFoldRaw(space, row) {
347
- if (row.length === 0) {
348
- const z = new Float32Array(space.D);
349
- return { tree: sema(z, new Uint8Array(0), null), len: 0 };
350
- }
351
- if (row.length === 1)
352
- return row[0];
353
- let level = row;
354
- while (level.length > 1) {
355
- const next = [];
356
- foldSlice(space, level, 0, level.length, next, false);
357
- if (next.length === level.length) {
358
- const forced = [];
359
- foldSlice(space, next, 0, next.length, forced, true);
360
- level = forced;
361
- }
362
- else {
363
- level = next;
364
- }
365
- }
366
- return level[0];
367
- }
368
- /** Plain bytes→tree (identical to capability-less {@link bytesToTree}) that
369
- * also RETURNS its pyramid, reusing `prev` — the pyramid of a PROPER
370
- * prefix of `bytes` (caller guarantees content match and
371
- * prev.bytes < bytes.length). */
372
- export function bytesToTreePyramid(space, alphabet, bytes, prev) {
373
- if (bytes.length === 0) {
374
- return {
375
- tree: sema(alphabet.vecs[0], new Uint8Array(0), null),
376
- pyramid: { levels: [], bytes: 0 },
377
- };
378
- }
379
- const mg = space.maxGroup;
380
- const reusable = (L) => {
381
- // prev's TOPMOST level holds its normalized ROOT — reusable blocks must
382
- // be raw interiors, so the top level is always excluded.
383
- if (!prev || L > prev.levels.length - 2)
384
- return null;
385
- return prev.levels[L];
386
- };
387
- // Level 0: reuse the prefix's leaf items wholesale (all are full blocks).
388
- const lv0 = reusable(0);
389
- const row = lv0
390
- ? [...lv0, ...bytesToLeaves(alphabet, bytes.subarray(prev.bytes))]
391
- : bytesToLeaves(alphabet, bytes);
392
- const levels = [row];
393
- let level = row;
394
- let L = 0;
395
- while (level.length > 1) {
396
- let next = [];
397
- // Reuse the leading FULL blocks of prev's level L+1 (len == mg^(L+1),
398
- // wholly inside the prefix); the rule below folds the rest from this
399
- // level's items exactly as the from-scratch fold would.
400
- const blockLen = mg ** (L + 1);
401
- let reused = 0;
402
- const cand = reusable(L + 1);
403
- if (cand) {
404
- const maxFull = Math.floor(prev.bytes / blockLen);
405
- while (reused < maxFull && reused < cand.length &&
406
- cand[reused].len === blockLen)
407
- reused++;
408
- for (let i = 0; i < reused; i++)
409
- next.push(cand[i]);
410
- }
411
- foldSlice(space, level, reused * mg, level.length - reused * mg, next, false);
412
- if (next.length === level.length) {
413
- // Stuck — same force-fold as riverFold (reuse cannot cause it: any
414
- // reused block replaces a whole folded group, so reused > 0 implies
415
- // progress).
416
- const forced = [];
417
- foldSlice(space, next, 0, next.length, forced, true);
418
- next = forced;
419
- }
420
- levels.push(next);
421
- level = next;
422
- L++;
423
- }
424
- if (row.length > 1)
425
- normalize(level[0].tree.v);
426
- return {
427
- tree: level[0].tree,
428
- pyramid: { levels, bytes: bytes.length },
429
- };
430
- }
431
- // ---- n-D Hilbert curve ----
432
- function gridDims(grid) {
433
- if (grid.dims && grid.dims.length > 0)
434
- return grid.dims.slice();
435
- const dims = [grid.height, grid.width];
436
- if (grid.channels > 1)
437
- dims.push(grid.channels);
438
- return dims;
439
- }
440
- function hilbertPoint(index, n, bits) {
441
- const x = new Array(n).fill(0);
442
- for (let b = 0; b < bits; b++) {
443
- for (let d = 0; d < n; d++) {
444
- const bit = (index >>> (b * n + (n - 1 - d))) & 1;
445
- x[d] |= bit << b;
446
- }
447
- }
448
- const N = 1 << bits;
449
- let t = x[n - 1] >> 1;
450
- for (let i = n - 1; i > 0; i--)
451
- x[i] ^= x[i - 1];
452
- x[0] ^= t;
453
- for (let q = 2; q !== N; q <<= 1) {
454
- const p = q - 1;
455
- for (let i = n - 1; i >= 0; i--) {
456
- if (x[i] & q)
457
- x[0] ^= p;
458
- else {
459
- t = (x[0] ^ x[i]) & p;
460
- x[0] ^= t;
461
- x[i] ^= t;
462
- }
463
- }
464
- }
465
- return x;
466
- }
467
- export function hilbertBytes(grid) {
468
- const dims = gridDims(grid);
469
- const n = dims.length;
470
- if (n === 0 || grid.data.length === 0)
471
- return new Uint8Array(0);
472
- if (n === 1)
473
- return grid.data.slice(0, dims[0]);
474
- const maxAxis = Math.max(...dims);
475
- const bits = Math.max(1, Math.ceil(Math.log2(maxAxis)));
476
- const side = 1 << bits;
477
- const total = Math.pow(side, n);
478
- const stride = new Array(n);
479
- stride[n - 1] = 1;
480
- for (let d = n - 2; d >= 0; d--)
481
- stride[d] = stride[d + 1] * dims[d + 1];
482
- const out = [];
483
- for (let h = 0; h < total; h++) {
484
- const pt = hilbertPoint(h, n, bits);
485
- let inside = true, flat = 0;
486
- for (let d = 0; d < n; d++) {
487
- if (pt[d] >= dims[d]) {
488
- inside = false;
489
- break;
490
- }
491
- flat += pt[d] * stride[d];
492
- }
493
- if (inside)
494
- out.push(grid.data[flat]);
495
- }
496
- return Uint8Array.from(out);
497
- }
498
- export function gridToTree(space, alphabet, grid) {
499
- return bytesToTree(space, alphabet, hilbertBytes(grid));
500
- }
501
- export function stackGrids(frames) {
502
- if (frames.length === 0) {
503
- return { width: 0, height: 0, channels: 0, data: new Uint8Array(0) };
504
- }
505
- const frameDims = gridDims(frames[0]);
506
- const per = frames[0].data.length;
507
- const data = new Uint8Array(per * frames.length);
508
- for (let i = 0; i < frames.length; i++)
509
- data.set(frames[i].data, i * per);
510
- return {
511
- width: 0,
512
- height: 0,
513
- channels: 0,
514
- dims: [frames.length, ...frameDims],
515
- data,
516
- };
517
- }
@@ -1,15 +0,0 @@
1
- export * from "./bytes.js";
2
- export * from "./vec.js";
3
- export * from "./sema.js";
4
- export * from "./alphabet.js";
5
- export * from "./geometry.js";
6
- export * from "./store.js";
7
- export * from "./mind/rationale.js";
8
- export * from "./mind/index.js";
9
- export * from "./store-sqlite.js";
10
- export * from "./config.js";
11
- export * from "./extension.js";
12
- export * from "./ingest-cache.js";
13
- export { IvfIndex, Prng, RaBitQuantizer, VectorDatabase, } from "./rabitq-ivf/src/index.js";
14
- export type { DatabaseOptions, ExternalId, IvfConfig, IvfHit, QueryContext, QueryResult, RaBitQOptions, StorageStats, } from "./rabitq-ivf/src/index.js";
15
- export * from "./derive/src/index.js";