Repository navigation
feat(core): motion blur reports what a composition holds, as counts - #7306
Conversation
The runtime scans a composition once per blur scan, on both tiers, and reports counts of what it holds (canvases, video, frames, custom elements, clips, masks, scroll containers, 3D, blend modes, and movers that overflow, sit under other content, hold fixed children or carry pseudo-elements outside their box), plus whether pose reads need callbacks. Counts only: no ids, classes, text or URLs. The engine puts the non-zero counts on its [motion-blur] line and hands the parsed report to the perf summary; the producer aggregates workers (first report wins, failed frames sum) and the CLI sends motion_blur_* properties on render_complete, from the CLI and from Studio renders. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Verdict: REQUEST CHANGES · 1 blocker · 1 important · 1 nit
Privacy holds: only counts reach render_complete, the engine rebuilds the 14 keys, tier is narrowed to capture/filter, and both CLI and Studio go through shouldTrack/telemetryOptOut. No names or reasonCode leave.
- blocker: CI
Buildis red atverify:packed-manifests.@hyperframes/inspectorunpacked is 3,684,092 B against a 3,645,432 B budget, about 2.2 KB past the 1% tolerance. Main'sBuildis green, so this PR causes it. Raisescripts/package-size-budgets.jsonand give the reason in the body (the runtime fingerprint code is bundled into inspector), or trim. - important:
docs/packages/cli.mdx:2267lists each telemetry category. Counts of what a composition holds (videos, iframes, canvases, custom elements…) is a new category. Add it there so the disclosure matches what the CLI sends.
Scope
Read the full non-test diff and the CLI/engine/producer tests. Locally, core captureMotionBlur (181), engine pageMotionBlur (20) and cli renderMotionBlur + studioRenderTelemetry (20) pass; I did not run producer. The Docker CLI path sends no blur properties because it has no perf summary. The producer server's HTTP response and log.info carry the full summary, reasonCode included; that is not telemetry. I am deferring to a CLI-telemetry owner on the second-eyes ask. CodeQL, regression and Windows were still running.
— Genesis
|
|
||
| function parseFingerprint(raw: unknown): MotionBlurFingerprint | null { | ||
| if (!raw || typeof raw !== "object") return null; | ||
| const count = (field: string): number => Number(Reflect.get(raw, field)) || 0; |
There was a problem hiding this comment.
nit: Number(x) || 0 still lets page-supplied Infinity, negatives and fractions through. movers (line 82) gets no sanitizing at all, so NaN can reach motion_blur_movers. Clamp both to finite non-negative integers so "small integers" holds at this layer too:
| const count = (field: string): number => Number(Reflect.get(raw, field)) || 0; | |
| const count = (field: string): number => { | |
| const n = Math.trunc(Number(Reflect.get(raw, field))); | |
| return Number.isFinite(n) && n > 0 ? n : 0; | |
| }; |
Do the same for movers.
The runtime fingerprint code is bundled into @hyperframes/inspector, which puts its unpacked size 2.2 KB past the 1% tolerance. The telemetry docs now list the composition counts the CLI sends with a blur render. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 1 nit · 2 resolved, 1 open (nit) · 6ae6059 → 473b458
✅ Inspector budget raised to the measured 3,684,092 B, with the reason in the body. CI / Build is green on 473b458.
✅ docs/packages/cli.mdx now lists the composition counts under telemetry.
The Number(x) || 0 clamp nit on pageMotionBlur.ts:63 is still open and optional.
— Genesis
…view to heygen-com#7258 Adopts heygen-com#7306 (motion-blur composition counts in render_complete telemetry, adapted to discoverMovers; readEvents stays false without heygen-com#7191) and heygen-com#7318 (a set that interrupts a tween holds at any worker count) together with its merged prerequisite heygen-com#6851. PR axis advances to heygen-com#7258; heygen-com#7253 and heygen-com#7257 are open and pinned as monitored (97->99). Co-authored-by: Vance Ingalls <vance@heygen.com> Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What
Motion blur now reports WHAT a composition holds, as counts, in the render log and in render telemetry, so the next failure class shows up as a rate ("films with a mover under a panel") before anyone files a bug. Five of the six blur bugs so far came from a composition feature no fixture held.
MotionBlurFingerprint(one definition incore/src/motionBlurFingerprint.ts, imported by engine and cli):canvas2d, webgl, video, iframe, customElement, svgClip, mask, scrollContainer, threeD, overflowingMover, frontOfMover, blendMode, fixedInMover, pseudoOutside, all numbers. Computed once per scan indiscoveron both tiers, never per frame; the element walk is capped at 4096 nodes and the per-mover descendant walk at 256, so the counts are lower bounds.holdsFixedChild(now inframes.ts) andpseudoBoxare reused.Report.fingerprint(null before a scan) andReport.readEvents.frontOfMovercounts movers that something else inside the composition root sits above at any sampled scan step (elementsFromPointat the mover's centre everyceil(steps/24)steps, at most 25 samples per candidate, a counted mover is not tested again; the tier's own canvases and the filter svg sit outside the root so never count). The first version sampled once at the restore pose and read 0 for anything entering after t=0: reviewed and changed before this PR. Real Chrome 153 on a mover passing behind a panel (behind-panel): the old code printedfrontOfMover0 (and, with two capture workers, a worker-dependent 0 or 1); this code printsfrontOfMover:1in every case.formatPageMotionBlurappendsreadEvents=true(only when true) andfp=webgl:1,video:2(non-zero counts, field order); the existing[motion-blur] on reasonCode=... movers=... failedFrames=... tier=...text is byte-identical when there is no fingerprint.RenderPerfSummary.motionBlur?: { tier, reasonCode, movers, failedFrames, readEvents, fingerprint }: first reporting worker wins,failedFramessummed.failedFramesis fresh at session end because the session holds onepageMotionBlurobject thatgetCapturePerfSummaryhands out andcloseCaptureSessionupdates in place.trackRenderComplete(CLI renders and Studio renders) sendsmotion_blur_tier,motion_blur_movers,motion_blur_failed_framesand onemotion_blur_fp_<snake_case_field>per non-zero count, nothing when blur never ran.readEventsis not sent.Privacy (please check this part)
Counts only: the runtime only increments numbers; the engine rebuilds the object from the 14 known keys with
Number(x) || 0(a test feedsid: "secret"andvideo: "many"and neither gets through); the CLI iterates the same field list.moverNamesis never forwarded andreasonCodeis not sent to telemetry. The existing opt-out (trackEventreturns when!shouldTrack():HYPERFRAMES_NO_TELEMETRY,DO_NOT_TRACK) and the StudiotelemetryOptOutpath cover the new properties without extra code. Cardinality: 3 scalars plus at most 14 small integers under fixed names.Tests
core
captureMotionBlur242 pass (fingerprint: one composition per field,carries no names, the cap, the sampler cases,onFastcalled once per scan step on the first pass only), engine 80, producer 64 (vitest) + 86 (bun test), cli 219;tscclean in all four packages. A real producer render ofwebgl-canvaslogs[motion-blur] on reasonCode=tier-auto movers=1 failedFrames=0 tier=filter fp=webgl:1; a forceddrawElementImagefailure across two workers givesfailedFrames30 and 31 at session end, summed to 61 in the perf summary and in the telemetry payload (mapping run offline; the durable proof is the two-worker aggregation test and the in-place close test). Per-file flake timings 1 to 6 s.Limits
frontOfMoveris a lower bound: a covering overlay withpointer-events: none(vignette, light leak) is not inelementsFromPoint; a mover passing behind a thin cover between two samples is missed; a transparent full-size container with pointer events counts as covering.canvas2dincludes the capture tier's own canvases on a rescan while wrapped; the recorder keeps the latestfailedFramesrather than the largest (a capture-to-filter hand-off mid-session resets it to 0).Size budget.
@hyperframes/inspectorunpacked goes 3,645,432 to 3,684,092 B inscripts/package-size-budgets.json: the runtime fingerprint code is bundled into inspector, which put the build 2.2 KB past the 1% tolerance.docs/packages/cli.mdxnow lists the composition counts under what telemetry collects.🤖 Generated with Claude Code