mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
fix(capture): bound static dedup verification time (#2457)
## What - cap static-dedup verification at 15 seconds of wall-clock time - disable the optimization and continue normal capture when the verification budget is exhausted - add a regression that models the reported 350-second / ~8,400-frame alpha render ## Why Static-frame verification uses full-page screenshots. Its existing screenshot-count budget still scales with composition duration, so a long composition can spend minutes proving an optimization before frame capture starts. The reported 350.35-second ProRes alpha render spent about eight minutes in this phase before safely disabling dedup. ## How The verifier now records a deadline before seeking verification frames. It checks the deadline before every full-page capture and returns the existing fail-closed `budgetExhausted` result when time is exhausted. This keeps the existing density-based safety checks while bounding their startup cost. ## Test plan - [x] Unit tests added/updated - [ ] Manual testing performed - [ ] Documentation updated (not applicable) - [x] Focused engine test: 9/9 passed - [x] Engine typecheck passed - [x] Pre-commit lint, format, tracked-artifact, fallow, and typecheck gates passed - [x] Full engine suite: 988 passed, 3 skipped; 2 pre-existing environment failures because host FFmpeg 4.2 lacks `-fps_mode`
This commit is contained in:
@@ -183,4 +183,35 @@ describe("verifyStaticFramesSafe catches drift the old fixed-point density would
|
||||
expect(seekCalls.map((call) => Math.round(call.t * fps))).toEqual([0, 1, 2, 0]);
|
||||
expect(seekCalls.every((call) => call.options?.suppressEvents === true)).toBe(true);
|
||||
});
|
||||
|
||||
it("disarms within a wall-clock budget instead of spending minutes verifying a long static run", async () => {
|
||||
const fps = 24;
|
||||
let nowMs = 0;
|
||||
const nowSpy = vi.spyOn(Date, "now").mockImplementation(() => nowMs);
|
||||
const page = {
|
||||
evaluate: vi.fn(async () => undefined),
|
||||
};
|
||||
vi.mocked(pageScreenshotCapture).mockImplementation(async () => {
|
||||
nowMs += 8_000;
|
||||
return Buffer.from("same");
|
||||
});
|
||||
|
||||
// Mirrors the reported 350.35s / 23.976fps alpha composition closely:
|
||||
// ~8,400 predicted-static frames used to schedule ~352 full-page PNG
|
||||
// screenshots before capture could begin.
|
||||
const staticFrames = new Set<number>();
|
||||
for (let frame = 1; frame < 8_400; frame++) staticFrames.add(frame);
|
||||
|
||||
const result = await verifyStaticFramesSafe(
|
||||
{ options: {} } as unknown as CaptureSession,
|
||||
page as unknown as Parameters<typeof verifyStaticFramesSafe>[1],
|
||||
staticFrames,
|
||||
fps,
|
||||
24,
|
||||
);
|
||||
nowSpy.mockRestore();
|
||||
|
||||
expect(result?.budgetExhausted).toBe(true);
|
||||
expect(pageScreenshotCapture).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2359,6 +2359,13 @@ export async function computeStaticFrameSet(
|
||||
// sampleCount, so that knob's effect on density stays monotonic (see below).
|
||||
const STATIC_VERIFY_REFERENCE_STRIDE = 24;
|
||||
|
||||
// Verification uses full-page screenshots, whose cost scales with canvas size
|
||||
// and page complexity rather than frame count. Bound wall time as well as the
|
||||
// capture count so a long composition cannot spend minutes proving an
|
||||
// optimization before the real render starts. Exhaustion fails closed: dedup is
|
||||
// disabled and normal capture proceeds.
|
||||
const STATIC_VERIFY_MAX_MS = 15_000;
|
||||
|
||||
/**
|
||||
* Interior verification points for a run [a..b], plus the always-included end `b`.
|
||||
* Density used to be a flat point-count cap (min(sampleCount, 8)), so a run's
|
||||
@@ -2414,6 +2421,7 @@ export async function verifyStaticFramesSafe(
|
||||
): Promise<{ badFrame: number; budgetExhausted: boolean } | null> {
|
||||
const frames = [...staticFrames].sort((a, b) => a - b);
|
||||
if (frames.length === 0) return null;
|
||||
const deadline = Date.now() + STATIC_VERIFY_MAX_MS;
|
||||
// Runs are maximal-contiguous (adjacent frames merge), so a run's anchor a-1 is
|
||||
// guaranteed NOT static — always a freshly-captured frame.
|
||||
const runs: Array<{ a: number; b: number }> = [];
|
||||
@@ -2460,9 +2468,11 @@ export async function verifyStaticFramesSafe(
|
||||
for (const { a, b } of runs) {
|
||||
const anchor = a - 1;
|
||||
if (anchor < 0) continue;
|
||||
if (Date.now() >= deadline) return { badFrame: a, budgetExhausted: true };
|
||||
const anchorBuf = await seekCapture(anchor);
|
||||
spent++;
|
||||
for (const f of computeStaticVerificationPoints(a, b, sampleCount)) {
|
||||
if (Date.now() >= deadline) return { badFrame: f, budgetExhausted: true };
|
||||
const cur = await seekCapture(f);
|
||||
spent++;
|
||||
if (!anchorBuf.equals(cur)) return { badFrame: f, budgetExhausted: false };
|
||||
|
||||
Reference in New Issue
Block a user