Skip to content

Timeline & property tracks

A <timeline> is a bounded, non-interactive choreography unit with its own local clock. Where consecutive non-wait directives give you loose concurrency, a timeline gives you temporal scoping: several tracks each hold time-positioned staging clips, all tracks play concurrently as the playhead advances, and the whole block blocks the following content until it completes.

<timeline duration="1.4">
<track subject="camera">
::camera{focus="marina" framing="tight" duration="0.4"}
::camera{move="shake" duration="0.3" at="0.5"}
</track>
<track channel="fg">
::cg{assetId="CUT.scenarios.marina.s01ep02.01" at="0.5"}
::cg{assetId="CUT.scenarios.marina.s01ep02.01" display="hide" at="1.1"}
</track>
<track channel="vfx">
::vfx{type="whiteOut" transition="flash" at="0.5"}
</track>
<track channel="sfx">
::sfx{sound="finger_beam_flash" assetId="PLACEHOLDER_finger_beam_flash" at="0.5"}
</track>
</timeline>

(From docs/examples/marina-s01ep02.lute — the “finger-beam performance” as one four-track beat.)

A <track> is identified by its key, given one of three ways:

  • subject="camera" — a subject track (a whole staged entity, e.g. the camera or a character).
  • channel="fg" — a channel track (a staging channel such as foreground CGs, vfx, sfx, music).
  • subject="marina" property="pos" — a property track: split one subject across property-scoped concurrent tracks.

Each track key must be distinct — one writer per key. Two subject="camera" tracks would silently fight and are rejected; a track with no usable key is E-TRACK-KEY.

Property tracks let one subject animate on several properties at once. Here marina is driven on her position and her opacity simultaneously, alongside an independent camera track:

<timeline duration="1.2">
<track subject="marina" property="pos">
::actor{character="marina" anchor="left" action="slideInLeft"}
</track>
<track subject="marina" property="opacity">
::actor{character="marina" action="fadeInUp"}
</track>
<track subject="camera">
::camera{focus="marina" framing="close" duration="0.6"}
::camera{move="shake" duration="0.3" at="0.7"}
</track>
</timeline>

(From docs/examples/property-tracks.lute.)

The keys marina.pos, marina.opacity, and camera are all distinct, so the checker accepts them.

Within a <timeline> the local clock starts at 0. For each track:

  • a clip’s at is an absolute time on that clock — never a relative nudge;
  • an omitted at starts the clip after the previous clip in that track (the first clip starts at 0.0);
  • clips lower to the same flat command records, sorted by resolved time, with a final barrier at duration (or the maximum resolved end).

A <timeline duration="1.2"> with no tracks at all is legal, and it is the only beat primitive the language has: it lowers to that final barrier and nothing else, holding the content after it for its duration. Use it when the pause is the point.

<timeline duration="1.2">
</timeline>
@narrator: after the beat.
{ "kind": "barrier", "family": "control", "position": "001-0100", "timeline": 0, "at": 1.2 }

Tracks are staging-only and non-interactive: they hold :: staging leaves (plus ::set for state marks). No dialogue, prose, <choice>, <branch>, or <match> may appear inside — those would make the beat reader-paced rather than clock-paced. Timelines do not nest, and are not admitted in quest documents. The checker warns when a timeline grows unwieldy (past roughly 8 tracks, 12 clips per track, or 40 clips total).