/** * TransitionProgram — the explicit multi-transition algebra (#141). * * Before W9, `keyframesForRouting` collapsed `seq` / `par` / `choice_then` to the * SAME two endpoint frames (a routing LABEL, not an algebra). A single `EdgeType` * on one {@link TransitionNode} cannot express "A THEN B" vs "A WITH B" vs "A OR B" * — the composition is a TREE over transitions, not a flag on one. * * `TransitionProgram` is that tree. It composes {@link TransitionNode}s with REAL * duration composition and branch selection, then lowers to: * - a deterministic `[0,1]` **timeline** of per-transition windows * ({@link lowerTransitionProgram}) — seq total is `Σ`, par total is `max`, * choice executes EXACTLY ONE branch; * - a {@link LoweredMotionPlan} ({@link interpretProgram}) whose `css.keyframes` * are REAL multi-offset stops (not the two-endpoint collapse) and whose * `runtime` plan carries per-window sub-samplers (each with its own W8 easing * descriptor), driven through the `client:motion` floor. * * `EdgeType` (`plan.ts`) stays the edge flavor BETWEEN adjacent transitions; the * program is the composition tree over it. Ordering reuses the existing Plan DAG * (`Plan.make`/`seq`/`par` + `topoSort`) as the substrate — acyclicity + * a deterministic topological order come for free, so window offsets are * reproducible. * * @module */ import type { ContentAddress, SignalInput } from './brands.js'; import type { DocumentGraph } from './document-graph.js'; import type { DiagnosticPayload } from './diagnostics.js'; import { type TypedValue } from './interpolate.js'; import { type LoweredMotionPlan, type RuntimeWritePlan, type RuntimeWriteWindow } from './interpret-transition.js'; /** * A predicate over a named signal's live value that selects a `choice` branch. * `op` mirrors the comparison vocabulary; `between` is the half-open `[lo, hi)` * band. Evaluated against {@link ProgramEnv} at lowering time so the selected * branch is a stable, auditable receipt. */ export type BranchCondition = { readonly op: 'lt' | 'lte' | 'gt' | 'gte' | 'eq'; readonly value: number; } | { readonly op: 'between'; readonly lo: number; readonly hi: number; }; /** One `choice` arm: a condition over a named signal source guarding a sub-program. */ export interface TransitionBranch { readonly when: BranchCondition; readonly source: SignalInput; readonly body: TransitionProgram; } /** * The composition tree over {@link TransitionNode}s. * * - `step` — one transition (Pose→Pose), optionally preceded by `delayMs` dead time. * - `seq` — deterministic duration composition: total is `Σ` children (+ delays), * each child mapped to a disjoint sub-window. * - `par` — total is the `max` child duration; children share the window, each * scaled to its own duration; a short child holds its final pose after completing. * - `choice` — EXACTLY ONE branch executes, selected by {@link BranchCondition} * over its named signal source; unchosen branches never write. */ export type TransitionProgram = { readonly kind: 'step'; readonly transitionId: ContentAddress; readonly delayMs?: number; } | { readonly kind: 'seq'; readonly children: readonly TransitionProgram[]; } | { readonly kind: 'par'; readonly children: readonly TransitionProgram[]; } | { readonly kind: 'choice'; readonly branches: readonly TransitionBranch[]; readonly otherwise?: TransitionProgram; }; /** Resolved signal values a `choice` selects against (e.g. `{ 'viewport.width': 1024 }`). */ export interface ProgramEnv { readonly signals: Readonly>; } /** The auditable record of which `choice` arm a window came from. */ export interface BranchGuard { readonly source: string; readonly when?: BranchCondition; readonly branchId: string; } /** One entry in a lowered program timeline: a transition mapped to its `[0,1]` window. */ export interface ProgramTimelineEntry { readonly transitionId: ContentAddress; /** Global normalized window start in `[0,1]`. */ readonly windowStart: number; /** Global normalized window end in `[0,1]`. */ readonly windowEnd: number; /** Present iff this entry was selected from a `choice` — the audit receipt. */ readonly branchGuard?: BranchGuard; } /** Result of {@link lowerTransitionProgram}: the composed duration + ordered windows. */ export interface LoweredProgramTimeline { /** Total composed duration in ms (seq: `Σ`; par: `max`; choice: selected branch). */ readonly totalMs: number; readonly entries: readonly ProgramTimelineEntry[]; /** The `branchId` of every executed `choice` arm, in traversal order (auditable). */ readonly selectedBranchIds: readonly string[]; readonly diagnostics: readonly DiagnosticPayload[]; } /** * Lower a {@link TransitionProgram} to a deterministic `[0,1]` timeline of * per-transition windows. * * The window MATH is the algebra, pinned as law: `seq` total is `Σ` child * durations (+ delays) with disjoint contiguous windows; `par` total is the `max` * child duration with children sharing `[0,1]`, each scaled to its own duration (a * shorter child ends before `1` and holds); `choice` lays out ONLY the branch * selected by {@link BranchCondition} over `env`, recording its `branchId`. * Ordering runs through `Plan.topoSort` for deterministic offsets. */ export declare function lowerTransitionProgram(graph: DocumentGraph, program: TransitionProgram, env?: ProgramEnv): LoweredProgramTimeline; /** * Interpret a {@link TransitionProgram} into a {@link LoweredMotionPlan} whose * `css.keyframes` are REAL multi-offset stops and whose `runtime.windows` are * per-transition sub-samplers (each carrying its own easing). This is the program * analogue of {@link interpretTransition} — the single-step reader stays the leaf; * `interpretProgram` walks the composition tree over it. * * `env` resolves `choice` branches; the selected `branchId`s ride the diagnostics * as an auditable receipt. Under reduced-motion the composite `runtime.toState` + * the `t=1` window sample settle to the terminal step's `toPose`. */ export declare function interpretProgram(graph: DocumentGraph, program: TransitionProgram, env?: ProgramEnv): LoweredMotionPlan; /** One sampled leaf: a `cssVar` and its interpolated {@link TypedValue} at a given `t`. */ export interface ProgramSample { readonly cssVar: string; readonly value: TypedValue; } /** * The per-window runtime sub-sampler — the READER of `RuntimeWritePlan.windows` * (Law 16). At global `t`, each window is sampled at its LOCAL eased progress, * interpolated `from`→`to`, last-window-wins. Delegates to the shared * `walkWindows` kernel so a multi-step chain and the CSS `@keyframes` are one * code path. Prefer `sampleProgram`, which also handles a flat single-tween plan. */ export declare function sampleProgramWindows(windows: readonly RuntimeWriteWindow[], t: number): readonly ProgramSample[]; /** * `sampleProgram` — THE shared motion kernel every non-CSS target samples (#130, Law 4). * * Given a lowered {@link RuntimeWritePlan} and a normalized time `t ∈ [0,1]`, returns the * typed leaf value of every animated `cssVar`. It unifies BOTH lowering shapes behind one * reader: * - a composed {@link TransitionProgram} (`plan.windows` present) → the per-window * sub-samplers ({@link sampleProgramWindows}); * - a single-step plan (`interpretTransition`, no windows) → one implicit window `[0,1]` * carrying `plan.easing` over `plan.properties`. * * The browser runtime floor (`writeContinuousMap`), the scene / stage / remotion frame * samplers, and the worker off-thread sampler ALL call this one function; the declarative * CSS `@keyframes` are generated from the SAME `walkWindows` kernel (see * `buildKeyframes`). The differential oracle (`motion-parity.test.ts`) is the * reader that pins every target to this reference. */ export declare function sampleProgram(plan: RuntimeWritePlan, t: number): readonly ProgramSample[]; /** The uniform payload a `sampleProgram` sample projects to: formatted CSS + GPU-bound WGSL scalars. */ export interface ProgramUniforms { /** Every animated `cssVar` formatted for a CSS custom-property / style write. */ readonly css: Record; /** GPU-bound numeric props (kind `number`/`opacity`) keyed by their WGSL struct field. */ readonly wgsl: Record; } /** * Project a `sampleProgram` sample into the `czap:uniform-update` payload — the ONE * uniform-building path shared by the `client:motion` floor (`writeContinuousMap`, which * adds the DOM writes) and the `@czap/worker` off-thread sampler (which posts it across the * worker boundary). Keeping the formatting here (not forked per host) is Law 4: the leaf a * browser writes and the leaf a worker posts are byte-identical because they format ONE * kernel sample. */ export declare function sampleProgramUniforms(plan: RuntimeWritePlan, t: number): ProgramUniforms; //# sourceMappingURL=transition-program.d.ts.map