Locked decisions. Change only with a written reason here.
- Seek, not playback — composition = pure function of time
t; any frame, any order. - Seconds, not frames — fps is a render parameter.
- Duration static — declared in comp header, immutable at render time.
- Determinism by construction — renderer owns the VM: clock stubbed, RNG seeded, zero I/O during render. All network/asset work happens in the resolve phase.
- Audio never touches the engine — resolved clips + volume envelopes → ffmpeg
filter_complexat encode; mux with-c copy.
- ellua host: a source-pinned LÖVE fork at
vendor/ellua-love, built as theellua-lovesidecar. The RustelluaCLI launches it withELLUA_HEADLESS=1forrender,hash,lint, andcheck. macOS Metal uses a virtual main backbuffer with no SDL window or drawable presentation; Vulkan must gain the same surface-free virtual-backbuffer implementation before Linux and Windows release bundles ship.previewis the only interactive windowed mode. Customlove.runevaluates fixed times, renders a canvas, reads back ordered RGBA frames, then writes those frames to the bundled ffmpeg sidecar. Golden hashes are scoped to an Ellua renderer build and platform. - web host (preview only): Lua 5.4 via wasmoon (official Lua compiled to
WebAssembly) plus a Canvas2D painter. Same host-free
lib/elluacompositions; seek is stillevaluate(t). This is not the encode path — ffmpeg and native helpers stay on ellua-love. Reason: shipping the Metal/LuaJIT fork through Emscripten would throw away FFI dylibs and the virtual backbuffer; the painter contract already exists so a second host can be small. - A Rust renderer is not a planned replacement host. Rust owns distribution and the native helpers; the LÖVE-compatible graphics implementation stays in ellua-love.
- Authoring core = Lua-native scene graph + signals + coroutine timeline
(
waitUntilevents). React model (react-elluavia react-lua/react-luau host config) = later optional skin, never the core. Optionale.comp { inputs = { bg = { kind = "video" } } }lets a host bind media without rewriting Lua:scene(s)readss.input.bg(a string path). Studio writes a sibling<comp>.inputs.json; the CLI accepts--inputs FILE.jsonand repeatable--input KEY=PATH. Merge is key-wise: defaults < sibling JSON <--inputsfile <--inputflags. Hardcodedsrc=remains valid. Missing required inputs error at compile (ellua: input "bg" is not bound). The host injects the resolved string table intoComp:compile;lib/elluastays disk-free.
Bundled ffmpeg subprocess only — never linked, never GStreamer. Three invocations per render:
raw-RGBA video pass → filter_complex audio mix → -c copy mux. ffprobe at
resolve. Pin and ship the ffmpeg build inside each Ellua distribution.
Formats: mp4/h264 default; webm/vp9 + mov/prores4444 for alpha; png-sequence; gif.
Lib-first Rust crate, zero IPC assumptions in core.
- rsmpeg + vendored FFmpeg 8 shared libs (only stack covering h264/h265/vp9/av1/prores across mp4/mov/webm/mkv).
- BestSource-style verified index: first-open linear pass → frame→(PTS, keyframe, position) table (+ opt-in per-frame 8-byte hash, "paranoid" mode); index cached on disk. Seek = keyframe-back + decode-forward + verify landed PTS; anomaly → linear fallback from known-good point. FFI-linking BestSource itself is an approved alternative to reimplementation.
- Hw decode via FFmpeg hwaccel layer (one code path): VideoToolbox on macOS (ProRes hw on M1 Pro+), NVDEC on 4070 (sessions unlimited; 10-bit h264 = sw fallback), VAAPI on Linux, transparent sw fallback.
- Determinism: decoded YUV is bit-exact by spec across conformant decoders (incl. hw). Only post-decode conversion diverges → ONE pinned YUV→RGB path we own; fixed-point/integer path when cross-machine hash-exactness required. Never hw scalers/CSC on the deterministic path.
- Cache: per-stream sequential decode state machine (forward fast path within a render chunk), small per-stream NV12 LRU (never RGBA in cache), global cap 256MB–1GB, mpv-style packet cache for back-seeks as needed.
- Linkage — one crate, three consumers (lib-first, zero IPC assumptions in core):
- love host (primary): cdylib with C ABI, loaded via LuaJIT FFI (
ffi.load) — in-process, no IPC; decoded frames land in memory LuaJIT wraps as ImageData. (mlua is the inverse direction — Rust hosting Lua — and applies only to the rust host; it cannot be injected into love, which already owns its LuaJIT VM.) - rust host: plain rlib in-process; mlua embeds LuaJIT/Luau to run comps.
- isolation fallback: daemon + msgpack unix socket + shm ring buffer (crash isolation for hostile files; shm sustains multiple 4K60 streams). macOS IOSurface zero-copy = profiling-gated later optimization.
- love host (primary): cdylib with C ABI, loaded via LuaJIT FFI (
- Rejected: GStreamer (random access fights pipeline model), libmpv render API (unsuited by design), vk-video (unmaintained), pure-Rust hevc/prores (doesn't exist).
Mac-first stack (locked 2026-08-01):
| role | model | note |
|---|---|---|
| frames / text-query | MobileCLIP2-S2 | CoreML/ANE, single-digit ms/frame |
| spatial / patch lint | C-RADIOv3-B | commercial-OK license; DINOv3 runs hot locally |
| temporal | X-CLIP-B | vanilla transformers; V-JEPA2 too heavy locally |
| audio | CLAP | same stack as 11l media-DNA pipeline |
Probe → embedding sidecar (stride frames + patch grids + audio windows + derived:
motion energy, cut/freeze/black via embedding deltas) → ellua query timestamped
hits; comp asserts (assert.visible("logo", 2, 4)) compile to calibrated
probability thresholds at check time. License tripwires: VideoCLIP-XL (NC),
RADIOv2.5/E-RADIO (NC — C-RADIO only).
Pre-render, network allowed, content-hash cached. ElevenLabs first-class: TTS
(eleven_v3), SFX, Music → clip durations known before render; Scribe word
timestamps → waitUntil('word:…') sync + karaoke captions for free.