@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
package/dist/src/vec.d.ts CHANGED
@@ -14,21 +14,17 @@ export declare function normalize(v: Vec): Vec;
14
14
  export declare function cosine(a: Vec, b: Vec): number;
15
15
  /** Set vector epsilon thresholds. Called once by Mind at construction. */
16
16
  export declare function setVecConfig(cfg: {
17
- normalizeEpsilon?: number;
18
- cosineEpsilon?: number;
17
+ normalizeEpsilon?: number;
18
+ cosineEpsilon?: number;
19
19
  }): void;
20
20
  export interface Permutation {
21
- fwd: Uint32Array;
22
- inv: Uint32Array;
21
+ fwd: Uint32Array;
22
+ inv: Uint32Array;
23
23
  }
24
24
  /** The keyring: one independent permutation per seat.
25
25
  * Independent keys do not commute, so an address in a tree is the
26
26
  * path itself — "seat 2 inside seat 1" ≠ "seat 1 inside seat 2". */
27
- export declare function makeKeyring(
28
- D: number,
29
- seats: number,
30
- rand: () => number,
31
- ): Permutation[];
27
+ export declare function makeKeyring(D: number, seats: number, rand: () => number): Permutation[];
32
28
  /** Apply permutation: out[i] = v[table[i]]. */
33
29
  export declare function permute(v: Vec, table: Uint32Array): Vec;
34
30
  /** Permute into existing buffer — zero allocation. */
package/dist/src/vec.js CHANGED
@@ -4,49 +4,44 @@
4
4
  // No weights. No gradients.
5
5
  /** Deterministic PRNG (32-bit mixer). */
6
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
- };
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
15
  }
16
16
  /** Standard normal sample (Box–Muller). */
17
17
  function gaussian(rand) {
18
- let u = 0, v = 0;
19
- while (u === 0) {
20
- u = rand();
21
- }
22
- while (v === 0) {
23
- v = rand();
24
- }
25
- return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
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);
26
24
  }
27
25
  export const zeros = (D) => new Float32Array(D);
28
26
  export const copy = (v) => new Float32Array(v);
29
27
  /** Random point on the unit sphere. */
30
28
  export function randomUnit(D, rand) {
31
- const v = zeros(D);
32
- for (let i = 0; i < D; i++) {
33
- v[i] = gaussian(rand);
34
- }
35
- return normalize(v);
29
+ const v = zeros(D);
30
+ for (let i = 0; i < D; i++)
31
+ v[i] = gaussian(rand);
32
+ return normalize(v);
36
33
  }
37
34
  /** target += src * scale (in place). */
38
35
  export function addInto(target, src, scale = 1) {
39
- for (let i = 0; i < target.length; i++) {
40
- target[i] += src[i] * scale;
41
- }
42
- return target;
36
+ for (let i = 0; i < target.length; i++)
37
+ target[i] += src[i] * scale;
38
+ return target;
43
39
  }
44
40
  export function dot(a, b) {
45
- let s = 0;
46
- for (let i = 0; i < a.length; i++) {
47
- s += a[i] * b[i];
48
- }
49
- return s;
41
+ let s = 0;
42
+ for (let i = 0; i < a.length; i++)
43
+ s += a[i] * b[i];
44
+ return s;
50
45
  }
51
46
  const norm = (v) => Math.sqrt(dot(v, v));
52
47
  // Epsilon thresholds (settable once via setVecConfig).
@@ -54,68 +49,61 @@ let _normalizeEpsilon = 1e-12;
54
49
  let _cosineEpsilon = 1e-12;
55
50
  /** In-place normalization. */
