bireactive 0.2.4 → 0.3.1

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 (132) hide show
  1. package/dist/animation/anim.js +4 -0
  2. package/dist/automerge/doc-cell.d.ts +20 -0
  3. package/dist/automerge/doc-cell.js +80 -0
  4. package/dist/automerge/index.d.ts +3 -0
  5. package/dist/automerge/index.js +12 -0
  6. package/dist/automerge/reconcile.d.ts +5 -0
  7. package/dist/automerge/reconcile.js +63 -0
  8. package/dist/coll.d.ts +7 -7
  9. package/dist/core/_counts.d.ts +48 -0
  10. package/dist/core/_counts.js +58 -0
  11. package/dist/core/cell.d.ts +182 -123
  12. package/dist/core/cell.js +1140 -721
  13. package/dist/core/debug.d.ts +25 -0
  14. package/dist/core/debug.js +121 -0
  15. package/dist/core/index.d.ts +9 -14
  16. package/dist/core/index.js +9 -14
  17. package/dist/core/lenses/aggregates.d.ts +1 -1
  18. package/dist/core/lenses/aggregates.js +4 -3
  19. package/dist/core/lenses/closed-form-policies.js +14 -9
  20. package/dist/core/lenses/decompositions.js +3 -3
  21. package/dist/core/lenses/domain-aggregates.js +5 -5
  22. package/dist/core/lenses/geometry.d.ts +1 -1
  23. package/dist/core/lenses/geometry.js +6 -7
  24. package/dist/core/lenses/index.d.ts +1 -0
  25. package/dist/core/lenses/index.js +1 -0
  26. package/dist/core/lenses/memory.d.ts +2 -2
  27. package/dist/core/lenses/memory.js +3 -3
  28. package/dist/core/lenses/snap.d.ts +18 -0
  29. package/dist/core/lenses/snap.js +145 -0
  30. package/dist/core/lenses/typed-factor.js +4 -3
  31. package/dist/core/optic.d.ts +13 -0
  32. package/dist/core/optic.js +44 -0
  33. package/dist/core/optics.d.ts +10 -0
  34. package/dist/core/optics.js +30 -0
  35. package/dist/core/store.d.ts +10 -0
  36. package/dist/core/store.js +85 -0
  37. package/dist/core/traits.d.ts +1 -0
  38. package/dist/core/values/audio.js +4 -5
  39. package/dist/core/values/box.js +7 -7
  40. package/dist/core/values/canvas.js +15 -18
  41. package/dist/core/values/color.js +5 -5
  42. package/dist/core/values/field.d.ts +70 -0
  43. package/dist/core/values/field.js +230 -0
  44. package/dist/core/values/gpu.d.ts +4 -2
  45. package/dist/core/values/gpu.js +11 -4
  46. package/dist/core/values/matrix.js +7 -7
  47. package/dist/core/values/num.d.ts +1 -1
  48. package/dist/core/values/num.js +1 -1
  49. package/dist/core/values/pose.js +4 -4
  50. package/dist/core/values/range.js +6 -6
  51. package/dist/core/values/str.js +8 -8
  52. package/dist/core/values/template.d.ts +1 -1
  53. package/dist/core/values/template.js +2 -1
  54. package/dist/core/values/transform.js +7 -7
  55. package/dist/core/values/tri.js +3 -3
  56. package/dist/core/values/vec.js +8 -12
  57. package/dist/ext/timeline.js +2 -2
  58. package/dist/formats/cst.d.ts +127 -0
  59. package/dist/formats/cst.js +280 -0
  60. package/dist/formats/edn.d.ts +2 -0
  61. package/dist/formats/edn.js +301 -0
  62. package/dist/formats/index.d.ts +6 -0
  63. package/dist/formats/index.js +8 -0
  64. package/dist/formats/json.d.ts +2 -0
  65. package/dist/formats/json.js +332 -0
  66. package/dist/formats/lens.d.ts +8 -0
  67. package/dist/formats/lens.js +51 -0
  68. package/dist/formats/toml.d.ts +2 -0
  69. package/dist/formats/toml.js +526 -0
  70. package/dist/formats/yaml.d.ts +2 -0
  71. package/dist/formats/yaml.js +661 -0
  72. package/dist/index.d.ts +10 -0
  73. package/dist/index.js +10 -0
  74. package/dist/jsx-dev-runtime.d.ts +2 -0
  75. package/dist/jsx-dev-runtime.js +5 -0
  76. package/dist/jsx-runtime.d.ts +54 -0
  77. package/dist/jsx-runtime.js +219 -0
  78. package/dist/learn/data.d.ts +49 -0
  79. package/dist/learn/data.js +181 -0
  80. package/dist/learn/index.d.ts +3 -0
  81. package/dist/learn/index.js +6 -0
  82. package/dist/learn/lens-net.d.ts +63 -0
  83. package/dist/learn/lens-net.js +219 -0
  84. package/dist/learn/mlp.d.ts +77 -0
  85. package/dist/learn/mlp.js +292 -0
  86. package/dist/propagators/csp.d.ts +13 -0
  87. package/dist/propagators/csp.js +52 -0
  88. package/dist/propagators/flex.d.ts +31 -0
  89. package/dist/propagators/flex.js +189 -0
  90. package/dist/propagators/graph.d.ts +73 -0
  91. package/dist/propagators/graph.js +543 -0
  92. package/dist/propagators/index.d.ts +8 -6
  93. package/dist/propagators/index.js +15 -6
  94. package/dist/propagators/lattice.d.ts +45 -0
  95. package/dist/propagators/lattice.js +113 -0
  96. package/dist/propagators/layout.d.ts +1 -27
  97. package/dist/propagators/layout.js +6 -175
  98. package/dist/propagators/numeric.d.ts +17 -0
  99. package/dist/propagators/numeric.js +93 -0
  100. package/dist/propagators/solver.d.ts +51 -0
  101. package/dist/propagators/solver.js +175 -0
  102. package/dist/schema/index.d.ts +1 -0
  103. package/dist/schema/index.js +3 -0
  104. package/dist/schema/lens.d.ts +121 -0
  105. package/dist/schema/lens.js +429 -0
  106. package/dist/shapes/annular-sector.js +4 -4
  107. package/dist/shapes/button.js +1 -1
  108. package/dist/shapes/circle.js +1 -1
  109. package/dist/shapes/drag-behaviors.d.ts +56 -0
  110. package/dist/shapes/drag-behaviors.js +102 -0
  111. package/dist/shapes/drag-spec.d.ts +52 -0
  112. package/dist/shapes/drag-spec.js +112 -0
  113. package/dist/shapes/handle.js +2 -2
  114. package/dist/shapes/index.d.ts +3 -1
  115. package/dist/shapes/index.js +3 -1
  116. package/dist/shapes/interaction.d.ts +2 -3
  117. package/dist/shapes/interaction.js +77 -56
  118. package/dist/shapes/label.js +7 -1
  119. package/dist/shapes/layout.d.ts +47 -1
  120. package/dist/shapes/layout.js +60 -2
  121. package/dist/shapes/rect.js +7 -7
  122. package/dist/shapes/shape.js +8 -8
  123. package/dist/web/diagram.js +2 -2
  124. package/package.json +24 -2
  125. package/dist/propagators/network.d.ts +0 -52
  126. package/dist/propagators/network.js +0 -185
  127. package/dist/propagators/propagator.d.ts +0 -12
  128. package/dist/propagators/propagator.js +0 -16
  129. package/dist/propagators/range.d.ts +0 -45
  130. package/dist/propagators/range.js +0 -147
  131. package/dist/propagators/relations.d.ts +0 -60
  132. package/dist/propagators/relations.js +0 -343
