Length of one iteration in seconds: the largest of every child's
at + totalDuration and every marker's time, or 0 when empty.
Labels do not extend the duration. A label names a time; it is a
bookmark, not content, and addLabel("end", 30) on a two-second timeline
describes a place, not two extra minutes of nothing. Markers do extend it,
because a marker is authored content that must be reachable by playback.
Infinity when a child never ends (tween.repeat(Infinity) or a nested
loop(Infinity)), in which case the timeline never finishes either.
Computed live while "idle" — children may still be configured — and frozen
at Timeline.play, which is also when Timeline.at stops being
legal.
Total played time in seconds, across loop iterations.
Whether playback has reached its end.
A playback fact, not a time fact: Timeline.seek never sets or clears it, exactly as on a tween, so scrubbing a finished timeline inspects values without pretending it is running again.
Zero-based index of the loop iteration currently playing.
Position inside the current iteration, in seconds.
Total iterations set by Timeline.loop; 1 by default.
The Timeline.speed multiplier.
Whether Timeline.reverse put playback into reverse.
Current playback state.
duration × loopCount — the elapsed time at which playback finishes.
Infinity for loop(Infinity), and 0 for an empty timeline (where
0 × Infinity would otherwise be NaN).
Names a time on the axis (§16: labels), for Timeline.at and Timeline.seek.
Names are unique: redefining one throws rather than silently moving every entry that was placed by name.
FourError INVALID_APPLICATION_STATE — called after
Timeline.play; the name is empty or already defined; or the time is
negative or non-finite.
Advances playback by deltaSeconds × speed and writes the resulting pose.
Called by the fixed-step AnimationSystem (plan P4-1); a timeline never
advances itself and never reads a clock (§33).
Markers in the crossed range fire, in time order, once each — including at exactly the end of the range and excluding exactly its start, so consecutive advances neither miss nor repeat one. Crossing an iteration boundary fires that iteration's remaining markers, re-arms every marker, and continues into the next iteration, so a single large delta over a looping timeline fires each marker once per iteration it spans.
A no-op unless the timeline is "running" and unfinished.
Places an entry on the axis at at seconds, or at a label's time when at
is a string (§16: labels).
The entry is one of:
Tween, which must be unplayed — the timeline owns its
playback and plays it at Timeline.play, so the §16 conflict
registry claims happen there, in at() insertion order;options configures it.timeline
.addLabel("impact", 1.2)
.at(0, tween(node.position, { x: 5 }, 0.8))
.at("impact", () => spawnDust(), { replayOnSeek: true });
Optionaloptions: TimelineMarkerOptionsFourError INVALID_APPLICATION_STATE — called after
Timeline.play; the time is negative or non-finite; the label is
unknown; the child has already been played or is already on this timeline;
the entry would nest this timeline inside itself; or options is given for
something that is not a marker.
Number of total iterations: loop(1) is the default single pass,
loop(3) plays the whole timeline three times, loop(Infinity) never ends.
Deliberately not Tween.repeat's "additional cycles" counting: repeat
and loop are different words and a reader who has to guess is a reader who
will guess wrong. Markers re-arm on every wrap, so a marker on a loop(3)
timeline fires three times.
Suspends advancing. A no-op unless the timeline is "running".
Starts playback at local time 0: freezes the duration, sorts the markers,
plays every child in at() insertion order (which is where §16's property
claims and their conflict warnings happen), and writes the pose at time 0.
No marker fires here, not even one at time 0 — the cursor starts below zero
and the first Timeline.advance crosses it. advance(0) is enough.
A no-op on a timeline that is not "idle", exactly as on a tween: restarting
would have to re-capture every child's start values from wherever the
properties happen to sit.
Resumes advancing. A no-op unless the timeline is "paused".
Plays toward time 0 (§16: reversing). reverse(false) restores forward
playback, mirroring Tween.yoyo(enabled)'s shape.
Reverse traverses loop iterations backwards and finishes at elapsed time 0.
Markers never fire while moving backwards: §16 defines a crossing as a
forward event, and clip.ts makes the same call for a reversed range.
Optionalreversed: boolean§16's scrubbing call: Timeline.seek restricted to a time in seconds.
An alias, deliberately — §16 gives seeking and scrubbing identical marker
semantics ("seek and scrubbing suppress them by default"), so a scrub that
behaved differently would be inventing a third policy. It exists because a
scrub bar reads better as scrub(t) than as seek(t), and because taking
only a number keeps a label out of a slider's hands.
Positions the timeline at a local time (or a label's time) and writes the pose for it — §16's scrubbing, and the positioning half of a snapshot restore (§34).
Pure: seeking twice to the same time writes identical values, and seeking backwards is as valid as forwards. Neither Timeline.state nor Timeline.finished changes, so a seek is an evaluation and never a playback decision.
Markers are suppressed, except that a { replayOnSeek: true } marker fires
when the seek crosses it forwards (§16). A backward seek fires nothing and
re-arms every marker it rewound past, so replaying forwards crosses them
again — which is exactly what "restoring a mid-timeline snapshot positions
playback without re-firing markers already crossed" needs.
The time is clamped to [0, duration] — one iteration. Seeking rewinds a
looping timeline to its first iteration: loop iterations are a fact about
playback history, and a position on the axis cannot name one.
Multiplier applied to every Timeline.advance delta, and to the delta a parent timeline maps into this one when nested.
Legal at any time, including mid-playback — §16 lists playback speed as a playback control, not a build-time property. Must be positive and finite; reverse playback is Timeline.reverse, not a negative speed, so that "the advance that finishes playback" stays unambiguous.
Stops playback and every child, releasing their §16 property claims and leaving all animated values exactly where the last write put them.
A stopped timeline writes nothing further: its children ignore writes too,
so even a later Timeline.seek is inert. A no-op while "idle".
§16's timeline.
The two time axes
Timeline.localTime is a position inside one iteration and is what children are mapped from;
elapsedis total played time and is what marker bookkeeping runs in, because it only ever moves forward during forward playback. Looping then needs no special case: a marker at local timemsimply occurs atm,m + d,m + 2d, … and the half-open crossing rule fires each of those exactly once.Child mapping
A child placed at
swith total durationwis evaluated atclamp(localTime - s, 0, w):Outside its window a child holds a boundary pose — its start pose before, its end pose after — which is what makes the whole timeline a pure function of time. The consequence is worth stating: a timeline keeps writing its children's properties for its entire duration, so a finished child still owns its property until the timeline stops. That is the point; anything else would make scrubbing backwards impossible.