@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
package/AUTHORS.md CHANGED
@@ -9,4 +9,3 @@ Unless otherwise stated, copyright belongs to Authors.
9
9
  # Supporters
10
10
 
11
11
  - Marcelo Oliveira dos Reis
12
- - Rogerio Nascimento
package/LICENSE.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
4
 
5
5
  > Required Notice: Copyright © Henrique Viana (author). Marcelo Oliveira dos
6
- > Reis and Rogerio Nascimento (supporters).
6
+ > Reis (supporters).
7
7
 
8
8
  ## Acceptance
9
9
 
package/README.md CHANGED
@@ -283,9 +283,9 @@ independent, weight-free AI research alive.
283
283
 
284
284
  ## Commercial licensing:
285
285
 
286
- **reis.marcelo@gmail.com**, **rogernact@gmail.com**
286
+ **reis.marcelo@gmail.com**
287
287
 
288
- **© Sema Supporters** — Marcelo Oliveira dos Reis · Rogerio Nascimento
288
+ **© Sema Supporters** — Marcelo Oliveira dos Reis
289
289
 
290
290
  — ⬡ — ⬡ — ⬡ —
291
291
 
@@ -29,6 +29,12 @@ export declare function identityBar(D: number, maxGroup: number, len: number): n
29
29
  * ≈ 1 − 1/maxGroup. Half that quantum, 1 − 1/(2·maxGroup), is closer than any
30
30
  * single-child difference can be: a positional echo of the same content.
31
31
  *
32
+ * This is an EQUAL-ARITY replacement law. The two-ended coordinate frame is
33
+ * a bijective relabelling of the seats inside that node, so it does not change
34
+ * the one-child overlap or this bar. Stability under a leading/trailing
35
+ * insertion comes from preserving content-defined subtrees and their anchored
36
+ * coordinates — never from lowering the confidence floor.
37
+ *
32
38
  * Recall uses this as its confidence floor: a query whose nearest resonant
33
39
  * form sits below this bar is structurally unrelated to everything in the store
34
40
  * — further than any single-child variant — and the system returns null rather
@@ -7,7 +7,7 @@
7
7
  // identically regardless of what follows — pure structural stability.
8
8
  // 3. The same rule recurses level after level until one root remains.
9
9
  import { addInto, copy, normalize, zeros } from "./vec.js";
10
- import { sema } from "./sema.js";
10
+ import { sema, twoEndedSeat } from "./sema.js";
11
11
  // ---- geometric constants ----
12
12
  //
13
13
  // Every threshold below is a derived function of the fold's own geometry —