56
51
  export function normalize(v) {
57
- const n = norm(v);
58
- if (n > _normalizeEpsilon) {
59
- for (let i = 0; i < v.length; i++) {
60
- v[i] /= n;
52
+ const n = norm(v);
53
+ if (n > _normalizeEpsilon) {
54
+ for (let i = 0; i < v.length; i++)
55
+ v[i] /= n;
61
56
  }
62
- }
63
- return v;
57
+ return v;
64
58
  }
65
59
  /** Resonance: 1 = same, 0 = unrelated. */
66
60
  export function cosine(a, b) {
67
- const na = norm(a), nb = norm(b);
68
- return na > _cosineEpsilon && nb > _cosineEpsilon ? dot(a, b) / (na * nb) : 0;
61
+ const na = norm(a), nb = norm(b);
62
+ return na > _cosineEpsilon && nb > _cosineEpsilon ? dot(a, b) / (na * nb) : 0;
69
63
  }
70
64
  /** Set vector epsilon thresholds. Called once by Mind at construction. */
71
65
  export function setVecConfig(cfg) {
72
- if (cfg.normalizeEpsilon !== undefined) {
73
- _normalizeEpsilon = cfg.normalizeEpsilon;
74
- }
75
- if (cfg.cosineEpsilon !== undefined) {
76
- _cosineEpsilon = cfg.cosineEpsilon;
77
- }
66
+ if (cfg.normalizeEpsilon !== undefined) {
67
+ _normalizeEpsilon = cfg.normalizeEpsilon;
68
+ }
69
+ if (cfg.cosineEpsilon !== undefined)
70
+ _cosineEpsilon = cfg.cosineEpsilon;
78
71
  }
79
72
  /** One fixed random permutation (Fisher–Yates). */
80
73
  function makePermutation(D, rand) {
81
- const fwd = new Uint32Array(D);
82
- for (let i = 0; i < D; i++) {
83
- fwd[i] = i;
84
- }
85
- for (let i = D - 1; i > 0; i--) {
86
- const j = Math.floor(rand() * (i + 1));
87
- const t = fwd[i];
88
- fwd[i] = fwd[j];
89
- fwd[j] = t;
90
- }
91
- const inv = new Uint32Array(D);
92
- for (let i = 0; i < D; i++) {
93
- inv[fwd[i]] = i;
94
- }
95
- return { fwd, inv };
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 };
96
87
  }
97
88
  /** The keyring: one independent permutation per seat.
98
89
  * Independent keys do not commute, so an address in a tree is the
99
90
  * path itself — "seat 2 inside seat 1" ≠ "seat 1 inside seat 2". */
100
91
  export function makeKeyring(D, seats, rand) {
101
- const ring = [];
102
- for (let s = 0; s < seats; s++) {
103
- ring.push(makePermutation(D, rand));
104
- }
105
- return ring;
92
+ const ring = [];
93
+ for (let s = 0; s < seats; s++)
94
+ ring.push(makePermutation(D, rand));
95
+ return ring;
106
96
  }
107
97
  /** Apply permutation: out[i] = v[table[i]]. */
108
98
  export function permute(v, table) {
109
- const out = new Float32Array(v.length);
110
- for (let i = 0; i < v.length; i++) {
111
- out[i] = v[table[i]];
112
- }
113
- return out;
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;
114
103
  }
115
104
  /** Permute into existing buffer — zero allocation. */
116
105
  export function permuteInto(out, v, table) {
117
- for (let i = 0; i < v.length; i++) {
118
- out[i] = v[table[i]];
119
- }
120
- return out;
106
+ for (let i = 0; i < v.length; i++)
107
+ out[i] = v[table[i]];
108
+ return out;
121
109
  }
