You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
data-motion-blur-samples / CaptureOptions.motionBlur.samplesPerFrame (packages/engine/src/services/motionBlur.ts:23,53,55,70-77) is a fixed per-render sample count, default 16, ceiling 64 (MAX_SAMPLES_PER_FRAME). Measuring the shipped feature (v0.8.45) against the accuracy bar set in #4010 (72.4 dB mean PSNR vs. the 960fps shutter integral) shows the default does not universally clear it: it depends on the content's edge hardness and displacement.
Measurements
Harness: an independent 128-sample reference per frame, built by hand (raw sub-frame window.__hf.seek(t, {subframe, suppressEvents}) calls + screenshots, averaged with a standalone accumulator — not the production accumulation code). Validated before trusting it: the production K=16 output and a hand-rolled K=16 reference built the same way are byte-identical, confirming the harness measures the real pipeline rather than a stand-in.
Composition: a single 20px-wide opaque bar on a 1536x200 transparent canvas, 30fps, default shutter (180°, phase -90°).
High-velocity (1020 px/frame, matching the registry motion-blur example's fastest row): K=16 → 15 true zero-coverage gaps across the swept region (the smear visibly breaks into separate copies). K=64 → 0 gaps (fully continuous, resolved into a smear).
At-rest: blur on vs. off is byte-identical — stays pixel-sharp.
Cases 2 and 3 match the acceptance criteria from #4010 as written. Case 1 does not.
Caveat: this is one hard-edge synthetic frame, not the original multi-frame text composition that produced 72.4 dB. A sharp, narrow, fully-opaque edge is a harder case for discretization at low K than typical content (antialiased text, softer boundaries) — this is not "the feature is broken," it's evidence that a fixed default sample count is content-dependent and can miss the bar on legitimately common content (thin high-contrast elements: text strokes, iconography, UI chrome).
Proposal
Adaptive per-frame sample count, derived from the motion magnitude measured across the shutter window (e.g. displacement of the animated properties between the window's sub-frame endpoints), rather than a fixed samplesPerFrame. Floor at the current default (16), ceiling at the current max (64, MAX_SAMPLES_PER_FRAME), so:
Static and slow-moving content pays what it already pays today, at the floor — case 3 above must stay free. Cost never drops below today's fixed 16; it only rises on the frames that need it.
Fast or hard-edged content scales up toward 64 automatically, closing the gap in case 1 without every render paying the 64-sample cost.
Open question for whoever picks this up: what "motion across the shutter window" should be measured from — screen-space displacement of the blurred element's bounding box between the window's endpoint samples is the cheapest signal and is already implicitly available since the endpoints are captured either way; whether that alone is a good enough proxy for rotation/scale-driven blur (as opposed to pure translation) needs checking against a case like case 1 but with rotation instead of translation.
Summary
data-motion-blur-samples/CaptureOptions.motionBlur.samplesPerFrame(packages/engine/src/services/motionBlur.ts:23,53,55,70-77) is a fixed per-render sample count, default 16, ceiling 64 (MAX_SAMPLES_PER_FRAME). Measuring the shipped feature (v0.8.45) against the accuracy bar set in #4010 (72.4 dB mean PSNR vs. the 960fps shutter integral) shows the default does not universally clear it: it depends on the content's edge hardness and displacement.Measurements
Harness: an independent 128-sample reference per frame, built by hand (raw sub-frame
window.__hf.seek(t, {subframe, suppressEvents})calls + screenshots, averaged with a standalone accumulator — not the production accumulation code). Validated before trusting it: the production K=16 output and a hand-rolled K=16 reference built the same way are byte-identical, confirming the harness measures the real pipeline rather than a stand-in.Composition: a single 20px-wide opaque bar on a 1536x200 transparent canvas, 30fps, default shutter (180°, phase -90°).
motion-blurexample's fastest row): K=16 → 15 true zero-coverage gaps across the swept region (the smear visibly breaks into separate copies). K=64 → 0 gaps (fully continuous, resolved into a smear).Cases 2 and 3 match the acceptance criteria from #4010 as written. Case 1 does not.
Caveat: this is one hard-edge synthetic frame, not the original multi-frame text composition that produced 72.4 dB. A sharp, narrow, fully-opaque edge is a harder case for discretization at low K than typical content (antialiased text, softer boundaries) — this is not "the feature is broken," it's evidence that a fixed default sample count is content-dependent and can miss the bar on legitimately common content (thin high-contrast elements: text strokes, iconography, UI chrome).
Proposal
Adaptive per-frame sample count, derived from the motion magnitude measured across the shutter window (e.g. displacement of the animated properties between the window's sub-frame endpoints), rather than a fixed
samplesPerFrame. Floor at the current default (16), ceiling at the current max (64,MAX_SAMPLES_PER_FRAME), so:Open question for whoever picks this up: what "motion across the shutter window" should be measured from — screen-space displacement of the blurred element's bounding box between the window's endpoint samples is the cheapest signal and is already implicitly available since the endpoints are captured either way; whether that alone is a good enough proxy for rotation/scale-driven blur (as opposed to pure translation) needs checking against a case like case 1 but with rotation instead of translation.