@hviana/sema 0.2.4 → 0.2.5
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/dist/src/geometry.d.ts +26 -0
- package/dist/src/geometry.js +32 -3
- package/dist/src/mind/attention.d.ts +232 -5
- package/dist/src/mind/attention.js +718 -166
- package/dist/src/mind/index.d.ts +2 -0
- package/dist/src/mind/junction.d.ts +30 -1
- package/dist/src/mind/junction.js +73 -18
- package/dist/src/mind/mind.d.ts +2 -0
- package/dist/src/mind/rationale.d.ts +7 -2
- package/dist/src/mind/rationale.js +6 -5
- package/dist/src/mind/traverse.js +74 -10
- package/dist/src/mind/types.d.ts +18 -0
- package/package.json +1 -1
- package/src/geometry.ts +52 -3
- package/src/mind/attention.ts +1134 -121
- package/src/mind/index.ts +16 -0
- package/src/mind/junction.ts +125 -16
- package/src/mind/mind.ts +15 -0
- package/src/mind/rationale.ts +12 -4
- package/src/mind/traverse.ts +75 -7
- package/src/mind/types.ts +25 -0
- package/test/51-structural-resonance-ladder.test.mjs +552 -0
- package/test/52-climb-consensus-instrumentation.test.mjs +324 -0
package/src/mind/index.ts
CHANGED
|
@@ -16,3 +16,19 @@ export type {
|
|
|
16
16
|
RationaleItem,
|
|
17
17
|
RationaleStep,
|
|
18
18
|
} from "./rationale.js";
|
|
19
|
+
export type {
|
|
20
|
+
AnchorRejectionReason,
|
|
21
|
+
ClimbConsensusData,
|
|
22
|
+
ConsensusAnchorTrace,
|
|
23
|
+
ConsensusReachTrace,
|
|
24
|
+
ConsensusRegionTrace,
|
|
25
|
+
CrossRegionTier,
|
|
26
|
+
JunctionVoteTrace,
|
|
27
|
+
RegionOutcome,
|
|
28
|
+
} from "./attention.js";
|
|
29
|
+
export type {
|
|
30
|
+
AncestorReach,
|
|
31
|
+
AttentionRead,
|
|
32
|
+
SaturationReason,
|
|
33
|
+
SaturationStop,
|
|
34
|
+
} from "./types.js";
|
package/src/mind/junction.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// adjacent ANSWER pieces) and cross-region attention (the joint CONTEXT of two
|
|
13
13
|
// non-adjacent QUERY regions) ascend by the same disciplined, bounded walk.
|
|
14
14
|
|
|
15
|
+
import type { Hit } from "../store.js";
|
|
15
16
|
import type { MindContext } from "./types.js";
|
|
16
17
|
import { read, resolve } from "./primitives.js";
|
|
17
18
|
import { windowIds } from "./canonical.js";
|
|
@@ -27,6 +28,52 @@ export interface Junction {
|
|
|
27
28
|
interior: Uint8Array;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/** Which relaxation produced a {@link SynonymJunction}: one side replaced by
|
|
32
|
+
* a distributional halo sibling (`single-synonym`), or both (`double-
|
|
33
|
+
* synonym`) — the two remaining rungs of the graded ladder below exact DAG
|
|
34
|
+
* containment (see the module doc atop {@link junctionSynonyms}). */
|
|
35
|
+
export type SynonymJunctionTier =
|
|
36
|
+
| "single-synonym"
|
|
37
|
+
| "double-synonym";
|
|
38
|
+
|
|
39
|
+
export interface SynonymJunction extends Junction {
|
|
40
|
+
tier: SynonymJunctionTier;
|
|
41
|
+
/** Sibling score for a single-synonym junction; min(left, right) sibling
|
|
42
|
+
* score for a double-synonym junction. */
|
|
43
|
+
confidence: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The exact node ids and halo siblings resolved for one junction call's two
|
|
47
|
+
* sides — computed ONCE and reused by every ladder rung that needs them
|
|
48
|
+
* (junctionSynonyms' two tiers, and the structural-resonance tier beyond
|
|
49
|
+
* it). A failed synonym junction means only "no common DAG container was
|
|
50
|
+
* proven" — it does NOT mean the loaded siblings stop being useful. */
|
|
51
|
+
export interface JunctionSynonymSides {
|
|
52
|
+
leftId: number | null;
|
|
53
|
+
rightId: number | null;
|
|
54
|
+
leftSiblings: Hit[];
|
|
55
|
+
rightSiblings: Hit[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Resolve `left`/`right` to their exact node ids (when known) and load each
|
|
59
|
+
* resolved side's halo siblings once — deterministic (haloSiblings already
|
|
60
|
+
* ranks nearest-first) and shared by every ladder rung that consults
|
|
61
|
+
* siblings, so no ladder rung repeats a halo ANN query the previous one
|
|
62
|
+
* already paid for. */
|
|
63
|
+
export async function loadJunctionSynonymSides(
|
|
64
|
+
ctx: MindContext,
|
|
65
|
+
left: Uint8Array,
|
|
66
|
+
right: Uint8Array,
|
|
67
|
+
): Promise<JunctionSynonymSides> {
|
|
68
|
+
const leftId = resolve(ctx, left);
|
|
69
|
+
const rightId = resolve(ctx, right);
|
|
70
|
+
const leftSiblings = leftId !== null ? await haloSiblings(ctx, leftId) : [];
|
|
71
|
+
const rightSiblings = rightId !== null
|
|
72
|
+
? await haloSiblings(ctx, rightId)
|
|
73
|
+
: [];
|
|
74
|
+
return { leftId, rightId, leftSiblings, rightSiblings };
|
|
75
|
+
}
|
|
76
|
+
|
|
30
77
|
/** Seed node ids to ascend from for one side of a junction: the side's own
|
|
31
78
|
* node when it is a stored form, plus — when the node has no structural
|
|
32
79
|
* parents — its canonical window ids. A non-W-aligned node may have no
|
|
@@ -292,20 +339,32 @@ export async function junctionSynonyms(
|
|
|
292
339
|
right: Uint8Array,
|
|
293
340
|
maxInterior: number,
|
|
294
341
|
unordered = false,
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const rId = resolve(ctx, right);
|
|
300
|
-
if (lId === null && rId === null) return out;
|
|
342
|
+
sides?: JunctionSynonymSides,
|
|
343
|
+
): Promise<SynonymJunction[]> {
|
|
344
|
+
const s = sides ?? await loadJunctionSynonymSides(ctx, left, right);
|
|
345
|
+
if (s.leftId === null && s.rightId === null) return [];
|
|
301
346
|
|
|
302
|
-
|
|
347
|
+
// ── Tier 2.5a: single-synonym — one side replaced by a halo sibling ──────
|
|
348
|
+
// ONE shared expansion budget across BOTH directions of this tier.
|
|
349
|
+
const singleBudget = { n: hubBound(ctx) * ctx.space.maxGroup };
|
|
350
|
+
const singleOut = new Map<number, SynonymJunction>();
|
|
351
|
+
const keepBest = (
|
|
352
|
+
map: Map<number, SynonymJunction>,
|
|
353
|
+
j: Junction,
|
|
354
|
+
tier: SynonymJunctionTier,
|
|
355
|
+
confidence: number,
|
|
356
|
+
) => {
|
|
357
|
+
const prev = map.get(j.id);
|
|
358
|
+
if (prev === undefined || confidence > prev.confidence) {
|
|
359
|
+
map.set(j.id, { ...j, tier, confidence });
|
|
360
|
+
}
|
|
361
|
+
};
|
|
303
362
|
|
|
304
363
|
// Left-side synonyms: containers of sibling+right. `right`'s seeds are
|
|
305
364
|
// FIXED across every sibling this loop tries.
|
|
306
|
-
if (
|
|
365
|
+
if (s.leftId !== null) {
|
|
307
366
|
const rightSeeds = junctionSeeds(ctx, right);
|
|
308
|
-
for (const sib of
|
|
367
|
+
for (const sib of s.leftSiblings) {
|
|
309
368
|
const sibBytes = read(ctx, sib.id, maxInterior + 1);
|
|
310
369
|
if (sibBytes.length === 0 || sibBytes.length > maxInterior) continue;
|
|
311
370
|
const containers = junctionContainersFrom(
|
|
@@ -315,18 +374,20 @@ export async function junctionSynonyms(
|
|
|
315
374
|
sibBytes.length + right.length + maxInterior,
|
|
316
375
|
junctionSeeds(ctx, sibBytes),
|
|
317
376
|
rightSeeds,
|
|
318
|
-
|
|
377
|
+
singleBudget,
|
|
319
378
|
unordered,
|
|
320
379
|
);
|
|
321
|
-
for (const c of containers)
|
|
380
|
+
for (const c of containers) {
|
|
381
|
+
keepBest(singleOut, c, "single-synonym", sib.score);
|
|
382
|
+
}
|
|
322
383
|
}
|
|
323
384
|
}
|
|
324
385
|
|
|
325
386
|
// Right-side synonyms: containers of left+sibling. `left`'s seeds are
|
|
326
387
|
// likewise fixed across this loop.
|
|
327
|
-
if (
|
|
388
|
+
if (s.rightId !== null) {
|
|
328
389
|
const leftSeeds = junctionSeeds(ctx, left);
|
|
329
|
-
for (const sib of
|
|
390
|
+
for (const sib of s.rightSiblings) {
|
|
330
391
|
const sibBytes = read(ctx, sib.id, maxInterior + 1);
|
|
331
392
|
if (sibBytes.length === 0 || sibBytes.length > maxInterior) continue;
|
|
332
393
|
const containers = junctionContainersFrom(
|
|
@@ -336,12 +397,60 @@ export async function junctionSynonyms(
|
|
|
336
397
|
left.length + sibBytes.length + maxInterior,
|
|
337
398
|
leftSeeds,
|
|
338
399
|
junctionSeeds(ctx, sibBytes),
|
|
339
|
-
|
|
400
|
+
singleBudget,
|
|
340
401
|
unordered,
|
|
341
402
|
);
|
|
342
|
-
for (const c of containers)
|
|
403
|
+
for (const c of containers) {
|
|
404
|
+
keepBest(singleOut, c, "single-synonym", sib.score);
|
|
405
|
+
}
|
|
343
406
|
}
|
|
344
407
|
}
|
|
345
408
|
|
|
346
|
-
return
|
|
409
|
+
if (singleOut.size > 0) return [...singleOut.values()];
|
|
410
|
+
|
|
411
|
+
// ── Tier 2.5b: double-synonym — BOTH sides replaced, tried only when
|
|
412
|
+
// single-synonym found NOTHING. Every (leftSibling, rightSibling) pair,
|
|
413
|
+
// sorted deterministically, bounded to haloQueryK pairs total, ONE fresh
|
|
414
|
+
// shared budget for the whole tier. ─────────────────────────────────────
|
|
415
|
+
if (s.leftSiblings.length === 0 || s.rightSiblings.length === 0) return [];
|
|
416
|
+
|
|
417
|
+
const pairs: Array<{ l: Hit; r: Hit; confidence: number }> = [];
|
|
418
|
+
for (const l of s.leftSiblings) {
|
|
419
|
+
for (const r of s.rightSiblings) {
|
|
420
|
+
pairs.push({ l, r, confidence: Math.min(l.score, r.score) });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
pairs.sort((a, b) =>
|
|
424
|
+
b.confidence - a.confidence ||
|
|
425
|
+
a.l.id - b.l.id ||
|
|
426
|
+
a.r.id - b.r.id
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const doubleOut = new Map<number, SynonymJunction>();
|
|
430
|
+
const budget = { n: hubBound(ctx) * ctx.space.maxGroup };
|
|
431
|
+
const tries = Math.min(pairs.length, ctx.cfg.haloQueryK);
|
|
432
|
+
for (let i = 0; i < tries; i++) {
|
|
433
|
+
const { l, r, confidence } = pairs[i];
|
|
434
|
+
const lBytes = read(ctx, l.id, maxInterior + 1);
|
|
435
|
+
const rBytes = read(ctx, r.id, maxInterior + 1);
|
|
436
|
+
if (
|
|
437
|
+
lBytes.length === 0 || lBytes.length > maxInterior ||
|
|
438
|
+
rBytes.length === 0 || rBytes.length > maxInterior
|
|
439
|
+
) continue;
|
|
440
|
+
const containers = junctionContainersFrom(
|
|
441
|
+
ctx,
|
|
442
|
+
lBytes,
|
|
443
|
+
rBytes,
|
|
444
|
+
lBytes.length + rBytes.length + maxInterior,
|
|
445
|
+
junctionSeeds(ctx, lBytes),
|
|
446
|
+
junctionSeeds(ctx, rBytes),
|
|
447
|
+
budget,
|
|
448
|
+
unordered,
|
|
449
|
+
);
|
|
450
|
+
for (const c of containers) {
|
|
451
|
+
keepBest(doubleOut, c, "double-synonym", confidence);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return [...doubleOut.values()];
|
|
347
456
|
}
|
package/src/mind/mind.ts
CHANGED
|
@@ -136,6 +136,21 @@ import {
|
|
|
136
136
|
climbAttention as climbAttentionFn,
|
|
137
137
|
naturalBreak as naturalBreakFn,
|
|
138
138
|
} from "./attention.js";
|
|
139
|
+
export type {
|
|
140
|
+
AnchorRejectionReason,
|
|
141
|
+
ClimbConsensusData,
|
|
142
|
+
ConsensusAnchorTrace,
|
|
143
|
+
ConsensusReachTrace,
|
|
144
|
+
ConsensusRegionTrace,
|
|
145
|
+
CrossRegionTier,
|
|
146
|
+
JunctionVoteTrace,
|
|
147
|
+
RegionOutcome,
|
|
148
|
+
} from "./attention.js";
|
|
149
|
+
export type {
|
|
150
|
+
AncestorReach,
|
|
151
|
+
SaturationReason,
|
|
152
|
+
SaturationStop,
|
|
153
|
+
} from "./types.js";
|
|
139
154
|
import { aluToMechanism, defaultMechanisms, think } from "./pipeline.js";
|
|
140
155
|
import { articulate } from "./articulation.js";
|
|
141
156
|
import { ingest } from "./learning.js";
|
package/src/mind/rationale.ts
CHANGED
|
@@ -83,6 +83,11 @@ export interface RationaleStep {
|
|
|
83
83
|
/** A one-line, human account of what the mechanism did and why — the sentence
|
|
84
84
|
* that turns the data into an explanation. */
|
|
85
85
|
note?: string;
|
|
86
|
+
/** Optional structured payload — a mechanism-specific, plain-serialisable
|
|
87
|
+
* shape (no Map/Set/vectors/mutable internals) that carries more than the
|
|
88
|
+
* human-readable `note` can, for a debugger or downstream tool to consume
|
|
89
|
+
* programmatically. Never read by inference; purely additive. */
|
|
90
|
+
data?: unknown;
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
/** The callback {@link Mind.respond} / {@link Mind.respondText} accept. It is
|
|
@@ -142,7 +147,7 @@ export interface Scope {
|
|
|
142
147
|
/** Close the mechanism: emit its step with these outputs and pop it off the
|
|
143
148
|
* nesting stack. Idempotent — a second call is ignored, so a `finally` that
|
|
144
149
|
* closes after an early return is safe. */
|
|
145
|
-
done(outputs: RationaleItem[], note?: string): void;
|
|
150
|
+
done(outputs: RationaleItem[], note?: string, data?: unknown): void;
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
/** The live tracer: a stack of open mechanisms over one {@link Mind.respond}.
|
|
@@ -212,6 +217,7 @@ export class Rationale {
|
|
|
212
217
|
outputs: RationaleItem[],
|
|
213
218
|
deps: number[] | undefined,
|
|
214
219
|
note: string | undefined,
|
|
220
|
+
data?: unknown,
|
|
215
221
|
): void {
|
|
216
222
|
this.sink({
|
|
217
223
|
index,
|
|
@@ -223,6 +229,7 @@ export class Rationale {
|
|
|
223
229
|
inputs,
|
|
224
230
|
outputs,
|
|
225
231
|
note,
|
|
232
|
+
data,
|
|
226
233
|
});
|
|
227
234
|
}
|
|
228
235
|
|
|
@@ -248,11 +255,11 @@ export class Rationale {
|
|
|
248
255
|
};
|
|
249
256
|
return {
|
|
250
257
|
index,
|
|
251
|
-
done: (outputs, note) => {
|
|
258
|
+
done: (outputs, note, data) => {
|
|
252
259
|
if (closed) return;
|
|
253
260
|
closed = true;
|
|
254
261
|
pop();
|
|
255
|
-
emit(index, mechanism, inputs, outputs, resolvedDeps, note);
|
|
262
|
+
emit(index, mechanism, inputs, outputs, resolvedDeps, note, data);
|
|
256
263
|
},
|
|
257
264
|
};
|
|
258
265
|
}
|
|
@@ -265,11 +272,12 @@ export class Rationale {
|
|
|
265
272
|
outputs: RationaleItem[],
|
|
266
273
|
note?: string,
|
|
267
274
|
deps?: number[],
|
|
275
|
+
data?: unknown,
|
|
268
276
|
): number {
|
|
269
277
|
const mechanism = this.path(name);
|
|
270
278
|
const resolvedDeps = deps ?? this.defaultDeps();
|
|
271
279
|
const index = this.reserve(name);
|
|
272
|
-
this.emit(index, mechanism, inputs, outputs, resolvedDeps, note);
|
|
280
|
+
this.emit(index, mechanism, inputs, outputs, resolvedDeps, note, data);
|
|
273
281
|
return index;
|
|
274
282
|
}
|
|
275
283
|
}
|
package/src/mind/traverse.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// project) live in match.ts — the elementary match-and-project operation.
|
|
8
8
|
|
|
9
9
|
import { cosine, Vec } from "../vec.js";
|
|
10
|
-
import type { AncestorReach, MindContext } from "./types.js";
|
|
10
|
+
import type { AncestorReach, MindContext, SaturationStop } from "./types.js";
|
|
11
11
|
import { gistOf, read } from "./primitives.js";
|
|
12
12
|
|
|
13
13
|
// ── Per-response structural memo ────────────────────────────────────────
|
|
@@ -134,7 +134,22 @@ export function edgeAncestors(
|
|
|
134
134
|
// is withdrawn. On a small store the floor stays ≤ √N and the atom
|
|
135
135
|
// climbs exactly as before, so single-letter facts keep working.
|
|
136
136
|
if (id < 0 && atomIsHub(ctx, contextCount)) {
|
|
137
|
-
const
|
|
137
|
+
const bound0 = Math.ceil(Math.sqrt(Math.max(2, contextCount)));
|
|
138
|
+
const reach: AncestorReach = {
|
|
139
|
+
roots: [],
|
|
140
|
+
contextsReached: 0,
|
|
141
|
+
saturated: true,
|
|
142
|
+
...(ctx.trace
|
|
143
|
+
? {
|
|
144
|
+
saturation: {
|
|
145
|
+
reason: "byte-atom-commonality" as const,
|
|
146
|
+
node: id,
|
|
147
|
+
observed: atomReach(ctx, contextCount),
|
|
148
|
+
limit: bound0,
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
: {}),
|
|
152
|
+
};
|
|
138
153
|
memo?.set(id, reach);
|
|
139
154
|
return reach;
|
|
140
155
|
}
|
|
@@ -144,6 +159,10 @@ export function edgeAncestors(
|
|
|
144
159
|
const seen = new Set<number>([id]);
|
|
145
160
|
const ctxSeen = new Set<number>();
|
|
146
161
|
let saturated = false;
|
|
162
|
+
// Provenance of the FIRST decision that saturated this climb — allocated
|
|
163
|
+
// only when a trace is requested (see AncestorReach.saturation's doc); the
|
|
164
|
+
// climb itself never reads it back.
|
|
165
|
+
let satStop: SaturationStop | undefined;
|
|
147
166
|
|
|
148
167
|
// EXPAND-UNTIL-DECIDED: a reach is consumed either as a VOTE (which needs
|
|
149
168
|
// contextsReached exactly, and only while ≤ √N — beyond that the region is
|
|
@@ -188,12 +207,45 @@ export function edgeAncestors(
|
|
|
188
207
|
if (hasNx || pc > 0) {
|
|
189
208
|
roots.push(x);
|
|
190
209
|
if (hasNx) ctxSeen.add(x);
|
|
191
|
-
if (pc > bound)
|
|
210
|
+
if (pc > bound) {
|
|
211
|
+
// decided: ≥ pc > √N distinct contexts
|
|
212
|
+
if (ctx.trace) {
|
|
213
|
+
satStop = {
|
|
214
|
+
reason: "predecessor-fan-in",
|
|
215
|
+
node: x,
|
|
216
|
+
observed: pc,
|
|
217
|
+
limit: bound,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
192
222
|
for (const p of ctx.store.prevFirst(x, bound)) ctxSeen.add(p);
|
|
193
|
-
if (ctxSeen.size > bound)
|
|
223
|
+
if (ctxSeen.size > bound) {
|
|
224
|
+
// decided
|
|
225
|
+
if (ctx.trace) {
|
|
226
|
+
satStop = {
|
|
227
|
+
reason: "distinct-context-limit",
|
|
228
|
+
node: x,
|
|
229
|
+
observed: ctxSeen.size,
|
|
230
|
+
limit: bound,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
194
235
|
}
|
|
195
236
|
const parents = ctx.store.parentsFirst(x, bound + 1);
|
|
196
|
-
if (parents.length > bound)
|
|
237
|
+
if (parents.length > bound) {
|
|
238
|
+
// decided: hub
|
|
239
|
+
if (ctx.trace) {
|
|
240
|
+
satStop = {
|
|
241
|
+
reason: "parent-fan-out",
|
|
242
|
+
node: x,
|
|
243
|
+
observed: parents.length,
|
|
244
|
+
limit: bound,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
197
249
|
let fresh = 0;
|
|
198
250
|
for (const p of parents) {
|
|
199
251
|
if (!seen.has(p)) {
|
|
@@ -204,7 +256,18 @@ export function edgeAncestors(
|
|
|
204
256
|
}
|
|
205
257
|
if (fresh > 1) {
|
|
206
258
|
lateral += fresh - 1;
|
|
207
|
-
if (lateral > bound)
|
|
259
|
+
if (lateral > bound) {
|
|
260
|
+
// decided: cone-wide hub
|
|
261
|
+
if (ctx.trace) {
|
|
262
|
+
satStop = {
|
|
263
|
+
reason: "lateral-cone-limit",
|
|
264
|
+
node: x,
|
|
265
|
+
observed: lateral,
|
|
266
|
+
limit: bound,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
208
271
|
}
|
|
209
272
|
return true;
|
|
210
273
|
};
|
|
@@ -266,7 +329,12 @@ export function edgeAncestors(
|
|
|
266
329
|
}
|
|
267
330
|
}
|
|
268
331
|
|
|
269
|
-
const reach = {
|
|
332
|
+
const reach: AncestorReach = {
|
|
333
|
+
roots,
|
|
334
|
+
contextsReached: ctxSeen.size,
|
|
335
|
+
saturated,
|
|
336
|
+
...(saturated && satStop ? { saturation: satStop } : {}),
|
|
337
|
+
};
|
|
270
338
|
memo?.set(id, reach);
|
|
271
339
|
return reach;
|
|
272
340
|
}
|
package/src/mind/types.ts
CHANGED
|
@@ -175,11 +175,36 @@ export interface RegionVote {
|
|
|
175
175
|
absorbed?: number;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/** The structural gate that first decided an {@link edgeAncestors} climb was
|
|
179
|
+
* saturated (an abstention, not a discriminative conclusion) — pure
|
|
180
|
+
* instrumentation for {@link ClimbConsensusData}'s reach trace; it never
|
|
181
|
+
* feeds back into the climb itself. */
|
|
182
|
+
export type SaturationReason =
|
|
183
|
+
| "byte-atom-commonality"
|
|
184
|
+
| "predecessor-fan-in"
|
|
185
|
+
| "distinct-context-limit"
|
|
186
|
+
| "parent-fan-out"
|
|
187
|
+
| "lateral-cone-limit";
|
|
188
|
+
|
|
189
|
+
/** One saturation stop's provenance: which reason fired, at which node, the
|
|
190
|
+
* observed count against the bound that decided it. */
|
|
191
|
+
export interface SaturationStop {
|
|
192
|
+
reason: SaturationReason;
|
|
193
|
+
node: number;
|
|
194
|
+
observed: number;
|
|
195
|
+
limit: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
178
198
|
/** The edge-bearing contexts reached by climbing from a node, plus saturation info. */
|
|
179
199
|
export interface AncestorReach {
|
|
180
200
|
roots: number[];
|
|
181
201
|
contextsReached: number;
|
|
182
202
|
saturated: boolean;
|
|
203
|
+
/** The saturation gate that stopped this climb, when {@link saturated} is
|
|
204
|
+
* true and a trace was requested — see {@link edgeAncestors}. Absent for
|
|
205
|
+
* a non-saturated reach, and absent (even when saturated) when no trace
|
|
206
|
+
* was requested — instrumentation must not allocate when tracing is off. */
|
|
207
|
+
saturation?: SaturationStop;
|
|
183
208
|
}
|
|
184
209
|
|
|
185
210
|
/** Saturated-interval information for the noise-drop gate. */
|