bireactive 0.3.1 → 0.3.2

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 (89) hide show
  1. package/README.md +14 -7
  2. package/dist/core/_counts.js +5 -12
  3. package/dist/core/cell.d.ts +3 -3
  4. package/dist/core/cell.js +6 -7
  5. package/dist/core/derived-geometry.js +4 -7
  6. package/dist/core/index.d.ts +3 -1
  7. package/dist/core/index.js +3 -1
  8. package/dist/core/lenses/aggregates.d.ts +42 -52
  9. package/dist/core/lenses/aggregates.js +225 -116
  10. package/dist/core/lenses/geometry.d.ts +22 -4
  11. package/dist/core/lenses/geometry.js +59 -27
  12. package/dist/core/lenses/index.d.ts +5 -6
  13. package/dist/core/lenses/index.js +5 -6
  14. package/dist/core/lenses/memory.js +4 -17
  15. package/dist/core/lenses/numerical.d.ts +100 -0
  16. package/dist/core/lenses/{typed-factor.js → numerical.js} +136 -34
  17. package/dist/core/lenses/point-cloud.d.ts +67 -0
  18. package/dist/core/lenses/{closed-form-policies.js → point-cloud.js} +218 -81
  19. package/dist/core/lenses/snap.d.ts +1 -1
  20. package/dist/core/lenses/snap.js +3 -10
  21. package/dist/core/lenses/text.d.ts +40 -0
  22. package/dist/core/lenses/text.js +202 -0
  23. package/dist/core/lifecycle.js +3 -6
  24. package/dist/core/linalg.js +5 -11
  25. package/dist/core/optic.js +10 -15
  26. package/dist/core/optics.js +4 -8
  27. package/dist/core/store.d.ts +1 -2
  28. package/dist/core/store.js +7 -15
  29. package/dist/core/traits.d.ts +4 -7
  30. package/dist/core/traits.js +8 -12
  31. package/dist/core/values/anchor.js +0 -4
  32. package/dist/core/values/arr.d.ts +110 -0
  33. package/dist/core/values/arr.js +336 -0
  34. package/dist/core/values/audio.d.ts +8 -9
  35. package/dist/core/values/audio.js +7 -23
  36. package/dist/core/values/bool.d.ts +11 -11
  37. package/dist/core/values/bool.js +12 -22
  38. package/dist/core/values/box.d.ts +15 -20
  39. package/dist/core/values/box.js +20 -33
  40. package/dist/core/values/canvas.d.ts +18 -25
  41. package/dist/core/values/canvas.js +17 -48
  42. package/dist/core/values/color.d.ts +5 -7
  43. package/dist/core/values/color.js +5 -11
  44. package/dist/core/values/field.d.ts +6 -7
  45. package/dist/core/values/field.js +10 -35
  46. package/dist/core/values/flags.d.ts +1 -2
  47. package/dist/core/values/flags.js +1 -17
  48. package/dist/core/values/gpu.d.ts +6 -10
  49. package/dist/core/values/gpu.js +8 -22
  50. package/dist/core/values/matrix.d.ts +2 -4
  51. package/dist/core/values/matrix.js +2 -12
  52. package/dist/core/values/num.d.ts +19 -28
  53. package/dist/core/values/num.js +23 -41
  54. package/dist/core/values/pose.d.ts +2 -4
  55. package/dist/core/values/pose.js +3 -12
  56. package/dist/core/values/range.d.ts +18 -26
  57. package/dist/core/values/range.js +22 -39
  58. package/dist/core/values/reg/ambiguity.d.ts +8 -0
  59. package/dist/core/values/reg/ambiguity.js +131 -0
  60. package/dist/core/values/reg/engine.d.ts +91 -0
  61. package/dist/core/values/reg/engine.js +373 -0
  62. package/dist/core/values/reg/nfa.d.ts +42 -0
  63. package/dist/core/values/reg/nfa.js +391 -0
  64. package/dist/core/values/reg/regex.d.ts +7 -0
  65. package/dist/core/values/reg/regex.js +318 -0
  66. package/dist/core/values/reg/types.d.ts +60 -0
  67. package/dist/core/values/reg/types.js +3 -0
  68. package/dist/core/values/reg.d.ts +250 -0
  69. package/dist/core/values/reg.js +649 -0
  70. package/dist/core/values/str.d.ts +16 -60
  71. package/dist/core/values/str.js +133 -315
  72. package/dist/core/values/template.js +1 -24
  73. package/dist/core/values/transform.d.ts +3 -5
  74. package/dist/core/values/transform.js +3 -12
  75. package/dist/core/values/tri.d.ts +9 -10
  76. package/dist/core/values/tri.js +9 -15
  77. package/dist/core/values/vec.d.ts +9 -24
  78. package/dist/core/values/vec.js +9 -64
  79. package/dist/index.d.ts +0 -11
  80. package/dist/index.js +1 -11
  81. package/package.json +6 -7
  82. package/dist/coll.d.ts +0 -74
  83. package/dist/coll.js +0 -210
  84. package/dist/core/lenses/closed-form-policies.d.ts +0 -57
  85. package/dist/core/lenses/decompositions.d.ts +0 -14
  86. package/dist/core/lenses/decompositions.js +0 -224
  87. package/dist/core/lenses/domain-aggregates.d.ts +0 -42
  88. package/dist/core/lenses/domain-aggregates.js +0 -245
  89. package/dist/core/lenses/typed-factor.d.ts +0 -40
