Files
hyperframes/packages/producer/de-gatecheck.sh
Vance IngallsandClaude Fable 5 ec06f4bf89 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>
2026-07-06 16:29:30 -07:00

54 lines
3.0 KiB
Bash

#!/usr/bin/env bash
# Init-only gate classification over the full 500. The gate decision is logged at init
# ("drawElement canvas injected" OR "falling back to ... — <reason>"), so launch the fast
# render, wait for that line, kill before frames render. Writes /tmp/gatecheck/<pre>.txt.
# Resumable. PAR workers. Usage: PAR=4 bash de-gatecheck.sh
set -euo pipefail
cd "$(dirname "$0")"
OUT=/tmp/gatecheck; mkdir -p "$OUT"
PAR=${PAR:-4}
SAMPLE=${SAMPLE:-/tmp/sweep-sample.txt}
GPU="PRODUCER_BROWSER_GPU_MODE=hardware PRODUCER_ENABLE_BROWSER_POOL=false HF_DE_WORKER_ENCODE=false HF_STATIC_DEDUP=false PRODUCER_EXPERIMENTAL_FAST_CAPTURE=true"
export GPU OUT CAP
worker(){
local pre=$1
[ -f "$OUT/$pre.txt" ] && return
local dir; dir=$(find /tmp/cc-all/ -maxdepth 1 -type d -name "${pre}*" | head -1 || true)
[ -z "$dir" ] && { echo "$pre NODIR" > "$OUT/$pre.txt"; return; }
local log=/tmp/gc-$pre.log
( eval "env $GPU bun we-render.mjs \"$dir\" /tmp/gc-$pre.mp4" >"$log" 2>&1 </dev/null ) & local p=$!
local t=0
while kill -0 "$p" 2>/dev/null; do
sleep 1; t=$((t+1))
if grep -qE "drawElement canvas injected|falling back to (screenshot|beginframe) capture —|Fast capture: composition uses|render-mode compatibility hint" "$log" 2>/dev/null; then break; fi
[ "$t" -ge "${CAP:-120}" ] && break
done
# kill the render tree (don't need full render)
for c in $(pgrep -P "$p" 2>/dev/null || true); do kill -9 "$c" 2>/dev/null || true; done
kill -9 "$p" 2>/dev/null || true
pkill -f "we-render.mjs $dir" 2>/dev/null || true
wait "$p" 2>/dev/null || true
if grep -q "drawElement canvas injected" "$log" 2>/dev/null; then
echo "$pre drawelement" > "$OUT/$pre.txt"
else
local reason; reason=$(grep -oE "falling back to (screenshot|beginframe) capture — [^(]*" "$log" | head -1 | sed -E 's/.*— //' || true)
# Compile-time gates (3D / mix-blend-mode) log a different shape than the
# engine's init-time gates — "Fast capture: composition uses X — disabling".
if [ -z "$reason" ]; then
reason=$(grep -oE "Fast capture: composition uses [a-z0-9 -]*" "$log" | head -1 | sed -E 's/.*uses /compile:/' || true)
fi
# Compat-hint routing (raw rAF etc.): "fast capture: falling back to screenshot — render-mode ..."
if [ -z "$reason" ] && grep -q "render-mode compatibility hint" "$log" 2>/dev/null; then
reason="render-mode compat hint (raw rAF/alpha)"
fi
if [ -n "$reason" ]; then echo "$pre gated $reason" > "$OUT/$pre.txt"
elif grep -q "zero duration" "$log" 2>/dev/null; then echo "$pre comp-defect zero-duration" > "$OUT/$pre.txt"
elif grep -qiE "Failure summary|error:" "$log" 2>/dev/null; then echo "$pre comp-defect error" > "$OUT/$pre.txt"
else echo "$pre unknown" > "$OUT/$pre.txt"; fi
fi
rm -f /tmp/gc-$pre.mp4 /tmp/gc-$pre.log
}
export -f worker
awk '{print $1}' "$SAMPLE" | xargs -P "$PAR" -I{} bash -c 'set -uo pipefail; worker "$@"' _ {} || true
echo "GATECHECK_DONE ($(ls "$OUT"/*.txt 2>/dev/null | wc -l | tr -d ' ') comps)"