From 5ffc3effa7275a55865159ea39c033f3398cd132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Tue, 5 May 2026 09:11:26 -0700 Subject: [PATCH] fix(engine): warm-up seek after session init to prevent parallel worker flash frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When parallel workers each load a page independently, the first frame captured by a worker could show an uninitialized WebGL render state — typically a single flash frame with wrong 3D object orientation or missing MeshPhysicalMaterial transmission effects. This happened because the Three.js transmission pipeline needs at least one prior render to populate its internal render targets. Fix: after session initialization (page ready, fonts loaded, videos ready), perform three warm-up seeks via the __hf protocol before marking the session as initialized. The seeks prime the page's render pipeline at t=0, t=duration/2, and t=0 again, ensuring WebGL buffers and transmission render targets are fully initialized before real frame capture begins. Affects both screenshot and beginFrame capture modes. --- packages/engine/src/services/frameCapture.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/engine/src/services/frameCapture.ts b/packages/engine/src/services/frameCapture.ts index d19e8cf6f..3925a5a20 100644 --- a/packages/engine/src/services/frameCapture.ts +++ b/packages/engine/src/services/frameCapture.ts @@ -426,6 +426,17 @@ export async function initializeSession(session: CaptureSession): Promise await initTransparentBackground(session.page); } + // Warm-up seek: prime the page's render pipeline (WebGL transmission + // buffers, canvas compositing, etc.) before real frame capture begins. + // Without this, parallel workers whose first assigned frame is mid- + // animation can capture an uninitialized render state — typically a + // single flash frame with wrong 3D object orientation or missing + // transmission effects. Two seeks at different times ensure both the + // initial state and mid-animation state are primed. + await page.evaluate(`window.__hf?.seek?.(0)`); + await page.evaluate(`window.__hf?.seek?.(window.__hf?.duration * 0.5 || 0)`); + await page.evaluate(`window.__hf?.seek?.(0)`); + session.isInitialized = true; return; } @@ -522,6 +533,11 @@ export async function initializeSession(session: CaptureSession): Promise await initTransparentBackground(session.page); } + // Warm-up seek (same rationale as the screenshot-mode branch above). + await page.evaluate(`window.__hf?.seek?.(0)`); + await page.evaluate(`window.__hf?.seek?.(window.__hf?.duration * 0.5 || 0)`); + await page.evaluate(`window.__hf?.seek?.(0)`); + session.isInitialized = true; }