feat(engine,producer): drawElement fast-capture default-on with runtime self-verification safety net#1998
Conversation
95b4aef to
8009500
Compare
7ebb1ad to
1f3fc92
Compare
miga-heygen
left a comment
There was a problem hiding this comment.
PR Review — feat(engine,producer): drawElement fast-capture default-on with runtime self-verification safety net
TITLE: feat(engine,producer): drawElement fast-capture default-on with runtime self-verification safety net
VERDICT: Approve (comment-only — no blocking issues found)
DIFF_STATS: +725 / -87, 17 files changed
SUMMARY
This PR flips drawElement (DE) fast capture from opt-in to default-on, guarded by a three-layer runtime self-verification safety net that guarantees the failure mode is a slower render, never wrong pixels. The safety layers are:
- Pre-injection ground truth (engine) — K=4 screenshot samples captured before the DE canvas is injected, using a careful seek-order (frame 0 first, then ascending) to avoid corrupting GSAP
.from()tween caches. - Drain-time guard (producer) — a rolling-median blank guard + PSNR comparison against ground truth at sample indices, with verification-grade recapture that bypasses static-dedup and screenshot-fallback shortcuts.
- Whole-render auto-fallback (orchestrator) — catches
DrawElementVerificationError, re-renders withforceScreenshot=true, clears the observability failure flag.
The default is clamped to macOS + non-software GPU + worker-encode enabled. Explicit env opt-in bypasses all clamps. Kill switches are env-only, no deploy needed. Shader-transition comps are routed back to page-side compositing. Disk-path and parallel-capture renders disengage default DE since they lack the verification drain.
PONYTAIL
The architecture here is genuinely well-reasoned. The insight that verification must happen pre-injection (because post-injection screenshots show the canvas bitmap, not the live DOM) is a subtle correctness invariant that's easy to get wrong. The seek-order discipline (frame 0 first to initialize lazy GSAP tweens before sampling) shows deep understanding of the animation runtime. The discriminant-based error detection (deVerificationFailure property instead of instanceof) surviving duplicated module instances across package boundaries is a real-world concern that most PRs ignore. The double-capture trick for rAF-driven counters (first screenshot forces the frame, second captures the result) is a pragmatic workaround for headless Chrome's rAF scheduling. The blank-guard's acceptedSmall optimization avoids re-capturing every frame in a legitimately dark stretch. The explicit rejection of concurrent drain+capture (NOTE comment about 87dB noise floor perturbation) shows the team is measuring empirically and choosing reliability over marginal perf.
FINDINGS
(1) Observation — psnrDb spawns ffmpeg per verification sample (non-blocking)
captureStreamingStage.ts — The psnrDb function spawns a child process, writes two files, and parses stderr for each of the K=4 verification frames. At ~50-75ms per call this adds ~200-300ms per render, which is well within the init budget. But if HF_DE_VERIFY is ever raised toward 8, and someone runs a very fast short composition, the overhead could approach a meaningful fraction of total render time. The current default of 4 is fine, and the clamp to 0..8 caps the exposure. Just flagging for awareness — no action needed.
(2) Observation — clearFailure timing and observeRenderStage interaction (verified correct)
renderOrchestrator.ts — The retry flow is:
- First
invokeStreaming()throws throughobserveRenderStage→stageErrorsetsfailedPhase = "capture_streaming". - Orchestrator catches, mutates
captureForceScreenshot = true,probeSession = null. - Second
invokeStreaming()succeeds →stageEndruns but does NOT clearfailedPhase. clearFailure("capture_streaming")explicitly clears it.
This is correct.stageEndintentionally doesn't clear failures (a later phase could succeed while an earlier one still failed), so the explicitclearFailureis necessary. The closure properly capturescaptureForceScreenshotandprobeSessionby reference (letbindings), so the retry reads the updated values. Verified.
(3) Observation — explicitDrawElementOptIn only matches "true" strictly (by design, verified)
config.ts — env("PRODUCER_EXPERIMENTAL_FAST_CAPTURE") === "true" only matches the literal string "true", not "1", "yes", or other truthy representations. This is consistent with the existing env-var parsing convention in resolveConfig (all other boolean envs use the same === "true" pattern). The kill switch === "false" would need the literal "false" to disable via the env var, but the default flip itself means omitting the env var entirely now enables DE (on supported hosts), so the kill switch works by setting it to "false". Setting it to "0" or anything else would leave the default clamping logic in control. This is a bit subtle but consistent.
(4) Low — isNoCachedPaintRecordError changed from export to private (minor scope reduction)
frameCapture.ts — The function was exported before and is now module-private. The PR body doesn't mention this. If any downstream consumer was using it, this would be a breaking change. Since it's not in the engine's index.ts re-exports and is an internal diagnostic, this is fine — just noting the visibility change.
(5) Observation — Canary suite exit behavior in subshell
de-canary-suite.sh — The while read loop runs in a subshell (piped from echo "$CANARIES"). The exit 1 inside the loop exits the subshell, not the script. The || fail=1 after done catches this. However, the fail variable is initialized to 0 before the pipe, and the subshell's exit 1 doesn't set fail=1 — the || fail=1 does. The final check [ "$fail" = 0 ] works correctly because the || fires when the subshell exits non-zero. This is a known bash idiom; it works but is fragile (adding set -e to the script would change the semantics). Since this is a manual test harness, not CI, it's fine.
(6) Observation — Video comp without frame injector skips verification rather than failing
frameCapture.ts — When session.onBeforeCapture is null and the page has <video> elements, verification is skipped entirely ("video comp without frame injector"). The comment explains this avoids false positives (videos screenshot as black without the injector), but it also means a probe session initialized before the render wires up the injector gets no verification at all. The orchestrator's disengage-for-disk-path logic closes the probe session in this case, so it shouldn't reach production — but if a future code path reuses a probe session without wiring the injector, the verification gap would be silent. The log message makes this auditable.
(7) Observation — Binary test fixtures changed
Three binary files changed (css-var-fonts/output/output.mp4, video-hfid-no-id/output/output.mp4, video-hfid-no-id/src/clip.mp4). These are presumably snapshot-pinned test expectations that shifted because DE is now default-on (the render pipeline changed). As long as the test suite passes with these new fixtures, this is expected.
Review by Miga
miguel-heygen
left a comment
There was a problem hiding this comment.
Current-head review at 8009500002af7b0d0e0bfeae621990785ae8c3d0.
Audited: default-on config flow in packages/engine/src/config.ts, compile-stage routing in packages/producer/src/services/render/stages/compileStage.ts, self-verification threshold parsing in captureStreamingStage.ts, canary scripts, orchestrator fallback, and current check state.
Blocker
packages/producer/src/services/render/stages/compileStage.ts:210-215restores page-side compositing any timecfg.useDrawElementis false andHF_PAGE_SIDE_COMPOSITINGis notfalse. That loses the distinction between "resolveConfig disabled page-side compositing because default drawElement was going to run" and "the caller explicitly passedenablePageSideCompositing: falsethrough the programmatic API." In the latter case, a compile-time drawElement gate such as 3D/mix-blend/shader transitions silently flips the caller's explicit override back to true. Please preserve the original explicit config intent, for example by tracking whether this render disabled page-side compositing only because default drawElement was enabled, and only restore in that case.
Important follow-ups before default-on widens
packages/producer/src/services/render/stages/captureStreamingStage.ts:202-203only NaN-guardsHF_DE_VERIFY_MIN_DB;0disables meaningful PSNR fallback and999forces screenshot fallback on every verified render. Clamp to a defensible range and warn on out-of-range values.packages/producer/de-canary-suite.sh:12andpackages/producer/de-gatecheck.sh:6useset -uwithoutset -euo pipefail, so pipeline/grep failures can keep the release canaries moving after a broken command. These scripts are part of the safety net for default-on and should fail closed.
Checks: regression shards and Graphite are still pending on this head; no active required failure is visible yet.
Verdict: REQUEST CHANGES
Reasoning: The runtime fallback architecture is sound, but the current compile-stage restore path can override an explicit API config and change capture behavior for direct callers. That is a real contract regression in the default-on PR.
- Magi
…mplete Default-on drawElement ships with a runtime self-verification net (#1998); this makes its in-the-wild behavior observable. Every render_complete event now answers: which capture mode actually ran, why drawElement disengaged when it did (compile gate / producer clamp / engine init gate), whether the self-verify net fired and why, and how much margin verification had. Follows the static-dedup telemetry pattern: engine session fields → CapturePerfSummary → RenderPerfSummary.drawElement → snake_case props on render_complete. New event props: de_capture_mode, de_compile_gate, de_clamp_reason, de_gate_reason, de_worker_encode, de_verify_armed, de_verify_checked, de_verify_min_db (margin above the 32dB threshold — drift here is the early-warning signal before fallbacks spike), de_verify_init_ms, de_self_verify_fallback, de_fallback_reason, de_blank_suspects, de_blank_deterministic_accepts, de_blank_recaptures, de_boundary_frames, de_ncpr_fallbacks. Validated end-to-end on live renders: drawelement path reports mode/verify counters/minDb/init cost; a blur-gated comp reports mode=screenshot + gate_reason=css_effect:filter; a forced verification failure reports self_verify_fallback=true + fallback_reason=psnr. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1f3fc92 to
8a196da
Compare
8009500 to
6006125
Compare
|
All three items addressed in
|
…mplete Default-on drawElement ships with a runtime self-verification net (#1998); this makes its in-the-wild behavior observable. Every render_complete event now answers: which capture mode actually ran, why drawElement disengaged when it did (compile gate / producer clamp / engine init gate), whether the self-verify net fired and why, and how much margin verification had. Follows the static-dedup telemetry pattern: engine session fields → CapturePerfSummary → RenderPerfSummary.drawElement → snake_case props on render_complete. New event props: de_capture_mode, de_compile_gate, de_clamp_reason, de_gate_reason, de_worker_encode, de_verify_armed, de_verify_checked, de_verify_min_db (margin above the 32dB threshold — drift here is the early-warning signal before fallbacks spike), de_verify_init_ms, de_self_verify_fallback, de_fallback_reason, de_blank_suspects, de_blank_deterministic_accepts, de_blank_recaptures, de_boundary_frames, de_ncpr_fallbacks. Validated end-to-end on live renders: drawelement path reports mode/verify counters/minDb/init cost; a blur-gated comp reports mode=screenshot + gate_reason=css_effect:filter; a forced verification failure reports self_verify_fallback=true + fallback_reason=psnr. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8a196da to
fd9e027
Compare
6006125 to
9de5584
Compare
miguel-heygen
left a comment
There was a problem hiding this comment.
Delta re-review at 9de558449c18c733daaf761ba5c0e894b942d376.
Audited: packages/engine/src/config.ts, packages/engine/src/config.test.ts, packages/producer/src/services/render/stages/compileStage.ts, packages/producer/src/services/render/stages/captureStreamingStage.ts, and the two DE helper scripts.
The prior blocker is fixed. resolveConfig now records pageSideCompositingAutoDisabled only when it itself disabled page-side compositing because drawElement was enabled (config.ts:564-570), and compileStage.ts:210-218 restores page-side compositing only when that flag is present. That preserves explicit programmatic/API intent for enablePageSideCompositing: false. The config tests also pin both the auto-disabled and explicit-opt-out cases.
The prior important notes are also cleaned up: HF_DE_VERIFY_MIN_DB is clamped to [10, 60] with a warning (captureStreamingStage.ts:202-214), and de-canary-suite.sh / de-gatecheck.sh now run under set -euo pipefail with explicit expected-error handling.
CI note: the current active PR check view still has Graphite, preview-regression, and regression shards pending. I am approving the code delta, not declaring the full stack merge-ready before those finish.
Verdict: APPROVE
Reasoning: The config contract regression is closed without clobbering explicit caller intent, and the release-safety hardening notes were addressed as well.
- Magi
terencecho
left a comment
There was a problem hiding this comment.
LGTM per my per-PR review + re-review on the Slack thread. Non-blocking nits I raised (where present) are fair as post-merge follow-ups.
— Review by tai (pr-review)
fd9e027 to
b817ebd
Compare
…mplete Default-on drawElement ships with a runtime self-verification net (#1998); this makes its in-the-wild behavior observable. Every render_complete event now answers: which capture mode actually ran, why drawElement disengaged when it did (compile gate / producer clamp / engine init gate), whether the self-verify net fired and why, and how much margin verification had. Follows the static-dedup telemetry pattern: engine session fields → CapturePerfSummary → RenderPerfSummary.drawElement → snake_case props on render_complete. New event props: de_capture_mode, de_compile_gate, de_clamp_reason, de_gate_reason, de_worker_encode, de_verify_armed, de_verify_checked, de_verify_min_db (margin above the 32dB threshold — drift here is the early-warning signal before fallbacks spike), de_verify_init_ms, de_self_verify_fallback, de_fallback_reason, de_blank_suspects, de_blank_deterministic_accepts, de_blank_recaptures, de_boundary_frames, de_ncpr_fallbacks. Validated end-to-end on live renders: drawelement path reports mode/verify counters/minDb/init cost; a blur-gated comp reports mode=screenshot + gate_reason=css_effect:filter; a forced verification failure reports self_verify_fallback=true + fallback_reason=psnr. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9de5584 to
5cdeb6c
Compare
167da0e to
25adea0
Compare
…mplete Default-on drawElement ships with a runtime self-verification net (#1998); this makes its in-the-wild behavior observable. Every render_complete event now answers: which capture mode actually ran, why drawElement disengaged when it did (compile gate / producer clamp / engine init gate), whether the self-verify net fired and why, and how much margin verification had. Follows the static-dedup telemetry pattern: engine session fields → CapturePerfSummary → RenderPerfSummary.drawElement → snake_case props on render_complete. New event props: de_capture_mode, de_compile_gate, de_clamp_reason, de_gate_reason, de_worker_encode, de_verify_armed, de_verify_checked, de_verify_min_db (margin above the 32dB threshold — drift here is the early-warning signal before fallbacks spike), de_verify_init_ms, de_self_verify_fallback, de_fallback_reason, de_blank_suspects, de_blank_deterministic_accepts, de_blank_recaptures, de_boundary_frames, de_ncpr_fallbacks. Validated end-to-end on live renders: drawelement path reports mode/verify counters/minDb/init cost; a blur-gated comp reports mode=screenshot + gate_reason=css_effect:filter; a forced verification failure reports self_verify_fallback=true + fallback_reason=psnr. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
25adea0 to
f93e426
Compare
5cdeb6c to
379769b
Compare
The base branch was changed.
…me 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>
…ax 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>
…mp, 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>
…mplete Default-on drawElement ships with a runtime self-verification net (#1998); this makes its in-the-wild behavior observable. Every render_complete event now answers: which capture mode actually ran, why drawElement disengaged when it did (compile gate / producer clamp / engine init gate), whether the self-verify net fired and why, and how much margin verification had. Follows the static-dedup telemetry pattern: engine session fields → CapturePerfSummary → RenderPerfSummary.drawElement → snake_case props on render_complete. New event props: de_capture_mode, de_compile_gate, de_clamp_reason, de_gate_reason, de_worker_encode, de_verify_armed, de_verify_checked, de_verify_min_db (margin above the 32dB threshold — drift here is the early-warning signal before fallbacks spike), de_verify_init_ms, de_self_verify_fallback, de_fallback_reason, de_blank_suspects, de_blank_deterministic_accepts, de_blank_recaptures, de_boundary_frames, de_ncpr_fallbacks. Validated end-to-end on live renders: drawelement path reports mode/verify counters/minDb/init cost; a blur-gated comp reports mode=screenshot + gate_reason=css_effect:filter; a forced verification failure reports self_verify_fallback=true + fallback_reason=psnr. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
379769b to
d7653ee
Compare
…mplete Default-on drawElement ships with a runtime self-verification net (#1998); this makes its in-the-wild behavior observable. Every render_complete event now answers: which capture mode actually ran, why drawElement disengaged when it did (compile gate / producer clamp / engine init gate), whether the self-verify net fired and why, and how much margin verification had. Follows the static-dedup telemetry pattern: engine session fields → CapturePerfSummary → RenderPerfSummary.drawElement → snake_case props on render_complete. New event props: de_capture_mode, de_compile_gate, de_clamp_reason, de_gate_reason, de_worker_encode, de_verify_armed, de_verify_checked, de_verify_min_db (margin above the 32dB threshold — drift here is the early-warning signal before fallbacks spike), de_verify_init_ms, de_self_verify_fallback, de_fallback_reason, de_blank_suspects, de_blank_deterministic_accepts, de_blank_recaptures, de_boundary_frames, de_ncpr_fallbacks. Validated end-to-end on live renders: drawelement path reports mode/verify counters/minDb/init cost; a blur-gated comp reports mode=screenshot + gate_reason=css_effect:filter; a forced verification failure reports self_verify_fallback=true + fallback_reason=psnr. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mplete (#2002) Default-on drawElement ships with a runtime self-verification net (#1998); this makes its in-the-wild behavior observable. Every render_complete event now answers: which capture mode actually ran, why drawElement disengaged when it did (compile gate / producer clamp / engine init gate), whether the self-verify net fired and why, and how much margin verification had. Follows the static-dedup telemetry pattern: engine session fields → CapturePerfSummary → RenderPerfSummary.drawElement → snake_case props on render_complete. New event props: de_capture_mode, de_compile_gate, de_clamp_reason, de_gate_reason, de_worker_encode, de_verify_armed, de_verify_checked, de_verify_min_db (margin above the 32dB threshold — drift here is the early-warning signal before fallbacks spike), de_verify_init_ms, de_self_verify_fallback, de_fallback_reason, de_blank_suspects, de_blank_deterministic_accepts, de_blank_recaptures, de_boundary_frames, de_ncpr_fallbacks. Validated end-to-end on live renders: drawelement path reports mode/verify counters/minDb/init cost; a blur-gated comp reports mode=screenshot + gate_reason=css_effect:filter; a forced verification failure reports self_verify_fallback=true + fallback_reason=psnr. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

What
Ships drawElement fast capture default-on (no
--experimental-fast-captureneeded), wrapped in a runtime self-verification safety net that catches the damage classes no compile/init gate can predict and auto-falls-back to the screenshot path. Includes the release tooling (canary suite, corpus routing classifier) and a hardening round from an adversarial 50-agent code review (15 confirmed findings, all fixed in the second commit).Why
The combined fast-capture stack (#1916–#1928) is ~1.9–2.0× median vs screenshot capture, but drawElement has intermittent, run-dependent damage classes — stale compositor paints, dropped background images, transient blank frames — that static gates cannot see. Validation caught two real ones in the wild that previously shipped silently: comp
d95f20b6intermittently drops its photo background (~16dB, fires on some runs and not others), and398c0655renders with a vertical offset. Default-on is only safe if every drawElement render proves itself and falls back when it can't. The failure mode after this PR is a slower render, never wrong pixels.How
Safety net — three layers:
injectDrawElementCanvas(afterwardsPage.captureScreenshotreturns the canvas bitmap, not live DOM) — seek K=4 (HF_DE_VERIFY) evenly-spread frames and screenshot each. Seeks go frame 0 first, then ascending (lazy GSAP.from()tweens record start values on first seek; mid-timeline scrubs corrupt them for the whole render — and since DE frames and truth would share the corruption, PSNR would pass on damaged output). Each sample runs the video-injection hook and double-captures (the first screenshot forces the frame that runs rAF callbacks — count-up text counters land one tick afterseek()returns). Indices derive from the producer-resolved duration (CaptureOptions.compositionDurationSeconds), not raw__hf.duration, so they always land inside the drained range.guardFrame: a rolling-median blank guard (byte-identical recapture ⇒ legitimately dark frame, accepted; recapture uses a dedicated verification-grade path with no static-dedup shortcut and no stale-canvas fallback — both can return the wrong frame's pixels at drain time) plus PSNR self-verification against the ground truth (HF_DE_VERIFY_MIN_DB, default 32dB; natural DE-vs-screenshot agreement measures ≥45dB, damage ≤25dB). A breach dumps the frame pair to tmpdir and throws a typedDrawElementVerificationError.forceScreenshot=true; telemetry flagdeSelfVerifyFallback; the recovered first attempt no longer brands the render failed (observability.clearFailure).Default flip, confined to where the net runs:
useDrawElement+enableDrawElementWorkerEncodedefault true;HF_DE_BATCHdefault 4.resolveConfigclamps the default to macOS with a non-software GPU mode ("auto" passes — the stock CLI resolves auto; if it probes to software the SwiftShader init gate still routes away) and requires worker-encode (the only drain with the net).PRODUCER_EXPERIMENTAL_FAST_CAPTURE=truebypasses every clamp (old attempt-and-gate semantics for debugging).PRODUCER_EXPERIMENTAL_FAST_CAPTURE=false,HF_DE_WORKER_ENCODE=false,HF_DE_BATCH=0,HF_DE_VERIFY=0.Tooling:
de-canary-suite.sh(7 comps with expected verdicts clean/fallback/any + cross-path PSNR gate),de-gatecheck.sh(init-only corpus routing classifier — detects both engine init-gate and producer compile-gate log shapes),we-render.mjs(the render harness both scripts drive).Test plan
any.PRODUCER_BROWSER_GPU_MODE=autoengages the full stack; forced disk path andHF_DE_WORKER_ENCODE=falsedisengage default DE with a clear log; forced-failure (HF_DE_VERIFY_MIN_DB=99) trips at the first sample and completes via screenshot; malformed env values fall back to defaults instead of silently disabling verification.fix(engine,producer): harden the drawElement self-verification net.🤖 Generated with Claude Code