Skip to content

Video coverage gate aborts renders using the new hold-last-frame feature (#2516) #2565

Description

@granganantjyot

Summary

Follow-up to #2514 / #2516. PR #2516 (shipped in 0.7.60) added opt-in hold-last-frame: a non-looping <video> with an explicit data-duration longer than its source now holds its final decoded frame for the rest of the slot. That part works correctly.

But render aborts by default on exactly these compositions, because the video-coverage gate counts only decoded source frames and does not credit the intentionally-held tail introduced by #2516.

Repro

  1. Any non-looping video shorter than its slot — e.g. a 3s clip in a 10s slot:

    <video class="clip" src="short-3s.mp4" data-start="0" data-duration="10" data-track-index="0" muted playsinline></video>
  2. npx hyperframes@0.7.60 render -c comp.html

Actual

Video "hero-video" captured 90 of expected 300 frames (coverage 30.0%, threshold 95.0%).
check/snapshot may pass while the encoded MP4 renders this clip blank — aborting render
to prevent shipping a wrong MP4. Set HF_VIDEO_COVERAGE_THRESHOLD=0 to disable this gate.

(90 = 3s × 30fps decoded source frames; 300 = the 10s × 30fps authored slot. Coverage reads 30%.)

Expected

The render completes. The held tail (frames 90–300) is the intended output introduced by #2516 — the last frame repeated — not a blank or truncated clip. The gate should credit those held-tail frames toward coverage.

Why it's a false positive

The gate exists to catch genuinely truncated/blank video. With #2516, low source-frame coverage inside an explicit slot is now a supported, deliberate state, and the gate can't distinguish it from the failure it's meant to catch. So every held-video render is blocked unless the guard is disabled globally via HF_VIDEO_COVERAGE_THRESHOLD=0 — which also disables the guard for legitimately-broken videos in the same render.

Suggested fix

Where the held tail is already modeled (the isHeldVideoTail / final-frame-hold logic added in #2516, around packages/engine/src/services/videoFrameExtractor.ts), count the held-tail frames as covered — or compute coverage against min(slotFrames, sourceFrames) for a non-looping explicit-slot video — so an intentional hold reports 100% while a truly short/blank clip still trips the gate.

Workaround

HF_VIDEO_COVERAGE_THRESHOLD=0 (disables the gate for the whole render).

Verified

Confirmed the hold itself works on 0.7.60: a 3s clip in a 10s slot renders correctly with the override, and the video-card region holds a steady mid-tone (≈RGB 128,125,115) across t=1.5/2.9/6.0/9.5s — the held-tail frames are pixel-identical to the final source frame, not blank (≈255,255,255). Only the default coverage gate blocks it.

Environment

  • hyperframes 0.7.60
  • macOS (arm64), Node v24.15.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions