Skip to content

feat(core): auto motion blur draws with the filter tier - #7288

Merged
vanceingalls merged 5 commits into
mainfrom
feat/blur-auto-filter-tier
Nov 6, 2026
Merged

vanceingalls merged 5 commits into
mainfrom
feat/blur-auto-filter-tier

Conversation

@vanceingalls

@vanceingalls vanceingalls commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

What

data-motion-blur-tier="auto" now draws with the filter tier. Capture is opt-in with data-motion-blur-tier="capture" and still runs wherever html-in-canvas exists.

Why: Rames Jusso's sweep of 60 one-pattern compositions found 15 defects, and the worst ones are in the capture tier by design: anything in front of a mover is painted over (A), blend modes are ignored (B), a selector through the root's parent stops matching (H), and the first preview frame after play() is black (N). auto used to pick capture for any rotation, scale or left/top motion, which is what Desktop preview runs. The filter tier has none of those four.

Before

k3-promo frame 274 on main, auto drawing with capture: the swinging pendulum rods and the text carry streaks.

Before

After

The same frame on this branch, auto drawing with the filter tier: the rods soften, the text blurs gently, and nothing in front of a mover is painted over (a scanline overlay in front of the bars at frame 178 survives here and is lost on main).

After

What it costs: please read before approving

  • Rotation and zoom now soften instead of streaking. On k3-promo frame 274, main (capture) streaks the swinging pendulum rods and the "Every modality" text; this branch softens the rods and blurs the text gently. On the same film at frame 178 a scanline overlay in front of the bars survives on this branch and is lost on main, and on spacex-launch frame 71 a cursor missing on main is present here.
  • The filter tier is not cheap on mover-heavy films. Render CPU seconds (user+sys of the render's process tree, 4 workers, software GPU, Chrome 153, Subtle, arms interleaved, three runs on spacex-launch, one on k3-promo; wall times were taken at a load average of 30 to 250 and are not quoted): spacex-launch (77 movers) no blur 184-193, auto (filter) 547-552, forced capture 302-320, so filter is 2.9x no blur and 1.75x capture. k3-promo (106 movers) 117 no blur, 236 filter, 205 capture. "About what a render without blur costs" holds only for the four-whip bench the guide used to cite. The cause is not isolated (likely the 32 or more feOffset copies per mover per frame on the software GPU). The guide now says all of this.
  • Known regression until the follow-up PR that fixes it (to be linked): a position: fixed child of a mover that moves via left jumps into the mover, because the SVG filter makes the mover its containing block; in the sweep 3495 px differ from the 32-sample oracle for the filter tier against 721 for capture, and before this change auto kept such movers on capture. The guide lists this and the filter tier's other measured limits (::before/::after outside the box cut off, a mover's own clip-path or mask gets hard edges, :last-child on the root's children also matches the injected svg, a visibility toggle inside the shutter drawn at full strength, backdrop-filter differing from the oracle).

Vance saw these numbers and the held-merge note and asked on 2026-11-06 for everything in this plan to be merged. Merge order: #7271 (callbacks mute), #7292 (the fixed-child fix, which removes the regression above), then this PR.

Changes

  • tier.ts chooseTier: auto and filter draw with the filter tier when the fallback is on; capture draws with capture where it can run; renderer-off stays off. The report reason code for auto is tier-auto.
  • Deleted the translation-only hand-off and translationOnly from discovery, with its tests.
  • hyperframes check: tier-auto is a chosen tier, so no fallback info; motion_blur_unverified now only for forced capture in a browser without html-in-canvas.
  • Docs, the skill line and the Studio panel label (Motion blur (Beta)).
  • Tests: tier.test.ts, checkMotionBlur.test.ts, index.test.ts, a fixtures case proving auto draws a rotating mover with the filter tier and a page that asks for capture keeps it, and a filter-tier arm (20 cases) in its own file, captureMotionBlur.differential.filter.browser.test.ts. The differential is now three files (default 51 cases, inside 22, filter 20) because the 98-case single file timed out the Flake gate's 120 s per-run limit; each file now runs in at most 37 s locally under one CPU burner per core. The filter arm skips the four fixtures that already draw with the filter tier. Filter-arm inside noise is at most 4.9 against a third of insideMaxMae (12.3), so no thresholds changed. Filter-arm inside noise is at most 4.9 against a third of insideMaxMae (12.3), so no thresholds changed. Four pixels cases that tested capture's defect detection through auto now pass "capture".
  • With only the chooseTier line reverted, the new tier.test.ts case, two core index.test.ts cases and the new fixtures case fail.

Not in this PR

  • htmlCompiler.ts detectRenderModeHints still routes every blur render through screenshot capture and its message says "draws each frame with html-in-canvas". No longer accurate for the default; follow-up.
  • Defects D, E, F, I, O on the filter tier, and the capture-tier set (A, B, C, H, J, K, L, M, N), stay pinned by the sweep tests.

🤖 Generated with Claude Code

@mintlify

mintlify Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Nov 6, 2026, 4:36 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 110 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

vanceingalls and others added 5 commits November 5, 2026 20:33
Capture stays available as data-motion-blur-tier="capture". auto now draws with the
filter tier (reasonCode tier-auto), so the capture-only defects leave the default path.
Removes the translation-only hand-off and its Discovery field, adds a filter arm to the
differential, and updates the check, guide, skill and Blur panel label.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…w auto default

spacex-launch (77 movers) takes 2.9x the CPU of no blur on the filter tier and 1.7x on
capture; say so, and list the filter tier's measured limits (sweep D, E, F, I, O, B).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s mechanism

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… gate's 120 s run

The filter arm moves to its own file and skips fixtures that already draw with the filter
tier; inside cases get their own file; a fixture's blur-off page is opened once per file.
Shared helpers live in __fixtures__/motionBlur/differentialPage.ts.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE · 0 blocker · 0 important · 1 nit · 603ffd46

This approval assumes the stated merge order. #7292 has to land first, because it removes the fixed-child regression (F) that this PR otherwise ships. #7292, #7293 and this PR merge cleanly together, and the core blur suites pass on the combined tree (215/215).

  • nit (PR body): "the follow-up PR that fixes it (to be linked)" should now read #7292. The "Filter-arm inside noise…" sentence appears twice. After the rebase onto #7271, once-guard-label is in every differential arm, so the counts are now 54/24/22, not 51/22/20. The Flake gate still passed each file 6/6.
Scope
  • I read the whole diff.
  • I checked that reverting the chooseTier line fails 3 tests (tier + 2 index).
  • translation-only has no readers left.
  • The defect reason codes that the fixture edits stopped asserting (iframe-in-root, accelerated-canvas, backface-visibility) are still covered in htmlInCanvasDefects.test.ts and in the pixels cases that now pin "capture".
  • The sweep pins its tiers explicitly, so the new default does not change it.
  • CI is all green on 603ffd46. The merge state is only waiting on review.
  • For the htmlCompiler.ts:359 follow-up: every data-motion-blur="on" render still takes the screenshot route, and the no-blur arm did not. So some of the 2.9× may come from the route rather than the filter tier. That is worth separating when the follow-up is measured.

@vanceingalls
vanceingalls added this pull request to the merge queue Nov 6, 2026
Merged via the queue into main with commit e2bb7da Nov 6, 2026
102 checks passed
@vanceingalls
vanceingalls deleted the feat/blur-auto-filter-tier branch November 6, 2026 05:42
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 6, 2026
…view

Adopts heygen-com#7294 (line_unread findings carry the measured frames), heygen-com#7271
(blur mutes discrete callbacks instead of undoing them), heygen-com#7293 (the
filter tier's svg keeps the last child), heygen-com#7292 (a mover holding a fixed
child stays sharp), heygen-com#7288 (auto motion blur draws with the filter tier)
and heygen-com#7298 (Shift no longer re-renders the timeline). The fork's seek
recovery in captureMotionBlur stays. Defers heygen-com#7134 (no packages/share)
and heygen-com#7295 (needs the heygen-com#7080 crop pipeline). Watermark advances to
66cbabc.

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
Co-authored-by: Vance Ingalls <vance@heygen.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 6, 2026
…-com#7288)

537b88d adopted heygen-com#7288, which makes auto motion blur draw with the
filter tier, but left out the engine side. Pixel tests that expect the
capture tier then failed on Windows CI. Take upstream e2bb7da's
change to captureMotionBlur.pixels.browser.test.ts, which asks those
tests for the capture tier explicitly. The other engine files that
heygen-com#7271/heygen-com#7288/heygen-com#7292/heygen-com#7293 touch (fixtures, thresholds, sweep and
differential tests) do not exist in this fork.

Co-authored-by: Vance Ingalls <vance@heygen.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 6, 2026
Re-verified the nine merged PRs in heygen-com#7266-heygen-com#7288 against fork file:line and
reverse-apply checks; none needs a new port. The six open PRs (heygen-com#7266, heygen-com#7269,
heygen-com#7281, heygen-com#7282, heygen-com#7285, heygen-com#7286) are pinned as monitored with revisit conditions;
monitored 99 -> 105.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@miguel-heygen miguel-heygen mentioned this pull request Nov 7, 2026
2 of 4 tasks

This branch was successfully deployed

1 active deployment
staging - docs — 603ffd46 Deployed Nov 6, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant