D691 — The growth video is rendered offline, at 60 fps, in the background¶
Decision. Export video no longer records the visible replay. It renders
the replay on a second LivingScene in a detached container, advanced by a
virtual clock of exactly 1000/60 ms a frame and drawn at twice the export size;
each frame is downsampled into an export-size 2D canvas, overlaid, and encoded
with WebCodecs (VideoEncoder) into an MP4 (H.264 High 4.2) or, where H.264 is
not encoded, a WebM (VP9), muxed by mediabunny. The clip opens on a 1 s
logo card, so frame 0 is the thumbnail; the replay keeps its own pace
(growthPace) in video time. It supersedes the recording mechanism of
D677; the
dialog, aspects, overlay and file names stay.
Why. Real-time capture ties the clip to the display: it stops in a hidden
tab, runs at whatever rate the page paints, and leaves MediaRecorder the
choice of rate control and of the first frame (the videos showed black as a
share preview). An offline render has none of those limits, and costs the user
nothing to watch: the visible scene and camera are never touched, so the
replay, the graph and the rest of the Atlas stay usable while it runs and a
switch of KB or Map does not end it (the export owns its scene and its data).
Alternatives rejected.
- Keeping
MediaRecorder: real-time only, no control over rate or first frame. - ffmpeg.wasm: about 30 MB for a share button (as in D677).
- A server-side encoder: the Atlas is read-only and the server has none.
- Letting
mediabunny's ownCanvasSourcedrive the encoder: it hides the encoder queue, which is the one brake the render loop needs. - Pacing the loop with
setTimeout: timers are throttled to once a second in a hidden tab; the loop awaitsdequeueevents and aMessageChannelmessage (never throttled) instead.
Consequences.
LivingScenereads time only throughclock(). A scene built with theoffscreenoption has no frame loop,ResizeObserver,visibilitychangelistener or input, a fixed size, and no burst or settle camera;renderFrameadvances it.beginCapture/endCaptureare gone.- At most one offline scene exists at a time (a second export is refused), and
it is disposed with its WebGL context (
forceContextLoss) on done, cancel, error and unmount: a test holds it. - The 2D canvas copy of the WebGL canvas must happen in the task that rendered it (the renderer keeps no drawing buffer); only after that may the loop await.
- Frames are rendered at 2x the export size: a 1920x1080 export draws 3840x2160, so the GPU must allow it. Artifacts (diamonds) are not drawn in the video, which is of concepts only.
- Chromium without H.264 (and Firefox) gets WebM; the browser suite asserts the extension the browser reports, decodes the downloaded file in a page, and checks the opening card, a mid-replay frame, the duration and the 1/60 s frame step, with the page hidden halfway through the export.