@@ -0,0 +1,25 @@
1
+ import { Cell } from "./cell.js";
2
+ type SomeCell = Cell<any>;
3
+ /** Short, stable display name: the cell's `name`, else `Ctor#n`. */
4
+ export declare function label(c: SomeCell): string;
5
+ /** `source` (no getter), `lens` (writable derived), or `computed` (read-only derived). */
6
+ export declare function kind(c: SomeCell): "source" | "lens" | "computed";
7
+ /** Upstream cells: eager lens parents unioned with dynamic forward deps. */
8
+ export declare function upstream(c: SomeCell): Cell<unknown>[];
9
+ /** One-line summary: `name = value [kind]` plus upstream labels, if any. */
10
+ export declare function explain(c: SomeCell): string;
11
+ export interface DumpOpts {
12
+ /** Max upstream depth to descend. Default `Infinity`. */
13
+ depth?: number;
14
+ /** Include `= value` in each line. Default `true`. */
15
+ values?: boolean;
16
+ }
17
+ /** Render the upstream graph of `root` as an indented tree (cycle-safe). */
18
+ export declare function dumpGraph(root: SomeCell, opts?: DumpOpts): string;
19
+ /** Run `fn` and collect the source cells written during it (back-writes included,
20
+ * since a view write commits through its sources' `_writeSource`). */
21
+ export declare function traceWrites<T>(fn: () => T): {
22
+ result: T;
23
+ writes: Cell<unknown>[];
24
+ };
25
+ export {};
@@ -0,0 +1,121 @@
1
+ // debug.ts — read-only inspection of the cell graph (dev-time, no hot-path cost).
2
+ //
3
+ // Three small tools: `explain` (one line about a cell), `dumpGraph` (the upstream
4
+ // dependency tree), and `traceWrites` (which sources a block of work actually
5
+ // wrote). All walk the same edges the engine maintains — lens parents
6
+ // (`parentEdges`, eager) unioned with dynamic forward deps (`deps`, post-read) —
7
+ // and label cells by their optional `name` (falling back to `Ctor#n`). Nothing
8
+ // here mutates graph state; reads go through `peek` so they don't track.
9
+ import { Cell, isLens, isReadonly, setCellWriteHook } from "./cell.js";
10
+ const edges = (c) => c;
11
+ const erase = (c) => c;
12
+ const ids = new WeakMap();
13
+ let nextId = 1;
14
+ function idOf(c) {
15
+ let i = ids.get(c);
16
+ if (i === undefined) {
17
+ i = nextId++;
18
+ ids.set(c, i);
19
+ }
20
+ return i;
21
+ }
22
+ /** Short, stable display name: the cell's `name`, else `Ctor#n`. */
23
+ export function label(c) {
24
+ return c.name ?? `${c.constructor.name}#${idOf(erase(c))}`;
25
+ }
26
+ /** `source` (no getter), `lens` (writable derived), or `computed` (read-only derived). */
27
+ export function kind(c) {
28
+ return isLens(c) ? "lens" : isReadonly(c) ? "computed" : "source";
29
+ }
30
+ function short(v) {
31
+ try {
32
+ if (typeof v === "string")
33
+ return JSON.stringify(v);
34
+ if (v === null || v === undefined || typeof v !== "object")
35
+ return String(v);
36
+ const s = JSON.stringify(v);
37
+ if (s === undefined)
38
+ return Object.prototype.toString.call(v);
39
+ return s.length > 48 ? `${s.slice(0, 47)}…` : s;
40
+ }
41
+ catch {
42
+ return "?";
43
+ }
44
+ }
45
+ /** Upstream cells: eager lens parents unioned with dynamic forward deps. */
46
+ export function upstream(c) {
47
+ const out = [];
48
+ const seen = new Set();
49
+ const push = (n) => {
50
+ if (n instanceof Cell && !seen.has(n)) {
51
+ seen.add(n);
52
+ out.push(n);
53
+ }
54
+ };
55
+ for (let e = edges(erase(c)).parentEdges; e !== undefined; e = e.nextParent)
56
+ push(e.parent);
57
+ for (let l = edges(erase(c)).deps; l !== undefined; l = l.nextDep)
58
+ push(l.dep);
59
+ return out;
60
+ }
61
+ /** One-line summary: `name = value [kind]` plus upstream labels, if any. */
62
+ export function explain(c) {
63
+ let v;
64
+ try {
65
+ v = c.peek();
66
+ }
67
+ catch {
68
+ v = "?";
69
+ }
70
+ const ups = upstream(c);
71
+ const tail = ups.length > 0 ? ` ← ${ups.map(label).join(", ")}` : "";
72
+ return `${label(c)} = ${short(v)} [${kind(c)}]${tail}`;
73
+ }
74
+ /** Render the upstream graph of `root` as an indented tree (cycle-safe). */
75
+ export function dumpGraph(root, opts = {}) {
76
+ const maxDepth = opts.depth ?? Number.POSITIVE_INFINITY;
77
+ const withValues = opts.values ?? true;
78
+ const lines = [];
79
+ const path = new Set();
80
+ const line = (c, indent) => {
81
+ if (!withValues)
82
+ return `${indent}${label(c)} [${kind(c)}]`;
83
+ let v;
84
+ try {
85
+ v = c.peek();
86
+ }
87
+ catch {
88
+ v = "?";
89
+ }
90
+ return `${indent}${label(c)} = ${short(v)} [${kind(c)}]`;
91
+ };
92
+ const walk = (c, indent, depth) => {
93
+ if (path.has(c)) {
94
+ lines.push(`${indent}${label(c)} ↺`); // back-edge into the current path
95
+ return;
96
+ }
97
+ lines.push(line(c, indent));
98
+ if (depth >= maxDepth)
99
+ return;
100
+ path.add(c);
101
+ for (const u of upstream(c))
102
+ walk(u, `${indent} `, depth + 1);
103
+ path.delete(c);
104
+ };
105
+ walk(root, "", 0);
106
+ return lines.join("\n");
107
+ }
108
+ /** Run `fn` and collect the source cells written during it (back-writes included,
109
+ * since a view write commits through its sources' `_writeSource`). */
110
+ export function traceWrites(fn) {
111
+ const writes = [];
112
+ const restore = setCellWriteHook(c => {
113
+ writes.push(c);
114
+ });
115
+ try {
116
+ return { result: fn(), writes };
117
+ }
118
+ finally {
119
+ restore();
120
+ }
121
+ }
@@ -1,33 +1,28 @@
1
- export { batch, Cell, type CellOptions, cachedDerive, cell, derive, effect, field, type Init, type Inner, isCell, isLens, isReadonly, lazy, lens, type Network, network, type Read, reader, readNow, type StatefulBwd, type StatefulLensSpec, setCellWriteHook, transitiveDeps, untracked, type Val, type Writable, type WritableBrand, } from "./cell.js";
1
+ export { type Counts, counts, resetCounts, snapshotCounts, withCounts } from "./_counts.js";
2
+ export { batch, Cell, type CellOptions, cachedDerive, cell, derive, effect, fieldLens, fieldOf, type Init, type Inner, isCell, isLens, isReadonly, lazy, lens, type Network, network, type Optic, type Read, reader, readNow, SKIP, type Skip, type StatefulBwd, type StatefulBwd1, type StatefulLensSpec, type StatefulLensSpec1, setCellWriteHook, settle, transitiveDeps, untracked, type Val, type Writable, type WritableBrand, } from "./cell.js";
3
+ export { type DumpOpts, dumpGraph, explain, kind as cellKind, label as cellLabel, traceWrites, upstream, } from "./debug.js";
2
4
  export { bezier2, bezier3 } from "./derived-geometry.js";
