@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
@@ -199,6 +199,10 @@ export interface WeaveInfo {
199
199
  vote: number;
200
200
  ctx: Uint8Array;
201
201
  runs: GradedRun[];
202
+ /** The query span the CLIMB elected this anchor from — its evidence,
203
+ * independent of any literal run alignment (see Attention.start/end). */
204
+ start: number;
205
+ end: number;
202
206
  }>;
203
207
  /** Weighted depth at each query byte — sum of alignment weights.
204
208
  * `depth[i]` is the total evidence that byte i is shared among the
@@ -235,27 +239,31 @@ function computeWeave(
235
239
  const askerBytes = query.length -
236
240
  ctx.answeredSpans.reduce((n, [start, end]) => n + end - start, 0);
237
241
  const capBytes = askerBytes * quantum;
238
- // EXCLUSIVITY IS ARBITRATED BY THE CLIMB'S VOTE ORDER, DELIBERATELY. A query
239
- // byte can only be independent evidence for ONE point, so points are built in
240
- // ranked order and each new point's runs are trimmed against every point
241
- // already accepted; a point left with no run of a full quantum drops out of
242
- // the weave.
242
+ // RUNS ARE NOT TRIMMED AGAINST EACH OTHER. A point keeps every byte it
243
+ // aligned; exclusivity is a property of STRUCTURES (see "one place, one
244
+ // structure" below), not of individual query bytes.
243
245
  //
244
- // That reads like first-come-wins a point that merely ranked higher taking
245
- // a span from the point that actually explains it — and arbitrating by LOCAL
246
- // evidence instead (ownership of each byte to the longest covering run, then
247
- // the heavier weight, then rank) was implemented and MEASURED: test/29 went
248
- // 9/2 to 7/4, and the new failures name the reason. CAST requires the weave
249
- // to touch a COMMITTED point of attention ("2 aligned structure(s), but none
250
- // is one of the climb's 1 committed root(s)"), and it was precisely the vote
251
- // order that kept the committed root's own point alive in the weave. Local
252
- // run length knows nothing about what the climb settled on, so it evicted the
253
- // root's evidence and left CAST refusing on its own consistency check.
246
+ // This weave used to build points in the climb's vote order and cut each new
247
+ // point's runs against every point already accepted. It is worth recording
248
+ // what that cost, because the cut was invisible: it did not just resolve
249
+ // ties, it silently DECIDED downstream schemas. A point's `runs[0]` the
250
+ // run three CAST branches read as "the filler", "the seat", "the name" was
251
+ // whichever run happened to survive the cut, so those schemas were reading an
252
+ // elimination order as though it were evidence, and the query's own bytes
253
+ // were truncated on the way ("Shakespeare" surviving as "Shakes"). Each
254
+ // consumer now derives its own reading from the runs (cast.ts: `fillerRun`
255
+ // clips at the seat, redirection scans for the naming run, entry counts own
256
+ // bytes and the climb's dispersion), and with those in place removing the cut
257
+ // costs nothing — measured, the same 442 tests pass either way.
254
258
  //
255
- // So the vote order here is not an accident of construction it is what
256
- // holds the weave and the climb to the same conclusion. Weave-local
257
- // measures decide what is FRAME inside the weave (see the frame gates in
258
- // cast.ts); which structures are in the weave at all stays the climb's call.
259
+ // What the vote order was RIGHT about is kept: which structures belong in the
260
+ // weave is the climb's call, not a local run measure. Arbitrating byte
261
+ // ownership by local evidence instead (longest covering run, then weight,
262
+ // then rank) was implemented and MEASURED, and it evicted the committed
263
+ // root's own evidence — CAST then refused on its own consistency check ("2
264
+ // aligned structure(s), but none is one of the climb's 1 committed root(s)"),
265
+ // test/29 going 9/2 to 7/4. Weave-local measures decide what is FRAME inside
266
+ // the weave (see the frame gates in cast.ts); membership stays the climb's.
259
267
  //
260
268
  // TWO PASSES. `depth` — how much of the weave agrees on each query byte, and
261
269
  // therefore what counts as FRAME — must be the whole weave's, not "whatever
@@ -352,30 +360,38 @@ function computeWeave(
352
360
  },
353
361
  );
354
362
  if (raw.length === 0) continue;
363
+ // DEPTH COUNTS STRUCTURES, NOT WEIGHT. The frame test is
364
+ // `dominates(depth[i], aligned)` — "more than half the weave shares this
365
+ // byte" — and `aligned` is a COUNT of points. Accumulating graded
366
+ // alignment WEIGHT here compared weight-mass against a cardinality: two
367
+ // different dimensions, meaningful only while truncation happened to keep
368
+ // points.length small and weights near 1.
369
+ //
370
+ // Measured (test/29 C2, only truncation toggled): 9 candidates collapse to
371
+ // 2 points and 29/42 bytes read FRAME; without truncation 9 points survive
372
+ // and only 6/42 do. The elimination was SETTING the frame threshold, so
373
+ // every attempt to change run ownership inverted the frame reading and
374
+ // lost the same 10 tests (442 -> 432, twice, for opposite designs).
375
+ //
376
+ // Counting distinct covering candidates restores the documented meaning
377
+ // exactly and makes the comparison like-for-like, which decouples the
378
+ // frame gate from however many points survive.
379
+ const covered = new Uint8Array(query.length);
355
380
  for (const r of raw) {
356
- for (let i = r.qs; i < r.qe; i++) depth[i] += r.weight;
381
+ for (let i = r.qs; i < r.qe; i++) {
382
+ if (!covered[i]) {
383
+ covered[i] = 1;
384
+ depth[i] += 1;
385
+ }
386
+ }
357
387
  }
358
388
  cands.push({ cand, ctxBytes, raw });
359
389
  }
360
390
  for (const { cand, ctxBytes, raw } of cands) {
361
391
  const free: GradedRun[] = [];
362
392
  for (const r of raw) {
363
- let { qs, qe, cs, weight } = r;
364
- for (const p of points) {
365
- for (const o of p.runs) {
366
- if (qs >= qe) break;
367
- if (o.qe <= qs || o.qs >= qe) continue;
368
- const left = Math.max(0, o.qs - qs);
369
- const right = Math.max(0, qe - o.qe);
370
- if (left >= right) qe = qs + left;
371
- else {
372
- cs += qe - right - qs;
373
- qs = qe - right;
374
- }
375
- }
376
- }
377
- if (qe - qs >= Math.min(quantum, ctxBytes.length)) {
378
- free.push({ qs, qe, cs, weight });
393
+ if (r.qe - r.qs >= Math.min(quantum, ctxBytes.length)) {
394
+ free.push({ ...r });
379
395
  }
380
396
  }
381
397
  if (free.length > 0) {
@@ -384,6 +400,8 @@ function computeWeave(
384
400
  vote: cand.vote,
385
401
  ctx: ctxBytes,
386
402
  runs: free,
403
+ start: cand.start,
404
+ end: cand.end,
387
405
  };
388
406
  byAnchor.set(cand.anchor, pt);
389
407
  points.push(pt);
@@ -486,6 +504,8 @@ function computeWeave(
486
504
  vote: cand.vote,
487
505
  ctx: ctxBytes,
488
506
  runs: [run],
507
+ start: cand.start,
508
+ end: cand.end,
489
509
  };
490
510
  byAnchor.set(cand.anchor, made);
491
511
  points.push(made);
@@ -497,6 +517,46 @@ function computeWeave(
497
517
  }
498
518
  }
499
519
  }
520
+ // ONE PLACE, ONE STRUCTURE. A stored sentence and the entity it names are
521
+ // not two independent structures when the query's evidence for them is the
522
+ // same bytes — they are one place read at two grains, and admitting both
523
+ // lets a nest of containing sentences outvote the entity the query actually
524
+ // named. Measured on test/29 C2 ("How is Shakespeare like Leonardo da
525
+ // Vinci?"): the five sentences that merely CONTAIN the two names align the
526
+ // same q6-18 / q23-41 the names do, and comparison ended up seated on a
527
+ // 49-byte sentence instead of the 17-byte entity.
528
+ //
529
+ // A point earns its own place in the weave the same way a second point earns
530
+ // CAST's entry: at least one perception quantum of query bytes no
531
+ // better-voted point already explains. Points arrive in the climb's vote
532
+ // order, which is the arbiter this file already trusts for what belongs in
533
+ // the weave; unlike run trimming, nothing is CUT here — a point keeps every
534
+ // byte it aligned or it is not a separate structure at all.
535
+ const coveredOf = (p: WeaveInfo["points"][number]): Set<number> => {
536
+ const set = new Set<number>();
537
+ for (const r of p.runs) for (let i = r.qs; i < r.qe; i++) set.add(i);
538
+ return set;
539
+ };
540
+ const kept: WeaveInfo["points"] = [];
541
+ const keptCover: Array<Set<number>> = [];
542
+ for (const p of points) {
543
+ const cov = coveredOf(p);
544
+ let redundant = false;
545
+ for (const other of keptCover) {
546
+ let own = 0;
547
+ for (const i of cov) if (!other.has(i)) own++;
548
+ if (own < quantum) {
549
+ redundant = true;
550
+ break;
551
+ }
552
+ }
553
+ if (!redundant) {
554
+ kept.push(p);
555
+ keptCover.push(cov);
556
+ }
557
+ }
558
+ points.length = 0;
559
+ points.push(...kept);
500
560
  return { points, depth };
501
561
  }
502
562
 
@@ -12,11 +12,12 @@
12
12
  import type { MindContext } from "./types.js";
13
13
  import { PASS, STEP } from "./graph-search.js";
14
14
  import type { ComputedSpan } from "../extension.js";
15
- import { gistOf, resolve } from "./primitives.js";
15
+ import { gistOf, read, resolve } from "./primitives.js";
16
16
  import { recognise } from "./recognition.js";
17
17
  import { fuseAttention, reason } from "./reasoning.js";
18
18
  import { unexplainedSpans } from "./rationale.js";
19
19
  import { rItem } from "./trace.js";
20
+ import { hubBound } from "./traverse.js";
20
21
  import { type PipelineMechanism, Precomputed } from "./pipeline-mechanism.js";
21
22
  import { coverMechanism } from "./mechanisms/cover.js";
22
23
  import { castMechanism } from "./mechanisms/cast.js";
@@ -367,12 +368,41 @@ export async function think(
367
368
  // already a trained form's own continuation, reached through an identity
368
369
  // claim about the query, so a multi-hop pivot could only chain past the
369
370
  // fact that produced it (see MechanismResult.complete).
371
+ // WHAT THE MECHANISM WITHHELD, NOT WHAT IT VOICED. A pivot must not
372
+ // re-open content a grounding deliberately kept out: comparison cites two
373
+ // analogs and refuses their own downstream facts, so pivoting into one is
374
+ // the mechanism's own refusal undone one step later (test/29 C2 pivoted
375
+ // through `speare` — a stored fragment of the analog `William Shakespeare`
376
+ // — into the biography CAST had declined).
377
+ //
378
+ // Reading the used anchors' OWN bytes here says something stronger and
379
+ // wrong: that nothing INSIDE what was voiced may be pivoted through. A
380
+ // comparison's seat sentence legitimately contains further terms with
381
+ // their own unrelated facts, and C3 pins exactly that — `Mona Lisa`, inside
382
+ // the voiced seat `The Mona Lisa was painted by Leonardo da Vinci.`, leads
383
+ // on to `Mona Lisa hangs in the Louvre`, which is about neither analog.
384
+ // The withheld content is the used anchors' CONTINUATIONS, so that is what
385
+ // the containment rule reads: `speare` is contained in `Shakespeare wrote
386
+ // 39 plays` and stays refused, while `Mona Lisa` appears in no withheld
387
+ // continuation and the genuine further hop fires.
388
+ //
389
+ // Only a mechanism carrying its own `used` set (cast/join) gets this: there
390
+ // `preConsumed` is a deliberate, short list of the anchors the answer
391
+ // speaks for, so the fan-out is bounded. For every other provenance
392
+ // `preConsumed` is derived by re-recognising the answer — "everything in
393
+ // it", not "what it voiced" — and a containment rule over that would
394
+ // suppress every pivot the answer legitimately contains.
395
+ const voiced = (provenance === "cast" || provenance === "join")
396
+ ? [...castUsed].flatMap((id) =>
397
+ ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n))
398
+ )
399
+ : [];
370
400
  const reasoned = decided.complete ? answer : meter
371
401
  ? await meter.time(
372
402
  "reason",
373
- () => reason(ctx, query, answer, preConsumed, pre),
403
+ () => reason(ctx, query, answer, preConsumed, pre, voiced),
374
404
  )
375
- : await reason(ctx, query, answer, preConsumed, pre);
405
+ : await reason(ctx, query, answer, preConsumed, pre, voiced);
376
406
 
377
407
  // Fuse only when the query has a genuine REMAINDER no mechanism's
378
408
  // structural evidence touched at all. `decided.accounted` alone
@@ -5,7 +5,7 @@
5
5
  import { rItem, rNode } from "./trace.js";
6
6
 
7
7
  import { bytesEqual, indexOf } from "../bytes.js";
8
- import type { MindContext } from "./types.js";
8
+ import type { Attention, MindContext } from "./types.js";
9
9
  import { resolve } from "./primitives.js";
10
10
  import { corpusN, hubBound } from "./traverse.js";
11
11
  import { follow, haloSiblings, project } from "./match.js";
@@ -31,7 +31,9 @@ export function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean {
31
31
  * Pivots on the longest unconsumed learnt context each answer contains,
32
32
  * then follows the pivot's continuation to the next fact. Repeats up
33
33
  * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
34
- * spoken for by the grounding stage (cover/extract/CAST). `pre` is the
34
+ * spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
35
+ * the BYTES of the anchors a mechanism declared it voiced (its `used` set),
36
+ * when it declared one — see the pivot's own containment rule. `pre` is the
35
37
  * response's shared pre-computation — the post-grounding stages read the
36
38
  * same container the mechanisms did. */
