/** * VideoRenderer -- fixed-step frame generator for deterministic video rendering. * * Same compositor, same state pipeline -- different clock. The VideoRenderer * drives a FixedStepScheduler at target fps, producing VideoFrameOutput * per frame with the full CompositeState snapshot. * * @module */ import type { Scheduler } from './scheduler.js'; import type { CompositeState, Compositor } from './compositor.js'; import type { Signal } from './signal.js'; import type { Millis } from './brands.js'; /** Configuration for a {@link VideoRenderer}: resolution, target fps, and total duration. */ export interface VideoConfig { readonly fps: number; readonly width: number; readonly height: number; readonly durationMs: Millis; } /** * Single frame yielded by `VideoRenderer.frames()`: frame index, timestamp, * normalized progress, and the {@link CompositeState} snapshot captured at that tick. */ export interface VideoFrameOutput { readonly frame: number; readonly timestamp: number; readonly progress: number; readonly state: CompositeState; } interface VideoRendererShape { readonly config: VideoConfig; readonly totalFrames: number; readonly scheduler: Scheduler.FixedStep; frames(): AsyncGenerator; } /** * Paint one {@link CompositeState} into a solid `width*height*4` RGBA buffer * whose color is a DETERMINISTIC function of the frame's discrete state + css * outputs. * * This is the SINGLE source of truth for "frame state → pixels" shared by BOTH * headless byte-encoders — the `@czap/command` ffmpeg render backend that the * shipping `scene render` CLI drives, and the `@czap/stage` ffmpeg `FrameEncoder`. * Neither owns its own painter, so * the same `CompositeState` always yields byte-identical pixels regardless of * which path encoded it. It is HONEST, not a black stub: distinct frames (the * graph's poses crossing states over the timeline) yield distinct pixels, so the * encoded video genuinely VARIES with the graph state; re-encoding the same * frames yields byte-identical RGBA, so it is content-addressable and replayable. * * The mix is a small FNV-1a over the canonical-ish (key, value) pairs of the * state's `discrete` map and its compiled `css` outputs — the two fields that * carry the per-frame pose. (A richer renderer can paint geometry later; the * `(state, w, h) → RGBA` seam shape is unchanged, so both backends move * together.) * * @param state - the per-frame compositor snapshot (the real pose at this tick). * @param width - frame width in pixels. * @param height - frame height in pixels. * @returns a `width*height*4` RGBA byte buffer (alpha fully opaque). */ export declare function compositeStateToRgba(state: CompositeState, width: number, height: number): Uint8Array; /** * Create a video renderer that produces deterministic frames from a Compositor. * * Each call to `frames()` returns an async generator yielding one * `VideoFrameOutput` per frame at the configured fps/duration. * * When a `signal` is provided it is seeked to each frame's timestamp before * the compositor evaluates, so quantizers that read from that signal advance * deterministically with the render clock. */ declare function _make(config: VideoConfig, compositor: Compositor.Shape, signal?: Signal.Controllable): VideoRendererShape; /** * VideoRenderer — fixed-step frame generator for deterministic offline rendering. * Drives a {@link Compositor} at the configured fps and optionally seeks a * controllable time {@link Signal} so every frame is reproducible. */ export declare const VideoRenderer: { /** Create a renderer bound to the given compositor and optional seekable time signal. */ make: typeof _make; }; export declare namespace VideoRenderer { /** Structural shape of a renderer instance returned by {@link VideoRenderer.make}. */ type Shape = VideoRendererShape; } export {}; //# sourceMappingURL=video.d.ts.map