3
5
  export * from "./lenses/index.js";
4
6
  export { each, type Lifecycle } from "./lifecycle.js";
7
+ export { atKey, compose, iso, optic } from "./optic.js";
8
+ export { at, fields } from "./optics.js";
9
+ export { type Store, store } from "./store.js";
5
10
  export { type Equals, type Lerp, type Linear, type Metric, type Pack, type Pivotal, requireEquals, requireLerp, requireLinear, requireMetric, requirePack, requirePivotal, type TraitDict, type Traits, } from "./traits.js";
6
11
  export { Anchor, Dir } from "./values/anchor.js";
7
12
  export { Audio, type AudioClip, audio, stamp as audioStamp } from "./values/audio.js";
8
- export * as BoolMath from "./values/bool.js";
9
13
  export { Bool, bool } from "./values/bool.js";
10
- export * as BoxMath from "./values/box.js";
11
- export { Box, box } from "./values/box.js";
14
+ export { Box, box, edgeFrom as boxEdgeFrom, expand as boxExpand, union as boxUnion, } from "./values/box.js";
12
15
  export { Canvas, canvas, type Raster, stamp as canvasStamp } from "./values/canvas.js";
13
- export * as ColorMath from "./values/color.js";
14
16
  export { Color, rgb, rgba } from "./values/color.js";
