mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
feat(engine,producer): drawElement fast-capture default-on with runtime self-verification safety net (#1998)
* feat(engine,producer): drawElement fast-capture default-on with runtime self-verification safety net Flip useDrawElement + worker-encode defaults on (HF_DE_BATCH default 4), clamped in resolveConfig to hosts where drawElement can engage (macOS + hardware-GPU browser) so page-side shader compositing is untouched everywhere else; explicit env opt-in keeps attempt-and-gate semantics. Safety net makes default-on safe: the compile/init gates catch predictable incompatibility; this catches the intermittent residue no static analysis can see (stale paints, dropped background images, transient blank frames). - engine: captureDeVerificationFrames — K=4 (HF_DE_VERIFY) ground-truth screenshots at init, after gates + armStaticDedup, BEFORE canvas injection (post-injection screenshots show the canvas bitmap, not the DOM). Runs the video-injection hook per sample; double-captures so rAF-driven text counters settle (a single immediate screenshot captures stale text and false-positives). Skips png, <10 frames, implausible __hf.duration (infinite-repeat GSAP sentinel). - producer: guardFrame on both worker-encode drains — rolling-median blank guard with retry-once at drain (byte-identical retry ⇒ deterministic dark frame, accepted; retry save/restores the static-dedup anchor) + ffmpeg PSNR self-verify vs ground truth (HF_DE_VERIFY_MIN_DB, default 32dB; natural agreement ≥45dB, damage ≤25dB). Breach dumps the frame pair to tmpdir and throws DrawElementVerificationError. - orchestrator: one-shot retry — on verification error the whole render re-runs with forceScreenshot (slower, never wrong); telemetry flag deSelfVerifyFallback. - tooling: de-canary-suite.sh (7-comp release gate with expected verdicts), de-gatecheck.sh (init-only corpus routing classifier), we-render.mjs. Validated: canary suite 7/7; 611-comp routing sample 54% drawelement / 37.5% gated / 8.3% comp-defect; 12/12 risk-band renders clean on bare defaults (48/48 verify samples); engine suite 888 passed; caught two real intermittent damage classes in the wild (background-image drop, root-props offset) that previously shipped silently. Kill switches: PRODUCER_EXPERIMENTAL_FAST_CAPTURE=false, HF_DE_WORKER_ENCODE=false, HF_DE_BATCH=0, HF_DE_VERIFY=0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(engine,producer): harden the drawElement self-verification net (max code-review findings) 15 confirmed findings from the adversarial review of the default-on flip; the load-bearing five: - Ground-truth capture no longer scrubs GSAP state: seek(0) + forced frame FIRST (lazy .from()/overlap tweens record start values on first seek — mid-timeline scrubs corrupted them for the whole render, and since DE frames and truth shared the corruption, PSNR passed on damaged output), then ascending even-spread fractions, page left at frame 0. - Default-on drawElement is confined to the verified path: resolveConfig requires worker-encode (the drain that runs the net), the orchestrator disengages the default when the render takes the disk path or parallel capture (no drain verification there), and closes a drawElement-initialized probe session rather than letting the unverified path reuse it. Explicit PRODUCER_EXPERIMENTAL_FAST_CAPTURE=true keeps old attempt-and-gate behavior. - Blank-frame retry can no longer splice wrong-frame pixels: recapture goes through recaptureDrawElementFrameForVerify — no static-dedup shortcut (lastEncodeResult runs ahead of the drain) and no "No cached paint record" screenshot fallback (post-injection that captures the canvas = the LAST drawn frame); any recapture failure falls back the whole render. - Verify indices derive from the producer-resolved duration (CaptureOptions.compositionDurationSeconds) instead of raw __hf.duration, so samples always land inside the drained range. - The platform clamp accepts "auto" GPU mode — the stock CLI resolves auto, and the literal-"hardware" clamp made default-on a no-op for the primary audience (masked in validation by explicitly-set env). Also: NaN-safe env parses (HF_DE_VERIFY / HF_DE_VERIFY_MIN_DB / HF_DE_BATCH); video comps skip verification when the session has no frame injector (probe sessions — black-video truth false-positived); psnr infrastructure failures skip the sample instead of failing the render; boundary-saturated sample indices are skipped; shader-transition comps prefer page-side compositing over default drawElement and compile-gated comps get page-side compositing restored; observability.clearFailure un-brands the recovered first streaming attempt; canary suite exempts known-marginal "any" comps from the cross-path PSNR gate; dead we-render options removed; clamp tests pin their env. Validated: canary suite 7/7; auto-GPU bare render engages the full stack; disk-path and worker-encode-off renders disengage default drawElement; malformed HF_DE_VERIFY_MIN_DB still verifies at the default threshold; engine suite 890 passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(engine,producer): review fixes — PSC intent, verify-threshold clamp, fail-closed canaries Addresses miguel-heygen's review on #1998: - Page-side compositing restore preserves explicit caller intent (blocker): resolveConfig now records pageSideCompositingAutoDisabled only when IT turned page-side compositing off because drawElement was on; the compile-time drawElement gates restore page-side compositing only when that flag is set. An explicit enablePageSideCompositing:false from the programmatic API or HF_PAGE_SIDE_COMPOSITING=false stays off. Pinned by two config tests. - HF_DE_VERIFY_MIN_DB clamped to [10, 60] with a warning on out-of-range values: below ~10dB the check passes severe damage; above ~60dB natural encoder differences force a screenshot fallback on every verified render. - de-canary-suite.sh + de-gatecheck.sh run under set -euo pipefail with explicit `|| true` on expected-nonzero commands (render exits handled by the suite's own checks, grep no-match, kill/pkill/wait races) and a hard FAIL when the PSNR compare produces no value — release canaries fail closed. Full suite re-run green (7/7) under the new flags. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d5ecb013d7
commit
ec06f4bf89
@@ -64,20 +64,30 @@ export interface EngineConfig {
|
||||
*/
|
||||
staticFrameDedup: boolean;
|
||||
/**
|
||||
* EXPERIMENTAL. Use drawElementImage for frame capture (requires the
|
||||
* CanvasDrawElement Chrome flag, added globally in buildChromeArgs).
|
||||
* Surfaced via the CLI `--experimental-fast-capture` flag.
|
||||
* Env fallback: `PRODUCER_EXPERIMENTAL_FAST_CAPTURE`.
|
||||
* Use drawElementImage for frame capture (requires the CanvasDrawElement
|
||||
* Chrome flag, added globally in buildChromeArgs). Default ON, clamped in
|
||||
* `resolveConfig` to hosts where it can actually engage (macOS + hardware-GPU
|
||||
* browser); compile/init gates and the runtime self-verification net route
|
||||
* incompatible or damaged renders back to screenshot capture.
|
||||
* Kill switch: `PRODUCER_EXPERIMENTAL_FAST_CAPTURE=false` (or the CLI
|
||||
* `--experimental-fast-capture=false`).
|
||||
*/
|
||||
useDrawElement: boolean;
|
||||
/**
|
||||
* EXPERIMENTAL. Pipeline JPEG encode into an in-page OffscreenCanvas Worker
|
||||
* for the drawElement fast-capture path (macOS hardware GPU only). The worker
|
||||
* encodes frame N while the main thread seeks+paints frame N+1, targeting
|
||||
* ~1.65–1.96× wall-time speedup. No-op unless `useDrawElement` is also true.
|
||||
* Default: off. Env: `HF_DE_WORKER_ENCODE=true`.
|
||||
* Pipeline JPEG encode into an in-page OffscreenCanvas Worker for the
|
||||
* drawElement fast-capture path (macOS hardware GPU only). The worker
|
||||
* encodes frame N while the main thread seeks+paints frame N+1
|
||||
* (~1.65–1.96× wall-time speedup). No-op unless `useDrawElement` is also
|
||||
* true. Kill switch: `HF_DE_WORKER_ENCODE=false`.
|
||||
*/
|
||||
enableDrawElementWorkerEncode: boolean;
|
||||
/**
|
||||
* INTERNAL. Set by resolveConfig when it disabled enablePageSideCompositing
|
||||
* solely because drawElement was on. Lets the producer's compile-time gates
|
||||
* restore page-side compositing without overriding an explicit caller/env
|
||||
* opt-out. Not intended to be set by callers.
|
||||
*/
|
||||
pageSideCompositingAutoDisabled?: boolean;
|
||||
/**
|
||||
* Low-memory render profile. When `true`, the orchestrator collapses the
|
||||
* pipeline to its cheapest shape on memory-constrained hosts: it skips the
|
||||
@@ -250,8 +260,8 @@ export const DEFAULT_CONFIG: EngineConfig = {
|
||||
protocolTimeout: 300_000,
|
||||
forceScreenshot: false,
|
||||
staticFrameDedup: true,
|
||||
useDrawElement: false,
|
||||
enableDrawElementWorkerEncode: false,
|
||||
useDrawElement: true,
|
||||
enableDrawElementWorkerEncode: true,
|
||||
// Auto-detected per host in `resolveConfig`; defaults off for the raw
|
||||
// DEFAULT_CONFIG (used directly by tests and worker-sizing fallbacks).
|
||||
lowMemoryMode: false,
|
||||
@@ -516,15 +526,48 @@ export function resolveConfig(overrides?: Partial<EngineConfig>): EngineConfig {
|
||||
...overrides,
|
||||
};
|
||||
|
||||
// Default-on drawElement is clamped to hosts where it can actually engage
|
||||
// (macOS with a non-software-GPU browser; SwiftShader drops transparent
|
||||
// sub-layers — crbug 521434899). "auto" passes the clamp: the stock CLI
|
||||
// resolves GPU mode to auto, which probes to hardware on real Macs — and if
|
||||
// it resolves to software after all, the SwiftShader init-time gate still
|
||||
// routes the session to the screenshot baseline. Without the clamp, the
|
||||
// default would needlessly disable page-side shader compositing (below) on
|
||||
// Linux/Docker hosts where DE never runs. An EXPLICIT opt-in (env or caller override)
|
||||
// skips the clamp and keeps the old semantics — attempt DE, let the
|
||||
// init-time gates route away — which debugging relies on.
|
||||
const explicitDrawElementOptIn =
|
||||
env("PRODUCER_EXPERIMENTAL_FAST_CAPTURE") === "true" || overrides?.useDrawElement === true;
|
||||
if (
|
||||
merged.useDrawElement &&
|
||||
!explicitDrawElementOptIn &&
|
||||
!(process.platform === "darwin" && merged.browserGpuMode !== "software")
|
||||
) {
|
||||
merged.useDrawElement = false;
|
||||
}
|
||||
// The runtime self-verification net lives in the worker-encode drain — the
|
||||
// serial drawElement path has only the blank guard. Default-on drawElement
|
||||
// therefore requires worker-encode; disabling HF_DE_WORKER_ENCODE without an
|
||||
// explicit drawElement opt-in falls back to the screenshot baseline rather
|
||||
// than shipping unverified drawElement frames.
|
||||
if (merged.useDrawElement && !explicitDrawElementOptIn && !merged.enableDrawElementWorkerEncode) {
|
||||
merged.useDrawElement = false;
|
||||
}
|
||||
|
||||
// drawElement capture and page-side shader compositing are mutually
|
||||
// incompatible capture strategies (drawElement reads paint records directly
|
||||
// and bypasses the page-side prepare→composite→resolve protocol). When
|
||||
// experimental fast capture is on, force page-side compositing off so shader
|
||||
// fast capture is on, force page-side compositing off so shader
|
||||
// transitions fall back to the Node-side layered blend rather than silently
|
||||
// dropping. This keeps the flag self-consistent and avoids a per-session
|
||||
// incompatibility warning on every fast-capture render.
|
||||
if (merged.useDrawElement) {
|
||||
if (merged.useDrawElement && merged.enablePageSideCompositing) {
|
||||
merged.enablePageSideCompositing = false;
|
||||
// Record that THIS resolution (not the caller) turned page-side
|
||||
// compositing off, so a later compile-time drawElement gate can restore
|
||||
// it without clobbering an explicit enablePageSideCompositing:false from
|
||||
// the programmatic API or HF_PAGE_SIDE_COMPOSITING=false.
|
||||
merged.pageSideCompositingAutoDisabled = true;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user