@hviana/sema 0.2.6 → 0.2.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/dist/example/demo.d.ts +1 -0
- package/dist/example/demo.js +39 -0
- package/dist/example/train_base.d.ts +87 -0
- package/dist/example/train_base.js +2252 -0
- package/dist/src/alphabet.d.ts +7 -0
- package/dist/src/alphabet.js +33 -0
- package/dist/src/alu/src/alu.d.ts +185 -0
- package/dist/src/alu/src/alu.js +440 -0
- package/dist/src/alu/src/expr.d.ts +61 -0
- package/dist/src/alu/src/expr.js +318 -0
- package/dist/src/alu/src/index.d.ts +11 -0
- package/dist/src/alu/src/index.js +19 -0
- package/dist/src/alu/src/kernel-arith.d.ts +16 -0
- package/dist/src/alu/src/kernel-arith.js +264 -0
- package/dist/src/alu/src/kernel-bits.d.ts +19 -0
- package/dist/src/alu/src/kernel-bits.js +152 -0
- package/dist/src/alu/src/kernel-logic.d.ts +4 -0
- package/dist/src/alu/src/kernel-logic.js +60 -0
- package/dist/src/alu/src/kernel-nd.d.ts +3 -0
- package/dist/src/alu/src/kernel-nd.js +208 -0
- package/dist/src/alu/src/kernel-numeric.d.ts +54 -0
- package/dist/src/alu/src/kernel-numeric.js +366 -0
- package/dist/src/alu/src/operation.d.ts +168 -0
- package/dist/src/alu/src/operation.js +189 -0
- package/dist/src/alu/src/parser.d.ts +221 -0
- package/dist/src/alu/src/parser.js +577 -0
- package/dist/src/alu/src/resonance.d.ts +55 -0
- package/dist/src/alu/src/resonance.js +126 -0
- package/dist/src/alu/src/text.d.ts +31 -0
- package/dist/src/alu/src/text.js +73 -0
- package/dist/src/alu/src/value.d.ts +109 -0
- package/dist/src/alu/src/value.js +300 -0
- package/dist/src/alu/test/alu.test.d.ts +1 -0
- package/dist/src/alu/test/alu.test.js +764 -0
- package/dist/src/bytes.d.ts +14 -0
- package/dist/src/bytes.js +59 -0
- package/dist/src/canon.d.ts +26 -0
- package/dist/src/canon.js +57 -0
- package/dist/src/config.d.ts +111 -0
- package/dist/src/config.js +91 -0
- package/dist/src/derive/src/deduction.d.ts +125 -0
- package/dist/src/derive/src/deduction.js +155 -0
- package/dist/src/derive/src/index.d.ts +7 -0
- package/dist/src/derive/src/index.js +11 -0
- package/dist/src/derive/src/priority-queue.d.ts +20 -0
- package/dist/src/derive/src/priority-queue.js +73 -0
- package/dist/src/derive/src/rewrite.d.ts +56 -0
- package/dist/src/derive/src/rewrite.js +100 -0
- package/dist/src/derive/src/trie.d.ts +90 -0
- package/dist/src/derive/src/trie.js +217 -0
- package/dist/src/derive/test/derive.test.d.ts +1 -0
- package/dist/src/derive/test/derive.test.js +122 -0
- package/dist/src/extension.d.ts +37 -0
- package/dist/src/extension.js +7 -0
- package/dist/src/geometry.d.ts +202 -0
- package/dist/src/geometry.js +575 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +18 -0
- package/dist/src/ingest-cache.d.ts +41 -0
- package/dist/src/ingest-cache.js +161 -0
- package/dist/src/mind/articulation.d.ts +6 -0
- package/dist/src/mind/articulation.js +99 -0
- package/dist/src/mind/attention.d.ts +400 -0
- package/dist/src/mind/attention.js +1763 -0
- package/dist/src/mind/bridge.d.ts +30 -0
- package/dist/src/mind/bridge.js +569 -0
- package/dist/src/mind/canonical.d.ts +29 -0
- package/dist/src/mind/canonical.js +88 -0
- package/dist/src/mind/graph-search.d.ts +285 -0
- package/dist/src/mind/graph-search.js +956 -0
- package/dist/src/mind/index.d.ts +7 -0
- package/dist/src/mind/index.js +5 -0
- package/dist/src/mind/junction.d.ts +124 -0
- package/dist/src/mind/junction.js +317 -0
- package/dist/src/mind/learning.d.ts +47 -0
- package/dist/src/mind/learning.js +248 -0
- package/dist/src/mind/match.d.ts +126 -0
- package/dist/src/mind/match.js +441 -0
- package/dist/src/mind/mechanisms/alu.d.ts +4 -0
- package/dist/src/mind/mechanisms/alu.js +36 -0
- package/dist/src/mind/mechanisms/cast.d.ts +89 -0
- package/dist/src/mind/mechanisms/cast.js +716 -0
- package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
- package/dist/src/mind/mechanisms/confluence.js +225 -0
- package/dist/src/mind/mechanisms/cover.d.ts +6 -0
- package/dist/src/mind/mechanisms/cover.js +203 -0
- package/dist/src/mind/mechanisms/extraction.d.ts +67 -0
- package/dist/src/mind/mechanisms/extraction.js +387 -0
- package/dist/src/mind/mechanisms/recall.d.ts +13 -0
- package/dist/src/mind/mechanisms/recall.js +325 -0
- package/dist/src/mind/mind.d.ts +276 -0
- package/dist/src/mind/mind.js +572 -0
- package/dist/src/mind/pipeline-mechanism.d.ts +142 -0
- package/dist/src/mind/pipeline-mechanism.js +213 -0
- package/dist/src/mind/pipeline.d.ts +20 -0
- package/dist/src/mind/pipeline.js +217 -0
- package/dist/src/mind/primitives.d.ts +70 -0
- package/dist/src/mind/primitives.js +287 -0
- package/dist/src/mind/rationale.d.ts +139 -0
- package/dist/src/mind/rationale.js +163 -0
- package/dist/src/mind/reasoning.d.ts +34 -0
- package/dist/src/mind/reasoning.js +240 -0
- package/dist/src/mind/recognition.d.ts +20 -0
- package/dist/src/mind/recognition.js +390 -0
- package/dist/src/mind/resonance.d.ts +23 -0
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +15 -0
- package/dist/src/mind/trace.js +73 -0
- package/dist/src/mind/traverse.d.ts +113 -0
- package/dist/src/mind/traverse.js +568 -0
- package/dist/src/mind/types.d.ts +289 -0
- package/dist/src/mind/types.js +130 -0
- package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
- package/dist/src/rabitq-ivf/src/database.js +201 -0
- package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
- package/dist/src/rabitq-ivf/src/index.js +4 -0
- package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
- package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
- package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
- package/dist/src/rabitq-ivf/src/prng.js +36 -0
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
- package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
- package/dist/src/sema.d.ts +31 -0
- package/dist/src/sema.js +63 -0
- package/dist/src/store-sqlite.d.ts +184 -0
- package/dist/src/store-sqlite.js +942 -0
- package/dist/src/store.d.ts +663 -0
- package/dist/src/store.js +1618 -0
- package/dist/src/vec.d.ts +31 -0
- package/dist/src/vec.js +109 -0
- package/package.json +1 -1
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
// geometry.ts — every modality is a stream; geometry is only a reading order.
|
|
2
|
+
//
|
|
3
|
+
// 1. Each byte is a leaf — an atom carrying its own vector straight from
|
|
4
|
+
// the alphabet.
|
|
5
|
+
// 2. The river folds leaves upward in fixed-size groups (maxGroup). Items
|
|
6
|
+
// that cross the stable-prefix boundary are split so the prefix folds
|
|
7
|
+
// identically regardless of what follows — pure structural stability.
|
|
8
|
+
// 3. The same rule recurses level after level until one root remains.
|
|
9
|
+
import { addInto, copy, normalize, zeros } from "./vec.js";
|
|
10
|
+
import { sema } from "./sema.js";
|
|
11
|
+
// ---- geometric constants ----
|
|
12
|
+
//
|
|
13
|
+
// Every threshold below is a derived function of the fold's own geometry —
|
|
14
|
+
// dimension D, maxGroup, etc. — never a tuned magic number. They live here
|
|
15
|
+
// (not in a config file) because they follow from the structure itself.
|
|
16
|
+
//
|
|
17
|
+
// MEASUREMENT CAVEAT: these thresholds are compared against RaBitQ-ESTIMATED
|
|
18
|
+
// cosines (1-bit stored codes scored against a 4-bit-quantized query; the
|
|
19
|
+
// index never reranks with exact vectors). The derivations assume an exact
|
|
20
|
+
// cosine; the estimator adds a small, rotation-uniformised error the bars do
|
|
21
|
+
// not model. This is benign for the inequality thresholds (they gate broad
|
|
22
|
+
// regions), but it means NO decision may treat an estimated score as exact —
|
|
23
|
+
// identity in particular is decided by content-addressed resolve(), never by
|
|
24
|
+
// `score >= 1` (see recallByResonance tier 0).
|
|
25
|
+
/** The store's geometric identity bar: cosine ≥ 1 − 1/√D is the similarity at
|
|
26
|
+
* which `intern` already treats two gists as the SAME node. Recall reuses it
|
|
27
|
+
* to accept a near-identical query, and the climb to accept a containing form —
|
|
28
|
+
* one derived constant, never a tuned threshold. NOTE: this fixed bar is
|
|
29
|
+
* the ESTIMATOR floor of an identity claim; a whole-span claim over a span
|
|
30
|
+
* longer than the perception quantum must use the scale-aware
|
|
31
|
+
* {@link identityBar}, which converts the tolerated fraction into bytes. */
|
|
32
|
+
export function mergeThreshold(D) {
|
|
33
|
+
return 1 - 1 / Math.sqrt(D);
|
|
34
|
+
}
|
|
35
|
+
/** The scale-aware IDENTITY bar for a whole-span resonance claim over a span
|
|
36
|
+
* of `len` bytes. Under the linear fold a cosine reads "fraction of aligned
|
|
37
|
+
* shared bytes", so a FIXED cosine bar admits a byte budget that grows with
|
|
38
|
+
* the span: 1 − 1/√D over a 4·√D-byte span tolerates four whole river
|
|
39
|
+
* windows of foreign content while still claiming "near-identical". An
|
|
40
|
+
* identity claim may tolerate at most ONE river window W — the perception
|
|
41
|
+
* quantum, the same single-window budget near-dedup's differsByOneWindow
|
|
42
|
+
* grants — so the bar is 1 − W/len, floored at mergeThreshold(D), below
|
|
43
|
+
* which the RaBitQ estimator cannot certify identity anyway. This is the
|
|
44
|
+
* angle+magnitude form of the identity test: the ANGLE carries the shared
|
|
45
|
+
* fraction, the span's MAGNITUDE (√len, the linear fold's own norm) converts
|
|
46
|
+
* the tolerated fraction into tolerated bytes. Derived from W, D and the
|
|
47
|
+
* span; never tuned. */
|
|
48
|
+
export function identityBar(D, maxGroup, len) {
|
|
49
|
+
return Math.max(mergeThreshold(D), 1 - maxGroup / Math.max(1, len));
|
|
50
|
+
}
|
|
51
|
+
/** The reach bar: half a river quantum, derived from the fold's own geometry.
|
|
52
|
+
* A branch folds up to `maxGroup` children, so two forms that differ in ONE
|
|
53
|
+
* whole child — the smallest distinction perception can mean — sit at cosine
|
|
54
|
+
* ≈ 1 − 1/maxGroup. Half that quantum, 1 − 1/(2·maxGroup), is closer than any
|
|
55
|
+
* single-child difference can be: a positional echo of the same content.
|
|
56
|
+
*
|
|
57
|
+
* Recall uses this as its confidence floor: a query whose nearest resonant
|
|
58
|
+
* form sits below this bar is structurally unrelated to everything in the store
|
|
59
|
+
* — further than any single-child variant — and the system returns null rather
|
|
60
|
+
* than fabricate an answer from an unrelated form. Derived, never tuned. */
|
|
61
|
+
export function reachThreshold(maxGroup) {
|
|
62
|
+
return 1 - 1 / (2 * maxGroup);
|
|
63
|
+
}
|
|
64
|
+
/** The estimator's own noise floor: 1/√D — ONE standard deviation of the
|
|
65
|
+
* cosine between two independent random vectors in D dimensions (the same σ
|
|
66
|
+
* {@link significanceBar} takes three of). It is the smallest difference in
|
|
67
|
+
* cosine that is distinguishable from the rotation-uniformised RaBitQ
|
|
68
|
+
* estimation error (see the MEASUREMENT CAVEAT above): a contrastive margin
|
|
69
|
+
* below it is quantisation noise, not evidence. The consensus climb gates a
|
|
70
|
+
* region's vote on its discriminative margin clearing this floor — the
|
|
71
|
+
* minimal "above noise" bar, one σ, not the stricter 3σ relatedness bar.
|
|
72
|
+
* Derived, never tuned. */
|
|
73
|
+
export function estimatorNoise(D) {
|
|
74
|
+
return 1 / Math.sqrt(D);
|
|
75
|
+
}
|
|
76
|
+
/** The statistical-significance bar for whole-query resonance: 3/√D.
|
|
77
|
+
* In D dimensions the expected cosine of two independent random vectors is 0
|
|
78
|
+
* with standard deviation 1/√D. A cosine ≥ 3/√D is three standard deviations
|
|
79
|
+
* above chance — the query is statistically related to the store, not merely
|
|
80
|
+
* sharing random byte noise. Below this bar the consensus climb (which trusts
|
|
81
|
+
* sub-region resonance) is skipped: there is no evidence the query belongs to
|
|
82
|
+
* the same distribution as the stored content. Derived, never tuned. */
|
|
83
|
+
export function significanceBar(D) {
|
|
84
|
+
return 3 / Math.sqrt(D);
|
|
85
|
+
}
|
|
86
|
+
/** The concept (halo) threshold: the cosine above which two nodes share a
|
|
87
|
+
* distributional concept. A halo is a superposition of episode signatures in
|
|
88
|
+
* D-dimensional space, so the expected cosine between two unrelated halos is 0
|
|
89
|
+
* with standard deviation 1/√D. The structural midpoint 0.5 separates "more
|
|
90
|
+
* similar than not" from noise; the +0.5/√D term adds one half-sigma margin
|
|
91
|
+
* that vanishes as D → ∞, accounting for the wider noise band at lower D
|
|
92
|
+
* without inventing a tuned constant. At D=1024 this gives 0.516, within
|
|
93
|
+
* 3% of 0.5 — existing behavior is preserved while threshold and D move
|
|
94
|
+
* together. Derived, never tuned. */
|
|
95
|
+
export function conceptThreshold(D) {
|
|
96
|
+
return 0.5 + 0.5 / Math.sqrt(D);
|
|
97
|
+
}
|
|
98
|
+
/** The HALF-DOMINANCE predicate: whether a part covering `partLen` of a
|
|
99
|
+
* whole of `wholeLen` covers STRICTLY more than half of it. A span that
|
|
100
|
+
* dominates its whole can no longer discriminate the whole's own content —
|
|
101
|
+
* the one test behind liftAnswer's keep-the-frame rule, collectRegions'
|
|
102
|
+
* wrapper exclusion, and CAST's frame-depth majority (each cites this).
|
|
103
|
+
* CAST's frame-FRACTION gate is the deliberately CLOSED variant (≥ ½ is
|
|
104
|
+
* already unusable there) and stays inline where it is documented.
|
|
105
|
+
* Derived from the structural midpoint, never tuned. */
|
|
106
|
+
export function dominates(partLen, wholeLen) {
|
|
107
|
+
return partLen * 2 > wholeLen;
|
|
108
|
+
}
|
|
109
|
+
/** The consensus-vote significance floor: ln(N) + 1/2, where N is the number
|
|
110
|
+
* of learnt contexts (edge sources). A single region's IDF-weighted vote for
|
|
111
|
+
* an anchor reached through c contexts is at most ln(N/c) ≤ ln(N); the +1/2
|
|
112
|
+
* demands the pooled vote exceed what ONE maximally-specific region could
|
|
113
|
+
* contribute by half a unit — i.e. genuine corroboration beyond a lone
|
|
114
|
+
* region's echo at this corpus scale. The ONE floor both consumers gate on:
|
|
115
|
+
* recallByResonance trusting a climb anchor, and commitVotes admitting a
|
|
116
|
+
* further point of attention. Defined once here so the two can never
|
|
117
|
+
* drift apart. Derived from N, never tuned. */
|
|
118
|
+
export function consensusFloor(N) {
|
|
119
|
+
return Math.log(N) + 1 / 2;
|
|
120
|
+
}
|
|
121
|
+
/** The coverage bar for the reach (interior) index, when vector-similarity
|
|
122
|
+
* gating is used. Returns the concept threshold — the structural midpoint
|
|
123
|
+
* (~0.5 at D=1024) where two forms are "more similar than not."
|
|
124
|
+
*
|
|
125
|
+
* Currently UNUSED in the hot training path: interior nodes are indexed
|
|
126
|
+
* unconditionally (hash-cons dedup bounds the index naturally).
|
|
127
|
+
* Post-hoc structural compaction ({@link Store.compactContentIndex})
|
|
128
|
+
* replaces runtime coverage gating with a batch pass that removes
|
|
129
|
+
* structurally-isolated entries. Derived, never tuned. */
|
|
130
|
+
export function coverageBar(_maxGroup, D) {
|
|
131
|
+
return conceptThreshold(D);
|
|
132
|
+
}
|
|
133
|
+
// ---- folding ----
|
|
134
|
+
//
|
|
135
|
+
// The river fold is a hierarchical prefix network: each level contracts
|
|
136
|
+
// groups of `maxGroup` adjacent items into one via permute-then-add
|
|
137
|
+
// (positional seat binding), recursing until one root remains.
|
|
138
|
+
//
|
|
139
|
+
// FLAT per-level fold — one inline loop per level (foldSlice): no per-group
|
|
140
|
+
// function calls, no Array.slice per group, the permute and add FUSED
|
|
141
|
+
// (`gist[d] += v[seat[d]]`, no scratch buffer), and subtree byte lengths
|
|
142
|
+
// carried incrementally on Folded (the old boundary scan re-walked subtrees
|
|
143
|
+
// every level — O(n log n)). The per-level SUPERPOSITION is byte-identical
|
|
144
|
+
// to the original recursive foldGroup: the same FP additions in the same
|
|
145
|
+
// order.
|
|
146
|
+
//
|
|
147
|
+
// LINEAR fold — intermediate gists are NOT normalized; only the final root is
|
|
148
|
+
// (riverFold's single normalize). This is a deliberate change of similarity
|
|
149
|
+
// semantics from the original per-group normalize, not a cached optimization:
|
|
150
|
+
// the fold is now a pure linear operator — a superposition of positionally-
|
|
151
|
+
// bound leaf vectors — so an interior node carries its span's natural
|
|
152
|
+
// magnitude and a resonance score reads as byte-proportional overlap rather
|
|
153
|
+
// than a scale-free cosine. The mechanisms that depend on that reading
|
|
154
|
+
// compensate for it EXPLICITLY, never silently: the contrastive margin on
|
|
155
|
+
// approximate votes (voteRegions), company signatures decoupling halo company
|
|
156
|
+
// from gist content (sema.ts), and the shared-frame analogy tier (match.ts).
|
|
157
|
+
/** Fold `items[start .. start+count)` in groups of `mg` into `out`.
|
|
158
|
+
*
|
|
159
|
+
* With `force`, the trailing incomplete group (2..mg-1 items) is folded as
|
|
160
|
+
* well — only a lone singleton passes through. The river always folds with
|
|
161
|
+
* force: every level contracts by ~mg, so the tree's DEPTH is a function of
|
|
162
|
+
* ceil(log_mg(n)) alone. Letting leftovers pass through unfolded made depth
|
|
163
|
+
* depend on the exact byte count (39 bytes folded in 3 levels, 41 in 4), and
|
|
164
|
+
* each extra level applies another seat permutation to the whole gist —
|
|
165
|
+
* near-identical inputs straddling such a cliff read as orthogonal
|
|
166
|
+
* (measured: 33-byte-identical prefixes at cos ≈ 0). */
|
|
167
|
+
function foldSlice(space, items, start, count, out, force) {
|
|
168
|
+
const mg = space.maxGroup;
|
|
169
|
+
const D = space.D;
|
|
170
|
+
const complete = count - (count % mg);
|
|
171
|
+
const foldAt = (at, size) => {
|
|
172
|
+
const gist = new Float32Array(D);
|
|
173
|
+
const kids = new Array(size);
|
|
174
|
+
let len = 0;
|
|
175
|
+
for (let k = 0; k < size; k++) {
|
|
176
|
+
const f = items[at + k];
|
|
177
|
+
const seat = space.seats[k].fwd;
|
|
178
|
+
const v = f.tree.v;
|
|
179
|
+
// Fused permute-and-accumulate — same FP ops, same order as the old
|
|
180
|
+
// permuteInto + addInto pair, with no scratch buffer.
|
|
181
|
+
for (let d = 0; d < D; d++)
|
|
182
|
+
gist[d] += v[seat[d]];
|
|
183
|
+
kids[k] = f.tree;
|
|
184
|
+
len += f.len;
|
|
185
|
+
}
|
|
186
|
+
out.push({ tree: sema(gist, null, kids), len });
|
|
187
|
+
};
|
|
188
|
+
for (let i = 0; i < complete; i += mg)
|
|
189
|
+
foldAt(start + i, mg);
|
|
190
|
+
const leftover = count - complete;
|
|
191
|
+
if (leftover === 0)
|
|
192
|
+
return;
|
|
193
|
+
if (force && leftover >= 2)
|
|
194
|
+
foldAt(start + complete, leftover);
|
|
195
|
+
else
|
|
196
|
+
for (let i = complete; i < count; i++)
|
|
197
|
+
out.push(items[start + i]);
|
|
198
|
+
}
|
|
199
|
+
function riverFold(space, row, stableBytes) {
|
|
200
|
+
if (row.length === 0) {
|
|
201
|
+
const z = new Float32Array(space.D);
|
|
202
|
+
return { tree: sema(z, new Uint8Array(0), null), len: 0 };
|
|
203
|
+
}
|
|
204
|
+
let level = row;
|
|
205
|
+
while (level.length > 1) {
|
|
206
|
+
// Find the item index where accumulated bytes reaches stableBytes.
|
|
207
|
+
let boundary = level.length;
|
|
208
|
+
if (stableBytes > 0) {
|
|
209
|
+
let acc = 0;
|
|
210
|
+
for (let i = 0; i < level.length; i++) {
|
|
211
|
+
acc += level[i].len;
|
|
212
|
+
if (acc >= stableBytes) {
|
|
213
|
+
boundary = i + 1;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const next = [];
|
|
219
|
+
if (boundary < level.length) {
|
|
220
|
+
// Prefix folds independently of the suffix — structural stability.
|
|
221
|
+
foldSlice(space, level, 0, boundary, next, true);
|
|
222
|
+
foldSlice(space, level, boundary, level.length - boundary, next, true);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
foldSlice(space, level, 0, level.length, next, true);
|
|
226
|
+
}
|
|
227
|
+
level = next;
|
|
228
|
+
}
|
|
229
|
+
// LINEAR fold — this root normalize is the ONLY normalize of the entire
|
|
230
|
+
// fold; every intermediate gist stays unnormalized (see the folding
|
|
231
|
+
// header). Skipped for a single-leaf input: that root IS the shared
|
|
232
|
+
// alphabet vector (already unit), and normalizing in place would mutate the
|
|
233
|
+
// alphabet itself.
|
|
234
|
+
if (row.length > 1)
|
|
235
|
+
normalize(level[0].tree.v);
|
|
236
|
+
return level[0];
|
|
237
|
+
}
|
|
238
|
+
// ---- public API ----
|
|
239
|
+
function bytesToLeaves(alphabet, bytes) {
|
|
240
|
+
return Array.from(bytes, (b, i) => {
|
|
241
|
+
const v = alphabet.vecs[b];
|
|
242
|
+
return { tree: sema(v, bytes.slice(i, i + 1), null), len: 1 };
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/** Find the longest prefix of `bytes` whose leaf-id signature matches a
|
|
246
|
+
* known branch via `lookup`. Returns the byte-length of that prefix, or 0. */
|
|
247
|
+
export function knownPrefixLength(bytes, leafAt, lookup) {
|
|
248
|
+
const leafIds = [];
|
|
249
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
250
|
+
const lid = leafAt(i);
|
|
251
|
+
if (lid === null)
|
|
252
|
+
break;
|
|
253
|
+
leafIds.push(lid);
|
|
254
|
+
}
|
|
255
|
+
// Match the longest PROPER prefix — a full-length match means the entire
|
|
256
|
+
// input already exists as a stored form (e.g. the flat leaf-id branch
|
|
257
|
+
// stored alongside the structural root). That would hide the true split
|
|
258
|
+
// point and prevent the river from producing the same tree it folded
|
|
259
|
+
// during training, so the structural recognition cannot find the right
|
|
260
|
+
// forms. A proper prefix guarantees at least two regions.
|
|
261
|
+
for (let len = bytes.length - 1; len >= 2; len--) {
|
|
262
|
+
if (lookup(leafIds.slice(0, len)) !== null)
|
|
263
|
+
return len;
|
|
264
|
+
}
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
/** Bytes → Sema tree. `leafAt` and `lookup` are store capabilities for
|
|
268
|
+
* detecting previously-stored prefixes so the river can split at the
|
|
269
|
+
* correct boundary. Pass them through from `perceive`; the geometry
|
|
270
|
+
* computes the stable prefix internally.
|
|
271
|
+
*
|
|
272
|
+
* `boundaries` is the CALLER-computed stable-prefix boundary set (§10.3):
|
|
273
|
+
* strictly-increasing proper byte offsets, each the length of a prefix that
|
|
274
|
+
* is already a stored whole-stream form. When given, the fold splits into
|
|
275
|
+
* the segments between consecutive boundaries — each folded independently,
|
|
276
|
+
* exactly as it folded when it was learned — and the segment roots join
|
|
277
|
+
* LEFT-NESTED (((s₀·s₁)·s₂)…), so every learnt cumulative-context root
|
|
278
|
+
* reappears as an identical subtree (and, by hash-consing, the very same
|
|
279
|
+
* node) inside the grown stream. This is what lets a conversation's next
|
|
280
|
+
* turn extend perception instead of refolding it: identical prefixes
|
|
281
|
+
* produce identical subtrees regardless of what follows them. */
|
|
282
|
+
export function bytesToTree(space, alphabet, bytes, leafAt, lookup, boundaries) {
|
|
283
|
+
if (bytes.length === 0) {
|
|
284
|
+
return sema(alphabet.vecs[0], new Uint8Array(0), null);
|
|
285
|
+
}
|
|
286
|
+
if (boundaries !== undefined && boundaries.length > 0) {
|
|
287
|
+
return stablePrefixFold(space, alphabet, bytes, boundaries);
|
|
288
|
+
}
|
|
289
|
+
const sb = (leafAt && lookup) ? knownPrefixLength(bytes, leafAt, lookup) : 0;
|
|
290
|
+
return riverFold(space, bytesToLeaves(alphabet, bytes), sb > 0 ? sb : bytes.length).tree;
|
|
291
|
+
}
|
|
292
|
+
/** The stable-prefix segmented fold (§10.3). Each segment between
|
|
293
|
+
* consecutive boundaries folds PLAINLY and independently; segment roots
|
|
294
|
+
* join left-nested, and only the final root is normalized (the linear-fold
|
|
295
|
+
* contract: one normalize per perception). A segment's own inner splits
|
|
296
|
+
* need no recursion here: a nested learnt prefix is itself an earlier
|
|
297
|
+
* boundary, so the left-nested join reproduces every intermediate learnt
|
|
298
|
+
* root ((s₀·s₁) IS the root the store learnt for the first two segments'
|
|
299
|
+
* bytes, and so on). */
|
|
300
|
+
function stablePrefixFold(space, alphabet, bytes, boundaries) {
|
|
301
|
+
const cuts = [];
|
|
302
|
+
let prev = 0;
|
|
303
|
+
for (const b of boundaries) {
|
|
304
|
+
if (b > prev && b < bytes.length) {
|
|
305
|
+
cuts.push(b);
|
|
306
|
+
prev = b;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (cuts.length === 0) {
|
|
310
|
+
return riverFold(space, bytesToLeaves(alphabet, bytes), bytes.length).tree;
|
|
311
|
+
}
|
|
312
|
+
const edges = [0, ...cuts, bytes.length];
|
|
313
|
+
const segs = [];
|
|
314
|
+
for (let i = 0; i + 1 < edges.length; i++) {
|
|
315
|
+
const seg = bytes.subarray(edges[i], edges[i + 1]);
|
|
316
|
+
segs.push(riverFoldRaw(space, bytesToLeaves(alphabet, seg)));
|
|
317
|
+
}
|
|
318
|
+
let cur = segs[0];
|
|
319
|
+
for (let i = 1; i < segs.length; i++)
|
|
320
|
+
cur = fold2(space, cur, segs[i]);
|
|
321
|
+
normalize(cur.tree.v);
|
|
322
|
+
return cur.tree;
|
|
323
|
+
}
|
|
324
|
+
/** {@link stablePrefixFold} with incremental segment reuse — same cuts, same
|
|
325
|
+
* segment folds, same left-nested join, same single root normalize; `prev`
|
|
326
|
+
* only elides recomputing segments whose [start,end) offsets it already
|
|
327
|
+
* folded over a byte-identical prefix (the caller keys the cache by
|
|
328
|
+
* content). Requires a non-empty effective boundary set. */
|
|
329
|
+
export function stablePrefixFoldIncremental(space, alphabet, bytes, boundaries, prev) {
|
|
330
|
+
const cuts = [];
|
|
331
|
+
let prevB = 0;
|
|
332
|
+
for (const b of boundaries) {
|
|
333
|
+
if (b > prevB && b < bytes.length) {
|
|
334
|
+
cuts.push(b);
|
|
335
|
+
prevB = b;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const edges = [0, ...cuts, bytes.length];
|
|
339
|
+
const segs = [];
|
|
340
|
+
for (let i = 0; i + 1 < edges.length; i++) {
|
|
341
|
+
const hit = prev !== undefined && prev.edges[i] === edges[i] &&
|
|
342
|
+
prev.edges[i + 1] === edges[i + 1]
|
|
343
|
+
? prev.segs[i]
|
|
344
|
+
: undefined;
|
|
345
|
+
segs.push(hit ??
|
|
346
|
+
riverFoldRaw(space, bytesToLeaves(alphabet, bytes.subarray(edges[i], edges[i + 1]))));
|
|
347
|
+
}
|
|
348
|
+
if (segs.length === 1) {
|
|
349
|
+
// Degenerate boundary set — the plain fold, as stablePrefixFold does.
|
|
350
|
+
const tree = riverFold(space, bytesToLeaves(alphabet, bytes), bytes.length).tree;
|
|
351
|
+
return { tree, fold: { edges, segs } };
|
|
352
|
+
}
|
|
353
|
+
let cur = segs[0];
|
|
354
|
+
for (let i = 1; i < segs.length; i++)
|
|
355
|
+
cur = fold2(space, cur, segs[i]);
|
|
356
|
+
normalize(cur.tree.v);
|
|
357
|
+
return { tree: cur.tree, fold: { edges, segs } };
|
|
358
|
+
}
|
|
359
|
+
/** Join two folded items as one 2-kid branch — the top-level join of the
|
|
360
|
+
* stable-prefix fold, identical FP ops to foldSlice's seat-bound
|
|
361
|
+
* accumulation over a group of two. Unnormalized (interior). */
|
|
362
|
+
function fold2(space, a, b) {
|
|
363
|
+
const D = space.D;
|
|
364
|
+
const gist = new Float32Array(D);
|
|
365
|
+
const kids = [a.tree, b.tree];
|
|
366
|
+
for (let k = 0; k < 2; k++) {
|
|
367
|
+
const seat = space.seats[k].fwd;
|
|
368
|
+
const v = kids[k].v;
|
|
369
|
+
for (let d = 0; d < D; d++)
|
|
370
|
+
gist[d] += v[seat[d]];
|
|
371
|
+
}
|
|
372
|
+
return { tree: sema(gist, null, kids), len: a.len + b.len };
|
|
373
|
+
}
|
|
374
|
+
/** Plain river fold WITHOUT the final root normalize — the segment-level
|
|
375
|
+
* building block of {@link stablePrefixFold} (interiors must keep their
|
|
376
|
+
* byte-proportional magnitude; only the whole perception's root is ever
|
|
377
|
+
* normalized). Exported so callers that COMPOSE already-existing structural
|
|
378
|
+
* parts into a hypothetical synthetic root (see {@link composeStructuralGist})
|
|
379
|
+
* can feed the same raw primitive instead of duplicating its mathematics. */
|
|
380
|
+
export function riverFoldRaw(space, row) {
|
|
381
|
+
if (row.length === 0) {
|
|
382
|
+
const z = new Float32Array(space.D);
|
|
383
|
+
return { tree: sema(z, new Uint8Array(0), null), len: 0 };
|
|
384
|
+
}
|
|
385
|
+
if (row.length === 1)
|
|
386
|
+
return row[0];
|
|
387
|
+
let level = row;
|
|
388
|
+
while (level.length > 1) {
|
|
389
|
+
const next = [];
|
|
390
|
+
foldSlice(space, level, 0, level.length, next, true);
|
|
391
|
+
level = next;
|
|
392
|
+
}
|
|
393
|
+
return level[0];
|
|
394
|
+
}
|
|
395
|
+
/** Synthesize a hypothetical internal structure from already-existing
|
|
396
|
+
* structural vectors — NOT from bytes. This is the raw positional
|
|
397
|
+
* composition the linear river fold already uses (see the folding header
|
|
398
|
+
* above): each part is positionally bound into its own seat, its natural
|
|
399
|
+
* span magnitude is preserved, the parts are linearly superposed, and only
|
|
400
|
+
* the final synthetic root is normalized. It never calls {@link gistOf}
|
|
401
|
+
* (there is no `gistOf` here — geometry.ts has no store), never perceives a
|
|
402
|
+
* concatenated byte string, and never interns or stores a new node: the
|
|
403
|
+
* result is an opaque, ungrounded Vec for an ANN probe only. */
|
|
404
|
+
export function composeStructuralGist(space, parts) {
|
|
405
|
+
const foldedParts = [];
|
|
406
|
+
for (const part of parts) {
|
|
407
|
+
if (part.len <= 0)
|
|
408
|
+
continue;
|
|
409
|
+
const direction = copy(part.v);
|
|
410
|
+
normalize(direction);
|
|
411
|
+
const scaled = zeros(space.D);
|
|
412
|
+
addInto(scaled, direction, Math.sqrt(part.len));
|
|
413
|
+
foldedParts.push({ tree: sema(scaled), len: part.len });
|
|
414
|
+
}
|
|
415
|
+
if (foldedParts.length === 0)
|
|
416
|
+
return zeros(space.D);
|
|
417
|
+
const rawRoot = riverFoldRaw(space, foldedParts);
|
|
418
|
+
const result = copy(rawRoot.tree.v);
|
|
419
|
+
normalize(result);
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
/** Plain bytes→tree (identical to capability-less {@link bytesToTree}) that
|
|
423
|
+
* also RETURNS its pyramid, reusing `prev` — the pyramid of a PROPER
|
|
424
|
+
* prefix of `bytes` (caller guarantees content match and
|
|
425
|
+
* prev.bytes < bytes.length). */
|
|
426
|
+
export function bytesToTreePyramid(space, alphabet, bytes, prev) {
|
|
427
|
+
if (bytes.length === 0) {
|
|
428
|
+
return {
|
|
429
|
+
tree: sema(alphabet.vecs[0], new Uint8Array(0), null),
|
|
430
|
+
pyramid: { levels: [], bytes: 0 },
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
// ZERO GROWTH: the previous pyramid already covers every byte — its top
|
|
434
|
+
// level holds the finished, normalized root. Refolding here would not
|
|
435
|
+
// only waste the work: when the whole span is one full block, the refold's
|
|
436
|
+
// top item is a REUSED raw interior of `prev`, and the final normalize
|
|
437
|
+
// below would mutate that shared raw block in place, corrupting every
|
|
438
|
+
// later incremental fold built on `prev`.
|
|
439
|
+
if (prev && prev.bytes === bytes.length && prev.levels.length > 0) {
|
|
440
|
+
return {
|
|
441
|
+
tree: prev.levels[prev.levels.length - 1][0].tree,
|
|
442
|
+
pyramid: prev,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const mg = space.maxGroup;
|
|
446
|
+
const reusable = (L) => {
|
|
447
|
+
// prev's TOPMOST level holds its normalized ROOT — reusable blocks must
|
|
448
|
+
// be raw interiors, so the top level is always excluded.
|
|
449
|
+
if (!prev || L > prev.levels.length - 2)
|
|
450
|
+
return null;
|
|
451
|
+
return prev.levels[L];
|
|
452
|
+
};
|
|
453
|
+
// Level 0: reuse the prefix's leaf items wholesale (all are full blocks).
|
|
454
|
+
const lv0 = reusable(0);
|
|
455
|
+
const row = lv0
|
|
456
|
+
? [...lv0, ...bytesToLeaves(alphabet, bytes.subarray(prev.bytes))]
|
|
457
|
+
: bytesToLeaves(alphabet, bytes);
|
|
458
|
+
const levels = [row];
|
|
459
|
+
let level = row;
|
|
460
|
+
let L = 0;
|
|
461
|
+
while (level.length > 1) {
|
|
462
|
+
let next = [];
|
|
463
|
+
// Reuse the leading FULL blocks of prev's level L+1 (len == mg^(L+1),
|
|
464
|
+
// wholly inside the prefix); the rule below folds the rest from this
|
|
465
|
+
// level's items exactly as the from-scratch fold would.
|
|
466
|
+
const blockLen = mg ** (L + 1);
|
|
467
|
+
let reused = 0;
|
|
468
|
+
const cand = reusable(L + 1);
|
|
469
|
+
if (cand) {
|
|
470
|
+
const maxFull = Math.floor(prev.bytes / blockLen);
|
|
471
|
+
while (reused < maxFull && reused < cand.length &&
|
|
472
|
+
cand[reused].len === blockLen)
|
|
473
|
+
reused++;
|
|
474
|
+
for (let i = 0; i < reused; i++)
|
|
475
|
+
next.push(cand[i]);
|
|
476
|
+
}
|
|
477
|
+
foldSlice(space, level, reused * mg, level.length - reused * mg, next, true);
|
|
478
|
+
levels.push(next);
|
|
479
|
+
level = next;
|
|
480
|
+
L++;
|
|
481
|
+
}
|
|
482
|
+
if (row.length > 1)
|
|
483
|
+
normalize(level[0].tree.v);
|
|
484
|
+
return {
|
|
485
|
+
tree: level[0].tree,
|
|
486
|
+
pyramid: { levels, bytes: bytes.length },
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
// ---- n-D Hilbert curve ----
|
|
490
|
+
function gridDims(grid) {
|
|
491
|
+
if (grid.dims && grid.dims.length > 0)
|
|
492
|
+
return grid.dims.slice();
|
|
493
|
+
const dims = [grid.height, grid.width];
|
|
494
|
+
if (grid.channels > 1)
|
|
495
|
+
dims.push(grid.channels);
|
|
496
|
+
return dims;
|
|
497
|
+
}
|
|
498
|
+
function hilbertPoint(index, n, bits) {
|
|
499
|
+
const x = new Array(n).fill(0);
|
|
500
|
+
for (let b = 0; b < bits; b++) {
|
|
501
|
+
for (let d = 0; d < n; d++) {
|
|
502
|
+
const bit = (index >>> (b * n + (n - 1 - d))) & 1;
|
|
503
|
+
x[d] |= bit << b;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const N = 1 << bits;
|
|
507
|
+
let t = x[n - 1] >> 1;
|
|
508
|
+
for (let i = n - 1; i > 0; i--)
|
|
509
|
+
x[i] ^= x[i - 1];
|
|
510
|
+
x[0] ^= t;
|
|
511
|
+
for (let q = 2; q !== N; q <<= 1) {
|
|
512
|
+
const p = q - 1;
|
|
513
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
514
|
+
if (x[i] & q)
|
|
515
|
+
x[0] ^= p;
|
|
516
|
+
else {
|
|
517
|
+
t = (x[0] ^ x[i]) & p;
|
|
518
|
+
x[0] ^= t;
|
|
519
|
+
x[i] ^= t;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return x;
|
|
524
|
+
}
|
|
525
|
+
export function hilbertBytes(grid) {
|
|
526
|
+
const dims = gridDims(grid);
|
|
527
|
+
const n = dims.length;
|
|
528
|
+
if (n === 0 || grid.data.length === 0)
|
|
529
|
+
return new Uint8Array(0);
|
|
530
|
+
if (n === 1)
|
|
531
|
+
return grid.data.slice(0, dims[0]);
|
|
532
|
+
const maxAxis = Math.max(...dims);
|
|
533
|
+
const bits = Math.max(1, Math.ceil(Math.log2(maxAxis)));
|
|
534
|
+
const side = 1 << bits;
|
|
535
|
+
const total = Math.pow(side, n);
|
|
536
|
+
const stride = new Array(n);
|
|
537
|
+
stride[n - 1] = 1;
|
|
538
|
+
for (let d = n - 2; d >= 0; d--)
|
|
539
|
+
stride[d] = stride[d + 1] * dims[d + 1];
|
|
540
|
+
const out = [];
|
|
541
|
+
for (let h = 0; h < total; h++) {
|
|
542
|
+
const pt = hilbertPoint(h, n, bits);
|
|
543
|
+
let inside = true, flat = 0;
|
|
544
|
+
for (let d = 0; d < n; d++) {
|
|
545
|
+
if (pt[d] >= dims[d]) {
|
|
546
|
+
inside = false;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
flat += pt[d] * stride[d];
|
|
550
|
+
}
|
|
551
|
+
if (inside)
|
|
552
|
+
out.push(grid.data[flat]);
|
|
553
|
+
}
|
|
554
|
+
return Uint8Array.from(out);
|
|
555
|
+
}
|
|
556
|
+
export function gridToTree(space, alphabet, grid) {
|
|
557
|
+
return bytesToTree(space, alphabet, hilbertBytes(grid));
|
|
558
|
+
}
|
|
559
|
+
export function stackGrids(frames) {
|
|
560
|
+
if (frames.length === 0) {
|
|
561
|
+
return { width: 0, height: 0, channels: 0, data: new Uint8Array(0) };
|
|
562
|
+
}
|
|
563
|
+
const frameDims = gridDims(frames[0]);
|
|
564
|
+
const per = frames[0].data.length;
|
|
565
|
+
const data = new Uint8Array(per * frames.length);
|
|
566
|
+
for (let i = 0; i < frames.length; i++)
|
|
567
|
+
data.set(frames[i].data, i * per);
|
|
568
|
+
return {
|
|
569
|
+
width: 0,
|
|
570
|
+
height: 0,
|
|
571
|
+
channels: 0,
|
|
572
|
+
dims: [frames.length, ...frameDims],
|
|
573
|
+
data,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./bytes.js";
|
|
2
|
+
export * from "./vec.js";
|
|
3
|
+
export * from "./sema.js";
|
|
4
|
+
export * from "./alphabet.js";
|
|
5
|
+
export * from "./geometry.js";
|
|
6
|
+
export * from "./store.js";
|
|
7
|
+
export * from "./mind/rationale.js";
|
|
8
|
+
export * from "./mind/index.js";
|
|
9
|
+
export * from "./store-sqlite.js";
|
|
10
|
+
export * from "./config.js";
|
|
11
|
+
export * from "./extension.js";
|
|
12
|
+
export * from "./canon.js";
|
|
13
|
+
export * from "./ingest-cache.js";
|
|
14
|
+
export { IvfIndex, Prng, RaBitQuantizer, VectorDatabase, } from "./rabitq-ivf/src/index.js";
|
|
15
|
+
export type { DatabaseOptions, ExternalId, IvfConfig, IvfHit, QueryContext, QueryResult, RaBitQOptions, StorageStats, } from "./rabitq-ivf/src/index.js";
|
|
16
|
+
export * from "./derive/src/index.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// sema — an elementary, recursive, weight-free multimodal mind.
|
|
2
|
+
// One structure, one verb, one memory. See AGENTS.md for the full algorithm.
|
|
3
|
+
export * from "./bytes.js";
|
|
4
|
+
export * from "./vec.js";
|
|
5
|
+
export * from "./sema.js";
|
|
6
|
+
export * from "./alphabet.js";
|
|
7
|
+
export * from "./geometry.js";
|
|
8
|
+
export * from "./store.js";
|
|
9
|
+
export * from "./mind/rationale.js";
|
|
10
|
+
export * from "./mind/index.js";
|
|
11
|
+
export * from "./store-sqlite.js";
|
|
12
|
+
export * from "./config.js";
|
|
13
|
+
export * from "./extension.js";
|
|
14
|
+
export * from "./canon.js";
|
|
15
|
+
export * from "./ingest-cache.js";
|
|
16
|
+
// rabitq-ivf: the partitioned (IVF) vector index over 1-bit RaBitQ codes.
|
|
17
|
+
export { IvfIndex, Prng, RaBitQuantizer, VectorDatabase, } from "./rabitq-ivf/src/index.js";
|
|
18
|
+
export * from "./derive/src/index.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Mind } from "./mind/index.js";
|
|
2
|
+
import { type Sema } from "./sema.js";
|
|
3
|
+
import type { Input } from "./mind/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* An ingest cache layered over a Mind.
|
|
6
|
+
*
|
|
7
|
+
* Usage (drop-in for mind.ingest in training loops):
|
|
8
|
+
* const ci = new CachedIngest(mind);
|
|
9
|
+
* await ci.ingest(ctx, cont); // caches both sides
|
|
10
|
+
* await ci.ingest("some text"); // caches the single text
|
|
11
|
+
* await ci.ingest([[ctx, cont]]); // array form
|
|
12
|
+
*
|
|
13
|
+
* All other Mind methods (respond, save, …) are accessed on `ci.mind`.
|
|
14
|
+
*/
|
|
15
|
+
export declare class CachedIngest {
|
|
16
|
+
readonly mind: Mind;
|
|
17
|
+
/** One unified, content-addressed memo of interned inputs. Each value holds
|
|
18
|
+
* Float32 root vector (D·4 bytes) plus a small id array, so the byte budget
|
|
19
|
+
* is spent on the inputs most worth remembering (LRU). */
|
|
20
|
+
private _memo;
|
|
21
|
+
hits: number;
|
|
22
|
+
misses: number;
|
|
23
|
+
constructor(mind: Mind);
|
|
24
|
+
private newMemo;
|
|
25
|
+
private keyOf;
|
|
26
|
+
ingest(input: Input | (Input | [Input, Input])[], second?: Input): Promise<(Sema & {
|
|
27
|
+
id: number;
|
|
28
|
+
}) | undefined>;
|
|
29
|
+
/** Resolve an input to its interned nodes, from the memo when its content has
|
|
30
|
+
* been seen before, else by the Mind's OWN untracked {@link deposit}
|
|
31
|
+
* (perceive + intern + sub-span/containment indexing — identical to the
|
|
32
|
+
* direct path; those writes are durable and idempotent, so a later memo hit
|
|
33
|
+
* legitimately skips them). This is the single expensive operation both
|
|
34
|
+
* ingest paths share; caching it here is what makes every repeated input —
|
|
35
|
+
* in any role — cheap. */
|
|
36
|
+
private resolveInput;
|
|
37
|
+
private ingestPair;
|
|
38
|
+
private ingestOne;
|
|
39
|
+
clear(): void;
|
|
40
|
+
get size(): number;
|
|
41
|
+
}
|