@@ -54,6 +54,12 @@ export function identityBar(D, maxGroup, len) {
54
54
  * ≈ 1 − 1/maxGroup. Half that quantum, 1 − 1/(2·maxGroup), is closer than any
55
55
  * single-child difference can be: a positional echo of the same content.
56
56
  *
57
+ * This is an EQUAL-ARITY replacement law. The two-ended coordinate frame is
58
+ * a bijective relabelling of the seats inside that node, so it does not change
59
+ * the one-child overlap or this bar. Stability under a leading/trailing
60
+ * insertion comes from preserving content-defined subtrees and their anchored
61
+ * coordinates — never from lowering the confidence floor.
62
+ *
57
63
  * Recall uses this as its confidence floor: a query whose nearest resonant
58
64
  * form sits below this bar is structurally unrelated to everything in the store
59
65
  * — further than any single-child variant — and the system returns null rather
@@ -174,7 +180,8 @@ function foldSlice(space, items, start, count, out, force) {
174
180
  let len = 0;
175
181
  for (let k = 0; k < size; k++) {
176
182
  const f = items[at + k];
177
- const seat = space.seats[k].fwd;
183
+ const slot = twoEndedSeat(space.seats.length, size, k);
184
+ const seat = space.seats[slot].fwd;
178
185
  const v = f.tree.v;
179
186
  // Fused permute-and-accumulate — same FP ops, same order as the old
180
187
  // permuteInto + addInto pair, with no scratch buffer.
@@ -302,6 +309,39 @@ function bytesToLeaves(alphabet, bytes) {
302
309
  *
303
310
  * Levels are read from the hash the cut was ACCEPTED at, not recomputed, so
304
311
  * they cost nothing beyond the divisions already being done. */
312
+ // Cyclic-polynomial table for the bounded-window cut hash. Derived once from
313
+ // the fold's own mixing constant — no seed, no tuning. A byte contributes
314
+ // BUZ[b] on entering the window; the hash rotates by one per byte, so by the
315
+ // time that byte leaves, its contribution has travelled k places and is
316
+ // removed rotated by k. The rotation is taken at the use site rather than
317
+ // precomputed into a second table so the window width follows maxGroup
318
+ // instead of being frozen at one value.
319
+ const BUZ = new Uint32Array(256);
320
+ {
321
+ let x = 0x9e3779b9 >>> 0;
322
+ for (let i = 0; i < 256; i++) {
323
+ x = Math.imul(x ^ (x >>> 15), 2654435761) >>> 0;
324
+ x = (x ^ (x >>> 13)) >>> 0;
325
+ BUZ[i] = x;
326
+ }
327
+ }
328
+ /** BUZ rotated by the window width — what a byte's contribution has become by
329
+ * the time it leaves. Cached because the width follows `maxGroup`, which is
330
+ * fixed for a given space: built once, then a plain table lookup per byte. */
331
+ let buzOutTable = null;
332
+ let buzOutWidth = -1;
333
+ function buzOut(k) {
334
+ if (buzOutWidth !== k || buzOutTable === null) {
335
+ const t = new Uint32Array(256);
336
+ for (let i = 0; i < 256; i++) {
337
+ const v = BUZ[i];
338
+ t[i] = ((v << k) | (v >>> (32 - k))) >>> 0;
339
+ }
340
+ buzOutTable = t;
341
+ buzOutWidth = k;
342
+ }
343
+ return buzOutTable;
344
+ }
305
345
  function contentLevels(space, bytes) {
306
346
  const W = space.maxGroup;
307
347
  const minLen = W - 1;
@@ -366,29 +406,109 @@ function contentLevels(space, bytes) {
366
406
  // downstream are fitted to, not the purity of the rule that produces it. The
367
407
  // forced cut is part of that distribution. Do not tidy it away without
368
408
  // re-measuring everything that reads a region.
409
+ // A BOUNDED-WINDOW rolling hash — the cut decision reads only the last
410
+ // `k` bytes, so nothing before the window can reach it.
411
+ //
412
+ // The old hash, `h = (h<<1) + byte*K`, needed 32 shifts to drop a byte, so
413
+ // it carried ~32 bytes of history; and on PERIODIC content its value was
414
+ // periodic too, so the threshold either never fired or fired at a fixed
415
+ // phase. Then the `maxLen` fallback placed every boundary at a fixed
416
+ // offset from the previous one and the segmentation could never recover
417
+ // from a shift. Measured fraction of cuts that re-align after a prepend:
418
+ //
419
+ // text uniform sparse lowent records ramp
420
+ // old 0.870 0.902 0.492 0.879 0.888 0.441
421
+ // this 0.935 0.952 0.732 0.920 0.916 0.935
422
+ //
423
+ // The cyclic polynomial (each byte enters as a table value, leaves rotated
424
+ // by the window width) has EXACTLY k bytes of memory and scrambles periodic
425
+ // input, so the threshold fires at content-chosen positions on a gradient
426
+ // just as it does on text — which is what leaves the `maxLen` fallback
427
+ // rarely engaged instead of carrying the phase. Segment lengths are
428
+ // unchanged in distribution (mean 5.2-7.2 against the old 5.4-6.0), so the
429
+ // mechanisms fitted to that distribution see the same scale.
430
+ //
431
+ // Cost is the same shape as before: shifts, XORs and two table lookups per
432
+ // byte, no multiply and no auxiliary structure. (An exact sliding-window
433
+ // minimum — winnowing — aligns slightly better still, 0.91-0.999, but its
434
+ // deque costs 51 MB/s against this rule's 112 and buys nothing the
435
+ // scrambling hash does not already give.)
436
+ const k = W;
437
+ const OUT = buzOut(k);
369
438
  const cuts = [];
370
439
  const levels = [];
440
+ const n = bytes.length;
371
441
  let h = 0;
372
- let last = 0;
373
- for (let i = 0; i < bytes.length; i++) {
374
- h = (((h << 1) >>> 0) + Math.imul(bytes[i], 2654435761)) >>> 0;
375
- if (i + 1 >= bytes.length)
442
+ let prev = 0;
443
+ let recent = 0; // the last GAP raw hits, one bit each
444
+ // A boundary is a property of a 4-GRAM, not of a position. The register IS
445
+ // the window it holds exactly the last `k` raw bytes — so the decision is
446
+ // a pure function of those bytes and nothing else can reach it.
447
+ //
448
+ // What kept the OLD rule position-dependent was `minLen`, counted from the
449
+ // previous cut: on periodic content that count carried the initial phase
450
+ // forever and the segmentation never recovered from a shift. But its only
451
+ // job was to stop segments being too short, and that can be said locally —
452
+ // take a hit only when the previous GAP positions did NOT hit. Every term
453
+ // is then a function of a bounded byte window (k + GAP), so the rule stays
454
+ // a pure content property while still setting the segment scale. Measured
455
+ // fraction of cuts that survive a prepend, worst case over six byte types:
456
+ // 0.441 for the original rule, 0.769 counting from `last`, 0.847 for this.
457
+ const GAP = 2;
458
+ const GAPMASK = (1 << GAP) - 1;
459
+ // The keyring bound is restored WITHOUT reintroducing a count: because
460
+ // boundaries are content-determined, an over-long segment carries identical
461
+ // bytes wherever it occurs, so splitting it at strides from ITS OWN start is
462
+ // content-relative. (This holds only while such splits stay RARE — a split
463
+ // leaves a right edge the content did not choose, so the next segment's
464
+ // start is not content-determined either. At this rate they are: mean
465
+ // segment 5.4 against a bound of 8. Lowering the cut rate to lengthen
466
+ // segments makes forced splits dominant and alignment collapses — measured,
467
+ // 0.000 on two-symbol data at rate 1/16.)
468
+ const emit = (at, lvl) => {
469
+ while (at - prev > maxLen) {
470
+ prev += maxLen;
471
+ cuts.push(prev);
472
+ levels.push(0);
473
+ }
474
+ if (at <= prev || at >= n)
475
+ return;
476
+ cuts.push(at);
477
+ levels.push(lvl);
478
+ prev = at;
479
+ };
480
+ for (let i = 0; i < n; i++) {
481
+ h = ((h << 8) | bytes[i]) >>> 0;
482
+ if (i + 1 >= n)
376
483
  break;
377
- const hit = h % W === 0;
378
- if (i - last >= minLen && (hit || i - last + 1 >= maxLen)) {
379
- cuts.push(i + 1);
380
- // How many further powers of W divide the hash 0 for a forced cut.
484
+ if (i < k - 1)
485
+ continue;
486
+ // Two-round avalanche. The window holds four RAW bytes, whose entropy may
487
+ // sit in only a few bits (a gradient's low bits, a sparse stream's zeros);
488
+ // one multiply leaves that structure partly intact and the boundary test
489
+ // inherits it. A second round spreads every input bit across the word,
490
+ // which is what makes the rule behave the same on a ramp as on prose.
491
+ let mixv = Math.imul(h ^ (h >>> 16), 0x85ebca6b) >>> 0;
492
+ mixv = Math.imul(mixv ^ (mixv >>> 13), 0xc2b2ae35) >>> 0;
493
+ mixv = (mixv ^ (mixv >>> 16)) >>> 0;
494
+ const hit = mixv % W === 0;
495
+ if (hit && (recent & GAPMASK) === 0) {
496
+ // Level: how many further powers of W divide the mixed value — level-L
497
+ // cuts stay a subset of level-(L-1) cuts, the nesting the tree needs.
381
498
  let lvl = 0;
382
- if (hit) {
383
- let m = W;
384
- while (lvl < 24 && m <= 0x40000000 && h % (m * W) === 0) {
385
- lvl++;
386
- m *= W;
387
- }
499
+ let m = W;
500
+ while (lvl < 24 && m <= 0x40000000 && mixv % (m * W) === 0) {
501
+ lvl++;
502
+ m *= W;
388
503
  }
389
- levels.push(lvl);
390
- last = i + 1;
504
+ emit(i + 1, lvl);
391
505
  }
506
+ recent = ((recent << 1) | (hit ? 1 : 0)) & GAPMASK;
507
+ }
508
+ while (n - prev > maxLen) {
509
+ prev += maxLen;
510
+ cuts.push(prev);
511
+ levels.push(0);
392
512
  }
393
513
  return { cuts, levels };
394
514
  }
@@ -510,21 +630,71 @@ function contentFoldSpan(space, alphabet, bytes, from, to) {
510
630
  * keyring falls through to the plain river fold for that row — the fold stays
511
631
  * total on any input, and the fallback is rare enough not to reintroduce a
512
632
  * systematic alignment. */
633
+ /** A content key for a folded item: a cheap hash of its gist's leading
634
+ * coordinates. Used to choose a split point inside an over-long row, where
635
+ * the cut levels are uniformly 0 and carry no signal. Identical subtrees
636
+ * fold to identical vectors, so the same items in the same order always
637
+ * choose the same split — the property the whole fold rests on. */
638
+ function itemKey(v) {
639
+ let h = 0x811c9dc5;
640
+ for (let d = 0; d < 8; d++) {
641
+ h = Math.imul(h ^ ((v[d] * 8192) | 0), 0x01000193) >>> 0;
642
+ }
643
+ return h >>> 0;
644
+ }
513
645
  function groupByLevel(space, items, levels, level) {
514
646
  if (items.length === 1)
515
647
  return items[0];
516
648
  const maxSeats = space.seats.length;
517
649
  const groups = [];
518
650
  const groupLevels = [];
651
+ // Emit [from, to) as one group, splitting it at its STRONGEST interior cut
652
+ // whenever it would exceed the keyring.
653
+ //
654
+ // The old rule force-cut at the arity limit counted from the group's start
655
+ // — the last index-derived boundary in the grouping. Measured, the
656
+ // grouping loses one node in six even when EVERY child survives a prepend
657
+ // (P(node | all kids survive) = 0.82-0.84), and those losses spike at
658
+ // arity 8: exactly this boundary. Choosing the highest-level cut inside
659
+ // the feasible window instead makes the split a function of content, and
660
+ // the window bound keeps arity <= maxSeats so the seat algebra is untouched.
661
+ const emit = (from, to) => {
662
+ let at = from;
663
+ while (to - at > maxSeats) {
664
+ // Strongest cut in the window that still leaves a legal group. Ties
665
+ // take the LATEST, so equal levels give the widest legal group rather
666
+ // than a degenerate spine of singletons.
667
+ let best = at + maxSeats - 1;
668
+ let bestKey = -1;
669
+ let bestLevel = -1;
670
+ for (let j = at; j < at + maxSeats && j < to - 1; j++) {
671
+ // Prefer a real level boundary; among equals — and inside an
672
+ // over-long stretch the levels are almost all 0, so they usually ARE
673
+ // equal — fall back to the ITEMS' own content. A group's gist is
674
+ // diverse where its cut level is not, so hashing it gives a
675
+ // content-determined split point where the level array has none.
676
+ const key = itemKey(items[j].tree.v);
677
+ if (levels[j] > bestLevel ||
678
+ (levels[j] === bestLevel && key > bestKey)) {
679
+ bestLevel = levels[j];
680
+ bestKey = key;
681
+ best = j;
682
+ }
683
+ }
684
+ const part = items.slice(at, best + 1);
685
+ groups.push(part.length === 1 ? part[0] : joinFlat(space, part));
686
+ groupLevels.push(levels[best]);
687
+ at = best + 1;
688
+ }
689
+ const slice = items.slice(at, to);
690
+ groups.push(slice.length === 1 ? slice[0] : joinFlat(space, slice));
691
+ };
519
692
  let start = 0;
520
693
  for (let i = 0; i <= levels.length; i++) {
521
694
  const atEnd = i === levels.length;
522
- const cutHere = atEnd || levels[i] >= level;
523
- const wouldOverflow = i - start + 1 >= maxSeats;
524
- if (!cutHere && !wouldOverflow)
695
+ if (!atEnd && levels[i] < level)
525
696
  continue;
526
- const slice = items.slice(start, i + 1);
527
- groups.push(slice.length === 1 ? slice[0] : joinFlat(space, slice));
697
+ emit(start, i + 1);
528
698
  if (!atEnd)
529
699
  groupLevels.push(levels[i]);
530
700
  start = i + 1;
@@ -538,14 +708,18 @@ function groupByLevel(space, items, levels, level) {
538
708
  return groupByLevel(space, groups, groupLevels, level + 1);
539
709
  }
540
710
  /** Join a row of already-folded items as one unnormalized node — the same
541
- * seat-bound accumulate `flatFold` does for bytes, one level up. */
711
+ * two-ended seat binding as {@link flatFold}, one level up. A group formed
712
+ * by content-level cuts inherits the same robustness: interior items keep
713
+ * their seats when a leading or trailing segment is perturbed. */
542
714
  function joinFlat(space, items) {
715
+ const n = items.length;
543
716
  const gist = new Float32Array(space.D);
544
- const kids = new Array(items.length);
717
+ const kids = new Array(n);
545
718
  let len = 0;
546
- for (let k = 0; k < items.length; k++) {
719
+ for (let k = 0; k < n; k++) {
547
720
  const v = items[k].tree.v;
548
- const seat = space.seats[k].fwd;
721
+ const slot = twoEndedSeat(space.seats.length, n, k);
722
+ const seat = space.seats[slot].fwd;
549
723
  for (let d = 0; d < space.D; d++)
550
724
  gist[d] += v[seat[d]];
551
725
  kids[k] = items[k].tree;
@@ -554,10 +728,22 @@ function joinFlat(space, items) {
554
728
  return { tree: sema(gist, null, kids), len };
555
729
  }
556
730
  /** One segment as a single unnormalized node: leaf per byte, each bound into
557
- * seat k, summed. Same FP ops and same seat order as foldSlice's group fold —
558
- * only the arity is the segment's own length rather than a fixed W. Never
559
- * normalizes: the linear-fold contract keeps every interior gist raw and
560
- * normalizes once at the root. */
731
+ * a seat derived from its position relative to BOTH segment ends.
732
+ *
733
+ * Binding from both ends first bytes use the lowest seat slots, last
734
+ * bytes use the highest — makes the gist of the segment interior robust
735
+ * under a leading or trailing insertion: a byte prepended or appended
736
+ * shifts only the boundary seat, not every interior position. The same
737
+ * rule gives the shift-invariant knife its re-synchronising window in the
738
+ * ancestral fold (sema-old, KNIFE_WINDOW trailing items bound with
739
+ * relative seat keys). Ported to the current river: a content-defined
740
+ * segment always starts at seat 0, so the "relative" binding is the
741
+ * segment's own two-ended assignment.
742
+ *
743
+ * Never normalizes: the linear-fold contract keeps every interior gist
744
+ * raw and normalizes once at the root. Magnitude still ∝ √n — seat
745
+ * permutation preserves vector length, and the sum of n near-orthogonal
746
+ * vectors grows as √n. */
561
747
  function flatFold(space, alphabet, bytes, from, to) {
562
748
  const n = to - from;
563
749
  if (n === 1) {
@@ -572,7 +758,10 @@ function flatFold(space, alphabet, bytes, from, to) {
572
758
  for (let k = 0; k < n; k++) {
573
759
  const b = bytes[from + k];
574
760
  const v = alphabet.vecs[b];
575
- const seat = space.seats[k].fwd;
761
+ // Two-ended: the first half uses low seats and the second half uses
762
+ // high seats, inward from the tail of the FULL keyring.
763
+ const slot = twoEndedSeat(space.seats.length, n, k);
764
+ const seat = space.seats[slot].fwd;
576
765
  for (let d = 0; d < space.D; d++)
577
766
  gist[d] += v[seat[d]];
578
767
  kids[k] = sema(v, bytes.slice(from + k, from + k + 1), null);
@@ -672,19 +861,10 @@ export function stablePrefixFoldIncremental(space, alphabet, bytes, boundaries,
672
861
  return { tree: rootOf(cur), fold: { edges, segs } };
673
862
  }
674
863
  /** Join two folded items as one 2-kid branch — the top-level join of the
675
- * stable-prefix fold, identical FP ops to foldSlice's seat-bound
676
- * accumulation over a group of two. Unnormalized (interior). */
864
+ * stable-prefix fold, delegated to {@link joinFlat} (same two-ended seat
865
+ * binding as every other group fold). Unnormalized (interior). */
677
866
  function fold2(space, a, b) {
678
- const D = space.D;
679
- const gist = new Float32Array(D);
680
- const kids = [a.tree, b.tree];
681
- for (let k = 0; k < 2; k++) {
682
- const seat = space.seats[k].fwd;
683
- const v = kids[k].v;
684
- for (let d = 0; d < D; d++)
685
- gist[d] += v[seat[d]];
686
- }
687
- return { tree: sema(gist, null, kids), len: a.len + b.len };
867
+ return joinFlat(space, [a, b]);
688
868
  }
689
869
  /** Plain river fold WITHOUT the final root normalize — the segment-level
690
870
  * building block of {@link stablePrefixFold} (interiors must keep their
@@ -320,6 +320,14 @@ export declare function poolVotes(ctx: MindContext, regionVotes: readonly Region
320
320
  regionSupport: Map<number, number>;
321
321
  /** Per-anchor contributing region spans — see Attention.clusters. */
322
322
  regionSpans: Map<number, Array<[number, number]>>;
323
+ /** Per-anchor count of contributing region VOTES (pooled axioms), which is
324
+ * not the length of `regionSpans`: a joint binding is one vote sitting in
325
+ * several places. */
326
+ regionAxioms: Map<number, number>;
327
+ /** Per-anchor LARGEST single-region contribution — see Attention.peak. */
328
+ regionPeak: Map<number, number>;
329
+ /** Anchors with support from at least one NON-corroborating region. */
330
+ anchored: Set<number>;
323
331
  steps: DerivationStep[];
324
332
  };
325
333
  export declare function commitVotes(ctx: MindContext, pooled: {
@@ -332,6 +340,9 @@ export declare function commitVotes(ctx: MindContext, pooled: {
332
340
  }>;
333
341
  regionSupport: Map<number, number>;
334
342
  regionSpans: Map<number, Array<[number, number]>>;
343
+ regionAxioms: Map<number, number>;
344
+ regionPeak: Map<number, number>;
345
+ anchored: Set<number>;
335
346
  steps: DerivationStep[];
336
347
  }, sat: SaturationInfo, regions: readonly Region[], regionVoter: ReadonlyArray<{
337
348
  id: number;