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