@hviana/sema 0.1.8 → 0.2.0

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.
@@ -157,8 +157,13 @@ export function coverageBar(_maxGroup, D) {
157
157
  /** Fold `items[start .. start+count)` in groups of `mg` into `out`.
158
158
  *
159
159
  * With `force`, the trailing incomplete group (2..mg-1 items) is folded as
160
- * well — only a lone singleton passes through guaranteeing progress when
161
- * the caller detected a stall. */
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). */
162
167
  function foldSlice(space, items, start, count, out, force) {
163
168
  const mg = space.maxGroup;
164
169
  const D = space.D;
@@ -213,21 +218,13 @@ function riverFold(space, row, stableBytes) {
213
218
  const next = [];
214
219
  if (boundary < level.length) {
215
220
  // Prefix folds independently of the suffix — structural stability.
216
- foldSlice(space, level, 0, boundary, next, false);
217
- foldSlice(space, level, boundary, level.length - boundary, next, false);
221
+ foldSlice(space, level, 0, boundary, next, true);
222
+ foldSlice(space, level, boundary, level.length - boundary, next, true);
218
223
  }
219
224
  else {
220
- foldSlice(space, level, 0, level.length, next, false);
221
- }
222
- if (next.length === level.length) {
223
- // Stuck — every group was incomplete. Force-fold to break the stall.
224
- const forced = [];
225
- foldSlice(space, next, 0, next.length, forced, true);
226
- level = forced;
227
- }
228
- else {
229
- level = next;
225
+ foldSlice(space, level, 0, level.length, next, true);
230
226
  }
227
+ level = next;
231
228
  }
232
229
  // LINEAR fold — this root normalize is the ONLY normalize of the entire
233
230
  // fold; every intermediate gist stays unnormalized (see the folding
@@ -353,15 +350,8 @@ function riverFoldRaw(space, row) {
353
350
  let level = row;
354
351
  while (level.length > 1) {
355
352
  const next = [];
356
- foldSlice(space, level, 0, level.length, next, false);
357
- if (next.length === level.length) {
358
- const forced = [];
359
- foldSlice(space, next, 0, next.length, forced, true);
360
- level = forced;
361
- }
362
- else {
363
- level = next;
364
- }
353
+ foldSlice(space, level, 0, level.length, next, true);
354
+ level = next;
365
355
  }
366
356
  return level[0];
367
357
  }
@@ -420,15 +410,7 @@ export function bytesToTreePyramid(space, alphabet, bytes, prev) {
420
410
  for (let i = 0; i < reused; i++)
421
411
  next.push(cand[i]);
422
412
  }
423
- foldSlice(space, level, reused * mg, level.length - reused * mg, next, false);
424
- if (next.length === level.length) {
425
- // Stuck — same force-fold as riverFold (reuse cannot cause it: any
426
- // reused block replaces a whole folded group, so reused > 0 implies
427
- // progress).
428
- const forced = [];
429
- foldSlice(space, next, 0, next.length, forced, true);
430
- next = forced;
431
- }
413
+ foldSlice(space, level, reused * mg, level.length - reused * mg, next, true);
432
414
  levels.push(next);
433
415
  level = next;
434
416
  L++;
@@ -288,7 +288,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
288
288
  indexOf(dominant.ctx, p.ctx, 0) < 0 &&
289
289
  indexOf(p.ctx, dominant.ctx, 0) < 0 &&
290
290
  indexOf(query, p.ctx, 0) < 0) {
291
- analogs.push({ anchor: p.anchor, point: p });
291
+ analogs.push({ anchor: p.anchor, point: p, src: p });
292
292
  }
293
293
  // Reach through to the point's continuation targets regardless
294
294
  // of the point's own context length: when the point is a leaf
@@ -306,7 +306,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
306
306
  indexOf(nctx, dominant.ctx, 0) >= 0 ||
307
307
  indexOf(query, nctx, 0) >= 0)
308
308
  continue;
309
- analogs.push({ anchor: nid, point: null });
309
+ analogs.push({ anchor: nid, point: null, src: p });
310
310
  }
311
311
  }
312
312
  let bestAnalog = null;
@@ -404,13 +404,13 @@ export async function counterfactualTransfer(ctx, query, pre) {
404
404
  // A halo-mediated act (the analogy gate) plus two seat projections.
405
405
  CONCEPT + STEP + STEP,
406
406
  // What comparison READ: the dominant's own aligned runs, plus the
407
- // analog's aligned runs when it was itself an aligned point (a nextOf
408
- // descendant was never aligned to the query directly, so it
409
- // contributes no accounted span its seat is graph-reached, not
410
- // query-matched).
407
+ // aligned runs of the point that named the analog the analog itself
408
+ // when it was an aligned point, else the source point whose
409
+ // continuation edge reached it (that alignment IS the query evidence
410
+ // the hop rests on).
411
411
  [
412
412
  ...runSpans(dominant),
413
- ...(bestAnalog.point !== null ? runSpans(bestAnalog.point) : []),
413
+ ...runSpans(bestAnalog.point ?? bestAnalog.src),
414
414
  ]);
415
415
  }
416
416
  t?.done(results.map((r) => rItem(r.bytes, "answer")), results.length > 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hviana/sema",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "Sema: a non-parametric, instance-based reasoning system.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
package/src/geometry.ts CHANGED
@@ -189,8 +189,13 @@ export interface Grid {
189
189
  /** Fold `items[start .. start+count)` in groups of `mg` into `out`.
190
190
  *
191
191
  * With `force`, the trailing incomplete group (2..mg-1 items) is folded as
192
- * well — only a lone singleton passes through guaranteeing progress when
193
- * the caller detected a stall. */
192
+ * well — only a lone singleton passes through. The river always folds with
193
+ * force: every level contracts by ~mg, so the tree's DEPTH is a function of
194
+ * ceil(log_mg(n)) alone. Letting leftovers pass through unfolded made depth
195
+ * depend on the exact byte count (39 bytes folded in 3 levels, 41 in 4), and
196
+ * each extra level applies another seat permutation to the whole gist —
197
+ * near-identical inputs straddling such a cliff read as orthogonal
198
+ * (measured: 33-byte-identical prefixes at cos ≈ 0). */
194
199
  function foldSlice(
195
200
  space: Space,
196
201
  items: Folded[],
@@ -251,19 +256,12 @@ function riverFold(space: Space, row: Folded[], stableBytes: number): Folded {
251
256
  const next: Folded[] = [];
252
257
  if (boundary < level.length) {
253
258
  // Prefix folds independently of the suffix — structural stability.
254
- foldSlice(space, level, 0, boundary, next, false);
255
- foldSlice(space, level, boundary, level.length - boundary, next, false);
259
+ foldSlice(space, level, 0, boundary, next, true);
260
+ foldSlice(space, level, boundary, level.length - boundary, next, true);
256
261
  } else {
257
- foldSlice(space, level, 0, level.length, next, false);
258
- }
259
- if (next.length === level.length) {
260
- // Stuck — every group was incomplete. Force-fold to break the stall.
261
- const forced: Folded[] = [];
262
- foldSlice(space, next, 0, next.length, forced, true);
263
- level = forced;
264
- } else {
265
- level = next;
262
+ foldSlice(space, level, 0, level.length, next, true);
266
263
  }
264
+ level = next;
267
265
  }
268
266
  // LINEAR fold — this root normalize is the ONLY normalize of the entire
269
267
  // fold; every intermediate gist stays unnormalized (see the folding
@@ -413,14 +411,8 @@ function riverFoldRaw(space: Space, row: Folded[]): Folded {
413
411
  let level = row;
414
412
  while (level.length > 1) {
415
413
  const next: Folded[] = [];
416
- foldSlice(space, level, 0, level.length, next, false);
417
- if (next.length === level.length) {
418
- const forced: Folded[] = [];
419
- foldSlice(space, next, 0, next.length, forced, true);
420
- level = forced;
421
- } else {
422
- level = next;
423
- }
414
+ foldSlice(space, level, 0, level.length, next, true);
415
+ level = next;
424
416
  }
425
417
  return level[0];
426
418
  }
@@ -509,16 +501,8 @@ export function bytesToTreePyramid(
509
501
  reused * mg,
510
502
  level.length - reused * mg,
511
503
  next,
512
- false,
504
+ true,
513
505
  );
514
- if (next.length === level.length) {
515
- // Stuck — same force-fold as riverFold (reuse cannot cause it: any
516
- // reused block replaces a whole folded group, so reused > 0 implies
517
- // progress).
518
- const forced: Folded[] = [];
519
- foldSlice(space, next, 0, next.length, forced, true);
520
- next = forced;
521
- }
522
506
  levels.push(next);
523
507
  level = next;
524
508
  L++;
@@ -368,6 +368,11 @@ export async function counterfactualTransfer(
368
368
  /** The point this candidate came from, or null when it is a nextOf
369
369
  * descendant — then seatOfNode supplies the seat. */
370
370
  point: Point | null;
371
+ /** For a nextOf descendant: the aligned point whose continuation edge
372
+ * named it. Its runs ARE the query evidence this analog rests on
373
+ * (the hop was reached THROUGH that alignment), so the comparison
374
+ * schema accounts them. */
375
+ src: Point;
371
376
  }
372
377
  const analogs: AnalogCandidate[] = [];
373
378
  for (const p of points) {
@@ -380,7 +385,7 @@ export async function counterfactualTransfer(
380
385
  indexOf(p.ctx, dominant.ctx, 0) < 0 &&
381
386
  indexOf(query, p.ctx, 0) < 0
382
387
  ) {
383
- analogs.push({ anchor: p.anchor, point: p });
388
+ analogs.push({ anchor: p.anchor, point: p, src: p });
384
389
  }
385
390
  // Reach through to the point's continuation targets regardless
386
391
  // of the point's own context length: when the point is a leaf
@@ -399,7 +404,7 @@ export async function counterfactualTransfer(
399
404
  indexOf(nctx, dominant.ctx, 0) >= 0 ||
400
405
  indexOf(query, nctx, 0) >= 0
401
406
  ) continue;
402
- analogs.push({ anchor: nid, point: null });
407
+ analogs.push({ anchor: nid, point: null, src: p });
403
408
  }
404
409
  }
405
410
  let bestAnalog: AnalogCandidate | null = null;
@@ -521,13 +526,13 @@ export async function counterfactualTransfer(
521
526
  // A halo-mediated act (the analogy gate) plus two seat projections.
522
527
  CONCEPT + STEP + STEP,
523
528
  // What comparison READ: the dominant's own aligned runs, plus the
524
- // analog's aligned runs when it was itself an aligned point (a nextOf
525
- // descendant was never aligned to the query directly, so it
526
- // contributes no accounted span its seat is graph-reached, not
527
- // query-matched).
529
+ // aligned runs of the point that named the analog the analog itself
530
+ // when it was an aligned point, else the source point whose
531
+ // continuation edge reached it (that alignment IS the query evidence
532
+ // the hop rests on).
528
533
  [
529
534
  ...runSpans(dominant),
530
- ...(bestAnalog.point !== null ? runSpans(bestAnalog.point) : []),
535
+ ...runSpans(bestAnalog.point ?? bestAnalog.src),
531
536
  ],
532
537
  );
533
538
  }