@@ -1610,20 +1610,23 @@ async function saveProgress(store: Store, p: SavedProgress): Promise<void> {
1610
1610
  // ═══════════════════════════════════════════════════════════════════════
1611
1611
 
1612
1612
  async function main(): Promise<void> {
1613
- // The vector indices' single memory knob (MiB) — sizes both the SQLite page
1614
- // cache and the decoded-code LRU of each index. Training is HNSW-insert
1615
- // bound at scale (profiled at >80% of ingest wall-clock on a 5.5M-vector
1616
- // store, nearly all of it cache-missing code reads), and inserts walk the
1617
- // graph, so a cache that covers the indexed codes cuts wall-clock directly
1618
- // (measured ~1. at 1 GB vs 64 MB on a 2M-node store). 1 GB caches ~5.8M
1619
- // D=1024 codes the whole content index of a 350 MB-corpus store — and is
1620
- // the default; override with VECTOR_CACHE_MB (e.g. 256 on a small-RAM
1621
- // machine, 64 for the library default).
1622
- const VECTOR_CACHE_MB = Math.max(0, Number(env("VECTOR_CACHE_MB", "1024")));
1613
+ // The vector indices' memory knob (MiB) — each index's SQLite page cache.
1614
+ // The IVF index routes inserts through a RAM-resident pivot table and
1615
+ // appends to chunk blobs, so this cache mostly serves query-time cluster
1616
+ // scans; 256 MiB comfortably covers the probed working set of a trained
1617
+ // store. Override with VECTOR_CACHE_MB (64 is the library default).
1618
+ const VECTOR_CACHE_MB = Math.max(0, Number(env("VECTOR_CACHE_MB", "256")));
1619
+ // Page cache for the MAIN DAG database (node/kid/edge/contain tables).
1620
+ // Training issues millions of content-addressed point probes per session
1621
+ // against a GB-scale file; the library default (64 MiB) is sized for a
1622
+ // small machine — a training box affords more. Override with
1623
+ // SQLITE_CACHE_MB.
1624
+ const SQLITE_CACHE_MB = Math.max(0, Number(env("SQLITE_CACHE_MB", "256")));
1623
1625
  const store = new SQliteStore({
1624
1626
  path: DB_PATH,
1625
1627
  D,
1626
1628
  vectorCacheMb: VECTOR_CACHE_MB,
1629
+ sqliteCacheMb: SQLITE_CACHE_MB,
1627
1630
  });
1628
1631
 
1629
1632
  // The store IS the model: memories, progress, and metadata all persist in
@@ -1822,9 +1825,22 @@ async function main(): Promise<void> {
1822
1825
 
1823
1826
  /** Run index maintenance: compact (remove garbage) then repair (fill gaps).
1824
1827
  * Both are idempotent — running twice produces the same result as once.
1825
- * Compaction frees index space first; repair then adds back the bridges
1826
- * whose gists were evicted before indexing, completing the coverage that
1827
- * incremental bridge promotion alone cannot guarantee.
1828
+ * Compaction frees index space first; repair then adds back every
1829
+ * edge/halo-bearing node whose gist was evicted from the pending cache
1830
+ * before it reached the content index, completing the coverage that
1831
+ * incremental promotion alone cannot guarantee.
1832
+ *
1833
+ * repair runs with minParents = 0, NOT the library default of 2. The
1834
+ * default repairs only structural BRIDGES (≥2 parents), but this
1835
+ * trainer's fact deposits also leave answer-side DEPOSIT ROOTS with 0
1836
+ * structural parents ("The capital of France is Paris." as the dst of a
1837
+ * Q→A edge is a root of its own tree, contained in nothing). Those are
1838
+ * resonance targets recall depends on — a trained store shipped without
1839
+ * them cannot ground statement-shaped queries against its own answers
1840
+ * (observed: 33 such roots missing after a full curriculum, including
1841
+ * high-traffic conversation replies). minParents = 0 admits every
1842
+ * edge/halo bearer; the candidate set is still corpus-of-experiences-
1843
+ * sized, so the pass stays cheap.
1828
1844
  *
1829
1845
  * Logs the number of entries removed/added so a run that silently degrades
1830
1846
  * (growing compaction count, or repair never recovering anything) is
@@ -1846,10 +1862,12 @@ async function main(): Promise<void> {
1846
1862
  );
1847
1863
  }
1848
1864
  try {
1849
- const added = await mind.repairContentIndex();
1865
+ const added = await mind.repairContentIndex(0);
1850
1866
  if (added > 0) {
1851
1867
  progress.log(
1852
- ` ${GRN}index repair: added ${int(added)} missing bridges${R}`,
1868
+ ` ${GRN}index repair: added ${
1869
+ int(added)
1870
+ } missing resonance targets${R}`,
1853
1871
  );
1854
1872
  }
1855
1873
  } catch (err) {
package/index.html CHANGED
@@ -52,6 +52,10 @@
52
52
  z-index: 0;
53
53
  overflow: hidden;
54
54
  background: var(--bg);
55
+ /* own stacking context + GPU layer: keeps the blend-mode orbs from
56
+ forcing full-page re-rasterization every frame on mobile */
57
+ isolation: isolate;
58
+ transform: translateZ(0);
55
59
  }
56
60
 
57
61
  /* — colour weather: two huge drifting orbs + aurora sweep — */
@@ -60,6 +64,7 @@
60
64
  border-radius: 50%;
61
65
  filter: blur(70px);
62
66
  mix-blend-mode: screen;
67
+ will-change: transform;
63
68
  }
64
69
  .orb.a {
65
70
  width: 75vmax;
@@ -130,6 +135,7 @@
130
135
  transparent 360deg
131
136
  );
132
137
  animation: spin 70s linear infinite;
138
+ will-change: transform;
133
139
  }
