@hviana/sema 0.4.4 → 0.4.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 (54) hide show
  1. package/AUTHORS.md +0 -1
  2. package/LICENSE.md +1 -1
  3. package/README.md +2 -2
  4. package/dist/src/geometry.d.ts +6 -0
  5. package/dist/src/geometry.js +224 -44
  6. package/dist/src/mind/attention.d.ts +11 -0
  7. package/dist/src/mind/attention.js +344 -13
  8. package/dist/src/mind/junction.js +18 -2
  9. package/dist/src/mind/match.d.ts +11 -0
  10. package/dist/src/mind/match.js +13 -2
  11. package/dist/src/mind/mechanisms/cast.js +366 -34
  12. package/dist/src/mind/mechanisms/confluence.js +17 -1
  13. package/dist/src/mind/mechanisms/recall.js +17 -3
  14. package/dist/src/mind/pipeline-mechanism.d.ts +4 -0
  15. package/dist/src/mind/pipeline-mechanism.js +96 -40
  16. package/dist/src/mind/pipeline.js +31 -3
  17. package/dist/src/mind/reasoning.d.ts +4 -2
  18. package/dist/src/mind/reasoning.js +29 -4
  19. package/dist/src/mind/recognition.js +67 -2
  20. package/dist/src/mind/resonance.d.ts +14 -2
  21. package/dist/src/mind/resonance.js +0 -0
  22. package/dist/src/mind/types.d.ts +43 -1
  23. package/dist/src/rabitq-ivf/src/rabitq.js +31 -1
  24. package/dist/src/sema.d.ts +11 -1
  25. package/dist/src/sema.js +16 -2
  26. package/dist/src/store.d.ts +64 -1
  27. package/dist/src/store.js +107 -8
  28. package/index.html +2 -3
  29. package/package.json +1 -1
  30. package/src/geometry.ts +231 -43
  31. package/src/mind/attention.ts +366 -15
  32. package/src/mind/junction.ts +18 -2
  33. package/src/mind/match.ts +18 -2
  34. package/src/mind/mechanisms/cast.ts +376 -43
  35. package/src/mind/mechanisms/confluence.ts +16 -1
  36. package/src/mind/mechanisms/recall.ts +17 -2
  37. package/src/mind/pipeline-mechanism.ts +96 -36
  38. package/src/mind/pipeline.ts +33 -3
  39. package/src/mind/reasoning.ts +31 -4
  40. package/src/mind/recognition.ts +65 -2
  41. package/src/mind/resonance.ts +0 -0
  42. package/src/mind/types.ts +43 -1
  43. package/src/rabitq-ivf/src/rabitq.ts +31 -1
  44. package/src/sema.ts +21 -2
  45. package/src/store.ts +106 -5
  46. package/test/00-extract.test.mjs +28 -0
  47. package/test/15-decomposition-gap.test.mjs +0 -0
  48. package/test/24-generalization.test.mjs +67 -19
  49. package/test/29-counterfactual.test.mjs +106 -42
  50. package/test/33-multi-candidate.test.mjs +56 -12
  51. package/test/53-cross-region-probe-instrumentation.test.mjs +16 -1
  52. package/test/63-fold-invariants.test.mjs +489 -0
  53. package/test/64-two-ended-thresholds.test.mjs +76 -0
  54. package/test/65-ann-recall.test.mjs +331 -0
