Determinism
This content is for v0.4. Switch to the latest version for up-to-date documentation.
alpha2’s promise is that one project renders the same on every target, because every target runs the same rendering code. This page says precisely what is identical, what is only close, and what lies outside the engine. After reading it you know what you can rely on when you compare a browser preview with a server export.
What is the same everywhere
Section titled “What is the same everywhere”These hold on every target, by construction:
- Time is exact. Times are integer ticks, and the frame grid is computed in integers, so every host picks the same frame times and the same source times for a project. See Time and ticks.
- The document is read the same way. Every target loads a project with the engine’s own model code, so a document one target accepts, the others accept too, with the same meaning. (
validateProjectin TypeScript mirrors those checks so you can run them before loading.) - The rendering code is the same. The browser, the native host and the Rust crate run the same engine, with the same built-in effects and transitions.
What the test suite checks bit for bit
Section titled “What the test suite checks bit for bit”| Guarantee | Checked by |
|---|---|
| The CPU renderer draws the same bytes natively, as WebAssembly under Node.js, and through the packaged browser binding, on every machine and for any number of threads. | scripts/test-render-determinism.mjs, with hashes pinned in tests/render_determinism |
| The audio mix is bit-identical natively and as WebAssembly. | scripts/test-audio-determinism.mjs, with hashes pinned in tests/audio_determinism |
| Text rasterizes to identical pixels in WebAssembly and natively, given the same fonts. | scripts/test-wasm-parity.mjs |
alpha2-host writes the same bytes whether it runs from the CLI, through serve, through the Python client, or with more decode concurrency. |
scripts/test-host-cli-parity.mjs |
Layout measurement (measureLayout in the browser, measure_layout natively) also answers the same, to the bit.
In the browser, WebEffectRegistry.createCpu gives you that CPU renderer, which draws exactly what the native CPU renderer draws. On a native host, --renderer cpu (or renderer: "cpu") does.
What is close, not identical
Section titled “What is close, not identical”GPU output varies slightly between devices. Fused multiply-add contraction, fract and subtexel rounding differ across GPUs, by up to 7 codes on an edge. The cross-device tests allow 4 codes of difference, or 8 for a handful of isolated samples, and the repository keeps per-backend reference images (Direct3D 12, Metal, GL, lavapipe and others). The packaged browser engine is checked against the native host the same way: at most 4 I420 codes apart, with a mean error of at most 0.5.
The CPU renderer and the GPU are close, not identical. The CPU renderer draws the same render graph as the GPU, pass for pass, and its parity tests compare every CPU pass with the GPU’s within tolerance classes.
So if you need byte-identical frames across machines, render with the CPU renderer. If you render on GPUs, expect frames that look the same and differ by a few codes.
What lies outside the engine
Section titled “What lies outside the engine”Fonts. The engine draws text only with the fonts its host registers, in registration order. The same font files give the same pixels. Different fonts, or a different fallback order, can give different text. Give the browser and the native host the same files.
Decoders. The engine renders from the frames it is given. In the browser your app decodes them (with WebCodecs, for example), natively FFmpeg does, and in Rust your program does. Rendering is deterministic given the frames; what a decoder produces is up to the decoder.
Encoders. The rendered frames are deterministic; the encoded file depends on the encoder. Hardware encoders and the browser’s WebCodecs are not covered by the engine’s guarantees. The OpenH264 encoder alpha2-host can load picks its slice and thread count from the frame height alone, so its exports are the same bytes on every machine.
Runtime WGSL effects. An effect you register at run time runs only on a GPU, so it is subject to GPU variance and has no CPU renderer output to compare against.
Preview quality and export quality
Section titled “Preview quality and export quality”A preview and an export of the same time can differ on purpose. An export renders offscreen at export quality, on the project’s exact frame grid. In the Rust crate, QualityProfile::preview() renders at half resolution with a smaller filter budget, while QualityProfile::export() renders at full resolution. To check what an export will contain, render a still: still in @alpha2-sh/node and Python draws the frame an export draws at that time, as a PNG.
