Repository navigation
test(engine): the blur trail mask covers the shutter's sweep, not only whole frames - #7624
Conversation
…y whole frames A mover that moves further than its own width between frames leaves gaps in a trail built from whole-frame blur-off poses; the shutter's smear lands in them and read as a leak outside the trail (nightly seed 63098). The mask now adds the shutter's sub-frame poses, captured in time order. Seed 63098 joins the default seeds as the regression case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 1 nit
I checked the mask by hand. The new poses are the 8 shutter samples at t ± 1/90 s. That's strictly inside the t ± 1/30 frame neighbours, so the ascending capture order holds and wholeFrames[radiusFrames] is still frame t. 8 samples is even, so no sample lands on t and whole.includes can't misfile one. At about 46 px per frame the samples are about 4 px apart, and the 2 px dilation closes those gaps. The added area is a subset of where a correct blur is allowed to land, so the outside check loses no real sensitivity. The CI run Test (runtime) ran the generated file with 4 seeds, including 63098, on Linux, and it passed.
Scope / notes
- Read in full:
differentialPage.tsand the generated test's seed default. - Not part of this PR: the producer render-path oracle (
packages/producer/src/services/render/__test_utils__/blurRender.ts:129) still builds its outside mask from whole frames only. It can't add sub-frame poses from a render. Today's hand-written fixtures pass there, but a fast, narrow mover added toFIXTURESwould hit the same gap. - I did not reproduce #7486 / seed 62660. I'm going on the PR body's statement that it's out of scope.
— Genesis
| import { INVARIANTS } from "./__fixtures__/motionBlur/thresholds.js"; | ||
|
|
||
| const SEEDS = parseSeeds(process.env.HF_BLUR_SEEDS ?? "1,2,3"); | ||
| const SEEDS = parseSeeds(process.env.HF_BLUR_SEEDS ?? "1,2,3,63098"); |
There was a problem hiding this comment.
nit: seed 63098 only reproduces #7596 while generate.ts keeps producing the same composition for it. A change to the generator (a new feature, a reordered draw) quietly turns this into a different random case, and the test still passes. If you want the check to last, add a cheap assertion that generateComposition(63098) still has the leftTop/svgClip/onUpdateMover/tlCall features, or copy the case into a fixed fixture.
What
Fixes #7596. The nightly generated-composition run failed seed 63098 (
leftTop, svgClip, onUpdateMover, tlCall) with "outside the trail at 0.2: 2 bad tiles, worst MAE 4.0" on both Chrome flavours. It is a test-oracle defect, not a blur defect.Root cause
The outside check masks the trail with
trailMaskover the blur-off whole frames at t − 2 … t + 2, dilated by 2 px.#ltis a 30 px box moving about 33–46 px per frame (552 px in 0.4 s).Measured on row 135 at t = 0.2 (Chrome 153, local):
#lt: x 147–176;A tile fails only when a static object nearby leaves enough of it unmasked to be checked. In 63098 that is
#fl, thetlCallbox. Removing#flkeeps the 209 leaking pixels but unchecks the tile. That is why only some fast, narrow movers trip it, and whysvgClipis a coincidence here.Fix
measureOutsideadds the shutter's sub-frame blur-off poses (shutterTimes(t), the same samplesmeasureInsideuses) to the mask. All times are captured in ascending order, so callback state is never seeked backwards. The reference frame and tile rules are unchanged.Evidence
1,2,3,63098). With the fix reverted those give 1 failed / 3 passed; with it, 4 passed.captureMotionBlur.differential.browser.test.tsand.differential.filter.browser.test.ts: 76 passed.tsc --noEmitand the comment ratchet pass.Not fixed: #7486 (seed 62660)
Its bad tiles (x 208–271, y 0–47) are exactly a static, non-moving
#sibling: a gradient box withclip-path:url(#genClip). Its edge differs by MAE ≤ 2 with blur on, against a floor of 1.What I did NOT exercise
🤖 Generated with Claude Code