effect-motion 0.4.0 → 0.6.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 (91) hide show
  1. package/README.md +3 -3
  2. package/dist/Camera.d.ts +186 -49
  3. package/dist/Camera.js +343 -76
  4. package/dist/Color.d.ts +101 -1
  5. package/dist/Color.js +101 -1
  6. package/dist/EffectMotionError.d.ts +17 -0
  7. package/dist/EffectMotionError.js +17 -0
  8. package/dist/Entity.d.ts +684 -30
  9. package/dist/Entity.js +281 -27
  10. package/dist/Font.d.ts +108 -0
  11. package/dist/Font.js +95 -0
  12. package/dist/Image.d.ts +71 -0
  13. package/dist/Image.js +50 -0
  14. package/dist/Instance.d.ts +73 -11
  15. package/dist/Instance.js +44 -11
  16. package/dist/Motion.d.ts +328 -53
  17. package/dist/Motion.js +278 -46
  18. package/dist/Physics.d.ts +154 -20
  19. package/dist/Physics.js +92 -11
  20. package/dist/Projection.d.ts +36 -132
  21. package/dist/Projection.js +33 -292
  22. package/dist/Resource.d.ts +26 -0
  23. package/dist/Resource.js +41 -0
  24. package/dist/Runner.d.ts +714 -349
  25. package/dist/Runner.js +197 -150
  26. package/dist/Scene.d.ts +603 -175
  27. package/dist/Scene.js +596 -112
  28. package/dist/Timing.d.ts +170 -13
  29. package/dist/Timing.js +124 -6
  30. package/dist/Tree.d.ts +39 -0
  31. package/dist/Tree.js +127 -0
  32. package/dist/index.d.ts +54 -5
  33. package/dist/index.js +56 -5
  34. package/dist/particles/Particle.d.ts +2 -2
  35. package/dist/particles/ParticleField.d.ts +44 -42
  36. package/dist/particles/ParticleField.js +8 -9
  37. package/dist/particles/constructors.d.ts +5 -6
  38. package/dist/particles/constructors.js +8 -3
  39. package/dist/particles/legacy.d.ts +58 -0
  40. package/dist/particles/legacy.js +46 -0
  41. package/dist/particles/simulate.js +11 -3
  42. package/dist/particles/step.js +14 -10
  43. package/dist/types.d.ts +5 -0
  44. package/dist/types.js +1 -0
  45. package/package.json +58 -60
  46. package/dist/CameraHelpers.d.ts +0 -70
  47. package/dist/CameraHelpers.js +0 -239
  48. package/dist/CanvasExporter.d.ts +0 -12
  49. package/dist/CanvasExporter.js +0 -40
  50. package/dist/Fonts.d.ts +0 -41
  51. package/dist/Fonts.js +0 -27
  52. package/dist/Images.d.ts +0 -33
  53. package/dist/Images.js +0 -24
  54. package/dist/PngExporter.d.ts +0 -6
  55. package/dist/PngExporter.js +0 -85
  56. package/dist/Renderer.d.ts +0 -118
  57. package/dist/Renderer.js +0 -508
  58. package/dist/Shapes.d.ts +0 -11
  59. package/dist/Shapes.js +0 -11
  60. package/dist/demo.d.ts +0 -3
  61. package/dist/demo.js +0 -71
  62. package/dist/render/dof.d.ts +0 -27
  63. package/dist/render/dof.js +0 -37
  64. package/dist/render/paint.d.ts +0 -30
  65. package/dist/render/paint.js +0 -36
  66. package/dist/render/shapes.d.ts +0 -42
  67. package/dist/render/shapes.js +0 -310
  68. package/dist/shapes/Circle.d.ts +0 -32
  69. package/dist/shapes/Circle.js +0 -9
  70. package/dist/shapes/Ellipse.d.ts +0 -35
  71. package/dist/shapes/Ellipse.js +0 -10
  72. package/dist/shapes/Group.d.ts +0 -126
  73. package/dist/shapes/Group.js +0 -89
  74. package/dist/shapes/Hud.d.ts +0 -38
  75. package/dist/shapes/Hud.js +0 -35
  76. package/dist/shapes/Image.d.ts +0 -45
  77. package/dist/shapes/Image.js +0 -28
  78. package/dist/shapes/Line.d.ts +0 -47
  79. package/dist/shapes/Line.js +0 -35
  80. package/dist/shapes/Path.d.ts +0 -107
  81. package/dist/shapes/Path.js +0 -32
  82. package/dist/shapes/Rect.d.ts +0 -51
  83. package/dist/shapes/Rect.js +0 -22
  84. package/dist/shapes/Shape2D.d.ts +0 -49
  85. package/dist/shapes/Shape2D.js +0 -52
  86. package/dist/shapes/Shapes.d.ts +0 -11
  87. package/dist/shapes/Shapes.js +0 -11
  88. package/dist/shapes/Square.d.ts +0 -32
  89. package/dist/shapes/Square.js +0 -11
  90. package/dist/shapes/Text.d.ts +0 -51
  91. package/dist/shapes/Text.js +0 -24
package/dist/Scene.d.ts CHANGED
@@ -1,60 +1,211 @@
1
- import { Context } from "effect";
2
1
  import * as Cause from "effect/Cause";
3
2
  import type * as Duration from "effect/Duration";
4
3
  import * as Effect from "effect/Effect";
5
4
  import * as Fiber from "effect/Fiber";
6
5
  import type * as Schedule from "effect/Schedule";
7
- import type * as Schema from "effect/Schema";
8
6
  import type * as Scope from "effect/Scope";
9
7
  import * as Stream from "effect/Stream";
10
- import type * as Camera from "./Camera.js";
11
8
  import type * as Color from "./Color.js";
12
- import type * as Entity from "./Entity.js";
9
+ import * as Entity from "./Entity.js";
13
10
  import type * as Instance from "./Instance.js";
14
11
  import * as Phaser from "./Phaser.js";
12
+ import type * as Projection from "./Projection.js";
13
+ import type * as Resource from "./Resource.js";
15
14
  import * as Runner from "./Runner.js";
16
15
  export declare const TypeId: "~motion/Scene";
