@hviana/sema 0.4.3 → 0.4.6
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/bridge.js +46 -21
- 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/mind.js +11 -2
- 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/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/bridge.ts +55 -18
- 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/mind.ts +11 -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/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/src/mind/match.ts
CHANGED
|
@@ -499,10 +499,26 @@ export function sharedFrameStrength(
|
|
|
499
499
|
ctx: MindContext,
|
|
500
500
|
a: number,
|
|
501
501
|
b: number,
|
|
502
|
+
): number {
|
|
503
|
+
return sharedFrameStrengthOf(ctx, read(ctx, a), read(ctx, b));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/** The same measure over BYTES, for callers holding a role-establishing
|
|
507
|
+
* CONTEXT rather than the node whose role it establishes — CAST's comparison
|
|
508
|
+
* reads the tier this way when two candidate analogs are fillers (bare entity
|
|
509
|
+
* names) rather than frame-bearing structures themselves. A role is a
|
|
510
|
+
* property of the context that establishes a filler, never of the filler's
|
|
511
|
+
* own bytes: measured on test/29's corpus, "Michelangelo" against "Homer"
|
|
512
|
+
* reads 0.000 while their establishing contexts ("The David was sculpted
|
|
513
|
+
* by…" against "The Iliad was written by…") read 0.452, and a context in a
|
|
514
|
+
* genuinely different frame ("Water boils at…") still reads 0.000 — the tier
|
|
515
|
+
* discriminates, it was simply being asked about the wrong bytes. */
|
|
516
|
+
export function sharedFrameStrengthOf(
|
|
517
|
+
ctx: MindContext,
|
|
518
|
+
A: Uint8Array,
|
|
519
|
+
B: Uint8Array,
|
|
502
520
|
): number {
|
|
503
521
|
const W = ctx.space.maxGroup;
|
|
504
|
-
const A = read(ctx, a);
|
|
505
|
-
const B = read(ctx, b);
|
|
506
522
|
if (A.length < W || B.length < W) return 0;
|
|
507
523
|
// Mark every byte of the shorter side covered by a learnt W-window that
|
|
508
524
|
// also occurs in the longer side.
|
|
@@ -14,13 +14,20 @@
|
|
|
14
14
|
import type { MindContext } from "../types.js";
|
|
15
15
|
import type { Vec } from "../../vec.js";
|
|
16
16
|
import { read } from "../primitives.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
argmaxBy,
|
|
19
|
+
corpusN,
|
|
20
|
+
edgeAncestors,
|
|
21
|
+
hubBound,
|
|
22
|
+
sharedReachMemo,
|
|
23
|
+
} from "../traverse.js";
|
|
18
24
|
import {
|
|
19
25
|
analogyStrength,
|
|
20
26
|
follow,
|
|
21
27
|
type GradedRun,
|
|
22
28
|
project,
|
|
23
29
|
reverseContext,
|
|
30
|
+
sharedFrameStrengthOf,
|
|
24
31
|
} from "../match.js";
|
|
25
32
|
import { joinWithBridge } from "../resonance.js";
|
|
26
33
|
import { restatesQuery } from "../reasoning.js";
|
|
@@ -34,6 +41,7 @@ import {
|
|
|
34
41
|
} from "../rationale.js";
|
|
35
42
|
import { rItem, rNode } from "../trace.js";
|
|
36
43
|
import { dismissedKnownContent } from "../bridge.js";
|
|
44
|
+
import { leafIdRun } from "../canonical.js";
|
|
37
45
|
|
|
38
46
|
// ── CAST gates ────────────────────────────────────────────────────────────
|
|
39
47
|
//
|
|
@@ -218,7 +226,94 @@ export async function counterfactualTransfer(
|
|
|
218
226
|
const weave = await pre.weave();
|
|
219
227
|
const points = weave.points;
|
|
220
228
|
const depth = weave.depth;
|
|
221
|
-
|
|
229
|
+
// CAST'S OWN SINGLE-VS-MULTI TEST, MEASURED FROM THE QUERY.
|
|
230
|
+
//
|
|
231
|
+
// `points.length >= 2` reads as "two structures to transfer between", but
|
|
232
|
+
// measured, it functions as "the query is about more than one thing" — and
|
|
233
|
+
// it only discriminates because the weave's exclusivity eliminates hard
|
|
234
|
+
// enough that a single-topic query cannot reach two points. The condition
|
|
235
|
+
// is carried by the elimination, not by anything CAST measures. Traced on
|
|
236
|
+
// test/24 3.1 ("the importance of gender equality in the workplace"): the
|
|
237
|
+
// climb is byte-identical either way (16 of 31 sub-regions, one context),
|
|
238
|
+
// and relaxing the weave alone makes CAST fire and answer about the 1992
|
|
239
|
+
// Dream Team.
|
|
240
|
+
//
|
|
241
|
+
// What actually separates 3.1 from a genuine comparison (test/29 C2, "How is
|
|
242
|
+
// Shakespeare like Leonardo da Vinci?") is CONTENT: C2's two points are
|
|
243
|
+
// evidenced by DIFFERENT query spans, while 3.1's extra points align to the
|
|
244
|
+
// same shared frame the first one already explains. So require two points
|
|
245
|
+
// that explain genuinely different parts of the query — a second point must
|
|
246
|
+
// contribute at least one perception quantum of query bytes the
|
|
247
|
+
// best-covered point does not. Derived from the runs themselves, order-free,
|
|
248
|
+
// and independent of how many points survived.
|
|
249
|
+
const coveredBy = (p: typeof points[0]): Set<number> => {
|
|
250
|
+
const set = new Set<number>();
|
|
251
|
+
for (const r of p.runs) for (let i = r.qs; i < r.qe; i++) set.add(i);
|
|
252
|
+
return set;
|
|
253
|
+
};
|
|
254
|
+
let widest = points[0];
|
|
255
|
+
let widestN = -1;
|
|
256
|
+
for (const p of points) {
|
|
257
|
+
const n = coveredBy(p).size;
|
|
258
|
+
if (n > widestN) {
|
|
259
|
+
widestN = n;
|
|
260
|
+
widest = p;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const widestSet = widest === undefined
|
|
264
|
+
? new Set<number>()
|
|
265
|
+
: coveredBy(widest);
|
|
266
|
+
let distinct = points.length === 0 ? 0 : 1;
|
|
267
|
+
for (const p of points) {
|
|
268
|
+
if (p === widest) continue;
|
|
269
|
+
let own = 0;
|
|
270
|
+
for (const i of coveredBy(p)) if (!widestSet.has(i)) own++;
|
|
271
|
+
if (own >= quantum) {
|
|
272
|
+
distinct = 2;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// THE CLIMB ANSWERS THE SAME QUESTION, AND IT ANSWERS IT ORDER-FREE. Runs
|
|
277
|
+
// are literal W-gram agreement, so two structures the query names in its own
|
|
278
|
+
// words can share no run at all: on `How is ice like steel?` the query's
|
|
279
|
+
// `ice` and the stored `Ice is cold` agree on nothing but the ` is `
|
|
280
|
+
// scaffolding `Steel is hard` also matches, and the run test above reads one
|
|
281
|
+
// topic. The climb had already read two — it elected `Ice is cold` from
|
|
282
|
+
// q4-9 and `Steel is hard` from q16-20, two disjoint places — and DISPERSION
|
|
283
|
+
// (Attention.clusters) is exactly that reading: not how much evidence, but
|
|
284
|
+
// how many separate places in the query corroborate it. Measured against
|
|
285
|
+
// the case this gate exists to refuse, test/24 3.1: a genuinely single-topic
|
|
286
|
+
// query reads clusters 1, while C1's single committed root reads 2.
|
|
287
|
+
//
|
|
288
|
+
// Either source is sufficient — bytes the other point does not explain, or
|
|
289
|
+
// places the climb found the query's evidence in — and neither is a count of
|
|
290
|
+
// weave survivors.
|
|
291
|
+
// Dispersion alone is a property of the QUERY, not of the pair being woven,
|
|
292
|
+
// so it is read together with the pair's own elected spans: two points count
|
|
293
|
+
// as two topics when the climb found the query dispersed AND it elected them
|
|
294
|
+
// from places at least a quantum apart. (Dispersion alone was measured and
|
|
295
|
+
// is too weak — it let CAST into test/33's near-tie and test/24's list
|
|
296
|
+
// skill, whose points the climb elects from the same place.)
|
|
297
|
+
const dispersed = roots.length >= MIN_WEAVE ||
|
|
298
|
+
roots.some((r) => r.clusters >= MIN_WEAVE);
|
|
299
|
+
const apart = points.some((a) =>
|
|
300
|
+
points.some((b) =>
|
|
301
|
+
a !== b &&
|
|
302
|
+
(b.start - a.end >= quantum || a.start - b.end >= quantum)
|
|
303
|
+
)
|
|
304
|
+
);
|
|
305
|
+
// …and only where there is something left to transfer. When ONE point
|
|
306
|
+
// already explains the query down to the last quantum there is no analogy to
|
|
307
|
+
// draw — the query is that structure, restated or truncated — and the
|
|
308
|
+
// dispersion the climb reports is the SAME topic corroborated twice, not two
|
|
309
|
+
// topics. Measured on test/33's `steel is hard so steel is`, a prefix of one
|
|
310
|
+
// stored fact: its root disperses into 2 clusters purely because the fact
|
|
311
|
+
// repeats `steel is`, while that one point's runs cover all 25 query bytes.
|
|
312
|
+
const unexplained = query.length - widestN;
|
|
313
|
+
const aligned =
|
|
314
|
+
distinct >= 2 || (dispersed && apart && unexplained >= quantum)
|
|
315
|
+
? points.length
|
|
316
|
+
: 1;
|
|
222
317
|
if (aligned < 2) {
|
|
223
318
|
return fail(
|
|
224
319
|
`only ${aligned} structure(s) aligned across the query — CAST needs ` +
|
|
@@ -290,11 +385,39 @@ export async function counterfactualTransfer(
|
|
|
290
385
|
return [];
|
|
291
386
|
}
|
|
292
387
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
)
|
|
388
|
+
// WOVEN — is anything actually brought TOGETHER? A run restating a site
|
|
389
|
+
// the query already contains is not, by itself, evidence of that; but TWO
|
|
390
|
+
// points restating DIFFERENT sites is exactly a comparison ("How is
|
|
391
|
+
// Michelangelo like Homer?" names both entities, recognition finds both,
|
|
392
|
+
// and the weave aligns each to its own stored structure). The escape
|
|
393
|
+
// clause alone called that unwoven — a reading that held only while
|
|
394
|
+
// recognition UNDER-reported sites, and test/29 A2 started failing the
|
|
395
|
+
// moment recognition's interior chains stopped dying mid-form.
|
|
396
|
+
//
|
|
397
|
+
// Both points must be evidenced in what the asker JUST SAID. A multi-turn
|
|
398
|
+
// query is the whole transcript, so the earlier turns' own questions are
|
|
399
|
+
// aligned points too — traced on test/48, the weave for `And what is the
|
|
400
|
+
// capital of Spain?` holds `What is the capital of France?` (runs q0-61,
|
|
401
|
+
// entirely inside the previous turn and its answer) beside the new question
|
|
402
|
+
// (q65-94). Two points, two named sites, and nothing woven at all: one of
|
|
403
|
+
// them is conversation history. The current turn is the bytes past the last
|
|
404
|
+
// answered span — the same `askerBytes` notion computeWeave prices its read
|
|
405
|
+
// budget with — so requiring both points to have evidence THERE separates a
|
|
406
|
+
// genuine two-place weave from a follow-up. Single-turn queries have no
|
|
407
|
+
// answered spans, so the current turn is the whole query and nothing changes.
|
|
408
|
+
const turnStart = ctx.answeredSpans.reduce((n, [, e]) => Math.max(n, e), 0);
|
|
409
|
+
const inTurn = points.filter((p) => p.runs.some((r) => r.qe > turnStart));
|
|
410
|
+
const siteAt = (r: GradedRun): number =>
|
|
411
|
+
pre.rec.sites.findIndex((s) => r.qs >= s.start && r.qe <= s.end);
|
|
412
|
+
const namedSites = new Set<number>();
|
|
413
|
+
for (const p of inTurn) {
|
|
414
|
+
for (const r of p.runs) {
|
|
415
|
+
const i = siteAt(r);
|
|
416
|
+
if (i >= 0) namedSites.add(i);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const woven = points.some((p) => p.runs.some((r) => siteAt(r) < 0)) ||
|
|
420
|
+
(inTurn.length >= MIN_WEAVE && namedSites.size >= MIN_WEAVE);
|
|
298
421
|
if (!woven) {
|
|
299
422
|
return fail(
|
|
300
423
|
`every aligned run restates a recognised query site — nothing was ` +
|
|
@@ -357,11 +480,30 @@ export async function counterfactualTransfer(
|
|
|
357
480
|
const qv = pre.guide;
|
|
358
481
|
|
|
359
482
|
// ── SUBSTITUTION ──────────────────────────────────────────────────
|
|
360
|
-
const fillerOf = (s: Point): Uint8Array =>
|
|
361
|
-
|
|
362
|
-
return r.cs < quantum
|
|
483
|
+
const fillerOf = (s: Point, r: GradedRun = s.runs[0]): Uint8Array =>
|
|
484
|
+
r.cs < quantum
|
|
363
485
|
? s.ctx.subarray(0, r.cs + (r.qe - r.qs))
|
|
364
486
|
: query.subarray(r.qs, r.qe);
|
|
487
|
+
// THE FILLER IS WHAT THE SUBJECT CONTRIBUTES BEFORE THE SEAT — CLIPPED HERE,
|
|
488
|
+
// NOT ARBITRATED BY RANK. A subject whose alignment runs INTO the seat span
|
|
489
|
+
// agrees with the displaced structure there; those shared bytes are frame,
|
|
490
|
+
// and only the part before the seat is the subject's own contribution.
|
|
491
|
+
// Reading `runs[0]` whole made this schema depend on the weave having
|
|
492
|
+
// already cut that overlap away for it: on `steel is frigid` the weave's
|
|
493
|
+
// exclusivity handed `steel is hard so steel is strong` the run q0-5
|
|
494
|
+
// (`steel`) only because the seat's point ranked higher and took q5-15
|
|
495
|
+
// first. Read without that cut the same run is q0-9 (`steel is `), it ends
|
|
496
|
+
// PAST the seat at q5, and substitution found no subject at all — a schema
|
|
497
|
+
// silently reading a global elimination order as if it were local evidence.
|
|
498
|
+
// Clipping at the seat derives the same span from the two points actually
|
|
499
|
+
// involved, so the reading no longer moves when the weave's order does.
|
|
500
|
+
const fillerRun = (s: Point, at: number): GradedRun | null => {
|
|
501
|
+
const r0 = s.runs[0];
|
|
502
|
+
if (r0.qs >= at) return null;
|
|
503
|
+
const qe = Math.min(r0.qe, at);
|
|
504
|
+
return qe - r0.qs >= Math.min(quantum, s.ctx.length)
|
|
505
|
+
? (qe === r0.qe ? r0 : { ...r0, qe })
|
|
506
|
+
: null;
|
|
365
507
|
};
|
|
366
508
|
// The subject is the closest structure whose FILLER RUN precedes the seat.
|
|
367
509
|
// The gate is on `runs[0]` — the run `fillerOf` actually reads — not on the
|
|
@@ -376,14 +518,19 @@ export async function counterfactualTransfer(
|
|
|
376
518
|
// analogy — pushed lastRun past the seat and no substitution fired at all.
|
|
377
519
|
// The ordering key follows the gate to the same run, so "closest preceding"
|
|
378
520
|
// still means closest by the evidence actually used.
|
|
379
|
-
const beforeOf = (
|
|
521
|
+
const beforeOf = (
|
|
522
|
+
p: Point,
|
|
523
|
+
r: GradedRun,
|
|
524
|
+
): { point: Point; run: GradedRun } | undefined =>
|
|
380
525
|
argmaxBy(
|
|
381
|
-
points.
|
|
382
|
-
s
|
|
383
|
-
|
|
384
|
-
usable(
|
|
385
|
-
|
|
386
|
-
|
|
526
|
+
points.flatMap((s) => {
|
|
527
|
+
if (s === p) return [];
|
|
528
|
+
const f = fillerRun(s, r.qs);
|
|
529
|
+
return f !== null && f.cs < quantum && usable(f.qs, f.qe)
|
|
530
|
+
? [{ point: s, run: f }]
|
|
531
|
+
: [];
|
|
532
|
+
}),
|
|
533
|
+
(s) => s.run.qs,
|
|
387
534
|
-Infinity,
|
|
388
535
|
true,
|
|
389
536
|
)?.item;
|
|
@@ -395,7 +542,9 @@ export async function counterfactualTransfer(
|
|
|
395
542
|
}
|
|
396
543
|
const before = beforeOf(p, r);
|
|
397
544
|
if (before === undefined) return null;
|
|
398
|
-
if (r.cs > fillerOf(before).length + quantum)
|
|
545
|
+
if (r.cs > fillerOf(before.point, before.run).length + quantum) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
399
548
|
// SUBSTITUTION MUST ACTUALLY DISPLACE. The schema's premise is that the
|
|
400
549
|
// displaced structure's seat is held by something ELSE, which the
|
|
401
550
|
// subject then replaces. When the subject's filler already occurs in
|
|
@@ -407,7 +556,9 @@ export async function counterfactualTransfer(
|
|
|
407
556
|
// produced `Michelangelo sculpted by Michelangelo.` — then outbid every
|
|
408
557
|
// honest candidate with it (test/29 A2). Byte containment, the same
|
|
409
558
|
// primitive the self-evidence and contradiction guards use.
|
|
410
|
-
if (indexOf(p.ctx, fillerOf(before), 0) >= 0)
|
|
559
|
+
if (indexOf(p.ctx, fillerOf(before.point, before.run), 0) >= 0) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
411
562
|
return { p, before, depth: p.ctx.length - r.cs };
|
|
412
563
|
})
|
|
413
564
|
.filter((c): c is NonNullable<typeof c> => c !== null);
|
|
@@ -416,7 +567,7 @@ export async function counterfactualTransfer(
|
|
|
416
567
|
const subj = picked?.item.before ?? null;
|
|
417
568
|
if (proj !== null && subj !== null) {
|
|
418
569
|
const seat = proj.runs[0];
|
|
419
|
-
const filler = fillerOf(subj);
|
|
570
|
+
const filler = fillerOf(subj.point, subj.run);
|
|
420
571
|
const tail = proj.ctx.subarray(seat.cs);
|
|
421
572
|
let answer = await joinWithBridge(ctx, filler, tail);
|
|
422
573
|
const fwd = await follow(ctx, proj.anchor, qv);
|
|
@@ -429,7 +580,7 @@ export async function counterfactualTransfer(
|
|
|
429
580
|
ctx.trace?.step(
|
|
430
581
|
"projectCounterfactual",
|
|
431
582
|
[
|
|
432
|
-
rItem(filler, "filler", subj.anchor),
|
|
583
|
+
rItem(filler, "filler", subj.point.anchor),
|
|
433
584
|
rNode(ctx, proj.anchor, "displaced-structure"),
|
|
434
585
|
],
|
|
435
586
|
[rItem(answer, "projection")],
|
|
@@ -438,7 +589,7 @@ export async function counterfactualTransfer(
|
|
|
438
589
|
record(
|
|
439
590
|
answer,
|
|
440
591
|
"counterfactual substitution — the subject fills the analog's seat",
|
|
441
|
-
new Set([subj.anchor, proj.anchor]),
|
|
592
|
+
new Set([subj.point.anchor, proj.anchor]),
|
|
442
593
|
// The acts performed: one seat INSERT projection + one edge FOLLOW.
|
|
443
594
|
STEP + STEP,
|
|
444
595
|
// What substitution actually READ: the two points it transfers
|
|
@@ -446,12 +597,41 @@ export async function counterfactualTransfer(
|
|
|
446
597
|
// structure whose seat it fills — not every OTHER point the weave
|
|
447
598
|
// happened to align (a third, unrelated point in the same weave
|
|
448
599
|
// contributes nothing to what substitution itself explains).
|
|
449
|
-
[...runSpans(subj), ...runSpans(proj)],
|
|
600
|
+
[...runSpans(subj.point), ...runSpans(proj)],
|
|
450
601
|
);
|
|
451
602
|
}
|
|
452
603
|
|
|
453
604
|
// ── REDIRECTION ────────────────────────────────────────────────────
|
|
454
|
-
|
|
605
|
+
// REDIRECTION IS ABOUT THE SUBSTITUTE THE QUERY NAMES, SO IT LOOKS FOR THE
|
|
606
|
+
// RUN THAT NAMES ONE. A structure is named when the query quotes it from
|
|
607
|
+
// its own opening bytes (`cs === 0`) — `…were Lyon?` against `Lyon is a city
|
|
608
|
+
// in France`. Reading that off `runs[0]` assumed the weave had already
|
|
609
|
+
// eliminated everything the point shares with the dominant, which is the
|
|
610
|
+
// elimination deciding the schema again: relaxed, the same point also aligns
|
|
611
|
+
// the query's trailing ` France` (cs 17, frame it shares with `what is the
|
|
612
|
+
// capital of France?`), that run sorts FIRST, and redirection stopped seeing
|
|
613
|
+
// a named substitute at all. Scanning the point's runs for the naming one
|
|
614
|
+
// is the same reading, taken from the runs rather than from their order, and
|
|
615
|
+
// "latest named" then means latest by the run actually relied on.
|
|
616
|
+
const named = points.flatMap((p) => {
|
|
617
|
+
const r = p.runs.find((r) => r.cs === 0 && usable(r.qs, r.qe));
|
|
618
|
+
return r !== undefined ? [{ point: p, run: r }] : [];
|
|
619
|
+
});
|
|
620
|
+
// …and it must be named AFTER what it displaces. Redirection replaces the
|
|
621
|
+
// ANSWER, so the substitute is the newest thing the query says — `…of France
|
|
622
|
+
// were Lyon?` names Lyon past everything the displaced structure aligned.
|
|
623
|
+
// The old `latest last run` reduce encoded this implicitly and only held
|
|
624
|
+
// while trimming kept the dominant's runs latest; stated on the naming run
|
|
625
|
+
// it is the same reading without that dependency. Measured on test/29 D1
|
|
626
|
+
// (`steel is frigid`), where the point with a naming run is the SUBJECT at
|
|
627
|
+
// q0-9, ahead of the dominant's q5-15: redirection must not fire, and
|
|
628
|
+
// substitution — which is what that shape is — keeps the case.
|
|
629
|
+
const last = argmaxBy(
|
|
630
|
+
named.filter((n) => n.run.qs > lastRun(dominant).qs),
|
|
631
|
+
(n) => n.run.qs,
|
|
632
|
+
-Infinity,
|
|
633
|
+
true,
|
|
634
|
+
)?.item;
|
|
455
635
|
// Displacement test, capped at the hub bound: a hub anchor can carry a
|
|
456
636
|
// corpus-sized fan-out, and each continuation costs a full byte
|
|
457
637
|
// reconstruction plus an O(|query|·|bytes|) scan. The first √N edges (the
|
|
@@ -460,18 +640,14 @@ export async function counterfactualTransfer(
|
|
|
460
640
|
const domNext = ctx.store.nextFirst(dominant.anchor, hubBound(ctx));
|
|
461
641
|
const displaced = domNext
|
|
462
642
|
.every((n) => indexOf(query, read(ctx, n), 0) < 0);
|
|
463
|
-
if (
|
|
464
|
-
|
|
465
|
-
last.runs[0].cs === 0 && displaced &&
|
|
466
|
-
usable(last.runs[0].qs, last.runs[0].qe)
|
|
467
|
-
) {
|
|
468
|
-
const g = await project(ctx, last.anchor, qv);
|
|
643
|
+
if (last !== undefined && last.point !== dominant && displaced) {
|
|
644
|
+
const g = await project(ctx, last.point.anchor, qv);
|
|
469
645
|
if (g !== null) {
|
|
470
646
|
ctx.trace?.step(
|
|
471
647
|
"projectCounterfactual",
|
|
472
648
|
[
|
|
473
649
|
rNode(ctx, dominant.anchor, "displaced-structure"),
|
|
474
|
-
rNode(ctx, last.anchor, "substitute"),
|
|
650
|
+
rNode(ctx, last.point.anchor, "substitute"),
|
|
475
651
|
],
|
|
476
652
|
[rItem(g, "projection")],
|
|
477
653
|
"the substitute's own fact replaces the displaced structure's answer",
|
|
@@ -479,7 +655,7 @@ export async function counterfactualTransfer(
|
|
|
479
655
|
record(
|
|
480
656
|
g,
|
|
481
657
|
"counterfactual redirection — the named substitute's fact is followed",
|
|
482
|
-
new Set([dominant.anchor, last.anchor]),
|
|
658
|
+
new Set([dominant.anchor, last.point.anchor]),
|
|
483
659
|
// One forward projection across the substitute's own fact.
|
|
484
660
|
STEP,
|
|
485
661
|
// What redirection READ: the displaced structure's own recognized
|
|
@@ -487,7 +663,7 @@ export async function counterfactualTransfer(
|
|
|
487
663
|
// being overridden, it just doesn't answer from it) plus the named
|
|
488
664
|
// substitute's own aligned run — not every OTHER point the weave
|
|
489
665
|
// happened to align.
|
|
490
|
-
[...runSpans(dominant), ...runSpans(last)],
|
|
666
|
+
[...runSpans(dominant), ...runSpans(last.point)],
|
|
491
667
|
);
|
|
492
668
|
}
|
|
493
669
|
}
|
|
@@ -529,7 +705,20 @@ export async function counterfactualTransfer(
|
|
|
529
705
|
// distinction perception can make — the same quantum countClusters separates
|
|
530
706
|
// neighbourhoods by — so a context within one quantum of the query's length
|
|
531
707
|
// carries no independently perceivable unit beyond it and is the same scale.
|
|
532
|
-
|
|
708
|
+
//
|
|
709
|
+
// ONE QUANTUM OF EXCESS IS AN ABSOLUTE UNIT, AND SCALE IS NOT ABSOLUTE.
|
|
710
|
+
// `n - query.length < quantum` calls a 504-byte context the same scale as a
|
|
711
|
+
// 500-byte query while refusing a 47-byte context on a 42-byte one — the
|
|
712
|
+
// same 5 bytes, opposite verdicts, because the bar never looks at what it is
|
|
713
|
+
// measuring against. Measured on test/29 C3, whose query is C2's verbatim:
|
|
714
|
+
// the climb elects the exemplar SENTENCE (47) rather than the entity, five
|
|
715
|
+
// bytes past a 42-byte query, and comparison refused a pair it accepts at
|
|
716
|
+
// C2's grain. Read the excess against the query with `dominates` — the same
|
|
717
|
+
// half-dominance predicate this file uses for frame, and the one scale-free
|
|
718
|
+
// reading of "the seat sentence must not dominate the comparison" available
|
|
719
|
+
// without inventing a ratio.
|
|
720
|
+
const queryScale = (n: number): boolean =>
|
|
721
|
+
!dominates(n - query.length, query.length);
|
|
533
722
|
const analogs: AnalogCandidate[] = [];
|
|
534
723
|
for (const p of points) {
|
|
535
724
|
if (p === dominant) continue;
|
|
@@ -616,12 +805,84 @@ export async function counterfactualTransfer(
|
|
|
616
805
|
// content the query never asked about. Computed once here; both the
|
|
617
806
|
// hub fallback below and the comparison gate consume it.
|
|
618
807
|
const rootTrusted = roots.some((r) => r.vote >= consensusFloor(corpusN(ctx)));
|
|
808
|
+
// The context that ESTABLISHES a filler — the same reverse context, under
|
|
809
|
+
// the same naming test, `seatOfNode` uses to VOICE an analog (a predecessor
|
|
810
|
+
// whose bytes CONTAIN the node's: it names or describes it, rather than
|
|
811
|
+
// merely having preceded it somewhere). Memoised: the analogy loop below
|
|
812
|
+
// asks about the same dominant every time, and only ever asks at all when
|
|
813
|
+
// the cheap tiers already read zero.
|
|
814
|
+
// A NODE NOTHING ESTABLISHES IS ITS OWN ESTABLISHING CONTEXT — the same
|
|
815
|
+
// reading `seatOfNode` takes one gate up: a bare filler was learnt as some
|
|
816
|
+
// context's answer and has a predecessor that NAMES it, so no establishing
|
|
817
|
+
// predecessor means the node already IS a learnt context. Returning null
|
|
818
|
+
// there made the tier depend on both sides being elected at the same GRAIN:
|
|
819
|
+
// test/29 C2's climb elects the entity `Leonardo da Vinci` (established by
|
|
820
|
+
// `The Mona Lisa was painted by…`) and reads 0.371, while C3's identical
|
|
821
|
+
// query elects that sentence ITSELF for the same side, whose own
|
|
822
|
+
// predecessor establishes nothing — the tier read 0.000 and comparison
|
|
823
|
+
// never fired, on a pair that is strictly MORE explicit about its frame.
|
|
824
|
+
const estMemo = new Map<number, Uint8Array>();
|
|
825
|
+
const establishing = (id: number): Uint8Array => {
|
|
826
|
+
const hit = estMemo.get(id);
|
|
827
|
+
if (hit !== undefined) return hit;
|
|
828
|
+
const own = read(ctx, id);
|
|
829
|
+
const rev = reverseContext(ctx, id, pre.guide);
|
|
830
|
+
const out = rev !== null && indexOf(rev, own, 0) >= 0 ? rev : own;
|
|
831
|
+
estMemo.set(id, out);
|
|
832
|
+
return out;
|
|
833
|
+
};
|
|
834
|
+
// COMPARISON VOICES WHAT IT COMPARED. When the frame tier decided the
|
|
835
|
+
// analogy, the two establishing contexts it read ARE the roles being
|
|
836
|
+
// compared, so the schema below voices those same bytes instead of
|
|
837
|
+
// re-deriving a seat that can land somewhere else entirely. Measured on
|
|
838
|
+
// test/29 C3: the dominant is the exemplar sentence `The Mona Lisa was
|
|
839
|
+
// painted by Leonardo da Vinci.`, nothing establishes it, so `seatOf`
|
|
840
|
+
// took its FORWARD continuation and voiced `Leonardo was a Renaissance
|
|
841
|
+
// polymath` — the analog's own biography, exactly what C2 pins comparison
|
|
842
|
+
// must never leak, from the branch whose own doc says forward completion
|
|
843
|
+
// is right for a DOMINANT (true when the dominant is a bare name whose
|
|
844
|
+
// continuation establishes it; false when it already IS the establishing
|
|
845
|
+
// context). Only frame-tier pairs are affected: a halo-tier analogy was
|
|
846
|
+
// never measured on these bytes and keeps the seat it always had.
|
|
847
|
+
const frameSeats = new Map<AnalogCandidate, [Uint8Array, Uint8Array]>();
|
|
619
848
|
for (const c of analogs) {
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
849
|
+
const ev = await analogyStrength(ctx, dominant.anchor, c.anchor);
|
|
850
|
+
let sim = ev.score;
|
|
851
|
+
const halo = ev.halo;
|
|
852
|
+
// ROLE IS ESTABLISHED BY CONTEXT, NOT BY A NAME. When neither halo tier
|
|
853
|
+
// fired and the two anchors' own bytes share no learnt frame either, the
|
|
854
|
+
// anchors are FILLERS — bare entity names — not the frame-bearing
|
|
855
|
+
// structures the tier is about. Read the tier on what establishes each
|
|
856
|
+
// one instead: the aligned point's own context (or, for a hop-reached
|
|
857
|
+
// candidate, the point whose continuation edge reached it — the same
|
|
858
|
+
// context `cmpAccounted` already prices as that hop's query evidence).
|
|
859
|
+
// Both are ALREADY IN HAND, so this costs no extra read.
|
|
860
|
+
//
|
|
861
|
+
// Measured on test/29's corpus: "Michelangelo" vs "Homer" reads 0.000
|
|
862
|
+
// while "The David was sculpted by Michelangelo." vs "The Iliad was
|
|
863
|
+
// written by Homer." reads 0.452 — and a context in a different frame
|
|
864
|
+
// ("Water boils at one hundred degrees.") still reads 0.000. The tier
|
|
865
|
+
// was never failing to discriminate; it was reading the fillers.
|
|
866
|
+
//
|
|
867
|
+
// Still the FRAME tier (`halo` stays false), so this evidence remains
|
|
868
|
+
// subject to the naming / trusted-root bar the comparison gate holds all
|
|
869
|
+
// frame evidence to — a wider READING of the same tier, not a new licence.
|
|
870
|
+
// Containment is excluded for the same reason the generator excludes it:
|
|
871
|
+
// a context that contains the other establishes nothing independent.
|
|
872
|
+
if (!halo && sim === 0) {
|
|
873
|
+
// For a hop-reached candidate the thing whose ROLE is in question is
|
|
874
|
+
// the point the query named, not the fact one edge past it: "Homer"
|
|
875
|
+
// was named and "The Iliad was written by Homer." establishes it,
|
|
876
|
+
// while the hop's own destination ("Homer was an ancient Greek poet")
|
|
877
|
+
// has no establishing predecessor at all. The same reading
|
|
878
|
+
// `namedByQuery` and `cmpAccounted` already take of a hop.
|
|
879
|
+
const da = establishing(dominant.anchor);
|
|
880
|
+
const ca = establishing(c.point !== null ? c.anchor : c.src.anchor);
|
|
881
|
+
if (indexOf(da, ca, 0) < 0 && indexOf(ca, da, 0) < 0) {
|
|
882
|
+
sim = sharedFrameStrengthOf(ctx, da, ca);
|
|
883
|
+
frameSeats.set(c, [da, ca]);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
625
886
|
ctx.trace?.step(
|
|
626
887
|
"tryAnalog",
|
|
627
888
|
[
|
|
@@ -790,7 +1051,56 @@ export async function counterfactualTransfer(
|
|
|
790
1051
|
// FRAME-tier or fallback analog — whose "similarity" is an unbarred
|
|
791
1052
|
// coverage fraction or nothing — needs the query's naming or the climb's
|
|
792
1053
|
// trust.
|
|
793
|
-
|
|
1054
|
+
// A NAMING MUST NAME SOMETHING. `analogNamed` licences comparison on the
|
|
1055
|
+
// claim that the query's own bytes evidence the analog — but that claim is
|
|
1056
|
+
// only worth what those bytes discriminate. `edgeAncestors` already has the
|
|
1057
|
+
// system's verdict for content that discriminates nothing: SATURATION, the
|
|
1058
|
+
// √N parent-fan-out abstention `explainedSpan` (bridge.ts) and the climb
|
|
1059
|
+
// both respect. A window in too many places to discriminate cannot be
|
|
1060
|
+
// evidence that the query meant THIS analog rather than any other.
|
|
1061
|
+
//
|
|
1062
|
+
// So the naming must rest on at least ONE window that is not saturated —
|
|
1063
|
+
// not every window, which would be far too strong: test/29 C1's naming is
|
|
1064
|
+
// [" is "=SAT, "teel"=1, " is "=SAT], and the one discriminative run is
|
|
1065
|
+
// exactly what makes it a naming. Measured over the accounted runs of
|
|
1066
|
+
// every `analogNamed` comparison in the suite (contextsReached per window):
|
|
1067
|
+
//
|
|
1068
|
+
// C1 " cold"=1 "teel "=1 N=4 → names
|
|
1069
|
+
// C2 "Leonardo da Vinci"=4 " Shakespeare"=5 N=22 → names
|
|
1070
|
+
// C3 " Leonardo da Vinci"=3 " Shakespeare"=4 N=13 → names
|
|
1071
|
+
// "what i"=2 " the capital of France"=2 "Lyon"=1 → names
|
|
1072
|
+
// " is "=SAT "teel"=1 " is "=SAT → names
|
|
1073
|
+
// "The "=3 " painted by "=3 "Michelangelo"=2 → names
|
|
1074
|
+
// 50 " name"=SAT " the "=SAT "ing "=SAT
|
|
1075
|
+
// " the "=SAT "he b"=SAT "ing "=SAT N=205 → names NOTHING
|
|
1076
|
+
//
|
|
1077
|
+
// test/50's junk comparison is the only one in the suite whose naming is
|
|
1078
|
+
// saturated end to end: it "names" its analog with " the " and "ing ". The
|
|
1079
|
+
// ignored-known principle cannot reach that case — the planet probe's gaps
|
|
1080
|
+
// ("planet", "biggest", "sun") are genuinely untrained, so
|
|
1081
|
+
// `dismissedKnownContent` correctly returns false and there is no ignored
|
|
1082
|
+
// known content to find. This is a different question: not "did the
|
|
1083
|
+
// comparison ignore what the store knows" but "did the query name this
|
|
1084
|
+
// analog at all". Derived, never tuned — the saturation limit is
|
|
1085
|
+
// `edgeAncestors`' own √N, computed nowhere new.
|
|
1086
|
+
const namingDiscriminates = (): boolean => {
|
|
1087
|
+
const N = corpusN(ctx);
|
|
1088
|
+
const W = ctx.space.maxGroup;
|
|
1089
|
+
const memo = sharedReachMemo(ctx);
|
|
1090
|
+
for (const [from, to] of cmpAccounted) {
|
|
1091
|
+
for (let o = from; o + W <= to; o++) {
|
|
1092
|
+
const ids = leafIdRun(ctx, query, o, o + W);
|
|
1093
|
+
if (ids === null) continue;
|
|
1094
|
+
const wid = ctx.store.findBranch(ids);
|
|
1095
|
+
if (wid === null) continue;
|
|
1096
|
+
const r = edgeAncestors(ctx, wid, N, memo);
|
|
1097
|
+
if (!r.saturated && r.roots.length > 0) return true;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return false;
|
|
1101
|
+
};
|
|
1102
|
+
const analogNamed = bestAnalog !== null && namedByQuery(bestAnalog) &&
|
|
1103
|
+
namingDiscriminates();
|
|
794
1104
|
// NOTE — two further gates were tried here and empirically REFUTED,
|
|
795
1105
|
// recorded so they are not re-tried:
|
|
796
1106
|
// • dominant self-coverage (dominant's aligned runs must dominate its
|
|
@@ -818,7 +1128,27 @@ export async function counterfactualTransfer(
|
|
|
818
1128
|
// while a scrap-matched junk pair leaves the query's own trained content
|
|
819
1129
|
// ("…songs…times…", "…planet…sun.") dismissed as gaps. Halo-tier and
|
|
820
1130
|
// trusted-root comparisons are exempt — their evidence already stands.
|
|
821
|
-
|
|
1131
|
+
// A TRUSTED ROOT IS NOT A LICENCE TO IGNORE WHAT THE STORE KNOWS. This
|
|
1132
|
+
// exemption used to read `!(bestHalo || rootTrusted)`, so a root clearing
|
|
1133
|
+
// consensusFloor discarded the ignored-known verdict entirely — and that
|
|
1134
|
+
// verdict is the one piece of evidence in this gate that actually sees the
|
|
1135
|
+
// failure: measured on test/50's probes, `dismissedKnownContent` returns
|
|
1136
|
+
// TRUE for both ("songs"/"times"/"planet"-class trained content left in the
|
|
1137
|
+
// comparison's gaps) while `rootTrusted` is also true, so the gate read
|
|
1138
|
+
// false and comparison fired on a junk analog.
|
|
1139
|
+
//
|
|
1140
|
+
// The root's trust says the CLIMB settled on something; it says nothing
|
|
1141
|
+
// about whether THIS comparison's own evidence covers the query's known
|
|
1142
|
+
// content, which is a different question about a different quantity. Halo
|
|
1143
|
+
// stays exempt — halo-tier company is independent evidence in its own right
|
|
1144
|
+
// (test/33 1b's nickname-corroborated analog), which is exactly what a
|
|
1145
|
+
// pooled consensus vote is not.
|
|
1146
|
+
//
|
|
1147
|
+
// Measured cost, and it is a candidate COUNT, not an answer: test/33 1b
|
|
1148
|
+
// ("expected at least two CAST candidates") loses one of its two, because
|
|
1149
|
+
// the comparison schema now honestly declines. The junk analogs it used to
|
|
1150
|
+
// supply were never the ones that test is about.
|
|
1151
|
+
const cmpDismisses = !bestHalo &&
|
|
822
1152
|
dismissedKnownContent(ctx, query, cmpAccounted);
|
|
823
1153
|
if (
|
|
824
1154
|
bestAnalog !== null &&
|
|
@@ -838,7 +1168,8 @@ export async function counterfactualTransfer(
|
|
|
838
1168
|
[],
|
|
839
1169
|
"the two structures keep distributional company beyond chance — genuine analogs",
|
|
840
1170
|
);
|
|
841
|
-
const
|
|
1171
|
+
const seats = frameSeats.get(bestAnalog);
|
|
1172
|
+
const a = seats !== undefined ? seats[0] : await seatOf(dominant);
|
|
842
1173
|
// The analog is only being CITED for comparison — the query never asked
|
|
843
1174
|
// about it — so its seat never chases a FORWARD continuation (see
|
|
844
1175
|
// seatOfNode's `allowForward`): only reverse (if a predecessor genuinely
|
|
@@ -851,7 +1182,9 @@ export async function counterfactualTransfer(
|
|
|
851
1182
|
// [...] context will be the seat") — its own bytes ARE that seat
|
|
852
1183
|
// directly, with no predecessor to even check (it was found by a
|
|
853
1184
|
// forward edge, not matched in the query).
|
|
854
|
-
const b =
|
|
1185
|
+
const b = seats !== undefined
|
|
1186
|
+
? seats[1]
|
|
1187
|
+
: bestAnalog.point !== null
|
|
855
1188
|
? await seatOf(bestAnalog.point, false)
|
|
856
1189
|
: read(ctx, bestAnalog.anchor);
|
|
857
1190
|
const answer = await joinWithBridge(ctx, a, b);
|
|
@@ -237,8 +237,23 @@ export async function confluenceJoin(
|
|
|
237
237
|
reach = Math.min(reach, reachOf(ctx, wid, N, reachMemo));
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
-
|
|
240
|
+
// ONE WINDOW IS NOT AN ENTITY. The reach above is read at the
|
|
241
|
+
// finest grain the fold can address, where content-defined window
|
|
242
|
+
// identity is at its most phase-sensitive: a scaffolding phrase
|
|
243
|
+
// whose cut happens to land in a rare phase reads as rare content.
|
|
244
|
+
// Measured on test/29 C3, a 13-context store: the meet of `The Mona
|
|
245
|
+
// Lisa was painted by Leonardo da Vinci.` and `Hamlet was written by
|
|
246
|
+
// William Shakespeare.` came out as ` by ` at reach 3 — pure frame,
|
|
247
|
+
// priced as the corpus's third-rarest content, and voiced as the
|
|
248
|
+
// entity where the two evidence streams meet. A single quantum
|
|
249
|
+
// agrees with half the corpus by accident (the same argument
|
|
250
|
+
// pipeline-mechanism.ts's proposed-run gate makes about a 4-byte
|
|
251
|
+
// span), so a meet must clear the two-quantum floor this file's own
|
|
252
|
+
// entry gate uses — the smallest span that carries a perceivable
|
|
253
|
+
// unit BEYOND the one being matched.
|
|
241
254
|
const len = e - s;
|
|
255
|
+
if (len < 2 * W) continue;
|
|
256
|
+
if (!isFinite(reach) || dominates(reach, N)) continue;
|
|
242
257
|
if (
|
|
243
258
|
met === null || reach < met.reach ||
|
|
244
259
|
(reach === met.reach && len > met.len)
|