@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
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// gate = the frame gate below + analogyStrength, projection = insert / project /
|
|
12
12
|
// juxtapose.
|
|
13
13
|
import { read } from "../primitives.js";
|
|
14
|
-
import { argmaxBy, corpusN, hubBound } from "../traverse.js";
|
|
15
|
-
import { analogyStrength, follow, project, reverseContext, } from "../match.js";
|
|
14
|
+
import { argmaxBy, corpusN, edgeAncestors, hubBound, sharedReachMemo, } from "../traverse.js";
|
|
15
|
+
import { analogyStrength, follow, project, reverseContext, sharedFrameStrengthOf, } from "../match.js";
|
|
16
16
|
import { joinWithBridge } from "../resonance.js";
|
|
17
17
|
import { restatesQuery } from "../reasoning.js";
|
|
18
18
|
import { CONCEPT, STEP } from "../graph-search.js";
|
|
@@ -21,6 +21,7 @@ import { consensusFloor, dominates } from "../../geometry.js";
|
|
|
21
21
|
import { unexplainedLabel, unexplainedSpans, } from "../rationale.js";
|
|
22
22
|
import { rItem, rNode } from "../trace.js";
|
|
23
23
|
import { dismissedKnownContent } from "../bridge.js";
|
|
24
|
+
import { leafIdRun } from "../canonical.js";
|
|
24
25
|
// ── CAST gates ────────────────────────────────────────────────────────────
|
|
25
26
|
//
|
|
26
27
|
// The frame gate has TWO components, both derived from the weave itself:
|
|
@@ -173,7 +174,94 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
173
174
|
const weave = await pre.weave();
|
|
174
175
|
const points = weave.points;
|
|
175
176
|
const depth = weave.depth;
|
|
176
|
-
|
|
177
|
+
// CAST'S OWN SINGLE-VS-MULTI TEST, MEASURED FROM THE QUERY.
|
|
178
|
+
//
|
|
179
|
+
// `points.length >= 2` reads as "two structures to transfer between", but
|
|
180
|
+
// measured, it functions as "the query is about more than one thing" — and
|
|
181
|
+
// it only discriminates because the weave's exclusivity eliminates hard
|
|
182
|
+
// enough that a single-topic query cannot reach two points. The condition
|
|
183
|
+
// is carried by the elimination, not by anything CAST measures. Traced on
|
|
184
|
+
// test/24 3.1 ("the importance of gender equality in the workplace"): the
|
|
185
|
+
// climb is byte-identical either way (16 of 31 sub-regions, one context),
|
|
186
|
+
// and relaxing the weave alone makes CAST fire and answer about the 1992
|
|
187
|
+
// Dream Team.
|
|
188
|
+
//
|
|
189
|
+
// What actually separates 3.1 from a genuine comparison (test/29 C2, "How is
|
|
190
|
+
// Shakespeare like Leonardo da Vinci?") is CONTENT: C2's two points are
|
|
191
|
+
// evidenced by DIFFERENT query spans, while 3.1's extra points align to the
|
|
192
|
+
// same shared frame the first one already explains. So require two points
|
|
193
|
+
// that explain genuinely different parts of the query — a second point must
|
|
194
|
+
// contribute at least one perception quantum of query bytes the
|
|
195
|
+
// best-covered point does not. Derived from the runs themselves, order-free,
|
|
196
|
+
// and independent of how many points survived.
|
|
197
|
+
const coveredBy = (p) => {
|
|
198
|
+
const set = new Set();
|
|
199
|
+
for (const r of p.runs)
|
|
200
|
+
for (let i = r.qs; i < r.qe; i++)
|
|
201
|
+
set.add(i);
|
|
202
|
+
return set;
|
|
203
|
+
};
|
|
204
|
+
let widest = points[0];
|
|
205
|
+
let widestN = -1;
|
|
206
|
+
for (const p of points) {
|
|
207
|
+
const n = coveredBy(p).size;
|
|
208
|
+
if (n > widestN) {
|
|
209
|
+
widestN = n;
|
|
210
|
+
widest = p;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const widestSet = widest === undefined
|
|
214
|
+
? new Set()
|
|
215
|
+
: coveredBy(widest);
|
|
216
|
+
let distinct = points.length === 0 ? 0 : 1;
|
|
217
|
+
for (const p of points) {
|
|
218
|
+
if (p === widest)
|
|
219
|
+
continue;
|
|
220
|
+
let own = 0;
|
|
221
|
+
for (const i of coveredBy(p))
|
|
222
|
+
if (!widestSet.has(i))
|
|
223
|
+
own++;
|
|
224
|
+
if (own >= quantum) {
|
|
225
|
+
distinct = 2;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// THE CLIMB ANSWERS THE SAME QUESTION, AND IT ANSWERS IT ORDER-FREE. Runs
|
|
230
|
+
// are literal W-gram agreement, so two structures the query names in its own
|
|
231
|
+
// words can share no run at all: on `How is ice like steel?` the query's
|
|
232
|
+
// `ice` and the stored `Ice is cold` agree on nothing but the ` is `
|
|
233
|
+
// scaffolding `Steel is hard` also matches, and the run test above reads one
|
|
234
|
+
// topic. The climb had already read two — it elected `Ice is cold` from
|
|
235
|
+
// q4-9 and `Steel is hard` from q16-20, two disjoint places — and DISPERSION
|
|
236
|
+
// (Attention.clusters) is exactly that reading: not how much evidence, but
|
|
237
|
+
// how many separate places in the query corroborate it. Measured against
|
|
238
|
+
// the case this gate exists to refuse, test/24 3.1: a genuinely single-topic
|
|
239
|
+
// query reads clusters 1, while C1's single committed root reads 2.
|
|
240
|
+
//
|
|
241
|
+
// Either source is sufficient — bytes the other point does not explain, or
|
|
242
|
+
// places the climb found the query's evidence in — and neither is a count of
|
|
243
|
+
// weave survivors.
|
|
244
|
+
// Dispersion alone is a property of the QUERY, not of the pair being woven,
|
|
245
|
+
// so it is read together with the pair's own elected spans: two points count
|
|
246
|
+
// as two topics when the climb found the query dispersed AND it elected them
|
|
247
|
+
// from places at least a quantum apart. (Dispersion alone was measured and
|
|
248
|
+
// is too weak — it let CAST into test/33's near-tie and test/24's list
|
|
249
|
+
// skill, whose points the climb elects from the same place.)
|
|
250
|
+
const dispersed = roots.length >= MIN_WEAVE ||
|
|
251
|
+
roots.some((r) => r.clusters >= MIN_WEAVE);
|
|
252
|
+
const apart = points.some((a) => points.some((b) => a !== b &&
|
|
253
|
+
(b.start - a.end >= quantum || a.start - b.end >= quantum)));
|
|
254
|
+
// …and only where there is something left to transfer. When ONE point
|
|
255
|
+
// already explains the query down to the last quantum there is no analogy to
|
|
256
|
+
// draw — the query is that structure, restated or truncated — and the
|
|
257
|
+
// dispersion the climb reports is the SAME topic corroborated twice, not two
|
|
258
|
+
// topics. Measured on test/33's `steel is hard so steel is`, a prefix of one
|
|
259
|
+
// stored fact: its root disperses into 2 clusters purely because the fact
|
|
260
|
+
// repeats `steel is`, while that one point's runs cover all 25 query bytes.
|
|
261
|
+
const unexplained = query.length - widestN;
|
|
262
|
+
const aligned = distinct >= 2 || (dispersed && apart && unexplained >= quantum)
|
|
263
|
+
? points.length
|
|
264
|
+
: 1;
|
|
177
265
|
if (aligned < 2) {
|
|
178
266
|
return fail(`only ${aligned} structure(s) aligned across the query — CAST needs ` +
|
|
179
267
|
`at least two to transfer between`);
|
|
@@ -235,7 +323,39 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
235
323
|
});
|
|
236
324
|
return [];
|
|
237
325
|
}
|
|
238
|
-
|
|
326
|
+
// WOVEN — is anything actually brought TOGETHER? A run restating a site
|
|
327
|
+
// the query already contains is not, by itself, evidence of that; but TWO
|
|
328
|
+
// points restating DIFFERENT sites is exactly a comparison ("How is
|
|
329
|
+
// Michelangelo like Homer?" names both entities, recognition finds both,
|
|
330
|
+
// and the weave aligns each to its own stored structure). The escape
|
|
331
|
+
// clause alone called that unwoven — a reading that held only while
|
|
332
|
+
// recognition UNDER-reported sites, and test/29 A2 started failing the
|
|
333
|
+
// moment recognition's interior chains stopped dying mid-form.
|
|
334
|
+
//
|
|
335
|
+
// Both points must be evidenced in what the asker JUST SAID. A multi-turn
|
|
336
|
+
// query is the whole transcript, so the earlier turns' own questions are
|
|
337
|
+
// aligned points too — traced on test/48, the weave for `And what is the
|
|
338
|
+
// capital of Spain?` holds `What is the capital of France?` (runs q0-61,
|
|
339
|
+
// entirely inside the previous turn and its answer) beside the new question
|
|
340
|
+
// (q65-94). Two points, two named sites, and nothing woven at all: one of
|
|
341
|
+
// them is conversation history. The current turn is the bytes past the last
|
|
342
|
+
// answered span — the same `askerBytes` notion computeWeave prices its read
|
|
343
|
+
// budget with — so requiring both points to have evidence THERE separates a
|
|
344
|
+
// genuine two-place weave from a follow-up. Single-turn queries have no
|
|
345
|
+
// answered spans, so the current turn is the whole query and nothing changes.
|
|
346
|
+
const turnStart = ctx.answeredSpans.reduce((n, [, e]) => Math.max(n, e), 0);
|
|
347
|
+
const inTurn = points.filter((p) => p.runs.some((r) => r.qe > turnStart));
|
|
348
|
+
const siteAt = (r) => pre.rec.sites.findIndex((s) => r.qs >= s.start && r.qe <= s.end);
|
|
349
|
+
const namedSites = new Set();
|
|
350
|
+
for (const p of inTurn) {
|
|
351
|
+
for (const r of p.runs) {
|
|
352
|
+
const i = siteAt(r);
|
|
353
|
+
if (i >= 0)
|
|
354
|
+
namedSites.add(i);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const woven = points.some((p) => p.runs.some((r) => siteAt(r) < 0)) ||
|
|
358
|
+
(inTurn.length >= MIN_WEAVE && namedSites.size >= MIN_WEAVE);
|
|
239
359
|
if (!woven) {
|
|
240
360
|
return fail(`every aligned run restates a recognised query site — nothing was ` +
|
|
241
361
|
`actually WOVEN across structures, so there is nothing to transfer`);
|
|
@@ -278,11 +398,30 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
278
398
|
const lastRun = (p) => p.runs[p.runs.length - 1];
|
|
279
399
|
const qv = pre.guide;
|
|
280
400
|
// ── SUBSTITUTION ──────────────────────────────────────────────────
|
|
281
|
-
const fillerOf = (s) =>
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
401
|
+
const fillerOf = (s, r = s.runs[0]) => r.cs < quantum
|
|
402
|
+
? s.ctx.subarray(0, r.cs + (r.qe - r.qs))
|
|
403
|
+
: query.subarray(r.qs, r.qe);
|
|
404
|
+
// THE FILLER IS WHAT THE SUBJECT CONTRIBUTES BEFORE THE SEAT — CLIPPED HERE,
|
|
405
|
+
// NOT ARBITRATED BY RANK. A subject whose alignment runs INTO the seat span
|
|
406
|
+
// agrees with the displaced structure there; those shared bytes are frame,
|
|
407
|
+
// and only the part before the seat is the subject's own contribution.
|
|
408
|
+
// Reading `runs[0]` whole made this schema depend on the weave having
|
|
409
|
+
// already cut that overlap away for it: on `steel is frigid` the weave's
|
|
410
|
+
// exclusivity handed `steel is hard so steel is strong` the run q0-5
|
|
411
|
+
// (`steel`) only because the seat's point ranked higher and took q5-15
|
|
412
|
+
// first. Read without that cut the same run is q0-9 (`steel is `), it ends
|
|
413
|
+
// PAST the seat at q5, and substitution found no subject at all — a schema
|
|
414
|
+
// silently reading a global elimination order as if it were local evidence.
|
|
415
|
+
// Clipping at the seat derives the same span from the two points actually
|
|
416
|
+
// involved, so the reading no longer moves when the weave's order does.
|
|
417
|
+
const fillerRun = (s, at) => {
|
|
418
|
+
const r0 = s.runs[0];
|
|
419
|
+
if (r0.qs >= at)
|
|
420
|
+
return null;
|
|
421
|
+
const qe = Math.min(r0.qe, at);
|
|
422
|
+
return qe - r0.qs >= Math.min(quantum, s.ctx.length)
|
|
423
|
+
? (qe === r0.qe ? r0 : { ...r0, qe })
|
|
424
|
+
: null;
|
|
286
425
|
};
|
|
287
426
|
// The subject is the closest structure whose FILLER RUN precedes the seat.
|
|
288
427
|
// The gate is on `runs[0]` — the run `fillerOf` actually reads — not on the
|
|
@@ -297,9 +436,14 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
297
436
|
// analogy — pushed lastRun past the seat and no substitution fired at all.
|
|
298
437
|
// The ordering key follows the gate to the same run, so "closest preceding"
|
|
299
438
|
// still means closest by the evidence actually used.
|
|
300
|
-
const beforeOf = (p, r) => argmaxBy(points.
|
|
301
|
-
s
|
|
302
|
-
|
|
439
|
+
const beforeOf = (p, r) => argmaxBy(points.flatMap((s) => {
|
|
440
|
+
if (s === p)
|
|
441
|
+
return [];
|
|
442
|
+
const f = fillerRun(s, r.qs);
|
|
443
|
+
return f !== null && f.cs < quantum && usable(f.qs, f.qe)
|
|
444
|
+
? [{ point: s, run: f }]
|
|
445
|
+
: [];
|
|
446
|
+
}), (s) => s.run.qs, -Infinity, true)?.item;
|
|
303
447
|
const displacement = points
|
|
304
448
|
.map((p) => {
|
|
305
449
|
const r = p.runs[0];
|
|
@@ -309,8 +453,9 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
309
453
|
const before = beforeOf(p, r);
|
|
310
454
|
if (before === undefined)
|
|
311
455
|
return null;
|
|
312
|
-
if (r.cs > fillerOf(before).length + quantum)
|
|
456
|
+
if (r.cs > fillerOf(before.point, before.run).length + quantum) {
|
|
313
457
|
return null;
|
|
458
|
+
}
|
|
314
459
|
// SUBSTITUTION MUST ACTUALLY DISPLACE. The schema's premise is that the
|
|
315
460
|
// displaced structure's seat is held by something ELSE, which the
|
|
316
461
|
// subject then replaces. When the subject's filler already occurs in
|
|
@@ -322,8 +467,9 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
322
467
|
// produced `Michelangelo sculpted by Michelangelo.` — then outbid every
|
|
323
468
|
// honest candidate with it (test/29 A2). Byte containment, the same
|
|
324
469
|
// primitive the self-evidence and contradiction guards use.
|
|
325
|
-
if (indexOf(p.ctx, fillerOf(before), 0) >= 0)
|
|
470
|
+
if (indexOf(p.ctx, fillerOf(before.point, before.run), 0) >= 0) {
|
|
326
471
|
return null;
|
|
472
|
+
}
|
|
327
473
|
return { p, before, depth: p.ctx.length - r.cs };
|
|
328
474
|
})
|
|
329
475
|
.filter((c) => c !== null);
|
|
@@ -332,7 +478,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
332
478
|
const subj = picked?.item.before ?? null;
|
|
333
479
|
if (proj !== null && subj !== null) {
|
|
334
480
|
const seat = proj.runs[0];
|
|
335
|
-
const filler = fillerOf(subj);
|
|
481
|
+
const filler = fillerOf(subj.point, subj.run);
|
|
336
482
|
const tail = proj.ctx.subarray(seat.cs);
|
|
337
483
|
let answer = await joinWithBridge(ctx, filler, tail);
|
|
338
484
|
const fwd = await follow(ctx, proj.anchor, qv);
|
|
@@ -341,10 +487,10 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
341
487
|
answer = concat2(answer, fwd);
|
|
342
488
|
}
|
|
343
489
|
ctx.trace?.step("projectCounterfactual", [
|
|
344
|
-
rItem(filler, "filler", subj.anchor),
|
|
490
|
+
rItem(filler, "filler", subj.point.anchor),
|
|
345
491
|
rNode(ctx, proj.anchor, "displaced-structure"),
|
|
346
492
|
], [rItem(answer, "projection")], "transfer the displaced structure onto the subject filler (seat substitution)");
|
|
347
|
-
record(answer, "counterfactual substitution — the subject fills the analog's seat", new Set([subj.anchor, proj.anchor]),
|
|
493
|
+
record(answer, "counterfactual substitution — the subject fills the analog's seat", new Set([subj.point.anchor, proj.anchor]),
|
|
348
494
|
// The acts performed: one seat INSERT projection + one edge FOLLOW.
|
|
349
495
|
STEP + STEP,
|
|
350
496
|
// What substitution actually READ: the two points it transfers
|
|
@@ -352,10 +498,34 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
352
498
|
// structure whose seat it fills — not every OTHER point the weave
|
|
353
499
|
// happened to align (a third, unrelated point in the same weave
|
|
354
500
|
// contributes nothing to what substitution itself explains).
|
|
355
|
-
[...runSpans(subj), ...runSpans(proj)]);
|
|
501
|
+
[...runSpans(subj.point), ...runSpans(proj)]);
|
|
356
502
|
}
|
|
357
503
|
// ── REDIRECTION ────────────────────────────────────────────────────
|
|
358
|
-
|
|
504
|
+
// REDIRECTION IS ABOUT THE SUBSTITUTE THE QUERY NAMES, SO IT LOOKS FOR THE
|
|
505
|
+
// RUN THAT NAMES ONE. A structure is named when the query quotes it from
|
|
506
|
+
// its own opening bytes (`cs === 0`) — `…were Lyon?` against `Lyon is a city
|
|
507
|
+
// in France`. Reading that off `runs[0]` assumed the weave had already
|
|
508
|
+
// eliminated everything the point shares with the dominant, which is the
|
|
509
|
+
// elimination deciding the schema again: relaxed, the same point also aligns
|
|
510
|
+
// the query's trailing ` France` (cs 17, frame it shares with `what is the
|
|
511
|
+
// capital of France?`), that run sorts FIRST, and redirection stopped seeing
|
|
512
|
+
// a named substitute at all. Scanning the point's runs for the naming one
|
|
513
|
+
// is the same reading, taken from the runs rather than from their order, and
|
|
514
|
+
// "latest named" then means latest by the run actually relied on.
|
|
515
|
+
const named = points.flatMap((p) => {
|
|
516
|
+
const r = p.runs.find((r) => r.cs === 0 && usable(r.qs, r.qe));
|
|
517
|
+
return r !== undefined ? [{ point: p, run: r }] : [];
|
|
518
|
+
});
|
|
519
|
+
// …and it must be named AFTER what it displaces. Redirection replaces the
|
|
520
|
+
// ANSWER, so the substitute is the newest thing the query says — `…of France
|
|
521
|
+
// were Lyon?` names Lyon past everything the displaced structure aligned.
|
|
522
|
+
// The old `latest last run` reduce encoded this implicitly and only held
|
|
523
|
+
// while trimming kept the dominant's runs latest; stated on the naming run
|
|
524
|
+
// it is the same reading without that dependency. Measured on test/29 D1
|
|
525
|
+
// (`steel is frigid`), where the point with a naming run is the SUBJECT at
|
|
526
|
+
// q0-9, ahead of the dominant's q5-15: redirection must not fire, and
|
|
527
|
+
// substitution — which is what that shape is — keeps the case.
|
|
528
|
+
const last = argmaxBy(named.filter((n) => n.run.qs > lastRun(dominant).qs), (n) => n.run.qs, -Infinity, true)?.item;
|
|
359
529
|
// Displacement test, capped at the hub bound: a hub anchor can carry a
|
|
360
530
|
// corpus-sized fan-out, and each continuation costs a full byte
|
|
361
531
|
// reconstruction plus an O(|query|·|bytes|) scan. The first √N edges (the
|
|
@@ -364,16 +534,14 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
364
534
|
const domNext = ctx.store.nextFirst(dominant.anchor, hubBound(ctx));
|
|
365
535
|
const displaced = domNext
|
|
366
536
|
.every((n) => indexOf(query, read(ctx, n), 0) < 0);
|
|
367
|
-
if (last !== dominant &&
|
|
368
|
-
|
|
369
|
-
usable(last.runs[0].qs, last.runs[0].qe)) {
|
|
370
|
-
const g = await project(ctx, last.anchor, qv);
|
|
537
|
+
if (last !== undefined && last.point !== dominant && displaced) {
|
|
538
|
+
const g = await project(ctx, last.point.anchor, qv);
|
|
371
539
|
if (g !== null) {
|
|
372
540
|
ctx.trace?.step("projectCounterfactual", [
|
|
373
541
|
rNode(ctx, dominant.anchor, "displaced-structure"),
|
|
374
|
-
rNode(ctx, last.anchor, "substitute"),
|
|
542
|
+
rNode(ctx, last.point.anchor, "substitute"),
|
|
375
543
|
], [rItem(g, "projection")], "the substitute's own fact replaces the displaced structure's answer");
|
|
376
|
-
record(g, "counterfactual redirection — the named substitute's fact is followed", new Set([dominant.anchor, last.anchor]),
|
|
544
|
+
record(g, "counterfactual redirection — the named substitute's fact is followed", new Set([dominant.anchor, last.point.anchor]),
|
|
377
545
|
// One forward projection across the substitute's own fact.
|
|
378
546
|
STEP,
|
|
379
547
|
// What redirection READ: the displaced structure's own recognized
|
|
@@ -381,7 +549,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
381
549
|
// being overridden, it just doesn't answer from it) plus the named
|
|
382
550
|
// substitute's own aligned run — not every OTHER point the weave
|
|
383
551
|
// happened to align.
|
|
384
|
-
[...runSpans(dominant), ...runSpans(last)]);
|
|
552
|
+
[...runSpans(dominant), ...runSpans(last.point)]);
|
|
385
553
|
}
|
|
386
554
|
}
|
|
387
555
|
// ── COMPARISON ─────────────────────────────────────────────────────
|
|
@@ -408,7 +576,19 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
408
576
|
// distinction perception can make — the same quantum countClusters separates
|
|
409
577
|
// neighbourhoods by — so a context within one quantum of the query's length
|
|
410
578
|
// carries no independently perceivable unit beyond it and is the same scale.
|
|
411
|
-
|
|
579
|
+
//
|
|
580
|
+
// ONE QUANTUM OF EXCESS IS AN ABSOLUTE UNIT, AND SCALE IS NOT ABSOLUTE.
|
|
581
|
+
// `n - query.length < quantum` calls a 504-byte context the same scale as a
|
|
582
|
+
// 500-byte query while refusing a 47-byte context on a 42-byte one — the
|
|
583
|
+
// same 5 bytes, opposite verdicts, because the bar never looks at what it is
|
|
584
|
+
// measuring against. Measured on test/29 C3, whose query is C2's verbatim:
|
|
585
|
+
// the climb elects the exemplar SENTENCE (47) rather than the entity, five
|
|
586
|
+
// bytes past a 42-byte query, and comparison refused a pair it accepts at
|
|
587
|
+
// C2's grain. Read the excess against the query with `dominates` — the same
|
|
588
|
+
// half-dominance predicate this file uses for frame, and the one scale-free
|
|
589
|
+
// reading of "the seat sentence must not dominate the comparison" available
|
|
590
|
+
// without inventing a ratio.
|
|
591
|
+
const queryScale = (n) => !dominates(n - query.length, query.length);
|
|
412
592
|
const analogs = [];
|
|
413
593
|
for (const p of points) {
|
|
414
594
|
if (p === dominant)
|
|
@@ -492,8 +672,85 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
492
672
|
// content the query never asked about. Computed once here; both the
|
|
493
673
|
// hub fallback below and the comparison gate consume it.
|
|
494
674
|
const rootTrusted = roots.some((r) => r.vote >= consensusFloor(corpusN(ctx)));
|
|
675
|
+
// The context that ESTABLISHES a filler — the same reverse context, under
|
|
676
|
+
// the same naming test, `seatOfNode` uses to VOICE an analog (a predecessor
|
|
677
|
+
// whose bytes CONTAIN the node's: it names or describes it, rather than
|
|
678
|
+
// merely having preceded it somewhere). Memoised: the analogy loop below
|
|
679
|
+
// asks about the same dominant every time, and only ever asks at all when
|
|
680
|
+
// the cheap tiers already read zero.
|
|
681
|
+
// A NODE NOTHING ESTABLISHES IS ITS OWN ESTABLISHING CONTEXT — the same
|
|
682
|
+
// reading `seatOfNode` takes one gate up: a bare filler was learnt as some
|
|
683
|
+
// context's answer and has a predecessor that NAMES it, so no establishing
|
|
684
|
+
// predecessor means the node already IS a learnt context. Returning null
|
|
685
|
+
// there made the tier depend on both sides being elected at the same GRAIN:
|
|
686
|
+
// test/29 C2's climb elects the entity `Leonardo da Vinci` (established by
|
|
687
|
+
// `The Mona Lisa was painted by…`) and reads 0.371, while C3's identical
|
|
688
|
+
// query elects that sentence ITSELF for the same side, whose own
|
|
689
|
+
// predecessor establishes nothing — the tier read 0.000 and comparison
|
|
690
|
+
// never fired, on a pair that is strictly MORE explicit about its frame.
|
|
691
|
+
const estMemo = new Map();
|
|
692
|
+
const establishing = (id) => {
|
|
693
|
+
const hit = estMemo.get(id);
|
|
694
|
+
if (hit !== undefined)
|
|
695
|
+
return hit;
|
|
696
|
+
const own = read(ctx, id);
|
|
697
|
+
const rev = reverseContext(ctx, id, pre.guide);
|
|
698
|
+
const out = rev !== null && indexOf(rev, own, 0) >= 0 ? rev : own;
|
|
699
|
+
estMemo.set(id, out);
|
|
700
|
+
return out;
|
|
701
|
+
};
|
|
702
|
+
// COMPARISON VOICES WHAT IT COMPARED. When the frame tier decided the
|
|
703
|
+
// analogy, the two establishing contexts it read ARE the roles being
|
|
704
|
+
// compared, so the schema below voices those same bytes instead of
|
|
705
|
+
// re-deriving a seat that can land somewhere else entirely. Measured on
|
|
706
|
+
// test/29 C3: the dominant is the exemplar sentence `The Mona Lisa was
|
|
707
|
+
// painted by Leonardo da Vinci.`, nothing establishes it, so `seatOf`
|
|
708
|
+
// took its FORWARD continuation and voiced `Leonardo was a Renaissance
|
|
709
|
+
// polymath` — the analog's own biography, exactly what C2 pins comparison
|
|
710
|
+
// must never leak, from the branch whose own doc says forward completion
|
|
711
|
+
// is right for a DOMINANT (true when the dominant is a bare name whose
|
|
712
|
+
// continuation establishes it; false when it already IS the establishing
|
|
713
|
+
// context). Only frame-tier pairs are affected: a halo-tier analogy was
|
|
714
|
+
// never measured on these bytes and keeps the seat it always had.
|
|
715
|
+
const frameSeats = new Map();
|
|
495
716
|
for (const c of analogs) {
|
|
496
|
-
const
|
|
717
|
+
const ev = await analogyStrength(ctx, dominant.anchor, c.anchor);
|
|
718
|
+
let sim = ev.score;
|
|
719
|
+
const halo = ev.halo;
|
|
720
|
+
// ROLE IS ESTABLISHED BY CONTEXT, NOT BY A NAME. When neither halo tier
|
|
721
|
+
// fired and the two anchors' own bytes share no learnt frame either, the
|
|
722
|
+
// anchors are FILLERS — bare entity names — not the frame-bearing
|
|
723
|
+
// structures the tier is about. Read the tier on what establishes each
|
|
724
|
+
// one instead: the aligned point's own context (or, for a hop-reached
|
|
725
|
+
// candidate, the point whose continuation edge reached it — the same
|
|
726
|
+
// context `cmpAccounted` already prices as that hop's query evidence).
|
|
727
|
+
// Both are ALREADY IN HAND, so this costs no extra read.
|
|
728
|
+
//
|
|
729
|
+
// Measured on test/29's corpus: "Michelangelo" vs "Homer" reads 0.000
|
|
730
|
+
// while "The David was sculpted by Michelangelo." vs "The Iliad was
|
|
731
|
+
// written by Homer." reads 0.452 — and a context in a different frame
|
|
732
|
+
// ("Water boils at one hundred degrees.") still reads 0.000. The tier
|
|
733
|
+
// was never failing to discriminate; it was reading the fillers.
|
|
734
|
+
//
|
|
735
|
+
// Still the FRAME tier (`halo` stays false), so this evidence remains
|
|
736
|
+
// subject to the naming / trusted-root bar the comparison gate holds all
|
|
737
|
+
// frame evidence to — a wider READING of the same tier, not a new licence.
|
|
738
|
+
// Containment is excluded for the same reason the generator excludes it:
|
|
739
|
+
// a context that contains the other establishes nothing independent.
|
|
740
|
+
if (!halo && sim === 0) {
|
|
741
|
+
// For a hop-reached candidate the thing whose ROLE is in question is
|
|
742
|
+
// the point the query named, not the fact one edge past it: "Homer"
|
|
743
|
+
// was named and "The Iliad was written by Homer." establishes it,
|
|
744
|
+
// while the hop's own destination ("Homer was an ancient Greek poet")
|
|
745
|
+
// has no establishing predecessor at all. The same reading
|
|
746
|
+
// `namedByQuery` and `cmpAccounted` already take of a hop.
|
|
747
|
+
const da = establishing(dominant.anchor);
|
|
748
|
+
const ca = establishing(c.point !== null ? c.anchor : c.src.anchor);
|
|
749
|
+
if (indexOf(da, ca, 0) < 0 && indexOf(ca, da, 0) < 0) {
|
|
750
|
+
sim = sharedFrameStrengthOf(ctx, da, ca);
|
|
751
|
+
frameSeats.set(c, [da, ca]);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
497
754
|
ctx.trace?.step("tryAnalog", [
|
|
498
755
|
rNode(ctx, dominant.anchor, "dominant"),
|
|
499
756
|
rNode(ctx, c.anchor, "candidate", sim),
|
|
@@ -649,7 +906,59 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
649
906
|
// FRAME-tier or fallback analog — whose "similarity" is an unbarred
|
|
650
907
|
// coverage fraction or nothing — needs the query's naming or the climb's
|
|
651
908
|
// trust.
|
|
652
|
-
|
|
909
|
+
// A NAMING MUST NAME SOMETHING. `analogNamed` licences comparison on the
|
|
910
|
+
// claim that the query's own bytes evidence the analog — but that claim is
|
|
911
|
+
// only worth what those bytes discriminate. `edgeAncestors` already has the
|
|
912
|
+
// system's verdict for content that discriminates nothing: SATURATION, the
|
|
913
|
+
// √N parent-fan-out abstention `explainedSpan` (bridge.ts) and the climb
|
|
914
|
+
// both respect. A window in too many places to discriminate cannot be
|
|
915
|
+
// evidence that the query meant THIS analog rather than any other.
|
|
916
|
+
//
|
|
917
|
+
// So the naming must rest on at least ONE window that is not saturated —
|
|
918
|
+
// not every window, which would be far too strong: test/29 C1's naming is
|
|
919
|
+
// [" is "=SAT, "teel"=1, " is "=SAT], and the one discriminative run is
|
|
920
|
+
// exactly what makes it a naming. Measured over the accounted runs of
|
|
921
|
+
// every `analogNamed` comparison in the suite (contextsReached per window):
|
|
922
|
+
//
|
|
923
|
+
// C1 " cold"=1 "teel "=1 N=4 → names
|
|
924
|
+
// C2 "Leonardo da Vinci"=4 " Shakespeare"=5 N=22 → names
|
|
925
|
+
// C3 " Leonardo da Vinci"=3 " Shakespeare"=4 N=13 → names
|
|
926
|
+
// "what i"=2 " the capital of France"=2 "Lyon"=1 → names
|
|
927
|
+
// " is "=SAT "teel"=1 " is "=SAT → names
|
|
928
|
+
// "The "=3 " painted by "=3 "Michelangelo"=2 → names
|
|
929
|
+
// 50 " name"=SAT " the "=SAT "ing "=SAT
|
|
930
|
+
// " the "=SAT "he b"=SAT "ing "=SAT N=205 → names NOTHING
|
|
931
|
+
//
|
|
932
|
+
// test/50's junk comparison is the only one in the suite whose naming is
|
|
933
|
+
// saturated end to end: it "names" its analog with " the " and "ing ". The
|
|
934
|
+
// ignored-known principle cannot reach that case — the planet probe's gaps
|
|
935
|
+
// ("planet", "biggest", "sun") are genuinely untrained, so
|
|
936
|
+
// `dismissedKnownContent` correctly returns false and there is no ignored
|
|
937
|
+
// known content to find. This is a different question: not "did the
|
|
938
|
+
// comparison ignore what the store knows" but "did the query name this
|
|
939
|
+
// analog at all". Derived, never tuned — the saturation limit is
|
|
940
|
+
// `edgeAncestors`' own √N, computed nowhere new.
|
|
941
|
+
const namingDiscriminates = () => {
|
|
942
|
+
const N = corpusN(ctx);
|
|
943
|
+
const W = ctx.space.maxGroup;
|
|
944
|
+
const memo = sharedReachMemo(ctx);
|
|
945
|
+
for (const [from, to] of cmpAccounted) {
|
|
946
|
+
for (let o = from; o + W <= to; o++) {
|
|
947
|
+
const ids = leafIdRun(ctx, query, o, o + W);
|
|
948
|
+
if (ids === null)
|
|
949
|
+
continue;
|
|
950
|
+
const wid = ctx.store.findBranch(ids);
|
|
951
|
+
if (wid === null)
|
|
952
|
+
continue;
|
|
953
|
+
const r = edgeAncestors(ctx, wid, N, memo);
|
|
954
|
+
if (!r.saturated && r.roots.length > 0)
|
|
955
|
+
return true;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return false;
|
|
959
|
+
};
|
|
960
|
+
const analogNamed = bestAnalog !== null && namedByQuery(bestAnalog) &&
|
|
961
|
+
namingDiscriminates();
|
|
653
962
|
// NOTE — two further gates were tried here and empirically REFUTED,
|
|
654
963
|
// recorded so they are not re-tried:
|
|
655
964
|
// • dominant self-coverage (dominant's aligned runs must dominate its
|
|
@@ -677,7 +986,27 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
677
986
|
// while a scrap-matched junk pair leaves the query's own trained content
|
|
678
987
|
// ("…songs…times…", "…planet…sun.") dismissed as gaps. Halo-tier and
|
|
679
988
|
// trusted-root comparisons are exempt — their evidence already stands.
|
|
680
|
-
|
|
989
|
+
// A TRUSTED ROOT IS NOT A LICENCE TO IGNORE WHAT THE STORE KNOWS. This
|
|
990
|
+
// exemption used to read `!(bestHalo || rootTrusted)`, so a root clearing
|
|
991
|
+
// consensusFloor discarded the ignored-known verdict entirely — and that
|
|
992
|
+
// verdict is the one piece of evidence in this gate that actually sees the
|
|
993
|
+
// failure: measured on test/50's probes, `dismissedKnownContent` returns
|
|
994
|
+
// TRUE for both ("songs"/"times"/"planet"-class trained content left in the
|
|
995
|
+
// comparison's gaps) while `rootTrusted` is also true, so the gate read
|
|
996
|
+
// false and comparison fired on a junk analog.
|
|
997
|
+
//
|
|
998
|
+
// The root's trust says the CLIMB settled on something; it says nothing
|
|
999
|
+
// about whether THIS comparison's own evidence covers the query's known
|
|
1000
|
+
// content, which is a different question about a different quantity. Halo
|
|
1001
|
+
// stays exempt — halo-tier company is independent evidence in its own right
|
|
1002
|
+
// (test/33 1b's nickname-corroborated analog), which is exactly what a
|
|
1003
|
+
// pooled consensus vote is not.
|
|
1004
|
+
//
|
|
1005
|
+
// Measured cost, and it is a candidate COUNT, not an answer: test/33 1b
|
|
1006
|
+
// ("expected at least two CAST candidates") loses one of its two, because
|
|
1007
|
+
// the comparison schema now honestly declines. The junk analogs it used to
|
|
1008
|
+
// supply were never the ones that test is about.
|
|
1009
|
+
const cmpDismisses = !bestHalo &&
|
|
681
1010
|
dismissedKnownContent(ctx, query, cmpAccounted);
|
|
682
1011
|
if (bestAnalog !== null &&
|
|
683
1012
|
(bestHalo || analogNamed || rootTrusted) &&
|
|
@@ -690,7 +1019,8 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
690
1019
|
rNode(ctx, dominant.anchor, "analog", bestSim),
|
|
691
1020
|
rNode(ctx, bestAnalog.anchor, "analog", bestSim),
|
|
692
1021
|
], [], "the two structures keep distributional company beyond chance — genuine analogs");
|
|
693
|
-
const
|
|
1022
|
+
const seats = frameSeats.get(bestAnalog);
|
|
1023
|
+
const a = seats !== undefined ? seats[0] : await seatOf(dominant);
|
|
694
1024
|
// The analog is only being CITED for comparison — the query never asked
|
|
695
1025
|
// about it — so its seat never chases a FORWARD continuation (see
|
|
696
1026
|
// seatOfNode's `allowForward`): only reverse (if a predecessor genuinely
|
|
@@ -703,9 +1033,11 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
703
1033
|
// [...] context will be the seat") — its own bytes ARE that seat
|
|
704
1034
|
// directly, with no predecessor to even check (it was found by a
|
|
705
1035
|
// forward edge, not matched in the query).
|
|
706
|
-
const b =
|
|
707
|
-
?
|
|
708
|
-
:
|
|
1036
|
+
const b = seats !== undefined
|
|
1037
|
+
? seats[1]
|
|
1038
|
+
: bestAnalog.point !== null
|
|
1039
|
+
? await seatOf(bestAnalog.point, false)
|
|
1040
|
+
: read(ctx, bestAnalog.anchor);
|
|
709
1041
|
const answer = await joinWithBridge(ctx, a, b);
|
|
710
1042
|
record(answer, "analogical comparison — each analog voiced by the context that establishes its role", new Set([dominant.anchor, bestAnalog.anchor]),
|
|
711
1043
|
// A halo-mediated act (the analogy gate) plus two seat projections.
|
|
@@ -188,9 +188,25 @@ export async function confluenceJoin(ctx, query, pre) {
|
|
|
188
188
|
reach = Math.min(reach, reachOf(ctx, wid, N, reachMemo));
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
+
// ONE WINDOW IS NOT AN ENTITY. The reach above is read at the
|
|
192
|
+
// finest grain the fold can address, where content-defined window
|
|
193
|
+
// identity is at its most phase-sensitive: a scaffolding phrase
|
|
194
|
+
// whose cut happens to land in a rare phase reads as rare content.
|
|
195
|
+
// Measured on test/29 C3, a 13-context store: the meet of `The Mona
|
|
196
|
+
// Lisa was painted by Leonardo da Vinci.` and `Hamlet was written by
|
|
197
|
+
// William Shakespeare.` came out as ` by ` at reach 3 — pure frame,
|
|
198
|
+
// priced as the corpus's third-rarest content, and voiced as the
|
|
199
|
+
// entity where the two evidence streams meet. A single quantum
|
|
200
|
+
// agrees with half the corpus by accident (the same argument
|
|
201
|
+
// pipeline-mechanism.ts's proposed-run gate makes about a 4-byte
|
|
202
|
+
// span), so a meet must clear the two-quantum floor this file's own
|
|
203
|
+
// entry gate uses — the smallest span that carries a perceivable
|
|
204
|
+
// unit BEYOND the one being matched.
|
|
205
|
+
const len = e - s;
|
|
206
|
+
if (len < 2 * W)
|
|
207
|
+
continue;
|
|
191
208
|
if (!isFinite(reach) || dominates(reach, N))
|
|
192
209
|
continue;
|
|
193
|
-
const len = e - s;
|
|
194
210
|
if (met === null || reach < met.reach ||
|
|
195
211
|
(reach === met.reach && len > met.len)) {
|
|
196
212
|
met = { bytes: aBytes.subarray(s, e), reach, len, a, b };
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// index and grounds the nearest learned form. Four tiers, orderly degrading
|
|
5
5
|
// from exact self-match to honest echo.
|
|
6
6
|
import { cosine } from "../../vec.js";
|
|
7
|
-
import { consensusFloor, identityBar, reachThreshold, significanceBar, } from "../../geometry.js";
|
|
7
|
+
import { conceptThreshold, consensusFloor, identityBar, reachThreshold, significanceBar, } from "../../geometry.js";
|
|
8
8
|
import { gistOf, read, resolve } from "../primitives.js";
|
|
9
9
|
import { bytesEqual, indexOf } from "../../bytes.js";
|
|
10
10
|
import { corpusN, hubBound } from "../traverse.js";
|
|
@@ -240,8 +240,22 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
240
240
|
// bridge's structural channels (junction walks, anchor climbs) are the
|
|
241
241
|
// correct proposal source for a query whose gist has no clean match;
|
|
242
242
|
// the ANN cannot propose what the gist cannot rank.
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
// The condition above is the SCORE of the top hit, not the size of the
|
|
244
|
+
// corpus. It used to be spelled `corpusN(ctx) <= (k · W)³`, which asks
|
|
245
|
+
// a different question and answers it wrongly at exactly the scale the
|
|
246
|
+
// note was written from: on the trained store N = 325,608 with k = 24
|
|
247
|
+
// and W = 4 puts the cube at 884,736, so that store took the exhaustive
|
|
248
|
+
// branch — the very branch measured here as 38K–40K annVectorReads.
|
|
249
|
+
// Measured cost of the mismatch: substitutionBridge 8,544ms of a
|
|
250
|
+
// 19,548ms think (44%), against 1,248ms and 14,218ms without it, with
|
|
251
|
+
// every answer in the battery byte-identical and the suite unchanged
|
|
252
|
+
// at 445/445. Corpus size was never the discriminator; whether the
|
|
253
|
+
// gist ranks ANYTHING at concept level is.
|
|
254
|
+
//
|
|
255
|
+
// Reading it as the note states also removes a duplicated (k · W)³ —
|
|
256
|
+
// the same cube gates crossRegionVotes' walk budget, where it likewise
|
|
257
|
+
// never engages at real scale (see attention.ts).
|
|
258
|
+
if (whole.length > 0 && whole[0].score >= conceptThreshold(ctx.store.D)) {
|
|
245
259
|
const exhaustive = await ctx.store.resonate(queryGist, hubBound(ctx), true);
|
|
246
260
|
return exhaustive.map((h) => h.id);
|
|
247
261
|
}
|
|
@@ -90,6 +90,10 @@ export interface WeaveInfo {
|
|
|
90
90
|
vote: number;
|
|
91
91
|
ctx: Uint8Array;
|
|
92
92
|
runs: GradedRun[];
|
|
93
|
+
/** The query span the CLIMB elected this anchor from — its evidence,
|
|
94
|
+
* independent of any literal run alignment (see Attention.start/end). */
|
|
95
|
+
start: number;
|
|
96
|
+
end: number;
|
|
93
97
|
}>;
|
|
94
98
|
/** Weighted depth at each query byte — sum of alignment weights.
|
|
95
99
|
* `depth[i]` is the total evidence that byte i is shared among the
|