17
+ export { type ColorStop, Colour, Field, type FieldVal, field, type Kind as FieldKind, Scalar, Vector, } from "./values/field.js";
15
18
  export { Flags, flags } from "./values/flags.js";
16
19
  export { blit as gpuBlit, brush as gpuBrush, copy as gpuCopy, newTex as gpuNewTex, Spring, scratch2 as gpuScratch2, type Tex, } from "./values/gpu.js";
17
- export * as MatrixMath from "./values/matrix.js";
18
- export { Matrix, matrix, transformBox, transformPoint } from "./values/matrix.js";
19
- export * as NumMath from "./values/num.js";
20
+ export { compose as matrixCompose, Matrix, matrix, toMatrixString, transformBox, transformPoint, } from "./values/matrix.js";
20
21
  export { Num, num } from "./values/num.js";
21
- export * as PoseMath from "./values/pose.js";
22
22
  export { Pose, pose } from "./values/pose.js";
23
- export * as RangeMath from "./values/range.js";
24
23
  export { Range, range, span } from "./values/range.js";
25
- export * as StrMath from "./values/str.js";
26
24
  export { Str, str } from "./values/str.js";
27
25
  export { type Codec, enumCodec, numCodec, route, type Slot, slot, strCodec, template, tpl, } from "./values/template.js";
28
- export * as TransformMath from "./values/transform.js";
29
26
  export { Transform, type TransformInit, transform } from "./values/transform.js";
30
- export * as TriMath from "./values/tri.js";
31
27
  export { Tri, tri } from "./values/tri.js";
32
- export * as VecMath from "./values/vec.js";
33
28
  export { type PolarPolicy, polar, tangentPoint, Vec, vec } from "./values/vec.js";
@@ -1,33 +1,28 @@
1
- export { batch, Cell, cachedDerive, cell, derive, effect, field, isCell, isLens, isReadonly, lazy, lens, network, reader, readNow, setCellWriteHook, transitiveDeps, untracked, } from "./cell.js";
1
+ export { counts, resetCounts, snapshotCounts, withCounts } from "./_counts.js";
2
+ export { batch, Cell, cachedDerive, cell, derive, effect, fieldLens, fieldOf, isCell, isLens, isReadonly, lazy, lens, network, reader, readNow, SKIP, setCellWriteHook, settle, transitiveDeps, untracked, } from "./cell.js";
3
+ export { dumpGraph, explain, kind as cellKind, label as cellLabel, traceWrites, upstream, } from "./debug.js";
2
4
  export { bezier2, bezier3 } from "./derived-geometry.js";
3
5
  export * from "./lenses/index.js";
4
6
  export { each } from "./lifecycle.js";
7
+ export { atKey, compose, iso, optic } from "./optic.js";
8
+ export { at, fields } from "./optics.js";
9
+ export { store } from "./store.js";
5
10
  export { requireEquals, requireLerp, requireLinear, requireMetric, requirePack, requirePivotal, } from "./traits.js";
6
11
  export { Anchor, Dir } from "./values/anchor.js";
7
12
  export { Audio, audio, stamp as audioStamp } from "./values/audio.js";
8
- export * as BoolMath from "./values/bool.js";
9
13
  export { Bool, bool } from "./values/bool.js";
10
- export * as BoxMath from "./values/box.js";
11
- export { Box, box } from "./values/box.js";
14
+ export { Box, box, edgeFrom as boxEdgeFrom, expand as boxExpand, union as boxUnion, } from "./values/box.js";
12
15
  export { Canvas, canvas, stamp as canvasStamp } from "./values/canvas.js";
13
- export * as ColorMath from "./values/color.js";
14
16
  export { Color, rgb, rgba } from "./values/color.js";
