Scene orchestration
Recording authored intent — spawning actors, consuming time, and composing concurrent work.
This layer is what runs during compile. Everything here writes to the timeline record; nothing here renders.
createScene
Compiles a generator into a CompiledScene. The generator receives a CompileContext
(ctx) and returns typed scene exports.
const compiled = createScene(
function* (ctx) {
const card = ctx.spawn(Card, { id: "card" });
yield* card.appearance.opacity.tween(1, 0.3);
yield* ctx.wait(1);
yield* card.layout.x.tween(200, 0.5);
return { card };
},
{ fps: 60 },
);fps defaults to 30. It is the only place authored seconds become frames, so it belongs
with the scene rather than with the renderer.
ctx
// Spawn an actor instance
ctx.spawn(Card, { id: "card" });
// Wait in authored time (seconds)
yield* ctx.wait(1.5);
// Run generators concurrently — finishes when the longest finishes
yield* ctx.all(
card.appearance.opacity.tween(1, 0.3),
label.appearance.opacity.tween(1, 0.3),
);
// Run with a stagger delay between each start
yield* ctx.sequence(0.05, ...items.map((i) => i.enter()));
// Offset one generator without waiting for it first
yield* ctx.delay(0.2, card.enter());
// Scoped sub-context — the cursor resets to the scope start when it ends
yield* ctx.scope(function* (scope) {
yield* scope.wait(0.5);
});ctx.cursor and ctx.cursorSeconds read the current authoring position, in frames and in
seconds respectively.
Five bars raised with ctx.all, dropped, then raised again with ctx.sequence(0.08, …).
Scrub to the second rise to see the offset: all starts everything at the cursor,
sequence offsets each start by the delay.
ctx.all(...) — actx.all(...) — bctx.sequence(0.05, …) — 1ctx.sequence(0.05, …) — 2ctx.sequence(0.05, …) — 3all starts everything at the current cursor. sequence offsets each start by the
stagger and finishes when the last one does.
actor.runtime
The same primitives are available inside actor methods as this.runtime, plus two that
only make sense from inside an actor.
class Panel extends LayoutActor {
*enter() {
yield* this.runtime.all(this.header.enter(), this.body.enter());
}
*startPolling() {
// fire-and-forget: does not consume authored time
this.runtime.fork(function* (this: Panel) {
yield* this.refresh();
});
// repeats until the actor is despawned
this.runtime.loop(function* (this: Panel) {
yield* this.pulse();
yield* this.runtime.wait(2);
});
}
}| Helper | Consumes authored time |
|---|---|
all, sequence, wait, scope | yes |
fork, loop | no |
createComposition
Like createScene, plus a ctx.stage surface for composition-level concerns. It returns a
CompiledComposition, which extends CompiledScene — everything on the
compiled scene page applies.
const comp = createComposition(function* (ctx) {
const slide = ctx.spawn(Slide);
yield* ctx.wait(3);
return { slide };
});Scene exports
Return the actors callers need. This is the supported way to reach into a scene: typed, refactor-safe, and free of string lookups.
return { card, caption: card.caption };const { card, caption } = compiled.at(frame).scene;Use findActor / getActor only for actors
that were not exported.