bireactive 0.3.0 → 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 (117) hide show
  1. package/README.md +14 -7
  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/core/_counts.d.ts +48 -0
  9. package/dist/core/_counts.js +51 -0
  10. package/dist/core/cell.d.ts +148 -112
  11. package/dist/core/cell.js +945 -768
  12. package/dist/core/debug.d.ts +25 -0
  13. package/dist/core/debug.js +121 -0
  14. package/dist/core/derived-geometry.js +4 -7
  15. package/dist/core/index.d.ts +9 -2
  16. package/dist/core/index.js +8 -1
  17. package/dist/core/lenses/aggregates.d.ts +42 -52
  18. package/dist/core/lenses/aggregates.js +225 -116
  19. package/dist/core/lenses/geometry.d.ts +22 -4
  20. package/dist/core/lenses/geometry.js +59 -27
  21. package/dist/core/lenses/index.d.ts +6 -6
  22. package/dist/core/lenses/index.js +6 -6
  23. package/dist/core/lenses/memory.js +4 -17
  24. package/dist/core/lenses/numerical.d.ts +100 -0
  25. package/dist/core/lenses/{typed-factor.js → numerical.js} +136 -34
  26. package/dist/core/lenses/point-cloud.d.ts +67 -0
  27. package/dist/core/lenses/{closed-form-policies.js → point-cloud.js} +226 -84
  28. package/dist/core/lenses/snap.d.ts +18 -0
  29. package/dist/core/lenses/snap.js +138 -0
  30. package/dist/core/lenses/text.d.ts +40 -0
  31. package/dist/core/lenses/text.js +202 -0
  32. package/dist/core/lifecycle.js +3 -6
  33. package/dist/core/linalg.js +5 -11
  34. package/dist/core/optic.d.ts +13 -0
  35. package/dist/core/optic.js +39 -0
  36. package/dist/core/optics.d.ts +10 -0
  37. package/dist/core/optics.js +26 -0
  38. package/dist/core/store.d.ts +9 -0
  39. package/dist/core/store.js +77 -0
  40. package/dist/core/traits.d.ts +4 -7
  41. package/dist/core/traits.js +8 -12
  42. package/dist/core/values/anchor.js +0 -4
  43. package/dist/core/values/arr.d.ts +110 -0
  44. package/dist/core/values/arr.js +336 -0
  45. package/dist/core/values/audio.d.ts +8 -9
  46. package/dist/core/values/audio.js +11 -28
  47. package/dist/core/values/bool.d.ts +11 -11
  48. package/dist/core/values/bool.js +12 -22
  49. package/dist/core/values/box.d.ts +15 -20
  50. package/dist/core/values/box.js +20 -33
  51. package/dist/core/values/canvas.d.ts +18 -25
  52. package/dist/core/values/canvas.js +32 -66
  53. package/dist/core/values/color.d.ts +5 -7
  54. package/dist/core/values/color.js +5 -11
  55. package/dist/core/values/field.d.ts +6 -7
  56. package/dist/core/values/field.js +10 -35
  57. package/dist/core/values/flags.d.ts +1 -2
  58. package/dist/core/values/flags.js +1 -17
  59. package/dist/core/values/gpu.d.ts +6 -10
  60. package/dist/core/values/gpu.js +8 -22
  61. package/dist/core/values/matrix.d.ts +2 -4
  62. package/dist/core/values/matrix.js +2 -12
  63. package/dist/core/values/num.d.ts +19 -28
  64. package/dist/core/values/num.js +23 -41
  65. package/dist/core/values/pose.d.ts +2 -4
  66. package/dist/core/values/pose.js +3 -12
  67. package/dist/core/values/range.d.ts +18 -26
  68. package/dist/core/values/range.js +22 -39
  69. package/dist/core/values/reg/ambiguity.d.ts +8 -0
  70. package/dist/core/values/reg/ambiguity.js +131 -0
  71. package/dist/core/values/reg/engine.d.ts +91 -0
  72. package/dist/core/values/reg/engine.js +373 -0
  73. package/dist/core/values/reg/nfa.d.ts +42 -0
  74. package/dist/core/values/reg/nfa.js +391 -0
  75. package/dist/core/values/reg/regex.d.ts +7 -0
  76. package/dist/core/values/reg/regex.js +318 -0
  77. package/dist/core/values/reg/types.d.ts +60 -0
  78. package/dist/core/values/reg/types.js +3 -0
  79. package/dist/core/values/reg.d.ts +250 -0
  80. package/dist/core/values/reg.js +649 -0
  81. package/dist/core/values/str.d.ts +16 -60
  82. package/dist/core/values/str.js +133 -315
  83. package/dist/core/values/template.js +1 -24
  84. package/dist/core/values/transform.d.ts +3 -5
  85. package/dist/core/values/transform.js +3 -12
  86. package/dist/core/values/tri.d.ts +9 -10
  87. package/dist/core/values/tri.js +9 -15
  88. package/dist/core/values/vec.d.ts +9 -24
  89. package/dist/core/values/vec.js +9 -64
  90. package/dist/formats/lens.js +6 -9
  91. package/dist/index.d.ts +0 -11
  92. package/dist/index.js +1 -11
  93. package/dist/jsx-dev-runtime.d.ts +2 -0
  94. package/dist/jsx-dev-runtime.js +5 -0
  95. package/dist/jsx-runtime.d.ts +54 -0
  96. package/dist/jsx-runtime.js +219 -0
  97. package/dist/schema/lens.js +5 -5
  98. package/dist/shapes/drag-behaviors.d.ts +56 -0
  99. package/dist/shapes/drag-behaviors.js +102 -0
  100. package/dist/shapes/drag-spec.d.ts +52 -0
  101. package/dist/shapes/drag-spec.js +112 -0
  102. package/dist/shapes/index.d.ts +3 -1
  103. package/dist/shapes/index.js +3 -1
  104. package/dist/shapes/interaction.d.ts +2 -3
  105. package/dist/shapes/interaction.js +77 -56
  106. package/dist/shapes/label.js +6 -0
  107. package/dist/shapes/layout.d.ts +47 -1
  108. package/dist/shapes/layout.js +59 -1
  109. package/package.json +22 -1
  110. package/dist/coll.d.ts +0 -74
  111. package/dist/coll.js +0 -210
  112. package/dist/core/lenses/closed-form-policies.d.ts +0 -57
  113. package/dist/core/lenses/decompositions.d.ts +0 -14
  114. package/dist/core/lenses/decompositions.js +0 -224
  115. package/dist/core/lenses/domain-aggregates.d.ts +0 -42
  116. package/dist/core/lenses/domain-aggregates.js +0 -245
  117. package/dist/core/lenses/typed-factor.d.ts +0 -40