17
+ export { Colour, Field, field, Scalar, Vector, } from "./values/field.js";
15
18
  export { Flags, flags } from "./values/flags.js";
16
19
  export { blit as gpuBlit, brush as gpuBrush, copy as gpuCopy, newTex as gpuNewTex, Spring, scratch2 as gpuScratch2, } from "./values/gpu.js";
17
- export * as MatrixMath from "./values/matrix.js";
18
- export { Matrix, matrix, transformBox, transformPoint } from "./values/matrix.js";
19
- export * as NumMath from "./values/num.js";
20
+ export { compose as matrixCompose, Matrix, matrix, toMatrixString, transformBox, transformPoint, } from "./values/matrix.js";
20
21
  export { Num, num } from "./values/num.js";
21
- export * as PoseMath from "./values/pose.js";
22
22
  export { Pose, pose } from "./values/pose.js";
23
- export * as RangeMath from "./values/range.js";
24
23
  export { Range, range, span } from "./values/range.js";
25
- export * as StrMath from "./values/str.js";
26
24
  export { Str, str } from "./values/str.js";
27
25
  export { enumCodec, numCodec, route, slot, strCodec, template, tpl, } from "./values/template.js";
28
- export * as TransformMath from "./values/transform.js";
29
26
  export { Transform, transform } from "./values/transform.js";
30
- export * as TriMath from "./values/tri.js";
31
27
  export { Tri, tri } from "./values/tri.js";
32
- export * as VecMath from "./values/vec.js";
33
28
  export { polar, tangentPoint, Vec, vec } from "./values/vec.js";
@@ -1,4 +1,4 @@
1
- import type { Writable } from "../cell.js";
1
+ import { type Writable } from "../cell.js";
2
2
  import { Num } from "../values/num.js";
3
3
  import { Vec } from "../values/vec.js";
