@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
@@ -0,0 +1,1004 @@
1
+ // Self-contained tests for the `alu` library: the ALU kernel — logic from nand,
2
+ // the exact bit-vector arithmetic bootstrap, the polymorphic real arithmetic,
3
+ // the numerical limit layer, and the synchronous operator/operand scanner.
4
+ // Uses node:test; no dependency on sema (a stub resonance stands in for the
5
+ // host's halo space, so the polymorphic inverse is exercised with zero coupling).
6
+
7
+ import { test } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import {
10
+ addBits,
11
+ Alu,
12
+ type AluResonance,
13
+ asReal,
14
+ compareBits,
15
+ converge,
16
+ decimalCodec,
17
+ diff,
18
+ dot,
19
+ freeVariables,
20
+ int,
21
+ integrate,
22
+ interpolate,
23
+ isNd,
24
+ linsolve,
25
+ matMul,
26
+ mulBits,
27
+ nd,
28
+ negateBits,
29
+ NO_RESONANCE,
30
+ odeSolve,
31
+ type Operation,
32
+ OperationRegistry,
33
+ optimize,
34
+ parseValue,
35
+ polyEval,
36
+ powerEig,
37
+ prefetchRecognisedOps,
38
+ real,
39
+ registerArith,
40
+ registerBits,
41
+ registerLogic,
42
+ registerNd,
43
+ registerNumeric,
44
+ regress,
45
+ type ResonanceSync,
46
+ signBits,
47
+ solve,
48
+ symbol,
49
+ tagOf,
50
+ topSingular,
51
+ type Value,
52
+ } from "../src/index.js";
53
+
54
+ // ── helpers ─────────────────────────────────────────────────────────────────
55
+
56
+ const enc = (s: string) => new TextEncoder().encode(s);
57
+ const dec = (b: Uint8Array) => new TextDecoder().decode(b);
58
+ const close = (a: number, b: number, eps = 1e-6) =>
59
+ assert.ok(Math.abs(a - b) <= eps, `${a} !~ ${b} (eps ${eps})`);
60
+
61
+ /** Build an ALU Value from a JS literal — a TEST fixture, NOT a parser: the
62
+ * kernel no longer reads list structure from bytes (that is the host's
63
+ * meaning-level job — see src/mind/mind.ts recogniseValue), so the kernel's own tests
64
+ * construct Values directly. An array → nd, an integer → int, a fractional
65
+ * number → real, a string → an opaque symbol (its bytes verbatim). */
66
+ const V = (x: number | string | unknown[]): Value =>
67
+ Array.isArray(x)
68
+ ? nd(x.map(V as (e: unknown) => Value))
69
+ : typeof x === "number"
70
+ ? (Number.isInteger(x) ? int(BigInt(x)) : real(x))
71
+ : symbol(enc(x as string));
72
+
73
+ /** A logic-only registry + context — the substrate the bit bootstrap runs on. */
74
+ function logicCtx() {
75
+ const r = new OperationRegistry();
76
+ registerLogic(r);
77
+ return r.context(NO_RESONANCE, { tol: 1e-10, maxIter: 1000 });
78
+ }
79
+
80
+ /** A full registry context (all kernels). */
81
+ function fullCtx(resonance: ResonanceSync = NO_RESONANCE) {
82
+ const r = new OperationRegistry();
83
+ registerLogic(r);
84
+ registerBits(r);
85
+ registerArith(r);
86
+ registerNumeric(r);
87
+ return { r, ctx: r.context(resonance, { tol: 1e-12, maxIter: 2000 }) };
88
+ }
89
+
90
+ const B = (v: Value) => (v.domain === "bit" ? v.b : NaN);
91
+ const N = (v: Value) => (v.domain === "int" ? v.n : NaN);
92
+
93
+ // ─────────────────────────────────────────────────────────────────────────────
94
+ // 1 — Logic: the completeness layer. nand is the one axiom; every gate derives.
95
+ // ─────────────────────────────────────────────────────────────────────────────
96
+
97
+ test("nand truth table is the irreducible axiom", () => {
98
+ const ctx = logicCtx();
99
+ const t = (a: 0 | 1, b: 0 | 1) =>
100
+ B(ctx.apply("nand", [int(BigInt(a)), int(BigInt(b))]));
101
+ assert.equal(t(0, 0), 1);
102
+ assert.equal(t(0, 1), 1);
103
+ assert.equal(t(1, 0), 1);
104
+ assert.equal(t(1, 1), 0);
105
+ });
106
+
107
+ test("not/and/or derive from nand and match their truth tables", () => {
108
+ const ctx = logicCtx();
109
+ const bit01 = (n: 0 | 1) => int(BigInt(n));
110
+ assert.equal(B(ctx.apply("not", [bit01(0)])), 1);
111
+ assert.equal(B(ctx.apply("not", [bit01(1)])), 0);
112
+ for (
113
+ const [a, b, and, or] of [
114
+ [0, 0, 0, 0],
115
+ [0, 1, 0, 1],
116
+ [1, 0, 0, 1],
117
+ [1, 1, 1, 1],
118
+ ] as const
119
+ ) {
120
+ assert.equal(B(ctx.apply("and", [bit01(a), bit01(b)])), and);
121
+ assert.equal(B(ctx.apply("or", [bit01(a), bit01(b)])), or);
122
+ }
123
+ });
124
+
125
+ test("nor/xor/xnor/implies/iff all derive from nand", () => {
126
+ const ctx = logicCtx();
127
+ const bit01 = (n: 0 | 1) => int(BigInt(n));
128
+ const truth = (name: string) =>
129
+ ([0, 1] as const).flatMap((a) =>
130
+ ([0, 1] as const).map((b) => B(ctx.apply(name, [bit01(a), bit01(b)])))
131
+ );
132
+ assert.deepEqual(truth("nor"), [1, 0, 0, 0]);
133
+ assert.deepEqual(truth("xor"), [0, 1, 1, 0]);
134
+ assert.deepEqual(truth("xnor"), [1, 0, 0, 1]);
135
+ assert.deepEqual(truth("implies"), [1, 1, 0, 1]);
136
+ assert.deepEqual(truth("iff"), [1, 0, 0, 1]);
137
+ });
138
+
139
+ test("mux(s,a,b) selects a when s=0, b when s=1 — the bridge to control flow", () => {
140
+ const ctx = logicCtx();
141
+ const bit01 = (n: 0 | 1) => int(BigInt(n));
142
+ // s=0 → a
143
+ assert.equal(B(ctx.apply("mux", [bit01(0), bit01(0), bit01(1)])), 0);
144
+ assert.equal(B(ctx.apply("mux", [bit01(0), bit01(1), bit01(0)])), 1);
145
+ // s=1 → b
146
+ assert.equal(B(ctx.apply("mux", [bit01(1), bit01(0), bit01(1)])), 1);
147
+ assert.equal(B(ctx.apply("mux", [bit01(1), bit01(1), bit01(0)])), 0);
148
+ });
149
+
150
+ // ─────────────────────────────────────────────────────────────────────────────
151
+ // 2 — Arithmetic: the bit-vector bootstrap is EXACT (the nand→everything proof).
152
+ // ─────────────────────────────────────────────────────────────────────────────
153
+
154
+ test("full_adder built from xor/and/or matches its truth table", () => {
155
+ const { r, ctx } = fullCtx();
156
+ assert.ok(r.get("bits.fullAdder"));
157
+ // sum in bit 0, carry in bit 1.
158
+ const fa = (a: 0 | 1, b: 0 | 1, c: 0 | 1) =>
159
+ N(ctx.apply("bits.fullAdder", [
160
+ int(BigInt(a)),
161
+ int(BigInt(b)),
162
+ int(BigInt(c)),
163
+ ]));
164
+ assert.equal(fa(0, 0, 0), 0n); // sum 0 carry 0
165
+ assert.equal(fa(1, 0, 0), 1n); // sum 1 carry 0
166
+ assert.equal(fa(1, 1, 0), 2n); // sum 0 carry 1
167
+ assert.equal(fa(1, 1, 1), 3n); // sum 1 carry 1
168
+ });
169
+
170
+ test("ripple add / two's-complement negate / shift-add multiply equal native bigint", () => {
171
+ const ctx = logicCtx();
172
+ const cases: Array<[bigint, bigint]> = [
173
+ [0n, 0n],
174
+ [5n, 7n],
175
+ [12n, 30n],
176
+ [-3n, 8n],
177
+ [-15n, -9n],
178
+ [123n, 456n],
179
+ [1000n, -1n],
180
+ ];
181
+ for (const [a, b] of cases) {
182
+ assert.equal(addBits(ctx, a, b), a + b, `add ${a}+${b}`);
183
+ assert.equal(negateBits(ctx, a), -a, `negate ${a}`);
184
+ assert.equal(mulBits(ctx, a, b), a * b, `mul ${a}*${b}`);
185
+ assert.equal(
186
+ signBits(ctx, a),
187
+ a > 0n ? 1n : a < 0n ? -1n : 0n,
188
+ `sign ${a}`,
189
+ );
190
+ assert.equal(
191
+ compareBits(ctx, a, b),
192
+ a > b ? 1n : a < b ? -1n : 0n,
193
+ `compare ${a}?${b}`,
194
+ );
195
+ }
196
+ });
197
+
198
+ test("the bit bootstrap stays exact past 2^53", () => {
199
+ const ctx = logicCtx();
200
+ const a = 9007199254740993n; // 2^53 + 1, not representable as a double
201
+ const b = 1000000007n;
202
+ assert.equal(mulBits(ctx, a, b), a * b);
203
+ });
204
+
205
+ // ─────────────────────────────────────────────────────────────────────────────
206
+ // 3 — Arithmetic: polymorphic primitives + the derived field and order.
207
+ // ─────────────────────────────────────────────────────────────────────────────
208
+
209
+ test("add/multiply are exact on ints and lift to reals", () => {
210
+ const { ctx } = fullCtx();
211
+ assert.equal(N(ctx.apply("add", [int(2n), int(2n)])), 4n);
212
+ assert.equal(N(ctx.apply("multiply", [int(6n), int(7n)])), 42n);
213
+ // A real operand lifts the whole expression.
214
+ const r = ctx.apply("add", [int(2n), real(0.5)]);
215
+ assert.equal(r.domain, "real");
216
+ close(asReal(r), 2.5);
217
+ });
218
+
219
+ test("subtract = add∘negate, divide = multiply∘reciprocal", () => {
220
+ const { ctx } = fullCtx();
221
+ assert.equal(N(ctx.apply("subtract", [int(10n), int(3n)])), 7n);
222
+ close(asReal(ctx.apply("divide", [int(7n), int(2n)])), 3.5);
223
+ });
224
+
225
+ test("every comparison = sign∘subtract", () => {
226
+ const { ctx } = fullCtx();
227
+ const b = (name: string, x: number, y: number) =>
228
+ ctx.apply(name, [real(x), real(y)]);
229
+ assert.equal(B(b("lt", 2, 3)), 1);
230
+ assert.equal(B(b("lt", 3, 3)), 0);
231
+ assert.equal(B(b("le", 3, 3)), 1);
232
+ assert.equal(B(b("gt", 5, 3)), 1);
233
+ assert.equal(B(b("ge", 2, 3)), 0);
234
+ assert.equal(B(b("eq", 4, 4)), 1);
235
+ assert.equal(B(b("ne", 4, 5)), 1);
236
+ });
237
+
238
+ test("abs/min/max/power/gcd derive correctly", () => {
239
+ const { ctx } = fullCtx();
240
+ assert.equal(N(ctx.apply("abs", [int(-9n)])), 9n);
241
+ assert.equal(N(ctx.apply("min", [int(3n), int(8n)])), 3n);
242
+ assert.equal(N(ctx.apply("max", [int(3n), int(8n)])), 8n);
243
+ assert.equal(N(ctx.apply("power", [int(2n), int(10n)])), 1024n);
244
+ close(asReal(ctx.apply("power", [real(2), int(-2n)])), 0.25);
245
+ assert.equal(N(ctx.apply("gcd", [int(48n), int(36n)])), 12n);
246
+ });
247
+
248
+ test("real power via exp∘log agrees with Math.pow", () => {
249
+ const { ctx } = fullCtx();
250
+ close(asReal(ctx.apply("power", [real(2), real(0.5)])), Math.SQRT2, 1e-6);
251
+ });
252
+
253
+ // ─────────────────────────────────────────────────────────────────────────────
254
+ // 4 — Arithmetic: polynomial / vector / matrix / linsolve (structured arith).
255
+ // ─────────────────────────────────────────────────────────────────────────────
256
+
257
+ test("polyEval (Horner), dot, matMul", () => {
258
+ // 1 + 2x + 3x^2 at x=2 → 1 + 4 + 12 = 17
259
+ close(polyEval([1, 2, 3], 2), 17);
260
+ close(dot([1, 2, 3], [4, 5, 6]), 32);
261
+ assert.deepEqual(matMul([[1, 2], [3, 4]], [[5, 6], [7, 8]]), [
262
+ [19, 22],
263
+ [43, 50],
264
+ ]);
265
+ });
266
+
267
+ test("linsolve solves a 3x3 system (Gaussian elimination)", () => {
268
+ // x + y + z = 6 ; 2y + 5z = -4 ; 2x + 5y - z = 27 → (5, 3, -2)
269
+ const x = linsolve(
270
+ [[1, 1, 1], [0, 2, 5], [2, 5, -1]],
271
+ [6, -4, 27],
272
+ );
273
+ assert.ok(x);
274
+ close(x![0], 5);
275
+ close(x![1], 3);
276
+ close(x![2], -2);
277
+ });
278
+
279
+ test("linsolve returns null for a singular matrix", () => {
280
+ assert.equal(linsolve([[1, 2], [2, 4]], [3, 6]), null);
281
+ });
282
+
283
+ // ─────────────────────────────────────────────────────────────────────────────
284
+ // 5 — Numerical: the limit layer. Each op is a converge instance.
285
+ // ─────────────────────────────────────────────────────────────────────────────
286
+
287
+ test("converge reaches a contraction's fixed point", () => {
288
+ // x ← (x + 2/x)/2 converges to √2.
289
+ const fp = converge((x) => (x + 2 / x) / 2, 1, 1e-12, 1000);
290
+ close(fp, Math.SQRT2, 1e-9);
291
+ });
292
+
293
+ test("diff ≈ analytic derivative", () => {
294
+ close(diff((x) => x * x, 3, 1e-10, 200), 6, 1e-4);
295
+ close(diff(Math.sin, 0, 1e-10, 200), 1, 1e-4);
296
+ });
297
+
298
+ test("integrate ≈ closed form", () => {
299
+ close(integrate((x) => x, 0, 1, 1e-10, 50), 0.5, 1e-6);
300
+ close(integrate((x) => x * x, 0, 3, 1e-10, 50), 9, 1e-5);
301
+ });
302
+
303
+ test("solve (Newton) finds a root", () => {
304
+ close(solve((x) => x * x - 2, 1, 1e-12, 200), Math.SQRT2, 1e-8);
305
+ });
306
+
307
+ test("exp/log/sin/cos/sqrt converge to the right limits", () => {
308
+ const { ctx } = fullCtx();
309
+ const f = (name: string, x: number) => asReal(ctx.apply(name, [real(x)]));
310
+ close(f("exp", 0), 1, 1e-9);
311
+ close(f("exp", 1), Math.E, 1e-7);
312
+ close(f("log", Math.E), 1, 1e-7);
313
+ close(f("sin", 0), 0, 1e-9);
314
+ close(f("sin", Math.PI / 2), 1, 1e-7);
315
+ close(f("cos", 0), 1, 1e-9);
316
+ close(f("sqrt", 2), Math.SQRT2, 1e-8);
317
+ close(f("sqrt", 144), 12, 1e-8);
318
+ });
319
+
320
+ test("optimize finds a minimum; odeSolve integrates y'=y", () => {
321
+ close(optimize((x) => (x - 3) * (x - 3), 0, 1e-12, 500), 3, 1e-4);
322
+ close(odeSolve((_t, y) => y, 0, 1, 1, 2000), Math.E, 1e-4);
323
+ });
324
+
325
+ test("regress recovers a known line; interpolate is linear", () => {
326
+ // y = 2x + 1 sampled exactly → coeffs [1, 2].
327
+ const xs = [0, 1, 2, 3];
328
+ const ys = xs.map((x) => 2 * x + 1);
329
+ const c = regress(xs, ys, 1);
330
+ assert.ok(c);
331
+ close(c![0], 1, 1e-6);
332
+ close(c![1], 2, 1e-6);
333
+ close(interpolate([0, 10], [0, 100], 5), 50);
334
+ });
335
+
336
+ test("powerEig / topSingular by power iteration", () => {
337
+ // Diagonal(5, 2): dominant eigenvalue 5.
338
+ const { value } = powerEig([[5, 0], [0, 2]], 1e-12, 1000);
339
+ close(value, 5, 1e-6);
340
+ // Largest singular value of diag(3,4) is 4.
341
+ close(topSingular([[3, 0], [0, 4]], 1e-12, 1000), 4, 1e-6);
342
+ });
343
+
344
+ // ─────────────────────────────────────────────────────────────────────────────
345
+ // 6 — The Operation model: derived is indistinguishable; one-line extension.
346
+ // ─────────────────────────────────────────────────────────────────────────────
347
+
348
+ test("a derived op has the same record shape as a primitive", () => {
349
+ const { r } = fullCtx();
350
+ const nand = r.get("nand") as Operation;
351
+ const sub = r.get("subtract") as Operation;
352
+ assert.equal(nand.primitive, true);
353
+ assert.equal(sub.primitive, false);
354
+ // Same fields; a caller cannot tell them apart structurally.
355
+ for (const k of ["name", "arity", "primitive", "forms", "fn"] as const) {
356
+ assert.ok(k in nand && k in sub);
357
+ }
358
+ });
359
+
360
+ test("a brand-new derived op is registered in one call and computes", () => {
361
+ const { r, ctx } = fullCtx();
362
+ r.derive("hypot", 2, ["hypot"], (args, c) =>
363
+ c.apply("sqrt", [
364
+ c.apply("add", [
365
+ c.apply("multiply", [args[0], args[0]]),
366
+ c.apply("multiply", [args[1], args[1]]),
367
+ ]),
368
+ ]));
369
+ close(asReal(ctx.apply("hypot", [real(3), real(4)])), 5, 1e-7);
370
+ });
371
+
372
+ // ─────────────────────────────────────────────────────────────────────────────
373
+ // 7 — Values: parse, the codec round-trip, and the polymorphic inverse.
374
+ // ─────────────────────────────────────────────────────────────────────────────
375
+
376
+ test("parseValue: ints, reals, and symbols", () => {
377
+ assert.deepEqual(parseValue(enc("42")), int(42n));
378
+ assert.deepEqual(parseValue(enc("-7")), int(-7n));
379
+ assert.deepEqual(parseValue(enc("3.5")), real(3.5));
380
+ assert.deepEqual(parseValue(enc("1e3")), real(1000));
381
+ // A token that merely begins with a digit stays a symbol.
382
+ assert.equal(parseValue(enc("3dogs")).domain, "symbol");
383
+ assert.equal(parseValue(enc("large")).domain, "symbol");
384
+ });
385
+
386
+ test("decimalCodec round-trips and formats reals deterministically", () => {
387
+ const codec = decimalCodec(6);
388
+ assert.equal(dec(codec.encode(int(42n))), "42");
389
+ assert.equal(dec(codec.encode(real(0.5))), "0.5");
390
+ assert.equal(dec(codec.encode(real(2))), "2"); // trailing zeros trimmed
391
+ assert.equal(dec(codec.encode(real(-0))), "0"); // -0 normalised
392
+ // round-trip
393
+ const v = codec.decode(enc("8"));
394
+ assert.deepEqual(v, int(8n));
395
+ });
396
+
397
+ test("inverse is polymorphic: number negates, symbol resonates to its opposite", () => {
398
+ // Stub resonance: "large" ↔ "small", modality-agnostic over bytes.
399
+ const opposites = new Map<string, string>([["large", "small"]]);
400
+ const stub: ResonanceSync = {
401
+ opposite: (b) => {
402
+ const o = opposites.get(dec(b));
403
+ return o ? enc(o) : null;
404
+ },
405
+ recogniseOp: () => null,
406
+ };
407
+ const { ctx } = fullCtx(stub);
408
+ // numeric inverse = negate
409
+ assert.equal(N(ctx.apply("inverse", [int(3n)])), -3n);
410
+ // symbol inverse = resonant opposite
411
+ const r = ctx.apply("inverse", [symbol(enc("large"))]);
412
+ assert.equal(r.domain, "symbol");
413
+ assert.equal(
414
+ dec((r as { domain: "symbol"; bytes: Uint8Array }).bytes),
415
+ "small",
416
+ );
417
+ // a symbol with no known opposite is left unchanged (never fabricated)
418
+ const u = ctx.apply("inverse", [symbol(enc("zorp"))]);
419
+ assert.equal(
420
+ dec((u as { domain: "symbol"; bytes: Uint8Array }).bytes),
421
+ "zorp",
422
+ );
423
+ });
424
+
425
+ // ─────────────────────────────────────────────────────────────────────────────
426
+ // 8 — The Alu facade: the synchronous scanner and applyBytes.
427
+ // ─────────────────────────────────────────────────────────────────────────────
428
+
429
+ test("scan finds numeric operands and symbolic operators in raw bytes", () => {
430
+ const u = new Alu();
431
+ const { operands, operators } = u.scan(enc("12+30"));
432
+ assert.deepEqual(operands.map((o) => [o.i, o.j]), [[0, 2], [3, 5]]);
433
+ assert.equal(operands[0].value.domain, "int");
434
+ assert.equal(operators.length, 1);
435
+ assert.equal(operators[0].name, "add");
436
+ assert.deepEqual([operators[0].i, operators[0].j], [2, 3]);
437
+ });
438
+
439
+ test("scan keeps a multi-digit / decimal number whole, ignores a bare trailing dot", () => {
440
+ const u = new Alu();
441
+ const a = u.scan(enc("3.14"));
442
+ assert.equal(a.operands.length, 1);
443
+ assert.equal(a.operands[0].value.domain, "real");
444
+ // a trailing "." is not part of the numeral
445
+ const b = u.scan(enc("2+2."));
446
+ assert.deepEqual(b.operands.map((o) => o.j - o.i), [1, 1]);
447
+ });
448
+
449
+ test("scan resolves the longest symbolic operator form", () => {
450
+ const u = new Alu();
451
+ const { operators } = u.scan(enc("3<=4"));
452
+ assert.equal(operators.length, 1);
453
+ assert.equal(operators[0].name, "le"); // "<=" beats "<"
454
+ });
455
+
456
+ test("applyBytes computes the canonical result bytes", () => {
457
+ const u = new Alu();
458
+ assert.equal(dec(u.applyBytes("add", [enc("2"), enc("2")])!), "4");
459
+ assert.equal(dec(u.applyBytes("multiply", [enc("6"), enc("7")])!), "42");
460
+ assert.equal(dec(u.applyBytes("subtract", [enc("10"), enc("3")])!), "7");
461
+ assert.equal(dec(u.applyBytes("divide", [enc("7"), enc("2")])!), "3.5");
462
+ // an unknown op or an unparseable operand → null (the rule simply won't fire)
463
+ assert.equal(u.applyBytes("nope", [enc("1"), enc("2")]), null);
464
+ });
465
+
466
+ test("a computed result is itself an operand (composition for free)", () => {
467
+ const u = new Alu();
468
+ const first = u.applyBytes("add", [enc("2"), enc("3")])!; // "5"
469
+ const second = u.applyBytes("multiply", [first, enc("4")])!; // "20"
470
+ assert.equal(dec(second), "20");
471
+ });
472
+
473
+ // ─────────────────────────────────────────────────────────────────────────────
474
+ // 9 — Expressions: a numerical op acts on a FUNCTION, evaluated by a recursive
475
+ // application of the same ALU (the "recursive call" case).
476
+ // ─────────────────────────────────────────────────────────────────────────────
477
+
478
+ test("evalExpression evaluates through the kernel, auto-detecting the variable", () => {
479
+ const u = new Alu();
480
+ // x^2 + 1 at x=3 → 10; variable auto-detected.
481
+ close(u.evalExpression(enc("x^2 + 1"), "", 3)!, 10);
482
+ // a different one-letter variable, any script, still auto-detected
483
+ close(u.evalExpression(enc("t*t"), "", 4)!, 16);
484
+ // a named function resolves against the kernel
485
+ close(u.evalExpression(enc("sin(x)"), "x", Math.PI / 2)!, 1, 1e-6);
486
+ // constants
487
+ close(u.evalExpression(enc("2*pi"), "", 0)!, 2 * Math.PI);
488
+ // a malformed expression declines
489
+ assert.equal(u.evalExpression(enc("x +"), "x", 1), null);
490
+ });
491
+
492
+ test("freeVariables lists candidates, excluding function names", () => {
493
+ const u = new Alu();
494
+ const isFn = (n: string) => u.arityOf(n) === 1 && u.has(n);
495
+ assert.deepEqual(freeVariables("sin(x) + x", isFn), ["x"]);
496
+ assert.deepEqual(freeVariables("a*b + c", isFn).sort(), ["a", "b", "c"]);
497
+ });
498
+
499
+ test("diff: the derivative op acts on an expression operand", () => {
500
+ const u = new Alu();
501
+ // d/dx (x^2) at 3 = 6. Operand 0 is the expression symbol, operand 1 the point.
502
+ const r = u.apply("diff", [symbol(enc("x^2")), real(3)]);
503
+ assert.ok(r);
504
+ close(asReal(r!), 6, 1e-4);
505
+ });
506
+
507
+ test("integrate: definite integral over an expression", () => {
508
+ const u = new Alu();
509
+ // ∫₀¹ x dx = 0.5 ; ∫₀³ x^2 dx = 9
510
+ close(
511
+ asReal(u.apply("integrate", [symbol(enc("x")), real(0), real(1)])!),
512
+ 0.5,
513
+ 1e-5,
514
+ );
515
+ close(
516
+ asReal(u.apply("integrate", [symbol(enc("x^2")), real(0), real(3)])!),
517
+ 9,
518
+ 1e-4,
519
+ );
520
+ });
521
+
522
+ test("solve: a root of an expression near a guess", () => {
523
+ const u = new Alu();
524
+ // root of x^2 - 2 near 1 → √2
525
+ close(
526
+ asReal(u.apply("solve", [symbol(enc("x^2 - 2")), real(1)])!),
527
+ Math.SQRT2,
528
+ 1e-6,
529
+ );
530
+ });
531
+
532
+ test("limit: the value an expression approaches (a converge instance)", () => {
533
+ const u = new Alu();
534
+ // lim_{x→0} sin(x)/x = 1 — the removable singularity is skirted by sampling.
535
+ close(asReal(u.apply("limit", [symbol(enc("sin(x)/x")), real(0)])!), 1, 1e-4);
536
+ });
537
+
538
+ test("optimize: a minimiser of an expression", () => {
539
+ const u = new Alu();
540
+ // min of (x-3)^2 is at x=3
541
+ close(
542
+ asReal(u.apply("optimize", [symbol(enc("(x-3)^2")), real(0)])!),
543
+ 3,
544
+ 1e-3,
545
+ );
546
+ });
547
+
548
+ test("an expression op declines (null) when no evaluator can read the operand", () => {
549
+ const u = new Alu();
550
+ // a non-expression symbol → the evaluator fails → apply returns null
551
+ assert.equal(u.apply("diff", [symbol(enc("@@@")), real(1)]), null);
552
+ });
553
+
554
+ // ─────────────────────────────────────────────────────────────────────────────
555
+ // 10 — The operation CONCEPT vocabulary (the generic, resonant recognition seed).
556
+ // ─────────────────────────────────────────────────────────────────────────────
557
+
558
+ test("conceptAnchors exposes the operation vocabulary for resonant recognition", () => {
559
+ const u = new Alu();
560
+ const dec = new TextDecoder();
561
+ const byName = new Map<string, string[]>();
562
+ for (const { name, form } of u.conceptAnchors()) {
563
+ const a = byName.get(name) ?? [];
564
+ a.push(dec.decode(form));
565
+ byName.set(name, a);
566
+ }
567
+ // The numerical ops a query would NOT spell with a symbol still have named
568
+ // concepts a host can resonate against — generic over operations, not a fixed
569
+ // symbol table.
570
+ assert.ok(byName.get("integrate")!.includes("integral"));
571
+ assert.ok(byName.get("diff")!.includes("derivative"));
572
+ assert.ok(byName.get("limit")!.includes("limit"));
573
+ assert.ok(byName.get("add")!.includes("plus"));
574
+ // Forms the literal scanner already reads in full are NOT anchors: pure
575
+ // operator symbols and numerals stay on the literal path.
576
+ for (const forms of byName.values()) {
577
+ assert.ok(!forms.includes("<="));
578
+ assert.ok(!forms.includes("0"));
579
+ }
580
+ });
581
+
582
+ test("prefetchRecognisedOps bridges async recognition to a sync map", async () => {
583
+ // A stub host resonance: "the rate of change of" means a derivative.
584
+ const stub: AluResonance = {
585
+ recogniseOp: async (b) => dec(b).includes("rate of change") ? "diff" : null,
586
+ opposite: async () => null,
587
+ };
588
+ const map = await prefetchRecognisedOps(stub, [
589
+ enc("the rate of change of"),
590
+ enc("nonsense"),
591
+ ]);
592
+ assert.equal(map.get("the rate of change of"), "diff");
593
+ assert.equal(map.has("nonsense"), false);
594
+ });
595
+
596
+ // ─────────────────────────────────────────────────────────────────────────────
597
+ // 11 — N-dimensional values: the recursive container. Representation + codec.
598
+ // ─────────────────────────────────────────────────────────────────────────────
599
+
600
+ /** A full registry with the nd kernel too — the substrate for the nd tests. */
601
+ function ndCtx(resonance: ResonanceSync = NO_RESONANCE) {
602
+ const r = new OperationRegistry();
603
+ registerLogic(r);
604
+ registerBits(r);
605
+ registerArith(r);
606
+ registerNumeric(r);
607
+ registerNd(r);
608
+ return { r, ctx: r.context(resonance, { tol: 1e-12, maxIter: 2000 }) };
609
+ }
610
+
611
+ test("nd is the recursive container: nested, ragged, heterogeneous", () => {
612
+ // A list of any element values — including other lists (nesting) of unequal
613
+ // length (ragged) and mixed domains (heterogeneous).
614
+ const v = nd([
615
+ int(1n),
616
+ real(3.5),
617
+ symbol(enc("large")),
618
+ nd([int(2n), int(3n)]), // a nested sub-list, shorter than the outer
619
+ ]);
620
+ assert.equal(tagOf(v), "nd");
621
+ assert.ok(isNd(v));
622
+ assert.equal(v.items.length, 4);
623
+ assert.equal(tagOf(v.items[3]), "nd");
624
+ // a scalar is NOT an nd, even a numeric one
625
+ assert.equal(isNd(int(1n)), false);
626
+ });
627
+
628
+ test("decimalCodec encodes an nd to its canonical bracket spelling (nested, mixed)", () => {
629
+ // The bracket literal is the kernel's canonical OUTPUT spelling of a list — one
630
+ // deterministic form (the analogue of decimal for a number), so two derivations
631
+ // of the same list agree byte-for-byte. The kernel BUILDS the Value (the host
632
+ // recognised its structure); the codec only spells it.
633
+ const codec = decimalCodec(6);
634
+ const out = (v: Value) => dec(codec.encode(v));
635
+ // flat
636
+ assert.equal(out(V([1, 2, 3])), "[1,2,3]");
637
+ // nested (a matrix) and ragged
638
+ assert.equal(out(V([[1, 2], [3, 4, 5]])), "[[1,2],[3,4,5]]");
639
+ // heterogeneous: int, real, symbol all spell their own form
640
+ assert.equal(out(V([1, 3.5, "large"])), "[1,3.5,large]");
641
+ // empty list
642
+ assert.equal(out(V([])), "[]");
643
+ });
644
+
645
+ test("parseValue reads only SCALARS — a bracket literal decodes to an opaque symbol", () => {
646
+ // The kernel no longer parses list STRUCTURE from bytes (that is the host's
647
+ // meaning-level job — src/mind/mind.ts recogniseValue). So decode is scalar-only: a
648
+ // bracket literal is not a list to the kernel, it is an opaque symbol, exactly
649
+ // like any other form that merely contains a bracket.
650
+ assert.equal(parseValue(enc("[1,2,3]")).domain, "symbol");
651
+ assert.equal(parseValue(enc("[1,2")).domain, "symbol");
652
+ assert.equal(parseValue(enc("[1,,2]")).domain, "symbol");
653
+ assert.equal(parseValue(enc("not a list")).domain, "symbol");
654
+ // the scalar floor still grounds a numeral
655
+ assert.deepEqual(parseValue(enc("42")), int(42n));
656
+ });
657
+
658
+ // ─────────────────────────────────────────────────────────────────────────────
659
+ // 12 — Broadcast: EVERY scalar op lifts over nd automatically (one mechanism).
660
+ // ─────────────────────────────────────────────────────────────────────────────
661
+
662
+ test("arithmetic broadcasts: list∘list zips, list∘scalar holds the scalar", () => {
663
+ const { ctx } = ndCtx();
664
+ const out = (r: Value) => dec(decimalCodec(6).encode(r));
665
+ // element-wise over two lists
666
+ assert.equal(out(ctx.apply("add", [V([1, 2, 3]), V([4, 5, 6])])), "[5,7,9]");
667
+ assert.equal(
668
+ out(ctx.apply("multiply", [V([1, 2, 3]), V([10, 10, 10])])),
669
+ "[10,20,30]",
670
+ );
671
+ // a scalar operand is held constant against the list
672
+ assert.equal(out(ctx.apply("add", [V([1, 2, 3]), int(10n)])), "[11,12,13]");
673
+ assert.equal(out(ctx.apply("subtract", [int(10n), V([1, 2, 3])])), "[9,8,7]");
674
+ });
675
+
676
+ test("broadcast RECURSES through nesting (a matrix op is the same code)", () => {
677
+ const { ctx } = ndCtx();
678
+ const out = (r: Value) => dec(decimalCodec(6).encode(r));
679
+ // nd-of-nd lifts twice with no extra machinery
680
+ assert.equal(
681
+ out(ctx.apply("add", [V([[1, 2], [3, 4]]), V([[10, 20], [30, 40]])])),
682
+ "[[11,22],[33,44]]",
683
+ );
684
+ // scalar against a matrix reaches every leaf
685
+ assert.equal(
686
+ out(ctx.apply("multiply", [V([[1, 2], [3, 4]]), int(2n)])),
687
+ "[[2,4],[6,8]]",
688
+ );
689
+ });
690
+
691
+ test("broadcast spans op classes: logic, comparison, transcendental, inverse", () => {
692
+ const opposites = new Map<string, string>([["hot", "cold"], ["up", "down"]]);
693
+ const stub: ResonanceSync = {
694
+ opposite: (b) => opposites.has(dec(b)) ? enc(opposites.get(dec(b))!) : null,
695
+ recogniseOp: () => null,
696
+ };
697
+ const { ctx } = ndCtx(stub);
698
+ const out = (r: Value) => dec(decimalCodec(6).encode(r));
699
+ // logic gate over a bit-list
700
+ assert.equal(out(ctx.apply("not", [V([1, 0, 1])])), "[0,1,0]");
701
+ // comparison → a list of truth bits
702
+ assert.equal(out(ctx.apply("gt", [V([1, 5, 3]), int(2n)])), "[0,1,1]");
703
+ // a transcendental over a list
704
+ assert.equal(out(ctx.apply("sqrt", [V([1, 4, 9])])), "[1,2,3]");
705
+ // the POLYMORPHIC INVERSE broadcasts: numbers negate, symbols resonate, in one
706
+ // heterogeneous list — each element dispatched on its own domain.
707
+ assert.equal(
708
+ out(ctx.apply("inverse", [V(["hot", 3, "up"])])),
709
+ "[cold,-3,down]",
710
+ );
711
+ });
712
+
713
+ test("broadcast over lists of unequal length declines (no silent truncation)", () => {
714
+ const { ctx } = ndCtx();
715
+ assert.throws(() => ctx.apply("add", [V([1, 2, 3]), V([4, 5])]));
716
+ });
717
+
718
+ // ─────────────────────────────────────────────────────────────────────────────
719
+ // 13 — The nd core: nd / length / at are the only ops that touch `items`.
720
+ // ─────────────────────────────────────────────────────────────────────────────
721
+
722
+ test("core: nd packs, length counts, at projects (negative = from end)", () => {
723
+ const { ctx } = ndCtx();
724
+ const out = (r: Value) => dec(decimalCodec(6).encode(r));
725
+ // pack
726
+ assert.equal(out(ctx.apply("nd", [int(1n), int(2n), int(3n)])), "[1,2,3]");
727
+ assert.equal(out(ctx.apply("nd", [])), "[]");
728
+ // length
729
+ const xs = V([5, 6, 7, 8]);
730
+ assert.equal(N(ctx.apply("length", [xs])), 4n);
731
+ // at, forward and from the end
732
+ assert.equal(N(ctx.apply("at", [xs, int(0n)])), 5n);
733
+ assert.equal(N(ctx.apply("at", [xs, int(-1n)])), 8n);
734
+ // out of range declines (throws → the facade maps it to "rule does not fire")
735
+ assert.throws(() => ctx.apply("at", [xs, int(9n)]));
736
+ });
737
+
738
+ // ─────────────────────────────────────────────────────────────────────────────
739
+ // 14 — Higher-order ops: the FUNCTION ARGUMENT is ANY existing operation.
740
+ // ─────────────────────────────────────────────────────────────────────────────
741
+
742
+ test("reduce folds by ANY binary op — +, *, max are sum, product, maximum", () => {
743
+ const { ctx } = ndCtx();
744
+ const xs = V([3, 1, 4, 1, 5, 9, 2, 6]);
745
+ // The op argument is a Value naming an operation; resolveOp turns it into the
746
+ // canonical op, so reduce reuses the whole scalar vocabulary as folds.
747
+ assert.equal(N(ctx.apply("reduce", [xs, symbol(enc("+"))])), 31n); // sum
748
+ assert.equal(
749
+ N(ctx.apply("reduce", [V([1, 2, 3, 4]), symbol(enc("*"))])),
750
+ 24n, // product
751
+ );
752
+ assert.equal(N(ctx.apply("reduce", [xs, symbol(enc("max"))])), 9n); // maximum
753
+ assert.equal(N(ctx.apply("reduce", [xs, symbol(enc("min"))])), 1n); // minimum
754
+ // a seed makes the fold total over an empty list
755
+ assert.equal(
756
+ N(ctx.apply("reduce", [V([]), symbol(enc("+")), int(0n)])),
757
+ 0n,
758
+ );
759
+ });
760
+
761
+ test("reduce by a binary op BROADCASTS — a column sum falls out of nesting", () => {
762
+ const { ctx } = ndCtx();
763
+ const out = (r: Value) => dec(decimalCodec(6).encode(r));
764
+ // reduce(rows, +) folds the row-LISTS with "+", and "+" itself broadcasts over
765
+ // them — so summing a list of rows yields the column sums, no matrix code.
766
+ assert.equal(
767
+ out(
768
+ ctx.apply("reduce", [
769
+ V([[1, 2, 3], [4, 5, 6]]),
770
+ symbol(enc("+")),
771
+ ]),
772
+ ),
773
+ "[5,7,9]",
774
+ );
775
+ });
776
+
777
+ test("map applies a unary op to each element; works over nesting", () => {
778
+ const { ctx } = ndCtx();
779
+ const out = (r: Value) => dec(decimalCodec(6).encode(r));
780
+ assert.equal(
781
+ out(ctx.apply("map", [V([1, 2, 3]), symbol(enc("negate"))])),
782
+ "[-1,-2,-3]",
783
+ );
784
+ assert.equal(
785
+ out(ctx.apply("map", [V([1, 4, 9]), symbol(enc("sqrt"))])),
786
+ "[1,2,3]",
787
+ );
788
+ });
789
+
790
+ test("filter keeps elements a unary predicate accepts; find returns the first", () => {
791
+ const { ctx } = ndCtx();
792
+ const out = (r: Value) => dec(decimalCodec(6).encode(r));
793
+ const xs = V([0, 5, 0, 3, 0, 8]);
794
+ // "sign" is 0 for zero, 1 for positive → a truthiness predicate
795
+ assert.equal(out(ctx.apply("filter", [xs, symbol(enc("sign"))])), "[5,3,8]");
796
+ assert.equal(N(ctx.apply("find", [xs, symbol(enc("sign"))])), 5n);
797
+ // find with no match returns the empty nd (graph-evidenced "nothing"), not a guess
798
+ const none = ctx.apply("find", [
799
+ V([0, 0, 0]),
800
+ symbol(enc("sign")),
801
+ ]);
802
+ assert.ok(isNd(none) && none.items.length === 0);
803
+ });
804
+
805
+ test("structural plumbing: concat, reverse, flatten, zip, range, rank, shape", () => {
806
+ const { ctx } = ndCtx();
807
+ const out = (r: Value) => dec(decimalCodec(6).encode(r));
808
+ assert.equal(
809
+ out(
810
+ ctx.apply("concat", [V([1, 2]), V([3, 4])]),
811
+ ),
812
+ "[1,2,3,4]",
813
+ );
814
+ assert.equal(
815
+ out(ctx.apply("reverse", [V([1, 2, 3])])),
816
+ "[3,2,1]",
817
+ );
818
+ assert.equal(
819
+ out(ctx.apply("flatten", [V([[1, 2], [3, 4]])])),
820
+ "[1,2,3,4]",
821
+ );
822
+ assert.equal(
823
+ out(
824
+ ctx.apply("zip", [
825
+ V([1, 2, 3]),
826
+ V([4, 5, 6]),
827
+ ]),
828
+ ),
829
+ "[[1,4],[2,5],[3,6]]",
830
+ );
831
+ assert.equal(out(ctx.apply("range", [int(4n)])), "[0,1,2,3]");
832
+ assert.equal(out(ctx.apply("range", [int(2n), int(5n)])), "[2,3,4]");
833
+ // rank = nesting depth; shape = size down the first axis
834
+ assert.equal(N(ctx.apply("rank", [V([[1, 2], [3, 4]])])), 2n);
835
+ assert.equal(N(ctx.apply("rank", [int(7n)])), 0n);
836
+ assert.equal(
837
+ out(ctx.apply("shape", [V([[1, 2, 3], [4, 5, 6]])])),
838
+ "[2,3]",
839
+ );
840
+ });
841
+
842
+ // ─────────────────────────────────────────────────────────────────────────────
843
+ // 15 — resolveOp: the function argument is resolved by the SAME machinery as any
844
+ // operator — a surface form, then (when not literal) its resonant meaning.
845
+ // ─────────────────────────────────────────────────────────────────────────────
846
+
847
+ test("resolveOp: a literal surface form names its canonical op", () => {
848
+ const { ctx } = ndCtx();
849
+ // canonical name, a synonym spelling, and a glyph all name the same op
850
+ assert.equal(ctx.resolveOp(symbol(enc("add"))), "add");
851
+ assert.equal(ctx.resolveOp(symbol(enc("plus"))), "add");
852
+ assert.equal(ctx.resolveOp(symbol(enc("+"))), "add");
853
+ assert.equal(ctx.resolveOp(symbol(enc("max"))), "max");
854
+ // nothing that names an op → null (the higher-order op then declines)
855
+ assert.equal(ctx.resolveOp(symbol(enc("zzz"))), null);
856
+ });
857
+
858
+ test("resolveOp disambiguates a shared surface form by arity", () => {
859
+ // Build a registry where one surface "@" is claimed by both a unary and a
860
+ // binary op, to exercise the arity tie-break directly (the kernel happens to
861
+ // share no surface across arities today, so this proves the mechanism).
862
+ const r = new OperationRegistry();
863
+ r.prim("u_at", 1, ["@"], (a) => a[0]);
864
+ r.prim("b_at", 2, ["@"], (a) => a[0]);
865
+ const ctx = r.context(NO_RESONANCE, { tol: 1e-10, maxIter: 100 });
866
+ assert.equal(ctx.resolveOp(symbol(enc("@")), 1), "u_at");
867
+ assert.equal(ctx.resolveOp(symbol(enc("@")), 2), "b_at");
868
+ // no arity hint → the first claimant (registration order)
869
+ assert.equal(ctx.resolveOp(symbol(enc("@"))), "u_at");
870
+ });
871
+
872
+ test("resolveOp falls through to RESONANCE when the bytes are not a literal form", () => {
873
+ // The host's resonance maps a meaning ("grand total of") to an op; resolveOp
874
+ // uses it exactly as the scan would for an operator a query does not spell.
875
+ const stub: ResonanceSync = {
876
+ opposite: () => null,
877
+ recogniseOp: (b) => dec(b).includes("grand total") ? "add" : null,
878
+ };
879
+ const { ctx } = ndCtx(stub);
880
+ assert.equal(ctx.resolveOp(symbol(enc("the grand total of"))), "add");
881
+ // and a reduce driven by that meaning folds correctly
882
+ assert.equal(
883
+ N(ctx.apply("reduce", [
884
+ V([10, 20, 30]),
885
+ symbol(enc("the grand total of")),
886
+ ])),
887
+ 60n,
888
+ );
889
+ });
890
+
891
+ test("a higher-order op declines (throws) when its operation argument is unrecognised", () => {
892
+ const { ctx } = ndCtx();
893
+ // no surface form, no resonance → resolveFn throws → the Alu facade would map
894
+ // this to "rule does not fire".
895
+ assert.throws(() =>
896
+ ctx.apply("reduce", [
897
+ V([1, 2, 3]),
898
+ symbol(enc("flibbertigibbet")),
899
+ ])
900
+ );
901
+ });
902
+
903
+ // ─────────────────────────────────────────────────────────────────────────────
904
+ // 16 — The Alu facade over nd: applyValues (Values in, canonical bytes out).
905
+ // The kernel computes on Values the HOST built — it does NOT parse list
906
+ // structure from bytes (src/mind/mind.ts recogniseValue does), so a list reaches the
907
+ // facade as an already-assembled Value, never as a bracket byte string.
908
+ // ─────────────────────────────────────────────────────────────────────────────
909
+
910
+ test("applyValues computes nd results: broadcast, reduce, and a composed pipeline", () => {
911
+ const u = new Alu();
912
+ // broadcast — two list Values in, the canonical bracket spelling out
913
+ assert.equal(
914
+ dec(u.applyValues("add", [V([1, 2, 3]), V([4, 5, 6])])!),
915
+ "[5,7,9]",
916
+ );
917
+ // reduce with an operator surface form (the op argument is a symbol Value)
918
+ assert.equal(
919
+ dec(u.applyValues("reduce", [V([1, 2, 3, 4]), symbol(enc("+"))])!),
920
+ "10",
921
+ );
922
+ // a computed nd is itself a valid operand (composition for free): map then reduce
923
+ const mapped = u.applyValues("map", [
924
+ V([1, 2, 3, 4]),
925
+ symbol(enc("negate")),
926
+ ])!;
927
+ assert.equal(dec(mapped), "[-1,-2,-3,-4]");
928
+ // an unknown op or a declining computation → null (the rule does not fire)
929
+ assert.equal(
930
+ u.applyValues("reduce", [V([1, 2]), symbol(enc("nonexistent-op"))]),
931
+ null,
932
+ );
933
+ });
934
+
935
+ test("applyBytes is SCALAR-only: it does not read list structure from bytes", () => {
936
+ const u = new Alu();
937
+ // applyBytes decodes operands through the codec, which is scalar-only — a
938
+ // bracket literal becomes an opaque symbol, so arithmetic over it declines.
939
+ // Reading list STRUCTURE from bytes is recogniseValue's job (§17), not the
940
+ // codec's; applyBytes stays the pure scalar-operand path the search uses.
941
+ assert.equal(u.applyBytes("add", [enc("[1,2,3]"), enc("[4,5,6]")]), null);
942
+ // scalar arithmetic through the byte facade is unaffected
943
+ assert.equal(dec(u.applyBytes("add", [enc("2"), enc("2")])!), "4");
944
+ });
945
+
946
+ // ─────────────────────────────────────────────────────────────────────────────
947
+ // 17 — recogniseValue: the bytes→Value boundary. A span is a scalar, or a LIST
948
+ // (a run of element values joined by a CONSISTENT separator) — and no
949
+ // separator spelling is privileged. This is where structure is recognised,
950
+ // layered over scan, so the kernel itself only ever computes on Values.
951
+ // ─────────────────────────────────────────────────────────────────────────────
952
+
953
+ test("recogniseValue reads a SCALAR — a numeral, or an opaque symbol", () => {
954
+ const u = new Alu();
955
+ assert.deepEqual(u.recogniseValue(enc("42")), int(42n));
956
+ assert.deepEqual(u.recogniseValue(enc("3.5")), real(3.5));
957
+ // a single word / learnt form / operator name stays an opaque symbol
958
+ assert.equal(u.recogniseValue(enc("large")).domain, "symbol");
959
+ assert.equal(u.recogniseValue(enc("+")).domain, "symbol");
960
+ // surrounding whitespace is trimmed before the reading
961
+ assert.deepEqual(u.recogniseValue(enc(" 7 ")), int(7n));
962
+ });
963
+
964
+ test("recogniseValue reads a CONTAINER list, separator spelling not privileged", () => {
965
+ const u = new Alu();
966
+ const enc6 = decimalCodec(6);
967
+ const to = (s: string) => dec(enc6.encode(u.recogniseValue(enc(s))));
968
+ // comma, comma+space, or bare space inside the brackets — all the same list
969
+ for (const s of ["[1,2,3]", "[1, 2, 3]", "[1 2 3]"]) {
970
+ assert.equal(to(s), "[1,2,3]", s);
971
+ }
972
+ // nested (a matrix), ragged, and heterogeneous all recurse element-wise
973
+ assert.equal(to("[[1,2],[3,4,5]]"), "[[1,2],[3,4,5]]");
974
+ assert.equal(to("[1,3.5,large]"), "[1,3.5,large]");
975
+ assert.equal(to("[]"), "[]"); // empty container → the empty list
976
+ // a parsed container really is an nd with the right element domains
977
+ const v = u.recogniseValue(enc("[1,3.5,large]"));
978
+ assert.ok(isNd(v));
979
+ assert.deepEqual(v.items.map(tagOf), ["int", "real", "symbol"]);
980
+ });
981
+
982
+ test("recogniseValue reads a bare SEQUENCE by ANY consistent connective", () => {
983
+ const u = new Alu();
984
+ const enc6 = decimalCodec(6);
985
+ const to = (s: string) => dec(enc6.encode(u.recogniseValue(enc(s))));
986
+ // space, comma, comma+space, or the word " and " between ≥2 numeric operands
987
+ for (const s of ["1 2 3", "1,2,3", "1, 2, 3", "1 and 2 and 3"]) {
988
+ assert.equal(to(s), "[1,2,3]", s);
989
+ }
990
+ });
991
+
992
+ test("recogniseValue does NOT over-read: inconsistent or wordy spans stay scalar", () => {
993
+ const u = new Alu();
994
+ // a single numeral is a scalar, never a one-element list
995
+ assert.equal(u.recogniseValue(enc("5")).domain, "int");
996
+ // an INCONSISTENT separator ("space" then "comma") is not one sequence → symbol
997
+ assert.equal(u.recogniseValue(enc("1 2,3")).domain, "symbol");
998
+ // leftover material at an edge (a trailing word) is not a clean sequence
999
+ assert.equal(u.recogniseValue(enc("1 2 buckle")).domain, "symbol");
1000
+ // a bare run of words (no numeric operands) stays an opaque symbol
1001
+ assert.equal(u.recogniseValue(enc("large tall")).domain, "symbol");
1002
+ // a stray unbalanced bracket is not a container → opaque symbol
1003
+ assert.equal(u.recogniseValue(enc("[1,2")).domain, "symbol");
1004
+ });