@@ -188,27 +188,31 @@ function computeWeave(ctx, query, pre, climb) {
188
188
  const askerBytes = query.length -
189
189
  ctx.answeredSpans.reduce((n, [start, end]) => n + end - start, 0);
190
190
  const capBytes = askerBytes * quantum;
191
- // EXCLUSIVITY IS ARBITRATED BY THE CLIMB'S VOTE ORDER, DELIBERATELY. A query
192
- // byte can only be independent evidence for ONE point, so points are built in
193
- // ranked order and each new point's runs are trimmed against every point
194
- // already accepted; a point left with no run of a full quantum drops out of
195
- // the weave.
191
+ // RUNS ARE NOT TRIMMED AGAINST EACH OTHER. A point keeps every byte it
192
+ // aligned; exclusivity is a property of STRUCTURES (see "one place, one
193
+ // structure" below), not of individual query bytes.
196
194
  //
197
- // That reads like first-come-wins a point that merely ranked higher taking
198
- // a span from the point that actually explains it — and arbitrating by LOCAL
199
- // evidence instead (ownership of each byte to the longest covering run, then
200
- // the heavier weight, then rank) was implemented and MEASURED: test/29 went
201
- // 9/2 to 7/4, and the new failures name the reason. CAST requires the weave
202
- // to touch a COMMITTED point of attention ("2 aligned structure(s), but none
203
- // is one of the climb's 1 committed root(s)"), and it was precisely the vote
204
- // order that kept the committed root's own point alive in the weave. Local
205
- // run length knows nothing about what the climb settled on, so it evicted the
206
- // root's evidence and left CAST refusing on its own consistency check.
195
+ // This weave used to build points in the climb's vote order and cut each new
196
+ // point's runs against every point already accepted. It is worth recording
197
+ // what that cost, because the cut was invisible: it did not just resolve
198
+ // ties, it silently DECIDED downstream schemas. A point's `runs[0]` the
199
+ // run three CAST branches read as "the filler", "the seat", "the name" was
200
+ // whichever run happened to survive the cut, so those schemas were reading an
201
+ // elimination order as though it were evidence, and the query's own bytes
202
+ // were truncated on the way ("Shakespeare" surviving as "Shakes"). Each
203
+ // consumer now derives its own reading from the runs (cast.ts: `fillerRun`
204
+ // clips at the seat, redirection scans for the naming run, entry counts own
205
+ // bytes and the climb's dispersion), and with those in place removing the cut
206
+ // costs nothing — measured, the same 442 tests pass either way.
207
207
  //
208
- // So the vote order here is not an accident of construction it is what
209
- // holds the weave and the climb to the same conclusion. Weave-local
210
- // measures decide what is FRAME inside the weave (see the frame gates in
211
- // cast.ts); which structures are in the weave at all stays the climb's call.
208
+ // What the vote order was RIGHT about is kept: which structures belong in the
209
+ // weave is the climb's call, not a local run measure. Arbitrating byte
210
+ // ownership by local evidence instead (longest covering run, then weight,
211
+ // then rank) was implemented and MEASURED, and it evicted the committed
212
+ // root's own evidence — CAST then refused on its own consistency check ("2
213
+ // aligned structure(s), but none is one of the climb's 1 committed root(s)"),
214
+ // test/29 going 9/2 to 7/4. Weave-local measures decide what is FRAME inside
215
+ // the weave (see the frame gates in cast.ts); membership stays the climb's.
212
216
  //
213
217
  // TWO PASSES. `depth` — how much of the weave agrees on each query byte, and
214
218
  // therefore what counts as FRAME — must be the whole weave's, not "whatever
@@ -304,34 +308,38 @@ function computeWeave(ctx, query, pre, climb) {
304
308
  });
305
309
  if (raw.length === 0)
306
310
  continue;
311
+ // DEPTH COUNTS STRUCTURES, NOT WEIGHT. The frame test is
312
+ // `dominates(depth[i], aligned)` — "more than half the weave shares this
313
+ // byte" — and `aligned` is a COUNT of points. Accumulating graded
314
+ // alignment WEIGHT here compared weight-mass against a cardinality: two
315
+ // different dimensions, meaningful only while truncation happened to keep
316
+ // points.length small and weights near 1.
317
+ //
318
+ // Measured (test/29 C2, only truncation toggled): 9 candidates collapse to
319
+ // 2 points and 29/42 bytes read FRAME; without truncation 9 points survive
320
+ // and only 6/42 do. The elimination was SETTING the frame threshold, so
321
+ // every attempt to change run ownership inverted the frame reading and
322
+ // lost the same 10 tests (442 -> 432, twice, for opposite designs).
323
+ //
324
+ // Counting distinct covering candidates restores the documented meaning
325
+ // exactly and makes the comparison like-for-like, which decouples the
326
+ // frame gate from however many points survive.
327
+ const covered = new Uint8Array(query.length);
307
328
  for (const r of raw) {
308
- for (let i = r.qs; i < r.qe; i++)
309
- depth[i] += r.weight;
329
+ for (let i = r.qs; i < r.qe; i++) {
330
+ if (!covered[i]) {
331
+ covered[i] = 1;
332
+ depth[i] += 1;
333
+ }
334
+ }
310
335
  }
311
336
  cands.push({ cand, ctxBytes, raw });
312
337
  }
