bireactive 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.
Files changed (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/dist/animation/anim.d.ts +57 -0
  4. package/dist/animation/anim.js +318 -0
  5. package/dist/animation/combinators.d.ts +39 -0
  6. package/dist/animation/combinators.js +113 -0
  7. package/dist/animation/easings.d.ts +5 -0
  8. package/dist/animation/easings.js +5 -0
  9. package/dist/animation/index.d.ts +3 -0
  10. package/dist/animation/index.js +3 -0
  11. package/dist/assert/algebra.d.ts +20 -0
  12. package/dist/assert/algebra.js +79 -0
  13. package/dist/assert/claim.d.ts +40 -0
  14. package/dist/assert/claim.js +129 -0
  15. package/dist/assert/index.d.ts +7 -0
  16. package/dist/assert/index.js +19 -0
  17. package/dist/assert/predicates.d.ts +18 -0
  18. package/dist/assert/predicates.js +43 -0
  19. package/dist/assert/record.d.ts +20 -0
  20. package/dist/assert/record.js +78 -0
  21. package/dist/assert/scope.d.ts +42 -0
  22. package/dist/assert/scope.js +233 -0
  23. package/dist/assert/span.d.ts +37 -0
  24. package/dist/assert/span.js +68 -0
  25. package/dist/assert/tree.d.ts +22 -0
  26. package/dist/assert/tree.js +65 -0
  27. package/dist/code/code.d.ts +70 -0
  28. package/dist/code/code.js +361 -0
  29. package/dist/code/index.d.ts +2 -0
  30. package/dist/code/index.js +9 -0
  31. package/dist/code/morph.d.ts +5 -0
  32. package/dist/code/morph.js +194 -0
  33. package/dist/code/tokenize.d.ts +8 -0
  34. package/dist/code/tokenize.js +51 -0
  35. package/dist/constraints/cluster.d.ts +83 -0
  36. package/dist/constraints/cluster.js +213 -0
  37. package/dist/constraints/drivers.d.ts +15 -0
  38. package/dist/constraints/drivers.js +40 -0
  39. package/dist/constraints/factories.d.ts +73 -0
  40. package/dist/constraints/factories.js +248 -0
  41. package/dist/constraints/index.d.ts +11 -0
  42. package/dist/constraints/index.js +39 -0
  43. package/dist/constraints/interaction.d.ts +21 -0
  44. package/dist/constraints/interaction.js +148 -0
  45. package/dist/constraints/linalg.d.ts +18 -0
  46. package/dist/constraints/linalg.js +141 -0
  47. package/dist/constraints/phases.d.ts +21 -0
  48. package/dist/constraints/phases.js +60 -0
  49. package/dist/constraints/physics.d.ts +34 -0
  50. package/dist/constraints/physics.js +128 -0
  51. package/dist/constraints/rigid.d.ts +210 -0
  52. package/dist/constraints/rigid.js +835 -0
  53. package/dist/constraints/solver.d.ts +107 -0
  54. package/dist/constraints/solver.js +510 -0
  55. package/dist/constraints/term.d.ts +50 -0
  56. package/dist/constraints/term.js +80 -0
  57. package/dist/constraints/terms.d.ts +80 -0
  58. package/dist/constraints/terms.js +302 -0
  59. package/dist/constraints/world.d.ts +31 -0
  60. package/dist/constraints/world.js +245 -0
  61. package/dist/core/aggregates.d.ts +64 -0
  62. package/dist/core/aggregates.js +198 -0
  63. package/dist/core/anim.d.ts +84 -0
  64. package/dist/core/anim.js +301 -0
  65. package/dist/core/index.d.ts +38 -0
  66. package/dist/core/index.js +38 -0
  67. package/dist/core/introspect.d.ts +5 -0
  68. package/dist/core/introspect.js +31 -0
  69. package/dist/core/lenses/closed-form-policies.d.ts +64 -0
  70. package/dist/core/lenses/closed-form-policies.js +452 -0
  71. package/dist/core/lenses/domain-aggregates.d.ts +54 -0
  72. package/dist/core/lenses/domain-aggregates.js +259 -0
  73. package/dist/core/lenses/factor-lens.d.ts +42 -0
  74. package/dist/core/lenses/factor-lens.js +419 -0
  75. package/dist/core/lenses/index.d.ts +5 -0
  76. package/dist/core/lenses/index.js +16 -0
  77. package/dist/core/lenses/memory.d.ts +47 -0
  78. package/dist/core/lenses/memory.js +102 -0
  79. package/dist/core/lenses/typed-factor.d.ts +45 -0
  80. package/dist/core/lenses/typed-factor.js +376 -0
  81. package/dist/core/network-utils.d.ts +14 -0
  82. package/dist/core/network-utils.js +62 -0
  83. package/dist/core/new-primitives.d.ts +33 -0
  84. package/dist/core/new-primitives.js +113 -0
  85. package/dist/core/signal.d.ts +254 -0
  86. package/dist/core/signal.js +1349 -0
  87. package/dist/core/traits.d.ts +61 -0
  88. package/dist/core/traits.js +56 -0
  89. package/dist/core/tree.d.ts +23 -0
  90. package/dist/core/tree.js +62 -0
  91. package/dist/core/values/anchor.d.ts +23 -0
  92. package/dist/core/values/anchor.js +23 -0
  93. package/dist/core/values/audio.d.ts +33 -0
  94. package/dist/core/values/audio.js +107 -0
  95. package/dist/core/values/bool.d.ts +37 -0
  96. package/dist/core/values/bool.js +75 -0
  97. package/dist/core/values/box.d.ts +77 -0
  98. package/dist/core/values/box.js +211 -0
  99. package/dist/core/values/canvas.d.ts +71 -0
  100. package/dist/core/values/canvas.js +495 -0
  101. package/dist/core/values/color.d.ts +49 -0
  102. package/dist/core/values/color.js +106 -0
  103. package/dist/core/values/flags.d.ts +18 -0
  104. package/dist/core/values/flags.js +50 -0
  105. package/dist/core/values/gpu.d.ts +74 -0
  106. package/dist/core/values/gpu.js +426 -0
  107. package/dist/core/values/matrix.d.ts +53 -0
  108. package/dist/core/values/matrix.js +140 -0
  109. package/dist/core/values/num.d.ts +62 -0
  110. package/dist/core/values/num.js +166 -0
  111. package/dist/core/values/pose.d.ts +31 -0
  112. package/dist/core/values/pose.js +83 -0
  113. package/dist/core/values/range.d.ts +83 -0
  114. package/dist/core/values/range.js +167 -0
  115. package/dist/core/values/str.d.ts +76 -0
  116. package/dist/core/values/str.js +346 -0
  117. package/dist/core/values/template.d.ts +49 -0
  118. package/dist/core/values/template.js +148 -0
  119. package/dist/core/values/transform.d.ts +49 -0
  120. package/dist/core/values/transform.js +115 -0
  121. package/dist/core/values/tri.d.ts +31 -0
  122. package/dist/core/values/tri.js +95 -0
  123. package/dist/core/values/vec.d.ts +72 -0
  124. package/dist/core/values/vec.js +219 -0
  125. package/dist/core/writable.d.ts +15 -0
  126. package/dist/core/writable.js +29 -0
  127. package/dist/ext/events.d.ts +10 -0
  128. package/dist/ext/events.js +31 -0
  129. package/dist/ext/index.d.ts +4 -0
  130. package/dist/ext/index.js +4 -0
  131. package/dist/ext/snapshot.d.ts +8 -0
  132. package/dist/ext/snapshot.js +29 -0
  133. package/dist/ext/timeline.d.ts +56 -0
  134. package/dist/ext/timeline.js +94 -0
  135. package/dist/ext/waapi.d.ts +25 -0
  136. package/dist/ext/waapi.js +198 -0
  137. package/dist/index.d.ts +8 -0
  138. package/dist/index.js +10 -0
  139. package/dist/propagators/index.d.ts +6 -0
  140. package/dist/propagators/index.js +6 -0
  141. package/dist/propagators/layout.d.ts +68 -0
  142. package/dist/propagators/layout.js +336 -0
  143. package/dist/propagators/network.d.ts +52 -0
  144. package/dist/propagators/network.js +185 -0
  145. package/dist/propagators/propagator.d.ts +12 -0
  146. package/dist/propagators/propagator.js +16 -0
  147. package/dist/propagators/range.d.ts +45 -0
  148. package/dist/propagators/range.js +147 -0
  149. package/dist/propagators/relations.d.ts +60 -0
  150. package/dist/propagators/relations.js +343 -0
  151. package/dist/shapes/annular-sector.d.ts +15 -0
  152. package/dist/shapes/annular-sector.js +64 -0
  153. package/dist/shapes/button.d.ts +14 -0
  154. package/dist/shapes/button.js +31 -0
  155. package/dist/shapes/choreographers.d.ts +22 -0
  156. package/dist/shapes/choreographers.js +69 -0
  157. package/dist/shapes/circle.d.ts +17 -0
  158. package/dist/shapes/circle.js +57 -0
  159. package/dist/shapes/clip.d.ts +5 -0
  160. package/dist/shapes/clip.js +31 -0
  161. package/dist/shapes/connect.d.ts +16 -0
  162. package/dist/shapes/connect.js +70 -0
  163. package/dist/shapes/curve.d.ts +60 -0
  164. package/dist/shapes/curve.js +285 -0
  165. package/dist/shapes/dashed.d.ts +16 -0
  166. package/dist/shapes/dashed.js +142 -0
  167. package/dist/shapes/debug.d.ts +43 -0
  168. package/dist/shapes/debug.js +97 -0
  169. package/dist/shapes/group.d.ts +5 -0
  170. package/dist/shapes/group.js +10 -0
  171. package/dist/shapes/handle.d.ts +32 -0
  172. package/dist/shapes/handle.js +88 -0
  173. package/dist/shapes/index.d.ts +23 -0
  174. package/dist/shapes/index.js +23 -0
  175. package/dist/shapes/interaction.d.ts +32 -0
  176. package/dist/shapes/interaction.js +187 -0
  177. package/dist/shapes/label.d.ts +20 -0
  178. package/dist/shapes/label.js +42 -0
  179. package/dist/shapes/layout.d.ts +29 -0
  180. package/dist/shapes/layout.js +74 -0
  181. package/dist/shapes/line.d.ts +21 -0
  182. package/dist/shapes/line.js +79 -0
  183. package/dist/shapes/list.d.ts +18 -0
  184. package/dist/shapes/list.js +51 -0
  185. package/dist/shapes/mount.d.ts +7 -0
  186. package/dist/shapes/mount.js +10 -0
  187. package/dist/shapes/path.d.ts +77 -0
  188. package/dist/shapes/path.js +227 -0
  189. package/dist/shapes/rect.d.ts +30 -0
  190. package/dist/shapes/rect.js +131 -0
  191. package/dist/shapes/shape.d.ts +132 -0
  192. package/dist/shapes/shape.js +306 -0
  193. package/dist/shapes/text.d.ts +24 -0
  194. package/dist/shapes/text.js +53 -0
  195. package/dist/shapes/tokens.d.ts +28 -0
  196. package/dist/shapes/tokens.js +27 -0
  197. package/dist/shapes/transitions.d.ts +23 -0
  198. package/dist/shapes/transitions.js +62 -0
  199. package/dist/tex/decorations.d.ts +26 -0
  200. package/dist/tex/decorations.js +116 -0
  201. package/dist/tex/index.d.ts +5 -0
  202. package/dist/tex/index.js +5 -0
  203. package/dist/tex/marker.d.ts +17 -0
  204. package/dist/tex/marker.js +63 -0
  205. package/dist/tex/motion.d.ts +43 -0
  206. package/dist/tex/motion.js +290 -0
  207. package/dist/tex/parts.d.ts +65 -0
  208. package/dist/tex/parts.js +149 -0
  209. package/dist/tex/tex.d.ts +45 -0
  210. package/dist/tex/tex.js +244 -0
  211. package/dist/web/attr.d.ts +16 -0
  212. package/dist/web/attr.js +98 -0
  213. package/dist/web/diagram.d.ts +49 -0
  214. package/dist/web/diagram.js +260 -0
  215. package/dist/web/index.d.ts +6 -0
  216. package/dist/web/index.js +6 -0
  217. package/dist/web/md-marker.d.ts +6 -0
  218. package/dist/web/md-marker.js +39 -0
  219. package/dist/web/md-tex.d.ts +6 -0
  220. package/dist/web/md-tex.js +61 -0
  221. package/dist/web/raf.d.ts +6 -0
  222. package/dist/web/raf.js +24 -0
  223. package/dist/web/viewport.d.ts +7 -0
  224. package/dist/web/viewport.js +13 -0
  225. package/package.json +87 -0
@@ -0,0 +1,259 @@
1
+ // domain-aggregates.ts — closed-form lenses beyond point clouds.
2
+ //
3
+ // The group-action patterns from `closed-form-policies.ts`, applied to:
4
+ // (1) Generic Linear/Metric-trait aggregates — `meanOf`, `spreadOf`,
5
+ // `paletteLens` work for colors, poses, ranges, boxes for free.
6
+ // (2) Color aggregates — `meanColor`.
7
+ // (3) Bezier gestalt handles ({start, end, startTangent, endTangent}).
8
+ // (4) Time-series ({mean, slope}) over (i, value) samples.
9
+ // All exact, idempotent, cross-channel invariant by construction.
10
+ import { Num, reader, Vec, } from "../index.js";
11
+ import { remember } from "./memory.js";
12
+ // Generic Linear-trait aggregates.
13
+ //
14
+ // Ergonomic entry points over `meanLens` / `scaleAbout` that infer the
15
+ // value class from the first input (`meanOf(colors)` vs
16
+ // `meanLens(Color, colors)`). Same engine, no new infrastructure.
17
+ /** Class-inferring mean (writable of `inputs[0]`'s class). Needs `linear`. */
18
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
19
+ export function meanOf(inputs) {
20
+ if (inputs.length === 0)
21
+ throw new Error("meanOf: need ≥ 1 input");
22
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic class lookup
23
+ const Cls = inputs[0].constructor;
24
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic trait lookup
25
+ const lin = Cls.traits?.linear;
26
+ if (!lin)
27
+ throw new Error(`meanOf: ${Cls.name ?? "?"} has no traits.linear`);
28
+ const n = inputs.length;
29
+ const inv = 1 / n;
30
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape on Cls.lens
31
+ return Cls.lens(inputs,
32
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
33
+ (vals) => {
34
+ let acc = vals[0];
35
+ for (let i = 1; i < n; i++)
36
+ acc = lin.add(acc, vals[i]);
37
+ return lin.scale(acc, inv);
38
+ },
39
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
40
+ (target, vals) => {
41
+ let cur = vals[0];
42
+ for (let i = 1; i < n; i++)
43
+ cur = lin.add(cur, vals[i]);
44
+ cur = lin.scale(cur, inv);
45
+ const delta = lin.sub(target, cur);
46
+ const out = new Array(n);
47
+ for (let i = 0; i < n; i++)
48
+ out[i] = lin.add(vals[i], delta);
49
+ return out;
50
+ });
51
+ }
52
+ /** Rigid-translate aggregate over any Linear type. Alias of `meanOf`,
53
+ * named for the geometric intent. */
54
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
55
+ export function rigidTranslateOf(inputs) {
56
+ return meanOf(inputs);
57
+ }
58
+ // Weighted blend (the mix simplex).
59
+ //
60
+ // `mix` is `meanOf` with the uniform-weight assumption lifted: the read
61
+ // is the normalized weighted sum `Σ wᵢ·aᵢ`, the write is the minimum-norm
62
+ // delta `daᵢ = wᵢ·δ / Σwⱼ²` (the pseudoinverse of `wᵀ·da = δ`), so a
63
+ // zero-weight branch is left untouched. Weights are read-only controls —
64
+ // the bwd never writes them, keeping the blend fixed while the delta flows
65
+ // into the branches.
66
+ //
67
+ // The control lives on the K-simplex: a one-hot vertex is `select`
68
+ // (the live branch absorbs everything), a `(1−t, t)` edge is `crossfade`,
69
+ // uniform weights recover `meanOf`. Reactive weights are dynamically
70
+ // tracked (read via `.value` inside fwd), so flipping a Bool or sliding a
71
+ // Num re-reads with no extra wiring.
72
+ /** Weighted blend of K branches over any `Linear` type. See module note. */
73
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
74
+ export function mix(weights, branches) {
75
+ const K = branches.length;
76
+ if (K < 1)
77
+ throw new Error("mix: need ≥ 1 branch");
78
+ if (weights.length !== K)
79
+ throw new Error("mix: weights/branches length mismatch");
80
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic class lookup
81
+ const Cls = branches[0].constructor;
82
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic trait lookup
83
+ const lin = Cls.traits?.linear;
84
+ if (!lin)
85
+ throw new Error(`mix: ${Cls.name ?? "?"} has no traits.linear`);
86
+ const wf = weights.map(w => reader(w));
87
+ // Normalized weights + Σw². Degenerate (all-zero) weights fall back to
88
+ // uniform so the read stays defined.
89
+ const readW = () => {
90
+ const raw = wf.map(f => f());
91
+ let sum = 0;
92
+ for (const x of raw)
93
+ sum += x;
94
+ const w = Math.abs(sum) > 1e-12 ? raw.map(x => x / sum) : raw.map(() => 1 / K);
95
+ let sumSq = 0;
96
+ for (const x of w)
97
+ sumSq += x * x;
98
+ return { w, sumSq };
99
+ };
100
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape on Cls.lens
101
+ const combine = (vals, w) => {
102
+ let acc = lin.scale(vals[0], w[0]);
103
+ for (let i = 1; i < K; i++)
104
+ acc = lin.add(acc, lin.scale(vals[i], w[i]));
105
+ return acc;
106
+ };
107
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape on Cls.lens
108
+ return Cls.lens(branches,
109
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
110
+ (vals) => combine(vals, readW().w),
111
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
112
+ (target, vals) => {
113
+ const { w, sumSq } = readW();
114
+ const delta = lin.sub(target, combine(vals, w));
115
+ if (sumSq < 1e-12)
116
+ return vals.map(() => undefined);
117
+ const inv = 1 / sumSq;
118
+ return vals.map((v, i) => w[i] === 0 ? undefined : lin.add(v, lin.scale(delta, w[i] * inv)));
119
+ });
120
+ }
121
+ /** Two-branch router (mix simplex *vertex*): reads the live branch, writes
122
+ * flow entirely to it, the other is left put. Flipping `cond` snaps the
123
+ * output to the other branch's stored value. */
124
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
125
+ export function select(cond, whenFalse, whenTrue) {
126
+ return mix([Num.derive(() => (cond.value ? 0 : 1)), Num.derive(() => (cond.value ? 1 : 0))], [whenFalse, whenTrue]);
127
+ }
128
+ /** Two-branch crossfade (mix simplex *edge*): `lerp(a, b, t)`. Writing
129
+ * keeps `t` fixed and splits the delta by influence. */
130
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape
131
+ export function crossfade(t, a, b) {
132
+ return mix([Num.derive(() => 1 - t.value), Num.derive(() => t.value)], [a, b]);
133
+ }
134
+ /** Mean color of a palette; write shifts every color by the delta
135
+ * (rigid translate in RGBA). Via `meanOf`. */
136
+ export function meanColor(colors) {
137
+ return meanOf(colors);
138
+ }
139
+ /** Mean radial distance from the centroid; write scales the cluster's
140
+ * deviations so the new mean matches the target. Trait-driven via
141
+ * `Linear` + `Metric`, so it works for any class declaring both (Vec,
142
+ * Color, Pose, Box, Range, custom).
143
+ *
144
+ * Complement carries per-input deviations normalized by the current mean
145
+ * radius, so `spread = T` places each input at `centroid + normDev_i * T`
146
+ * and a collapse (spread → 0) reinflates the original SHAPE. Centroid is
147
+ * recomputed every read/write, so an intervening mean translate is not
148
+ * stale. */
149
+ export function spreadOf(inputs) {
150
+ const K = inputs.length;
151
+ if (K < 1)
152
+ throw new Error("spreadOf: need ≥ 1 input");
153
+ // biome-ignore lint/suspicious/noExplicitAny: dynamic class lookup
154
+ const Cls = inputs[0].constructor;
155
+ const lin = Cls.traits?.linear;
156
+ const met = Cls.traits?.metric;
157
+ if (!lin || !met) {
158
+ throw new Error(`spreadOf: ${Cls.name ?? "?"} needs Linear + Metric`);
159
+ }
160
+ const inv = 1 / K;
161
+ const centroid = (vals) => {
162
+ let acc = vals[0];
163
+ for (let i = 1; i < K; i++)
164
+ acc = lin.add(acc, vals[i]);
165
+ return lin.scale(acc, inv);
166
+ };
167
+ const meanSpread = (vals, ctr) => {
168
+ let total = 0;
169
+ for (let i = 0; i < K; i++)
170
+ total += met(vals[i], ctr);
171
+ return total * inv;
172
+ };
173
+ // Mean metric-distance from the centroid is a magnitude `remember`:
174
+ // writing it scales the cluster's deviations about the centroid, and a
175
+ // collapse (spread → 0) reinflates the remembered shape.
176
+ return remember(inputs, {
177
+ anchor: (vals) => centroid(vals),
178
+ feature: (vals, ctr) => meanSpread(vals, ctr),
179
+ });
180
+ }
181
+ /** Palette decomposition: K values → {mean, spread}, i.e. centroid +
182
+ * uniform scale about it. `meanOf` ∘ `spreadOf`; works for any
183
+ * Linear + Metric class. */
184
+ export function paletteLens(colors) {
185
+ return {
186
+ mean: meanOf(colors),
187
+ spread: spreadOf(colors),
188
+ };
189
+ }
190
+ export function bezierGestaltLens(p0, p1, p2, p3) {
191
+ const start = Vec.lens([p0, p1], (vals) => vals[0], (target, vals) => {
192
+ const dx = target.x - vals[0].x;
193
+ const dy = target.y - vals[0].y;
194
+ return [target, { x: vals[1].x + dx, y: vals[1].y + dy }];
195
+ });
196
+ const end = Vec.lens([p2, p3], (vals) => vals[1], (target, vals) => {
197
+ const dx = target.x - vals[1].x;
198
+ const dy = target.y - vals[1].y;
199
+ return [{ x: vals[0].x + dx, y: vals[0].y + dy }, target];
200
+ });
201
+ 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 }]);
202
+ 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]);
203
+ return { start, end, startTangent, endTangent };
204
+ }
205
+ // Time-series aggregates.
206
+ //
207
+ // Scalar values indexed by position → {mean, slope}:
208
+ // mean := average; writes shift all values by the delta.
209
+ // slope := least-squares slope of (i, value_i); writes tilt about mean.
210
+ // mean and slope are invariant under each other (a y-shift preserves
211
+ // slope; tilting about the mean preserves the mean).
212
+ /** Time-series scalar aggregate over Num values as (i, value_i) samples. */
213
+ export function timeSeriesLens(values) {
214
+ const N = values.length;
215
+ if (N < 2)
216
+ throw new Error("timeSeries: need ≥ 2 values");
217
+ const mean = Num.lens(values, (vals) => {
218
+ let s = 0;
219
+ for (let i = 0; i < N; i++)
220
+ s += vals[i];
221
+ return s / N;
222
+ }, (target, vals) => {
223
+ let s = 0;
224
+ for (let i = 0; i < N; i++)
225
+ s += vals[i];
226
+ const cur = s / N;
227
+ const delta = target - cur;
228
+ return vals.map(v => v + delta);
229
+ });
230
+ // Least-squares slope = Σ (i − idxMean)(v − mean) / Σ (i − idxMean)²,
231
+ // idxMean = (N−1)/2 constant. Write tilts about the mean:
232
+ // value_i = mean + (i − idxMean)·s.
233
+ const idxMean = (N - 1) / 2;
234
+ let denomSlope = 0;
235
+ for (let i = 0; i < N; i++) {
236
+ const di = i - idxMean;
237
+ denomSlope += di * di;
238
+ }
239
+ const slope = Num.lens(values, (vals) => {
240
+ let valMean = 0;
241
+ for (let i = 0; i < N; i++)
242
+ valMean += vals[i];
243
+ valMean /= N;
244
+ let num = 0;
245
+ for (let i = 0; i < N; i++)
246
+ num += (i - idxMean) * (vals[i] - valMean);
247
+ return num / denomSlope;
248
+ }, (target, vals) => {
249
+ let valMean = 0;
250
+ for (let i = 0; i < N; i++)
251
+ valMean += vals[i];
252
+ valMean /= N;
253
+ return vals.map((_, i) => valMean + (i - idxMean) * target);
254
+ });
255
+ return { mean, slope };
256
+ }
257
+ // `meanOf` / `rigidTranslateOf` / `spreadOf` are fully trait-driven
258
+ // (Linear, Metric); `bezierGestalt` and `timeSeries` stay value-specific
259
+ // (Vec / Num) since their operations don't benefit from the trait layer.
@@ -0,0 +1,42 @@
1
+ import { type Cell, Num, Vec, type Writable } from "../index.js";
2
+ export interface FactorLensOpts {
3
+ /** Per-input mobility weights. 0 = pinned input. Defaults to all 1. */
4
+ inputWeights?: readonly number[];
5
+ /** Levenberg-Marquardt damping on the M×M normal matrix. Default 1e-6. */
6
+ damping?: number;
7
+ /** Finite-difference epsilon. Default 1e-5. */
8
+ eps?: number;
9
+ }
10
+ export declare function factorLens(inputs: readonly Num[], forwards: readonly ((xs: readonly number[]) => number)[], opts?: FactorLensOpts): Writable<Num>[];
11
+ export declare function meanDiffLens(a: Num, b: Num): {
12
+ mean: Writable<Num>;
13
+ diff: Writable<Num>;
14
+ };
15
+ export declare function procrustesLens(points: readonly Writable<Vec>[]): {
16
+ centroid: Writable<Vec>;
17
+ rotation: Writable<Num>;
18
+ scale: Writable<Num>;
19
+ };
20
+ export declare function bboxLens(points: readonly Writable<Vec>[]): {
21
+ center: Writable<Vec>;
22
+ size: Writable<Vec>;
23
+ };
24
+ export declare function procrustesJacobianLens(points: readonly Writable<Vec>[]): {
25
+ centroidX: Writable<Num>;
26
+ centroidY: Writable<Num>;
27
+ rotation: Writable<Num>;
28
+ scale: Writable<Num>;
29
+ };
30
+ type PoseV = {
31
+ x: number;
32
+ y: number;
33
+ theta: number;
34
+ };
35
+ export declare function bundleLens(pose: Writable<Cell<PoseV>>, rotateAbout: {
36
+ x: number;
37
+ y: number;
38
+ }): {
39
+ position: Writable<Vec>;
40
+ rotation: Writable<Num>;
41
+ };
42
+ export {};