134
140
  @keyframes spin {
135
141
  to {
@@ -158,6 +164,7 @@
158
164
  );
159
165
  animation: drift 50s linear infinite alternate;
160
166
  opacity: 0.8;
167
+ will-change: transform;
161
168
  }
162
169
  @keyframes drift {
163
170
  to {
@@ -180,10 +187,12 @@
180
187
  .layerA {
181
188
  animation: sway 30s ease-in-out infinite alternate;
182
189
  transform-origin: 50% 50%;
190
+ will-change: transform;
183
191
  }
184
192
  .layerB {
185
193
  animation: sway 44s ease-in-out infinite alternate-reverse;
186
194
  transform-origin: 50% 50%;
195
+ will-change: transform;
187
196
  }
188
197
  @keyframes sway {
189
198
  from {
@@ -341,6 +350,7 @@
341
350
  opacity: 0.5;
342
351
  animation: spin 40s linear infinite;
343
352
  filter: drop-shadow(0 0 30px rgba(57, 230, 255, 0.3));
353
+ will-change: transform;
344
354
  }
345
355
  .hexring::before {
346
356
  content: "";
@@ -373,6 +383,7 @@
373
383
  transparent 70%
374
384
  );
375
385
  animation: breath 7s ease-in-out infinite;
386
+ will-change: transform, opacity;
376
387
  }
377
388
  @keyframes breath {
378
389
  0%, 100% {
@@ -423,6 +434,11 @@
423
434
  filter: drop-shadow(0 0 22px rgba(57, 230, 255, 0.35)) drop-shadow(
424
435
  0 8px 60px rgba(168, 120, 255, 0.25)
425
436
  );
437
+ /* gradient-clipped text flickers on iOS while its background animates
438
+ unless the element sits on its own composited layer */
439
+ -webkit-backface-visibility: hidden;
440
+ backface-visibility: hidden;
441
+ transform: translateZ(0);
426
442
  }
427
443
  @keyframes sheen {
428
444
  0%, 100% {
@@ -450,6 +466,7 @@
450
466
  color: transparent;
451
467
  -webkit-text-stroke: 1px rgba(57, 230, 255, 0.22);
452
468
  animation: echo 8s ease-in-out infinite;
469
+ will-change: transform, opacity;
453
470
  }
454
471
  @keyframes echo {
455
472
  0%, 100% {
@@ -594,6 +611,7 @@
594
611
  white-space: nowrap;
595
612
  width: max-content;
596
613
  animation: scroll 34s linear infinite;
614
+ will-change: transform;
597
615
  }
598
616
  .ticker span {
599
617
  padding: 0.72em 0;
@@ -652,6 +670,53 @@
652
670
  }
653
671
  }
654
672
 
673
+ /* ——————————————————— mobile / touch performance ——————————————————
674
+ Phones can't sustain the per-frame repaint work: SVG dash/scale
675
+ animations and background-position sheens repaint every frame, and
676
+ each pass runs through blur/drop-shadow filters. Strip the filters
677
+ and the repaint-driven animations; keep every compositor-friendly
678
+ transform/opacity animation so the page stays alive. */
679
+ @media (max-width: 820px), (pointer: coarse) {
680
+ .orb {
681
+ filter: blur(40px);
682
+ }
683
+ .aurora {
684
+ animation: none;
685
+ }
686
+ .grain {
687
+ display: none;
688
+ }
689
+ .wire,
690
+ .wire.v,
691
+ .wire.m,
692
+ .wire.g {
693
+ filter: none;
694
+ animation: none;
695
+ stroke-dasharray: none;
696
+ stroke-width: 1.2;
697
+ opacity: 0.75;
698
+ }
699
+ .layerA,
700
+ .layerB {
701
+ animation: none;
702
+ }
703
+ .ring {
704
+ animation-duration: 7s;
705
+ }
706
+ .hexring {
707
+ filter: none;
708
+ }
709
+ h1 {
710
+ animation: up 1s 0.2s cubic-bezier(0.2, 0.7, 0.2, 1) both;
711
+ background-position: 35% 50%;
712
+ filter: drop-shadow(0 0 22px rgba(57, 230, 255, 0.35));
713
+ }
714
+ .btn.primary {
715
+ animation: none;
716
+ background-position: 50% 50%;
717
+ }
718
+ }
719
+
655
720
  /* ————————————————————————— reduced motion ———————————————————————— */
656
721
  @media (prefers-reduced-motion: reduce) {
657
722
  *, *::before, *::after {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hviana/sema",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Sema: a non-parametric, instance-based reasoning system.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
package/src/alu/README.md CHANGED
@@ -15,7 +15,7 @@ search as authoritative axioms (at `STEP` cost, like a learned edge).
15
15
 
16
16
  It has no dependency on the rest of the codebase except the pure byte helpers in
17
17
  `../bytes.ts`, and is intended to be reused as a self-contained sublibrary in
18
- the spirit of `derive/` and `rabitq-hnsw/`.
18
+ the spirit of `derive/` and `rabitq-ivf/`.
19
19
 
20
20
  ## The thesis: one tiny kernel, everything else is a rewrite
21
21
 
@@ -1,7 +1,7 @@
1
1
  // alu — the ALU sub-library: a tiny irreducible kernel from which arithmetic,
2
2
  // logic, and numerical computation are all DERIVED.
3
3
  //
4
- // Self-contained in the spirit of derive/ and rabitq-hnsw/: it imports only the
4
+ // Self-contained in the spirit of derive/ and rabitq-ivf/: it imports only the
5
5
  // pure byte helpers (../../bytes.js) and nothing else from SEMA. The host
6
6
  // reaches meaning (operator synonymy, the symbolic inverse) through the injected
7
7
  // AluResonance, pre-resolved into a synchronous snapshot before the search runs.
package/src/config.ts CHANGED
@@ -39,18 +39,9 @@ export interface AlphabetConfig {
39
39
 
40
40
  export interface StoreConfig {
41
41
  minHaloMass: number;
42
- m: number;
43
- efConstruction: number;
44
- /** Construction budget for REACH-ONLY interior gists the ~90% of content
45
- * index inserts that exist so a partial query can resonate a sub-region
46
- * and climb, never as dedup targets. Deposit roots and halo-bearing
47
- * targets always build with the full `efConstruction`. A smaller budget
48
- * here is the one deliberate speed-for-quality trade in ingestion (an
49
- * interior's layer-0 wiring is built from a narrower candidate beam);
50
- * the recall suite (partial recall, multi-topic attention, counterfactual
51
- * anchoring) is the gate for its value. Set equal to `efConstruction`
52
- * to disable the trade. */
53
- efConstructionInterior: number;
42
+ /** Query breadth of the IVF vector indices: clusters probed per query =
43
+ * ceil(efSearch / 4). Inserts have no quality knob — the partitioned
44
+ * index routes and appends, so ingestion cost is flat by construction. */
54
45
  efSearch: number;
55
46
  /** Compact the in-memory vector indices after this many vectors are written.
56
47
  * Compaction rebuilds an index from its live codes to reclaim the slots left
@@ -58,7 +49,7 @@ export interface StoreConfig {
58
49
  * (not on a flush count that goes quiet during repeat-heavy training) keeps
59
50
  * the index dense and query cost bounded. */
60
51
  compactEveryNWrites: number;
61
- /** Over-fetch factor for HNSW queries (ANN recall cushion). */
52
+ /** Over-fetch factor for vector-index queries (ANN recall cushion). */
62
53
  overfetch: number;
63
54
  /** Combined buffered-write ceiling before a flush of both vector indices
64
55
  * (content + halo). Higher ⇒ fewer, larger flushes into the in-memory
@@ -91,14 +82,20 @@ export interface StoreConfig {
91
82
  * round-trips through the quantizer. An eviction or a reopen reads the
92
83
  * 2-bit row — the fidelity every cross-session consumer already gets. */
93
84
  haloCacheBytes: number;
94
- /** Size, in MiB, of each `rabitq-hnsw` `VectorDatabase`'s memory budget
95
- * (forwarded as its `cacheSizeMb`). It is the index's SINGLE memory knob: it
96
- * sizes both SQLite's page cache and the derived immutable-code LRU. A PURE
97
- * latency optimisation the index reads codes from SQLite on demand, so its
98
- * correctness and its asymptotic per-operation storage-read count are identical
99
- * with the budget at 0. Exposed so a scaling test can set it to 0 and measure
85
+ /** Size, in MiB, of each `rabitq-ivf` `VectorDatabase`'s memory budget
86
+ * (forwarded as its `cacheSizeMb` its SQLite page cache). A PURE latency
87
+ * optimisation the index reads chunk blobs from SQLite on demand, so its
88
+ * correctness and its per-operation storage-read count are identical with
89
+ * the budget at 0. Exposed so a scaling test can set it to 0 and measure
100
90
  * the honest, cache-independent cost. */
101
91
  vectorCacheMb: number;
92
+ /** Size, in MiB, of the MAIN DAG database's SQLite page cache. The node /
93
+ * kid / edge / contain tables serve millions of point probes per training
94
+ * session (content-addressed findLeaf/findBranch, parent probes, contain
95
+ * appends); SQLite's default cache (~2 MiB) thrashes once the DB outgrows
96
+ * it, so every probe pays a file read. A PURE latency knob — correctness
97
+ * and result identical at any value. */
98
+ sqliteCacheMb: number;
102
99
  /** Max entries in the skipped-interior LRU set. Interiors that
103
100
  * {@link Store.indexSubtree} has already visited (indexed or skipped) are
104
101
  * remembered here so subsequent calls prune their subtrees. Session-local
@@ -151,9 +148,6 @@ export const DEFAULT_CONFIG: MindConfig = {
151
148
  },
152
149
  store: {
153
150
  minHaloMass: 1,
154
- m: 8,
155
- efConstruction: 64,
156
- efConstructionInterior: 16,
157
151
  efSearch: 64,
158
152
  compactEveryNWrites: 50_000,
159
153
  overfetch: 4,
@@ -165,6 +159,7 @@ export const DEFAULT_CONFIG: MindConfig = {
165
159
  pendingGistBytes: 16_000_000,
166
160
  haloCacheBytes: 16_000_000,
167
161
  vectorCacheMb: 64,
162
+ sqliteCacheMb: 64,
168
163
  coveredIdsMax: 100_000,
169
164
  chainCacheBytes: 16_000_000,
170
165
  },
@@ -194,11 +189,6 @@ export function resolveConfig(opts: Partial<MindConfig> = {}): MindConfig {
194
189
  },
195
190
  store: {
196
191
  minHaloMass: opts.store?.minHaloMass ?? DEFAULT_CONFIG.store.minHaloMass,
197
- m: opts.store?.m ?? DEFAULT_CONFIG.store.m,
198
- efConstruction: opts.store?.efConstruction ??
199
- DEFAULT_CONFIG.store.efConstruction,
200
- efConstructionInterior: opts.store?.efConstructionInterior ??
201
- DEFAULT_CONFIG.store.efConstructionInterior,
202
192
  efSearch: opts.store?.efSearch ?? DEFAULT_CONFIG.store.efSearch,
203
193
  compactEveryNWrites: opts.store?.compactEveryNWrites ??
204
194
  DEFAULT_CONFIG.store.compactEveryNWrites,
@@ -218,6 +208,8 @@ export function resolveConfig(opts: Partial<MindConfig> = {}): MindConfig {
218
208
  DEFAULT_CONFIG.store.haloCacheBytes,
219
209
  vectorCacheMb: opts.store?.vectorCacheMb ??
220
210
  DEFAULT_CONFIG.store.vectorCacheMb,
211
+ sqliteCacheMb: opts.store?.sqliteCacheMb ??
212
+ DEFAULT_CONFIG.store.sqliteCacheMb,
221
213
  coveredIdsMax: opts.store?.coveredIdsMax ??
222
214
  DEFAULT_CONFIG.store.coveredIdsMax,
223
215
  chainCacheBytes: opts.store?.chainCacheBytes ??
package/src/geometry.ts CHANGED
@@ -314,17 +314,32 @@ export function knownPrefixLength(
314
314
  /** Bytes → Sema tree. `leafAt` and `lookup` are store capabilities for
315
315
  * detecting previously-stored prefixes so the river can split at the
316
316
  * correct boundary. Pass them through from `perceive`; the geometry
317
- * computes the stable prefix internally. */
317
+ * computes the stable prefix internally.
318
+ *
319
+ * `boundaries` is the CALLER-computed stable-prefix boundary set (§10.3):
320
+ * strictly-increasing proper byte offsets, each the length of a prefix that
321
+ * is already a stored whole-stream form. When given, the fold splits into
322
+ * the segments between consecutive boundaries — each folded independently,
323
+ * exactly as it folded when it was learned — and the segment roots join
324
+ * LEFT-NESTED (((s₀·s₁)·s₂)…), so every learnt cumulative-context root
325
+ * reappears as an identical subtree (and, by hash-consing, the very same
326
+ * node) inside the grown stream. This is what lets a conversation's next
327
+ * turn extend perception instead of refolding it: identical prefixes
328
+ * produce identical subtrees regardless of what follows them. */
318
329
  export function bytesToTree(
319
330
  space: Space,
320
331
  alphabet: Alphabet,
321
332
  bytes: Uint8Array,
322
333
  leafAt?: (i: number) => number | null,
323
334
  lookup?: (leafIds: number[]) => number | null,
335
+ boundaries?: readonly number[],
324
336
  ): Sema {
325
337
  if (bytes.length === 0) {
326
338
  return sema(alphabet.vecs[0], new Uint8Array(0), null);
327
339
  }
340
+ if (boundaries !== undefined && boundaries.length > 0) {
341
+ return stablePrefixFold(space, alphabet, bytes, boundaries);
342
+ }
328
343
  const sb = (leafAt && lookup) ? knownPrefixLength(bytes, leafAt, lookup) : 0;
329
344
  return riverFold(
330
345
  space,
@@ -333,6 +348,83 @@ export function bytesToTree(
333
348
  ).tree;
334
349
  }
335
350
 
351
+ /** The stable-prefix segmented fold (§10.3). Each segment between
352
+ * consecutive boundaries folds PLAINLY and independently; segment roots
353
+ * join left-nested, and only the final root is normalized (the linear-fold
354
+ * contract: one normalize per perception). A segment's own inner splits
355
+ * need no recursion here: a nested learnt prefix is itself an earlier
356
+ * boundary, so the left-nested join reproduces every intermediate learnt
357
+ * root ((s₀·s₁) IS the root the store learnt for the first two segments'
358
+ * bytes, and so on). */
359
+ function stablePrefixFold(
360
+ space: Space,
361
+ alphabet: Alphabet,
362
+ bytes: Uint8Array,
363
+ boundaries: readonly number[],
364
+ ): Sema {
365
+ const cuts: number[] = [];
366
+ let prev = 0;
367
+ for (const b of boundaries) {
368
+ if (b > prev && b < bytes.length) {
369
+ cuts.push(b);
370
+ prev = b;
371
+ }
372
+ }
373
+ if (cuts.length === 0) {
374
+ return riverFold(space, bytesToLeaves(alphabet, bytes), bytes.length).tree;
375
+ }
376
+ const edges = [0, ...cuts, bytes.length];
377
+ const segs: Folded[] = [];
378
+ for (let i = 0; i + 1 < edges.length; i++) {
379
+ const seg = bytes.subarray(edges[i], edges[i + 1]);
380
+ segs.push(riverFoldRaw(space, bytesToLeaves(alphabet, seg)));
381
+ }
382
+ let cur = segs[0];
383
+ for (let i = 1; i < segs.length; i++) cur = fold2(space, cur, segs[i]);
384
+ normalize(cur.tree.v);
385
+ return cur.tree;
386
+ }
387
+
388
+ /** Join two folded items as one 2-kid branch — the top-level join of the
389
+ * stable-prefix fold, identical FP ops to foldSlice's seat-bound
390
+ * accumulation over a group of two. Unnormalized (interior). */
391
+ function fold2(space: Space, a: Folded, b: Folded): Folded {
392
+ const D = space.D;
393
+ const gist = new Float32Array(D);
394
+ const kids = [a.tree, b.tree];
395
+ for (let k = 0; k < 2; k++) {
396
+ const seat = space.seats[k].fwd;
397
+ const v = kids[k].v;
398
+ for (let d = 0; d < D; d++) gist[d] += v[seat[d]];
399
+ }
400
+ return { tree: sema(gist, null, kids), len: a.len + b.len };
401
+ }
402
+
403
+ /** Plain river fold WITHOUT the final root normalize — the segment-level
404
+ * building block of {@link stablePrefixFold} (interiors must keep their
405
+ * byte-proportional magnitude; only the whole perception's root is ever
406
+ * normalized). */
407
+ function riverFoldRaw(space: Space, row: Folded[]): Folded {
408
+ if (row.length === 0) {
409
+ const z = new Float32Array(space.D);
410
+ return { tree: sema(z, new Uint8Array(0), null), len: 0 };
411
+ }
412
+ if (row.length === 1) return row[0];
413
+ let level = row;
414
+ while (level.length > 1) {
415
+ const next: Folded[] = [];
416
+ foldSlice(space, level, 0, level.length, next, false);
417
+ if (next.length === level.length) {
418
+ const forced: Folded[] = [];
419
+ foldSlice(space, next, 0, next.length, forced, true);
420
+ level = forced;
421
+ } else {
422
+ level = next;
423
+ }
424
+ }
425
+ return level[0];
426
+ }
427
+
336
428
  // ---- pyramid fold (incremental plain perception) ----
337
429
 
338
430
  /** The PLAIN fold's full level pyramid — every level's item list, bottom