313
338
  for (const { cand, ctxBytes, raw } of cands) {
314
339
  const free = [];
315
340
  for (const r of raw) {
316
- let { qs, qe, cs, weight } = r;
317
- for (const p of points) {
318
- for (const o of p.runs) {
319
- if (qs >= qe)
320
- break;
321
- if (o.qe <= qs || o.qs >= qe)
322
- continue;
323
- const left = Math.max(0, o.qs - qs);
324
- const right = Math.max(0, qe - o.qe);
325
- if (left >= right)
326
- qe = qs + left;
327
- else {
328
- cs += qe - right - qs;
329
- qs = qe - right;
330
- }
331
- }
332
- }
333
- if (qe - qs >= Math.min(quantum, ctxBytes.length)) {
334
- free.push({ qs, qe, cs, weight });
341
+ if (r.qe - r.qs >= Math.min(quantum, ctxBytes.length)) {
342
+ free.push({ ...r });
335
343
  }
336
344
  }
337
345
  if (free.length > 0) {
@@ -340,6 +348,8 @@ function computeWeave(ctx, query, pre, climb) {
340
348
  vote: cand.vote,
341
349
  ctx: ctxBytes,
342
350
  runs: free,
351
+ start: cand.start,
352
+ end: cand.end,
343
353
  };
344
354
  byAnchor.set(cand.anchor, pt);
345
355
  points.push(pt);
@@ -449,6 +459,8 @@ function computeWeave(ctx, query, pre, climb) {
449
459
  vote: cand.vote,
450
460
  ctx: ctxBytes,
451
461
  runs: [run],
462
+ start: cand.start,
463
+ end: cand.end,
452
464
  };
453
465
  byAnchor.set(cand.anchor, made);
454
466
  points.push(made);
@@ -461,5 +473,49 @@ function computeWeave(ctx, query, pre, climb) {
461
473
  }
462
474
  }
463
475
  }
476
+ // ONE PLACE, ONE STRUCTURE. A stored sentence and the entity it names are
477
+ // not two independent structures when the query's evidence for them is the
478
+ // same bytes — they are one place read at two grains, and admitting both
479
+ // lets a nest of containing sentences outvote the entity the query actually
480
+ // named. Measured on test/29 C2 ("How is Shakespeare like Leonardo da
481
+ // Vinci?"): the five sentences that merely CONTAIN the two names align the
482
+ // same q6-18 / q23-41 the names do, and comparison ended up seated on a
483
+ // 49-byte sentence instead of the 17-byte entity.
484
+ //
485
+ // A point earns its own place in the weave the same way a second point earns
486
+ // CAST's entry: at least one perception quantum of query bytes no
487
+ // better-voted point already explains. Points arrive in the climb's vote
488
+ // order, which is the arbiter this file already trusts for what belongs in
489
+ // the weave; unlike run trimming, nothing is CUT here — a point keeps every
490
+ // byte it aligned or it is not a separate structure at all.
491
+ const coveredOf = (p) => {
492
+ const set = new Set();
493
+ for (const r of p.runs)
494
+ for (let i = r.qs; i < r.qe; i++)
495
+ set.add(i);
496
+ return set;
497
+ };
498
+ const kept = [];
499
+ const keptCover = [];
500
+ for (const p of points) {
501
+ const cov = coveredOf(p);
502
+ let redundant = false;
503
+ for (const other of keptCover) {
504
+ let own = 0;
505
+ for (const i of cov)
506
+ if (!other.has(i))
507
+ own++;
508
+ if (own < quantum) {
509
+ redundant = true;
510
+ break;
511
+ }
512
+ }
513
+ if (!redundant) {
514
+ kept.push(p);
515
+ keptCover.push(cov);
516
+ }
517
+ }
518
+ points.length = 0;
519
+ points.push(...kept);
464
520
  return { points, depth };
465
521
  }
@@ -9,11 +9,12 @@
9
9
  // each); the shared pre-computation they exchange lives in Precomputed
10
10
  // (pipeline-mechanism.ts).
11
11
  import { PASS, STEP } from "./graph-search.js";
12
- import { gistOf, resolve } from "./primitives.js";
12
+ import { gistOf, read, resolve } from "./primitives.js";
13
13
  import { recognise } from "./recognition.js";
14
14
  import { fuseAttention, reason } from "./reasoning.js";
15
15
  import { unexplainedSpans } from "./rationale.js";
16
16
  import { rItem } from "./trace.js";
17
+ import { hubBound } from "./traverse.js";
17
18
  import { Precomputed } from "./pipeline-mechanism.js";
18
19
  import { coverMechanism } from "./mechanisms/cover.js";
19
20
  import { castMechanism } from "./mechanisms/cast.js";
