mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 10:06:21 +00:00
feat(lint): dense motion re-sampling for content_overlap (#2746)
* feat(lint): dense motion re-sampling for content_overlap Transient text-on-text collisions during continuous motion (e.g. an orbiting label card crossing the center card) overlap for a fraction of a second that the sparse 9-point layout grid seeks straight past. The content_overlap detector is correct; it just never gets a sample at the crossing moment. Rerun ONLY content_overlap on an 8fps grid (text-only, cheap) when the composition animates; findings feed the existing persistence tiering unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(lint): unconditional dense content_overlap pass + honor 500ms floor Round-1 blocker: the dense motion-overlap re-pass was gated on sparse-grid geometry fingerprints changing, so an animation aliased to the sparse grid (identical fingerprints, yet colliding between samples) bypassed the pass — exactly the transient false-negative it was built to catch. Remove the gate: the dense pass now runs unconditionally (bounded, text-only), driven by the composition timeline rather than a fingerprint heuristic. Round-2 follow-ups: - Persistence-tier drift: at 8fps, occurrences>=2 spans only ~125ms, not the ~500ms the design intends, and it short-circuited before the ms floor. content_overlap promotion now requires BOTH occurrences>=2 AND a literal firstSeen..lastSeen span >= 500ms, so the wall-clock floor is honored at any sampling density. Comment block updated to match. - Sample cap scales to hold a true 8fps grid up to ~75s (raised 120 -> 600) with an explicit note that longer comps degrade below 8fps to stay bounded. Tests: - Replaced the trivial "warning at every sample" test with a real between-grid regression: a collision living only inside (3.5,4.5) — a gap the sparse grid seeks past — is detected and, held ~750ms, promoted to error. - Replaced the now-invalid "skips when static" test with one asserting the dense pass runs even when sparse fingerprints are identical (aliased motion). - Added a tiering regression: two dense occurrences spanning ~125ms stay a warning (not error). Both new guards verified red before the fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf(lint): settle-free geometry seek for dense content_overlap pass The dense overlap re-pass did up to OVERLAP_MAX_SAMPLES full-settle seeks (120ms paint settle each, ~72s of pure sleep at the ceiling) even though collectOverlap only reads getBoundingClientRect geometry, valid synchronously after the timeline setTime. Add a settle-free DENSE_GEOMETRY_SEEK_OPTIONS + driver.seekGeometry used only by the dense loop; the base grid keeps full-settle driver.seek. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(lint): document + cover content_overlap 500ms boundary for sparse callers The occurrences>=2 AND heldMs>=500 promotion rule is a semantics change for sparse callers (--samples 20, --at, short comps) whose two samples can land <500ms apart. Document the change in the tiering comment and add boundary tests: 499ms span stays warning, 500ms span promotes to error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(lint): make dense content_overlap seek genuinely geometry-only Per review: DENSE_GEOMETRY_SEEK_OPTIONS only overrode settleMs, still inheriting animationFrameSettle:double + waitForFontsMs:500 → ~3 frame waits + font wait per seek → ~30s at the 600-sample cap. Geometry (getBoundingClientRect) is valid synchronously post-setTime, so drop all post-seek waits (animationFrameSettle:none, waitForFontsMs:0, settleMs:0). Add options-level regression locking the geometry-only contract. Also fix a stale comment name (detectMotionTextOverlap → collectMotionOverlapSamples). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style: collapse multi-line comments to single lines Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3b3d4f559c
commit
72e2f08f15
@@ -417,9 +417,42 @@ async function collectGridSamples(
|
||||
collected.screenshots.push({ time, pngBase64: capture.pngBase64 });
|
||||
}
|
||||
}
|
||||
await collectMotionOverlapSamples(driver, grid, collected);
|
||||
return collected;
|
||||
}
|
||||
|
||||
// Dense grid catches mid-motion text collisions the sparse layout grid seeks past; text-only overlap collection is cheap enough to afford it.
|
||||
const OVERLAP_SAMPLE_FPS = 8;
|
||||
// Ceiling that bounds dense seeks; past ~75s the grid degrades below 8fps rather than growing the seek budget without limit.
|
||||
const OVERLAP_MAX_SAMPLES = 600;
|
||||
|
||||
function buildOverlapSampleTimes(duration: number): number[] {
|
||||
if (!Number.isFinite(duration) || duration <= 0) return [];
|
||||
const count = Math.min(
|
||||
OVERLAP_MAX_SAMPLES,
|
||||
Math.max(2, Math.ceil(duration * OVERLAP_SAMPLE_FPS) + 1),
|
||||
);
|
||||
const step = duration / (count - 1);
|
||||
return mergeSampleTimes(
|
||||
Array.from({ length: count }, (_, index) => Math.round(index * step * 1000) / 1000),
|
||||
);
|
||||
}
|
||||
|
||||
/** Reruns content_overlap on a fine grid, unconditionally rather than gated on fingerprint change, since an animation aliased to the sparse grid collides between identical-fingerprint samples. */
|
||||
async function collectMotionOverlapSamples(
|
||||
driver: CheckAuditDriver,
|
||||
grid: SampleGrid,
|
||||
collected: GridSamples,
|
||||
): Promise<void> {
|
||||
const baseTimes = new Set(grid.layoutSamples);
|
||||
for (const time of buildOverlapSampleTimes(grid.duration)) {
|
||||
if (baseTimes.has(time)) continue;
|
||||
// Settle-free seek: collectOverlap reads getBoundingClientRect geometry, valid synchronously after setTime, so the dense pass skips the per-seek paint settle.
|
||||
await driver.seekGeometry(time);
|
||||
collected.layoutIssues.push(...(await driver.collectOverlap(time)));
|
||||
}
|
||||
}
|
||||
|
||||
// Frozen-sweep guard (#U10): compositions this short can legitimately hold a
|
||||
// single static frame the whole time (a title card) — never flag those.
|
||||
const SWEEP_STATIC_MIN_DURATION_SEC = 3;
|
||||
|
||||
Reference in New Issue
Block a user