Repository navigation
feat(core): capture motion blur runtime lifecycle - #5967
vanceingalls wants to merge 2 commits into
Conversation
miguel-heygen
left a comment
There was a problem hiding this comment.
Reviewed exact head69441a0e01070d8bc8d0e90d6c9cdae0a750620d and the engine integration. Pause/teardown restores plain DOM; the deadline path does not.
P2 blocker at captureMotionBlur/index.ts:417–421: a released frame receiving no paint event times out into finishJob, which settles/drops pending without capture or unwrap. The root remains inside the capture canvas, output has never drawn (or contains an earlier frame), and failures remains0. The later forced engine composite cannot recover because pending is already null. The engine waits for seek completion before forcing its screenshot, so that fallback can run after this timer. Unwrap/reveal to a sharp frame or complete a verified capture on timeout, account for the failure, and cover both first-frame and previously-painted-frame timeouts. #5968's thrown-capture fallback does not repair this branch.
The independent exact-module control reproduces zero captures, root parent CANVAS, state on/failures0 after2001ms and after the forced resolver. This is a recording Canvas boundary, not a claim of a newly measured Chrome hang. Authored focused suites passed124 tests; hosted checks currently all pass/skip. No prior human finding duplicates this. The separate #5875 pose issue remains inherited. No live render, paid call, merge or release.
— Magi (Home)
Verdict: REQUEST CHANGES
Reasoning: The deadline resolves a render frame while leaving undrawn/stale output active, bypassing the advertised sharp-frame fallback.
There was a problem hiding this comment.
P2: This deadline needs the plain-frame fallback before settling. finishJob reveals movers and clears pending, but never captures or unwraps. Released engine frame + no paint event +2001ms leaves root inside CANVAS, drawElementImage never called, report on/failures0. The engine then forces a paint after waiting for seek completion, but its resolver cannot finish a now-null pending job. A first frame can have undrawn output and a later one stale output. Unwrap to plain DOM (or finish a verified sharp capture), account for the timeout, and test first/later-frame recovery. The thrown-capture repair in #5968 does not cover this branch.
f78f8a6 to
c654769
Compare
69441a0 to
9e77fa5
Compare
c654769 to
e809a44
Compare
9e77fa5 to
16b4b2e
Compare
Add the three pure modules capture motion blur is built from: pose (an element's box as an affine in composition space, and the poses between two times), discover (find the elements that move fast enough for a smear to show) and composite (redraw each mover along its path into one frame). Nothing calls them yet; the runtime that wires them up lands in the next change. The pose reads the individual translate, rotate and scale properties in CSS order, adds the offsetParent border back and subtracts ancestor scroll, so it matches getBoundingClientRect for a box centered with translate: -50% -50%. Folded review fixes: R3 (pose half; the scan's 3D skip for individual transforms lands with the mover scan). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Wire the pure modules into the composition runtime. A root that opts in with data-motion-blur wraps itself in a capture canvas and, per frame, redraws its fast movers along their paths into an output canvas. The runtime turns itself off, and says why, when the browser lacks html-in-canvas, when a WebGL or nested capture canvas is present, or when the engine's own sub-frame blur is on. It unwraps when paused so Studio can select inside. A frame whose paint never comes within 2000 ms is rendered as plain DOM (root back in the page, movers revealed), counted in report().failedFrames and never counted toward giving up. The page-side off switch, PAGE_MOTION_BLUR_OFF_GLOBAL, is exported from @hyperframes/core for the engine to set. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
e809a44 to
af29b40
Compare
16b4b2e to
254e20a
Compare
miguel-heygen
left a comment
There was a problem hiding this comment.
Reviewed exact head 254e20a5d84aafbb29dea1e9744694aa1b258326 relative to af29b40ef15eb11e1c347b1feed2a62e6b398ae0.
The no-paint deadline blocker is repaired: captureMotionBlur/index.ts:onPaintTimeout counts the failed frame, unwraps and settles; a late forced resolver cannot leave stale output active. Independent final-runtime released-engine-frame+2001ms control returns plain DOM with zero captures and failedFrames1/failures0. Own deadline branch and authored first/later-frame tests inspected. Lifecycle/pause/teardown and engine seek/composite consumer checked.
Independent revision-bound review of the nine-PR composition. Earlier approvals/requests covered older heads. Current saved head/review/inline/check snapshot has no fresh duplicate finding and no failing/pending latest check. Stacked broad-suite skips are not executed tests.
Audited: Runtime acquisition/release: wrap/pause/end/teardown, pending paint/queue, timeout and first/later frame recovery; engine seek/composite ordering.
Limits: Composed local focused run:196 authored core tests pass; separate WAAPI/CLI44 pass. Synthetic geometry/canvas controls, not fresh browser pixel/performance proof. No app/model/provider call, source fix, merge or release.
— Magi / desktop-review
Verdict: APPROVE
Reasoning: No additional blocker in this own delta. The later #5971 defect must be repaired before landing the complete stack.
The base branch was changed.