@@ -220,9 +221,36 @@ export async function think(ctx, query, mechs) {
220
221
  // already a trained form's own continuation, reached through an identity
221
222
  // claim about the query, so a multi-hop pivot could only chain past the
222
223
  // fact that produced it (see MechanismResult.complete).
224
+ // WHAT THE MECHANISM WITHHELD, NOT WHAT IT VOICED. A pivot must not
225
+ // re-open content a grounding deliberately kept out: comparison cites two
226
+ // analogs and refuses their own downstream facts, so pivoting into one is
227
+ // the mechanism's own refusal undone one step later (test/29 C2 pivoted
228
+ // through `speare` — a stored fragment of the analog `William Shakespeare`
229
+ // — into the biography CAST had declined).
230
+ //
231
+ // Reading the used anchors' OWN bytes here says something stronger and
232
+ // wrong: that nothing INSIDE what was voiced may be pivoted through. A
233
+ // comparison's seat sentence legitimately contains further terms with
234
+ // their own unrelated facts, and C3 pins exactly that — `Mona Lisa`, inside
235
+ // the voiced seat `The Mona Lisa was painted by Leonardo da Vinci.`, leads
236
+ // on to `Mona Lisa hangs in the Louvre`, which is about neither analog.
237
+ // The withheld content is the used anchors' CONTINUATIONS, so that is what
238
+ // the containment rule reads: `speare` is contained in `Shakespeare wrote
239
+ // 39 plays` and stays refused, while `Mona Lisa` appears in no withheld
240
+ // continuation and the genuine further hop fires.
241
+ //
242
+ // Only a mechanism carrying its own `used` set (cast/join) gets this: there
243
+ // `preConsumed` is a deliberate, short list of the anchors the answer
244
+ // speaks for, so the fan-out is bounded. For every other provenance
245
+ // `preConsumed` is derived by re-recognising the answer — "everything in
246
+ // it", not "what it voiced" — and a containment rule over that would
247
+ // suppress every pivot the answer legitimately contains.
248
+ const voiced = (provenance === "cast" || provenance === "join")
249
+ ? [...castUsed].flatMap((id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)))
250
+ : [];
223
251
  const reasoned = decided.complete ? answer : meter
224
- ? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre))
225
- : await reason(ctx, query, answer, preConsumed, pre);
252
+ ? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre, voiced))
253
+ : await reason(ctx, query, answer, preConsumed, pre, voiced);
226
254
  // Fuse only when the query has a genuine REMAINDER no mechanism's
227
255
  // structural evidence touched at all. `decided.accounted` alone
228
256
  // undercounts this: it is a COST-LADDER quantity (cover.ts prices its
@@ -15,10 +15,12 @@ export declare function restatesQuery(query: Uint8Array, bytes: Uint8Array): boo
15
15
  * Pivots on the longest unconsumed learnt context each answer contains,
16
16
  * then follows the pivot's continuation to the next fact. Repeats up
17
17
  * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
18
- * spoken for by the grounding stage (cover/extract/CAST). `pre` is the
18
+ * spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
19
+ * the BYTES of the anchors a mechanism declared it voiced (its `used` set),
20
+ * when it declared one — see the pivot's own containment rule. `pre` is the
19
21
  * response's shared pre-computation — the post-grounding stages read the
20
22
  * same container the mechanisms did. */
21
- export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed): Promise<Uint8Array>;
23
+ export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed, voiced?: readonly Uint8Array[]): Promise<Uint8Array>;
22
24
  /** Fuse independent points of attention into one answer (multi-topic).
23
25
  * When the consensus climb finds more than one dominant point, each
24
26
  * independent point grounds its own answer; they are bridged together
@@ -25,10 +25,12 @@ export function restatesQuery(query, bytes) {
25
25
  * Pivots on the longest unconsumed learnt context each answer contains,
26
26
  * then follows the pivot's continuation to the next fact. Repeats up
27
27
  * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
28
- * spoken for by the grounding stage (cover/extract/CAST). `pre` is the
28
+ * spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
29
+ * the BYTES of the anchors a mechanism declared it voiced (its `used` set),
30
+ * when it declared one — see the pivot's own containment rule. `pre` is the
29
31
  * response's shared pre-computation — the post-grounding stages read the
30
32
  * same container the mechanisms did. */
31
- export async function reason(ctx, query, answer, preConsumed, pre) {
33
+ export async function reason(ctx, query, answer, preConsumed, pre, voiced = []) {
32
34
  // Echo guard: a query that is ITSELF a learnt continuation (some context's
33
35
  // answer) is being asked back at the system — hopping forward from it would
34
36
  // chain through the very fact that produced it and echo the conversation
@@ -115,7 +117,7 @@ export async function reason(ctx, query, answer, preConsumed, pre) {
115
117
  }
116
118
  // Pivot: find the longest unconsumed learnt context the answer contains.
117
119
  consumeAll(curId);
118
- const pivot = await pivotInto(ctx, cur, consumed);
120
+ const pivot = await pivotInto(ctx, cur, consumed, voiced);
119
121
  if (pivot === null)
120
122
  break;
121
123
  const fc = await follow(ctx, pivot, qv);
@@ -171,8 +173,31 @@ primarySpans = []) {
171
173
  // here, since it is an absolute ln(N)-scaled quantity (a genuine root on
172
174
  // a large store can score BELOW its own floor while a coincidental echo
173
175
  // on a small one scores comfortably above its own, smaller, floor).
176
+ //
177
+ // Breadth alone is not enough when primary is a pure COMPUTATION. The ALU
178
+ // answers "2+2 equals what?" with 4, and the store's own arithmetic table
179
+ // then supplies a lone root — an exemplar like "1+2" — whose breadth
180
+ // dominates because it is corroborated by the computation's OWN bytes.
181
+ // Fusing it projected that exemplar's continuation and the bridge voiced
182
+ // "4+3" (test/11 seed 99). A second point of attention must stand on
183
+ // evidence that is structurally SEPARATE from primary's: at least one
184
+ // perceptual quantum of query between them, the same separation
185
+ // countClusters uses to tell independent evidence neighbourhoods apart.
186
+ // Not a score, and not a tuned bar — the fold's own quantum.
187
+ //
188
+ // With no primarySpans (the caller did not resolve them) every span
189
+ // vacuously qualifies, preserving the original behaviour exactly.
190
+ const quantum = ctx.space.maxGroup;
191
+ const independentOfPrimary = (root) => primarySpans.every(([s, e]) => {
192
+ const gap = root.end <= s
193
+ ? s - root.end
194
+ : e <= root.start
195
+ ? root.start - e
196
+ : 0;
197
+ return gap >= quantum;
198
+ });
174
199
  const lonePromotes = unclimbed && forest.length === 1 &&
175
- forest[0].breadth > 0.5;
200
+ forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
176
201
  if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
177
202
  return primary;
178
203
  }
@@ -225,6 +225,58 @@ function recogniseImpl(ctx, bytes) {
225
225
  emit(start, end - k, eRight);
226
226
  }
227
227
  }
