Scene orchestration
A scene script runs once, moving a cursor through time while it spawns actors and records their motion.
A scene is a generator function. createScene declares it; each scene.build({ viewport, fps })
runs it once, and every verb it calls writes to a timeline record at the current position of a cursor. A
yield* moves the cursor forward. Nothing here draws: a host later asks the compiled scene
for a frame and gets back the state the record describes.
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
class class CardCard extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Card.label: ActorFieldSignal<string>label = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal("");
}
export const const compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
card: Card;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Card;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const card: Cardcard = ctx: SceneContextctx.CompileContext.spawn<Card>(definition: ActorClass<Card>, overrides?: {
measuredBox?: undefined;
label?: SignalInputValue<string> | undefined;
appearance?: {
opacity?: SignalInputValue<number> | undefined;
} | undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 19 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Card (+1 overload)
spawn(class CardCard, {
label?: SignalInputValue<string> | undefinedlabel: "Hello",
appearance?: {
opacity?: SignalInputValue<number> | undefined;
} | undefined
appearance: { opacity?: SignalInputValue<number> | undefinedopacity: 0 },
});
yield* const card: Cardcard.LayoutActor<{}>.appearance: {
opacity: TypedFieldSignal<number, number>;
} & SignalGroupDefinition
appearance.opacity: TypedFieldSignal<number, number>opacity.function tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(1, 0.3);
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
yield* const card: Cardcard.LayoutActor<{}>.layout: {
x: ActorFieldSignal<LayoutValue>;
y: ActorFieldSignal<LayoutValue>;
width: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
height: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
} & SignalGroupDefinition
layout.x: ActorFieldSignal<LayoutValue>x.LiveSignal<LayoutValue>.tween(target: LayoutValue, options: number | SignalTweenOptions<LayoutValue>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(200, 0.5);
return { card: Cardcard };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
const compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.totalDuration: numbertotalDuration; // 54
const compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
card: Card;
}>
at(9).FrameState<{ ...; }>.scene: {
card: ResolvedActorOfInstance<Card> | undefined;
}
scene.card: ResolvedActorOfInstance<Card> | undefinedcard?.appearance: {
opacity: number;
}
appearance.opacity: number | undefinedopacity; // 1
Durations are seconds. A build converts them to frames at the fps its host passes, rounding
each one to the nearest frame. Built at 30 fps, 0.3 s is 9 frames, the wait is 30 and the slide is 15, so the scene is 54 frames long.
The cursor
ctx.cursor is the cursor in frames and ctx.cursorSeconds the same position in seconds.
Every timing verb is a generator that starts its branches at the cursor and moves it on when
you yield* it:
| Verb | Branches start | Cursor moves by |
|---|---|---|
wait(s) |
s |
|
all(...branches) |
All at the cursor | The longest branch |
sequence(d, ...branches) |
Branch i at i × d |
Until the last branch ends |
chain(...branches) |
Each when the one before ends | The sum |
delay(d, branch) |
After d |
d plus the branch |
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor } from "@motionactor/core";
class class DotDot extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {}
createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const dots: Dot[]dots = [0, 1, 2].Array<number>.map<Dot>(callbackfn: (value: number, index: number, array: number[]) => Dot, thisArg?: any): Dot[]Calls a defined callback function on each element of an array, and returns an array that contains the results.map(() => ctx: SceneContextctx.CompileContext.spawn<Dot>(definition: ActorClass<Dot>, overrides?: {
measuredBox?: undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<...>> | undefined;
} | undefined;
... 20 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Dot (+1 overload)
spawn(class DotDot));
yield* ctx: SceneContextctx.CompileContext.all(...generators: Generator<Frame, unknown, unknown>[]): Generator<Frame, void, unknown>all(...const dots: Dot[]dots.Array<Dot>.map<Generator<number, void, unknown>>(callbackfn: (value: Dot, index: number, array: Dot[]) => Generator<number, void, unknown>, thisArg?: any): Generator<number, void, unknown>[]Calls a defined callback function on each element of an array, and returns an array that contains the results.map((d: Dotd) => d: Dotd.LayoutActor<{}>.layout: {
x: ActorFieldSignal<LayoutValue>;
y: ActorFieldSignal<LayoutValue>;
width: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
height: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
} & SignalGroupDefinition
layout.y: ActorFieldSignal<LayoutValue>y.LiveSignal<LayoutValue>.tween(target: LayoutValue, options: number | SignalTweenOptions<LayoutValue>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(100, 0.5)));
ctx: SceneContextctx.CompileContext.cursor: numbercursor; // 15
yield* ctx: SceneContextctx.CompileContext.sequence(delay: Duration, ...generators: Generator<Frame, unknown, unknown>[]): Generator<Frame, void, unknown>sequence(
0.1,
...const dots: Dot[]dots.Array<Dot>.map<Generator<number, void, unknown>>(callbackfn: (value: Dot, index: number, array: Dot[]) => Generator<number, void, unknown>, thisArg?: any): Generator<number, void, unknown>[]Calls a defined callback function on each element of an array, and returns an array that contains the results.map((d: Dotd) => d: Dotd.LayoutActor<{}>.layout: {
x: ActorFieldSignal<LayoutValue>;
y: ActorFieldSignal<LayoutValue>;
width: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
height: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
} & SignalGroupDefinition
layout.y: ActorFieldSignal<LayoutValue>y.LiveSignal<LayoutValue>.tween(target: LayoutValue, options: number | SignalTweenOptions<LayoutValue>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(0, 0.5)),
);
ctx: SceneContextctx.CompileContext.cursor: numbercursor; // 36: the third starts at 21 and ends at 36
yield* ctx: SceneContextctx.CompileContext.chain(...generators: Generator<Frame, unknown, unknown>[]): Generator<Frame, void, unknown>Run operations consecutively, waiting for each to finish.chain(...const dots: Dot[]dots.Array<Dot>.map<Generator<number, void, unknown>>(callbackfn: (value: Dot, index: number, array: Dot[]) => Generator<number, void, unknown>, thisArg?: any): Generator<number, void, unknown>[]Calls a defined callback function on each element of an array, and returns an array that contains the results.map((d: Dotd) => d: Dotd.LayoutActor<{}>.layout: {
x: ActorFieldSignal<LayoutValue>;
y: ActorFieldSignal<LayoutValue>;
width: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
height: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
} & SignalGroupDefinition
layout.x: ActorFieldSignal<LayoutValue>x.LiveSignal<LayoutValue>.tween(target: LayoutValue, options: number | SignalTweenOptions<LayoutValue>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(50, 0.2)));
ctx: SceneContextctx.CompileContext.cursor: numbercursor; // 54
yield* ctx: SceneContextctx.CompileContext.delay(duration: Duration, generator: Generator<Frame, unknown, unknown>): Generator<Frame, void, unknown>delay(0.5, const dots: Dot[]dots[0].LayoutActor<{}>.layout: {
x: ActorFieldSignal<LayoutValue>;
y: ActorFieldSignal<LayoutValue>;
width: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
height: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
} & SignalGroupDefinition
layout.x: ActorFieldSignal<LayoutValue>x.LiveSignal<LayoutValue>.tween(target: LayoutValue, options: number | SignalTweenOptions<LayoutValue>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(0, 1));
ctx: SceneContextctx.CompileContext.cursor: numbercursor; // 99
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
A branch is any generator: a tween, an actor method, another verb. Nesting them is how a scene builds a timeline.
Five bars raised with ctx.all, dropped, then raised again with ctx.sequence(0.08, …).
The track beside raise shows both: five starts on one frame under all, a staircase under
sequence.
Scopes
ctx.scope(fn) runs fn with a child context and moves the cursor as fn does. Actors
spawned through the child despawn when it ends, so a scope suits something that exists for
one beat of the scene. It returns what fn returns:
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor } from "@motionactor/core";
class class FlashFlash extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {}
createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const peak: numberpeak = yield* ctx: SceneContextctx.CompileContext.scope<number>(fn: (scope: CompileContext) => Generator<number, number, unknown>): Generator<number, number, unknown>scope(function* (beat: CompileContextbeat) {
const const flash: Flashflash = beat: CompileContextbeat.CompileContext.spawn<Flash>(definition: ActorClass<Flash>, overrides?: {
measuredBox?: undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | ... 1 more ... | Readonly<...>> | undefined;
} | undefined;
... 20 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Flash (+1 overload)
spawn(class FlashFlash);
yield* const flash: Flashflash.LayoutActor<{}>.appearance: {
opacity: TypedFieldSignal<number, number>;
} & SignalGroupDefinition
appearance.opacity: TypedFieldSignal<number, number>opacity.function tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(0, 1);
return beat: CompileContextbeat.CompileContext.cursor: numbercursor;
});
const peak: numberpeak; // 30
ctx: SceneContextctx.CompileContext.cursor: numbercursor; // 30, and the flash has despawned
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
Spawning
ctx.spawn(Class, overrides, { id }) creates an actor at the cursor and returns it at once,
taking no time. Overrides set its initial values and are required when the class declares a
field with no default. Without an id the actor gets a generated one such as Card-2.
Give the options an enter and spawn returns a generator instead: it spawns, then runs the
actor’s onEnter with enter as its params, and returns the
actor when that finishes.
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, const Easing: {
readonly linear: EasingFunction;
readonly inQuad: EasingFunction;
readonly outQuad: EasingFunction;
readonly inOutQuad: EasingFunction;
readonly inCubic: EasingFunction;
readonly outCubic: EasingFunction;
readonly inOutCubic: EasingFunction;
readonly inQuart: EasingFunction;
readonly outQuart: EasingFunction;
readonly inOutQuart: EasingFunction;
readonly bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
readonly wiggle: (count: number, type?: WiggleType, amplitude?: number) => EasingFunction;
readonly in: (easing: EasingFunction) => EasingFunction;
readonly out: (easing: EasingFunction) => EasingFunction;
readonly inOut: (easing: EasingFunction) => EasingFunction;
}
Easing, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor } from "@motionactor/core";
class class CardCard extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
*Card.onEnter({ rise }?: {
rise?: number;
}): Generator<number, void, unknown>
onEnter({ rise: numberrise = 40 }: { rise?: number | undefinedrise?: number } = {}) {
this.LayoutActor<{}>.layout: {
x: ActorFieldSignal<LayoutValue>;
y: ActorFieldSignal<LayoutValue>;
width: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
height: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
} & SignalGroupDefinition
layout.y: ActorFieldSignal<LayoutValue>y.LiveSignal<LayoutValue>.set(value: LayoutValue): voidSet value instantly at current cursor (no yield needed)set(rise: numberrise);
yield* this.Actor<{}>.runtime: ActorRuntimeOrchestration<this>runtime.ActorRuntimeOrchestration<this>.all(...generators: Generator<Frame, unknown, unknown>[]): Generator<Frame, void, unknown>all(
this.LayoutActor<{}>.layout: {
x: ActorFieldSignal<LayoutValue>;
y: ActorFieldSignal<LayoutValue>;
width: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
height: TypedFieldSignal<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>, number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>>;
} & SignalGroupDefinition
layout.y: ActorFieldSignal<LayoutValue>y.LiveSignal<LayoutValue>.tween(target: LayoutValue, options: number | SignalTweenOptions<LayoutValue>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(0, { SignalTweenOptions<LayoutValue>.duration: numberduration: 0.5, SignalTweenOptions<LayoutValue>.easing?: EasingFunction | undefinedeasing: const Easing: {
readonly linear: EasingFunction;
readonly inQuad: EasingFunction;
readonly outQuad: EasingFunction;
readonly inOutQuad: EasingFunction;
readonly inCubic: EasingFunction;
readonly outCubic: EasingFunction;
readonly inOutCubic: EasingFunction;
readonly inQuart: EasingFunction;
readonly outQuart: EasingFunction;
readonly inOutQuart: EasingFunction;
readonly bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
readonly wiggle: (count: number, type?: WiggleType, amplitude?: number) => EasingFunction;
readonly in: (easing: EasingFunction) => EasingFunction;
readonly out: (easing: EasingFunction) => EasingFunction;
readonly inOut: (easing: EasingFunction) => EasingFunction;
}
Easing.outCubic: EasingFunctionoutCubic }),
this.LayoutActor<{}>.appearance: {
opacity: TypedFieldSignal<number, number>;
} & SignalGroupDefinition
appearance.opacity: TypedFieldSignal<number, number>opacity.function tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(1, 0.5),
);
}
}
createScene<{
card: Card;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Card;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const card: Cardcard = yield* ctx: SceneContextctx.CompileContext.spawn<Card, {
rise: number;
}>(definition: ActorClass<Card>, overrides: {
measuredBox?: undefined;
transform?: {
scale?: SignalInputValue<number> | undefined;
rotation?: SignalInputValue<number> | undefined;
} | undefined;
id?: undefined;
appearance?: {
opacity?: SignalInputValue<number> | undefined;
} | undefined;
enter?: undefined;
onEnter?: undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<...> | ... 1 more ... | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 15 more ...;
parent?: undefined;
} | undefined, options: {
...;
}): Generator<...> (+1 overload)
spawn(
class CardCard,
{ appearance?: {
opacity?: SignalInputValue<number> | undefined;
} | undefined
appearance: { opacity?: SignalInputValue<number> | undefinedopacity: 0 } },
{ id?: string | undefinedid: "card", enter: {
rise: number;
}
enter: { rise: numberrise: 80 } },
);
ctx: SceneContextctx.CompileContext.cursor: numbercursor; // 15
return { card: Cardcard };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
Inside an actor
An actor method is a generator too, and this.runtime gives it the same all, chain,
sequence, delay and wait. They work while the actor is in a scene; called on an actor
that is not, they throw “Actor instances can only coordinate generators while attached to a
scene.”
The runtime adds two verbs that start work without moving anyone’s cursor:
| Verb | Runs | Ends |
|---|---|---|
fork(fn) |
fn once, from the cursor |
When fn returns |
loop(fn) |
fn again each time it returns |
When the scene ends |
loop({ iterations }, fn) |
fn that many times |
After the last time |
Both take an optional name first. Starting a fork or a loop under a name the actor already runs replaces the old one: it starts nothing new, though a tween already under way plays to its end.
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor } from "@motionactor/core";
class class BeaconBeacon extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
*Beacon.beat(): Generator<number, void, unknown>beat() {
yield* this.LayoutActor<{}>.appearance: {
opacity: TypedFieldSignal<number, number>;
} & SignalGroupDefinition
appearance.opacity: TypedFieldSignal<number, number>opacity.function tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(0.4, 0.5);
yield* this.LayoutActor<{}>.appearance: {
opacity: TypedFieldSignal<number, number>;
} & SignalGroupDefinition
appearance.opacity: TypedFieldSignal<number, number>opacity.function tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(1, 0.5);
}
Beacon.calm(): voidcalm() {
this.Actor<{}>.runtime: ActorRuntimeOrchestration<this>runtime.ActorRuntimeOrchestration<this>.loop(name: string, generatorFactory: (this: this) => Generator<Frame, void, unknown>): void (+3 overloads)loop("pulse", function* (this: Beaconthis: class BeaconBeacon) {
yield* this.Beacon.beat(): Generator<number, void, unknown>beat();
yield* this.Actor<{}>.runtime: ActorRuntimeOrchestration<Beacon>runtime.ActorRuntimeOrchestration<Beacon>.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
});
}
Beacon.alarm(): voidalarm() {
this.Actor<{}>.runtime: ActorRuntimeOrchestration<this>runtime.ActorRuntimeOrchestration<this>.loop(name: string, generatorFactory: (this: this) => Generator<Frame, void, unknown>): void (+3 overloads)loop("pulse", function* (this: Beaconthis: class BeaconBeacon) {
yield* this.Beacon.beat(): Generator<number, void, unknown>beat();
});
}
}
createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const beacon: Beaconbeacon = ctx: SceneContextctx.CompileContext.spawn<Beacon>(definition: ActorClass<Beacon>, overrides?: {
measuredBox?: undefined;
transform?: {
scale?: SignalInputValue<number> | undefined;
rotation?: SignalInputValue<number> | undefined;
} | undefined;
id?: undefined;
beat?: undefined;
calm?: undefined;
alarm?: undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<...> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 18 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Beacon (+1 overload)
spawn(class BeaconBeacon);
const beacon: Beaconbeacon.Beacon.calm(): voidcalm();
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(4);
const beacon: Beaconbeacon.Beacon.alarm(): voidalarm();
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(3);
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
A scene lasts until its script returns or until its last finite fork or loop ends, whichever is later. An endless loop never lengthens it.
Reads across branches
The script, every branch of all and sequence, every fork, loop and generator handler
step together in frame order. A read sees every write made at an earlier frame, whichever
branch made it. At a frame two branches share, work a branch started steps before that
branch, so the script reads what its forks and handlers wrote at that frame. A tween takes
its start value the same way, at the frame its branch reaches it.
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
class class CounterCounter extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Counter.count: ActorFieldSignal<number>count = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(0);
}
createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const counter: Countercounter = ctx: SceneContextctx.CompileContext.spawn<Counter>(definition: ActorClass<Counter>, overrides?: {
measuredBox?: undefined;
count?: SignalInputValue<number> | undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 20 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Counter (+1 overload)
spawn(class CounterCounter);
yield* ctx: SceneContextctx.CompileContext.all(...generators: Generator<Frame, unknown, unknown>[]): Generator<Frame, void, unknown>all(
(function* () {
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
const counter: Countercounter.Counter.count: ActorFieldSignal<number>count.LiveSignal<number>.set(value: number): voidSet value instantly at current cursor (no yield needed)set(const counter: Countercounter.Counter.count: () => numbercount() + 1); // 11: the other branch wrote 10 at frame 15
})(),
(function* () {
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(0.5);
const counter: Countercounter.Counter.count: ActorFieldSignal<number>count.LiveSignal<number>.set(value: number): voidSet value instantly at current cursor (no yield needed)set(10);
})(),
);
const counter: Countercounter.Actor<{}>.runtime: ActorRuntimeOrchestration<Counter>runtime.ActorRuntimeOrchestration<Counter>.fork(generatorFactory: (this: Counter) => Generator<Frame, void, unknown>): void (+1 overload)fork(function* () {
yield* const counter: Countercounter.Actor<{}>.runtime: ActorRuntimeOrchestration<Counter>runtime.ActorRuntimeOrchestration<Counter>.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
const counter: Countercounter.Counter.count: ActorFieldSignal<number>count.LiveSignal<number>.set(value: number): voidSet value instantly at current cursor (no yield needed)set(20);
});
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
const counter: Countercounter.Counter.count: () => numbercount(); // 20: the fork wrote at frame 60 and stepped first
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
Inputs and layers
Give createScene inputs or layers to declare what a host supplies. The script
receives the input values, the viewport and the layer handles after ctx, and compiles
each time a host calls build:
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, const input: {
of: {
<T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
<T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
<T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
<TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
};
}
input, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
import { const image: AssetType<"image", "image">image, const text: DataType<"text", ZodString>text } from "@motionactor/schema";
class class PosterPoster extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Poster.source: TypedRequiredSignal<AssetRef<"image">, AssetRef<"image">>source = function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal.signal.of: <AssetType<"image", "image">>(type: AssetType<"image", "image">) => TypedRequiredSignal<AssetRef<"image">, AssetRef<"image">> (+2 overloads)A signal whose value is a named type from `@motionactor/schema`. The type
survives to reflection (`field.type`) and spawn checks values against its
schema. `signal.of(audio)` is required; `signal.of(image, null)` is not.
A decoding type (`typeDecodes`: `colorType`) also maps what is written:
the default, spawn inputs, and `set`/`tween`/`play` values go through its
schema, so the field only ever holds the type's value.of(const image: AssetType<"image", "image">image);
Poster.caption: ActorFieldSignal<string>caption = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal("");
}
export const const poster: SceneDefinition<{
readonly hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>;
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Poster;
}>
poster = createScene<{
card: Poster;
}, {
readonly hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>;
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<{
readonly hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>;
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<...>, {
card: Poster;
}>, options?: SceneDefinitionOptions<...> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(
function* (ctx: SceneContextctx, { hero: AssetRef<"image">hero, title: stringtitle }, { width: numberwidth }) {
const const card: Postercard = ctx: SceneContextctx.CompileContext.spawn<Poster>(definition: ActorClass<Poster>, overrides: {
source: SignalInputValue<AssetRef<"image">>;
transform?: {
scale?: SignalInputValue<number> | undefined;
rotation?: SignalInputValue<number> | undefined;
} | undefined;
measuredBox?: undefined;
id?: undefined;
caption?: SignalInputValue<string> | undefined;
layout?: {
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
x?: SignalInputValue<...> | undefined;
y?: SignalInputValue<...> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 18 more ...;
parent?: undefined;
}, options?: {
...;
} | undefined): Poster (+1 overload)
spawn(class PosterPoster, {
source: SignalInputValue<AssetRef<"image">>source: hero: AssetRef<"image">hero,
caption?: SignalInputValue<string> | undefinedcaption: title: stringtitle,
layout?: {
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
height?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>> | undefined;
} | undefined
layout: { x?: SignalInputValue<LayoutValue> | undefinedx: width: numberwidth * 0.1 },
});
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(2);
return { card: Postercard };
},
{
SceneDefinitionOptions<{ readonly hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>; readonly title: ValueInput<DataType<"text", ZodString>, string, false>; }, Readonly<...>>.inputs?: {
readonly hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>;
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
} | undefined
inputs: {
hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>hero: const input: {
of: {
<T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
<T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
<T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
<TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
};
}
input.of: <AssetType<"image", "image">>(type: AssetType<"image", "image">) => ValueInput<AssetType<"image", "image">, AssetRef<"image">, true> (+3 overloads)Declare a scene input. Over a named type from `@motionactor/schema`,
`input.of(image)` is required; `input.of(text, "Launch day")` and
`input.of(image, null)` are not. Over an actor blueprint,
`input.of(Narrator)` takes a `Narrator` from the scene that stages it,
and has no default.of(const image: AssetType<"image", "image">image),
title: ValueInput<DataType<"text", ZodString>, string, false>title: const input: {
of: {
<T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
<T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
<T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
<TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
};
}
input.of: <DataType<"text", ZodString>>(type: DataType<"text", ZodString>, defaultValue: string) => ValueInput<DataType<"text", ZodString>, string, false> (+3 overloads)Declare a scene input. Over a named type from `@motionactor/schema`,
`input.of(image)` is required; `input.of(text, "Launch day")` and
`input.of(image, null)` are not. Over an actor blueprint,
`input.of(Narrator)` takes a `Narrator` from the scene that stages it,
and has no default.of(const text: DataType<"text", ZodString>text, "Launch day"),
},
},
);
const const compiled: CompiledScene<{
card: Poster;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = const poster: SceneDefinition<{
readonly hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>;
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Poster;
}>
poster.SceneDefinition<{ readonly hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>; readonly title: ValueInput<DataType<"text", ZodString>, string, false>; }, Readonly<...>, { ...; }>.build(args: SceneBuildInput<{
readonly hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>;
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>): CompiledScene<{
card: Poster;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({
viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 },
inputs: HostInputArgs<{
readonly hero: ValueInput<AssetType<"image", "image">, AssetRef<"image">, true>;
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}>
inputs: { hero: AssetRef<"image">hero: const image: AssetType<"image", "image">image.AssetType<"image", "image">.file(src: string, options?: {
mimeType?: string;
}): AssetRef<"image">
file("hero.jpg") },
fps: numberfps: 30,
});
const compiled: CompiledScene<{
card: Poster;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
card: Poster;
}>
at(0).FrameState<{ ...; }>.scene: {
card: ResolvedActorOfInstance<Poster> | undefined;
}
scene.card: ResolvedActorOfInstance<Poster> | undefinedcard?.layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
x: LayoutValue;
y: LayoutValue;
height: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
}
layout.x: LayoutValue | undefinedx; // 192
input.of(type) is required and input.of(type, fallback) is not, as with signal.of.
build checks each value against its type and names the input that is missing or wrong.
An input is a plain value: a different one is a new build.
reflectSceneInputs(poster) lists the declared inputs for an editor. Layers are on
Backends and layers.
Staging scenes
Every scene’s ctx has stage, which runs a scene as one part of a longer timeline.
stage takes a script, or a definition with its inputs, and returns the scene’s exports:
import {
function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene,
const input: {
of: {
<T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
<T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
<T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
<TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
};
}
input,
class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor,
function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal,
} from "@motionactor/core";
import { const text: DataType<"text", ZodString>text } from "@motionactor/schema";
class class TitleTitle extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Title.text: ActorFieldSignal<string>text = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal("");
}
const const intro: SceneDefinition<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>
intro = createScene<void, {
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<...>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(
function* (ctx: SceneContextctx, { title: stringtitle }) {
ctx: SceneContextctx.CompileContext.spawn<Title>(definition: ActorClass<Title>, overrides?: {
text?: SignalInputValue<string> | undefined;
transform?: {
scale?: SignalInputValue<number> | undefined;
rotation?: SignalInputValue<number> | undefined;
} | undefined;
measuredBox?: undefined;
id?: undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<...> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 18 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Title (+1 overload)
spawn(class TitleTitle, { text?: SignalInputValue<string> | undefinedtext: title: stringtitle });
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
},
{ SceneDefinitionOptions<{ readonly title: ValueInput<DataType<"text", ZodString>, string, false>; }, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>.inputs?: {
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
} | undefined
inputs: { title: ValueInput<DataType<"text", ZodString>, string, false>title: const input: {
of: {
<T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
<T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
<T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
<TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
};
}
input.of: <DataType<"text", ZodString>>(type: DataType<"text", ZodString>, defaultValue: string) => ValueInput<DataType<"text", ZodString>, string, false> (+3 overloads)Declare a scene input. Over a named type from `@motionactor/schema`,
`input.of(image)` is required; `input.of(text, "Launch day")` and
`input.of(image, null)` are not. Over an actor blueprint,
`input.of(Narrator)` takes a `Narrator` from the scene that stages it,
and has no default.of(const text: DataType<"text", ZodString>text, "Hi") } },
);
const const film: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>film = createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
yield* ctx: SceneContextctx.SceneContext.stage<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>(definition: SceneDefinition<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: (StageOptions & {
inputs?: SceneInputArgs<...> | undefined;
}) | undefined): Generator<...> (+2 overloads)
Stage a `createScene` definition with its inputs, under the build's
viewport and on its clock. Its
declared layers are in force while it is on stage, below this scene's.
It may stage scenes in turn.stage(const intro: SceneDefinition<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>
intro, { StageOptions.id?: string | undefinedid: "intro", inputs?: SceneInputArgs<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
}> | undefined
inputs: { title?: string | undefinedtitle: "Hello" } });
yield* ctx: SceneContextctx.SceneContext.stage<void>(sceneFn: (ctx: SceneContext) => Generator<number, void, unknown>, options?: StageOptions): Generator<number, void, unknown> (+2 overloads)stage(
function* (scene: SceneContextscene) {
yield* scene: SceneContextscene.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(2);
},
{ StageOptions.id?: string | undefinedid: "outro" },
);
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
const film: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>film.CompiledScene<TSceneExports = void | SceneExportShape, TLayers extends CompiledLayers = Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>.totalDuration: numbertotalDuration; // 90
const film: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>film.CompiledScene<TSceneExports = void | SceneExportShape, TLayers extends CompiledLayers = Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>.placements: readonly ScenePlacement[]Every scene staged in the build, at any depth, in start order: a stager before what it stages.placements.ReadonlyArray<ScenePlacement>.map<(string | number)[]>(callbackfn: (value: ScenePlacement, index: number, array: readonly ScenePlacement[]) => (string | number)[], thisArg?: any): (string | number)[][]Calls a defined callback function on each element of an array, and returns an array that contains the results.map((p: ScenePlacementp) => [p: ScenePlacementp.ScenePlacement.sceneId: stringIts `ctx.stage` id: given, or minted.sceneId, p: ScenePlacementp.ScenePlacement.startFrame: numberOn the build's timeline, at any depth.startFrame, p: ScenePlacementp.ScenePlacement.endFrame: numberendFrame]);
// [["intro", 0, 30], ["outro", 30, 90]]
Each stage runs in its stager’s scope, so its actors leave when it ends. Actors spawned on the
stager’s own ctx stay for its whole timeline, which is where a music bed goes. Two scenes one
stager stages cannot overlap: staging two under ctx.all throws, naming both and their frames.
A staged definition runs on the built scene’s clock, and its length in seconds holds.
A staged scene stages in turn. placements lists every staged scene at any depth, stager
first: each has a path ("a/leaf"), its stager’s parentPath (null at the top) and its
depth (1 at the top). A definition already on stage above itself throws, and so does a second
stage with one path; give each stage under one stager its own id.
const const leaf: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>leaf = createScene<void, {}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
}, { SceneDefinitionOptions<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>.inputs?: {} | undefinedinputs: {} });
const const chapter: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>chapter = createScene<void, {}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(0.5);
yield* ctx: SceneContextctx.SceneContext.stage<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>(definition: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: (StageOptions & {
inputs?: SceneInputArgs<{}> | undefined;
}) | undefined): Generator<number, void, unknown> (+2 overloads)
Stage a `createScene` definition with its inputs, under the build's
viewport and on its clock. Its
declared layers are in force while it is on stage, below this scene's.
It may stage scenes in turn.stage(const leaf: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>leaf, { StageOptions.id?: string | undefinedid: "leaf" });
}, { SceneDefinitionOptions<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>.inputs?: {} | undefinedinputs: {} });
const const book: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>book = createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
yield* ctx: SceneContextctx.SceneContext.stage<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>(definition: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: (StageOptions & {
inputs?: SceneInputArgs<{}> | undefined;
}) | undefined): Generator<number, void, unknown> (+2 overloads)
Stage a `createScene` definition with its inputs, under the build's
viewport and on its clock. Its
declared layers are in force while it is on stage, below this scene's.
It may stage scenes in turn.stage(const chapter: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>chapter, { StageOptions.id?: string | undefinedid: "a" });
yield* ctx: SceneContextctx.SceneContext.stage<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>(definition: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: (StageOptions & {
inputs?: SceneInputArgs<{}> | undefined;
}) | undefined): Generator<number, void, unknown> (+2 overloads)
Stage a `createScene` definition with its inputs, under the build's
viewport and on its clock. Its
declared layers are in force while it is on stage, below this scene's.
It may stage scenes in turn.stage(const chapter: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>chapter, { StageOptions.id?: string | undefinedid: "b" });
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
const book: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>book.CompiledScene<TSceneExports = void | SceneExportShape, TLayers extends CompiledLayers = Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>.placements: readonly ScenePlacement[]Every scene staged in the build, at any depth, in start order: a stager before what it stages.placements.ReadonlyArray<ScenePlacement>.map<(string | number)[]>(callbackfn: (value: ScenePlacement, index: number, array: readonly ScenePlacement[]) => (string | number)[], thisArg?: any): (string | number)[][]Calls a defined callback function on each element of an array, and returns an array that contains the results.map((p: ScenePlacementp) => [p: ScenePlacementp.ScenePlacement.path: stringIts stagers' ids and its own, joined by "/": `a/leaf`. Unique in the build.path, p: ScenePlacementp.ScenePlacement.depth: number1 when the built scene staged it, 2 for a scene that one staged.depth, p: ScenePlacementp.ScenePlacement.startFrame: numberOn the build's timeline, at any depth.startFrame, p: ScenePlacementp.ScenePlacement.endFrame: numberendFrame]);
// [["a", 1, 0, 45], ["a/leaf", 2, 15, 45], ["b", 1, 45, 90], ["b/leaf", 2, 60, 90]]
The host can pass the scenes to stage as well: a definition’s build takes scenes, and
its script reads them from ctx.scenes. Only the built scene lists them; a staged scene’s
ctx.scenes is empty.
const const video: SceneDefinition<{
readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>
video = createScene<void, {
readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<{
readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<{
readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<...>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(
function* (ctx: SceneContextctx, { music: AssetRef<"audio">music }) {
const const bed: Audiobed = ctx: SceneContextctx.CompileContext.spawn<Audio>(definition: ActorClass<Audio>, overrides: {
content: {
source: SignalInputValue<AssetRef<"audio">>;
};
volume?: SignalInputValue<number> | undefined;
fade?: {
in?: SignalInputValue<number> | undefined;
out?: SignalInputValue<number> | undefined;
} | undefined;
timing?: {
start?: SignalInputValue<number> | undefined;
end?: SignalInputValue<number> | undefined;
} | undefined;
sourceTime?: SignalInputValue<number> | undefined;
... 18 more ...;
parent?: undefined;
}, options?: {
...;
} | undefined): Audio (+1 overload)
spawn(class AudioAudio, {
content: {
source: SignalInputValue<AssetRef<"audio">>;
}
content: { source: SignalInputValue<AssetRef<"audio">>source: music: AssetRef<"audio">music },
volume?: SignalInputValue<number> | undefinedvolume: 0.5,
fade?: {
in?: SignalInputValue<number> | undefined;
out?: SignalInputValue<number> | undefined;
} | undefined
fade: { in?: SignalInputValue<number> | undefinedin: 2, out?: SignalInputValue<number> | undefinedout: 3 },
});
const bed: Audiobed.Audio.loop(params?: MediaLoopParams): voidloop();
for (const const scene: StagedScenescene of ctx: SceneContextctx.SceneContext.scenes: readonly StagedScene[]The scenes the host passed to `build`, in order. Empty for a staged scene.scenes) yield* ctx: SceneContextctx.SceneContext.stage(scene: StagedScene): Generator<Frame, unknown, unknown> (+2 overloads)stage(const scene: StagedScenescene);
},
{ SceneDefinitionOptions<{ readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>; }, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>.inputs?: {
readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
} | undefined
inputs: { music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>music: const input: {
of: {
<T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
<T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
<T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
<TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
};
}
input.of: <AssetType<"audio", "audio">>(type: AssetType<"audio", "audio">) => ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true> (+3 overloads)Declare a scene input. Over a named type from `@motionactor/schema`,
`input.of(image)` is required; `input.of(text, "Launch day")` and
`input.of(image, null)` are not. Over an actor blueprint,
`input.of(Narrator)` takes a `Narrator` from the scene that stages it,
and has no default.of(const audio: AssetType<"audio", "audio">audio) } },
);
const video: SceneDefinition<{
readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>
video.SceneDefinition<{ readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>; }, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<{
readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({
viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 },
inputs: HostInputArgs<{
readonly music: ValueInput<AssetType<"audio", "audio">, AssetRef<"audio">, true>;
}>
inputs: { music: AssetRef<"audio">music: const theme: AssetRef<"audio">theme },
scenes?: readonly StagedScene[] | undefinedWhat the built scene's `ctx.scenes` lists, in order: definitions the host picked, typically from JSON props.scenes: [
{ StagedScene.id: stringid: "intro", StagedScene.definition: AnySceneDefinitiondefinition: const intro: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>intro },
{ StagedScene.id: stringid: "outro", StagedScene.definition: AnySceneDefinitiondefinition: const outro: SceneDefinition<{}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>outro },
],
fps: numberfps: 30,
});
Actor inputs
input.of(Blueprint) declares an input that is an actor. The scene that stages the
definition passes one of its own actors, and the script receives that actor: its fields stay
live, so whatever the stager does to it reaches the staged scene, across cuts.
import {
function channel<TValue>(options: {
readonly default: TValue;
readonly record?: boolean;
readonly equals?: (previous: TValue, next: TValue) => boolean;
}): ChannelDefinition<TValue> & {
readonly hasDefault: true;
readonly default: TValue;
} (+1 overload)
A channel is a writable, scope-aware cell carrying an actor-channel brand.
Declared as an actor field it lives in the spawning build's store (keyed
`${actorId}:${name}` by the spawn walk, like a signal); declared free (in
`buildScene`, a script, or module top) it keys by its scope, in its build's
store or the module's, and is shared by reference. Either way resolution
reads it live.
The static return type stays `ChannelDefinition<TValue>` so the
`isChannelDefinition` predicate is unchanged; at runtime the handle is a
live cell.channel,
function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene,
function derived<TThis, T>(fn: (this: TThis) => T): DerivedDefinition<T, TThis>derived,
const input: {
of: {
<T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
<T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
<T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
<TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
};
}
input,
class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor,
function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal,
} from "@motionactor/core";
import { const text: DataType<"text", ZodString>text } from "@motionactor/schema";
class class NarratorNarrator extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Narrator.volume: ActorFieldSignal<number>volume = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(1);
Narrator.loudness: ChannelDefinition<number> & {
readonly hasDefault: true;
readonly default: number;
}
loudness = channel<number>(options: {
readonly default: number;
readonly record?: boolean;
readonly equals?: ((previous: number, next: number) => boolean) | undefined;
}): ChannelDefinition<number> & {
readonly hasDefault: true;
readonly default: number;
} (+1 overload)
A channel is a writable, scope-aware cell carrying an actor-channel brand.
Declared as an actor field it lives in the spawning build's store (keyed
`${actorId}:${name}` by the spawn walk, like a signal); declared free (in
`buildScene`, a script, or module top) it keys by its scope, in its build's
store or the module's, and is shared by reference. Either way resolution
reads it live.
The static return type stays `ChannelDefinition<TValue>` so the
`isChannelDefinition` predicate is unchanged; at runtime the handle is a
live cell.channel({ default: numberdefault: 0 });
}
class class MeterMeter extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Meter.level: ActorFieldSignal<number>level = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(0);
}
const const outro: SceneDefinition<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: string;
}>
outro = createScene<{
title: string;
}, {
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: string;
}>, options?: SceneDefinitionOptions<...> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(
function* (ctx: SceneContextctx, { title: stringtitle, narrator: Narratornarrator }) {
ctx: SceneContextctx.CompileContext.spawn<Meter>(definition: ActorClass<Meter>, overrides?: {
transform?: {
scale?: SignalInputValue<number> | undefined;
rotation?: SignalInputValue<number> | undefined;
} | undefined;
measuredBox?: undefined;
id?: undefined;
level?: SignalInputValue<number> | undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<...> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 18 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Meter (+1 overload)
spawn(class MeterMeter, { level?: SignalInputValue<number> | undefinedlevel: derived<any, number>(fn: (this: any) => number): DerivedDefinition<number, any>derived(() => narrator: Narratornarrator.Narrator.loudness: () => numberloudness() * 2) });
ctx: SceneContextctx.CompileContext.spawn<Meter>(definition: ActorClass<Meter>, overrides?: {
transform?: {
scale?: SignalInputValue<number> | undefined;
rotation?: SignalInputValue<number> | undefined;
} | undefined;
measuredBox?: undefined;
id?: undefined;
level?: SignalInputValue<number> | undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<...> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 18 more ...;
parent?: undefined;
} | undefined, options?: {
...;
} | undefined): Meter (+1 overload)
spawn(class MeterMeter, { level?: SignalInputValue<number> | undefinedlevel: narrator: Narratornarrator.Narrator.volume: ActorFieldSignal<number>volume });
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
return { title: stringtitle };
},
{ SceneDefinitionOptions<{ readonly title: ValueInput<DataType<"text", ZodString>, string, false>; readonly narrator: ActorInput<Narrator>; }, Readonly<...>>.inputs?: {
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
} | undefined
inputs: { title: ValueInput<DataType<"text", ZodString>, string, false>title: const input: {
of: {
<T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
<T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
<T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
<TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
};
}
input.of: <DataType<"text", ZodString>>(type: DataType<"text", ZodString>, defaultValue: string) => ValueInput<DataType<"text", ZodString>, string, false> (+3 overloads)Declare a scene input. Over a named type from `@motionactor/schema`,
`input.of(image)` is required; `input.of(text, "Launch day")` and
`input.of(image, null)` are not. Over an actor blueprint,
`input.of(Narrator)` takes a `Narrator` from the scene that stages it,
and has no default.of(const text: DataType<"text", ZodString>text, "Outro"), narrator: ActorInput<Narrator>narrator: const input: {
of: {
<T extends AnyType>(type: T): ValueInput<T, TypeValue<T>, true>;
<T extends AnyType>(type: T, defaultValue: null): ValueInput<T, TypeValue<T> | null, false>;
<T extends AnyType>(type: T, defaultValue: TypeValue<T>): ValueInput<T, TypeValue<T>, false>;
<TInstance extends object>(blueprint: ActorClass<TInstance>): ActorInput<TInstance>;
};
}
input.of: <Narrator>(blueprint: ActorClass<Narrator>) => ActorInput<Narrator> (+3 overloads)Declare a scene input. Over a named type from `@motionactor/schema`,
`input.of(image)` is required; `input.of(text, "Launch day")` and
`input.of(image, null)` are not. Over an actor blueprint,
`input.of(Narrator)` takes a `Narrator` from the scene that stages it,
and has no default.of(class NarratorNarrator) } },
);
const const video: CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>video = createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const narrator: Narratornarrator = ctx: SceneContextctx.CompileContext.spawn<Narrator>(definition: ActorClass<Narrator>, overrides?: {
transform?: {
scale?: SignalInputValue<number> | undefined;
rotation?: SignalInputValue<number> | undefined;
} | undefined;
measuredBox?: undefined;
id?: undefined;
layout?: {
x?: SignalInputValue<LayoutValue> | undefined;
y?: SignalInputValue<LayoutValue> | undefined;
width?: Readonly<{
kind: "layout.percent";
value: number;
}> | SignalInputValue<number | `${number}%` | Readonly<...>> | undefined;
height?: Readonly<...> | ... 1 more ... | undefined;
} | undefined;
... 20 more ...;
loudness?: undefined;
} | undefined, options?: {
...;
} | undefined): Narrator (+1 overload)
spawn(class NarratorNarrator);
const narrator: Narratornarrator.Narrator.volume: ActorFieldSignal<number>volume.LiveSignal<number>.animate(name: string, options: SignalAnimationOptions<number>): void (+1 overload)Start named branch-owned ambient signal animation.animate("fade", { SignalPlaybackOptions<number>.to: numberto: 0, SignalTweenOptions<T>.duration: numberduration: 2 });
yield* ctx: SceneContextctx.SceneContext.stage<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: string;
}>(definition: SceneDefinition<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: string;
}>, options?: (StageOptions & {
...;
}) | undefined): Generator<...> (+2 overloads)
Stage a `createScene` definition with its inputs, under the build's
viewport and on its clock. Its
declared layers are in force while it is on stage, below this scene's.
It may stage scenes in turn.stage(const outro: SceneDefinition<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: string;
}>
outro, { inputs?: SceneInputArgs<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}> | undefined
inputs: { narrator: Narratornarrator, title?: string | undefinedtitle: "One" } });
yield* ctx: SceneContextctx.SceneContext.stage<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: string;
}>(definition: SceneDefinition<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: string;
}>, options?: (StageOptions & {
...;
}) | undefined): Generator<...> (+2 overloads)
Stage a `createScene` definition with its inputs, under the build's
viewport and on its clock. Its
declared layers are in force while it is on stage, below this scene's.
It may stage scenes in turn.stage(const outro: SceneDefinition<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: string;
}>
outro, { inputs?: SceneInputArgs<{
readonly title: ValueInput<DataType<"text", ZodString>, string, false>;
readonly narrator: ActorInput<Narrator>;
}> | undefined
inputs: { narrator: Narratornarrator, title?: string | undefinedtitle: "Two" } });
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
ctx.stagechecks the actor: one of another blueprint, a value that is not a spawned actor, or an actor of another build throws, naming the input. A subclass of the blueprint passes.- An actor input takes no default and has no host form. A host has no actor to pass, so
buildon such a definition is a type error and throwsscene: input "narrator" takes a Narrator actor, which only a stager can pass, at ctx.stage. reflectSceneInputs(outro)reports it as{ name: "narrator", actor: "Narrator", required: true }: anactorinstead of atype, so an editor shows no form for it.
Exports
What the script returns is the scene’s public surface. Each actor in it resolves, at every frame, to that actor’s state, typed by its class:
const { const card: ResolvedActorOfInstance<Card> | undefinedcard } = const compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ card: Card; }, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
card: Card;
}>
at(15).FrameState<{ card: Card; }>.scene: {
card: ResolvedActorOfInstance<Card> | undefined;
}
scene;
const card: ResolvedActorOfInstance<Card> | undefinedcard?.label: string | undefinedlabel; // "Hello"
const card: ResolvedActorOfInstance<Card> | undefinedcard?.layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
}
layout.x: LayoutValue | undefinedx; // 0
Return what callers need. A lookup by class or id, on
Compiled scenes, is for the actors you did not
return. compiled.scene holds the same exports as the live handles the script returned:
compiled.scene.card names the card in every frame, and page markup binds it
(page bindings).