◆ Flux

The TRANSITION plane

The transition plane is the fourth and smallest of Flux’s planes: it interpolates the rendering between two states the analysis plane has already computed, and it can do nothing else. This page specifies the five powers a script has here, the descriptor every transition compiles to, the settle-versus-trajectory rule that keeps animation out of the oracle, and the boundary with the native morph controller. The signal algebra a transition borrows is specified in Canvas; the compositor behaviour, the two strata and the transition invariants live in display.

That “already computed” is the entire safety argument. If both endpoints are values the analysis plane produced, interpolating between them cannot produce a new value — so a transition is cosmetic by definition, and no animation, however elaborate, can repaint a chart. The plane exists because the alternative is worse: animation bolted onto a rendering layer ends up reading state it should not read and re-entering computations it should not re-enter. Giving it its own plane, with its own clock and its own rule, keeps the guarantee where it belongs.

New here? Start with Guide §9 — A scene that moves →

What a script can do here

Five powers, and no more:

FLUX
// ① parameterize a built-in morph
on switch(asset) -> morph chart over 500ms { ease: inOutCubic ; stagger: 0.3 ; surplus: collapse }

// ② carry the transition of a custom representation (the `morph:` hook of its descriptor — a later rollout)

// ③ trigger a bounded effect on a signal
on close cross_up ema(close, 50) -> flash

// ④ animate the view
on click -> focus(view, at: (bar.i, close), zoom: 2.0, over: 600ms, ease: outBack(1.2))

// ⑤ replay history from the bar where a signal fired
replay from close cross_up ema(close, 200) over 8s

switch(asset) is a host event, not an analysis symbol: the current asset is a fact the host owns, and the firewall forbids analysis from reading it. It is delivered like any other presentation edge (hover, click, enter), which is why a transition can respond to it while an indicator cannot.

Power ② — the per-representation morph: hook — is designed, but not yet an author key: the morph controller is single-type (candle), and the descriptor the hook would hang from is reified in the same rollout. Until then a custom representation animates through the built-in morph (①), which is the path every built-in representation already takes.

replay from takes a signal, not a bar index. The replay begins at the bar where the condition fires and runs forward over the declared duration, which is what lets you address a replay by what happened rather than by how far back it was. The start point is therefore an analysis value the oracle already contains — the transition plane discovers it, it never computes it.

The settle is in the oracle; the trajectory is not

This is the distinction that makes the plane work, and it is worth being exact about:

In the byte-identity oracle? Why
the settle value — where the transition lands yes it is analysis data (or a Model value); it is what the scene is once the animation ends
the trajectory — how it gets there, t ∈ (0,1) no it is per-frame, device-dependent, and observable by nobody but the eye

One transition between two analysis-computed endpoints: both endpoint states and the journaled terminal edge sit inside the byte-identity oracle; the interpolated trajectory t in (0,1) arcs outside it Figure — the endpoints and the terminal edge (Done, at a deterministic rank) live inside the byte-identity oracle and the journal; the trajectory t ∈ (0,1) is drawn outside it — per-frame, device-dependent, the one thing the plane withholds.

Two consequences follow directly.

prefers-reduced-motion changes nothing that matters. The host applies it at the compositor: it jumps to the settle state. Since the settle is in the oracle and the trajectory is not, the verdict — the numbers, the golden, the replay — is identical either way. The compositor rule is specified once in display; here we state only why the plane’s guarantee is indifferent to it.

A transition never leaks its progress into a decision. The plane exposes only the terminal edge (Done), never the continuous progress; and that edge is scheduled at a deterministic journal rank derived from the declared duration, not at the wall-clock moment the animation happened to finish. Two clients, one animating and one with motion reduced, journal the same edge at the same rank — same numbers, same scene, on both machines. This is the [TransSettle] invariant, whose canonical statement lives in display.

Why the progress is withheld. If a script could read t = 0.63 mid-animation and store it in a model, then the model — and any verdict computed from it — would depend on the frame rate, the device, and the GPU’s mood. Withholding the trajectory is not a limitation of the animation; it is what makes the animation free.

The transition descriptor

Built-ins and scripts converge on one descriptor, so a custom representation animates exactly the way a candle does:

Field Meaning
durationMs how long
easing the curve — a value of the ease sort, opaque and host-vetted
wave, staggerSpread the shape of the stagger across elements
wickLead the lead-in of the thin parts, before the bodies
surplusPolicy collapse | spawn | hold — what happens to elements that have no counterpart on the other side
chromeFadeFrac, holdDeadlineMs, flipTiming the cosmetic timings around the morph

Per-call overrides (over D, stagger, surplus:) refine it at the point of use.

ease is a sort, not a variant — you can pass inOutCubic or cubicBezier(a,b,c,d) around, but you cannot match on a curve and take it apart. That opacity is deliberate: a decomposable curve would let a script re-parameterize time in a data-dependent way, and the boundedness of the animation would go with it.

The boundary with the native engine

The heavy per-candle morph stays native — it is a hot path, and it is not the language’s job to re-implement it. Flux orchestrates: it fills in the descriptor once, and the native controller runs it. The descriptor is a compilation target; how the host receives and drives it is specified in Host integration.

That split is why there is no performance cliff when you animate: the script does not run per frame, and the thing that does is the code that was already there.

What a transition may not do

May May not
interpolate the rendering between two computed states compute a state
read analysis values (to know where to land) write an analysis value
respond to a host edge (switch(asset), a click) read the wall clock into a Model
expose its terminal edge expose its progress

See also