@@ -1,7 +1,3 @@
1
- // box.ts — reactive axis-aligned rectangle.
2
- //
3
- // Invertibles (`add`, `sub`, `scale`, `expand`) return `: this` and ride
4
- // on `Cell#lens(fwd, bwd)`. Chained calls compose into a lens chain.
5
1
  import { tween } from "../../animation/index.js";
6
2
  import { Cell, cachedDerive, fieldLens, isReadonly, lazy, reader, readNow, SKIP, } from "../cell.js";
7
3
  import { Bool } from "./bool.js";
@@ -17,7 +13,7 @@ export const lerp = (a, b, t) => ({
17
13
  h: a.h + (b.h - a.h) * t,
18
14
  });
19
15
  export const equals = (a, b) => a === b || (a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h);
20
- /** L2 distance over the flat (x, y, w, h) representation. */
16
+ /** Euclidean distance over (x, y, w, h). */
21
17
  export const metric = (a, b) => Math.hypot(a.x - b.x, a.y - b.y, a.w - b.w, a.h - b.h);
22
18
  export const expand = (b, n) => ({
23
19
  x: b.x - n,
@@ -26,15 +22,14 @@ export const expand = (b, n) => ({
26
22
  h: b.h + 2 * n,
27
23
  });
28
24
  export const contains = (b, p) => p.x >= b.x && p.x <= b.x + b.w && p.y >= b.y && p.y <= b.y + b.h;
29
- /** Closest point inside `b` to `p`. Already-inside is identity; outside
30
- * snaps to the nearest boundary point. `Box#contains`'s true-side bwd. */
25
+ /** Closest point inside `b` to `p`; already-inside is identity, outside snaps
26
+ * to the nearest boundary point. */
31
27
  export const clampToBox = (p, b) => ({
32
28
  x: Math.max(b.x, Math.min(b.x + b.w, p.x)),
33
29
  y: Math.max(b.y, Math.min(b.y + b.h, p.y)),
34
30
  });
35
- /** Closest point strictly outside `b` to `p`, displaced past the nearest
36
- * edge by `eps`. Already-outside is identity. `Box#contains`'s
37
- * false-side bwd. */
31
+ /** Closest point strictly outside `b` to `p`, displaced past the nearest edge
32
+ * by `eps`; already-outside is identity. */
38
33
  export const ejectFromBox = (p, b, eps = 1e-6) => {
39
34
  if (!contains(b, p))
40
35
  return p;
@@ -70,7 +65,7 @@ export function union(...bs) {
70
65
  }
71
66
  return { x: xMin, y: yMin, w: xMax - xMin, h: yMax - yMin };
72
67
  }
73
- /** Perimeter point on a Box facing `toward`. Default `Shape.boundary`. */
68
+ /** Perimeter point on a box facing `toward`. */
74
69
  export function edgeFrom(b, toward) {
75
70
  const cx = b.x + b.w / 2;
76
71
  const cy = b.y + b.h / 2;
@@ -122,18 +117,16 @@ export class Box extends Cell {
122
117
  lerp(b, t) {
123
118
  return Box.derive(() => lerp(this.value, readNow(b), readNow(t)));
124
119
  }
125
- /** Membership predicate. Conditional return type: a writable `Vec`
126
- * yields `Writable<Bool>` and flipping the view moves the source
127
- * `true` clamps to the nearest in-box point, `false` ejects past the
128
- * nearest edge by `eps`. Literal / RO inputs yield a bare RO `Bool`. */
120
+ /** True when `p` is inside the box. A writable `Vec` yields a `Writable<Bool>`:
121
+ * flipping it clamps `p` to the nearest in-box point (`true`) or ejects it
122
+ * past the nearest edge (`false`). Literal/RO inputs yield a read-only `Bool`. */
129
123
  contains(p) {
130
124
  if (p instanceof Vec) {
131
- // A read-only Vec has no backward path RO branch; sources and
132
- // writable lenses accept write-back.
125
+ // RO Vec has no backward path; only writable Vecs accept write-back.
133
126
  if (!isReadonly(p)) {
134
- // `.bind(Bool)` + cast steps past the generic overloads, whose
135
- // mapped-tuple inference over the full class types otherwise blows
136
- // the instantiation depth.
127
+ // `.bind(Bool)` + cast sidesteps the generic overloads, whose
128
+ // mapped-tuple inference over the full class types blows the
129
+ // instantiation depth.
137
130
  const mk = Bool.lens.bind(Bool);
138
131
  return mk([this, p], vals => contains(vals[0], vals[1]), (target, vals) => {
139
132
  const [b, v] = vals;
@@ -160,15 +153,12 @@ export class Box extends Cell {
160
153
  get area() {
161
154
  return cachedDerive(this, "area", Num, b => b.w * b.h);
162
155
  }
163
- /** Vec at parametric (u, v) within `[0,1]²`. Not memoised (arbitrary
164
- * pairs would leak a cache entry each) use the named edge getters
165
- * (`.center`, `.top`, …) for stable identity. */
156
+ /** Vec at parametric `(u, v)` in `[0,1]²`. Not memoised; use the named edge
157
+ * getters (`.center`, `.top`, ) for stable identity. */
166
158
  at(u, v) {
167
159
  return Vec.derive(this, b => ({ x: b.x + u * b.w, y: b.y + v * b.h }));
168
160
  }
169
- // Named edges RO views over `at(u, v)`, memoised under stable keys
170
- // so subscribers always see the same Vec. `lazy()` directly since
171
- // `at()` already returns a Vec.
161
+ // Named edges: memoised views over `at(u, v)` with stable identity.
172
162
  get center() {
173
163
  return lazy(this, "center", () => this.at(0.5, 0.5));
174
164
  }
@@ -184,16 +174,14 @@ export class Box extends Cell {
184
174
  get right() {
185
175
  return lazy(this, "right", () => this.at(1, 0.5));
186
176
  }
187
- /** Tween-builder, implied by the lerp trait. */
177
+ /** Tween-builder. */
188
178
  to(target, dur, ease) {
189
179
  return tween(this, target, dur, ease);
190
180
  }
191
181
  }
192
- /** Writable `Box` at `(x, y, w, h)`. Each component is a literal `number`
193
- * (lifted to a fresh seed) or an existing `Writable<Num>` (identity
194
- * passthrough). RO sources are rejected at the type level — use
195
- * `Box.derive(...)` for reactive RO tracking, or `cell.value` to
196
- * snapshot. Lock a component with `Num.pin(c)`. */
182
+ /** Writable `Box` at `(x, y, w, h)`. Each component is a literal (new cell) or
183
+ * existing writable (passed through); for read-only sources use `Box.derive`.
184
+ * Lock a component with `Num.pin`. */
197
185
  export function box(x = 0, y = 0, w = 0, h = 0) {
198
186
  if (typeof x === "number" &&
199
187
  typeof y === "number" &&
@@ -205,6 +193,5 @@ export function box(x = 0, y = 0, w = 0, h = 0) {
205
193
  const yN = num(y);
206
194
  const wN = num(w);
207
195
  const hN = num(h);
208
- // The view fully reconstructs all 4 axes (1-arg bwd ⇒ no source read).
209
196
  return Box.lens([xN, yN, wN, hN], ([bx, by, bw, bh]) => ({ x: bx, y: by, w: bw, h: bh }), v => [v.x, v.y, v.w, v.h]);
210
197
  }
@@ -3,8 +3,8 @@ import { Bool } from "./bool.js";
3
3
  import { Color } from "./color.js";
4
4
  import { Spring, type SpringOpts } from "./gpu.js";
5
5
  import { Vec } from "./vec.js";
6
- /** Raster header. The graph compares `epoch`; `tex` is an RGBA32F texture in
7
- * the shared GL context (channels 0–1, may overshoot mid-deconvolution). */
6
+ /** Raster header; the graph compares `epoch`. `tex` is an RGBA32F texture in
7
+ * the shared GL context. */
8
8
  export interface Raster {
9
9
  readonly tex: WebGLTexture;
10
10
  readonly w: number;
@@ -21,20 +21,18 @@ export declare class Canvas extends Cell<V> {
21
21
  };
22
22
  readonly _t: typeof Canvas.traits;
23
23
  constructor(v?: V);
24
- /** Per-channel invert (alpha preserved). Involution. */
24
+ /** Per-channel invert (alpha preserved). */
25
25
  invert(): this;
26
- /** Horizontal flip. Involution. */
26
+ /** Horizontal flip. */
27
27
  flipH(): this;
28
28
  /** Multiply RGB by reactive `k` (alpha preserved). Invertible while
29
29
  * k ≠ 0. */
30
30
  brightness(k: Val<number>): this;
31
- /** Grayscale (Rec.601 luma) view; complement is the per-pixel chroma
32
- * residual `(r−Y, g−Y, b−Y)`, so editing the gray view recolours the
33
- * source. The raster analog of `str.lowercase()`. */
31
+ /** Grayscale (Rec.601 luma) view; the complement is the per-pixel chroma
32
+ * residual `(r−Y, g−Y, b−Y)`, so editing the gray view recolours the source. */
34
33
  grayscale(): Writable<Canvas>;
35
- /** Chroma view (the dual of `grayscale`): `(r−Y, g−Y, b−Y)` on a mid-grey
36
- * base, complement is the luma `Y`. Editing the colour re-lights nothing —
37
- * it rewrites hue while keeping the original brightness. */
34
+ /** Chroma view: `(r−Y, g−Y, b−Y)` on a mid-grey base, complement is the luma
35
+ * `Y`. Editing it rewrites hue while keeping the original brightness. */
38
36
  chroma(): Writable<Canvas>;
39
37
  /** Sub-rectangle view (reactive `x,y,w,h`). Editing the crop composites
40
38
  * back into the source; the surround reads straight from the parent. */
@@ -43,26 +41,21 @@ export declare class Canvas extends Cell<V> {
43
41
  * Laplacian residual `source − up(down(source))`; editing the thumbnail
44
42
  * reconstructs full-res detail on top of the edit. */
45
43
  downsample(factor: number): Writable<Canvas>;
46
- /** Gaussian blur (reactive `radius`). Writable: the backward direction
47
- * runs an iterated Richardson–Lucy deconvolution seeded from the source.
48
- * Each step is `x ← x · H(target / H(x))`, non-negative and multiplicative,
49
- * so untouched regions stay fixed (their ratio is 1) while a stroke
50
- * back-solves to a sharp pre-image with far less ringing than an additive
51
- * solve. PutGet, not exact GetPut — the residual is the honest signature
52
- * of an ill-posed inverse. */
44
+ /** Gaussian blur (reactive `radius`). The backward pass runs an iterated
45
+ * Richardson–Lucy deconvolution seeded from the source
46
+ * (`x ← x · H(target / H(x))`), so untouched regions stay fixed. PutGet,
47
+ * not exact GetPut. */
53
48
  blur(radius: Val<number>): this;
54
- /** Mean colour (0–1) as a writable `Color`; the GPU reduces, the write
55
- * shifts every pixel by the delta (a rigid translate in RGB). */
49
+ /** Mean colour (0–1) as a writable `Color`; the GPU reduces, a write shifts
50
+ * every pixel by the delta. */
56
51
  meanColor(): Writable<Color>;
57
- /** Mean luma ≥ reactive `threshold` (0–1) as a writable `Bool`; flipping
58
- * the bit auto-exposes iterate the gain (clipping caps a single step)
59
- * until the mean crosses the line. */
52
+ /** Mean luma ≥ `threshold` (0–1) as a writable `Bool`; flipping the bit
53
+ * auto-exposes by iterating the gain until the mean crosses the threshold. */
60
54
  brighterThan(threshold: Val<number>): Writable<Bool>;
61
55
  /** Dimensions `(w, h)` as a read-only `Vec`. */
62
56
  get dimensions(): Vec;
63
- /** A GPU per-pixel spring driver seeded from this value's texture. The
64
- * host steps it and writes `current()` back into a root cell each frame;
65
- * settle is the GPU energy reduction. */
57
+ /** GPU per-pixel spring driver seeded from this value's texture. The host
58
+ * steps it and writes `current()` back each frame. */
66
59
  spring(opts?: SpringOpts): Spring;
67
60
  }
68
61
  /** Writable `Canvas` of size `w×h`. `painter` fills it pixel-by-pixel
@@ -1,25 +1,3 @@
1
- // canvas.ts — GPU-resident reactive raster (handle-as-value).
2
- //
3
- // Every Canvas value is a float texture in the shared WebGL2 context (see
4
- // gpu.ts); the reactive graph transports only a tiny HEADER {tex, w, h,
5
- // epoch}, comparing the monotonic epoch, so propagation never reads a pixel
6
- // and nothing is copied across the bus. Forward lenses render into a per-lens
7
- // scratch texture (reused across recomputes); the single ownership rule that
8
- // keeps no-copy safe is the same as on the CPU — a node writes only its own
9
- // texture, never one another live node still reads, which holds under the
10
- // engine's glitch-free flush.
11
- //
12
- // Tiers mirror the rest of values/:
13
- // - pure isomorphisms (`invert`, `flipH`) — one pointwise shader each way.
14
- // - reactive-param invertibles (`brightness(k)`) — read `Val<number>`.
15
- // - complement projections (`grayscale`, `downsample`) — the lossy view
16
- // plus a complement texture (chroma / Laplacian residual) recovered on
17
- // write-back, the raster analog of str.ts's case-preserving lenses.
18
- // - cross-type lenses (`meanColor` → Color, `brighterThan` → Bool).
19
- //
20
- // A backward pass that produces a ROOT cell's next value reads that root and
21
- // writes a result that becomes its value, so it uses `scratch2` (a feedback-
22
- // safe pair) to avoid reading and writing the same texture.
23
1
  import { Cell, cachedDerive, reader } from "../cell.js";
24
2
  import { Bool } from "./bool.js";
25
3
  import { Color } from "./color.js";
@@ -98,9 +76,7 @@ void main() {
98
76
  o = vec4(acc / wsum, a);
99
77
  }`;
100
78
  // Richardson–Lucy step. RL_RATIO forms target / blur(estimate) (guarded);
101
- // RL_MUL multiplies the estimate by blur(ratio) and clamps to [0,1]. Both
102
- // the non-negativity clamp and the multiplicative form keep ringing far
103
- // below an additive Van-Cittert iteration.
79
+ // RL_MUL multiplies the estimate by blur(ratio) and clamps to [0,1].
104
80
  const RL_RATIO = `${HEAD}
105
81
  uniform sampler2D u_t; uniform sampler2D u_est;
106
82
  void main() {
@@ -162,7 +138,7 @@ export class Canvas extends Cell {
162
138
  constructor(v = { tex: null, w: 0, h: 0, epoch: 0 }) {
163
139
  super(v, { equals });
164
140
  }
165
- /** Per-channel invert (alpha preserved). Involution. */
141
+ /** Per-channel invert (alpha preserved). */
166
142
  invert() {
167
143
  const sf = scratch();
168
144
  const sb = scratch();
@@ -173,7 +149,7 @@ export class Canvas extends Cell {
173
149
  };
174
150
  return this.lens(run(sf), run(sb));
175
151
  }
176
- /** Horizontal flip. Involution. */
152
+ /** Horizontal flip. */
177
153
  flipH() {
178
154
  const sf = scratch();
179
155
  const sb = scratch();
@@ -200,9 +176,8 @@ export class Canvas extends Cell {
200
176
  };
201
177
  return this.lens(run(sf, () => kf()), run(sb, () => 1 / kf()));
202
178
  }
203
- /** Grayscale (Rec.601 luma) view; complement is the per-pixel chroma
204
- * residual `(r−Y, g−Y, b−Y)`, so editing the gray view recolours the
205
- * source. The raster analog of `str.lowercase()`. */
179
+ /** Grayscale (Rec.601 luma) view; the complement is the per-pixel chroma
180
+ * residual `(r−Y, g−Y, b−Y)`, so editing the gray view recolours the source. */
206
181
  grayscale() {
207
182
  const sc = scratch();
208
183
  const sf = scratch();
@@ -213,27 +188,25 @@ export class Canvas extends Cell {
213
188
  return c;
214
189
  };
215
190
  const self = this;
216
- return Canvas.lens([self], {
217
- init: ([s]) => chromaOf(s),
218
- step: ([s], c, external) => (external ? chromaOf(s) : c),
219
- fwd: ([s]) => {
191
+ return Canvas.lens(self, {
192
+ init: s => chromaOf(s),
193
+ fwd: s => {
220
194
  const out = sf(s.w, s.h);
221
195
  pass(LUMA, out, x => x.tex("u_s", 0, s.tex));
222
196
  return stamp(out.tex, s.w, s.h);
223
197
  },
224
- bwd: (target, [s], c) => {
198
+ bwd: (target, s, c) => {
225
199
  const out = sb(s.w, s.h);
226
200
  pass(RECOLOR, out, x => {
227
201
  x.tex("u_t", 0, target.tex);
228
202
  x.tex("u_c", 1, c.tex);
229
203
  });
230
- return { updates: [stamp(out.tex, s.w, s.h)], complement: c };
204
+ return { update: stamp(out.tex, s.w, s.h), complement: c };
231
205
  },
232
206
  });
233
207
  }
234
- /** Chroma view (the dual of `grayscale`): `(r−Y, g−Y, b−Y)` on a mid-grey
235
- * base, complement is the luma `Y`. Editing the colour re-lights nothing —
236
- * it rewrites hue while keeping the original brightness. */
208
+ /** Chroma view: `(r−Y, g−Y, b−Y)` on a mid-grey base, complement is the luma
209
+ * `Y`. Editing it rewrites hue while keeping the original brightness. */
237
210
  chroma() {
238
211
  const sc = scratch();
239
212
  const sf = scratch();
@@ -244,21 +217,20 @@ export class Canvas extends Cell {
244
217
  return c;
245
218
  };
246
219
  const self = this;
247
- return Canvas.lens([self], {
248
- init: ([s]) => lumaOf(s),
249
- step: ([s], c, external) => (external ? lumaOf(s) : c),
250
- fwd: ([s]) => {
220
+ return Canvas.lens(self, {
221
+ init: s => lumaOf(s),
222
+ fwd: s => {
251
223
  const out = sf(s.w, s.h);
252
224
  pass(CHROMA_VIEW, out, x => x.tex("u_s", 0, s.tex));
253
225
  return stamp(out.tex, s.w, s.h);
254
226
  },
255
- bwd: (target, [s], c) => {
227
+ bwd: (target, s, c) => {
256
228
  const out = sb(s.w, s.h);
257
229
  pass(DELUMA, out, x => {
258
230
  x.tex("u_t", 0, target.tex);
259
231
  x.tex("u_c", 1, c.tex);
260
232
  });
261
- return { updates: [stamp(out.tex, s.w, s.h)], complement: c };
233
+ return { update: stamp(out.tex, s.w, s.h), complement: c };
262
234
  },
263
235
  });
264
236
  }
@@ -332,14 +304,13 @@ export class Canvas extends Cell {
332
304
  return res;
333
305
  };
334
306
  const self = this;
335
- return Canvas.lens([self], {
336
- init: ([s]) => residualOf(s),
337
- step: ([s], c, external) => (external ? residualOf(s) : c),
338
- fwd: ([s]) => {
307
+ return Canvas.lens(self, {
308
+ init: s => residualOf(s),
309
+ fwd: s => {
339
310
  const small = down(sdF, s.tex, s.w, s.h);
340
311
  return stamp(small.tex, small.w, small.h);
341
312
  },
342
- bwd: (target, [s], c) => {
313
+ bwd: (target, s, c) => {
343
314
  const up = suB(s.w, s.h);
344
315
  pass(UP, up, x => {
345
316
  x.tex("u_small", 0, target.tex);
@@ -351,17 +322,14 @@ export class Canvas extends Cell {
351
322
  x.tex("u_a", 0, up.tex);
352
323
  x.tex("u_b", 1, c.tex);
353
324
  });
354
- return { updates: [stamp(out.tex, s.w, s.h)], complement: c };
325
+ return { update: stamp(out.tex, s.w, s.h), complement: c };
355
326
  },
356
327
  });
357
328
  }
358
- /** Gaussian blur (reactive `radius`). Writable: the backward direction
359
- * runs an iterated Richardson–Lucy deconvolution seeded from the source.
360
- * Each step is `x ← x · H(target / H(x))`, non-negative and multiplicative,
361
- * so untouched regions stay fixed (their ratio is 1) while a stroke
362
- * back-solves to a sharp pre-image with far less ringing than an additive
363
- * solve. PutGet, not exact GetPut — the residual is the honest signature
364
- * of an ill-posed inverse. */
329
+ /** Gaussian blur (reactive `radius`). The backward pass runs an iterated
330
+ * Richardson–Lucy deconvolution seeded from the source
331
+ * (`x ← x · H(target / H(x))`), so untouched regions stay fixed. PutGet,
332
+ * not exact GetPut. */
365
333
  blur(radius) {
366
334
  const rf = reader(radius);
367
335
  const fTmp = scratch();
@@ -405,8 +373,8 @@ export class Canvas extends Cell {
405
373
  return stamp(cur.tex, v.w, v.h);
406
374
  });
407
375
  }
408
- /** Mean colour (0–1) as a writable `Color`; the GPU reduces, the write
409
- * shifts every pixel by the delta (a rigid translate in RGB). */
376
+ /** Mean colour (0–1) as a writable `Color`; the GPU reduces, a write shifts
377
+ * every pixel by the delta. */
410
378
  meanColor() {
411
379
  const self = this;
412
380
  const sb = scratch2();
@@ -423,9 +391,8 @@ export class Canvas extends Cell {
423
391
  return stamp(out.tex, v.w, v.h);
424
392
  });
425
393
  }
426
- /** Mean luma ≥ reactive `threshold` (0–1) as a writable `Bool`; flipping
427
- * the bit auto-exposes iterate the gain (clipping caps a single step)
428
- * until the mean crosses the line. */
394
+ /** Mean luma ≥ `threshold` (0–1) as a writable `Bool`; flipping the bit
395
+ * auto-exposes by iterating the gain until the mean crosses the threshold. */
429
396
  brighterThan(threshold) {
430
397
  const tf = reader(threshold);
431
398
  const self = this;
@@ -465,9 +432,8 @@ export class Canvas extends Cell {
465
432
  get dimensions() {
466
433
  return cachedDerive(this, "dimensions", Vec, v => ({ x: v.w, y: v.h }));
467
434
  }
468
- /** A GPU per-pixel spring driver seeded from this value's texture. The
469
- * host steps it and writes `current()` back into a root cell each frame;
470
- * settle is the GPU energy reduction. */
435
+ /** GPU per-pixel spring driver seeded from this value's texture. The host
436
+ * steps it and writes `current()` back each frame. */
471
437
  spring(opts) {
472
438
  const s = new Spring(this.value.w, this.value.h, opts);
473
439
  s.seed(this.value.tex);
@@ -13,7 +13,7 @@ export declare const sub: (a: V, b: V) => V;
13
13
  export declare const scale: (a: V, k: number) => V;
14
14
  export declare const lerp: (a: V, b: V, t: number) => V;
15
15
  export declare const equals: (a: V, b: V) => boolean;
16
- /** L2 distance in RGBA-space. */
16
+ /** Euclidean distance in RGBA-space. */
17
17
  export declare const metric: (a: V, b: V) => number;
18
18
  export declare class Color extends Cell<V> {
19
19
  static traits: {
@@ -35,14 +35,12 @@ export declare class Color extends Cell<V> {
35
35
  get a(): this extends import("../index.js").WritableBrand ? Writable<Num> : Num;
36
36
  get luminance(): Num;
37
37
  get css(): Cell<string>;
38
- /** Tween-builder, implied by the lerp trait. */
38
+ /** Tween-builder. */
39
39
  to(this: Writable<Color>, target: V, dur: Val<number>, ease?: Easing): Tween<V>;
40
40
  }
41
- /** Writable `Color` from RGB channels (alpha = 1). Each channel is a
42
- * literal `number` (lifted to a fresh seed) or an existing `Writable<Num>`
43
- * (identity passthrough). All-literal inputs take a fast path; mixed
44
- * inputs build a lens so channel-writes round-trip to source. */
41
+ /** Writable `Color` from RGB channels (alpha = 1). Each channel is a literal
42
+ * or a `Writable<Num>`. */
45
43
  export declare function rgb(r: Init<Num>, g: Init<Num>, b: Init<Num>): Writable<Color>;
46
- /** Writable `Color` from RGBA channels. Same lift rule as `rgb`. */
44
+ /** Writable `Color` from RGBA channels. */
47
45
  export declare function rgba(r: Init<Num>, g: Init<Num>, b: Init<Num>, a: Init<Num>): Writable<Color>;
48
46
  export {};
@@ -1,7 +1,3 @@
1
- // color.ts — reactive RGBA color.
2
- //
3
- // Invertibles (`add`, `sub`, `scale`) return `: this` and ride on
4
- // `Cell#lens(fwd, bwd)`. Chained calls compose into a lens chain.
5
1
  import { tween } from "../../animation/index.js";
6
2
  import { Cell, cachedDerive, derive, fieldLens, lazy, reader, readNow, } from "../cell.js";
7
3
  import { Num, num } from "./num.js";
@@ -15,7 +11,7 @@ export const lerp = (a, b, t) => ({
15
11
  a: a.a + (b.a - a.a) * t,
16
12
  });
17
13
  export const equals = (a, b) => a === b || (a.r === b.r && a.g === b.g && a.b === b.b && a.a === b.a);
18
- /** L2 distance in RGBA-space. */
14
+ /** Euclidean distance in RGBA-space. */
19
15
  export const metric = (a, b) => Math.hypot(a.r - b.r, a.g - b.g, a.b - b.b, a.a - b.a);
20
16
  const linearImpl = { add, sub, scale };
21
17
  const packImpl = {
@@ -78,22 +74,20 @@ export class Color extends Cell {
78
74
  return `rgba(${r}, ${g}, ${b}, ${c.a})`;
79
75
  }));
80
76
  }
81
- /** Tween-builder, implied by the lerp trait. */
77
+ /** Tween-builder. */
82
78
  to(target, dur, ease) {
83
79
  return tween(this, target, dur, ease);
84
80
  }
85
81
  }
86
- /** Writable `Color` from RGB channels (alpha = 1). Each channel is a
87
- * literal `number` (lifted to a fresh seed) or an existing `Writable<Num>`
88
- * (identity passthrough). All-literal inputs take a fast path; mixed
89
- * inputs build a lens so channel-writes round-trip to source. */
82
+ /** Writable `Color` from RGB channels (alpha = 1). Each channel is a literal
83
+ * or a `Writable<Num>`. */
90
84
  export function rgb(r, g, b) {
91
85
  if (typeof r === "number" && typeof g === "number" && typeof b === "number") {
92
86
  return new Color({ r, g, b, a: 1 });
93
87
  }
94
88
  return Color.lens([num(r), num(g), num(b)], ([r, g, b]) => ({ r, g, b, a: 1 }), target => [target.r, target.g, target.b]);
95
89
  }
96
- /** Writable `Color` from RGBA channels. Same lift rule as `rgb`. */
90
+ /** Writable `Color` from RGBA channels. */
97
91
  export function rgba(r, g, b, a) {
98
92
  if (typeof r === "number" &&
99
93
  typeof g === "number" &&
@@ -46,22 +46,21 @@ export declare class Field<T> extends Cell<FieldVal> {
46
46
  * field as `u_src`; `u_texel` = 1/size is provided). One value is committed
47
47
  * after the substeps, so the reactive graph sees a single new epoch/frame. */
48
48
  evolve(frag: string, uniforms?: Record<string, number | readonly number[]>, steps?: number): void;
49
- /** Stamp a Gaussian disc of `value` at data pixel `(x, y)`, radius `r`. The
50
- * raster interaction primitive — seed a sim, inject, paint density. */
49
+ /** Stamp a Gaussian disc of `value` at data pixel `(x, y)`, radius `r`. */
51
50
  splat(x: number, y: number, r: number, value: T, strength?: number): void;
52
- /** Whole-field mean as a read-only `T` cell. Reactive: recomputes on every
53
- * new epoch, propagates only when the value moves. One 1×1 GPU readback. */
51
+ /** Whole-field mean as a read-only `T` cell. Recomputes per epoch; one 1×1
52
+ * GPU readback. */
54
53
  mean(): Read<T>;
55
54
  /** Mean over a sub-rectangle (data pixels, reactive `box`) as a read-only
56
- * `T` cell — the field's "density of this region" observation. */
55
+ * `T` cell. */
57
56
  regionMean(box: Val<{
58
57
  x: number;
59
58
  y: number;
60
59
  w: number;
61
60
  h: number;
62
61
  }>): Read<T>;
63
- /** Render `channel` through a colormap to a read-only `Canvas`. Reactive:
64
- * re-renders on every new epoch, so rendering is itself a reactive edge. */
62
+ /** Render `channel` through a colormap to a read-only `Canvas`; re-renders
63
+ * per epoch. */
65
64
  colormap(channel: number, stops: readonly ColorStop[]): Canvas;
66
65
  }
67
66
  /** Writable `Field<T>` of size `w×h`. `painter` fills each texel (returns a
@@ -1,26 +1,3 @@
1
- // field.ts — dense, GPU-resident reactive field: a grid of `T` as ONE cell.
2
- //
3
- // A Field is a single reactive node, not one-per-texel: the value is a header
4
- // {tex, w, h, epoch} (the Canvas handle trick, see gpu.ts), the graph compares
5
- // `epoch`, and no pixel crosses the bus. So a 1000×1000 field costs the engine
6
- // exactly what a 4×4 does — graph size is the lens-chain length, independent of
7
- // resolution. Per-frame work is allocation-free: `evolve`/`splat` ping-pong
8
- // through reused scratch textures and mint only the small header.
9
- //
10
- // Generic over the texel encoding via `Kind<T>` (channel `dim` + the `Pack`
11
- // codec + the boundary cell class). The dense interior stays a flat RGBA32F
12
- // texture forever; `T` (Num/Vec/Color) is instantiated ONLY at the boundary —
13
- // `mean`, `regionMean` — never per-texel. `dim` (1/2/4) selects the channel
14
- // mask: scalar (heatmap / SDF / density), vector (flow / gradient), colour
15
- // (i.e. a Canvas).
16
- //
17
- // The point of interest is the GPU↔reactivity bridge. `evolve` runs a sim on
18
- // its own clock (a raf/Anim loop), committing one new value per frame; the
19
- // reductions (`mean`, `regionMean`) are ordinary read-only derived cells, so
20
- // they recompute when the field's epoch changes and only *propagate* when their
21
- // scalar actually moves. Reactive logic downstream (`density > 0.5` → effect)
22
- // is thereby loosely coupled to time: it observes a continuously-running GPU
23
- // simulation without knowing a frame exists.
24
1
  import { Cell, reader } from "../cell.js";
25
2
  import { Canvas, stamp as canvasStamp } from "./canvas.js";
26
3
  import { Color } from "./color.js";
@@ -70,8 +47,7 @@ void main() {
70
47
  /** GLSL float literal (always carries a decimal point). */
71
48
  const glf = (n) => (Number.isInteger(n) ? `${n}.0` : String(n));
72
49
  const glv3 = (c) => `${glf(c[0])}, ${glf(c[1])}, ${glf(c[2])}`;
73
- /** Build a piecewise-linear ramp shader over `channel` (baked per stop set,
74
- * cached by source string in gpu.ts's program cache). */
50
+ /** Build a piecewise-linear ramp shader over `channel`. */
75
51
  function rampFrag(channel, stops) {
76
52
  const ch = "rgba"[channel] ?? "r";
77
53
  let body = ` vec3 c = vec3(${glv3(stops[0][1])});\n`;
@@ -87,9 +63,9 @@ void main() {
87
63
  ${body} o = vec4(c, 1.0);
88
64
  }`;
89
65
  }
90
- /** Read-only typed view via the kind's boundary class (`Num`/`Vec`/`Color`).
91
- * Cast through the concrete `derive` signature the static's polymorphic
92
- * `this` rejects the abstract `Ctor<T>`, but at runtime `k.cls` is concrete. */
66
+ /** Read-only typed view via the kind's boundary class. The cast bypasses the
67
+ * polymorphic `this` rejecting the abstract `Ctor<T>`; `k.cls` is concrete at
68
+ * runtime. */
93
69
  function deriveT(k, parent, fn) {
94
70
  const d = k.cls;
95
71
  return d.derive(parent, fn);
@@ -152,8 +128,7 @@ export class Field extends Cell {
152
128
  if (last)
153
129
  this.value = stamp(last.tex, w, h);
154
130
  }
155
- /** Stamp a Gaussian disc of `value` at data pixel `(x, y)`, radius `r`. The
156
- * raster interaction primitive — seed a sim, inject, paint density. */
131
+ /** Stamp a Gaussian disc of `value` at data pixel `(x, y)`, radius `r`. */
157
132
  splat(x, y, r, value, strength = 1) {
158
133
  const ping = this.pingTex();
159
134
  const v = this.peek();
@@ -169,14 +144,14 @@ export class Field extends Cell {
169
144
  });
170
145
  this.value = stamp(dst.tex, v.w, v.h);
171
146
  }
172
- /** Whole-field mean as a read-only `T` cell. Reactive: recomputes on every
173
- * new epoch, propagates only when the value moves. One 1×1 GPU readback. */
147
+ /** Whole-field mean as a read-only `T` cell. Recomputes per epoch; one 1×1
148
+ * GPU readback. */
174
149
  mean() {
175
150
  const k = this.kind;
176
151
  return deriveT(k, this, v => packT(k, reduceMean({ tex: v.tex, w: v.w, h: v.h })));
177
152
  }
178
153
  /** Mean over a sub-rectangle (data pixels, reactive `box`) as a read-only
179
- * `T` cell — the field's "density of this region" observation. */
154
+ * `T` cell. */
180
155
  regionMean(box) {
181
156
  const k = this.kind;
182
157
  const bf = reader(box);
@@ -197,8 +172,8 @@ export class Field extends Cell {
197
172
  return packT(k, [m[0] / cov, m[1] / cov, m[2] / cov, 1]);
198
173
  });
199
174
  }
200
- /** Render `channel` through a colormap to a read-only `Canvas`. Reactive:
201
- * re-renders on every new epoch, so rendering is itself a reactive edge. */
175
+ /** Render `channel` through a colormap to a read-only `Canvas`; re-renders
176
+ * per epoch. */
202
177
  colormap(channel, stops) {
203
178
  const frag = rampFrag(channel, stops);
204
179
  const sc = scratch();
@@ -8,8 +8,7 @@ export declare class Flags<K extends string> extends Cell<number> {
8
8
  };
9
9
  readonly _t: typeof Flags.traits;
10
10
  constructor(names: readonly K[], v?: number);
11
- /** Bit lens for `name`; set/clear round-trips through the packed mask.
12
- * Cached per name so repeated calls return the same lens. */
11
+ /** Cached writable bit lens for `name`. */
13
12
  flag<F extends K>(name: F): Writable<Bool>;
14
13
  }
15
14
  /** Writable `Flags` from variadic bit names (bit `i` = the i-th name), or
@@ -1,18 +1,3 @@
1
- // flags.ts — named bit-flags over a packed integer.
2
- //
3
- // A `number` bitmask whose bits are named at construction. `flag(name)` is a
4
- // `Writable<Bool>` mask lens onto one bit — set/clear round-trips through the
5
- // packed value, so the integer and its named booleans are one source seen
6
- // two ways. Sparse-trait (`equals` only): bits are discrete, so there's no
7
- // linear/pack/factor surface. Bit `i` carries value `2^i`, so a flag list
8
- // ordered low→high makes the packed int equal the conventional mask (e.g.
9
- // chmod octal). Two front-ends: variadic names, or an object of defaults.
10
- //
11
- // `flag()` is the collision-free accessor (a single generic method, names
12
- // checked as a union) rather than dynamic `.name` members — no risk of a
13
- // flag named "value"/"flag" shadowing the class. Since each flag is a
14
- // writable Bool, `Tri.allOf([f.flag(a), f.flag(b)])` gives all/none/mixed
15
- // group toggles for free.
16
1
  import { Cell } from "../cell.js";
17
2
  import { Bool } from "./bool.js";
18
3
  const equals = (a, b) => a === b;
@@ -24,8 +9,7 @@ export class Flags extends Cell {
24
9
  super(v, { equals });
25
10
  this.names = names;
26
11
  }
27
- /** Bit lens for `name`; set/clear round-trips through the packed mask.
28
- * Cached per name so repeated calls return the same lens. */
12
+ /** Cached writable bit lens for `name`. */
29
13
  flag(name) {
30
14
  let lens = this.#bits.get(name);
31
15
  if (lens === undefined) {