Architecture Overview
Four diagrams covering the state model, timeline mutations, the playback clock, and the GPU rendering pipeline.
Three-Ring State Model
State lives in three concentric rings. Ring 0 is the single source of truth — a plain TypeScript class with no framework dependencies. Ring 1 mirrors it into Zustand so React can subscribe. Ring 2 holds ephemeral UI state (selection, drag) that never persists to history.
Why keep Ring 0 framework-free? The engine can run in Node, Web Workers, or WASM with no changes. Tests import it directly without mounting any React tree.
Timeline Engine
All project mutations — adding clips, trimming, splitting, undo/redo — go through one method: commit(). Immer applies the draft, records a history entry, and emits a 'change' event. Zustand listeners pick it up and React re-renders only the changed slices.
Single mutation funnel. There is no direct way to mutate the Project from outside the engine. Every edit automatically lands in the undo history and notifies all subscribers.
Playback Engine & Clock
The clock uses an anchor-and-integrate model rather than a counter. On every play() or seek(), it records the current frame and wall-clock time. Each RAF tick derives the current frame from that anchor — no accumulated drift, and rate changes are instantaneous.
Tab visibility. When the tab is hidden, the engine freezes the anchor frame so playback does not fast-forward on resume. Audio syncs to the same anchor so there is no A/V drift.
Rendering Engine
The renderer receives a Scene — a plain list of active layers — and draws textured quads to a WebGL2 canvas. It knows nothing about the project, history, or React. Video frames are decoded asynchronously into a ring-buffer cache; the render call itself is synchronous and stays under 1 ms on the main thread.
Cache miss graceful degradation. On a seek or cold start the FrameCache may not have the target frame yet. The renderer shows the last cached frame while the async decoder catches up — no blank frames, no stalls.
Layer Reference
Six vertical layers. Each layer only talks to the one directly below it — the React UI never touches WebGL; the renderer never imports React.
React components. All reads go through Zustand hooks — no direct engine calls from render.
Thin reactive bridges. Populated by engine event subscribers — not owned by any component.
Framework-agnostic TypeScript. All mutations funnel through engine.commit(). No React imports.
A pure function. Given a frame number and the immutable Project, returns the exact Scene to render. Deterministic, side-effect-free, runnable in tests or workers.
Reads Scene, writes pixels. Knows nothing about the project, history, or React.
Async decode pipeline feeding ImageBitmaps to the GPU textures and PCM chunks to WebAudio.