37
39
  export async function reason(
@@ -40,6 +42,7 @@ export async function reason(
40
42
  answer: Uint8Array,
41
43
  preConsumed: ReadonlySet<number>,
42
44
  pre: Precomputed,
45
+ voiced: readonly Uint8Array[] = [],
43
46
  ): Promise<Uint8Array> {
44
47
  // Echo guard: a query that is ITSELF a learnt continuation (some context's
45
48
  // answer) is being asked back at the system — hopping forward from it would
@@ -132,7 +135,7 @@ export async function reason(
132
135
 
133
136
  // Pivot: find the longest unconsumed learnt context the answer contains.
134
137
  consumeAll(curId);
135
- const pivot = await pivotInto(ctx, cur, consumed);
138
+ const pivot = await pivotInto(ctx, cur, consumed, voiced);
136
139
  if (pivot === null) break;
137
140
 
138
141
  const fc = await follow(ctx, pivot, qv);
@@ -201,8 +204,32 @@ export async function fuseAttention(
201
204
  // here, since it is an absolute ln(N)-scaled quantity (a genuine root on
202
205
  // a large store can score BELOW its own floor while a coincidental echo
203
206
  // on a small one scores comfortably above its own, smaller, floor).
207
+ //
208
+ // Breadth alone is not enough when primary is a pure COMPUTATION. The ALU
209
+ // answers "2+2 equals what?" with 4, and the store's own arithmetic table
210
+ // then supplies a lone root — an exemplar like "1+2" — whose breadth
211
+ // dominates because it is corroborated by the computation's OWN bytes.
212
+ // Fusing it projected that exemplar's continuation and the bridge voiced
213
+ // "4+3" (test/11 seed 99). A second point of attention must stand on
214
+ // evidence that is structurally SEPARATE from primary's: at least one
215
+ // perceptual quantum of query between them, the same separation
216
+ // countClusters uses to tell independent evidence neighbourhoods apart.
217
+ // Not a score, and not a tuned bar — the fold's own quantum.
218
+ //
219
+ // With no primarySpans (the caller did not resolve them) every span
220
+ // vacuously qualifies, preserving the original behaviour exactly.
221
+ const quantum = ctx.space.maxGroup;
222
+ const independentOfPrimary = (root: Attention): boolean =>
223
+ primarySpans.every(([s, e]) => {
224
+ const gap = root.end <= s
225
+ ? s - root.end
226
+ : e <= root.start
227
+ ? root.start - e
228
+ : 0;
229
+ return gap >= quantum;
230
+ });
204
231
  const lonePromotes = unclimbed && forest.length === 1 &&
205
- forest[0].breadth > 0.5;
232
+ forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
206
233
  if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
207
234
  return primary;
208
235
  }
@@ -242,6 +242,56 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
242
242
  if (eRight !== null) emit(start, end - k, eRight);
243
243
  }
244
244
  }
245
+ // A trained form embedded at this span's left edge, past the
246
+ // chunk-scale bound above. The loop above probes exactly this — trim
247
+ // k leading bytes, verify the remainder is a stored branch — but only
248
+ // for spans of at most W². A turn prefixed with a connective is
249
+ // turn-scale, so it never qualified.
250
+ //
251
+ // Widening that loop's SIZE bound is what reopens test/46's
252
+ // root-scale false positive. Widening only its LEFT trim, to a
253
+ // bounded W offsets, does not: every candidate is still verified by
254
+ // exact content addressing (the leaf-id run must BE a stored branch),
255
+ // and the result always ends where this span ends, so it can never
256
+ // introduce the smaller-subtree duplicate that regression was about.
257
+ //
258
+ // This replaces an assumption that no longer holds — that such a
259
+ // form's left edge must be a cut the fold itself drew. It held while
260
+ // cuts had long memory and a turn boundary reliably produced one; a
261
+ // bounded-window rule re-synchronises a byte or two INTO the turn
262
+ // instead, so the edge itself is often not a cut ("And " ends at 65,
263
+ // and the fold's nearest cuts are 61 and 67).
264
+ // No leaf-id prefilter here, unlike the loop above: a leaf id is the
265
+ // LONGEST known leaf at a position, so the run itself is context
266
+ // sensitive — measured, the embedded copy of a trained form yields a
267
+ // different run from the standalone one and findBranch misses even
268
+ // though the bytes resolve exactly (span [65,94): findBranch null,
269
+ // resolve 91). With only W candidates the exact fold is affordable,
270
+ // and it is the stronger evidence anyway: if it resolves, these exact
271
+ // bytes ARE a stored node.
272
+ for (let k = 1; k <= W && start + k < end - 1; k++) {
273
+ const eLeft = resolve(ctx, bytes.subarray(start + k, end));
274
+ if (eLeft !== null) emit(start + k, end, eLeft);
275
+ }
276
+ // THE SAME SEARCH ON THE OTHER EDGE. Everything above trims from the
277
+ // LEFT and keeps this span's END fixed, so a stored form was findable
278
+ // only when it ENDED where a fold node ends. Measured on a 12-context
279
+ // store, probing for a trained 47-byte sentence wrapped in filler:
280
+ // 1-4 bytes of LEFT padding kept it recognisable, while ONE byte of
281
+ // right padding lost it. That asymmetry was never argued for — the
282
+ // reasoning above is about a form's left edge landing on a cut, and it
283
+ // says nothing about which side the noise is on.
284
+ //
285
+ // The stated hazard for widening this search is test/46's root-scale
286
+ // false positive, and it is a hazard of the SIZE bound, not of the
287
+ // direction: like its mirror this loop is bounded to W offsets and
288
+ // every candidate is verified by exact content addressing, so it can
289
+ // only ever emit spans that ARE stored nodes. Measured: neutral on
290
+ // the suite, and the right-padded cases above become recognisable.
291
+ for (let k = 1; k <= W && start < end - k - 1; k++) {
292
+ const eRight = resolve(ctx, bytes.subarray(start, end - k));
293
+ if (eRight !== null) emit(start, end - k, eRight);
294
+ }
245
295
  // A REAL extra word at the left edge (a discourse connective like
246
296
  // "And " prepended to a follow-up turn — not boundary noise, actual
247
297
  // content the injected canonicalizer has no equivalence for) shows
@@ -384,12 +434,14 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
384
434
  }
385
435
 
386
436
  const chunkEnd = new Uint32Array(bytes.length);
437
+ const chunkSpan = new Uint32Array(bytes.length);
387
438
  const sorted = [...starts].sort((a, b) => a - b);
388
439
  for (let si = 0; si < sorted.length; si++) {
389
440
  const chunkStart = sorted[si];
390
441
  const chunkLimit = si + 1 < sorted.length ? sorted[si + 1] : bytes.length;
391
442
  for (let p = chunkStart; p < chunkLimit; p++) {
392
443
  chunkEnd[p] = chunkLimit;
444
+ chunkSpan[p] = chunkLimit - chunkStart;
393
445
  }
394
446
  }
395
447
 
@@ -443,8 +495,19 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
443
495
  if (starts.has(p)) {
444
496
  tryChain(p, chainReach(W), true); // boundary start — full reach
445
497
  } else {
446
- const limit = chunkEnd[p] + W;
447
- tryChain(p, Math.min(limit - p, chainReach(W)), false);
498
+ // THE INTERIOR BUDGET IS "ONE CHUNK PLUS A QUANTUM", MEASURED FROM THE
499
+ // CHAIN'S OWN START. It used to be `chunkEnd[p] + W - p`, which counts
500
+ // from the chunk's END, so the reach an interior chain gets depended on
501
+ // WHERE INSIDE its chunk it happened to begin: measured on a composed
502
+ // answer, a chunk spanning [0,6) gave offset 1 nine ids and offset 4
503
+ // only six — and the 9-id trained form `Mona Lisa` starting at 4 died
504
+ // three ids short of itself. The same form one byte earlier would have
505
+ // been found. That is the position artifact this module has been
506
+ // removing everywhere else, not a budget.
507
+ //
508
+ // Stated from `p` the trust is unchanged — a chain may span its own
509
+ // chunk and one quantum beyond it — and it no longer varies with phase.
510
+ tryChain(p, Math.min(chunkSpan[p] + W, chainReach(W)), false);
448
511
  }