16
+ /**
17
+ * A scene: an animation body plus the composition it plays in.
18
+ *
19
+ * @remarks
20
+ * Inert on its own — building one runs no animation and produces no frames.
21
+ * It is a description that {@link run}, {@link stream}, or {@link play}
22
+ * later executes, which is what lets the same scene be replayed, nested, or
23
+ * rendered at different settings without change.
24
+ *
25
+ * @typeParam E - How the scene can fail.
26
+ * @typeParam R - What it needs to run (fonts, images, and other resources).
27
+ */
17
28
  export interface Scene<E = never, R = never> {
18
29
  readonly [TypeId]: typeof TypeId;
19
- readonly runner: Effect.Effect<void, E, R | Scope.Scope>;
30
+ /**
31
+ * The scene body. Loader requirements are EXCLUDED here: frames are pure
32
+ * of resource bytes (the engine cannot measure text), so running a scene
33
+ * never needs a loader — only rendering its frames does (they re-surface
34
+ * on `Frame<Resources>` via `~resources`).
35
+ */
36
+ readonly runner: Effect.Effect<void, E, Resource.ExcludeLoaders<R> | Scope.Scope>;
20
37
  /**
21
38
  * composition config, After Effects–style: what this comp IS. The
22
39
  * runner inherits the ROOT scene's; a played scene keeps its own as
23
- * its bounds (see {@link play}). Unlike annotations, read by the runtime.
40
+ * its bounds (see {@link play}).
24
41
  */
25
42
  readonly width: number;
26
43
  readonly height: number;
27
44
  readonly backgroundColor: Color.Color;
28
- /** tooling-facing metadata; never read by the runtime */
29
- readonly annotations: Context.Context<never>;
30
- annotate<I, S>(key: Context.Key<I, S>, value: S): Scene<E, R>;
31
- annotateMerge(context: Context.Context<never>): Scene<E, R>;
45
+ /**
46
+ * DISPLAY-ONLY name (a picker label, never an identifier) — set via the
47
+ * optional leading argument of {@link make}. Names may collide; unique
48
+ * identity belongs to whatever registers the scene (e.g. a studio.ts
49
+ * record key). Never read by the runtime: playback is identical with
50
+ * and without a name.
51
+ */
52
+ readonly name?: string;
53
+ /** phantom: the loader members of R, carried to `Frame<Resources>` */
54
+ readonly "~resources": Resource.ExtractLoaders<R>;
32
55
  }
