@hviana/sema 0.1.0

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 (110) hide show
  1. package/AGENTS.md +470 -0
  2. package/AUTHORS.md +12 -0
  3. package/COMMERCIAL-LICENSE.md +19 -0
  4. package/CONTRIBUTING.md +15 -0
  5. package/HOW_IT_WORKS.md +4210 -0
  6. package/LICENSE.md +117 -0
  7. package/README.md +290 -0
  8. package/TRADEMARKS.md +19 -0
  9. package/example/demo.ts +46 -0
  10. package/example/train_base.ts +2675 -0
  11. package/index.html +893 -0
  12. package/package.json +25 -0
  13. package/src/alphabet.ts +34 -0
  14. package/src/alu/README.md +332 -0
  15. package/src/alu/src/alu.ts +541 -0
  16. package/src/alu/src/expr.ts +339 -0
  17. package/src/alu/src/index.ts +115 -0
  18. package/src/alu/src/kernel-arith.ts +377 -0
  19. package/src/alu/src/kernel-bits.ts +199 -0
  20. package/src/alu/src/kernel-logic.ts +102 -0
  21. package/src/alu/src/kernel-nd.ts +235 -0
  22. package/src/alu/src/kernel-numeric.ts +466 -0
  23. package/src/alu/src/operation.ts +344 -0
  24. package/src/alu/src/parser.ts +574 -0
  25. package/src/alu/src/resonance.ts +161 -0
  26. package/src/alu/src/text.ts +83 -0
  27. package/src/alu/src/value.ts +327 -0
  28. package/src/alu/test/alu.test.ts +1004 -0
  29. package/src/bytes.ts +62 -0
  30. package/src/config.ts +227 -0
  31. package/src/derive/README.md +295 -0
  32. package/src/derive/src/deduction.ts +263 -0
  33. package/src/derive/src/index.ts +24 -0
  34. package/src/derive/src/priority-queue.ts +70 -0
  35. package/src/derive/src/rewrite.ts +127 -0
  36. package/src/derive/src/trie.ts +242 -0
  37. package/src/derive/test/derive.test.ts +137 -0
  38. package/src/extension.ts +42 -0
  39. package/src/geometry.ts +511 -0
  40. package/src/index.ts +38 -0
  41. package/src/ingest-cache.ts +224 -0
  42. package/src/mind/articulation.ts +137 -0
  43. package/src/mind/attention.ts +1061 -0
  44. package/src/mind/canonical.ts +107 -0
  45. package/src/mind/graph-search.ts +1100 -0
  46. package/src/mind/index.ts +18 -0
  47. package/src/mind/junction.ts +347 -0
  48. package/src/mind/learning.ts +246 -0
  49. package/src/mind/match.ts +507 -0
  50. package/src/mind/mechanisms/alu.ts +33 -0
  51. package/src/mind/mechanisms/cast.ts +568 -0
  52. package/src/mind/mechanisms/confluence.ts +268 -0
  53. package/src/mind/mechanisms/cover.ts +248 -0
  54. package/src/mind/mechanisms/extraction.ts +422 -0
  55. package/src/mind/mechanisms/recall.ts +241 -0
  56. package/src/mind/mind.ts +483 -0
  57. package/src/mind/pipeline-mechanism.ts +326 -0
  58. package/src/mind/pipeline.ts +300 -0
  59. package/src/mind/primitives.ts +201 -0
  60. package/src/mind/rationale.ts +275 -0
  61. package/src/mind/reasoning.ts +198 -0
  62. package/src/mind/recognition.ts +234 -0
  63. package/src/mind/resonance.ts +0 -0
  64. package/src/mind/trace.ts +108 -0
  65. package/src/mind/traverse.ts +556 -0
  66. package/src/mind/types.ts +281 -0
  67. package/src/rabitq-hnsw/README.md +303 -0
  68. package/src/rabitq-hnsw/src/database.ts +492 -0
  69. package/src/rabitq-hnsw/src/heap.ts +90 -0
  70. package/src/rabitq-hnsw/src/hnsw.ts +514 -0
  71. package/src/rabitq-hnsw/src/index.ts +15 -0
  72. package/src/rabitq-hnsw/src/prng.ts +39 -0
  73. package/src/rabitq-hnsw/src/rabitq.ts +308 -0
  74. package/src/rabitq-hnsw/src/store.ts +994 -0
  75. package/src/rabitq-hnsw/test/hnsw.test.ts +1213 -0
  76. package/src/store-sqlite.ts +928 -0
  77. package/src/store.ts +2148 -0
  78. package/src/vec.ts +124 -0
  79. package/test/00-extract.test.mjs +151 -0
  80. package/test/01-floor.test.mjs +20 -0
  81. package/test/02-roundtrip.test.mjs +83 -0
  82. package/test/03-recall.test.mjs +98 -0
  83. package/test/04-think.test.mjs +627 -0
  84. package/test/05-concepts.test.mjs +84 -0
  85. package/test/08-storage.test.mjs +948 -0
  86. package/test/09-edges.test.mjs +65 -0
  87. package/test/11-universality.test.mjs +180 -0
  88. package/test/13-conversation.test.mjs +228 -0
  89. package/test/14-scaling.test.mjs +503 -0
  90. package/test/15-decomposition-gap.test.mjs +0 -0
  91. package/test/16-bridge.test.mjs +250 -0
  92. package/test/17-intelligence.test.mjs +240 -0
  93. package/test/18-alu.test.mjs +209 -0
  94. package/test/19-nd.test.mjs +254 -0
  95. package/test/20-stability.test.mjs +489 -0
  96. package/test/21-partial.test.mjs +158 -0
  97. package/test/22-multihop.test.mjs +130 -0
  98. package/test/23-rationale.test.mjs +316 -0
  99. package/test/24-generalization.test.mjs +416 -0
  100. package/test/25-embedding.test.mjs +75 -0
  101. package/test/26-cooperative.test.mjs +89 -0
  102. package/test/27-saturation-drop.test.mjs +637 -0
  103. package/test/28-unknowable.test.mjs +88 -0
  104. package/test/29-counterfactual.test.mjs +476 -0
  105. package/test/30-conflict-resolution.test.mjs +166 -0
  106. package/test/31-audit.test.mjs +288 -0
  107. package/test/32-confluence.test.mjs +173 -0
  108. package/test/33-multi-candidate.test.mjs +222 -0
  109. package/test/34-cross-region.test.mjs +252 -0
  110. package/tsconfig.json +16 -0
