effect-motion 0.4.0 → 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 +639 -350
  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 +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 +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 +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 -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
@@ -1,16 +1,78 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Pipeable from "effect/Pipeable";
3
- import type * as Schema from "effect/Schema";
4
- import type * as Entity from "./Entity.js";
5
- export declare const TypeId: "~motion/Instance";
6
- export interface Instance<Name extends string = string, Data extends Schema.Top = Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> = {}> extends Pipeable.Pipeable {
7
- readonly [TypeId]: typeof TypeId;
3
+ import type { EntityByTag, EntityTag } from "./Entity.js";
4
+ /**
5
+ * An Instance is a REFERENCE to a live entity in the runner tree: an id and
6
+ * the tag of what it is. It carries no entity data — the runner owns that —
7
+ * so it stays a tiny, pipeable handle that animator chains flow through.
8
+ */
9
+ /**
10
+ * A handle to something living in a scene — what `Scene.instantiate` returns
11
+ * and what every animator takes.
12
+ *
13
+ * @remarks
14
+ * An instance holds no entity data of its own, only an id and a kind. The
15
+ * data lives in the scene and changes every frame; the handle stays valid
16
+ * throughout, which is why it can be captured once and animated repeatedly.
17
+ * To read the current values, use `Scene.data`.
18
+ *
19
+ * The `Tag` parameter is what makes animators statically safe: `fadeTo`
20
+ * accepts only instances whose entity actually has an `opacity`, so fading a
21
+ * Camera fails at compile time with a message naming the missing field
22
+ * rather than misbehaving at runtime.
23
+ *
24
+ * Instances are Pipeable — that is what lets `circle.pipe(moveTo(…),
25
+ * fadeTo(…))` read as one chain. A handle is NOT itself an Effect, so
26
+ * anything accepting one in Effect position lifts it with
27
+ * {@link flattenInstance} first.
28
+ *
29
+ * @typeParam Tag - Which entity this refers to, e.g. `"Circle"`.
30
+ */
31
+ export interface Instance<Tag extends EntityTag = EntityTag> extends Pipeable.Pipeable {
32
+ readonly _tag: "Instance";
8
33
  readonly id: string;
9
- readonly entity: Entity.Entity<Name, Data, Traits>;
34
+ readonly kind: Tag;
10
35
  }
11
- /** the Instance type of a given entity, traits included */
12
- export type Of<E extends Entity.AnyEntity> = E extends Entity.Entity<infer Name, infer Data, infer Traits> ? Instance<Name, Data, Traits> : never;
36
+ /**
37
+ * Build an instance handle from an id and a kind.
38
+ *
39
+ * @remarks
40
+ * Internal plumbing — the scene runner calls this when creating entities.
41
+ * Author code gets handles from `Scene.instantiate` instead; constructing
42
+ * one by hand refers to an entity that may not exist.
43
+ */
44
+ export declare const makeInstance: <Tag extends EntityTag>(id: string, kind: Tag) => Instance<Tag>;
45
+ /**
46
+ * Whether `u` is an {@link Instance}.
47
+ *
48
+ * @remarks
49
+ * The dispatch behind every dual animator: `moveTo(circle, …)` and
50
+ * `circle.pipe(moveTo(…))` are told apart by testing the first argument
51
+ * with this, rather than by counting arguments — trailing optional
52
+ * parameters make arity ambiguous.
53
+ */
13
54
  export declare const isInstance: (u: unknown) => u is Instance;
14
- export declare const make: <Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>>(entity: Entity.Entity<Name, Data, Traits>, id: string) => Instance<Name, Data, Traits>;
15
- export type InstanceOrEffect<Name extends string = string, Data extends Schema.Top = Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> = {}, E = never, R = never> = Instance<Name, Data, Traits> | Effect.Effect<Instance<Name, Data, Traits>, E, R>;
16
- export declare const flatten: <Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>, E = never, R = never>(instance: InstanceOrEffect<Name, Data, Traits, E, R>) => Effect.Effect<Instance<Name, Data, Traits>, E, R>;
55
+ /**
56
+ * Whether `u` is an instance of one specific entity kind.
57
+ *
58
+ * @remarks
59
+ * Narrows to `Instance<Tag>`, so a heterogeneous list can be filtered down
60
+ * to the circles and then animated with circle-only fields.
61
+ *
62
+ * @param tag - The kind to test for, e.g. `"Circle"`.
63
+ * @param u - The value to test.
64
+ */
65
+ export declare const isInstanceOf: <Tag extends EntityTag>(tag: Tag, u: unknown) => u is Instance<Tag>;
66
+ /**
67
+ * An instance, or an un-yielded `instantiate` effect that produces one.
68
+ *
69
+ * @remarks
70
+ * Animators accept both, so a scene can animate straight off an
71
+ * `instantiate` call without binding it to a variable first — the create
72
+ * step and the first animation read as one expression.
73
+ */
74
+ export type InstanceOrEffect<Tag extends EntityTag = EntityTag, E = never, R = never> = Instance<Tag> | Effect.Effect<Instance<Tag>, E, R>;
75
+ /** Resolve an {@link InstanceOrEffect} to the instance itself. */
76
+ export declare const flattenInstance: <Tag extends EntityTag, E = never, R = never>(instance: InstanceOrEffect<Tag, E, R>) => Effect.Effect<Instance<Tag>, E, R>;
77
+ /** The entity data type a given instance handle refers to. */
78
+ export type DataOf<I> = I extends Instance<infer Tag> ? EntityByTag<Tag> : never;
package/dist/Instance.js CHANGED
@@ -1,18 +1,51 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Pipeable from "effect/Pipeable";
3
- export const TypeId = "~motion/Instance";
4
- export const isInstance = (u) => typeof u === "object" && u !== null && TypeId in u;
5
- const Proto = {
6
- [TypeId]: TypeId,
3
+ /**
4
+ * Instances are Pipeable so animator chains read data-last:
5
+ * `circle.pipe(moveTo(...), fadeTo(...))`. That is the authored form in most
6
+ * scenes, so the protocol is load-bearing, not a convenience.
7
+ */
8
+ const InstanceProto = {
9
+ _tag: "Instance",
7
10
  pipe() {
8
11
  // biome-ignore lint: lint/style/noArguments: Pipeable's variadic protocol
9
12
  return Pipeable.pipeArguments(this, arguments);
10
13
  },
11
14
  };
12
- export const make = (entity, id) => Object.assign(Object.create(Proto), { id, entity });
13
- export const flatten = (instance) => {
14
- if (isInstance(instance)) {
15
- return Effect.succeed(instance);
16
- }
17
- return instance;
18
- };
15
+ /**
16
+ * Build an instance handle from an id and a kind.
17
+ *
18
+ * @remarks
19
+ * Internal plumbing — the scene runner calls this when creating entities.
20
+ * Author code gets handles from `Scene.instantiate` instead; constructing
21
+ * one by hand refers to an entity that may not exist.
22
+ */
23
+ export const makeInstance = (id, kind) => Object.assign(Object.create(InstanceProto), { id, kind });
24
+ /**
25
+ * Whether `u` is an {@link Instance}.
26
+ *
27
+ * @remarks
28
+ * The dispatch behind every dual animator: `moveTo(circle, …)` and
29
+ * `circle.pipe(moveTo(…))` are told apart by testing the first argument
30
+ * with this, rather than by counting arguments — trailing optional
31
+ * parameters make arity ambiguous.
32
+ */
33
+ export const isInstance = (u) => typeof u === "object" &&
34
+ u !== null &&
35
+ "_tag" in u &&
36
+ u._tag === "Instance";
37
+ /**
38
+ * Whether `u` is an instance of one specific entity kind.
39
+ *
40
+ * @remarks
41
+ * Narrows to `Instance<Tag>`, so a heterogeneous list can be filtered down
42
+ * to the circles and then animated with circle-only fields.
43
+ *
44
+ * @param tag - The kind to test for, e.g. `"Circle"`.
45
+ * @param u - The value to test.
46
+ */
47
+ export const isInstanceOf = (tag, u) => isInstance(u) && u.kind === tag;
48
+ /** Resolve an {@link InstanceOrEffect} to the instance itself. */
49
+ export const flattenInstance = (instance) => isInstance(instance)
50
+ ? Effect.succeed(instance)
51
+ : instance;
package/dist/Motion.d.ts CHANGED
@@ -1,6 +1,49 @@
1
+ /**
2
+ * Duration-based animation: the animators that interpolate a value over a
3
+ * span of scene time, paced by an easing curve.
4
+ *
5
+ * The module is two layers over one engine:
6
+ *
7
+ * - **raw** — {@link tween} / {@link tweenTo} interpolate numeric fields BY
8
+ * NAME (`radius`, `width`, `fontSize`), whatever the entity's schema
9
+ * declares.
10
+ * - **semantic** — {@link move} / {@link moveTo} and {@link fade} /
11
+ * {@link fadeTo} speak in concepts rather than field names, and are what
12
+ * you should reach for when one exists: geometry is relative to
13
+ * `position`, so moving a Line translates both endpoints and moving a
14
+ * Group carries its whole subtree, with no per-entity handling.
15
+ *
16
+ * Every animator ships as a base/To pair — `verbTo` starts from the
17
+ * instance's CURRENT value, `verb` takes an explicit origin — and each is a
18
+ * dual, callable data-first (`moveTo(circle, …)`) or pipeable
19
+ * (`circle.pipe(moveTo(…))`). They resolve with the instance, so chains
20
+ * compose. For durationless motion whose length emerges from a simulation,
21
+ * see `Physics`.
22
+ *
23
+ * Timing is frame-exact: a `duration` is converted to whole frames at the
24
+ * runner's frame rate, and the last frame receives exactly the target value
25
+ * for any easing with f(1) = 1 — a tween never lands "almost" on its
26
+ * target. A zero-length duration still consumes one frame.
27
+ *
28
+ * @example
29
+ * Chain a raw tween into two semantic animators. `1 second` at 30fps is 30
30
+ * animation frames, and `radius`/`position`/`opacity` all land exactly.
31
+ * ```typescript
32
+ * import * as Motion from "effect-motion/Motion";
33
+ * import * as Scene from "effect-motion/Scene";
34
+ *
35
+ * const scene = Scene.make(function* () {
36
+ * const dot = yield* Scene.instantiate("Circle", { radius: 4 });
37
+ * yield* dot.pipe(
38
+ * Motion.tweenTo({ radius: 40 }, "1 second", "easeOutCubic"),
39
+ * Motion.moveTo({ x: 300 }, "1 second"),
40
+ * Motion.fadeTo(0, "500 millis"),
41
+ * );
42
+ * });
43
+ * ```
44
+ */
1
45
  import type * as Duration from "effect/Duration";
2
46
  import * as Effect from "effect/Effect";
3
- import type * as Schema from "effect/Schema";
4
47
  import * as Color from "./Color.js";
5
48
  import * as Entity from "./Entity.js";
6
49
  import * as Instance from "./Instance.js";
@@ -16,76 +59,308 @@ export type InterpolableOnly<T> = Pick<T, InterpolableKeys<T>>;
16
59
  type InterpolableOrInterpolator<T> = {
17
60
  [K in keyof T]-?: NonNullable<T[K]> extends number ? number : NonNullable<T[K]> extends Color.Color ? Color.Color | ColorInterpolator : never;
18
61
  };
19
- /** target props, or an updater computing them from the current data */
20
- export type Target<Data extends Schema.Top> = Partial<InterpolableOrInterpolator<Data["Type"]>> | ((data: Data["Type"]) => Partial<InterpolableOrInterpolator<Data["Type"]>>);
21
- export declare const resolveTarget: <Data extends Schema.Top>(target: Target<Data>, current: Data["Type"]) => Record<string, number>;
62
+ /**
63
+ * What {@link tween} / {@link tweenTo} accept as an endpoint: the fields to
64
+ * animate and their values.
65
+ *
66
+ * @remarks
67
+ * Two forms. A plain object states targets literally. A function receives
68
+ * the entity's data at animation start and computes them, which is how you
69
+ * express a target relative to where the entity already is (`data.radius *
70
+ * 2`) without reading it out in a separate step.
71
+ *
72
+ * Only numeric and Color fields of the entity are addressable — those are
73
+ * the ones that can be interpolated.
74
+ *
75
+ * @typeParam Tag - The entity tag, which determines the available fields.
76
+ */
77
+ export type Target<Tag extends Entity.EntityTag> = Partial<InterpolableOrInterpolator<Entity.EntityByTag<Tag>>> | ((data: Entity.EntityByTag<Tag>) => Partial<InterpolableOrInterpolator<Entity.EntityByTag<Tag>>>);
78
+ export declare const resolveTarget: <Tag extends Entity.EntityTag>(target: Target<Tag>, current: Entity.EntityByTag<Tag>) => Record<string, number>;
22
79
  export declare const startValues: (current: unknown, target: Record<string, number>, explicitFrom: Record<string, number>) => Record<string, number>;
23
80
  /**
24
- * Animate interpolable (numeric) props of an instance toward `to` over
25
- * `duration` by raw field name, starting from the instance's current
26
- * data, optionally paced by a timing function (name or function, default
27
- * linear). Dual: `tweenTo(instance, to, duration, timing?)` or
28
- * `instance.pipe(tweenTo(to, duration, timing?))`. Resolves with the
29
- * instance, so animations chain.
81
+ * Interpolate numeric fields of an instance to `to` over `duration`,
82
+ * starting from whatever they currently are.
83
+ *
84
+ * @remarks
85
+ * This is the RAW layer: fields are addressed by their schema name, so
86
+ * `tweenTo` reaches everything the semantic animators don't cover —
87
+ * `radius`, `width`, `height`, `fontSize`, `strokeWidth`, a Camera's
88
+ * `focalLength`. Prefer {@link moveTo} / {@link fadeTo} where they apply;
89
+ * they move an entity as a unit, which a per-field tween cannot.
90
+ *
91
+ * Only the fields named in `to` are touched; the rest are left alone.
92
+ * `to` may also be a function of the current data, for a target computed
93
+ * at animation start. Colors are interpolable too, mixed through Lab by
94
+ * default.
95
+ *
96
+ * The final frame receives exactly `to` (for any easing with f(1) = 1), so
97
+ * a chain of tweens never accumulates drift. A field that has no current
98
+ * value — an optional one never set — is a loud defect rather than a
99
+ * silent NaN; pass an explicit origin with {@link tween} instead.
100
+ *
101
+ * @param to - The fields to animate and their target values.
102
+ * @param duration - How long, in scene time; rounded to whole frames at
103
+ * the runner's frame rate. A zero duration still takes one frame.
104
+ * @param timing - An easing name from `Timing.timingFunctions`, or your own
105
+ * `(t: number) => number`.
106
+ * @defaultValue `timing` — `"linear"`
107
+ * @returns The instance, so animators chain.
108
+ * @see {@link tween} to specify the starting value explicitly.
109
+ *
110
+ * @example
111
+ * Grow a circle's radius, then shrink it back with a different curve.
112
+ * ```typescript
113
+ * yield* dot.pipe(
114
+ * Motion.tweenTo({ radius: 40 }, "600 millis", "easeOutBack"),
115
+ * Motion.tweenTo({ radius: 4 }, "400 millis", "easeInQuad"),
116
+ * );
117
+ * ```
30
118
  */
31
- export declare const tweenTo: (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>>(to: Target<Data>, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>) & (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>, E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>, to: Target<Data>, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>);
119
+ export declare const tweenTo: (<Tag extends Entity.EntityTag>(to: Target<Tag>, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>) & (<Tag extends Entity.EntityTag, E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>, to: Target<Tag>, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>);
32
120
  /**
33
- * Parametric animator: each frame applies `fn` with the eased parameter
34
- * and the current data, then ticks. The final frame receives exactly
35
- * `t = 1` for any timing with f(1) = 1; a zero-length duration still
36
- * takes one frame. This is the primitive under coordinated multi-field
37
- * motion — arcs, orbits, counters — where independent per-field tweens
38
- * cannot express the coupling. Determinism: `fn` sees only `(t, data)`.
39
- * Dual: `drive(instance, duration, timing, fn)` or
40
- * `instance.pipe(drive(duration, timing, fn))`.
121
+ * The escape hatch: run `fn` once per frame with the eased progress and the
122
+ * entity's current data, and use whatever it returns as the new data.
123
+ *
124
+ * @remarks
125
+ * Reach for `drive` when fields must move TOGETHER in a way independent
126
+ * tweens cannot express. Tweening `x` and `y` separately gives you a
127
+ * straight line; a circular orbit needs both derived from one angle, and
128
+ * that coupling is what this provides. It is the primitive `Camera.orbit`
129
+ * and `Camera.dolly` are themselves built on.
130
+ *
131
+ * `fn` must be pure — it receives only `(t, data)` and returns new data, so
132
+ * a scene stays reproducible frame-for-frame. Reading a clock or a random
133
+ * number here is what breaks determinism. `t` is the EASED parameter, so a
134
+ * non-linear `timing` reshapes the pacing without `fn` knowing.
135
+ *
136
+ * The final frame receives exactly `t = 1` for any easing with f(1) = 1, so
137
+ * the motion lands precisely on whatever `fn(1, …)` computes.
138
+ *
139
+ * @param duration - How long, in scene time.
140
+ * @param timing - An easing name or function; applied to `t` before `fn`
141
+ * sees it.
142
+ * @param fn - Pure `(t, data) => data`, called once per frame.
143
+ * @returns The instance, so animators chain.
144
+ *
145
+ * @example
146
+ * Sweep a dot around a circle — one angle driving both axes, which two
147
+ * independent tweens could not do.
148
+ * ```typescript
149
+ * yield* dot.pipe(
150
+ * Motion.drive("2 seconds", "linear", (t, data) => ({
151
+ * ...data,
152
+ * position: Entity.vec3({
153
+ * x: 250 + Math.cos(t * 2 * Math.PI) * 120,
154
+ * y: 150 + Math.sin(t * 2 * Math.PI) * 120,
155
+ * }),
156
+ * })),
157
+ * );
158
+ * ```
41
159
  */
42
- export declare const drive: (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>>(duration: Duration.Input, timing: Timing.TimingInput, fn: (t: number, data: Data["Type"]) => Data["Type"]) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>) & (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>, E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>, duration: Duration.Input, timing: Timing.TimingInput, fn: (t: number, data: Data["Type"]) => Data["Type"]) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>);
160
+ export declare const drive: (<Tag extends Entity.EntityTag>(duration: Duration.Input, timing: Timing.TimingInput, fn: (t: number, data: Entity.EntityByTag<Tag>) => Entity.EntityByTag<Tag>) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>) & (<Tag extends Entity.EntityTag, E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>, duration: Duration.Input, timing: Timing.TimingInput, fn: (t: number, data: Entity.EntityByTag<Tag>) => Entity.EntityByTag<Tag>) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>);
43
161
  /**
44
- * Like `tweenTo`, but with an explicit start: interpolates the keys of
45
- * `to` from `from` (keys missing in `from` start at the current data).
46
- * Dual: `tween(instance, from, to, duration, timing?)` or
47
- * `instance.pipe(tween(from, to, duration, timing?))`.
162
+ * Like {@link tweenTo}, but with an explicit starting value.
163
+ *
164
+ * @remarks
165
+ * Reach for this when the animation should not begin where the entity
166
+ * currently sits — a "flash in from nothing" that must start at 0 no matter
167
+ * what the last animation left behind, or a field with no current value at
168
+ * all (an optional one never set), which {@link tweenTo} rejects as a
169
+ * defect.
170
+ *
171
+ * Keys present in `to` but missing from `from` still start at the current
172
+ * data, so a partial origin is a targeted override rather than an
173
+ * all-or-nothing switch.
174
+ *
175
+ * @param from - Starting values; keys omitted here start at the current data.
176
+ * @param to - Target values — this key set defines what gets animated.
177
+ * @param duration - How long, in scene time.
178
+ * @param timing - An easing name or function.
179
+ * @defaultValue `timing` — `"linear"`
180
+ * @returns The instance, so animators chain.
181
+ *
182
+ * @example
183
+ * Pop a label in from zero size, regardless of its current `fontSize`.
184
+ * ```typescript
185
+ * yield* label.pipe(
186
+ * Motion.tween({ fontSize: 0 }, { fontSize: 32 }, "400 millis", "easeOutBack"),
187
+ * );
188
+ * ```
48
189
  */
49
- export declare const tween: (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>>(from: Target<Data>, to: Target<Data>, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>) & (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>>, E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>, from: Target<Data>, to: Target<Data>, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>);
50
- type HasPosition<Data extends Schema.Top> = {
51
- readonly "~position": Entity.TraitLens<Data["Type"], Entity.Position>;
52
- };
53
- type HasOpacity<Data extends Schema.Top> = {
54
- readonly "~opacity": Entity.TraitLens<Data["Type"], number>;
190
+ export declare const tween: (<Tag extends Entity.EntityTag>(from: Target<Tag>, to: Target<Tag>, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>) & (<Tag extends Entity.EntityTag, E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>, from: Target<Tag>, to: Target<Tag>, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>);
191
+ /** a 3D position as the semantic animators speak it */
192
+ export type Position = {
193
+ readonly x: number;
194
+ readonly y: number;
195
+ readonly z: number;
55
196
  };
197
+ /** tags whose entity carries a position: every one of them */
198
+ type Positionable = Entity.TagsWith<"position">;
199
+ /** tags whose entity carries opacity: every paintable one (not Camera) */
200
+ type Fadeable = Entity.TagsWith<"opacity">;
201
+ /**
202
+ * Move an instance to a position over `duration`.
203
+ *
204
+ * @remarks
205
+ * The semantic counterpart to tweening x/y/z by hand, and what you should
206
+ * reach for whenever you want something to travel. Because every entity's
207
+ * geometry is expressed RELATIVE to its `position`, one `moveTo` translates
208
+ * the whole shape rigidly: a Line carries both endpoints, a Path its whole
209
+ * command list, a Group its entire subtree. There is no per-entity special
210
+ * casing to remember.
211
+ *
212
+ * Targets are partial — naming only `x` holds `y` and `z` at their current
213
+ * values, which is what makes single-axis motion read cleanly. `z` is depth:
214
+ * moving along it changes an entity's size and parallax under a perspective
215
+ * camera rather than just its sort order.
216
+ *
217
+ * Accepted for any entity that carries a position, which is all of them —
218
+ * the Camera included, so this is also how you fly the camera.
219
+ *
220
+ * @param to - Target position; omitted axes hold their current value.
221
+ * @param duration - How long, in scene time.
222
+ * @param timing - An easing name or function.
223
+ * @defaultValue `timing` — `"linear"`
224
+ * @returns The instance, so animators chain.
225
+ * @see {@link move} to start from an explicit position, and
226
+ * `Physics.springTo` for durationless motion with momentum.
227
+ *
228
+ * @example
229
+ * Slide right, then drop — the second leg holds the x reached by the first.
230
+ * ```typescript
231
+ * yield* box.pipe(
232
+ * Motion.moveTo({ x: 400 }, "1 second", "easeInOutCubic"),
233
+ * Motion.moveTo({ y: 250 }, "600 millis", "easeInQuad"),
234
+ * );
235
+ * ```
236
+ */
237
+ export declare const moveTo: (<Tag extends Positionable>(to: Partial<Position>, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>) & (<Tag extends Positionable, E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>, to: Partial<Position>, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>);
238
+ /**
239
+ * Like {@link moveTo}, but starting from an explicit position.
240
+ *
241
+ * @remarks
242
+ * The entrance animator: an instance can sit at its final position in the
243
+ * scene tree and still fly in from off-screen, because the origin is stated
244
+ * rather than read. Both `from` and `to` are partial, and each fills its
245
+ * missing axes from the current position.
246
+ *
247
+ * @param from - Starting position; omitted axes start at the current value.
248
+ * @param to - Target position; omitted axes hold their current value.
249
+ * @param duration - How long, in scene time.
250
+ * @param timing - An easing name or function.
251
+ * @defaultValue `timing` — `"linear"`
252
+ * @returns The instance, so animators chain.
253
+ *
254
+ * @example
255
+ * Slide a title in from off the left edge to where it was declared.
256
+ * ```typescript
257
+ * yield* title.pipe(
258
+ * Motion.move({ x: -200 }, { x: 60 }, "700 millis", "easeOutCubic"),
259
+ * );
260
+ * ```
261
+ */
262
+ export declare const move: (<Tag extends Positionable>(from: Partial<Position>, to: Partial<Position>, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>) & (<Tag extends Positionable, E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>, from: Partial<Position>, to: Partial<Position>, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>);
56
263
  /**
57
- * Move an instance to a position via its `~position` trait — per-entity
58
- * semantics (a Line translates whole, a Group carries its subtree).
59
- * Partial targets hold the missing axis. Dual:
60
- * `moveTo(instance, to, duration, timing?)` or
61
- * `instance.pipe(moveTo(to, duration, timing?))`.
264
+ * Fade an instance's `opacity` to `to` over `duration`.
265
+ *
266
+ * @remarks
267
+ * Opacity runs 0 (invisible) to 1 (fully opaque), and applies to the whole
268
+ * subtree: fading a Group fades everything inside it, which is the usual way
269
+ * to dissolve a composed element as one piece.
270
+ *
271
+ * Only entities that actually paint carry `opacity`, so this is statically
272
+ * gated — fading a Camera is a compile error naming the missing field, not a
273
+ * runtime surprise.
274
+ *
275
+ * @param to - Target opacity, 0 to 1.
276
+ * @param duration - How long, in scene time.
277
+ * @param timing - An easing name or function.
278
+ * @defaultValue `timing` — `"linear"`
279
+ * @returns The instance, so animators chain.
280
+ * @see {@link fade} to start from an explicit opacity.
281
+ *
282
+ * @example
283
+ * Land, hold, then dissolve out.
284
+ * ```typescript
285
+ * yield* card.pipe(
286
+ * Motion.moveTo({ y: 100 }, "500 millis"),
287
+ * Motion.wait("1 second"),
288
+ * Motion.fadeTo(0, "400 millis"),
289
+ * );
290
+ * ```
62
291
  */
63
- export declare const moveTo: (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> & HasPosition<Data>>(to: Partial<Entity.Position>, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>) & (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> & HasPosition<Data>, E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>, to: Partial<Entity.Position>, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>);
64
- /** Like `moveTo`, but from an explicit position (partials filled from current). */
65
- export declare const move: (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> & HasPosition<Data>>(from: Partial<Entity.Position>, to: Partial<Entity.Position>, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>) & (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> & HasPosition<Data>, E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>, from: Partial<Entity.Position>, to: Partial<Entity.Position>, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>);
292
+ export declare const fadeTo: (<Tag extends Fadeable>(to: number, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>) & (<Tag extends Fadeable, E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>);
66
293
  /**
67
- * Fade an instance's opacity via its `~opacity` trait. Dual:
68
- * `fadeTo(instance, opacity, duration, timing?)` or
69
- * `instance.pipe(fadeTo(opacity, duration, timing?))`.
294
+ * Like {@link fadeTo}, but starting from an explicit opacity.
295
+ *
296
+ * @remarks
297
+ * The fade-in idiom: entities are born fully opaque, so `fadeTo(1)` alone
298
+ * would be a no-op. Stating the origin is what makes an entrance possible
299
+ * without first writing `opacity: 0` at instantiate time.
300
+ *
301
+ * @param from - Starting opacity, 0 to 1.
302
+ * @param to - Target opacity, 0 to 1.
303
+ * @param duration - How long, in scene time.
304
+ * @param timing - An easing name or function.
305
+ * @defaultValue `timing` — `"linear"`
306
+ * @returns The instance, so animators chain.
307
+ *
308
+ * @example
309
+ * Fade a caption in from nothing.
310
+ * ```typescript
311
+ * yield* caption.pipe(Motion.fade(0, 1, "500 millis"));
312
+ * ```
70
313
  */
71
- export declare const fadeTo: (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> & HasOpacity<Data>>(to: number, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>) & (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> & HasOpacity<Data>, E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>);
72
- /** Like `fadeTo`, but from an explicit opacity. */
73
- export declare const fade: (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> & HasOpacity<Data>>(from: number, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>) & (<Name extends string, Data extends Schema.Top, Traits extends Partial<Entity.EntityTraits<Data["Type"]>> & HasOpacity<Data>, E = never, R = never>(instance: Instance.InstanceOrEffect<Name, Data, Traits, E, R>, from: number, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Name, Data, Traits>, E, R | Runner.Runner>);
314
+ export declare const fade: (<Tag extends Fadeable>(from: number, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => <E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>) & (<Tag extends Fadeable, E = never, R = never>(instance: Instance.InstanceOrEffect<Tag, E, R>, from: number, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => Effect.Effect<Instance.Instance<Tag>, E, R | Runner.Runner>);
74
315
  /**
75
- * `Motion.wait(duration)` is both an Effect and a pipe step: yield it
76
- * directly, or place it between chained animations, where it holds the
77
- * scene AFTER the previous step and passes that step's result through.
316
+ * The dual nature of {@link wait}: an Effect you can yield, and a function
317
+ * you can drop into a `pipe` chain.
318
+ *
319
+ * @remarks
320
+ * Being callable is what lets a hold sit BETWEEN two animators in the same
321
+ * chain — it wraps the preceding step, waits after it completes, and passes
322
+ * that step's value (the instance) through untouched, so the chain keeps
323
+ * flowing.
78
324
  */
79
325
  export interface Wait extends Effect.Effect<void, never, Runner.Runner> {
80
- <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R | Runner.Runner>;
326
+ <Self extends Effect.Effect<any, any, any> | Instance.Instance>(self: Self): Self extends Instance.Instance ? Effect.Effect<Self, never, Runner.Runner> : Self extends Effect.Effect<infer A, infer E, infer R> ? Effect.Effect<A, E, R | Runner.Runner> : never;
81
327
  }
82
328
  /**
83
- * Hold the scene for `duration` of scene time (frames at the runner's
84
- * frame rate) — `Scene.sleep`'s chainable sibling.
329
+ * Hold for `duration` of scene time — the chainable sibling of
330
+ * `Scene.sleep`.
331
+ *
332
+ * @remarks
333
+ * This is frame time, not wall time: `Effect.sleep` would block on a real
334
+ * clock and make the scene non-deterministic, so `wait` advances whole
335
+ * frames at the runner's frame rate instead. The same hold produces the
336
+ * same frame count on every run and on every machine.
337
+ *
338
+ * Its value over `Scene.sleep` is placement — because it doubles as a pipe
339
+ * step, a beat can sit inside an animator chain without breaking it apart
340
+ * into separate statements.
341
+ *
342
+ * As a pipe step it wraps the PRECEDING step and passes its value through,
343
+ * so it sits anywhere in a chain — including first, where the step it wraps
344
+ * is the bare instance handle.
345
+ *
346
+ * @param duration - How long to hold, in scene time.
347
+ *
348
+ * @example
349
+ * A hold between two animations, mid-chain — `card` flows through to the
350
+ * fade.
351
+ * ```typescript
352
+ * yield* card.pipe(
353
+ * Motion.moveTo({ y: 100 }, "500 millis"),
354
+ * Motion.wait("1 second"),
355
+ * Motion.fadeTo(0, "300 millis"),
356
+ * );
357
+ * ```
85
358
  *
86
- * - `yield* Motion.wait("1 second")` — plain frame-based sleep
87
- * - `instance.pipe(moveTo(...), Motion.wait("1 second"), fadeTo(...))`
88
- * — the hold runs between the two animations and the instance flows on
359
+ * @example
360
+ * Or yielded on its own, as a plain pause in the scene body.
361
+ * ```typescript
362
+ * yield* Motion.wait("2 seconds");
363
+ * ```
89
364
  */
90
365
  export declare const wait: (duration: Duration.Input) => Wait;
91
366
  export {};