@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.
- package/AUTHORS.md +0 -1
- package/LICENSE.md +1 -1
- package/README.md +2 -2
- package/dist/src/geometry.d.ts +6 -0
- package/dist/src/geometry.js +224 -44
- package/dist/src/mind/attention.d.ts +11 -0
- package/dist/src/mind/attention.js +344 -13
- package/dist/src/mind/junction.js +18 -2
- package/dist/src/mind/match.d.ts +11 -0
- package/dist/src/mind/match.js +13 -2
- package/dist/src/mind/mechanisms/cast.js +366 -34
- package/dist/src/mind/mechanisms/confluence.js +17 -1
- package/dist/src/mind/mechanisms/recall.js +17 -3
- package/dist/src/mind/pipeline-mechanism.d.ts +4 -0
- package/dist/src/mind/pipeline-mechanism.js +96 -40
- package/dist/src/mind/pipeline.js +31 -3
- package/dist/src/mind/reasoning.d.ts +4 -2
- package/dist/src/mind/reasoning.js +29 -4
- package/dist/src/mind/recognition.js +67 -2
- package/dist/src/mind/resonance.d.ts +14 -2
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/types.d.ts +43 -1
- package/dist/src/rabitq-ivf/src/rabitq.js +31 -1
- package/dist/src/sema.d.ts +11 -1
- package/dist/src/sema.js +16 -2
- package/dist/src/store.d.ts +64 -1
- package/dist/src/store.js +107 -8
- package/index.html +2 -3
- package/package.json +1 -1
- package/src/geometry.ts +231 -43
- package/src/mind/attention.ts +366 -15
- package/src/mind/junction.ts +18 -2
- package/src/mind/match.ts +18 -2
- package/src/mind/mechanisms/cast.ts +376 -43
- package/src/mind/mechanisms/confluence.ts +16 -1
- package/src/mind/mechanisms/recall.ts +17 -2
- package/src/mind/pipeline-mechanism.ts +96 -36
- package/src/mind/pipeline.ts +33 -3
- package/src/mind/reasoning.ts +31 -4
- package/src/mind/recognition.ts +65 -2
- package/src/mind/resonance.ts +0 -0
- package/src/mind/types.ts +43 -1
- package/src/rabitq-ivf/src/rabitq.ts +31 -1
- package/src/sema.ts +21 -2
- package/src/store.ts +106 -5
- package/test/00-extract.test.mjs +28 -0
- package/test/15-decomposition-gap.test.mjs +0 -0
- package/test/24-generalization.test.mjs +67 -19
- package/test/29-counterfactual.test.mjs +106 -42
- package/test/33-multi-candidate.test.mjs +56 -12
- package/test/53-cross-region-probe-instrumentation.test.mjs +16 -1
- package/test/63-fold-invariants.test.mjs +489 -0
- package/test/64-two-ended-thresholds.test.mjs +76 -0
- package/test/65-ann-recall.test.mjs +331 -0
package/AUTHORS.md
CHANGED
package/LICENSE.md
CHANGED
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
|
|
286
|
+
**reis.marcelo@gmail.com**
|
|
287
287
|
|
|
288
|
-
**© Sema Supporters** — Marcelo Oliveira dos Reis
|
|
288
|
+
**© Sema Supporters** — Marcelo Oliveira dos Reis
|
|
289
289
|
|
|
290
290
|
— ⬡ — ⬡ — ⬡ —
|
|
291
291
|
|
package/dist/src/geometry.d.ts
CHANGED
|
@@ -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
|
package/dist/src/geometry.js
CHANGED
|
@@ -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
|
|
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
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
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
|
-
|
|
523
|
-
const wouldOverflow = i - start + 1 >= maxSeats;
|
|
524
|
-
if (!cutHere && !wouldOverflow)
|
|
695
|
+
if (!atEnd && levels[i] < level)
|
|
525
696
|
continue;
|
|
526
|
-
|
|
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
|
-
*
|
|
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(
|
|
717
|
+
const kids = new Array(n);
|
|
545
718
|
let len = 0;
|
|
546
|
-
for (let k = 0; k <
|
|
719
|
+
for (let k = 0; k < n; k++) {
|
|
547
720
|
const v = items[k].tree.v;
|
|
548
|
-
const
|
|
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
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
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
|
-
|
|
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,
|
|
676
|
-
*
|
|
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
|
-
|
|
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;
|