package/src/vec.ts ADDED
@@ -0,0 +1,124 @@
1
+ // vec.ts — vector primitives.
2
+ // Superposition (add), normalization (stay on the sphere),
3
+ // resonance (cosine), and seat binding (a keyring of fixed permutations).
4
+ // No weights. No gradients.
5
+
6
+ export type Vec = Float32Array;
7
+
8
+ /** Deterministic PRNG (32-bit mixer). */
9
+ export function rng(seed: number): () => number {
10
+ let s = seed >>> 0;
11
+ return () => {
12
+ s = (s + 0x6d2b79f5) >>> 0;
13
+ let t = s;
14
+ t = Math.imul(t ^ (t >>> 15), t | 1);
15
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
16
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
17
+ };
18
+ }
19
+
20
+ /** Standard normal sample (Box–Muller). */
21
+ function gaussian(rand: () => number): number {
22
+ let u = 0, v = 0;
23
+ while (u === 0) u = rand();
24
+ while (v === 0) v = rand();
25
+ return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
26
+ }
27
+
28
+ export const zeros = (D: number): Vec => new Float32Array(D);
29
+ export const copy = (v: Vec): Vec => new Float32Array(v);
30
+
31
+ /** Random point on the unit sphere. */
32
+ export function randomUnit(D: number, rand: () => number): Vec {
33
+ const v = zeros(D);
34
+ for (let i = 0; i < D; i++) v[i] = gaussian(rand);
35
+ return normalize(v);
36
+ }
37
+
38
+ /** target += src * scale (in place). */
39
+ export function addInto(target: Vec, src: Vec, scale = 1): Vec {
40
+ for (let i = 0; i < target.length; i++) target[i] += src[i] * scale;
41
+ return target;
42
+ }
43
+
44
+ export function dot(a: Vec, b: Vec): number {
45
+ let s = 0;
46
+ for (let i = 0; i < a.length; i++) s += a[i] * b[i];
47
+ return s;
48
+ }
49
+
50
+ const norm = (v: Vec): number => Math.sqrt(dot(v, v));
51
+
52
+ // Epsilon thresholds (settable once via setVecConfig).
53
+ let _normalizeEpsilon = 1e-12;
54
+ let _cosineEpsilon = 1e-12;
55
+
56
+ /** In-place normalization. */
57
+ export function normalize(v: Vec): Vec {
58
+ const n = norm(v);
59
+ if (n > _normalizeEpsilon) { for (let i = 0; i < v.length; i++) v[i] /= n; }
60
+ return v;
61
+ }
62
+
63
+ /** Resonance: 1 = same, 0 = unrelated. */
64
+ export function cosine(a: Vec, b: Vec): number {
65
+ const na = norm(a), nb = norm(b);
66
+ return na > _cosineEpsilon && nb > _cosineEpsilon ? dot(a, b) / (na * nb) : 0;
67
+ }
68
+
69
+ /** Set vector epsilon thresholds. Called once by Mind at construction. */
70
+ export function setVecConfig(cfg: {
71
+ normalizeEpsilon?: number;
72
+ cosineEpsilon?: number;
73
+ }): void {
74
+ if (cfg.normalizeEpsilon !== undefined) {
75
+ _normalizeEpsilon = cfg.normalizeEpsilon;
76
+ }
77
+ if (cfg.cosineEpsilon !== undefined) _cosineEpsilon = cfg.cosineEpsilon;
78
+ }
79
+
80
+ export interface Permutation {
81
+ fwd: Uint32Array;
82
+ inv: Uint32Array;
83
+ }
84
+
85
+ /** One fixed random permutation (Fisher–Yates). */
86
+ function makePermutation(D: number, rand: () => number): Permutation {
87
+ const fwd = new Uint32Array(D);
88
+ for (let i = 0; i < D; i++) fwd[i] = i;
89
+ for (let i = D - 1; i > 0; i--) {
90
+ const j = Math.floor(rand() * (i + 1));
91
+ const t = fwd[i];
92
+ fwd[i] = fwd[j];
93
+ fwd[j] = t;
94
+ }
95
+ const inv = new Uint32Array(D);
96
+ for (let i = 0; i < D; i++) inv[fwd[i]] = i;
97
+ return { fwd, inv };
98
+ }
99
+
100
+ /** The keyring: one independent permutation per seat.
101
+ * Independent keys do not commute, so an address in a tree is the
102
+ * path itself — "seat 2 inside seat 1" ≠ "seat 1 inside seat 2". */
103
+ export function makeKeyring(
104
+ D: number,
105
+ seats: number,
106
+ rand: () => number,
107
+ ): Permutation[] {
108
+ const ring: Permutation[] = [];
109
+ for (let s = 0; s < seats; s++) ring.push(makePermutation(D, rand));
110
+ return ring;
111
+ }
112
+
113
+ /** Apply permutation: out[i] = v[table[i]]. */
114
+ export function permute(v: Vec, table: Uint32Array): Vec {
115
+ const out = new Float32Array(v.length);
116
+ for (let i = 0; i < v.length; i++) out[i] = v[table[i]];
117
+ return out;
118
+ }
119
+
120
+ /** Permute into existing buffer — zero allocation. */
121
+ export function permuteInto(out: Vec, v: Vec, table: Uint32Array): Vec {
122
+ for (let i = 0; i < v.length; i++) out[i] = v[table[i]];
123
+ return out;
124
+ }
@@ -0,0 +1,151 @@
1
+ // 00-extract.test.mjs — extract a PIECE from prose by a TAUGHT skill.
2
+ //
3
+ // The requirement: ask about a part of a sentence and get just that part — a
4
+ // value the mind was NEVER told (it appears only in the question's own sentence).
5
+ //
6
+ // This is not blind carving of an isolated sentence (that is impossible — a never-
7
+ // seen value is, by construction, unrelated to anything learned, so nothing can
8
+ // locate it). It is a learned, transferable SKILL, the way a reasoner works:
9
+ //
10
+ // • You TEACH the skill with a handful of episodes whose answer is a span of
11
+ // the context — "The dog is named Rex." → "Rex", etc. The episodes share a
12
+ // shape; the answer is the part that varies.
13
+ // • You then ask an UNSEEN sentence of the same shape. The mind matches it to
14
+ // the skill (the consensus climb, `climbAttentionAll`, over the structural DAG), locates the invariant
15
+ // frame bytes bordering the answer in the exemplar, finds those same bytes
16
+ // in the query by exact match, and reads whatever sits between them out of
17
+ // the QUESTION itself — returning a value it was never told.
18
+ //
19
+ // The result is approximate by nature — a reasoner's relational read, not a byte
20
+ // rule — but consistent: the analogous span transfers across unseen values and
21
+ // across entirely different relations (named / capital / born). These assertions
22
+ // are literal equality on the extracted value.
23
+
24
+ import { test } from "node:test";
25
+ import assert from "node:assert/strict";
26
+ import { Mind } from "../dist/src/index.js";
27
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
28
+
29
+ const taught = async (episodes) => {
30
+ const mind = new Mind({
31
+ seed: 7,
32
+ });
33
+ await mind.ingest(episodes); // (sentence → answer-span) episodes: the SKILL
34
+ return mind;
35
+ };
36
+
37
+ const ask = async (mind, q) => (await mind.respondText(q)).trim();
38
+
39
+ test("learn to extract a NAME, then extract it from unseen sentences", async () => {
40
+ const mind = await taught([
41
+ ["The dog is named Rex.", "Rex"],
42
+ ["The cat is named Whiskers.", "Whiskers"],
43
+ ["The horse is named Comet.", "Comet"],
44
+ ["The parrot is named Echo.", "Echo"],
45
+ ["The fish is named Bubbles.", "Bubbles"],
46
+ ]);
47
+ // Unseen sentences; the names below were NEVER deposited.
48
+ assert.equal(await ask(mind, "The rabbit is named Clover."), "Clover");
49
+ assert.equal(await ask(mind, "The owl is named Sage."), "Sage");
50
+ assert.equal(await ask(mind, "The bear is named Honey."), "Honey");
51
+ await mind.store.close();
52
+ });
53
+
54
+ test("the SAME mechanism learns a different relation: the capital", async () => {
55
+ const mind = await taught([
56
+ ["The capital of France is Paris.", "Paris"],
57
+ ["The capital of Japan is Tokyo.", "Tokyo"],
58
+ ["The capital of Egypt is Cairo.", "Cairo"],
59
+ ["The capital of Peru is Lima.", "Lima"],
60
+ ]);
61
+ assert.equal(
62
+ await ask(mind, "The capital of Brazil is Brasilia."),
63
+ "Brasilia",
64
+ );
65
+ assert.equal(await ask(mind, "The capital of Italy is Rome."), "Rome");
66
+ await mind.store.close();
67
+ });
68
+
69
+ test("and another relation: where someone was born", async () => {
70
+ const mind = await taught([
71
+ ["Einstein was born in Germany.", "Germany"],
72
+ ["Curie was born in Poland.", "Poland"],
73
+ ["Newton was born in England.", "England"],
74
+ ["Tesla was born in Serbia.", "Serbia"],
75
+ ]);
76
+ assert.equal(await ask(mind, "Darwin was born in England."), "England");
77
+ assert.equal(await ask(mind, "Mozart was born in Austria."), "Austria");
78
+ await mind.store.close();
79
+ });
80
+
81
+ // Extraction and multi-hop are COMPOSABLE, not isolated branches: extracting a
82
+ // value and then reasoning from it is one pipeline. Here the extracted value
83
+ // ("Rome") has its own downstream fact, so the answer chains through it.
84
+ test("extraction COMPOSES with multi-hop reasoning", async () => {
85
+ const mind = await taught([
86
+ ["The capital of France is Paris.", "Paris"],
87
+ ["The capital of Japan is Tokyo.", "Tokyo"],
88
+ ["The capital of Egypt is Cairo.", "Cairo"],
89
+ ["The capital of Peru is Lima.", "Lima"],
90
+ ]);
91
+ // A fact keyed on a value the skill will extract.
92
+ await mind.ingest(["Rome", "Rome was the heart of a vast empire"]);
93
+ // "Italy" is an unseen subject → extraction yields "Rome" → reason hops to its
94
+ // fact. One pipeline: ground-by-skill, then walk the graph forward.
95
+ assert.equal(
96
+ await ask(mind, "The capital of Italy is Rome."),
97
+ "Rome was the heart of a vast empire",
98
+ );
99
+ await mind.store.close();
100
+ });
101
+
102
+ // A MULTI-WORD answer span, reached from EITHER seat. The discriminative slice of
103
+ // the query ("painted by") is itself part of the answer span, so the consensus
104
+ // climb can land on the CONTINUATION seat (an answer like "Leonardo da Vinci",
105
+ // which bears no forward edge) rather than the context. The skill must still
106
+ // apply — rise through the answer's `prev` edge to the context that taught it —
107
+ // and the multi-word value must come back WHOLE (not truncated to one word, and
108
+ // not collapsed to a neighbour's answer via fallback recall).
109
+ test("extracts a MULTI-WORD span, from whichever seat the climb lands on", async () => {
110
+ const mind = await taught([
111
+ ["The Mona Lisa was painted by Leonardo da Vinci.", "Leonardo da Vinci"],
112
+ ["The Starry Night was painted by Vincent van Gogh.", "Vincent van Gogh"],
113
+ [
114
+ "The Night Watch was painted by Rembrandt van Rijn.",
115
+ "Rembrandt van Rijn",
116
+ ],
117
+ ["The Scream was painted by Edvard Munch.", "Edvard Munch"],
118
+ ]);
119
+ assert.equal(
120
+ await ask(mind, "Guernica was painted by Pablo Picasso."),
121
+ "Pablo Picasso",
122
+ );
123
+ assert.equal(
124
+ await ask(mind, "The Persistence of Memory was painted by Salvador Dali."),
125
+ "Salvador Dali",
126
+ );
127
+ await mind.store.close();
128
+ });
129
+
130
+ // The multi-word extraction COMPOSES with the forward hop: lift a never-seen
131
+ // painter out of the sentence, then reason onward to a stored fact keyed on that
132
+ // name. The reply contains no word of the question — generalization and a
133
+ // reasoning hop in one query (the README's headline demo).
134
+ test("multi-word extraction COMPOSES with a forward reasoning hop", async () => {
135
+ const mind = await taught([
136
+ ["The Mona Lisa was painted by Leonardo da Vinci.", "Leonardo da Vinci"],
137
+ ["The Starry Night was painted by Vincent van Gogh.", "Vincent van Gogh"],
138
+ [
139
+ "The Night Watch was painted by Rembrandt van Rijn.",
140
+ "Rembrandt van Rijn",
141
+ ],
142
+ ]);
143
+ await mind.ingest([
144
+ ["Pablo Picasso", "Pablo Picasso co-founded the Cubist movement"],
145
+ ]);
146
+ assert.equal(
147
+ await ask(mind, "The Weeping Woman was painted by Pablo Picasso."),
148
+ "Pablo Picasso co-founded the Cubist movement",
149
+ );
150
+ await mind.store.close();
151
+ });
@@ -0,0 +1,20 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { cosine, Mind } from "../dist/src/index.js";
4
+
5
+ test("fractal floor: ordinal geometry of the byte line", async () => {
6
+ const m = new Mind({ seed: 7 });
7
+ const a = m.alphabet;
8
+ // same-mid neighbours resonate more than coarse-only, more than far
9
+ const sameMid = cosine(a.vecs[64], a.vecs[65]);
10
+ const sameCoarse = cosine(a.vecs[64], a.vecs[76]);
11
+ const far = Math.abs(cosine(a.vecs[64], a.vecs[200]));
12
+ assert.ok(
13
+ sameMid > sameCoarse + 0.1,
14
+ `mid ${sameMid} vs coarse ${sameCoarse}`,
15
+ );
16
+ assert.ok(sameCoarse > far, `coarse ${sameCoarse} vs far ${far}`);
17
+ });
18
+
19
+ // encodeChunk / decodeChunk were removed — 1-byte leaves have no chunk
20
+ // encoding. Each byte is its own leaf with an implicit negative id.
@@ -0,0 +1,83 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { hilbertBytes, Mind } from "../dist/src/index.js";
4
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
5
+
6
+ const txt = (r) => new TextDecoder().decode(r.bytes).replace(/\u0000+/g, "");
7
+
8
+ test("random byte streams roundtrip exactly", async () => {
9
+ const m = new Mind({
10
+ seed: 11,
11
+ });
12
+ let s = 42;
13
+ const rnd = () => ((s = (s * 1103515245 + 12345) & 0x7fffffff), s % 256);
14
+ for (let t = 0; t < 20; t++) {
15
+ const len = 1 + (t * 17) % 180;
16
+ const bytes = new Uint8Array(len);
17
+ for (let j = 0; j < len; j++) bytes[j] = rnd();
18
+ const tree = await m.ingest(bytes);
19
+ const out = await m.express(tree.v);
20
+ assert.deepEqual([...out], [...bytes], `trial ${t} len ${len}`);
21
+ }
22
+ });
23
+
24
+ test("text roundtrips: unicode, emoji, whitespace, the full byte table, runs", async () => {
25
+ const m = new Mind({
26
+ seed: 7,
27
+ });
28
+ const table = new Uint8Array(256);
29
+ for (let i = 0; i < 256; i++) table[i] = i;
30
+ const cases = [
31
+ "hello world",
32
+ "naïve café — émigré: ¿señor? Übermensch",
33
+ "数据是雨水,记忆是河流。",
34
+ "🌊🧠 memory is the model 🜁",
35
+ "line one\nline two\ttabbed\r\nwindows line",
36
+ "a".repeat(300),
37
+ "ab".repeat(150),
38
+ ];
39
+ for (const c of cases) {
40
+ const bytes = new TextEncoder().encode(c);
41
+ const t = await m.ingest(bytes);
42
+ const out = await m.express(t.v);
43
+ assert.deepEqual([...out], [...bytes], JSON.stringify(c.slice(0, 20)));
44
+ }
45
+ const t = await m.ingest(table);
46
+ const out = await m.express(t.v);
47
+ assert.deepEqual([...out], [...table], "byte table");
48
+ });
49
+
50
+ test("images roundtrip through one root vector", async () => {
51
+ const m = new Mind({
52
+ seed: 7,
53
+ });
54
+ const mk = (w, h, ch) => {
55
+ const g = {
56
+ width: w,
57
+ height: h,
58
+ channels: ch,
59
+ data: new Uint8Array(w * h * ch),
60
+ };
61
+ for (let i = 0; i < g.data.length; i++) g.data[i] = (i * 7 + 3) & 0xff;
62
+ return g;
63
+ };
64
+ for (const g of [mk(8, 8, 1), mk(16, 16, 1), mk(8, 8, 3)]) {
65
+ const want = hilbertBytes(g);
66
+ const t = await m.ingest(g);
67
+ const out = await m.express(t.v);
68
+ assert.deepEqual(
69
+ [...out],
70
+ [...want],
71
+ `${g.width}x${g.height}x${g.channels}`,
72
+ );
73
+ }
74
+ });
75
+
76
+ test("empty input is graceful silence", async () => {
77
+ const m = new Mind({
78
+ seed: 7,
79
+ });
80
+ const r = await m.respond("");
81
+ assert.equal(r.v, null);
82
+ assert.equal(r.bytes.length, 0);
83
+ });
@@ -0,0 +1,98 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { Mind } from "../dist/src/index.js";
4
+ import { SQliteStore } from "../dist/src/store-sqlite.js";
5
+
6
+ const txt = (r) => new TextDecoder().decode(r.bytes).replace(/\u0000+/g, "");
7
+
8
+ const FACTS = [
9
+ ["what is ice?", "ice is frozen water"],
10
+ ["what is fire?", "fire is hot plasma"],
11
+ ["who are you?", "a forest of resonant memories"],
12
+ ];
13
+
14
+ test("respond before any training is silence", async () => {
15
+ const m = new Mind({
16
+ seed: 7,
17
+ });
18
+ assert.equal((await m.respond("anything at all")).v, null);
19
+ });
20
+
21
+ test("duplicate training is idempotent", async () => {
22
+ const m = new Mind({
23
+ seed: 7,
24
+ });
25
+ await m.ingest([FACTS[0], FACTS[0], FACTS[0]]);
26
+ assert.equal(await m.respondText(FACTS[0][0]), FACTS[0][1]);
27
+ });
28
+
29
+ test("conflicting episodes answer one side, deterministically", async () => {
30
+ const run = async () => {
31
+ const m = new Mind({
32
+ seed: 7,
33
+ });
34
+ await m.ingest([["the sky", "blue"], ["the sky", "grey"]]);
35
+ return await m.respondText("the sky");
36
+ };
37
+ const a = await run();
38
+ const b = await run();
39
+ assert.ok(a === "blue" || a === "grey", `got "${a}"`);
40
+ assert.equal(a, b, "same seed, same answer");
41
+ });
42
+
43
+ test("same seed → identical behaviour and identical save blobs", async () => {
44
+ const mk = async () => {
45
+ const m = new Mind({
46
+ seed: 99,
47
+ });
48
+ await m.ingest(FACTS);
49
+ return m;
50
+ };
51
+ const m1 = await mk(), m2 = await mk();
52
+ assert.equal(
53
+ await m1.respondText(FACTS[1][0]),
54
+ await m2.respondText(FACTS[1][0]),
55
+ );
56
+ const b1 = await m1.save(), b2 = await m2.save();
57
+ assert.deepEqual([...new Uint8Array(b1)], [...new Uint8Array(b2)]);
58
+ });
59
+
60
+ test("known query returns an answer, unknown returns null", async () => {
61
+ const m = new Mind({
62
+ seed: 7,
63
+ });
64
+ await m.ingest(FACTS);
65
+ const known = await m.respond(FACTS[0][0]);
66
+ assert.ok(known.v !== null, "known query must return an answer");
67
+ assert.ok(known.bytes.length > 0, "known query must return bytes");
68
+ // Unknown query: may return null or a closest-approximation answer.
69
+ // Either is correct — the system always gives its best.
70
+ });
71
+
72
+ test("seat symmetry: an episode completes from either seat (whole-level)", async () => {
73
+ for (const seed of [1, 7, 42]) {
74
+ const m = new Mind({
75
+ seed,
76
+ });
77
+ await m.ingest([
78
+ ["what is ice?", "ice is frozen water"],
79
+ ["what is fire?", "fire is hot plasma"],
80
+ ]);
81
+ // reverse: a known CONTINUATION recalls its context
82
+ assert.equal(
83
+ await m.respondText("ice is frozen water"),
84
+ "what is ice?",
85
+ `seed ${seed}`,
86
+ );
87
+ // forward priority untouched
88
+ assert.equal(
89
+ await m.respondText("what is fire?"),
90
+ "fire is hot plasma",
91
+ `seed ${seed}`,
92
+ );
93
+ // a stranger continuation: may be null (no close match) or a
94
+ // deterministic approximation — either is correct behaviour.
95
+ const r = await m.respond("lava is molten rock");
96
+ assert.ok(r.v !== undefined, "respond returns a valid response");
97
+ }
98
+ });