@hviana/sema 0.1.5 → 0.1.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.
Files changed (160) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1687 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +17 -42
  51. package/dist/src/geometry.js +235 -266
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +749 -871
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +126 -185
  82. package/dist/src/mind/mind.d.ts +129 -154
  83. package/dist/src/mind/mind.js +258 -288
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +8 -32
  89. package/dist/src/mind/primitives.js +99 -117
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +183 -208
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +16 -58
  101. package/dist/src/mind/traverse.js +304 -357
  102. package/dist/src/mind/types.d.ts +120 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +169 -166
  117. package/dist/src/store-sqlite.js +762 -662
  118. package/dist/src/store.d.ts +586 -630
  119. package/dist/src/store.js +1423 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +13 -10
  123. package/package.json +1 -1
  124. package/src/alu/README.md +1 -1
  125. package/src/alu/src/index.ts +1 -1
  126. package/src/config.ts +19 -27
  127. package/src/index.ts +6 -11
  128. package/src/rabitq-ivf/README.md +56 -0
  129. package/src/rabitq-ivf/src/database.ts +276 -0
  130. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  131. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  132. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  133. package/src/store-sqlite.ts +196 -9
  134. package/src/store.ts +8 -32
  135. package/test/08-storage.test.mjs +3 -3
  136. package/test/14-scaling.test.mjs +2 -2
  137. package/test/35-ivf.test.mjs +263 -0
  138. package/test/36-bloom.test.mjs +123 -0
  139. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  140. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  141. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  142. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  143. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  144. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  145. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  146. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  147. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  148. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  149. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  150. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  151. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  152. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  153. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  154. package/src/rabitq-hnsw/README.md +0 -303
  155. package/src/rabitq-hnsw/src/database.ts +0 -492
  156. package/src/rabitq-hnsw/src/heap.ts +0 -90
  157. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  158. package/src/rabitq-hnsw/src/store.ts +0 -994
  159. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  160. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -13,34 +13,29 @@ import type { Input } from "./mind/index.js";
13
13
  * All other Mind methods (respond, save, …) are accessed on `ci.mind`.
14
14
  */
15
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(
27
- input: Input | (Input | [Input, Input])[],
28
- second?: Input,
29
- ): Promise<
30
- (Sema & {
31
- id: number;
32
- }) | undefined
33
- >;
34
- /** Resolve an input to its interned nodes, from the memo when its content has
35
- * been seen before, else by the Mind's OWN untracked {@link deposit}
36
- * (perceive + intern + sub-span/containment indexing — identical to the
37
- * direct path; those writes are durable and idempotent, so a later memo hit
38
- * legitimately skips them). This is the single expensive operation both
39
- * ingest paths share; caching it here is what makes every repeated input —
40
- * in any role — cheap. */
41
- private resolveInput;
42
- private ingestPair;
43
- private ingestOne;
44
- clear(): void;
45
- get size(): number;
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;
46
41
  }
@@ -31,11 +31,7 @@
31
31
  // and drifted: no indexSubSpans/addContainer, wrong part-link stride). Only
32
32
  // the memo layer lives here. Behaviour is identical to mind.ingest() (links
33
33
  // and halos are reinforced on every call), just faster when content repeats.
34
- import {
35
- deposit,
36
- dispatchIngest,
37
- ingestOne as depositOne,
38
- } from "./mind/learning.js";
34
+ import { deposit, dispatchIngest, ingestOne as depositOne, } from "./mind/learning.js";
39
35
  import { bindSeat, companySignature } from "./sema.js";
40
36
  import { BoundedMap } from "./store.js";
41
37
  /**
@@ -50,131 +46,116 @@ import { BoundedMap } from "./store.js";
50
46
  * All other Mind methods (respond, save, …) are accessed on `ci.mind`.
51
47
  */
52
48
  export class CachedIngest {
53
- mind;
54
- /** One unified, content-addressed memo of interned inputs. Each value holds
55
- * Float32 root vector (D·4 bytes) plus a small id array, so the byte budget
56
- * is spent on the inputs most worth remembering (LRU). */
57
- _memo;
58
- hits = 0;
59
- misses = 0;
60
- constructor(mind) {
61
- this.mind = mind;
62
- this._memo = this.newMemo();
63
- }
64
- newMemo() {
65
- return new BoundedMap(
66
- this.mind.cfg.store.ingestCacheBytes,
67
- (c) => c.rootV.byteLength + c.partIds.length * 4 + 8 + c.keyBytes,
68
- );
69
- }
70
- // ── cache key ───────────────────────────────────────────────────────────
71
- // Content key for an input, or null when the input cannot be keyed reliably
72
- // — in which case it BYPASSES the cache (recomputed every time) rather than
73
- // risk a collision returning the wrong nodes. A string is its own key; raw
74
- // bytes hash by FNV-1a (+ length, so a hash collision still needs an equal
75
- // length to alias); a Grid / Grid[] has no cheap stable key, so it bypasses.
76
- keyOf(input) {
77
- if (typeof input === "string") {
78
- return "S:" + input;
49
+ mind;
50
+ /** One unified, content-addressed memo of interned inputs. Each value holds
51
+ * Float32 root vector (D·4 bytes) plus a small id array, so the byte budget
52
+ * is spent on the inputs most worth remembering (LRU). */
53
+ _memo;
54
+ hits = 0;
55
+ misses = 0;
56
+ constructor(mind) {
57
+ this.mind = mind;
58
+ this._memo = this.newMemo();
79
59
  }
80
- if (input instanceof Uint8Array) {
81
- // FNV-1a no spread onto the call stack, safe on large binary inputs.
82
- let h = 0x811c9dc5 >>> 0;
83
- for (let i = 0; i < input.length; i++) {
84
- h ^= input[i];
85
- h = Math.imul(h, 0x01000193) >>> 0;
86
- }
87
- return "B:" + (h >>> 0).toString(16) + ":" + input.length;
60
+ newMemo() {
61
+ return new BoundedMap(this.mind.cfg.store.ingestCacheBytes, (c) => c.rootV.byteLength + c.partIds.length * 4 + 8 + c.keyBytes);
88
62
  }
89
- return null; // Grid / Grid[] — bypass the cache
90
- }
91
- // ── public API ────────────────────────────────────────────────────────
92
- async ingest(input, second) {
93
- // One shape-reading for both ingest paths see {@link dispatchIngest}.
94
- return dispatchIngest(
95
- input,
96
- second,
97
- (i) => this.ingestOne(i),
98
- (a, b) => this.ingestPair(a, b),
99
- );
100
- }
101
- // ── the one cached step: perceive + intern + index ONE input ──────────────
102
- /** Resolve an input to its interned nodes, from the memo when its content has
103
- * been seen before, else by the Mind's OWN untracked {@link deposit}
104
- * (perceive + intern + sub-span/containment indexing — identical to the
105
- * direct path; those writes are durable and idempotent, so a later memo hit
106
- * legitimately skips them). This is the single expensive operation both
107
- * ingest paths share; caching it here is what makes every repeated input —
108
- * in any role — cheap. */
109
- async resolveInput(input) {
110
- const key = this.keyOf(input);
111
- if (key !== null) {
112
- const cached = this._memo.get(key);
113
- if (cached) {
114
- this.hits++;
115
- return cached;
116
- }
63
+ // ── cache key ───────────────────────────────────────────────────────────
64
+ // Content key for an input, or null when the input cannot be keyed reliably
65
+ // in which case it BYPASSES the cache (recomputed every time) rather than
66
+ // risk a collision returning the wrong nodes. A string is its own key; raw
67
+ // bytes hash by FNV-1a (+ length, so a hash collision still needs an equal
68
+ // length to alias); a Grid / Grid[] has no cheap stable key, so it bypasses.
69
+ keyOf(input) {
70
+ if (typeof input === "string")
71
+ return "S:" + input;
72
+ if (input instanceof Uint8Array) {
73
+ // FNV-1a — no spread onto the call stack, safe on large binary inputs.
74
+ let h = 0x811c9dc5 >>> 0;
75
+ for (let i = 0; i < input.length; i++) {
76
+ h ^= input[i];
77
+ h = Math.imul(h, 0x01000193) >>> 0;
78
+ }
79
+ return "B:" + (h >>> 0).toString(16) + ":" + input.length;
80
+ }
81
+ return null; // Grid / Grid[] bypass the cache
117
82
  }
118
- this.misses++;
119
- // The full deposit, untracked (a continuation must not break the tracked
120
- // deposit chain). `partIds` are the root's immediate children, already
121
- // interned by the deposit free to capture. Only the root vector and
122
- // ids are retained; the tree is GC'd.
123
- const { tree, rootId, ids } = await deposit(this.mind, input, false);
124
- const partIds = tree.kids ? tree.kids.map((k) => ids.get(k)) : [rootId];
125
- const entry = {
126
- rootV: tree.v,
127
- rootId,
128
- partIds,
129
- // ~2 bytes per UTF-16 code unit — the key is retained by the map.
130
- keyBytes: key !== null ? key.length * 2 : 0,
131
- };
132
- if (key !== null) {
133
- this._memo.set(key, entry);
83
+ // ── public API ────────────────────────────────────────────────────────
84
+ async ingest(input, second) {
85
+ // One shape-reading for both ingest paths see {@link dispatchIngest}.
86
+ return dispatchIngest(input, second, (i) => this.ingestOne(i), (a, b) => this.ingestPair(a, b));
134
87
  }
135
- return entry;
136
- }
137
- // ── pair ingest ───────────────────────────────────────────────────────
138
- async ingestPair(ctx, cont) {
139
- // The CONTEXT side is TRACKED (it continues the Mind's deposit chain and
140
- // is the growing, unique-per-turn side — a cache miss anyway), via the
141
- // same {@link deposit} the direct path uses; the continuation resolves
142
- // through the memo.
143
- const c = await deposit(this.mind, ctx, true);
144
- const b = await this.resolveInput(cont);
145
- // EDGE on the FULL context (disambiguates a shared pivot). HALO only on the
146
- // CHANGED NODES — the coherent new subtrees this turn adds — so a recurring
147
- // shared prefix never collects halo mass. (See Mind.ingestPair.)
148
- await this.mind.store.link(c.rootId, b.rootId);
149
- // Halos pour company SIGNATURES (identity), not gists (content) — see
150
- // companySignature in sema.ts.
151
- const contSeat = bindSeat(
152
- this.mind.space,
153
- companySignature(this.mind.space, b.rootId),
154
- 1,
155
- );
156
- for (const part of c.changed) {
157
- const partId = c.ids.get(part);
158
- await this.mind.store.pourHalo(partId, contSeat);
159
- await this.mind.store.pourHalo(
160
- b.rootId,
161
- bindSeat(this.mind.space, companySignature(this.mind.space, partId), 0),
162
- );
88
+ // ── the one cached step: perceive + intern + index ONE input ──────────────
89
+ /** Resolve an input to its interned nodes, from the memo when its content has
90
+ * been seen before, else by the Mind's OWN untracked {@link deposit}
91
+ * (perceive + intern + sub-span/containment indexing — identical to the
92
+ * direct path; those writes are durable and idempotent, so a later memo hit
93
+ * legitimately skips them). This is the single expensive operation both
94
+ * ingest paths share; caching it here is what makes every repeated input —
95
+ * in any role — cheap. */
96
+ async resolveInput(input) {
97
+ const key = this.keyOf(input);
98
+ if (key !== null) {
99
+ const cached = this._memo.get(key);
100
+ if (cached) {
101
+ this.hits++;
102
+ return cached;
103
+ }
104
+ }
105
+ this.misses++;
106
+ // The full deposit, untracked (a continuation must not break the tracked
107
+ // deposit chain). `partIds` are the root's immediate children, already
108
+ // interned by the deposit — free to capture. Only the root vector and
109
+ // ids are retained; the tree is GC'd.
110
+ const { tree, rootId, ids } = await deposit(this.mind, input, false);
111
+ const partIds = tree.kids
112
+ ? tree.kids.map((k) => ids.get(k))
113
+ : [rootId];
114
+ const entry = {
115
+ rootV: tree.v,
116
+ rootId,
117
+ partIds,
118
+ // ~2 bytes per UTF-16 code unit — the key is retained by the map.
119
+ keyBytes: key !== null ? key.length * 2 : 0,
120
+ };
121
+ if (key !== null)
122
+ this._memo.set(key, entry);
123
+ return entry;
124
+ }
125
+ // ── pair ingest ───────────────────────────────────────────────────────
126
+ async ingestPair(ctx, cont) {
127
+ // The CONTEXT side is TRACKED (it continues the Mind's deposit chain and
128
+ // is the growing, unique-per-turn side — a cache miss anyway), via the
129
+ // same {@link deposit} the direct path uses; the continuation resolves
130
+ // through the memo.
131
+ const c = await deposit(this.mind, ctx, true);
132
+ const b = await this.resolveInput(cont);
133
+ // EDGE on the FULL context (disambiguates a shared pivot). HALO only on the
134
+ // CHANGED NODES — the coherent new subtrees this turn adds — so a recurring
135
+ // shared prefix never collects halo mass. (See Mind.ingestPair.)
136
+ await this.mind.store.link(c.rootId, b.rootId);
137
+ // Halos pour company SIGNATURES (identity), not gists (content) — see
138
+ // companySignature in sema.ts.
139
+ const contSeat = bindSeat(this.mind.space, companySignature(this.mind.space, b.rootId), 1);
140
+ for (const part of c.changed) {
141
+ const partId = c.ids.get(part);
142
+ await this.mind.store.pourHalo(partId, contSeat);
143
+ await this.mind.store.pourHalo(b.rootId, bindSeat(this.mind.space, companySignature(this.mind.space, partId), 0));
144
+ }
145
+ }
146
+ // ── one-side ingest ───────────────────────────────────────────────────
147
+ async ingestOne(input) {
148
+ // A bare experience is the direct path verbatim — tracked deposit, root
149
+ // marked a resonance target, part chain linked at the maxGroup stride.
150
+ // (The tracked side is a memo miss by design, so there is nothing for the
151
+ // cache to add here.)
152
+ return depositOne(this.mind, input);
153
+ }
154
+ // ── helpers ───────────────────────────────────────────────────────────
155
+ clear() {
156
+ this._memo = this.newMemo();
157
+ }
158
+ get size() {
159
+ return this._memo.size;
163
160
  }
164
- }
165
- // ── one-side ingest ───────────────────────────────────────────────────
166
- async ingestOne(input) {
167
- // A bare experience is the direct path verbatim — tracked deposit, root
168
- // marked a resonance target, part chain linked at the maxGroup stride.
169
- // (The tracked side is a memo miss by design, so there is nothing for the
170
- // cache to add here.)
171
- return depositOne(this.mind, input);
172
- }
173
- // ── helpers ───────────────────────────────────────────────────────────
174
- clear() {
175
- this._memo = this.newMemo();
176
- }
177
- get size() {
178
- return this._memo.size;
179
- }
180
161
  }
@@ -3,8 +3,4 @@ import type { MindContext } from "./types.js";
3
3
  * in the answer, find a concept-sibling in the query (by halo resonance)
4
4
  * and substitute the asker's wording. The search's own cover mechanism
5
5
  * splices the substitutes into the answer exactly where the forms sit. */
6
- export declare function articulate(
7
- ctx: MindContext,
8
- answer: Uint8Array,
9
- query: Uint8Array | null,
10
- ): Promise<Uint8Array>;
6
+ export declare function articulate(ctx: MindContext, answer: Uint8Array, query: Uint8Array | null): Promise<Uint8Array>;
@@ -13,122 +13,87 @@ import { rItem, rNode, traceDerivation } from "./trace.js";
13
13
  * and substitute the asker's wording. The search's own cover mechanism
14
14
  * splices the substitutes into the answer exactly where the forms sit. */
15
15
  export async function articulate(ctx, answer, query) {
16
- if (!query || query.length < 2 || answer.length < 2) {
17
- return answer;
18
- }
19
- const store = ctx.store;
20
- const t = ctx.trace?.enter("articulate", [
21
- rItem(answer, "answer"),
22
- rItem(query, "query"),
23
- ]);
24
- const keep = (note) => {
25
- t?.done([rItem(answer, "answer")], note);
26
- return answer;
27
- };
28
- const qCandidates = [];
29
- for (const s of recognise(ctx, query).sites) {
30
- // payload < 0 is a SINGLE-BYTE leaf (byte leaves occupy the implicit
31
- // negative id range −256…−1 see store.ts). A one-byte form's halo is
32
- // promiscuous (it keeps company with everything it ever neighboured), so
33
- // admitting it as a voice or a revoicing target lets e.g. the digit "4"
34
- // be "re-voiced" into an unrelated digit the query happens to contain.
35
- // Articulation therefore operates on multi-byte forms only.
36
- if (s.payload < 0) {
37
- continue;
16
+ if (!query || query.length < 2 || answer.length < 2)
17
+ return answer;
18
+ const store = ctx.store;
19
+ const t = ctx.trace?.enter("articulate", [
20
+ rItem(answer, "answer"),
21
+ rItem(query, "query"),
22
+ ]);
23
+ const keep = (note) => {
24
+ t?.done([rItem(answer, "answer")], note);
25
+ return answer;
26
+ };
27
+ const qCandidates = [];
28
+ for (const s of recognise(ctx, query).sites) {
29
+ // payload < 0 is a SINGLE-BYTE leaf (byte leaves occupy the implicit
30
+ // negative id range −256…−1 see store.ts). A one-byte form's halo is
31
+ // promiscuous (it keeps company with everything it ever neighboured), so
32
+ // admitting it as a voice or a revoicing target lets e.g. the digit "4"
33
+ // be "re-voiced" into an unrelated digit the query happens to contain.
34
+ // Articulation therefore operates on multi-byte forms only.
35
+ if (s.payload < 0)
36
+ continue;
37
+ const h = store.halo(s.payload);
38
+ if (!h)
39
+ continue;
40
+ qCandidates.push({
41
+ start: s.start,
42
+ end: s.end,
43
+ payload: {
44
+ halo: h,
45
+ bytes: query.slice(s.start, s.end),
46
+ node: s.payload,
47
+ },
48
+ });
38
49
  }
39
- const h = store.halo(s.payload);
40
- if (!h) {
41
- continue;
50
+ if (qCandidates.length === 0) {
51
+ return keep("no asker concept to revoice — answer unchanged");
42
52
  }
43
- qCandidates.push({
44
- start: s.start,
45
- end: s.end,
46
- payload: {
47
- halo: h,
48
- bytes: query.slice(s.start, s.end),
49
- node: s.payload,
50
- },
51
- });
52
- }
53
- if (qCandidates.length === 0) {
54
- return keep("no asker concept to revoice — answer unchanged");
55
- }
56
- if (coverSequence(query.length, qCandidates).spans.length === 0) {
57
- return keep("no asker concept to revoice — answer unchanged");
58
- }
59
- const voices = qCandidates.map((s) => s.payload);
60
- const ans = recognise(ctx, answer);
61
- const substitutions = new Map();
62
- for (const s of ans.sites) {
63
- // Same single-byte-leaf exclusion as the query loop above.
64
- if (s.payload < 0) {
65
- continue;
53
+ if (coverSequence(query.length, qCandidates).spans.length === 0) {
54
+ return keep("no asker concept to revoice — answer unchanged");
66
55
  }
67
- const h = store.halo(s.payload);
68
- if (!h) {
69
- continue;
56
+ const voices = qCandidates.map((s) => s.payload);
57
+ const ans = recognise(ctx, answer);
58
+ const substitutions = new Map();
59
+ for (const s of ans.sites) {
60
+ // Same single-byte-leaf exclusion as the query loop above.
61
+ if (s.payload < 0)
62
+ continue;
63
+ const h = store.halo(s.payload);
64
+ if (!h)
65
+ continue;
66
+ const found = bestHaloMate(ctx, h, voices, (v) => v.halo);
67
+ if (!found)
68
+ continue;
69
+ const voice = found.item;
70
+ if (voice.node === s.payload || contains(ctx, voice.node, s.payload)) {
71
+ continue;
72
+ }
73
+ substitutions.set(s.payload, voice.bytes);
70
74
  }
71
- const found = bestHaloMate(ctx, h, voices, (v) => v.halo);
72
- if (!found) {
73
- continue;
75
+ if (substitutions.size === 0) {
76
+ return keep("no answer form is a synonym of an asker concept");
74
77
  }
75
- const voice = found.item;
76
- if (voice.node === s.payload || contains(ctx, voice.node, s.payload)) {
77
- continue;
78
+ ctx.trace?.step("substitute", [...substitutions.keys()].map((n) => rNode(ctx, n, "answer-form")), [...substitutions.values()].map((b) => rItem(b, "asker-voice")), `revoice ${substitutions.size} answer form(s) in the asker's own words (synonym splice)`);
79
+ const voicedSites = ans.sites.filter((s) => substitutions.has(s.payload));
80
+ const tArtCover = ctx.trace?.enter("cover", [
81
+ ...voicedSites.map((s) => rItem(answer.subarray(s.start, s.end), "form", s.payload, [
82
+ s.start,
83
+ s.end,
84
+ ])),
85
+ ]);
86
+ const solved = ctx.search.cover(answer.length, voicedSites, new Map(), ans.leaves, ans.splits, substitutions, undefined, undefined, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
87
+ const segs = solved && solved.segs;
88
+ tArtCover?.done(segs === null
89
+ ? []
90
+ : segs.map((s) => rItem(s.bytes, s.rec ? "voiced" : "kept", s.node, [s.i, s.j])), segs === null
91
+ ? "the revoiced cover did not compose"
92
+ : "lightest derivation of the revoiced answer");
93
+ const result = segs && spliceAll(segs);
94
+ if (result) {
95
+ t?.done([rItem(result, "voiced", ctx.store.findLeaf(result.subarray(0, 1)) ?? undefined)], "splice the asker's wording into the answer where the same concept appears");
96
+ return result;
78
97
  }
79
- substitutions.set(s.payload, voice.bytes);
80
- }
81
- if (substitutions.size === 0) {
82
- return keep("no answer form is a synonym of an asker concept");
83
- }
84
- ctx.trace?.step(
85
- "substitute",
86
- [...substitutions.keys()].map((n) => rNode(ctx, n, "answer-form")),
87
- [...substitutions.values()].map((b) => rItem(b, "asker-voice")),
88
- `revoice ${substitutions.size} answer form(s) in the asker's own words (synonym splice)`,
89
- );
90
- const voicedSites = ans.sites.filter((s) => substitutions.has(s.payload));
91
- const tArtCover = ctx.trace?.enter("cover", [
92
- ...voicedSites.map((s) =>
93
- rItem(answer.subarray(s.start, s.end), "form", s.payload, [
94
- s.start,
95
- s.end,
96
- ])
97
- ),
98
- ]);
99
- const solved = ctx.search.cover(
100
- answer.length,
101
- voicedSites,
102
- new Map(),
103
- ans.leaves,
104
- ans.splits,
105
- substitutions,
106
- undefined,
107
- undefined,
108
- ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined,
109
- );
110
- const segs = solved && solved.segs;
111
- tArtCover?.done(
112
- segs === null
113
- ? []
114
- : segs.map((s) =>
115
- rItem(s.bytes, s.rec ? "voiced" : "kept", s.node, [s.i, s.j])
116
- ),
117
- segs === null
118
- ? "the revoiced cover did not compose"
119
- : "lightest derivation of the revoiced answer",
120
- );
121
- const result = segs && spliceAll(segs);
122
- if (result) {
123
- t?.done(
124
- [rItem(
125
- result,
126
- "voiced",
127
- ctx.store.findLeaf(result.subarray(0, 1)) ?? undefined,
128
- )],
129
- "splice the asker's wording into the answer where the same concept appears",
130
- );
131
- return result;
132
- }
133
- return keep("the revoiced cover did not compose — answer unchanged");
98
+ return keep("the revoiced cover did not compose — answer unchanged");
134
99
  }