fix(producer): retry segmented capture on screenshot after a sequential stall

captureSegmentFrames races each frame through raceAgainstStall, which throws
SequentialCaptureStallError. The segmented catch only retried drawElement
capture, verification and renderer-stall errors, so a drawElement frame that
hung without the engine's frame timeout firing failed the whole render, where
the streaming path retries the same error on a fresh screenshot session.

Retry it when the plan did not already force screenshot. The failure kind is
draw_element_capture, which keeps segmenting and forces screenshot;
capture_failure would replan to plain streaming, which this catch cannot
drive. deFallbackReason stays "capture_error", the value streaming records
for a sequential stall.

— Via

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-10-26 21:35:01 +00:00
co-authored by Claude Opus 5.5
parent b0e8eff4d1
commit 30e2987d52
2 changed files with 18 additions and 2 deletions
@@ -190,6 +190,14 @@ function render(resumeSegments = false): Promise<void> {
return executeRenderJob(job, root, join(root, "output.mp4"));
}
function sequentialStall(): Error {
const err = new Error(
"[Render] Sequential drawElement capture stalled: no frame progress for 1000ms (stuck at frame 640/1200).",
);
err.name = "SequentialCaptureStallError";
return err;
}
function stalledRenderer(): Error {
const err = new Error("drawElement frame exceeded 5000ms (renderer stopped scheduling)");
err.name = "DeFrameTimeoutError";
@@ -200,6 +208,7 @@ it.each([
["a suspect small frame", () => new DrawElementCaptureError(640, "suspect small frame")],
["no cached paint record", () => new DrawElementCaptureError(640, "No cached paint record")],
["a stalled renderer", stalledRenderer],
["a sequential capture stall", sequentialStall],
[
"a failed self-verification",
() => new DrawElementVerificationError("drawElement PSNR too low", { kind: "psnr" }),
@@ -4741,8 +4741,13 @@ async function executeRenderPipeline(input: {
} catch (err) {
const isVerifyError = isDrawElementVerificationError(err);
const isDeStall = isDeRendererStallError(err);
const isUnforcedSequentialStall =
isSequentialCaptureStallError(err) && !capturePlan.forceScreenshot;
if (
(!isVerifyError && !isDrawElementCaptureError(err) && !isDeStall) ||
(!isVerifyError &&
!isDrawElementCaptureError(err) &&
!isDeStall &&
!isUnforcedSequentialStall) ||
err instanceof RenderCancelledError ||
executionSignal?.aborted === true
) {
@@ -4759,7 +4764,9 @@ async function executeRenderPipeline(input: {
deFallbackFrameIndex = t.frameIndex;
deFallbackThresholdDb = t.thresholdDb;
log.warn(
"[Render] drawElement capture failed on the segmented path; re-rendering via screenshot",
isUnforcedSequentialStall
? "[Render] segmented capture stalled; retrying on a fresh screenshot session"
: "[Render] drawElement capture failed on the segmented path; re-rendering via screenshot",
{
error: err instanceof Error ? err.message : String(err),
},