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,245 @@
1
+ // world.ts — `world(opts)` factory for 2D rigid-body scenes.
2
+ //
3
+ // Builds a `Constraints` with a physics + contacts pipeline:
4
+ //
5
+ // [angularClamp, broadphase, snapshot, prepare, integrate,
6
+ // solveWithVelocity, writeback]
7
+ //
8
+ // Tracks bodies (via `onAdd`/`onRemove`), runs the box-box contact
9
+ // manifold lifecycle, and skips joint-linked pairs in the broadphase.
10
+ // The reactive driver is disposed on construction — rigid scenes
11
+ // drive their step explicitly (`world.step(dt)`, `animate(world)`,
12
+ // `fixedStep(world, 1/60)`). Splice `c.pipeline` to extend.
13
+ import { Constraints } from "./cluster.js";
14
+ import { ensureCapacity, prepare, snapshot, writeback } from "./phases.js";
15
+ import { Body, BoxContact, Joint } from "./rigid.js";
16
+ /** Build a `Constraints` configured for 2D rigid-body physics:
17
+ * velocity + gravity + broadphase + contact manifolds.
18
+ *
19
+ * const w = world({ gravity: [0, 1500], iterations: 24 });
20
+ * const a = w.add(body({ size: { w: 40, h: 40 } }, { x: 0, y: 0 }));
21
+ * const b = w.add(body({ size: { w: 40, h: 40 } }, { x: 100, y: 0 }));
22
+ * w.add(joint(a, b, { x: 0, y: 0 }, { x: 0, y: 0 }));
23
+ * this.anim.start(fixedStep(w, 1/60));
24
+ */
25
+ export function world(opts = {}) {
26
+ // Defaults tuned for AVBD physics — postStabilize + gentle damping.
27
+ const c = new Constraints({
28
+ iterations: opts.iterations ?? 10,
29
+ alpha: opts.alpha ?? 0.99,
30
+ beta: opts.beta ?? 1e5,
31
+ gamma: opts.gamma ?? 0.99,
32
+ postStabilize: opts.postStabilize ?? true,
33
+ });
34
+ const solver = c.solver;
35
+ let velocities = new Float64Array(solver.positions.length);
36
+ let prevVelocities = new Float64Array(velocities.length);
37
+ const aExt = opts.gravity ? Float64Array.from(opts.gravity) : new Float64Array(0);
38
+ let aExtNormSq = 0;
39
+ for (let i = 0; i < aExt.length; i++)
40
+ aExtNormSq += aExt[i] * aExt[i];
41
+ let damping = opts.damping ?? 1;
42
+ const adaptive = (opts.adaptiveWarmstart ?? aExtNormSq > 0) && aExtNormSq > 0;
43
+ const maxAngularSpeed = opts.maxAngularSpeed ?? 50;
44
+ const bodies = [];
45
+ const manifolds = new Map();
46
+ const jointed = new Map();
47
+ c.onAdd(rel => {
48
+ if (rel instanceof Body) {
49
+ bodies.push(rel);
50
+ }
51
+ else if (rel instanceof Joint) {
52
+ // Joint must be added AFTER its bodies (so cellIds are set).
53
+ const lo = Math.min(rel.bodyA.cellId, rel.bodyB.cellId);
54
+ const hi = Math.max(rel.bodyA.cellId, rel.bodyB.cellId);
55
+ const key = `${lo}_${hi}`;
56
+ jointed.set(key, (jointed.get(key) ?? 0) + 1);
57
+ }
58
+ });
59
+ c.onRemove(rel => {
60
+ if (rel instanceof Body) {
61
+ const idx = bodies.indexOf(rel);
62
+ if (idx >= 0)
63
+ bodies.splice(idx, 1);
64
+ }
65
+ else if (rel instanceof Joint) {
66
+ const lo = Math.min(rel.bodyA.cellId, rel.bodyB.cellId);
67
+ const hi = Math.max(rel.bodyA.cellId, rel.bodyB.cellId);
68
+ const key = `${lo}_${hi}`;
69
+ const n = (jointed.get(key) ?? 0) - 1;
70
+ if (n <= 0)
71
+ jointed.delete(key);
72
+ else
73
+ jointed.set(key, n);
74
+ }
75
+ });
76
+ // Clamp ω to ±maxAngularSpeed first each sub-step.
77
+ const angularClamp = (c, dt) => {
78
+ if (dt <= 0)
79
+ return;
80
+ velocities = ensureCapacity(velocities, c.solver.positions.length);
81
+ const offsets = c.solver.offsets;
82
+ const dims = c.solver.dims;
83
+ for (const body of bodies) {
84
+ if (body.cellId < 0)
85
+ continue;
86
+ const off = offsets[body.cellId];
87
+ if (dims[body.cellId] >= 3) {
88
+ const w = velocities[off + 2];
89
+ if (w > maxAngularSpeed)
90
+ velocities[off + 2] = maxAngularSpeed;
91
+ else if (w < -maxAngularSpeed)
92
+ velocities[off + 2] = -maxAngularSpeed;
93
+ }
94
+ }
95
+ };
96
+ // Broadphase: O(n²) bounding-radius sweep. Creates a `BoxContact`
97
+ // per newly-overlapping pair, disposes manifolds that no longer
98
+ // overlap. Joint-linked pairs are skipped.
99
+ const broadphase = c => {
100
+ const N = bodies.length;
101
+ const positions = c.solver.positions;
102
+ const offsets = c.solver.offsets;
103
+ const seen = new Set();
104
+ for (let i = 0; i < N; i++) {
105
+ const A = bodies[i];
106
+ if (A.cellId < 0)
107
+ continue;
108
+ const aOff = offsets[A.cellId];
109
+ const ax = positions[aOff];
110
+ const ay = positions[aOff + 1];
111
+ for (let j = i + 1; j < N; j++) {
112
+ const B = bodies[j];
113
+ if (B.cellId < 0)
114
+ continue;
115
+ if (A.mass === 0 && B.mass === 0)
116
+ continue;
117
+ const lo = Math.min(A.cellId, B.cellId);
118
+ const hi = Math.max(A.cellId, B.cellId);
119
+ if (jointed.has(`${lo}_${hi}`))
120
+ continue;
121
+ const bOff = offsets[B.cellId];
122
+ const dx = ax - positions[bOff];
123
+ const dy = ay - positions[bOff + 1];
124
+ const r = A.radius + B.radius;
125
+ if (dx * dx + dy * dy > r * r)
126
+ continue;
127
+ const key = `${lo}_${hi}`;
128
+ seen.add(key);
129
+ if (!manifolds.has(key)) {
130
+ const m = new BoxContact(c.solver, A, B);
131
+ c.solver.addTerm(m);
132
+ manifolds.set(key, m);
133
+ }
134
+ }
135
+ }
136
+ for (const [key, m] of manifolds) {
137
+ if (!seen.has(key)) {
138
+ m.dispose();
139
+ manifolds.delete(key);
140
+ }
141
+ }
142
+ };
143
+ // Integrate (inertial extrapolation). Same shape as `physics()`'s
144
+ // integrate; kept local (closing over `velocities` / `aExt` /
145
+ // `damping`) to stay self-contained.
146
+ const integrate = (c, dt) => {
147
+ if (dt <= 0)
148
+ return;
149
+ velocities = ensureCapacity(velocities, c.solver.positions.length);
150
+ prevVelocities = ensureCapacity(prevVelocities, c.solver.positions.length);
151
+ const dt2 = dt * dt;
152
+ const aExtLen = aExt.length;
153
+ const positions = c.solver.positions;
154
+ const initials = c.solver.initials;
155
+ const anchors = c.solver.anchors;
156
+ const masses = c.solver.masses;
157
+ const dims = c.solver.dims;
158
+ const offsets = c.solver.offsets;
159
+ const N = c.solver.cellCount;
160
+ for (let id = 0; id < N; id++) {
161
+ const off = offsets[id];
162
+ if (masses[off] <= 0)
163
+ continue;
164
+ const dim = dims[id];
165
+ let accelWeight = 1;
166
+ if (adaptive) {
167
+ let dot = 0;
168
+ for (let k = 0; k < dim && k < aExtLen; k++) {
169
+ const accelK = (velocities[off + k] - prevVelocities[off + k]) / dt;
170
+ dot += accelK * aExt[k];
171
+ }
172
+ const w = dot / aExtNormSq;
173
+ accelWeight = w < 0 ? 0 : w > 1 ? 1 : w;
174
+ if (!Number.isFinite(accelWeight))
175
+ accelWeight = 0;
176
+ }
177
+ for (let k = 0; k < dim; k++) {
178
+ const a = k < aExtLen ? aExt[k] : 0;
179
+ const linTerm = initials[off + k] + dt * velocities[off + k];
180
+ anchors[off + k] = linTerm + dt2 * a;
181
+ positions[off + k] = linTerm + dt2 * a * accelWeight;
182
+ }
183
+ }
184
+ };
185
+ const solveWithVelocity = (c, dt) => {
186
+ c.solver.solve(dt, () => {
187
+ if (dt <= 0)
188
+ return;
189
+ const positions = c.solver.positions;
190
+ const initials = c.solver.initials;
191
+ const masses = c.solver.masses;
192
+ const dims = c.solver.dims;
193
+ const offsets = c.solver.offsets;
194
+ const N = c.solver.cellCount;
195
+ for (let id = 0; id < N; id++) {
196
+ const off = offsets[id];
197
+ if (masses[off] <= 0)
198
+ continue;
199
+ const dim = dims[id];
200
+ for (let k = 0; k < dim; k++) {
201
+ prevVelocities[off + k] = velocities[off + k];
202
+ velocities[off + k] = ((positions[off + k] - initials[off + k]) / dt) * damping;
203
+ }
204
+ }
205
+ });
206
+ };
207
+ c.pipeline = [
208
+ angularClamp,
209
+ broadphase,
210
+ snapshot,
211
+ prepare,
212
+ integrate,
213
+ solveWithVelocity,
214
+ writeback,
215
+ ];
216
+ // Take over the time loop.
217
+ c.dispose();
218
+ Object.defineProperty(c, "aExt", { value: aExt, writable: false, enumerable: true });
219
+ Object.defineProperty(c, "bodies", { value: bodies, enumerable: true });
220
+ Object.defineProperty(c, "damping", {
221
+ get: () => damping,
222
+ set: (v) => {
223
+ damping = v;
224
+ },
225
+ enumerable: true,
226
+ });
227
+ c.velocity = (id, out = []) => {
228
+ velocities = ensureCapacity(velocities, c.solver.positions.length);
229
+ const off = c.solver.offsets[id];
230
+ const dim = c.solver.dims[id];
231
+ for (let k = 0; k < dim; k++)
232
+ out[k] = velocities[off + k];
233
+ out.length = dim;
234
+ return out;
235
+ };
236
+ c.setVelocity = (id, value) => {
237
+ velocities = ensureCapacity(velocities, c.solver.positions.length);
238
+ prevVelocities = ensureCapacity(prevVelocities, c.solver.positions.length);
239
+ const off = c.solver.offsets[id];
240
+ const dim = c.solver.dims[id];
241
+ for (let k = 0; k < dim; k++)
242
+ velocities[off + k] = value[k] ?? 0;
243
+ };
244
+ return c;
245
+ }
@@ -0,0 +1,64 @@
1
+ import type { Cell, Writable } from "./signal.js";
2
+ import { Num } from "./values/num.js";
3
+ import { Vec } from "./values/vec.js";
4
+ type V = {
5
+ x: number;
6
+ y: number;
7
+ };
8
+ /** Equal-weight mean of N Linear values; writes distribute the delta evenly. */
9
+ export declare function meanLens<T, C extends new (...args: never[]) => Cell<any>>(Cls: C, parents: readonly Cell<T>[]): Writable<InstanceType<C>>;
10
+ /** Midpoint of two writable Vecs. Drag-translates both endpoints. */
11
+ export declare function midpointLens(a: Cell<V>, b: Cell<V>): Writable<Vec>;
12
+ /** Centroid of N writable Vecs. Drag-translates all members. */
13
+ export declare function centroidLens(parents: readonly Cell<V>[]): Writable<Vec>;
14
+ export interface ArgminOpts {
15
+ /** Finite-difference epsilon for the Jacobian. Default 1e-4. */
16
+ eps?: number;
17
+ /** Levenberg-Marquardt damping. Default `1e-6` for `argminNum`
18
+ * (Jacobian is always well-conditioned for linear constraints) and
19
+ * `1e-3` for `argminVec` (IK chains hit rank-deficient regimes at
20
+ * full extension). Larger → smaller, more stable updates; smaller
21
+ * → closer to pure pseudoinverse. */
22
+ damping?: number;
23
+ }
24
+ /** Target-shaping for `argminVec`: project a write into the reachable
25
+ * workspace before the Jacobian step, sidestepping the rank-deficient
26
+ * swings at the boundary. For an N-link chain rooted at `R` with reach
27
+ * `L`, pass `clampToDisc(R, L)`. */
28
+ export interface ArgminVecOpts extends ArgminOpts {
29
+ /** Pre-write hook: transform the requested target into one that's
30
+ * guaranteed solvable. Most useful as a workspace clamp. */
31
+ clampTarget?: (target: {
32
+ x: number;
33
+ y: number;
34
+ }, currentInputs: readonly number[]) => {
35
+ x: number;
36
+ y: number;
37
+ };
38
+ }
39
+ /** Project `p` into the closed disc of radius `r` centred on `c` (points
40
+ * inside pass through). Use as `argminVec`'s `clampTarget` to fix IK
41
+ * explosion at maximum reach. */
42
+ export declare function clampToDisc(c: {
43
+ x: number;
44
+ y: number;
45
+ }, r: number): (p: {
46
+ x: number;
47
+ y: number;
48
+ }) => {
49
+ x: number;
50
+ y: number;
51
+ };
52
+ /** Scalar-output argmin lens: write does one Newton step against the FD
53
+ * Jacobian, distributing the residual by `weights`. For typed/multi-
54
+ * output cases use `factor()`; this M=1 path is kept for its hand-rolled
55
+ * inner loop. */
56
+ export declare function argminNum(inputs: readonly Num[], forward: (xs: readonly number[]) => number, weights: readonly number[], opts?: ArgminOpts): Writable<Num>;
57
+ /** 2D-output argmin lens (scalar Num inputs, `{x, y}` forward). For IK
58
+ * arms, draggable points, handle projection. Kept for its hand-rolled
59
+ * 2×2 inverse + `clampTarget` hook; see `factor()` for other M. */
60
+ export declare function argminVec(inputs: readonly Num[], forward: (xs: readonly number[]) => {
61
+ x: number;
62
+ y: number;
63
+ }, weights: readonly number[], opts?: ArgminVecOpts): Writable<Vec>;
64
+ export {};
@@ -0,0 +1,198 @@
1
+ // aggregates.ts — N→1 aggregate lens primitives over `Cls.lens` /
2
+ // `Cls.derive`.
3
+ //
4
+ // All route through the engine's N-input lens path. Stateless-bwd
5
+ // (`(target) => updates`) skips the peek loop on the hot path;
6
+ // stateful-bwd (`(target, vals) => updates`) reads the scratch.
7
+ import { Num } from "./values/num.js";
8
+ import { Vec } from "./values/vec.js";
9
+ /** Equal-weight mean of N Linear values; writes distribute the delta evenly. */
10
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape, mirrors Cls.lens
11
+ export function meanLens(Cls, parents) {
12
+ const lin = (Cls.traits?.linear ??
13
+ (() => {
14
+ throw new Error("meanLens: value class has no 'linear' trait");
15
+ })());
16
+ const n = parents.length;
17
+ const inv = 1 / n;
18
+ // biome-ignore lint/suspicious/noExplicitAny: variance escape on Cls.lens
19
+ return Cls.lens(parents,
20
+ // biome-ignore lint/suspicious/noExplicitAny: tuple-vs-array variance
21
+ (vals) => {
22
+ let acc = vals[0];
23
+ for (let i = 1; i < n; i++)
24
+ acc = lin.add(acc, vals[i]);
25
+ return lin.scale(acc, inv);
26
+ },
27
+ // biome-ignore lint/suspicious/noExplicitAny: tuple-vs-array variance
28
+ (target, vals) => {
29
+ let cur = vals[0];
30
+ for (let i = 1; i < n; i++)
31
+ cur = lin.add(cur, vals[i]);
32
+ cur = lin.scale(cur, inv);
33
+ const delta = lin.sub(target, cur);
34
+ const out = new Array(n);
35
+ for (let i = 0; i < n; i++)
36
+ out[i] = lin.add(vals[i], delta);
37
+ return out;
38
+ });
39
+ }
40
+ /** Midpoint of two writable Vecs. Drag-translates both endpoints. */
41
+ export function midpointLens(a, b) {
42
+ return Vec.lens([a, b], vals => {
43
+ const [av, bv] = vals;
44
+ return { x: (av.x + bv.x) / 2, y: (av.y + bv.y) / 2 };
45
+ }, (target, vals) => {
46
+ const [av, bv] = vals;
47
+ const dx = target.x - (av.x + bv.x) / 2;
48
+ const dy = target.y - (av.y + bv.y) / 2;
49
+ return [
50
+ { x: av.x + dx, y: av.y + dy },
51
+ { x: bv.x + dx, y: bv.y + dy },
52
+ ];
53
+ });
54
+ }
55
+ /** Centroid of N writable Vecs. Drag-translates all members. */
56
+ export function centroidLens(parents) {
57
+ const n = parents.length;
58
+ const inv = 1 / n;
59
+ return Vec.lens(parents, vals => {
60
+ const arr = vals;
61
+ let sx = 0, sy = 0;
62
+ for (let i = 0; i < n; i++) {
63
+ sx += arr[i].x;
64
+ sy += arr[i].y;
65
+ }
66
+ return { x: sx * inv, y: sy * inv };
67
+ }, (target, vals) => {
68
+ const arr = vals;
69
+ let sx = 0, sy = 0;
70
+ for (let i = 0; i < n; i++) {
71
+ sx += arr[i].x;
72
+ sy += arr[i].y;
73
+ }
74
+ const dx = target.x - sx * inv;
75
+ const dy = target.y - sy * inv;
76
+ const out = new Array(n);
77
+ for (let i = 0; i < n; i++) {
78
+ out[i] = { x: arr[i].x + dx, y: arr[i].y + dy };
79
+ }
80
+ return out;
81
+ });
82
+ }
83
+ /** Project `p` into the closed disc of radius `r` centred on `c` (points
84
+ * inside pass through). Use as `argminVec`'s `clampTarget` to fix IK
85
+ * explosion at maximum reach. */
86
+ export function clampToDisc(c, r) {
87
+ return p => {
88
+ const dx = p.x - c.x;
89
+ const dy = p.y - c.y;
90
+ const d = Math.hypot(dx, dy);
91
+ if (d <= r)
92
+ return p;
93
+ const k = r / d;
94
+ return { x: c.x + dx * k, y: c.y + dy * k };
95
+ };
96
+ }
97
+ /** Scalar-output argmin lens: write does one Newton step against the FD
98
+ * Jacobian, distributing the residual by `weights`. For typed/multi-
99
+ * output cases use `factor()`; this M=1 path is kept for its hand-rolled
100
+ * inner loop. */
101
+ export function argminNum(inputs, forward, weights, opts = {}) {
102
+ if (weights.length !== inputs.length) {
103
+ throw new Error("argminNum: weights/inputs length mismatch");
104
+ }
105
+ const eps = opts.eps ?? 1e-4;
106
+ const damping = opts.damping ?? 1e-6;
107
+ const n = inputs.length;
108
+ // Pre-allocated to avoid per-write allocations.
109
+ const J = new Array(n);
110
+ const out = new Array(n);
111
+ return Num.lens(inputs, vals => forward(vals), (target, vals) => {
112
+ const xs = vals;
113
+ const y0 = forward(xs);
114
+ const dy = target - y0;
115
+ for (let i = 0; i < n; i++) {
116
+ const saved = xs[i];
117
+ xs[i] = saved + eps;
118
+ J[i] = (forward(xs) - y0) / eps;
119
+ xs[i] = saved;
120
+ }
121
+ let denom = damping;
122
+ for (let i = 0; i < n; i++)
123
+ denom += weights[i] * J[i] * J[i];
124
+ const k = dy / denom;
125
+ for (let i = 0; i < n; i++) {
126
+ if (weights[i] === 0) {
127
+ out[i] = undefined;
128
+ }
129
+ else {
130
+ out[i] = xs[i] + weights[i] * J[i] * k;
131
+ }
132
+ }
133
+ return out;
134
+ });
135
+ }
136
+ /** 2D-output argmin lens (scalar Num inputs, `{x, y}` forward). For IK
137
+ * arms, draggable points, handle projection. Kept for its hand-rolled
138
+ * 2×2 inverse + `clampTarget` hook; see `factor()` for other M. */
139
+ export function argminVec(inputs, forward, weights, opts = {}) {
140
+ if (weights.length !== inputs.length) {
141
+ throw new Error("argminVec: weights/inputs length mismatch");
142
+ }
143
+ const eps = opts.eps ?? 1e-4;
144
+ const damping = opts.damping ?? 1e-3;
145
+ const clamp = opts.clampTarget;
146
+ const n = inputs.length;
147
+ // Pre-allocated to avoid per-write allocations.
148
+ const Jx = new Array(n);
149
+ const Jy = new Array(n);
150
+ const out = new Array(n);
151
+ return Vec.lens(inputs, vals => forward(vals), (rawTarget, vals) => {
152
+ const xs = vals;
153
+ const target = clamp ? clamp(rawTarget, xs) : rawTarget;
154
+ const y0 = forward(xs);
155
+ const dx = target.x - y0.x;
156
+ const dy = target.y - y0.y;
157
+ for (let i = 0; i < n; i++) {
158
+ const saved = xs[i];
159
+ xs[i] = saved + eps;
160
+ const ye = forward(xs);
161
+ xs[i] = saved;
162
+ Jx[i] = (ye.x - y0.x) / eps;
163
+ Jy[i] = (ye.y - y0.y) / eps;
164
+ }
165
+ // J·W·Jᵀ is the 2×2 [a b; b c]. Add damping to the diagonal, invert.
166
+ let a = damping;
167
+ let b = 0;
168
+ let c = damping;
169
+ for (let i = 0; i < n; i++) {
170
+ const w = weights[i];
171
+ a += w * Jx[i] * Jx[i];
172
+ b += w * Jx[i] * Jy[i];
173
+ c += w * Jy[i] * Jy[i];
174
+ }
175
+ const det = a * c - b * b;
176
+ if (Math.abs(det) < 1e-14) {
177
+ // Singular; leave inputs unchanged.
178
+ for (let i = 0; i < n; i++)
179
+ out[i] = undefined;
180
+ return out;
181
+ }
182
+ const invA = c / det;
183
+ const invB = -b / det;
184
+ const invC = a / det;
185
+ const kx = invA * dx + invB * dy;
186
+ const ky = invB * dx + invC * dy;
187
+ for (let i = 0; i < n; i++) {
188
+ const w = weights[i];
189
+ if (w === 0) {
190
+ out[i] = undefined;
191
+ }
192
+ else {
193
+ out[i] = xs[i] + w * (Jx[i] * kx + Jy[i] * ky);
194
+ }
195
+ }
196
+ return out;
197
+ });
198
+ }
@@ -0,0 +1,84 @@
1
+ import { type Animator, type Easing, type Tick, type Yieldable } from "../animation/index.js";
2
+ import { Cell, type Read, type Val, type Writable } from "./signal.js";
3
+ import { type TraitKey, type Traits } from "./traits.js";
4
+ /** Animator-style constraint: a writable reactive carrying `T` whose
5
+ * class declares the listed traits. Reads as a sentence:
6
+ *
7
+ * function spring<T>(s: Animatable<T, "linear" | "metric">, …) */
8
+ export type Animatable<T, K extends TraitKey = never> = Writable<Cell<T>> & Traits<T, K>;
9
+ type Seg<T> = {
10
+ readonly kind: "pose";
11
+ readonly target: T;
12
+ } | {
13
+ readonly kind: "to";
14
+ readonly target: T;
15
+ readonly dur: Val<number>;
16
+ readonly ease?: Easing;
17
+ };
18
+ /** Chainable Animator over a writable cell: `.to(...).to(...).from(start)`
19
+ * reads naturally. `.to`/`.from` are pure data — segments accumulate at
20
+ * construction; the executor generator runs them in order on iteration. */
21
+ export declare class Tween<T> implements Animator<void> {
22
+ #private;
23
+ /** @internal — use `tween(...)` or `sig.to(...)` to construct. */
24
+ constructor(sig: Animatable<T, "lerp">, segs?: readonly Seg<T>[]);
25
+ /** Append a tween segment from current value to `target` over `dur`. */
26
+ to(target: T, dur: Val<number>, ease?: Easing): Tween<T>;
27
+ /** Pose `start` as the first step, then run the rest of the chain. */
28
+ from(start: T): Tween<T>;
29
+ next(v?: Tick): IteratorResult<Yieldable, void>;
30
+ return(v?: void): IteratorResult<Yieldable, void>;
31
+ throw(e: unknown): IteratorResult<Yieldable, void>;
32
+ [Symbol.iterator](): this;
33
+ }
34
+ /** Free-fn form of one-shot tween — returns a chainable `Tween<T>`. */
35
+ export declare function tween<T>(sig: Animatable<T, "lerp">, target: T, dur: Val<number>, ease?: Easing): Tween<T>;
36
+ export interface SpringOpts<T = unknown> {
37
+ /** Natural angular frequency (rad/s). Default 13 (~0.48 s period). */
38
+ omega?: number;
39
+ /** Damping ratio. <1 underdamped, =1 critical, >1 overdamped. Default 1. */
40
+ zeta?: number;
41
+ /** Settle threshold; snap+complete when both ‖e‖ < eps and ‖v‖ < eps·ω. */
42
+ precision?: number;
43
+ /** Per-frame rate multiplier on `tick.dt`. 0 freezes evolution; 2× doubles
44
+ * speed. Reactive — re-read each frame. Default 1. */
45
+ rate?: () => number;
46
+ /** Project each frame's next value into an admissible set (clamp to a
47
+ * range, snap to a manifold, etc.). If projection moves the value,
48
+ * velocity is reset to zero — soft absorbing wall, no integrator
49
+ * fighting the boundary. Pair with `precision: 0` when the target
50
+ * may lie outside the admissible set (settle never fires there). */
51
+ project?: (v: T) => T;
52
+ }
53
+ /** Second-order damped-spring pull. */
54
+ export declare function spring<T>(sig: Animatable<T, "linear" | "metric">, target: Val<T>, opts?: SpringOpts<T>): Animator<void>;
55
+ /** Constant-speed approach (units-of-T per second). Needs linear+metric. */
56
+ export declare function toward<T>(sig: Animatable<T, "linear" | "metric">, target: Val<T>, speed: Val<number>): Animator<void>;
57
+ /** Exponential pull toward `target` at rate `k`/s (no overshoot). Needs linear. */
58
+ export declare function attract<T>(sig: Animatable<T, "linear">, target: Val<T>, k?: Val<number>): Animator<void>;
59
+ /** Drive `sig` per frame with a pure function `f(t, initial)`. */
60
+ export declare function wave<T>(sig: Writable<Cell<T>>, fn: (t: number, initial: T) => T): Animator<void>;
61
+ /** Escape hatch: drive sig per frame with `step(dt, t, current)`.
62
+ * Return `false` to terminate. Use `wave` instead for pure `f(t)`. */
63
+ export declare function driven<T>(sig: Writable<Cell<T>>, step: (dt: number, t: number, v: T) => T | false): Animator<void>;
64
+ type PlayTrigger = Yieldable | Read<unknown>;
65
+ export interface Play<R = void> extends Animator<R> {
66
+ /** End when `p` fires (truthy cell / animator completion / sleep). */
67
+ until(p: PlayTrigger): Play<R>;
68
+ /** Sequence: this, then `next`. */
69
+ then(next: PlayTrigger): Play<unknown>;
70
+ }
71
+ /** Lift any yieldable / cell-trigger / animator-factory into a Play. */
72
+ export declare function play<R>(g: Animator<R> | (() => Animator<R>)): Play<R>;
73
+ export declare function play(p: PlayTrigger | (() => Animator)): Play<unknown>;
74
+ /** Wait until `sig.value` is truthy. Wakes immediately if already true. */
75
+ export declare function when(sig: Read<unknown>): Animator<void>;
76
+ /** Reactive boolean negation as a `Cell<boolean>` (RO). */
77
+ export declare function not(sig: Read<unknown>): Cell<boolean>;
78
+ /** Wait until `sig` changes; resumes with the new value. */
79
+ export declare function untilChange<T>(sig: Cell<T>): Animator<T>;
80
+ /** Repeat `factory()` forever; bound via `.until(sig)`. */
81
+ export declare function loop(factory: () => Yieldable): Play;
82
+ /** Run `fn` every `sec` seconds (drift-corrected, `sec` may be reactive). */
83
+ export declare function every(sec: Val<number>, fn: () => void): Play;
84
+ export {};