4
4
  export interface ArgminOpts {
@@ -5,6 +5,7 @@
5
5
  // All route through the engine's N-input lens path. Stateless-bwd
6
6
  // (`(target) => updates`) skips the peek loop on the hot path;
7
7
  // stateful-bwd (`(target, vals) => updates`) reads the scratch.
8
+ import { SKIP } from "../cell.js";
8
9
  import { Num } from "../values/num.js";
9
10
  import { Vec } from "../values/vec.js";
10
11
  /** Project `p` into the closed disc of radius `r` centred on `c` (points
@@ -51,7 +52,7 @@ export function argminNum(inputs, forward, weights, opts = {}) {
51
52
  const k = dy / denom;
52
53
  for (let i = 0; i < n; i++) {
53
54
  if (weights[i] === 0) {
54
- out[i] = undefined;
55
+ out[i] = SKIP;
55
56
  }
56
57
  else {
57
58
  out[i] = xs[i] + weights[i] * J[i] * k;
@@ -103,7 +104,7 @@ export function argminVec(inputs, forward, weights, opts = {}) {
103
104
  if (Math.abs(det) < 1e-14) {
104
105
  // Singular; leave inputs unchanged.
105
106
  for (let i = 0; i < n; i++)
106
- out[i] = undefined;
107
+ out[i] = SKIP;
107
108
  return out;
108
109
  }
109
110
  const invA = c / det;
@@ -114,7 +115,7 @@ export function argminVec(inputs, forward, weights, opts = {}) {
114
115
  for (let i = 0; i < n; i++) {
115
116
  const w = weights[i];
116
117
  if (w === 0) {
117
- out[i] = undefined;
118
+ out[i] = SKIP;
118
119
  }
119
120
  else {
120
121
  out[i] = xs[i] + w * (Jx[i] * kx + Jy[i] * ky);
@@ -10,7 +10,7 @@
10
10
  // (bestFitLine, bestFitCircle, pca, total). All exact, idempotent,
11
11
  // cross-channel invariant by construction, on the same `Cls.lens`
12
12
  // machinery — no engine changes.
13
- import { mean, Num, Vec, } from "../index.js";
13
+ import { mean, Num, SKIP, Vec, } from "../index.js";
14
14
  import { continuous, remember } from "./memory.js";
15
15
  // Pivotal trait lookup via the value class's `static traits.pivotal` slot.
16
16
  // biome-ignore lint/suspicious/noExplicitAny: dynamic trait lookup
@@ -50,7 +50,7 @@ export function rotateAbout(points, pivot) {
50
50
  const rx0 = vals[0].x - p.x;
51
51
  const ry0 = vals[0].y - p.y;
52
52
  if (rx0 * rx0 + ry0 * ry0 < 1e-24) {
53
- return vals.map(() => undefined);
53
+ return vals.map(() => SKIP);
54
54
  }
55
55
  const oldθ = Math.atan2(ry0, rx0);
56
56
  const dθ = target - oldθ;
@@ -96,11 +96,11 @@ export function scaleAbout(points, pivot) {
96
96
  // re-projects to the current radius and is absorbed (sources put).
97
97
  const rNow = Math.hypot(vals[0].x - p.x, vals[0].y - p.y);
98
98
  if (Math.abs(target) === rNow)
99
- return { updates: vals.map(() => undefined), complement: c };
99
+ return { updates: vals.map(() => SKIP), complement: c };
100
100
  const d0 = c.devs[0];
101
101
  const r0 = Math.hypot(d0.x, d0.y);
102
102
  if (r0 < 1e-12)
103
- return { updates: vals.map(() => undefined), complement: c };
103
+ return { updates: vals.map(() => SKIP), complement: c };
104
104
  const k = target / r0;
105
105
  const out = vals.map((v, i) => ({
106
106
  ...v,
@@ -299,7 +299,7 @@ export function pca(points) {
299
299
  const rotation = Num.lens(points, (vals) => decompose(vals)?.θ ?? 0, (target, vals) => {
300
300
  const d = decompose(vals);
301
301
  if (!d)
302
- return vals.map(() => undefined);
302
+ return vals.map(() => SKIP);
303
303
  const dθ = target - d.θ;
304
304
  const cos = Math.cos(dθ);
305
305
  const sin = Math.sin(dθ);
@@ -382,10 +382,15 @@ export function pca(points) {
382
382
  // Lossy magnitude view: a same-magnitude target re-projects to
383
383
  // the current axis length and is absorbed (cluster left put).
384
384
  if (Math.abs(target) === c.lenThis)
385
- return { updates: vals.map(() => undefined), complement: c };
386
- // Non-degenerate fast path: scale current cluster along axis.
385
+ return { updates: vals.map(() => SKIP), complement: c };
386
+ // Non-degenerate fast path: scale current cluster along axis. The scale
387
+ // sets the axis length to |target|, so the complement is consistent
388
+ // without a post-write `step` (the engine no longer re-steps own writes).
387
389
  const k = target / c.lenThis;
388
- return { updates: scaleAlongAxis(vals, d.cx, d.cy, c.uX, c.uY, k), complement: c };
390
+ return {
391
+ updates: scaleAlongAxis(vals, d.cx, d.cy, c.uX, c.uY, k),
392
+ complement: { ...c, lenThis: Math.abs(target) },
393
+ };
389
394
  }
390
395
  // Degenerate: reconstruct from complement. Centroid still
391
396
  // derivable from current source (mean translates always work).
@@ -403,7 +408,7 @@ export function pca(points) {
403
408
  const b = c.projOther[i] * c.lenOther;
404
409
  out[i] = { x: cx + a * c.uX + b * c.vX, y: cy + a * c.uY + b * c.vY };
405
410
  }
406
- return { updates: out, complement: c };
411
+ return { updates: out, complement: { ...c, lenThis: Math.abs(target) } };
407
412
  },
408
413
  });
409
414
  };
@@ -6,7 +6,7 @@
6
6
  // about the centroid), so cross-channel invariance is EXACT and one
7
7
  // write lands in O(K). For the generic numerical N→M escape hatch (when
8
8
  // no closed form fits) see `factor` in `typed-factor.ts`.
9
- import { Num, Vec } from "../index.js";
9
+ import { Num, SKIP, Vec } from "../index.js";
10
10
  // meanDiff — M=2 isomorphism baseline.
11
11
  //
12
12
  // (a, b) → ((a+b)/2, a−b). Square full-rank linear lens; bwd is the
@@ -84,7 +84,7 @@ export function procrustes(points) {
84
84
  const ry0 = vals[0].y - cy;
85
85
  if (rx0 * rx0 + ry0 * ry0 < 1e-24) {
86
86
  // Collapsed cluster; no angle to rotate from.
87
- return vals.map(() => undefined);
87
+ return vals.map(() => SKIP);
88
88
  }
89
89
  const oldθ = Math.atan2(ry0, rx0);
90
90
  const dθ = target - oldθ;
@@ -130,7 +130,7 @@ export function procrustes(points) {
130
130
  const d0 = c.devs[0];
131
131
  const r0 = Math.hypot(d0.x, d0.y);
132
132
  if (r0 < 1e-12)
133
- return { updates: vals.map(() => undefined), complement: c };
133
+ return { updates: vals.map(() => SKIP), complement: c };
134
134
  const k = target / r0;
135
135
  const out = c.devs.map(d => ({ x: cen.x + k * d.x, y: cen.y + k * d.y }));
136
136
  return { updates: out, complement: c };
@@ -6,7 +6,7 @@
6
6
  // (2) Bezier gestalt handles ({start, end, startTangent, endTangent}).
7
7
  // (3) Time-series ({mean, slope}) over (i, value) samples.
8
8
  // All exact, idempotent, cross-channel invariant by construction.
9
- import { Num, reader, Vec, } from "../index.js";
9
+ import { Num, reader, SKIP, Vec, } from "../index.js";
10
10
  import { remember } from "./memory.js";
11
11
  // Generic Linear-trait aggregates.
12
12
  //
@@ -107,9 +107,9 @@ export function mix(weights, branches) {
107
107
  const { w, sumSq } = readW();
108
108
  const delta = lin.sub(target, combine(vals, w));
109
109
  if (sumSq < 1e-12)
110
- return vals.map(() => undefined);
110
+ return vals.map(() => SKIP);
111
111
  const inv = 1 / sumSq;
112
- return vals.map((v, i) => w[i] === 0 ? undefined : lin.add(v, lin.scale(delta, w[i] * inv)));
112
+ return vals.map((v, i) => w[i] === 0 ? SKIP : lin.add(v, lin.scale(delta, w[i] * inv)));
113
113
  });
114
114
  }
115
115
  /** Two-branch router (mix simplex *vertex*): reads the live branch, writes
@@ -187,8 +187,8 @@ export function bezierGestalt(p0, p1, p2, p3) {
187
187
  const dy = target.y - vals[1].y;
188
188
  return [{ x: vals[0].x + dx, y: vals[0].y + dy }, target];
189
189
  });
190
- const startTangent = Vec.lens([p0, p1], (vals) => ({ x: vals[1].x - vals[0].x, y: vals[1].y - vals[0].y }), (target, vals) => [undefined, { x: vals[0].x + target.x, y: vals[0].y + target.y }]);
191
- const endTangent = Vec.lens([p2, p3], (vals) => ({ x: vals[1].x - vals[0].x, y: vals[1].y - vals[0].y }), (target, vals) => [{ x: vals[1].x - target.x, y: vals[1].y - target.y }, undefined]);
190
+ const startTangent = Vec.lens([p0, p1], (vals) => ({ x: vals[1].x - vals[0].x, y: vals[1].y - vals[0].y }), (target, vals) => [SKIP, { x: vals[0].x + target.x, y: vals[0].y + target.y }]);
191
+ const endTangent = Vec.lens([p2, p3], (vals) => ({ x: vals[1].x - vals[0].x, y: vals[1].y - vals[0].y }), (target, vals) => [{ x: vals[1].x - target.x, y: vals[1].y - target.y }, SKIP]);
192
192
  return { start, end, startTangent, endTangent };
193
193
  }
194
194
  // Time-series aggregates.
@@ -1,4 +1,4 @@
1
- import type { Cell, Read, Writable } from "../cell.js";
1
+ import { type Cell, type Read, type Writable } from "../cell.js";
2
2
  import { Num } from "../values/num.js";
3
3
  import { Vec } from "../values/vec.js";
4
4
  type V = {
@@ -1,5 +1,6 @@
1
1
  // geometry.ts — geometric lens building blocks over the N-input
2
2
  // `Cls.lens` / `Cls.derive` forms. All are a few lines on top of the engine.
3
+ import { SKIP } from "../cell.js";
3
4
  import { Num } from "../values/num.js";
4
5
  import { Vec } from "../values/vec.js";
5
6
  import { rotateAbout } from "./closed-form-policies.js";
@@ -36,7 +37,7 @@ export function reflection(point, axisStart, axisEnd) {
36
37
  const projY = a.y + t * dy;
37
38
  return { x: 2 * projX - p.x, y: 2 * projY - p.y };
38
39
  };
39
- return Vec.lens([point, axisStart, axisEnd], vals => reflect(vals[0], vals[1], vals[2]), (target, vals) => [reflect(target, vals[1], vals[2]), undefined, undefined]);
40
+ return Vec.lens([point, axisStart, axisEnd], vals => reflect(vals[0], vals[1], vals[2]), (target, vals) => [reflect(target, vals[1], vals[2]), SKIP, SKIP]);
40
41
  }
41
42
  /** Lerp between two Vecs at parameter `t`. Writing the interpolated point
42
43
  * shifts both endpoints rigidly (preserving t). */
@@ -48,7 +49,7 @@ export function vecLerp(a, b, t) {
48
49
  const [av, bv, tv] = vals;
49
50
  const dx = target.x - (av.x + (bv.x - av.x) * tv);
50
51
  const dy = target.y - (av.y + (bv.y - av.y) * tv);
51
- return [{ x: av.x + dx, y: av.y + dy }, { x: bv.x + dx, y: bv.y + dy }, undefined];
52
+ return [{ x: av.x + dx, y: av.y + dy }, { x: bv.x + dx, y: bv.y + dy }, SKIP];
52
53
  });
53
54
  }
54
55
  /** Sum of two nums; writing the sum splits the delta equally between
@@ -77,23 +78,21 @@ export function clampedMean(parents, lo, hi) {
77
78
  const n = parents.length;
78
79
  const inv = 1 / n;
79
80
  return Num.lens(parents, vals => {
80
- const arr = vals;
81
81
  let s = 0;
82
82
  for (let i = 0; i < n; i++)
83
- s += arr[i];
83
+ s += vals[i];
84
84
  const m = s * inv;
85
85
  return m < lo ? lo : m > hi ? hi : m;
86
86
  }, (target, vals) => {
87
- const arr = vals;
88
87
  const clamped = target < lo ? lo : target > hi ? hi : target;
89
88
  let s = 0;
90
89
  for (let i = 0; i < n; i++)
91
- s += arr[i];
90
+ s += vals[i];
92
91
  const cur = s * inv;
93
92
  const delta = clamped - cur;
94
93
  const out = new Array(n);
95
94
  for (let i = 0; i < n; i++)
96
- out[i] = arr[i] + delta;
95
+ out[i] = vals[i] + delta;
97
96
  return out;
98
97
  });
99
98
  }
@@ -4,4 +4,5 @@ export { bbox, meanDiff, procrustes } from "./decompositions.js";
4
4
  export { bezierGestalt, crossfade, mean, meanSpread, mix, select, spread, timeSeries, } from "./domain-aggregates.js";
5
5
  export { angle, clampedMean, diff, distance, pulleySum, reflection, vecLerp } from "./geometry.js";
6
6
  export { type ContinuousOpts, continuous, type RememberOpts, remember, } from "./memory.js";
7
+ export { type ClosestOpts, hullWeights, nearestIndex } from "./snap.js";
7
8
  export { bundle, type FactorOpts, type FactorResult, factor, factorTuple, type OutputSpec, type PackedInput, } from "./typed-factor.js";
@@ -4,4 +4,5 @@ export { bbox, meanDiff, procrustes } from "./decompositions.js";
4
4
  export { bezierGestalt, crossfade, mean, meanSpread, mix, select, spread, timeSeries, } from "./domain-aggregates.js";
5
5
  export { angle, clampedMean, diff, distance, pulleySum, reflection, vecLerp } from "./geometry.js";
6
6
  export { continuous, remember, } from "./memory.js";
7
+ export { hullWeights, nearestIndex } from "./snap.js";
7
8
  export { bundle, factor, factorTuple, } from "./typed-factor.js";
@@ -1,4 +1,4 @@
1
- import { type Cell, Num, type Traits, type Writable } from "../index.js";
1
+ import { type Cell, Num, type Skip, type Traits, type Writable } from "../index.js";
2
2
  /** Options for {@link remember}. `anchor` is the fixed point sources scale
3
3
  * about (a pivot, or the live centroid); `feature` is the writable scalar
4
4
  * (a radius, mean distance, or sum). */