228
+ // A trained form embedded at this span's left edge, past the
229
+ // chunk-scale bound above. The loop above probes exactly this — trim
230
+ // k leading bytes, verify the remainder is a stored branch — but only
231
+ // for spans of at most W². A turn prefixed with a connective is
232
+ // turn-scale, so it never qualified.
233
+ //
234
+ // Widening that loop's SIZE bound is what reopens test/46's
235
+ // root-scale false positive. Widening only its LEFT trim, to a
236
+ // bounded W offsets, does not: every candidate is still verified by
237
+ // exact content addressing (the leaf-id run must BE a stored branch),
238
+ // and the result always ends where this span ends, so it can never
239
+ // introduce the smaller-subtree duplicate that regression was about.
240
+ //
241
+ // This replaces an assumption that no longer holds — that such a
242
+ // form's left edge must be a cut the fold itself drew. It held while
243
+ // cuts had long memory and a turn boundary reliably produced one; a
244
+ // bounded-window rule re-synchronises a byte or two INTO the turn
245
+ // instead, so the edge itself is often not a cut ("And " ends at 65,
246
+ // and the fold's nearest cuts are 61 and 67).
247
+ // No leaf-id prefilter here, unlike the loop above: a leaf id is the
248
+ // LONGEST known leaf at a position, so the run itself is context
249
+ // sensitive — measured, the embedded copy of a trained form yields a
250
+ // different run from the standalone one and findBranch misses even
251
+ // though the bytes resolve exactly (span [65,94): findBranch null,
252
+ // resolve 91). With only W candidates the exact fold is affordable,
253
+ // and it is the stronger evidence anyway: if it resolves, these exact
254
+ // bytes ARE a stored node.
255
+ for (let k = 1; k <= W && start + k < end - 1; k++) {
256
+ const eLeft = resolve(ctx, bytes.subarray(start + k, end));
257
+ if (eLeft !== null)
258
+ emit(start + k, end, eLeft);
259
+ }
260
+ // THE SAME SEARCH ON THE OTHER EDGE. Everything above trims from the
261
+ // LEFT and keeps this span's END fixed, so a stored form was findable
262
+ // only when it ENDED where a fold node ends. Measured on a 12-context
263
+ // store, probing for a trained 47-byte sentence wrapped in filler:
264
+ // 1-4 bytes of LEFT padding kept it recognisable, while ONE byte of
265
+ // right padding lost it. That asymmetry was never argued for — the
266
+ // reasoning above is about a form's left edge landing on a cut, and it
267
+ // says nothing about which side the noise is on.
268
+ //
269
+ // The stated hazard for widening this search is test/46's root-scale
270
+ // false positive, and it is a hazard of the SIZE bound, not of the
271
+ // direction: like its mirror this loop is bounded to W offsets and
272
+ // every candidate is verified by exact content addressing, so it can
273
+ // only ever emit spans that ARE stored nodes. Measured: neutral on
274
+ // the suite, and the right-padded cases above become recognisable.
275
+ for (let k = 1; k <= W && start < end - k - 1; k++) {
276
+ const eRight = resolve(ctx, bytes.subarray(start, end - k));
277
+ if (eRight !== null)
278
+ emit(start, end - k, eRight);
279
+ }
228
280
  // A REAL extra word at the left edge (a discourse connective like
229
281
  // "And " prepended to a follow-up turn — not boundary noise, actual
230
282
  // content the injected canonicalizer has no equivalence for) shows
@@ -375,12 +427,14 @@ function recogniseImpl(ctx, bytes) {
375
427
  }