449
512
  }
450
513
 
Binary file
package/src/mind/types.ts CHANGED
@@ -101,9 +101,28 @@ export interface Attention {
101
101
  anchor: number;
102
102
  /** IDF-weighted consensus vote — the strength that orders points. */
103
103
  vote: number;
104
- /** The union of the query byte-spans whose evidence supports this point. */
104
+ /** The query span of the point's STRONGEST contributing region the argmax
105
+ * over `wFocus` (see `peak`, which is that same region's weight), NOT a
106
+ * union or hull over every region that voted. Measured on test/24 3.2: the
107
+ * winning anchor's span here was 2 bytes while its contributing regions
108
+ * together covered most of the query. It is the minimal honest statement
109
+ * of what a grounding on this anchor rests on, and recall accounts exactly
110
+ * it for that reason — widening it to every contributing region made recall
111
+ * out-bid mechanisms that had genuinely explained more (a GENERATED list
112
+ * degraded to a RETRIEVED one, test/24 3.2 and test/04 1). */
105
113
  start: number;
106
114
  end: number;
115
+ /** The largest SINGLE region's contribution to this point's pooled vote —
116
+ * the weight of the very region `start`..`end` delimits (both are the
117
+ * argmax over `wFocus`), so the two fields describe one region: its
118
+ * strength and its place.
119
+ * `vote` is a sum over every region that agreed, so it grows with how many
120
+ * places corroborated; `peak` is what the strongest one of them said on its
121
+ * own. A consumer holding this point to consensusFloor(N) — a bar that
122
+ * prices ONE region's maximally-discriminative evidence — must read `peak`,
123
+ * not `vote`: six scaffolding regions summing past the floor is not the
124
+ * same claim as one region clearing it. */
125
+ peak: number;
107
126
  /** SCALE-INVARIANT confidence: the fraction of the query's OWN regions
108
127
  * whose evidence this point accounts for (Σ RegionVote.absorbed among
109
128
  * its contributors, over the query's total region count) — read PER-
@@ -158,6 +177,24 @@ export interface Region {
158
177
  * resonance score is byte-overlap, evidence only in excess of its best
159
178
  * rival conclusion. */
160
179
  known: boolean;
180
+ /** The stored node this region's bytes ARE, when the region came from a
181
+ * recognised SITE — content-addressed and exact, so the climb has no
182
+ * reason to re-derive it approximately. A perceived sub-tree leaves this
183
+ * undefined; chunks get the same thing from `canonicalChunkId`. */
184
+ id?: number;
185
+ /** EVIDENCE, NOT A POINT OF ATTENTION. True for a region the query's own
186
+ * fold never produced — a stored form that a content-defined cut SPLIT,
187
+ * recovered by sliding-window lookup in collectRegions. The store
188
+ * guarantees such a form is addressable (canonicalWindows interns both
189
+ * lengths), so it may corroborate an anchor's vote; but the query did not
190
+ * weave it as an independent structure, so it must not make the query look
191
+ * like it holds one more point of attention than it does — it is kept out
192
+ * of the root-cut distribution and out of the breadth ratio (see
193
+ * poolVotes/commitVotes). Absent/false for every region from the fold.
194
+ * (Flagging these `chunk: true` instead is REFUTED — a chunk is a
195
+ * smallest unit the FOLD produced, and claiming first-class unit status
196
+ * for an assembled span cost 5 tests.) */
197
+ corroborating?: boolean;
161
198
  }