What
Wires the pure modules from #5875 into the composition runtime.
captureMotionBlur/index.ts: reads the root'sdata-motion-blurattributes (on/off,-shutter0–720° default 180,-samples2–64 default 16,-events="on"to run timeline callbacks at sub-frame samples), scans for movers once per timeline, wraps the root in a capture canvas with an output canvas after it, and draws each frame in two phases (capture the root and crop the movers, hide them and capture the background, then redraw each mover along its path).window.__hfMotionBlur.report()says whether blur is on and, when it is off, why.init.ts/entry.ts: the runtime hooks (init, seek, play / pause, and a final-frame release that the engine and CLI capture wait on), and tracking of WebGL canvases.pageMotionBlur.tsand the@hyperframes/coreexport ofPAGE_MOTION_BLUR_OFF_GLOBAL, the switch the engine sets to tell the page to stand down. Nothing sets it until feat(producer,studio): wire capture motion blur into renders and Studio #5969.A frame whose paint never comes (2000 ms after release) is not left half-wrapped: the root goes back into the document as plain DOM, the movers are revealed, the failure is counted in
report().failedFramesand logged once per 25, and the engine's late composite step finds nothing to finish. A paint timeout never counts toward giving up (failureskeeps its streak meaning) and the next frame wraps again.Blur turns itself off, and logs why, when html-in-canvas or
drawElementImageis missing, when the engine's own sub-frame blur is on, when the composition already nests a capture canvas, or when a WebGL canvas is present or appears after init.Why
Part 2 of 9 in the capture motion blur stack: #5875 → #5967 → #5968 → #5969 → #5970 → #5971 → #5990 → #5991 → #5992. It builds on #5875, which has the modules this wires together.
Stack goal: opt-in capture motion blur for compositions, in Studio preview and in renders, drawn with Chrome's html-in-canvas. Nothing blurs without
data-motion-blur="on"on the root.Notes
chrome://flags/#html-in-canvas; 157 and 158 builds expose it without a flag (whether it ships in stable is not confirmed).overflow:hidden, a fast child inside a parent that moved earlier is not blurred, SVG elements are not scanned).Testing
captureMotionBlur/index.test.ts(opt-in and off switches, each reason blur stays off, wrapping and unwrapping, and paint timeouts: a released frame whose paint never comes ends as plain DOM with the failure counted, for a first frame whose output was never drawn and for a frame after one that painted, in engine mode, plus a preview run; each guard was shown to fail with its mutation applied) andentry.test.ts(WebGL canvas tracking is installed with the runtime). The tests for engine-driven frames, painting, and unwrap-on-pause come with #5968.bun run build, coretsc --noEmitand the core runtime tests pass; comment ratchet against #5875's branch passes; core stays under its size budget.🤖 Generated with Claude Code