effect-motion 0.3.2 → 0.5.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 +2 -2
  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 +682 -38
  9. package/dist/Entity.js +271 -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 +37 -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 +653 -330
  25. package/dist/Runner.js +208 -158
  26. package/dist/Scene.d.ts +621 -171
  27. package/dist/Scene.js +620 -89
  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 +8 -6
  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 +13 -5
  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 +2 -4
  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 -381
  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 -65
  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 -116
  73. package/dist/shapes/Group.js +0 -82
  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,63 +1,246 @@
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>;
20
- /** tooling-facing metadata; never read by the runtime */
21
- readonly annotations: Context.Context<never>;
22
- annotate<I, S>(key: Context.Key<I, S>, value: S): Scene<E, R>;
23
- annotateMerge(context: Context.Context<never>): Scene<E, R>;
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>;
37
+ /**
38
+ * composition config, After Effects–style: what this comp IS. The
39
+ * runner inherits the ROOT scene's; a played scene keeps its own as
40
+ * its bounds (see {@link play}).
41
+ */
42
+ readonly width: number;
43
+ readonly height: number;
44
+ readonly backgroundColor: Color.Color;
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>;
24
55
  }
25
- export type AnyScene = Scene<never, never>;
26
- export declare const make: <const Eff extends Effect.Effect<any, any, any>, const AEff>(f: () => Generator<Eff, AEff, never>) => 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>;
27
- 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>;
28
152
  export declare const tick: Effect.Effect<void, never, Runner.Runner>;
29
153
  /**
30
- * Hold the scene for `duration` of scene time (frames at the runner's
31
- * frame rate) — `Effect.sleep`'s sibling, but in frames, not wall time.
32
- * 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
+ * ```
33
173
  */
34
174
  export declare const sleep: (duration: Duration.Input) => Effect.Effect<void, never, Runner.Runner>;
35
- export interface FrameEntry<Entity extends Entity.AnyEntity> {
36
- data: Entity["data"]["Type"];
37
- 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> {
38
202
  /**
39
- * builtin visibility, held beside the data; renderers skip `false`.
40
- * Optional in the frame type (a hand-built frame or external producer
41
- * 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.
42
206
  */
43
- $visible?: boolean;
44
- }
45
- export type EntriesFromEntities<Entities> = Entities extends Entity.AnyEntity ? {
46
- [K in Entities as K["name"]]: FrameEntry<K>;
47
- }[Entities["name"]] : never;
48
- export interface Frame<Entities extends Entity.AnyEntity = Entity.AnyEntity> {
49
- instances: Record<string, EntriesFromEntities<Entities>>;
207
+ readonly "~resources"?: Resources;
208
+ instances: Record<string, FrameEntry>;
50
209
  /** id of the root group (conventionally "root"); never rendered itself */
51
210
  root: string;
52
- /** render metadata from the runner settings — a frame is self-describing */
211
+ /** render metadata — frameRate from the runner settings, resolution and
212
+ * background from the ROOT scene's comp config; a frame is self-describing */
53
213
  frameRate: number;
54
214
  width: number;
55
215
  height: number;
56
216
  backgroundColor: Color.Color;
57
- /** the active camera's view; Camera.IDENTITY when unused */
58
- 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>;
59
226
  }
60
- 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>;
61
244
  export interface RunningScene<E, R> {
62
245
  readonly runner: Runner.Runner["Service"];
63
246
  readonly scene: Scene<E, R>;
@@ -66,11 +249,120 @@ export interface RunningScene<E, R> {
66
249
  /** frames delivered so far — mutated by `step` for the maxFrames cap */
67
250
  framesDelivered: number;
68
251
  }
69
- 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>>;
70
- 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>>;
71
310
  type Updater<Data> = Data | ((data: Data) => Data);
72
- export declare const data: <Name extends string, Data extends Schema.Top>(instance: Instance.Instance<Name, Data>) => Effect.Effect<Data["Type"] & ({} | undefined), never, Runner.Runner>;
73
- 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>;
74
366
  /**
75
367
  * Move `child` under `parent`, detaching it from its current parent first
76
368
  * (so it is never double-referenced). Instances are born mounted under the
@@ -82,87 +374,40 @@ export declare const appendChild: (parent: Runner.GroupInstance, child: Instance
82
374
  export declare const removeChild: (parent: Runner.GroupInstance, child: Instance.Instance) => Effect.Effect<void, never, Runner.Runner>;
83
375
  export declare const settings: () => Effect.Effect<{
84
376
  frameRate: number;
85
- width: number;
86
- height: number;
87
- backgroundColor: Color.Color;
88
377
  seed: Runner.Seed;
89
378
  maxFrames: number;
90
379
  }, never, Runner.Runner>;
380
+ /** the movie's composition config — the ROOT scene's width/height/background */
381
+ export declare const comp: () => Effect.Effect<Runner.CompConfig, never, Runner.Runner>;
91
382
  /**
92
- * The active camera instance — an ordinary instance carrying `~position`
93
- * (world x/y/z), Euler orientation (`rotX`/`rotY`/`rotZ`), and
94
- * `focalLength` (perspective strength — see Projection.defaultFocalLength),
95
- * so the existing animators drive it: `Scene.make(function* () { const cam
96
- * = yield* Scene.camera; yield* cam.pipe(Motion.moveTo({ z: -400 })) })`.
97
- * A default resting camera is always present (width-relative 50mm-equivalent
98
- * focal length, projecting z=0 content to plain-2D placement); animate it
99
- * directly, or `Scene.setCamera` to swap in another instance. The camera is
100
- * 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
+ * ```
101
407
  */
102
- export declare const camera: Effect.Effect<Instance.Instance<"Camera", Schema.Struct<{
103
- x: Schema.withConstructorDefault<Schema.Number>;
104
- y: Schema.withConstructorDefault<Schema.Number>;
105
- z: Schema.withConstructorDefault<Schema.Number>;
106
- rotX: Schema.withConstructorDefault<Schema.Number>;
107
- rotY: Schema.withConstructorDefault<Schema.Number>;
108
- rotZ: Schema.withConstructorDefault<Schema.Number>;
109
- focalLength: Schema.withConstructorDefault<Schema.Number>;
110
- focusDistance: Schema.withConstructorDefault<Schema.Number>;
111
- aperture: Schema.withConstructorDefault<Schema.Number>;
112
- poiX: Schema.optionalKey<Schema.Number>;
113
- poiY: Schema.optionalKey<Schema.Number>;
114
- poiZ: Schema.optionalKey<Schema.Number>;
115
- }>, {
116
- readonly "~position": {
117
- get: (data: {
118
- readonly x: number;
119
- readonly y: number;
120
- readonly z: number;
121
- readonly rotX: number;
122
- readonly rotY: number;
123
- readonly rotZ: number;
124
- readonly focalLength: number;
125
- readonly focusDistance: number;
126
- readonly aperture: number;
127
- readonly poiX?: number;
128
- readonly poiY?: number;
129
- readonly poiZ?: number;
130
- }) => {
131
- x: number;
132
- y: number;
133
- z: number;
134
- };
135
- set: (data: {
136
- readonly x: number;
137
- readonly y: number;
138
- readonly z: number;
139
- readonly rotX: number;
140
- readonly rotY: number;
141
- readonly rotZ: number;
142
- readonly focalLength: number;
143
- readonly focusDistance: number;
144
- readonly aperture: number;
145
- readonly poiX?: number;
146
- readonly poiY?: number;
147
- readonly poiZ?: number;
148
- }, value: Entity.Position) => {
149
- readonly x: number;
150
- readonly y: number;
151
- readonly z: number;
152
- readonly rotX: number;
153
- readonly rotY: number;
154
- readonly rotZ: number;
155
- readonly focalLength: number;
156
- readonly focusDistance: number;
157
- readonly aperture: number;
158
- readonly poiX?: number;
159
- readonly poiY?: number;
160
- readonly poiZ?: number;
161
- };
162
- };
163
- }>, never, Runner.Runner>;
408
+ export declare const camera: Effect.Effect<Instance.Instance<"Camera">, never, Runner.Runner>;
164
409
  /** Swap the active camera to `instance`; its live data becomes the view. */
165
- 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>;
166
411
  /**
167
412
  * Handle to a branch of animation (a fork, background, or played scene).
168
413
  * `finished` resolves at the branch's SEMANTIC end — `Scene.finish` or
@@ -176,93 +421,298 @@ export interface BranchHandle<A = unknown, E = never> {
176
421
  readonly fiber: Fiber.Fiber<A, E>;
177
422
  }
178
423
  /**
179
- * Finish the innermost enclosing branch (the current fork, played scene,
180
- * or the scene body itself): whoever awaits the branch's `finished`
181
- * proceeds, the branch stops blocking its parent's end, and the code
182
- * after `finish` keeps running as a TAIL — bounded by the parent, which
183
- * interrupts it at scene end like a background. Idempotent; completion
184
- * implies finish. NOTE: a failure in the tail (after finish) is NOT
185
- * 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
+ * ```
186
454
  */
187
455
  export declare const finish: Effect.Effect<undefined, never, never>;
188
456
  /**
189
- * Run `effect`, then repeat it as long as `schedule` recurs, with the
190
- * schedule evaluated in scene time (frames at the runner's frame rate) —
191
- * `Effect.repeat`'s sibling, but paced by frames instead of the wall
192
- * clock. The first run is immediate; the schedule paces the gaps after
193
- * runs; each run's result is fed to the schedule as input. Resolves with
194
- * the schedule's final output once it is done; a failed run fails
195
- * 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
+ * ```
196
486
  */
197
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>;
198
488
  /**
199
- * Run `effect` concurrently with the rest of the scene, sharing frame
200
- * phases, and return its fiber immediately.
201
- *
202
- * NOTE: this inverts Effect's own `fork` semantics — the scene's end
203
- * WAITS for forked work. A scene whose body returns while forks are
204
- * still animating keeps producing frames until the last fork finishes
205
- * (so a scene containing only a fork still plays). Use
206
- * {@link background} for work that should be cut off at scene end
207
- * instead. Forks are supervised by the fiber that spawned them: a fork
208
- * 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
+ * ```
209
527
  */
210
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>>;
211
529
  /**
212
- * Like {@link fork}, but the fiber is INTERRUPTED at scene end instead
213
- * of awaited — for indefinite work (`Scene.repeat(…, Schedule.forever)`)
214
- * that should play for the duration of the scene without keeping it
215
- * alive. "Scene end" includes the fork drain: backgrounds keep animating
216
- * 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
+ * ```
217
568
  */
218
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>>;
219
570
  export interface PlayOptions {
220
- /** group to mount the scene's instances under (default: the root) */
571
+ /** group to mount the child's bounds group under (default: the ambient parent) */
221
572
  readonly parent?: Runner.GroupInstance;
222
573
  /** seed for this evaluation (default: the movie's seed) */
223
574
  readonly seed?: Runner.Seed;
224
575
  }
225
576
  /**
226
- * Play a scene as a branch of the current scene — the explicit door to
227
- * nesting. The child shares the movie's runner, phaser, frame rate, and
228
- * frame cap, and gets its own scope, branch handle, mount parent, and a
229
- * FRESH seeded Random stream: `play(scene)` inside a movie seeded `S`
230
- * animates exactly like `run(scene, { seed: S })` standalone. Awaited
231
- * like a fork — `yield* handle.finished` for sequential nesting, or
232
- * don't await for concurrent scenes.
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.
582
+ */
583
+ export interface PlayHandle<A = void, E = never> extends BranchHandle<A, E> {
584
+ readonly group: Runner.GroupInstance;
585
+ }
586
+ /**
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
+ * ```
233
623
  */
234
- export declare const play: <E, R>(scene: Scene<E, R>, options?: PlayOptions) => Effect.Effect<BranchHandle<void, E>, never, Runner.Runner | Exclude<R, Scope.Scope>>;
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>>;
235
625
  /**
236
- * Run effects in lockstep parallel, sharing frame phases — the public
237
- * counterpart to the low-level `Phaser.all`. Takes no schedule: pacing a
238
- * list sequentially belongs to {@link chain}, overlapping staggered
239
- * 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
+ * ```
240
650
  */
241
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>>;
242
652
  /**
243
- * Run items one at a time, in order — items NEVER overlap, mirroring
244
- * Effect's guarantee for scheduled effects. The first item runs
245
- * immediately; after each item completes, `schedule` is stepped once
246
- * (with the item's result as input) to pace the next start. `fixed`
247
- * gives a start cadence with catch-up, `spaced` gives rests between
248
- * items. When the schedule ends early, the remaining items are skipped —
249
- * it is the release policy, including how many. Without a schedule,
250
- * plain sequential composition. Resolves with how many items completed.
251
- * For overlapping runs, reach for {@link stagger} or {@link fork}
252
- * 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
+ * ```
253
681
  */
254
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<{
255
683
  completed: number;
256
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>;
257
685
  /**
258
- * Release effects on `schedule` with OVERLAP: the first starts
259
- * immediately, each next one on the schedule's next emission, and
260
- * released effects run concurrently — semantically
261
- * `chain(effects.map(Scene.fork))`, but resolving when all released
262
- * effects finish rather than at the last release. When the schedule ends
263
- * early, the remaining effects are skipped. Overlap is this
264
- * combinator's purpose; the schedule-paced default ({@link chain})
265
- * 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
+ * ```
266
716
  */
267
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<{
268
718
  released: number;