Fast-capture: fallback-ratio guard for #3423 x #3429 seam (100%-fallback renders pass artifact validation)

Author: miga-heygenCreated Aug 25, 2026Updated Sep 15, 2026

Context

PR #3480 (branch fix/draw-element-canvas-fallback, closes #3423) added a per-frame screenshot fallback for the drawElement fast-capture path: when drawElementImage throws a recoverable error (canvas not initialized, or the native "No cached paint record" InvalidStateError), the affected frame falls back to Page.captureScreenshot instead of hard-failing the render.

Review feedback on #3480 (from tai) flagged a seam with PR #3429's artifact validation (ArtifactTransaction.validate() / packages/producer/src/services/render/artifactTransaction.ts):

If a composition is 100% broken and every frame falls back to screenshot capture, the render passes #3429's artifact validation (correct frame count, correct duration) but ships N wrong-pixel frames as "complete."

To be precise about the actual risk: the screenshot fallback itself produces correct pixels (it's the platform's normal, well-tested capture path) — the real risk is a silent, total loss of the fast-capture speedup (and by extension a masked persistent bug, e.g. a canvas-injection regression) going completely unnoticed because the render still "succeeds" with the right frame count/duration. Today nothing distinguishes "recovered a handful of edge-case frames" from "drawElement never engaged for the whole render" in either telemetry or artifact validation.

What #3480 does today

getCapturePerfSummary() in packages/engine/src/services/frameCapture.ts now emits a console.warn when deNcprFallbacks / frames exceeds 50%, so the condition is at least visible in render logs / telemetry ingestion. This is a diagnostic only — it does not fail or flag the render artifact.

Follow-up needed (pick one, or combine)

  1. Fallback-ratio circuit breaker (engine or producer-side): if the realized deNcprFallbacks / frames ratio exceeds some threshold (e.g. 50%), fail the render explicitly instead of shipping it as "complete" — forcing a re-render with forceScreenshot (which the orchestrator already knows how to do for DrawElementVerificationError) rather than silently accepting a render that never got the fast-capture speedup.
  2. deNcprFallbacks/frames guard on the #3429 side: thread the ratio (already available via CapturePerfSummary.deNcprFallbacks / RenderPerfSummary) into ArtifactTransaction.validate() or the orchestrator's post-render checks, so artifact validation can distinguish "correct frame count via drawElement" from "correct frame count via total fallback."
  3. At minimum: promote the current warn-only diagnostic into a structured CaptureWarning / render-telemetry field so it's queryable across the fleet, not just visible in ad hoc log scraping.

Pointers

  • packages/engine/src/services/frameCapture.ts: getCapturePerfSummary (ratio warning), captureFrameCore / captureFrameToBufferPipelined / captureFramesBatchPipelined (the three per-frame/batch fallback sites), CaptureSession.deNcprFallbacks.
  • packages/engine/src/services/drawElementService.ts: DE_CANVAS_NOT_INITIALIZED_CODE, produceDrawElementFrameBatch.
  • packages/producer/src/services/render/artifactTransaction.ts: ArtifactTransaction.validate() (current frame-count/duration-blind validation).

Filed from PR #3480 review discussion (reviewer: tai).