package/dist/coll.js DELETED
@@ -1,210 +0,0 @@
1
- // coll.ts — a keyed, ordered, *bidirectional* collection.
2
- //
3
- // The structural sibling of `tree.ts`. A `Coll<E>` holds stable element
4
- // handles (records of cells), and each projection — `filter` / `sortBy` /
5
- // `groupBy` — is a WRITABLE structural lens. You edit the *view* and the
6
- // backward pass writes the elements' own fields:
7
- //
8
- // sortBy(rank) → move(e, i) writes `rank` (position is a field)
9
- // groupBy(key) → move(e, k, i) writes `key` (membership is a field)
10
- // filter(pred) → insert/assert writes whatever pred demands
11
- //
12
- // Because position / group / visibility are the elements' own writable
13
- // cells, these lenses are COMPLEMENT-FREE — the "complement" lives in the
14
- // field cells, not the lens (contrast `Seq<T>`, where order is imposed and
15
- // must be remembered). One `move`/`insert` composes the backward passes up
16
- // the chain in a single `batch`: drop into a filtered + grouped + sorted
17
- // view and the filter's predicate, the group key, and the rank all get set
18
- // at once.
19
- //
20
- // Two change classes, mirroring the engine's two levels:
21
- // • value change — a field cell changes; forward derivations re-flow.
22
- // • structural edit — insert/remove/move; a discrete batched transition.
23
- // No lens edges are built at runtime, so acyclicity holds as ever.
24
- import { batch, cell, derive } from "./core/index.js";
25
- /** `field === value`, assertable by writing the field. */
26
- export function is(field, value) {
27
- const p = ((e) => field(e).value === value);
28
- p.assert = (e) => {
29
- field(e).value = value;
30
- };
31
- return p;
32
- }
33
- /** Conjunction; asserts every clause. */
34
- export function allPass(...preds) {
35
- const p = ((e) => preds.every(q => q(e)));
36
- p.assert = (e) => {
37
- for (const q of preds)
38
- q.assert?.(e);
39
- };
40
- return p;
41
- }
42
- /** Read-only projection with chainable structural lenses. */
43
- export class View {
44
- list;
45
- key;
46
- parent;
47
- constructor(list, key, parent) {
48
- this.list = list;
49
- this.key = key;
50
- this.parent = parent;
51
- }
52
- /** Current members; tracked when read in an effect/derive. */
53
- get items() {
54
- return this.list.value;
55
- }
56
- /** The source collection at the head of the chain. */
57
- get root() {
58
- let v = this;
59
- while (v.parent)
60
- v = v.parent;
61
- return v;
62
- }
63
- /** Make `e` appear in this view: satisfy own constraint, recursively up. */
64
- assertContains(e) {
65
- this.parent?.assertContains(e);
66
- this.assertSelf(e);
67
- }
68
- assertSelf(_e) { }
69
- filter(pred) {
70
- return new FilterView(this, pred);
71
- }
72
- sortBy(field) {
73
- return new SortView(this, field);
74
- }
75
- groupBy(field, opts) {
76
- return new GroupView(this, field, opts);
77
- }
78
- map(f) {
79
- return derive(() => this.list.value.map(f));
80
- }
81
- /** Remove `e` from the source. */
82
- remove(e) {
83
- this.root.removeFromSource(e);
84
- }
85
- }
86
- /** A writable source collection. */
87
- export class Coll extends View {
88
- #src;
89
- constructor(items, key) {
90
- const src = cell(items);
91
- super(src, key, null);
92
- this.#src = src;
93
- }
94
- assertContains(e) {
95
- if (!this.#src.value.includes(e))
96
- this.insert(e);
97
- }
98
- insert(e, at) {
99
- const arr = [...this.#src.value];
100
- if (at == null)
101
- arr.push(e);
102
- else
103
- arr.splice(at, 0, e);
104
- this.#src.value = arr;
105
- }
106
- removeFromSource(e) {
107
- this.#src.value = this.#src.value.filter(x => x !== e);
108
- }
109
- }
110
- class FilterView extends View {
111
- #pred;
112
- constructor(parent, pred) {
113
- super(derive(() => parent.items.filter(pred)), parent.key, parent);
114
- this.#pred = pred;
115
- }
116
- assertSelf(e) {
117
- this.#pred.assert?.(e);
118
- }
119
- }
120
- /** Sorted view. `move` writes the order field between the drop neighbours. */
121
- export class SortView extends View {
122
- #field;
123
- constructor(parent, field) {
124
- super(derive(() => [...parent.items].sort((a, b) => field(a).value - field(b).value)), parent.key, parent);
125
- this.#field = field;
126
- }
127
- move(e, to) {
128
- const others = this.items.filter(x => x !== e);
129
- batch(() => {
130
- this.assertContains(e);
131
- this.#field(e).value = between(rankAt(others, this.#field, to - 1), rankAt(others, this.#field, to));
132
- });
133
- }
134
- }
135
- /** Grouped view. `move`/`insert` write the group field (and, with a `sort`
136
- * field, the order field). Backward composition runs the parent chain. */
137
- export class GroupView {
138
- groups;
139
- #parent;
140
- #field;
141
- #order;
142
- #sort;
143
- constructor(parent, field, opts = {}) {
144
- this.#parent = parent;
145
- this.#field = field;
146
- this.#order = opts.order ?? [];
147
- this.#sort = opts.sort;
148
- this.groups = derive(() => {
149
- const sort = this.#sort;
150
- const items = sort
151
- ? [...parent.items].sort((a, b) => sort(a).value - sort(b).value)
152
- : parent.items;
153
- return groupItems(items, e => field(e).value, this.#order);
154
- });
155
- }
156
- get value() {
157
- return this.groups.value;
158
- }
159
- map(f) {
160
- return derive(() => this.value.map(f));
161
- }
162
- /** Place `e` in group `toKey` at `index`. Inserts it into the source if
163
- * it isn't there yet, asserts every upstream filter, then writes the
164
- * group key and order field — all in one batch. */
165
- move(e, toKey, index) {
166
- const target = (this.value.find(g => Object.is(g.key, toKey))?.items ?? []).filter(x => x !== e);
167
- const sort = this.#sort;
168
- batch(() => {
169
- this.#parent.assertContains(e);
170
- this.#field(e).value = toKey;
171
- if (sort && index != null)
172
- sort(e).value = between(rankAt(target, sort, index - 1), rankAt(target, sort, index));
173
- });
174
- }
175
- insert(e, toKey, index) {
176
- this.move(e, toKey, index);
177
- }
178
- remove(e) {
179
- this.#parent.remove(e);
180
- }
181
- }
182
- function groupItems(items, keyOf, order) {
183
- const buckets = new Map();
184
- for (const k of order)
185
- buckets.set(k, []);
186
- for (const e of items) {
187
- const k = keyOf(e);
188
- const arr = buckets.get(k);
189
- if (arr)
190
- arr.push(e);
191
- else
192
- buckets.set(k, [e]);
193
- }
194
- return [...buckets].map(([key, items]) => ({ key, items }));
195
- }
196
- function rankAt(arr, field, i) {
197
- return i >= 0 && i < arr.length ? field(arr[i]).value : undefined;
198
- }
199
- function between(a, b) {
200
- if (a != null && b != null)
201
- return (a + b) / 2;
202
- if (a != null)
203
- return a + 1;
204
- if (b != null)
205
- return b - 1;
206
- return 0;
207
- }
208
- export function coll(items, key) {
209
- return new Coll(items, key);
210
- }
@@ -1,57 +0,0 @@
1
- import { type Cell, Num, type Read, type Traits, Vec, type Writable } from "../index.js";
2
- type V = {
3
- x: number;
4
- y: number;
5
- };
6
- /** Writable centroid; on write, translates every point by the delta.
7
- * The Vec-specific group-action reading of `mean`. */
8
- export declare function rigidTranslate(points: readonly Writable<Vec>[]): Writable<Vec>;
9
- /** Writable angle from `pivot` to `points[0]`; write rotates every input
10
- * about `pivot` by (target − current) via its `Pivotal` trait.
11
- *
12
- * Trait-generic: Vec rotates position; Pose rotates position AND
13
- * orientation. Rotation-about-pivot fixes the pivot and preserves radial
14
- * distances, so scale-about-pivot reads unchanged. `pivot` is reactive
15
- * (re-read per write); pass `rigidTranslate(points)` for rotation about
16
- * the cluster's own centroid. */
17
- export declare function rotateAbout<T extends {
18
- x: number;
19
- y: number;
20
- }>(points: readonly Writable<Traits<T, "pivotal"> & Cell<T>>[], pivot: Read<V>): Writable<Num>;
21
- /** Writable radial distance from pivot to `points[0]`; write scales every
22
- * input radially about `pivot` (negative target reflects). Exact
23
- * cross-channel invariance with `rotateAbout`.
24
- *
25
- * Complement carries per-point offsets from the pivot at the last
26
- * non-degenerate state, so a collapse onto the pivot (radius ≈ 0)
27
- * reinflates from the stored shape. Pose `theta` survives the round-trip
28
- * (only spatial offset is stored). */
29
- export declare function scaleAbout<T extends {
30
- x: number;
31
- y: number;
32
- }>(points: readonly Writable<Traits<T, "pivotal"> & Cell<T>>[], pivot: Read<V>): Writable<Num>;
33
- /** Per-axis scale about a pivot. Vec-specific (Pivotal has no per-axis
34
- * method yet). Complement carries per-point per-axis fractions of
35
- * point 0's offset, so a per-axis collapse is recoverable (cf.
36
- * `bboxLens.size`). */
37
- export declare function scaleAboutXY(points: readonly Writable<Vec>[], pivot: Read<V>): Writable<Vec>;
38
- export declare function bestFitLine(points: readonly Writable<Vec>[]): {
39
- point: Writable<Vec>;
40
- direction: Writable<Num>;
41
- };
42
- export declare function bestFitCircle(points: readonly Writable<Vec>[]): {
43
- center: Writable<Vec>;
44
- radius: Writable<Num>;
45
- };
46
- export declare function pca(points: readonly Writable<Vec>[]): {
47
- mean: Writable<Vec>;
48
- rotation: Writable<Num>;
49
- majorLength: Writable<Num>;
50
- minorLength: Writable<Num>;
51
- };
52
- /** Writable total over K parts; write scales all parts proportionally,
53
- * preserving their ratios. A `remember` anchored at zero with a signed
54
- * sum feature: a collapse to zero reinflates the stored ratios, seeded
55
- * uniform so an all-zero start splits evenly. */
56
- export declare function total(parts: readonly Writable<Num>[]): Writable<Num>;
57
- export {};
@@ -1,14 +0,0 @@
1
- import { Num, Vec, type Writable } from "../index.js";
2
- export declare function meanDiff(a: Num, b: Num): {
3
- mean: Writable<Num>;
4
- diff: Writable<Num>;
5
- };
6
- export declare function procrustes(points: readonly Writable<Vec>[]): {
7
- centroid: Writable<Vec>;
8
- rotation: Writable<Num>;
9
- scale: Writable<Num>;
10
- };
11
- export declare function bbox(points: readonly Writable<Vec>[]): {
12
- center: Writable<Vec>;
13
- size: Writable<Vec>;
14
- };
@@ -1,224 +0,0 @@
1
- // decompositions.ts — closed-form N→M lens decompositions (Vec/Num).
2
- //
3
- // N inputs → M coupled writable outputs, where writing one output
4
- // preserves the readings of the other M−1 (cross-channel invariance).
5
- // Each bwd is a hand-rolled group action (translate / rotate / scale
6
- // about the centroid), so cross-channel invariance is EXACT and one
7
- // write lands in O(K). For the generic numerical N→M escape hatch (when
8
- // no closed form fits) see `factor` in `typed-factor.ts`.
9
- import { Num, SKIP, Vec } from "../index.js";
10
- // meanDiff — M=2 isomorphism baseline.
11
- //
12
- // (a, b) → ((a+b)/2, a−b). Square full-rank linear lens; bwd is the
13
- // inverse change of basis — exact, cross-channel invariant.
14
- export function meanDiff(a, b) {
15
- const mean = Num.lens([a, b], vals => (vals[0] + vals[1]) / 2, (target, vals) => {
16
- const d = vals[0] - vals[1];
17
- return [target + d / 2, target - d / 2];
18
- });
19
- const diff = Num.lens([a, b], vals => vals[0] - vals[1], (target, vals) => {
20
- const m = (vals[0] + vals[1]) / 2;
21
- return [m + target / 2, m - target / 2];
22
- });
23
- return { mean, diff };
24
- }
25
- // procrustes — closed-form similarity (the showcase).
26
- //
27
- // K writable Vecs → {centroid, rotation (angle of point[0] about
28
- // centroid), scale (its distance from centroid)}. Each bwd is a
29
- // closed-form transform about the centroid:
30
- // write centroid → translate every point by (c − old c)
31
- // write rotation → rotate every point about centroid by (θ − old θ)
32
- // write scale → scale every point about centroid by (s / old s)
33
- // These commute on the cluster's similarity orbit, so the three outputs
34
- // have EXACT cross-channel invariance. Degenerate: K < 2 leaves
35
- // rotation/scale undefined; a collapsed cluster (scale → 0) makes
36
- // rotation singular and scale writes no-ops; target scale = 0 collapses
37
- // to the centroid.
38
- export function procrustes(points) {
39
- const K = points.length;
40
- if (K < 2)
41
- throw new Error("procrustes: need ≥ 2 points");
42
- const centroid = Vec.lens(points, (vals) => {
43
- let sx = 0;
44
- let sy = 0;
45
- for (let i = 0; i < K; i++) {
46
- sx += vals[i].x;
47
- sy += vals[i].y;
48
- }
49
- return { x: sx / K, y: sy / K };
50
- }, (target, vals) => {
51
- let sx = 0;
52
- let sy = 0;
53
- for (let i = 0; i < K; i++) {
54
- sx += vals[i].x;
55
- sy += vals[i].y;
56
- }
57
- const dx = target.x - sx / K;
58
- const dy = target.y - sy / K;
59
- const out = new Array(K);
60
- for (let i = 0; i < K; i++)
61
- out[i] = { x: vals[i].x + dx, y: vals[i].y + dy };
62
- return out;
63
- });
64
- const rotation = Num.lens(points, (vals) => {
65
- let sx = 0;
66
- let sy = 0;
67
- for (let i = 0; i < K; i++) {
68
- sx += vals[i].x;
69
- sy += vals[i].y;
70
- }
71
- const cx = sx / K;
72
- const cy = sy / K;
73
- return Math.atan2(vals[0].y - cy, vals[0].x - cx);
74
- }, (target, vals) => {
75
- let sx = 0;
76
- let sy = 0;
77
- for (let i = 0; i < K; i++) {
78
- sx += vals[i].x;
79
- sy += vals[i].y;
80
- }
81
- const cx = sx / K;
82
- const cy = sy / K;
83
- const rx0 = vals[0].x - cx;
84
- const ry0 = vals[0].y - cy;
85
- if (rx0 * rx0 + ry0 * ry0 < 1e-24) {
86
- // Collapsed cluster; no angle to rotate from.
87
- return vals.map(() => SKIP);
88
- }
89
- const oldθ = Math.atan2(ry0, rx0);
90
- const dθ = target - oldθ;
91
- const cos = Math.cos(dθ);
92
- const sin = Math.sin(dθ);
93
- const out = new Array(K);
94
- for (let i = 0; i < K; i++) {
95
- const rx = vals[i].x - cx;
96
- const ry = vals[i].y - cy;
97
- out[i] = { x: cx + cos * rx - sin * ry, y: cy + sin * rx + cos * ry };
98
- }
99
- return out;
100
- });
101
- const centroidOf = (vals) => {
102
- let sx = 0;
103
- let sy = 0;
104
- for (let i = 0; i < K; i++) {
105
- sx += vals[i].x;
106
- sy += vals[i].y;
107
- }
108
- return { x: sx / K, y: sy / K };
109
- };
110
- const refreshDevs = (devs, vals) => {
111
- const c = centroidOf(vals);
112
- return devs.map((d, i) => {
113
- const dx = vals[i].x - c.x;
114
- const dy = vals[i].y - c.y;
115
- return dx * dx + dy * dy > 1e-18 ? { x: dx, y: dy } : d;
116
- });
117
- };
118
- const scale = Num.lens(points, {
119
- init: (vals) => {
120
- const c = centroidOf(vals);
121
- return { devs: vals.map(v => ({ x: v.x - c.x, y: v.y - c.y })) };
122
- },
123
- step: (vals, c) => ({ devs: refreshDevs(c.devs, vals) }),
124
- fwd: (vals) => {
125
- const c = centroidOf(vals);
126
- return Math.hypot(vals[0].x - c.x, vals[0].y - c.y);
127
- },
128
- bwd: (target, vals, c) => {
129
- const cen = centroidOf(vals);
130
- const d0 = c.devs[0];
131
- const r0 = Math.hypot(d0.x, d0.y);
132
- if (r0 < 1e-12)
133
- return { updates: vals.map(() => SKIP), complement: c };
134
- const k = target / r0;
135
- const out = c.devs.map(d => ({ x: cen.x + k * d.x, y: cen.y + k * d.y }));
136
- return { updates: out, complement: c };
137
- },
138
- });
139
- return { centroid, rotation, scale };
140
- }
141
- // bbox — closed-form axis-aligned bounding box.
142
- //
143
- // K Vecs → {center, size}. Forward is min/max (piecewise-constant
144
- // Jacobian — fatal for FD), but the closed-form bwd is exact:
145
- // write center → translate all points by (c − old c)
146
- // write size → scale all about center by component-wise ratio
147
- // Center↔size invariance is exact. Degenerate axes (size = 0) write
148
- // as no-ops; negative size reflects (kept permissive).
149
- export function bbox(points) {
150
- const K = points.length;
151
- if (K < 1)
152
- throw new Error("bbox: need ≥ 1 point");
153
- const computeBox = (vals) => {
154
- let minX = Number.POSITIVE_INFINITY;
155
- let minY = Number.POSITIVE_INFINITY;
156
- let maxX = Number.NEGATIVE_INFINITY;
157
- let maxY = Number.NEGATIVE_INFINITY;
158
- for (let i = 0; i < K; i++) {
159
- const x = vals[i].x;
160
- const y = vals[i].y;
161
- if (x < minX)
162
- minX = x;
163
- if (x > maxX)
164
- maxX = x;
165
- if (y < minY)
166
- minY = y;
167
- if (y > maxY)
168
- maxY = y;
169
- }
170
- return {
171
- cx: (minX + maxX) / 2,
172
- cy: (minY + maxY) / 2,
173
- sx: maxX - minX,
174
- sy: maxY - minY,
175
- };
176
- };
177
- const center = Vec.lens(points, (vals) => {
178
- const b = computeBox(vals);
179
- return { x: b.cx, y: b.cy };
180
- }, (target, vals) => {
181
- const b = computeBox(vals);
182
- const dx = target.x - b.cx;
183
- const dy = target.y - b.cy;
184
- const out = new Array(K);
185
- for (let i = 0; i < K; i++)
186
- out[i] = { x: vals[i].x + dx, y: vals[i].y + dy };
187
- return out;
188
- });
189
- const refreshFracs = (fracs, vals) => {
190
- const b = computeBox(vals);
191
- const hx = b.sx > 1e-12 ? b.sx / 2 : 0;
192
- const hy = b.sy > 1e-12 ? b.sy / 2 : 0;
193
- return fracs.map((f, i) => ({
194
- x: hx > 0 ? (vals[i].x - b.cx) / hx : f.x,
195
- y: hy > 0 ? (vals[i].y - b.cy) / hy : f.y,
196
- }));
197
- };
198
- const size = Vec.lens(points, {
199
- init: (vals) => {
200
- const b = computeBox(vals);
201
- const halfX0 = b.sx > 1e-12 ? b.sx / 2 : 1;
202
- const halfY0 = b.sy > 1e-12 ? b.sy / 2 : 1;
203
- return {
204
- fracs: vals.map(v => ({
205
- x: b.sx > 1e-12 ? (v.x - b.cx) / halfX0 : 0,
206
- y: b.sy > 1e-12 ? (v.y - b.cy) / halfY0 : 0,
207
- })),
208
- };
209
- },
210
- step: (vals, c) => ({ fracs: refreshFracs(c.fracs, vals) }),
211
- fwd: (vals) => {
212
- const b = computeBox(vals);
213
- return { x: b.sx, y: b.sy };
214
- },
215
- bwd: (target, vals, c) => {
216
- const b = computeBox(vals);
217
- const halfTx = target.x / 2;
218
- const halfTy = target.y / 2;
219
- const out = c.fracs.map(f => ({ x: b.cx + f.x * halfTx, y: b.cy + f.y * halfTy }));
220
- return { updates: out, complement: c };
221
- },
222
- });
223
- return { center, size };
224
- }
@@ -1,42 +0,0 @@
1
- import { type Cell, Num, type Read, type Traits, type Val, Vec, type Writable } from "../index.js";
2
- /** Equal-weight mean (writable of `inputs[0]`'s class); writes distribute
3
- * the delta evenly. Class inferred from the first input; needs `linear`. */
4
- export declare function mean<S extends Traits<any, "linear">>(inputs: readonly Writable<S>[]): Writable<S>;
5
- /** Weighted blend of K branches over any `Linear` type. See module note. */
6
- export declare function mix<S extends Traits<any, "linear">>(weights: readonly Val<number>[], branches: readonly Writable<S>[]): Writable<S>;
7
- /** Two-branch router (mix simplex *vertex*): reads the live branch, writes
8
- * flow entirely to it, the other is left put. Flipping `cond` snaps the
9
- * output to the other branch's stored value. */
10
- export declare function select<S extends Traits<any, "linear">>(cond: Read<boolean>, whenFalse: Writable<S>, whenTrue: Writable<S>): Writable<S>;
11
- /** Two-branch crossfade (mix simplex *edge*): `lerp(a, b, t)`. Writing
12
- * keeps `t` fixed and splits the delta by influence. */
13
- export declare function crossfade<S extends Traits<any, "linear">>(t: Read<number>, a: Writable<S>, b: Writable<S>): Writable<S>;
14
- /** Mean radial distance from the centroid; write scales the cluster's
15
- * deviations so the new mean matches the target. Trait-driven via
16
- * `Linear` + `Metric`, so it works for any class declaring both (Vec,
17
- * Color, Pose, Box, Range, custom).
18
- *
19
- * Complement carries per-input deviations normalized by the current mean
20
- * radius, so `spread = T` places each input at `centroid + normDev_i * T`
21
- * and a collapse (spread → 0) reinflates the original SHAPE. Centroid is
22
- * recomputed every read/write, so an intervening mean translate is not
23
- * stale. */
24
- export declare function spread<T extends NonNullable<unknown>, S extends Cell<T> & Traits<T, "linear" | "metric">>(inputs: readonly Writable<S>[]): Writable<Num>;
25
- /** Mean/spread decomposition: K values → {mean, spread}, i.e. centroid +
26
- * uniform scale about it. `mean` ∘ `spread`; works for any
27
- * Linear + Metric class (palettes, point clouds, poses, …). */
28
- export declare function meanSpread<T extends NonNullable<unknown>, S extends Cell<T> & Traits<T, "linear" | "metric">>(colors: readonly Writable<S>[]): {
29
- mean: Writable<S>;
30
- spread: Writable<Num>;
31
- };
32
- export declare function bezierGestalt(p0: Writable<Vec>, p1: Writable<Vec>, p2: Writable<Vec>, p3: Writable<Vec>): {
33
- start: Writable<Vec>;
34
- end: Writable<Vec>;
35
- startTangent: Writable<Vec>;
36
- endTangent: Writable<Vec>;
37
- };
38
- /** Time-series scalar aggregate over Num values as (i, value_i) samples. */
39
- export declare function timeSeries(values: readonly Writable<Num>[]): {
40
- mean: Writable<Num>;
41
- slope: Writable<Num>;
42
- };