@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
|
@@ -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
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
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
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
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
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
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++)
|
|
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
|
-
|
|
364
|
-
|
|
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
|
|
package/src/mind/pipeline.ts
CHANGED
|
@@ -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
|
package/src/mind/reasoning.ts
CHANGED
|
@@ -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). `
|
|
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
|
}
|
package/src/mind/recognition.ts
CHANGED
|
@@ -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
|
-
|
|
447
|
-
|
|
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
|
|
package/src/mind/resonance.ts
CHANGED
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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);
|