162
199
 
163
200
  /** Per-region vote data from the consensus climb's resonance pass. */
@@ -189,6 +226,11 @@ export interface RegionVote {
189
226
  * a MULTI-topic query structurally cannot. Absent for an ordinary
190
227
  * per-region vote, where the merged span already is the truth. */
191
228
  parts?: readonly (readonly [number, number])[];
229
+ /** Carried through from {@link Region.corroborating}: this vote's evidence
230
+ * is a stored form the query's fold SPLIT, not a structure the query wove.
231
+ * Votes are what the pool sees (regions are not), so the flag has to
232
+ * travel with the vote for the root election to honour it. */
233
+ corroborating?: boolean;
192
234
  }
193
235
 
194
236
  /** The structural gate that first decided an {@link edgeAncestors} climb was
@@ -249,7 +249,37 @@ export class RaBitQuantizer {
249
249
  const lut = q.qlut;
250
250
  let dot = 0;
251
251
  let popcount = 0;
252
- for (let p = 0; p < nb; p++) {
252
+ // THE INNERMOST LOOP OF SEARCH. Profiled on the trained store: 170 ANN
253
+ // queries scan 8,702,005 slots, and this estimate — inlined by V8 into
254
+ // IvfIndex.scanClusters, which is why it does not appear separately — was
255
+ // 21% of all inference CPU.
256
+ //
257
+ // The `dot` half is an irreducible data-dependent LUT probe per byte. The
258
+ // `popcount` half is not: it is the same sign-bit count `codeDistanceBytes`
259
+ // below already folds into 32-bit words ("~4x fewer loop iterations"), and
260
+ // that reasoning applies verbatim here. Four bytes are packed into one
261
+ // word and popcounted with the standard SWAR reduction, while the four LUT
262
+ // probes are issued together so their loads overlap instead of serialising
263
+ // behind the popcount.
264
+ //
265
+ // BIT-IDENTICAL, not an approximation: popcount over four bytes equals the
266
+ // sum of their individual popcounts, and the LUT terms are added in the
267
+ // same order at the same indices. Verified by direct comparison, and the
268
+ // 445 suite plus the battery's answers are unchanged.
269
+ let p = 0;
270
+ for (const n4 = nb & ~3; p < n4; p += 4) {
271
+ const o = byteOffset + p;
272
+ const b0 = codeBytes[o], b1 = codeBytes[o + 1];
273
+ const b2 = codeBytes[o + 2], b3 = codeBytes[o + 3];
274
+ dot += lut[(p << 8) + b0] + lut[((p + 1) << 8) + b1] +
275
+ lut[((p + 2) << 8) + b2] + lut[((p + 3) << 8) + b3];
276
+ let x = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
277
+ x -= (x >>> 1) & 0x55555555;
278
+ x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
279
+ x = (x + (x >>> 4)) & 0x0f0f0f0f;
280
+ popcount += Math.imul(x, 0x01010101) >>> 24;
281
+ }
282
+ for (; p < nb; p++) {
253
283
  const b = codeBytes[byteOffset + p];
254
284
  dot += lut[(p << 8) + b];
255
285
  popcount += POPCOUNT8[b];
package/src/sema.ts CHANGED
@@ -47,6 +47,23 @@ let _foldBuf: Vec | null = null;
47
47
  export const bindSeat = (space: Space, v: Vec, seat: number): Vec =>
48
48
  permute(v, space.seats[seat].fwd);
49
49
 
50
+ /** The positional coordinate of child `index` inside a node of `size` items.
51
+ *
52
+ * Positions are anchored at BOTH ends of the full keyring: the left half uses
53
+ * the lowest seats and the right half uses the highest seats. Growing a node
54
+ * at one edge therefore preserves the coordinates anchored at the other edge,
55
+ * while every position remains injective as long as
56
+ * `0 <= index < size <= seatCount`. The helper is shared by perception and by
57
+ * synthetic/canonical folds so there is exactly one structural algebra. */
58
+ export function twoEndedSeat(
59
+ seatCount: number,
60
+ size: number,
61
+ index: number,
62
+ ): number {
63
+ const front = (size + 1) >> 1;
64
+ return index < front ? index : seatCount - size + index;
65
+ }
66
+
50
67
  // ── Company signatures ──────────────────────────────────────────────────
51
68
  //
52
69
  // A halo is a superposition of EPISODE SIGNATURES: it answers "who does this
@@ -82,7 +99,8 @@ export function companySignature(space: Space, id: number): Vec {
82
99
  }
83
100
 
84
101
  /** fold — combine ordered children into one gist.
85
- * Each child is turned with its seat's own key, superposed, normalized. */
102
+ * Each child is turned in the same two-ended coordinate frame used by the
103
+ * perception tree, then superposed and normalized. */
86
104
  export function fold(space: Space, kids: Vec[]): Vec {
87
105
  if (kids.length > space.seats.length) {
88
106
  throw new Error(
@@ -95,7 +113,8 @@ export function fold(space: Space, kids: Vec[]): Vec {
95
113
  }
96
114
  const buf = _foldBuf;
97
115
  for (let i = 0; i < kids.length; i++) {
98
- permuteInto(buf, kids[i], space.seats[i].fwd);
116
+ const slot = twoEndedSeat(space.seats.length, kids.length, i);
117
+ permuteInto(buf, kids[i], space.seats[slot].fwd);
99
118
  addInto(out, buf);
100
119
  }
101
120
  return normalize(out);