33
- export type AnyScene = Scene<never, never>;
34
- export declare const make: <const Eff extends Effect.Effect<any, any, any>, const AEff>(f: () => Generator<Eff, AEff, never>, meta?: Partial<Runner.CompConfig>) => Scene<[Eff] extends [never] ? never : [Eff] extends [Effect.Effect<infer _A, infer E, infer _R>] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Effect<infer _A, infer _E, infer R>] ? R : never>;
35
- export declare const instantiate: <Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>>(entity: Entity.Entity<Name, Data, Traits>, props: Runner.InstantiateProps<Data["~type.make.in"]>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, never, Runner.Runner>;
56
+ export type AnyScene = Scene<any, any>;
57
+ /** the loader requirements a scene's frames carry (what render will demand) */
58
+ export type Resources<S extends AnyScene> = S["~resources"];
59
+ /** the scene's failure channel */
60
+ export type Error<S extends AnyScene> = S extends Scene<infer E, any> ? E : never;
61
+ type GeneratorE<Eff> = [Eff] extends [never] ? never : [Eff] extends [Effect.Effect<infer _A, infer E, infer _R>] ? E : never;
62
+ type GeneratorR<Eff> = [Eff] extends [never] ? never : [Eff] extends [Effect.Effect<infer _A, infer _E, infer R>] ? R : never;
63
+ /**
64
+ * Declare a scene from a generator body.
65
+ *
66
+ * @remarks
67
+ * The generator is where a scene is written: `yield*` an
68
+ * {@link instantiate} to create something, `yield*` an animator to move it.
69
+ * Yielding is what makes time pass — statements between yields all happen on
70
+ * the same frame.
71
+ *
72
+ * The body does NOT run here. `make` captures it, so a scene can be
73
+ * declared once at module scope and run many times; each run re-executes
74
+ * the body from scratch with fresh entities.
75
+ *
76
+ * `meta` sets what the composition IS — its pixel dimensions and background.
77
+ * That is distinct from playback settings like frame rate and seed, which
78
+ * are chosen later at {@link run} / {@link stream}, because the same scene
79
+ * may legitimately be played back at different rates.
80
+ *
81
+ * An optional leading `name` is a display label for pickers and tooling
82
+ * only; it is never read during playback.
83
+ *
84
+ * @param f - The scene body.
85
+ * @param meta - Composition config: `width`, `height`, `backgroundColor`.
86
+ * @defaultValue `meta` — 1920×1080, transparent background
87
+ * @returns An inert {@link Scene}, ready to run, stream, or play.
88
+ *
89
+ * @example
90
+ * A named 500×300 scene on a dark background.
91
+ * ```typescript
92
+ * const scene = Scene.make(
93
+ * "intro",
94
+ * function* () {
95
+ * const dot = yield* Scene.instantiate("Circle", { radius: 20 });
96
+ * yield* dot.pipe(Motion.moveTo({ x: 400 }, "1 second"));
97
+ * },
98
+ * { width: 500, height: 300, backgroundColor: Color.hex("#16161d") },
99
+ * );
100
+ * ```
101
+ */
102
+ export declare const make: {
103
+ <const Eff extends Effect.Effect<any, any, any>, const AEff>(f: () => Generator<Eff, AEff, never>, meta?: Partial<Runner.CompConfig>): Scene<GeneratorE<Eff>, GeneratorR<Eff>>;
104
+ <const Eff extends Effect.Effect<any, any, any>, const AEff>(name: string, f: () => Generator<Eff, AEff, never>, meta?: Partial<Runner.CompConfig>): Scene<GeneratorE<Eff>, GeneratorR<Eff>>;
105
+ };
106
+ /**
107
+ * Create an entity and put it in the scene.
108
+ *
109
+ * @remarks
110
+ * `kind` selects the entity and, with it, the exact props allowed — asking
111
+ * for a `"Circle"` gets you `radius`, a `"Text"` gets `text` and
112
+ * `fontSize`. Everything is optional and defaulted, so `instantiate("Circle",
113
+ * {})` is a valid white circle at the origin.
114
+ *
115
+ * The entity appears immediately and stays for the rest of the scene. It is
116
+ * mounted under the ambient parent — the root, or the enclosing Group when
117
+ * created inside one.
118
+ *
119
+ * What you get back is a lightweight HANDLE, not the entity's data. It is
120
+ * what animators take, and it stays valid as the data changes underneath;
121
+ * to read the current state use {@link data}. Because the handle is itself
122
+ * pipeable, you can animate straight off the call without binding it first.
123
+ *
124
+ * Containers (`Group`, `Hud`) accept a `children` array that is deliberately
125
+ * permissive: a bare string becomes a Text, an existing handle is adopted,
126
+ * and an un-yielded `instantiate` is resolved for you.
127
+ *
128
+ * @param kind - Which entity: `"Circle"`, `"Rect"`, `"Text"`, `"Line"`,
129
+ * `"Path"`, `"Ellipse"`, `"Group"`, `"Hud"`, `"Image"`, or `"Camera"`.
130
+ * @param props - Initial field values; all optional.
131
+ * @returns A handle to the live entity.
132
+ *
133
+ * @example
134
+ * A shape, and a Group adopting mixed children.
135
+ * ```typescript
136
+ * const dot = yield* Scene.instantiate("Circle", {
137
+ * position: Entity.vec3({ x: 100, y: 50 }),
138
+ * radius: 20,
139
+ * fillColor: Color.hex("#7f5af0"),
140
+ * });
141
+ *
142
+ * const panel = yield* Scene.instantiate("Group", {
143
+ * children: [
144
+ * "a bare string becomes a Text",
145
+ * Scene.instantiate("Rect", { width: 200, height: 40 }),
146
+ * dot,
147
+ * ],
148
+ * });
149
+ * ```
150
+ */
151
+ export declare const instantiate: <Tag extends Entity.EntityTag>(kind: Tag, props: Runner.InstantiateProps<Tag>) => Effect.Effect<Instance.Instance<Tag>, never, Runner.Runner>;
36
152
  export declare const tick: Effect.Effect<void, never, Runner.Runner>;
37
153
  /**
38
- * Hold the scene for `duration` of scene time (frames at the runner's
39
- * frame rate) — `Effect.sleep`'s sibling, but in frames, not wall time.
40
- * A zero-length duration is a no-op.
154
+ * Hold the scene still for `duration`.
155
+ *
156
+ * @remarks
157
+ * `Effect.sleep`'s sibling, but counted in FRAMES at the runner's frame
158
+ * rate rather than read from a clock. That distinction is load-bearing:
159
+ * a wall-clock sleep would produce a different number of frames on a slow
160
+ * machine, and scenes must be reproducible.
161
+ *
162
+ * A zero-length duration is a no-op — unlike an animator, which always
163
+ * consumes at least one frame.
164
+ *
165
+ * Use `Motion.wait` instead when the hold belongs inside an animator chain.
166
+ *
167
+ * @param duration - How long to hold, in scene time.
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * yield* Scene.sleep("500 millis");
172
+ * ```
41
173
  */
42
174
  export declare const sleep: (duration: Duration.Input) => Effect.Effect<void, never, Runner.Runner>;
43
- export interface FrameEntry<Entity extends Entity.AnyEntity> {
44
- data: Entity["data"]["Type"];
45
- entity: Entity;
175
+ /**
176
+ * One instance as a frame carries it. The entity DEFINITION is gone: `data`
177
+ * is a member of the closed union, so `data._tag` is the identity and the
178
+ * renderer narrows on it instead of dispatching on an entity object.
179
+ */
180
+ export interface FrameEntry {
181
+ data: Entity.Entity;
182
+ }
183
+ /**
184
+ * One rendered moment: every entity's state at a single instant, plus the
185
+ * metadata needed to draw it.
186
+ *
187
+ * @remarks
188
+ * A frame is self-describing — it carries its own resolution, background,
189
+ * frame rate, and camera view, so a renderer needs nothing but the frame to
190
+ * produce a picture. That is what lets frames be serialized, cached, or sent
191
+ * to a different process.
192
+ *
193
+ * `instances` is keyed by instance id and holds plain data, not handles. The
194
+ * active camera is deliberately absent from it (it is view state, surfaced
195
+ * as `camera` instead), and `root` names the group everything hangs from,
196
+ * which is never itself drawn.
197
+ *
198
+ * @typeParam Resources - Fonts and images the renderer will need; frames
199
+ * carry the requirement, never the bytes.
200
+ */
201
+ export interface Frame<out Resources = never> {
46
202
  /**
47
- * builtin visibility, held beside the data; renderers skip `false`.
48
- * Optional in the frame type (a hand-built frame or external producer
49
- * may omit it) — absent means visible; the runner always sets it.
203
+ * phantom: the loader requirements the renderer will demand for this
204
+ * frame. Never a runtime value — an unused type parameter would be
205
+ * structurally erased, so it must anchor on an (always-absent) field.
50
206
  */
51
- $visible?: boolean;
52
- }
53
- export type EntriesFromEntities<Entities> = Entities extends Entity.AnyEntity ? {
54
- [K in Entities as K["name"]]: FrameEntry<K>;
55
- }[Entities["name"]] : never;
56
- export interface Frame<Entities extends Entity.AnyEntity = Entity.AnyEntity> {
57
- instances: Record<string, EntriesFromEntities<Entities>>;
207
+ readonly "~resources"?: Resources;
208
+ instances: Record<string, FrameEntry>;
58
209
  /** id of the root group (conventionally "root"); never rendered itself */
59
210
  root: string;
60
211
  /** render metadata — frameRate from the runner settings, resolution and
@@ -63,10 +214,33 @@ export interface Frame<Entities extends Entity.AnyEntity = Entity.AnyEntity> {
63
214
  width: number;
64
215
  height: number;
65
216
  backgroundColor: Color.Color;
66
- /** the active camera's view; Camera.IDENTITY when unused */
67
- camera: Camera.CameraState;
217
+ /** the active camera's view; the resting view when unused */
218
+ camera: Projection.CameraView & Projection.PointOfInterest;
219
+ /**
220
+ * Mounted scenes (`Scene.play`), keyed by the id of the group they mount
221
+ * under. Each is a render-to-texture boundary: the renderer draws the
222
+ * subtree to its own target at these bounds, under an identity camera,
223
+ * and composites the result. An id absent here is a plain group.
224
+ */
225
+ comps: Record<string, Runner.CompConfig>;
68
226
  }
69
- export declare const step: <E, R>(runningScene: RunningScene<E, R>) => Effect.Effect<Frame<Entity.AnyEntity> | null, E, never>;
227
+ /**
228
+ * Advance a running scene by exactly one frame.
229
+ *
230
+ * @remarks
231
+ * Returns the frame that was produced, or `null` once the scene is over —
232
+ * which is the signal to stop pulling. Every concurrent branch advances
233
+ * together on each call, which is what keeps concurrency from affecting the
234
+ * frames a scene produces.
235
+ *
236
+ * A scene that runs past its `maxFrames` cap dies here with a message
237
+ * naming the limit, rather than looping forever — the guard against an
238
+ * accidental `Schedule.forever` with nothing to stop it.
239
+ *
240
+ * @param runningScene - The handle from {@link run}.
241
+ * @returns The next frame, or `null` when the scene has ended.
242
+ */
243
+ export declare const step: <E, R>(runningScene: RunningScene<E, R>) => Effect.Effect<Frame<Resource.ExtractLoaders<R>> | null, E, never>;
70
244
  export interface RunningScene<E, R> {
71
245
  readonly runner: Runner.Runner["Service"];
72
246
  readonly scene: Scene<E, R>;
@@ -75,11 +249,120 @@ export interface RunningScene<E, R> {
75
249
  /** frames delivered so far — mutated by `step` for the maxFrames cap */
76
250
  framesDelivered: number;
77
251
  }
78
- export declare const run: <E, R>(scene: Scene<E, R>, settings?: Partial<Runner.Settings>) => Effect.Effect<RunningScene<E, R>, never, Scope.Scope | Exclude<Exclude<Exclude<Exclude<R, Scope.Scope>, never>, Phaser.Phaser>, Runner.Runner>>;
79
- export declare const stream: <E = never, R = never>(scene: Scene<E, R>, settings?: Partial<Runner.Settings>) => Stream.Stream<Frame<Entity.AnyEntity>, Exclude<E, Cause.Done<any>>, Exclude<Exclude<Exclude<Exclude<Exclude<R, Scope.Scope>, never>, Phaser.Phaser>, Runner.Runner>, Scope.Scope>>;
252
+ /**
253
+ * Start a scene and hand back a handle for advancing it manually.
254
+ *
255
+ * @remarks
256
+ * The low-level entry point, for drivers that need to own the frame loop —
257
+ * an exporter writing files, or a player synchronizing to its own clock.
258
+ * Starting a scene does not produce any frames; pair this with {@link step}
259
+ * to pull them one at a time.
260
+ *
261
+ * Most code wants {@link stream} instead, which wraps exactly this pairing
262
+ * in a stream.
263
+ *
264
+ * @param scene - The scene to start.
265
+ * @param settings - Playback settings.
266
+ * @returns A running-scene handle to pass to {@link step}.
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * const running = yield* Scene.run(scene, { frameRate: 30 });
271
+ * let frame = yield* Scene.step(running);
272
+ * while (frame !== null) {
273
+ * frame = yield* Scene.step(running);
274
+ * }
275
+ * ```
276
+ */
277
+ export declare const run: <E, R>(scene: Scene<E, R>, settings?: Partial<Runner.Settings>) => Effect.Effect<RunningScene<E, R>, never, Scope.Scope | Exclude<Exclude<Exclude<Exclude<Resource.ExcludeLoaders<R>, Scope.Scope>, never>, Phaser.Phaser>, Runner.Runner>>;
278
+ /**
279
+ * Play a scene and get its frames as a lazy stream — the usual way to
280
+ * consume one.
281
+ *
282
+ * @remarks
283
+ * Frames are produced on demand, so a player can pull at its own pace and a
284
+ * long scene never has to be materialized all at once. The stream ends when
285
+ * the scene does.
286
+ *
287
+ * `settings` is where playback choices live — `frameRate`, `seed`,
288
+ * `maxFrames` — as opposed to what the composition IS (its size and
289
+ * background), which was fixed at {@link make}. The same scene can therefore
290
+ * be streamed at 30fps for a preview and 60fps for a final render without
291
+ * being rewritten.
292
+ *
293
+ * Note the frame count depends on the frame rate: a one-second animation is
294
+ * 30 frames at 30fps and 60 at 60fps, plus a final resting frame.
295
+ *
296
+ * @param scene - The scene to play.
297
+ * @param settings - Playback settings.
298
+ * @defaultValue `frameRate` 60, `seed` `"effect-motion"`, `maxFrames` 36_000
299
+ * @returns A stream of frames.
300
+ *
301
+ * @example
302
+ * Collect every frame of a scene at 30fps.
303
+ * ```typescript
304
+ * const frames = yield* Scene.stream(scene, { frameRate: 30 }).pipe(
305
+ * Stream.runCollect,
306
+ * );
307
+ * ```
308
+ */
309
+ export declare const stream: <E = never, R = never>(scene: Scene<E, R>, settings?: Partial<Runner.Settings>) => Stream.Stream<Frame<Resource.ExtractLoaders<R>>, Exclude<E, Cause.Done<any>>, Exclude<Exclude<Exclude<Exclude<Exclude<Resource.ExcludeLoaders<R>, Scope.Scope>, never>, Phaser.Phaser>, Runner.Runner>, Scope.Scope>>;
80
310
  type Updater<Data> = Data | ((data: Data) => Data);
81
- export declare const data: <Name extends string, Data extends Schema.Top>(instance: Instance.Instance<Name, Data>) => Effect.Effect<Data["Type"] & ({} | undefined), never, Runner.Runner>;
82
- export declare const update: <Name extends string, Data extends Schema.Top>(instance: Instance.Instance<Name, Data>, props: Updater<Data["Type"]>) => Effect.Effect<void | false, never, Runner.Runner>;
311
+ /**
312
+ * Read an entity's current data.
313
+ *
314
+ * @remarks
315
+ * An {@link Instance} is only a handle, so this is how you get at the live
316
+ * values behind it — to branch on where something is, or to compute a target
317
+ * relative to its current state.
318
+ *
319
+ * The result is a snapshot for THIS frame, not a live view; read again on a
320
+ * later frame to see later values. The returned type is narrowed by the
321
+ * handle's kind, so a Circle's `radius` is available without casting.
322
+ *
323
+ * Reading a destroyed entity is a loud defect rather than a silent
324
+ * `undefined`.
325
+ *
326
+ * @param instance - Handle to read.
327
+ * @returns The entity's data as of this frame.
328
+ * @see {@link update} to write it.
329
+ *
330
+ * @example
331
+ * ```typescript
332
+ * const { position, radius } = yield* Scene.data(dot);
333
+ * yield* dot.pipe(Motion.moveTo({ x: position.x + radius * 4 }, "1 second"));
334
+ * ```
335
+ */
336
+ export declare const data: <Tag extends Entity.EntityTag>(instance: Instance.Instance<Tag>) => Effect.Effect<Entity.EntityByTag<Tag>, never, Runner.Runner>;
337
+ /**
338
+ * Set an entity's data immediately, with no animation.
339
+ *
340
+ * @remarks
341
+ * A hard cut on the current frame — the counterpart to the animators, which
342
+ * interpolate. Use it to set something up before animating (jolt the camera,
343
+ * then spring it back), or to change a field no animator covers, like
344
+ * `text`, `visible`, or a Path's `commands`.
345
+ *
346
+ * Pass an object to replace the data, or a function to derive it from the
347
+ * current values — the function form is preferred, since it reads and writes
348
+ * atomically.
349
+ *
350
+ * Updating a destroyed entity is a no-op returning `false`, not an error.
351
+ *
352
+ * @param instance - Handle to update.
353
+ * @param props - New data, or `(current) => next`.
354
+ *
355
+ * @example
356
+ * Retitle a label and jolt the camera, both on this frame.
357
+ * ```typescript
358
+ * yield* Scene.update(label, (d) => ({ ...d, text: "done" }));
359
+ * yield* Scene.update(camera, (d) => ({
360
+ * ...d,
361
+ * position: Entity.vec3({ ...d.position, x: 22 }),
362
+ * }));
363
+ * ```
364
+ */
365
+ export declare const update: <Tag extends Entity.EntityTag>(instance: Instance.Instance<Tag>, props: Updater<Entity.EntityByTag<Tag>>) => Effect.Effect<void | false, never, Runner.Runner>;
83
366
  /**
84
367
  * Move `child` under `parent`, detaching it from its current parent first
85
368
  * (so it is never double-referenced). Instances are born mounted under the
@@ -97,80 +380,34 @@ export declare const settings: () => Effect.Effect<{
97
380
  /** the movie's composition config — the ROOT scene's width/height/background */
98
381
  export declare const comp: () => Effect.Effect<Runner.CompConfig, never, Runner.Runner>;
99
382
  /**
100
- * The active camera instance — an ordinary instance carrying `~position`
101
- * (world x/y/z), Euler orientation (`rotX`/`rotY`/`rotZ`), and
102
- * `focalLength` (perspective strength — see Projection.defaultFocalLength),
103
- * so the existing animators drive it: `Scene.make(function* () { const cam
104
- * = yield* Scene.camera; yield* cam.pipe(Motion.moveTo({ z: -400 })) })`.
105
- * A default resting camera is always present (width-relative 50mm-equivalent
106
- * focal length, projecting z=0 content to plain-2D placement); animate it
107
- * directly, or `Scene.setCamera` to swap in another instance. The camera is
108
- * never drawn.
383
+ * The active camera, as an ordinary animatable instance.
384
+ *
385
+ * @remarks
386
+ * There is always a camera — a resting one is present from the first frame,
387
+ * placed so that content at `z = 0` renders exactly as flat 2D. A scene that
388
+ * never touches the camera looks like a plain 2D scene, and reaching for
389
+ * `Scene.camera` is how you opt into depth.
390
+ *
391
+ * It is a normal instance, so every animator drives it with no special
392
+ * vocabulary: `moveTo` flies it (including along `z` to push in or pull
393
+ * back), `tweenTo` on `focalLength` changes the lens, springs and forks work
394
+ * as they do anywhere. `Camera` helpers add aiming on top.
395
+ *
396
+ * The camera is view state and is never itself drawn.
397
+ *
398
+ * @returns A handle to the active camera.
399
+ * @see {@link setCamera} to swap in a different one.
400
+ *
401
+ * @example
402
+ * Push the camera in, revealing depth in the scene.
403
+ * ```typescript
404
+ * const camera = yield* Scene.camera;
405
+ * yield* camera.pipe(Motion.moveTo({ z: -300 }, "1200 millis", "easeInOutCubic"));
406
+ * ```
109
407
  */
110
- export declare const camera: Effect.Effect<Instance.Instance<"Camera", Schema.Struct<{
111
- x: Schema.withConstructorDefault<Schema.Number>;
112
- y: Schema.withConstructorDefault<Schema.Number>;
113
- z: Schema.withConstructorDefault<Schema.Number>;
114
- rotX: Schema.withConstructorDefault<Schema.Number>;
115
- rotY: Schema.withConstructorDefault<Schema.Number>;
116
- rotZ: Schema.withConstructorDefault<Schema.Number>;
117
- focalLength: Schema.withConstructorDefault<Schema.Number>;
118
- focusDistance: Schema.withConstructorDefault<Schema.Number>;
119
- aperture: Schema.withConstructorDefault<Schema.Number>;
120
- poiX: Schema.optionalKey<Schema.Number>;
121
- poiY: Schema.optionalKey<Schema.Number>;
122
- poiZ: Schema.optionalKey<Schema.Number>;
123
- }>, {
124
- readonly "~position": {
125
- get: (data: {
126
- readonly x: number;
127
- readonly y: number;
128
- readonly z: number;
129
- readonly rotX: number;
130
- readonly rotY: number;
131
- readonly rotZ: number;
132
- readonly focalLength: number;
133
- readonly focusDistance: number;
134
- readonly aperture: number;
135
- readonly poiX?: number;
136
- readonly poiY?: number;
137
- readonly poiZ?: number;
138
- }) => {
139
- x: number;
140
- y: number;
141
- z: number;
142
- };
143
- set: (data: {
144
- readonly x: number;
145
- readonly y: number;
146
- readonly z: number;
147
- readonly rotX: number;
148
- readonly rotY: number;
149
- readonly rotZ: number;
150
- readonly focalLength: number;
151
- readonly focusDistance: number;
152
- readonly aperture: number;
153
- readonly poiX?: number;
154
- readonly poiY?: number;
155
- readonly poiZ?: number;
156
- }, value: Entity.Position) => {
157
- readonly x: number;
158
- readonly y: number;
159
- readonly z: number;
160
- readonly rotX: number;
161
- readonly rotY: number;
162
- readonly rotZ: number;
163
- readonly focalLength: number;
164
- readonly focusDistance: number;
165
- readonly aperture: number;
166
- readonly poiX?: number;
167
- readonly poiY?: number;
168
- readonly poiZ?: number;
169
- };
170
- };
171
- }>, never, Runner.Runner>;
408
+ export declare const camera: Effect.Effect<Instance.Instance<"Camera">, never, Runner.Runner>;
172
409
  /** Swap the active camera to `instance`; its live data becomes the view. */
173
- export declare const setCamera: (instance: Instance.Instance) => Effect.Effect<void, never, Runner.Runner>;
410
+ export declare const setCamera: (instance: Instance.Instance<"Camera">) => Effect.Effect<void, never, Runner.Runner>;
174
411
  /**
175
412
  * Handle to a branch of animation (a fork, background, or played scene).
176
413
  * `finished` resolves at the branch's SEMANTIC end — `Scene.finish` or
@@ -184,44 +421,150 @@ export interface BranchHandle<A = unknown, E = never> {
184
421
  readonly fiber: Fiber.Fiber<A, E>;
185
422
  }
186
423
  /**
187
- * Finish the innermost enclosing branch (the current fork, played scene,
188
- * or the scene body itself): whoever awaits the branch's `finished`
189
- * proceeds, the branch stops blocking its parent's end, and the code
190
- * after `finish` keeps running as a TAIL — bounded by the parent, which
191
- * interrupts it at scene end like a background. Idempotent; completion
192
- * implies finish. NOTE: a failure in the tail (after finish) is NOT
193
- * reported — by then nothing is listening.
424
+ * Declare the current branch semantically over, while its code keeps
425
+ * running.
426
+ *
427
+ * @remarks
428
+ * Separates "this is done as far as everyone else is concerned" from "this
429
+ * fiber has stopped". Anyone awaiting the branch's `finished` proceeds
430
+ * immediately, and the branch stops holding the scene open — but code after
431
+ * `finish` keeps running as a TAIL, bounded by the parent exactly like a
432
+ * {@link background}.
433
+ *
434
+ * The use is a beat that should hand off early: an entrance whose successor
435
+ * starts as soon as the element has landed, while a slow ring-out continues
436
+ * underneath. Without `finish`, the successor would wait for the tail.
437
+ *
438
+ * Idempotent, and completion implies finish. Note that a failure in the tail
439
+ * is NOT reported — by then nothing is listening.
440
+ *
441
+ * Calling it outside a running scene is a loud defect.
442
+ *
443
+ * @example
444
+ * Hand off after the landing; the wobble plays on borrowed time.
445
+ * ```typescript
446
+ * yield* Scene.fork(
447
+ * Effect.gen(function* () {
448
+ * yield* badge.pipe(Motion.moveTo({ y: 100 }, "400 millis"));
449
+ * yield* Scene.finish;
450
+ * yield* badge.pipe(Physics.springTo({ y: 96 }, "bounce"));
451
+ * }),
452
+ * );
453
+ * ```
194
454
  */
195
455
  export declare const finish: Effect.Effect<undefined, never, never>;
196
456
  /**
197
- * Run `effect`, then repeat it as long as `schedule` recurs, with the
198
- * schedule evaluated in scene time (frames at the runner's frame rate) —
199
- * `Effect.repeat`'s sibling, but paced by frames instead of the wall
200
- * clock. The first run is immediate; the schedule paces the gaps after
201
- * runs; each run's result is fed to the schedule as input. Resolves with
202
- * the schedule's final output once it is done; a failed run fails
203
- * immediately without consulting the schedule again.
457
+ * Play an animation again and again, on a schedule.
458
+ *
459
+ * @remarks
460
+ * `Effect.repeat`'s sibling, paced by FRAMES rather than the wall clock —
461
+ * which is what keeps a looping scene deterministic.
462
+ *
463
+ * The first run happens immediately, and the schedule paces the gaps AFTER
464
+ * each run. So `Schedule.spaced("400 millis")` means "run, rest 400ms, run
465
+ * again", and the loop count comes from the schedule: `Schedule.forever`
466
+ * for ambient motion (usually inside {@link background}), or
467
+ * `Schedule.upTo({ times: 2 })` for a bounded three-run sequence.
468
+ *
469
+ * A failing run fails immediately, without consulting the schedule again.
470
+ *
471
+ * @param effect - The animation to repeat.
472
+ * @param schedule - How often, and how many times.
473
+ * @returns The schedule's final output.
474
+ *
475
+ * @example
476
+ * Three round-trips, resting 400ms between them.
477
+ * ```typescript
478
+ * yield* Scene.repeat(
479
+ * ball.pipe(
480
+ * Motion.moveTo({ x: 430 }, "600 millis", "easeInOutCubic"),
481
+ * Motion.moveTo({ x: 70 }, "600 millis", "easeInOutCubic"),
482
+ * ),
483
+ * Schedule.spaced("400 millis").pipe(Schedule.upTo({ times: 2 })),
484
+ * );
485
+ * ```
204
486
  */
205
487
  export declare const repeat: <A, E, R, Output, ScheduleE, ScheduleR>(effect: Effect.Effect<A, E, R>, schedule: Schedule.Schedule<Output, A, ScheduleE, ScheduleR>) => Effect.Effect<Output, E | ScheduleE, R | ScheduleR | Runner.Runner>;
206
488
  /**
207
- * Run `effect` concurrently with the rest of the scene, sharing frame
208
- * phases, and return its fiber immediately.
209
- *
210
- * NOTE: this inverts Effect's own `fork` semantics — the scene's end
211
- * WAITS for forked work. A scene whose body returns while forks are
212
- * still animating keeps producing frames until the last fork finishes
213
- * (so a scene containing only a fork still plays). Use
214
- * {@link background} for work that should be cut off at scene end
215
- * instead. Forks are supervised by the fiber that spawned them: a fork
216
- * made inside another fork is interrupted when its spawner completes.
489
+ * Start an animation alongside the rest of the scene and continue
490
+ * immediately, without waiting for it.
491
+ *
492
+ * @remarks
493
+ * Where {@link all} blocks until its branches finish, `fork` returns at
494
+ * once — so the scene body carries on while the forked animation plays.
495
+ * That is what lets independent timelines overlap, and what makes spawning
496
+ * work in a loop possible.
497
+ *
498
+ * Note this INVERTS Effect's own `fork`: the scene's end waits for forked
499
+ * work. A body that returns while forks are still animating keeps producing
500
+ * frames until the last one finishes, so a scene consisting only of a fork
501
+ * still plays in full. For work that should instead be cut off when the
502
+ * scene ends, use {@link background}.
503
+ *
504
+ * The returned handle carries `finished` — yield it to wait for this branch
505
+ * specifically — and `fiber`, to interrupt it early.
506
+ *
507
+ * @param effect - The animation to run alongside.
508
+ * @returns A handle with `finished` and `fiber`.
509
+ * @see {@link background} for work bounded by the scene's end.
510
+ *
511
+ * @example
512
+ * Spawn overlapping dots; the scene lives until the last one has faded.
513
+ * ```typescript
514
+ * yield* Scene.repeat(
515
+ * Scene.fork(
516
+ * Effect.gen(function* () {
517
+ * const dot = yield* Scene.instantiate("Circle", { radius: 8 });
518
+ * yield* dot.pipe(
519
+ * Motion.moveTo({ x: 440 }, "1200 millis"),
520
+ * Motion.fadeTo(0, "300 millis"),
521
+ * );
522
+ * }),
523
+ * ),
524
+ * Schedule.fixed("200 millis").pipe(Schedule.upTo({ times: 5 })),
525
+ * );
526
+ * ```
217
527
  */
218
528
  export declare const fork: <A, E = never, R = never>(effect: Effect.Effect<A, E, R>) => Effect.Effect<BranchHandle<A, E>, never, Runner.Runner | Exclude<Exclude<R, never>, Phaser.Phaser>>;
219
529
  /**
220
- * Like {@link fork}, but the fiber is INTERRUPTED at scene end instead
221
- * of awaited — for indefinite work (`Scene.repeat(…, Schedule.forever)`)
222
- * that should play for the duration of the scene without keeping it
223
- * alive. "Scene end" includes the fork drain: backgrounds keep animating
224
- * while awaited forks finish, and are stopped after the last one.
530
+ * Like {@link fork}, but the animation is CUT OFF at scene end rather than
531
+ * awaited.
532
+ *
533
+ * @remarks
534
+ * For ambient motion that should play for as long as the scene lasts
535
+ * without deciding how long that is — a pulsing indicator, a drifting
536
+ * backdrop, anything paired with `Schedule.forever`. A background never
537
+ * holds the scene open, so the scene's real content governs its length and
538
+ * the ambient loop simply stops when everything else is done.
539
+ *
540
+ * "Scene end" includes the fork drain: backgrounds keep animating while
541
+ * awaited forks finish, and are stopped only after the last one.
542
+ *
543
+ * Because backgrounds do not keep a scene alive, a body that spawns only
544
+ * backgrounds ends immediately and produces NO frames — the background is
545
+ * not content, so there is nothing to give the scene a length. Pair one
546
+ * with something that does define the length, whether a real animation or
547
+ * an explicit {@link sleep}, or the ambient motion never gets a frame to
548
+ * play on.
549
+ *
550
+ * @param effect - The ambient animation.
551
+ * @returns A handle with `finished` and `fiber`.
552
+ *
553
+ * @example
554
+ * A pulse that runs the whole scene, with the scene's length set by the
555
+ * animation after it.
556
+ * ```typescript
557
+ * yield* Scene.background(
558
+ * Scene.repeat(
559
+ * pulse.pipe(
560
+ * Motion.tweenTo({ radius: 24 }, "400 millis"),
561
+ * Motion.tweenTo({ radius: 10 }, "400 millis"),
562
+ * ),
563
+ * Schedule.forever,
564
+ * ),
565
+ * );
566
+ * yield* title.pipe(Motion.moveTo({ y: 100 }, "2 seconds"));
567
+ * ```
225
568
  */
226
569
  export declare const background: <A, E = never, R = never>(effect: Effect.Effect<A, E, R>) => Effect.Effect<BranchHandle<A, E>, never, Runner.Runner | Exclude<Exclude<R, never>, Phaser.Phaser>>;
227
570
  export interface PlayOptions {
@@ -231,60 +574,145 @@ export interface PlayOptions {
231
574
  readonly seed?: Runner.Seed;
232
575
  }
233
576
  /**
234
- * A played scene's branch handle plus its mount group — the child comp as
235
- * one unit. Move/fade the group (trait lenses) or scale it (transform
236
- * operations) to transform the whole nested scene, bounds included.
577
+ * A played scene's handle: its branch, plus the group it is mounted under.
578
+ *
579
+ * @remarks
580
+ * `group` is what makes a nested scene manipulable as ONE object — move,
581
+ * fade, or scale it and the entire child scene follows, its bounds included.
237
582
  */
238
583
  export interface PlayHandle<A = void, E = never> extends BranchHandle<A, E> {
239
584
  readonly group: Runner.GroupInstance;
240
585
  }
241
586
  /**
242
- * Play a scene as a branch of the current scene — the explicit door to
243
- * nesting, After Effects–precomp-style. The child shares the movie's
244
- * runner, phaser, frame rate, and frame cap, and gets its own scope,
245
- * branch handle, and a FRESH seeded Random stream: `play(scene)` inside a
246
- * movie seeded `S` animates exactly like `run(scene, { seed: S })`
247
- * standalone. Each evaluation mounts the child under an implicit group
248
- * carrying the child scene's bounds (width/height/backgroundColor):
249
- * content clips to them, a non-transparent background paints within them,
250
- * and the group is placed so the child's bounds CENTER in the enclosing
251
- * comp (the movie, or the enclosing played scene) — a child smaller or
252
- * bigger than the movie renders centered. Awaited like a fork —
253
- * `yield* handle.finished` for sequential nesting, or don't await for
254
- * concurrent scenes.
587
+ * Nest a whole scene inside the current one — the precomp.
588
+ *
589
+ * @remarks
590
+ * The door to composing scenes rather than writing one flat timeline. A
591
+ * played scene is authored and tested independently, then dropped into a
592
+ * larger one as a unit: an intro built alone becomes the first beat of a
593
+ * longer piece without edits.
594
+ *
595
+ * The child mounts under an implicit group carrying its OWN bounds. Content
596
+ * clips to them, a non-transparent background paints within them, and the
597
+ * group is placed so those bounds sit centered in the enclosing composition
598
+ * — so a child smaller or larger than its parent still lands sensibly. The
599
+ * handle's `group` is that mount point: move or fade it to transform the
600
+ * entire nested scene as one object.
601
+ *
602
+ * The child shares the movie's frame clock but gets a FRESH seeded random
603
+ * stream, so a nested scene animates exactly as it did standalone under the
604
+ * same seed — nesting never perturbs a child's randomness.
605
+ *
606
+ * Awaited like a {@link fork}: yield `handle.finished` to play children in
607
+ * sequence, or skip the await to run them concurrently.
608
+ *
609
+ * @param scene - The scene to nest.
610
+ * @param options - `parent` to mount elsewhere, `seed` to vary this
611
+ * evaluation.
612
+ * @returns A handle with `finished`, `fiber`, and the mount `group`.
613
+ *
614
+ * @example
615
+ * Play one scene, then another, and fade the second out as a whole.
616
+ * ```typescript
617
+ * const intro = yield* Scene.play(introScene);
618
+ * yield* intro.finished;
619
+ *
620
+ * const outro = yield* Scene.play(outroScene);
621
+ * yield* outro.group.pipe(Motion.fadeTo(0, "500 millis"));
622
+ * ```
255
623
  */
256
624
  export declare const play: <E, R>(scene: Scene<E, R>, options?: PlayOptions) => Effect.Effect<PlayHandle<void, E>, never, Runner.Runner | Exclude<R, Scope.Scope>>;
257
625
  /**
258
- * Run effects in lockstep parallel, sharing frame phases — the public
259
- * counterpart to the low-level `Phaser.all`. Takes no schedule: pacing a
260
- * list sequentially belongs to {@link chain}, overlapping staggered
261
- * starts to {@link stagger}.
626
+ * Run animations simultaneously, and resolve when the last one finishes.
627
+ *
628
+ * @remarks
629
+ * The everyday way to make things move at once. Every branch advances
630
+ * exactly one frame per tick in lockstep, so two one-second animations run
631
+ * as one second of frames — not two.
632
+ *
633
+ * Branches need not be the same length; `all` waits for the slowest. This
634
+ * is also the idiom for synchronizing springs, whose durations are emergent
635
+ * and unknown up front.
636
+ *
637
+ * There is deliberately no schedule parameter: pacing a list one-at-a-time
638
+ * is {@link chain}, and overlapping starts is {@link stagger}.
639
+ *
640
+ * @param effects - The animations to run together.
641
+ *
642
+ * @example
643
+ * A dot slides while the camera pushes in — one second of frames total.
644
+ * ```typescript
645
+ * yield* Scene.all([
646
+ * dot.pipe(Motion.moveTo({ x: 400 }, "1 second")),
647
+ * camera.pipe(Motion.moveTo({ z: -300 }, "1 second")),
648
+ * ]);
649
+ * ```
262
650
  */
263
651
  export declare const all: <Eff extends Effect.Effect<any, any, any>>(effects: Iterable<Eff>) => Effect.Effect<void, Eff extends Effect.Effect<any, infer E, any> ? E : never, Runner.Runner | Exclude<Eff extends Effect.Effect<any, any, infer R> ? R : never, Phaser.Phaser>>;
264
652
  /**
265
- * Run items one at a time, in order — items NEVER overlap, mirroring
266
- * Effect's guarantee for scheduled effects. The first item runs
267
- * immediately; after each item completes, `schedule` is stepped once
268
- * (with the item's result as input) to pace the next start. `fixed`
269
- * gives a start cadence with catch-up, `spaced` gives rests between
270
- * items. When the schedule ends early, the remaining items are skipped —
271
- * it is the release policy, including how many. Without a schedule,
272
- * plain sequential composition. Resolves with how many items completed.
273
- * For overlapping runs, reach for {@link stagger} or {@link fork}
274
- * explicitly.
653
+ * Run animations one at a time, in order, optionally resting between them.
654
+ *
655
+ * @remarks
656
+ * Items NEVER overlap — each begins only after the previous one has fully
657
+ * finished. That guarantee is the difference between this and
658
+ * {@link stagger}, and it holds no matter what schedule you pass.
659
+ *
660
+ * Without a schedule this is plain sequencing, equivalent to yielding each
661
+ * item in turn but composable as a list. With one, the schedule paces the
662
+ * GAPS after each item: `Schedule.spaced("400 millis")` rests 400ms between
663
+ * items, while `Schedule.fixed` targets a steady start-to-start cadence.
664
+ *
665
+ * The schedule also decides how many items run: when it ends, the remaining
666
+ * items are skipped. `Schedule.recurs(2)` therefore plays three items — the
667
+ * first, plus two more the schedule released.
668
+ *
669
+ * @param effects - The animations, in order.
670
+ * @param schedule - Optional pacing for the gaps between them.
671
+ * @returns `{ completed }` — how many items actually ran.
672
+ *
673
+ * @example
674
+ * Three shapes flashing in turn, resting 400ms between each.
675
+ * ```typescript
676
+ * const { completed } = yield* Scene.chain(
677
+ * [a, b, c].map((shape) => shape.pipe(Motion.fadeTo(1, "300 millis"))),
678
+ * Schedule.spaced("400 millis"),
679
+ * );
680
+ * ```
275
681
  */
276
682
  export declare const chain: <Eff extends Effect.Effect<any, any, any>, ScheduleE = never, ScheduleR = never>(effects: Iterable<Eff>, schedule?: Schedule.Schedule<unknown, Eff extends Effect.Effect<infer A, any, any> ? A : never, ScheduleE, ScheduleR>) => Effect.Effect<{
277
683
  completed: number;
278
684
  }, (Eff extends Effect.Effect<any, infer E, any> ? E : never) | ScheduleE, (Eff extends Effect.Effect<any, any, infer R> ? R : never) | Runner.Runner | ScheduleR>;
279
685
  /**
280
- * Release effects on `schedule` with OVERLAP: the first starts
281
- * immediately, each next one on the schedule's next emission, and
282
- * released effects run concurrently — semantically
283
- * `chain(effects.map(Scene.fork))`, but resolving when all released
284
- * effects finish rather than at the last release. When the schedule ends
285
- * early, the remaining effects are skipped. Overlap is this
286
- * combinator's purpose; the schedule-paced default ({@link chain})
287
- * never overlaps.
686
+ * Start animations one after another WITHOUT waiting for each to finish —
687
+ * the cascade.
688
+ *
689
+ * @remarks
690
+ * The first starts immediately and each next one on the schedule's next
691
+ * emission, so earlier items are still running when later ones begin. That
692
+ * overlap is the entire point, and the difference from {@link chain}: use
693
+ * `stagger` for a ripple across many elements, `chain` when items must not
694
+ * coincide.
695
+ *
696
+ * The schedule paces the STARTS here, not the gaps. Resolution waits for
697
+ * every released animation to finish, not merely for the last one to be
698
+ * released — so the whole cascade is complete when this returns.
699
+ *
700
+ * When the schedule ends before the list does, the remaining effects are
701
+ * skipped.
702
+ *
703
+ * @param effects - The animations to release in order.
704
+ * @param schedule - When to release each subsequent one.
705
+ * @returns `{ released }` — how many actually started.
706
+ *
707
+ * @example
708
+ * A row of bars rising in a ripple, each starting 80ms after the last while
709
+ * the earlier ones keep going.
710
+ * ```typescript
711
+ * yield* Scene.stagger(
712
+ * bars.map((bar) => bar.pipe(Motion.moveTo({ y: 40 }, "600 millis"))),
713
+ * Schedule.spaced("80 millis"),
714
+ * );
715
+ * ```
288
716
  */
289
717
  export declare const stagger: <Eff extends Effect.Effect<any, any, any>, ScheduleE = never, ScheduleR = never>(effects: Iterable<Eff>, schedule: Schedule.Schedule<unknown, void, ScheduleE, ScheduleR>) => Effect.Effect<{
290
718
  released: number;