@@ -38,7 +38,7 @@ export interface ContinuousOpts<T> {
38
38
  };
39
39
  /** Realize `target` (already unwrapped, absolute) onto the sources,
40
40
  * given the `current` unwrapped reading (for a delta). */
41
- apply: (target: number, vals: readonly T[], current: number) => readonly (T | undefined)[];
41
+ apply: (target: number, vals: readonly T[], current: number) => readonly (T | Skip)[];
42
42
  }
43
43
  /** Continuous (winding-aware) lens over a cyclic reading. The complement
44
44
  * tracks the last emitted value and unwraps each raw reading to the
@@ -15,7 +15,7 @@
15
15
  // unwraps the raw reading to the nearest sheet, so the
16
16
  // view never tears at a branch cut. Covers the eigenvector
17
17
  // angle of bestFitLine; the primitive behind winding.
18
- import { Num } from "../index.js";
18
+ import { Num, SKIP } from "../index.js";
19
19
  /** Scalar shape-memory lens. Reads `feature(sources)`; writing it scales
20
20
  * the cluster about `anchor` so the new feature matches, reinflating the
21
21
  * remembered shape when the cluster has collapsed onto the anchor. The
@@ -59,7 +59,7 @@ export function remember(sources, opts) {
59
59
  // Magnitude is lossy (|−f| = f): a same-magnitude target re-projects
60
60
  // to the current feature, so the cluster is left put.
61
61
  if (magnitude && Math.abs(target) === f) {
62
- return { updates: vals.map(() => undefined), complement: c };
62
+ return { updates: vals.map(() => SKIP), complement: c };
63
63
  }
64
64
  if (f > eps) {
65
65
  const k = target / f;
@@ -94,7 +94,7 @@ export function continuous(sources, opts) {
94
94
  bwd: (target, vals, c) => {
95
95
  const r = raw(vals);
96
96
  if (!r.defined)
97
- return { updates: vals.map(() => undefined), complement: { prev: target } };
97
+ return { updates: vals.map(() => SKIP), complement: { prev: target } };
98
98
  const current = unwrap(r.value, c.prev);
99
99
  return { updates: apply(target, vals, current), complement: { prev: target } };
100
100
  },
@@ -0,0 +1,18 @@
1
+ import { type Cell, type Read } from "../cell.js";
2
+ type V = {
3
+ x: number;
4
+ y: number;
5
+ };
6
+ /** Convex-hull barycentric weights of `q` over `pts` (Σ = 1, all ≥ 0,
7
+ * clamped to the hull). Closed form for K ≤ 3; Frank–Wolfe for K > 3. */
8
+ export declare function hullWeights(q: V, pts: readonly V[]): number[];
9
+ export interface ClosestOpts {
10
+ /** Hysteresis margin (px): the current pick is kept until a rival is
11
+ * nearer by more than this. Dragology's stickiness. Default 0. */
12
+ sticky?: number;
13
+ }
14
+ /** Index of the candidate nearest `pointer`, with hysteresis. Read-only
15
+ * selection: the stickiness state lives in the lens complement (the
16
+ * sanctioned place for path-dependence), so reads stay pure. */
17
+ export declare function nearestIndex(pointer: Read<V>, candidates: readonly Read<V>[], opts?: ClosestOpts): Cell<number>;
18
+ export {};