376
428
  }
377
429
  const chunkEnd = new Uint32Array(bytes.length);
430
+ const chunkSpan = new Uint32Array(bytes.length);
378
431
  const sorted = [...starts].sort((a, b) => a - b);
379
432
  for (let si = 0; si < sorted.length; si++) {
380
433
  const chunkStart = sorted[si];
381
434
  const chunkLimit = si + 1 < sorted.length ? sorted[si + 1] : bytes.length;
382
435
  for (let p = chunkStart; p < chunkLimit; p++) {
383
436
  chunkEnd[p] = chunkLimit;
437
+ chunkSpan[p] = chunkLimit - chunkStart;
384
438
  }
385
439
  }
386
440
  // A chain rebuilt from a NON-boundary offset (the query's own perceived
@@ -434,8 +488,19 @@ function recogniseImpl(ctx, bytes) {
434
488
  tryChain(p, chainReach(W), true); // boundary start — full reach
435
489
  }
436
490
  else {
437
- const limit = chunkEnd[p] + W;
438
- tryChain(p, Math.min(limit - p, chainReach(W)), false);
491
+ // THE INTERIOR BUDGET IS "ONE CHUNK PLUS A QUANTUM", MEASURED FROM THE
492
+ // CHAIN'S OWN START. It used to be `chunkEnd[p] + W - p`, which counts
493
+ // from the chunk's END, so the reach an interior chain gets depended on
494
+ // WHERE INSIDE its chunk it happened to begin: measured on a composed
495
+ // answer, a chunk spanning [0,6) gave offset 1 nine ids and offset 4
496
+ // only six — and the 9-id trained form `Mona Lisa` starting at 4 died
497
+ // three ids short of itself. The same form one byte earlier would have
498
+ // been found. That is the position artifact this module has been
499
+ // removing everywhere else, not a budget.
500
+ //
501
+ // Stated from `p` the trust is unchanged — a chain may span its own
502
+ // chunk and one quantum beyond it — and it no longer varies with phase.
503
+ tryChain(p, Math.min(chunkSpan[p] + W, chainReach(W)), false);
439
504
  }
440
505
  }
441
506
  // ── splits: a form boundary that does not fall on a leaf edge ────────
@@ -15,8 +15,20 @@ export declare function joinWithBridge(ctx: MindContext, left: Uint8Array, right
15
15
  * CONTEXT (a node bearing a continuation edge) whose bytes `answer` literally
16
16
  * contains. Candidates are gathered by resonating the answer's sub-regions
17
17
  * (breadth-first, leaves skipped, probes capped by branch count), then
18
- * confirmed by exact byte containment — a near-resonance alone never hops. */
19
- export declare function pivotInto(ctx: MindContext, answer: Uint8Array, consumed: ReadonlySet<number>): Promise<number | null>;
18
+ * confirmed by exact byte containment — a near-resonance alone never hops.
19
+ *
20
+ * `voiced` carries the BYTES of the anchors the grounding mechanism declared
21
+ * it spoke for, and no candidate CONTAINED IN one of them may be pivoted
22
+ * through. Node identity alone is too coarse a reading of "already spoken
23
+ * for": the same content is interned at several scales, so a strict fragment
24
+ * of a voiced anchor is a different id carrying no evidence the anchor did
25
+ * not already carry. Measured on test/29 C2 — CAST voiced "William
26
+ * Shakespeare" (id 676) and the pivot hopped through "speare" (id 606, not
27
+ * in `consumed`) straight into that analog's own biography, which is exactly
28
+ * what the comparison had refused to voice. The rule is CONTAINMENT, not
29
+ * overlap, so C3's genuine further hop — "Mona Lisa", a term inside the seat
30
+ * sentence but part of NEITHER analog — still fires. */
31
+ export declare function pivotInto(ctx: MindContext, answer: Uint8Array, consumed: ReadonlySet<number>, voiced?: readonly Uint8Array[]): Promise<number | null>;
20
32
  export declare function meaningOf(ctx: MindContext, bytes: Uint8Array, anchors: ReadonlyArray<{
21
33
  name: string;
22
34
  form: Uint8Array;
Binary file
@@ -64,9 +64,28 @@ export interface Attention {
64
64
  anchor: number;
65
65
  /** IDF-weighted consensus vote — the strength that orders points. */
66
66
  vote: number;
67
- /** The union of the query byte-spans whose evidence supports this point. */
67
+ /** The query span of the point's STRONGEST contributing region the argmax
68
+ * over `wFocus` (see `peak`, which is that same region's weight), NOT a
69
+ * union or hull over every region that voted. Measured on test/24 3.2: the
70
+ * winning anchor's span here was 2 bytes while its contributing regions
71
+ * together covered most of the query. It is the minimal honest statement
72
+ * of what a grounding on this anchor rests on, and recall accounts exactly
73
+ * it for that reason — widening it to every contributing region made recall
74
+ * out-bid mechanisms that had genuinely explained more (a GENERATED list
75
+ * degraded to a RETRIEVED one, test/24 3.2 and test/04 1). */
68
76
  start: number;
69
77
  end: number;
78
+ /** The largest SINGLE region's contribution to this point's pooled vote —
79
+ * the weight of the very region `start`..`end` delimits (both are the
80
+ * argmax over `wFocus`), so the two fields describe one region: its
81
+ * strength and its place.
82
+ * `vote` is a sum over every region that agreed, so it grows with how many
83
+ * places corroborated; `peak` is what the strongest one of them said on its
84
+ * own. A consumer holding this point to consensusFloor(N) — a bar that
85
+ * prices ONE region's maximally-discriminative evidence — must read `peak`,
86
+ * not `vote`: six scaffolding regions summing past the floor is not the
87
+ * same claim as one region clearing it. */
88
+ peak: number;
70
89
  /** SCALE-INVARIANT confidence: the fraction of the query's OWN regions
71
90
  * whose evidence this point accounts for (Σ RegionVote.absorbed among
72
91
  * its contributors, over the query's total region count) — read PER-
@@ -118,6 +137,24 @@ export interface Region {
118
137
  * resonance score is byte-overlap, evidence only in excess of its best
119
138
  * rival conclusion. */
120
139
  known: boolean;
140
+ /** The stored node this region's bytes ARE, when the region came from a
141
+ * recognised SITE — content-addressed and exact, so the climb has no
142
+ * reason to re-derive it approximately. A perceived sub-tree leaves this
143
+ * undefined; chunks get the same thing from `canonicalChunkId`. */
144
+ id?: number;
145
+ /** EVIDENCE, NOT A POINT OF ATTENTION. True for a region the query's own
146
+ * fold never produced — a stored form that a content-defined cut SPLIT,
147
+ * recovered by sliding-window lookup in collectRegions. The store
148
+ * guarantees such a form is addressable (canonicalWindows interns both
149
+ * lengths), so it may corroborate an anchor's vote; but the query did not
150
+ * weave it as an independent structure, so it must not make the query look
151
+ * like it holds one more point of attention than it does — it is kept out
152
+ * of the root-cut distribution and out of the breadth ratio (see
153
+ * poolVotes/commitVotes). Absent/false for every region from the fold.
154
+ * (Flagging these `chunk: true` instead is REFUTED — a chunk is a
155
+ * smallest unit the FOLD produced, and claiming first-class unit status
156
+ * for an assembled span cost 5 tests.) */
157
+ corroborating?: boolean;
121
158
  }
122
159
  /** Per-region vote data from the consensus climb's resonance pass. */
123
160
  export interface RegionVote {
@@ -148,6 +185,11 @@ export interface RegionVote {
148
185
  * a MULTI-topic query structurally cannot. Absent for an ordinary
149
186
  * per-region vote, where the merged span already is the truth. */
150
187
  parts?: readonly (readonly [number, number])[];
188
+ /** Carried through from {@link Region.corroborating}: this vote's evidence
189
+ * is a stored form the query's fold SPLIT, not a structure the query wove.
190
+ * Votes are what the pool sees (regions are not), so the flag has to
191
+ * travel with the vote for the root election to honour it. */
192
+ corroborating?: boolean;
151
193
  }
152
194
  /** The structural gate that first decided an {@link edgeAncestors} climb was
153
195
  * saturated (an abstention, not a discriminative conclusion) — pure
@@ -225,7 +225,37 @@ export class RaBitQuantizer {
225
225
  const lut = q.qlut;
226
226
  let dot = 0;
227
227
  let popcount = 0;
228
- for (let p = 0; p < nb; p++) {
228
+ // THE INNERMOST LOOP OF SEARCH. Profiled on the trained store: 170 ANN
229
+ // queries scan 8,702,005 slots, and this estimate — inlined by V8 into
230
+ // IvfIndex.scanClusters, which is why it does not appear separately — was
231
+ // 21% of all inference CPU.
232
+ //
233
+ // The `dot` half is an irreducible data-dependent LUT probe per byte. The
234
+ // `popcount` half is not: it is the same sign-bit count `codeDistanceBytes`
235
+ // below already folds into 32-bit words ("~4x fewer loop iterations"), and
236
+ // that reasoning applies verbatim here. Four bytes are packed into one
237
+ // word and popcounted with the standard SWAR reduction, while the four LUT
238
+ // probes are issued together so their loads overlap instead of serialising
239
+ // behind the popcount.
240
+ //
241
+ // BIT-IDENTICAL, not an approximation: popcount over four bytes equals the
242
+ // sum of their individual popcounts, and the LUT terms are added in the
243
+ // same order at the same indices. Verified by direct comparison, and the
244
+ // 445 suite plus the battery's answers are unchanged.
245
+ let p = 0;
246
+ for (const n4 = nb & ~3; p < n4; p += 4) {
247
+ const o = byteOffset + p;
248
+ const b0 = codeBytes[o], b1 = codeBytes[o + 1];
249
+ const b2 = codeBytes[o + 2], b3 = codeBytes[o + 3];
250
+ dot += lut[(p << 8) + b0] + lut[((p + 1) << 8) + b1] +
251
+ lut[((p + 2) << 8) + b2] + lut[((p + 3) << 8) + b3];
252
+ let x = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
253
+ x -= (x >>> 1) & 0x55555555;
254
+ x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
255
+ x = (x + (x >>> 4)) & 0x0f0f0f0f;
256
+ popcount += Math.imul(x, 0x01010101) >>> 24;
257
+ }
258
+ for (; p < nb; p++) {
229
259
  const b = codeBytes[byteOffset + p];
230
260
  dot += lut[(p << 8) + b];
231
261
  popcount += POPCOUNT8[b];
@@ -24,8 +24,18 @@ export interface Space {
24
24
  /** Bind one vector into a seat — the elementary half of fold. Used to index an
25
25
  * episode from either side and to pour a partner into a form's halo. */
26
26
  export declare const bindSeat: (space: Space, v: Vec, seat: number) => Vec;
27
+ /** The positional coordinate of child `index` inside a node of `size` items.
28
+ *
29
+ * Positions are anchored at BOTH ends of the full keyring: the left half uses
30
+ * the lowest seats and the right half uses the highest seats. Growing a node
31
+ * at one edge therefore preserves the coordinates anchored at the other edge,
32
+ * while every position remains injective as long as
33
+ * `0 <= index < size <= seatCount`. The helper is shared by perception and by
34
+ * synthetic/canonical folds so there is exactly one structural algebra. */
35
+ export declare function twoEndedSeat(seatCount: number, size: number, index: number): number;
27
36
  /** The company signature of node `id` — the halo's pour unit (see above). */
28
37
  export declare function companySignature(space: Space, id: number): Vec;
29
38
  /** fold — combine ordered children into one gist.
30
- * Each child is turned with its seat's own key, superposed, normalized. */
39
+ * Each child is turned in the same two-ended coordinate frame used by the
40
+ * perception tree, then superposed and normalized. */
31
41
  export declare function fold(space: Space, kids: Vec[]): Vec;
package/dist/src/sema.js CHANGED
@@ -10,6 +10,18 @@ let _foldBuf = null;
10
10
  /** Bind one vector into a seat — the elementary half of fold. Used to index an
11
11
  * episode from either side and to pour a partner into a form's halo. */
12
12
  export const bindSeat = (space, v, seat) => permute(v, space.seats[seat].fwd);
13
+ /** The positional coordinate of child `index` inside a node of `size` items.
14
+ *
15
+ * Positions are anchored at BOTH ends of the full keyring: the left half uses
16
+ * the lowest seats and the right half uses the highest seats. Growing a node
17
+ * at one edge therefore preserves the coordinates anchored at the other edge,
18
+ * while every position remains injective as long as
19
+ * `0 <= index < size <= seatCount`. The helper is shared by perception and by
20
+ * synthetic/canonical folds so there is exactly one structural algebra. */
21
+ export function twoEndedSeat(seatCount, size, index) {
22
+ const front = (size + 1) >> 1;
23
+ return index < front ? index : seatCount - size + index;
24
+ }
13
25
  // ── Company signatures ──────────────────────────────────────────────────
14
26
  //
15
27
  // A halo is a superposition of EPISODE SIGNATURES: it answers "who does this
@@ -45,7 +57,8 @@ export function companySignature(space, id) {
45
57
  return v;
46
58
  }
47
59
  /** fold — combine ordered children into one gist.
48
- * Each child is turned with its seat's own key, superposed, normalized. */
60
+ * Each child is turned in the same two-ended coordinate frame used by the
61
+ * perception tree, then superposed and normalized. */
49
62
  export function fold(space, kids) {
50
63
  if (kids.length > space.seats.length) {
51
64
  throw new Error(`fold: ${kids.length} children but the keyring has only ${space.seats.length} seats`);
@@ -56,7 +69,8 @@ export function fold(space, kids) {
56
69
  }
57
70
  const buf = _foldBuf;
58
71
  for (let i = 0; i < kids.length; i++) {
59
- permuteInto(buf, kids[i], space.seats[i].fwd);
72
+ const slot = twoEndedSeat(space.seats.length, kids.length, i);
73
+ permuteInto(buf, kids[i], space.seats[slot].fwd);
60
74
  addInto(out, buf);
61
75
  }
62
76
  return normalize(out);