Skip to content

fix(producer): credit looping short videos in coverage gate (#2665)#2732

Merged
jrusso1020 merged 1 commit into
mainfrom
jerrai/fix-loop-coverage-gate-2665
Jul 22, 2026
Merged

fix(producer): credit looping short videos in coverage gate (#2665)#2732
jrusso1020 merged 1 commit into
mainfrom
jerrai/fix-loop-coverage-gate-2665

Conversation

@jrusso1020

Copy link
Copy Markdown
Collaborator

Description

Fixes #2665. Regression window: 0.7.60 (#2606's original ship) → 0.7.67 (current).

#2606 taught the video-coverage gate that a non-looping short video holds its final decoded frame across the tail, so the delivered source frames are enough to cover the authored slot. But that fix gated the credit on !video.loop — a looping short video was still measured as unique-source-frames / slot-frames and aborted with captured 90 of expected 300 frames (coverage 30.0%).

This is the mainline "loop a short clip to fill a longer scene" case — the reason loop exists. Same source without loop renders clean via #2606's freeze credit.

Fix: extend #2606's source-credit to loops symmetrically. The delivered set (all N source frames) covers every repeat within the slot, so expectedFrames = min(slotFrames, sourceFrames) for both hold and loop. In packages/producer/src/services/render/videoFrameCoverage.ts:

- const sourceFrames = entry && !video.loop && hasUsableSourceDuration
+ const sourceFrames = entry && hasUsableSourceDuration
    ? expectedFramesForClip(0, sourceDuration, fps)
    : slotFrames;
- const expectedFrames = entry && !video.loop ? Math.min(slotFrames, sourceFrames) : slotFrames;
+ const expectedFrames = entry ? Math.min(slotFrames, sourceFrames) : slotFrames;

Fail-loud preserved. A genuinely-broken loop (extractor truncated below its own source) still aborts: a 60/90 delivery on a 300-frame slot with a 3s source reports expectedFrames=90, capturedFrames=60, ratio=0.667 → below 0.95 → throws. Missing extractions (!entry) continue to require the full slot.

Testing

  • bunx vitest run packages/producer/src/services/render/videoFrameCoverage.test.ts — 23 tests pass.
  • Replaced the test that locked in the buggy behavior (still requires the full authored slot for looping clips — asserted ratio ≈ 0.3) with a correctness test asserting expectedFrames=90, capturedFrames=90, ratio=1.0 for a looping 3s clip in a 10s slot.
  • Added still fails when a looping clip's source extraction is truncated (60/90 delivery aborts at 66.7% < 95%) as the new fail-loud floor.
  • Root cause verified by matching the reporter's cited code path at packages/producer/src/services/render/videoFrameCoverage.ts:150-154.
  • Non-looping paths untouched — every non-loop test in the file still passes.

— Rames Jusso

#2606 taught the video-coverage gate that a non-looping short video holds
its final decoded frame across the tail, so the delivered source frames
are enough to cover the authored slot. But that fix gated the credit on
'!video.loop' — a looping short video was still measured as
unique-source-frames / slot-frames and aborted at ratio << threshold.

Reproduced on 0.7.64 with the reporter's exact composition (3s clip in a
10s slot, loop attribute): render aborts with 'captured 90 of expected
300 frames (coverage 30.0%)'. Same source without loop renders clean via
#2606's freeze credit. This is the mainline 'loop a short clip to fill a
longer scene' case, the reason loop exists.

Fix: extend #2606's source-credit to loops symmetrically — the delivered
set (all N source frames) covers every repeat within the slot, so
expectedFrames = min(slotFrames, sourceFrames) for both hold and loop.
Fail-loud preserved for a genuinely-broken loop (extractor truncated
below its own source): a 60/90 delivery still aborts at 66.7% < 95%
because the delivered set no longer covers the full source period the
loop reuses. Missing extractions still require the full slot.

Test updates:
- 'still requires the full authored slot for looping clips' locked in
  the buggy behavior; replaced with 'credits a looping short clip
  against the source portion' which asserts the correct 90/90/1.0.
- Added 'still fails when a looping clip's source extraction is
  truncated' as the new fail-loud floor.

Fixes #2665. Regression window: 0.7.60 (#2606's original ship)
through 0.7.67 (current).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix at exact head 052c7835ee30c048520fd68f85538cf72a3ddb86.

Strengths

  • packages/producer/src/services/render/videoFrameCoverage.ts:143:161 correctly aligns the denominator with the extractor contract: held tails and loops both reuse a complete unique-source frame set, while missing entries and unusable source durations still fail closed against the full slot.
  • packages/producer/src/services/render/videoFrameCoverage.test.ts:158:176 pins both sides of the regression: 90/90 complete loop coverage passes, while a truncated 60/90 loop still trips the 95% fail-loud gate.

Verification

  • Traced both local extraction and distributed reconstruction: framePaths is rebuilt from the unique extracted files in both paths, so source-duration credit is the correct invariant.
  • Focused suite: 23/23 pass.
  • git diff --check passes. GitHub Format, Lint, Producer unit/integration, Build, Typecheck, runtime contract, and CLI smoke are green; remaining required general Test/Windows lanes are still running, not failing.

Verdict: APPROVE
Reasoning: The change removes the loop-only false positive without weakening detection for missing or truncated extraction, and the regression proof covers both success and failure semantics.

— Magi

@jrusso1020
jrusso1020 merged commit a637f39 into main Jul 22, 2026
49 checks passed
@jrusso1020
jrusso1020 deleted the jerrai/fix-loop-coverage-gate-2665 branch July 22, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression: video coverage gate aborts renders of a looping video shorter than its slot (#2516 / #2606)

2 participants