Skip to content

test(engine): nightly motion blur run over generated compositions - #7437

Merged
vanceingalls merged 4 commits into
mainfrom
test/blur-generated-nightly
Nov 9, 2026
Merged

vanceingalls merged 4 commits into
mainfrom
test/blur-generated-nightly

Conversation

@vanceingalls

Copy link
Copy Markdown
Collaborator

What

Tests and CI only; no runtime change. A nightly run that renders generated compositions with motion blur on and checks the picture against blur off, so combinations of features nobody wrote a fixture for get looked at. It is one of the gates for flipping blur default-on in Desktop.

How it works

  • generate.ts: generateComposition(seed) builds a 320x180, 2 s, 30 fps composition from 1 to 4 of 19 features (transform, left/top mover, rotation, overflow parent, scroll container, canvas2d, webgl, svg clip-path, mask, onUpdate mover, tl.call, once-guard, CSS keyframes, nesting, pseudo-elements, panel in front, blend mode on a non-mover, fixed child, :last-child). Deterministic per seed. All extra motion sits in 0..0.4 s and 1.6..2.0 s, so every checkpoint is 3 frames from any DOM change. Sweep defects E and O are excluded from the generator (EXCLUDED_FEATURES) because they are known and pinned elsewhere. No tier pin, so auto (the filter tier since feat(core): auto motion blur draws with the filter tier #7288) is what gets tested.
  • captureMotionBlur.generated.browser.test.ts: HF_BLUR_SEEDS (list or a-b, default 1,2,3). Per seed: outside the trail at two hold frames and at a moving frame, inside the trail at the moving frame, with the same invariants as the differential suite. A failure prints seed 17 [rotation, panelInFront]: rerun with HF_BLUR_SEEDS=17. HF_BLUR_REQUIRED=1 makes a missing Chrome throw instead of skip, so the nightly cannot pass by skipping everything.
  • .github/workflows/motion-blur-nightly.yml: daily at 08:41 UTC and on dispatch with a seeds input. 200 seeds a day as 4 chunks of 50, on Chrome 153 pinned and on stable, fail-fast: false. A report job opens one issue per failing seed (deduped by exact title, capped at 25 a run), or one systemic failure <date> issue when more than 5 seeds fail.
  • scripts/ci/motion-blur-nightly-report.mjs holds the report logic (with a test run by test:scripts).

Proof it catches things

With #7293 reverted locally (document.documentElement.append(defs) back to root.append(defs)), 28 of 28 seeds in 1-200 that carry lastChild fail; on the restored runtime 199 of 200 pass. The one failure, seed 129, is not a blur defect: Chrome renders two blur-off captures of it differently about 70% of runs (a nested will-change canvas next to a clip-path sibling). So when a check fails, the test recaptures blur off on up to 3 fresh pages, and if those disagree with the first, the seed is recorded as an unstable reference (listed in the run summary, no issue). With that, seed 129 passed 24 of 24 runs, and the 28 real failures stayed real.

Failure recording and the issue job

  • Every failing seed is recorded through onTestFailed (assertion, page crash, vitest timeout) in blur-nightly-failures.json, written before Chrome is stopped, so a close error cannot lose it. A Chrome launch failure records a seedless entry.
  • A red night that left no record (killed job, no artifacts) still warns and writes the step summary.
  • Failure text goes into the log and issue bodies as untrusted data: :: workflow commands are escaped for %, CR, LF and made inert if the message contains ::, and markdown from the artifact is confined to code spans and fences. Both have tests with injection attempts.
  • The report job's checkout sets persist-credentials: false; permissions are issues: write, contents: read on that job only.

What this does not do

  • Nobody is paged: unstable seeds and launch failures show in the step summary and a warning annotation only.
  • A feature combination (not a seed) cannot be pinned as known-bad for the nightly; KNOWN_BAD is keyed by seed and ships empty.
  • The nightly's judge does not apply the "blur on is not the sharp frame" check that the differential suite has.
  • The workflow has not run on GitHub yet; I validated the YAML, the needs/if references and the jq programs locally, not with actionlint. First real run is the first scheduled one, or a workflow_dispatch after merge.

Verification

Chrome 153 (PRODUCER_HEADLESS_SHELL_PATH), --maxWorkers=2: generate.test.ts 8 pass; default seeds 1,2,3 pass in about 6 s idle and 8 s under 14 CPU burners; report test 21 pass; inside-case differential file 24 pass after rebase. Reachability, classification, comment ratchet and citation checks clean.

🤖 Generated with Claude Code

vanceingalls and others added 3 commits November 9, 2026 00:28
A seeded generator of text-free compositions combines 19 features (nested
canvas, overflow, clip path, scroll container, callbacks, a last-child rule)
and checks the two pixel invariants on auto: outside the trail at two holds
and at a moving time, inside the trail at a moving time. The runtime lane
runs seeds 1 to 3; motion-blur-nightly.yml runs 200 a day on the Chrome 153
pin and on stable, and opens one issue per failing seed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nces, caps its issues

A crash, a timeout or a launch failure is recorded before the job goes red; a
failing check whose blur-off reference disagrees with itself is reported as
unstable instead of failing; the report job moves into a tested script that
fetches open titles once, caps first, and opens one systemic issue above 5 seeds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eps a crashed leg's record

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

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 104 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)

@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 · 2 important · 2 nits · head aebfb99

The trust-boundary handling holds up. I found no injection path in the report job, so the two important findings are about signal quality. ↓ inline

Scope

Audited: scripts/ci/motion-blur-nightly-report.mjs (end to end), .github/workflows/motion-blur-nightly.yml, captureMotionBlur.generated.browser.test.ts, the differentialPage.ts diff. Trusting: generate.ts (read parseSeeds only), generate.test.ts, the report test (ran it locally: 0 fail).

Security checks that passed:

  • workflowCommand matches the toolkit's escaping (data % CR LF, properties also : ,), so failure text can't start a new command line.
  • inline code spans can't be closed because backticks are replaced. fenced uses a fence longer than the longest backtick run, so it can't be closed early.
  • Seed is checked with isSafeInteger, date must match ^\d{4}-\d{2}-\d{2}$, and flavour comes from the artifact name the workflow sets.
  • @/# text only appears inside code, so it can't ping anyone or link an issue.
  • inputs.seeds reaches the job through env and jq --arg, with no ${{ }} in run:.
  • issues: write is granted on report only, and that checkout uses persist-credentials: false.

CI: all workflows green on this head.

No earlier reviews or comments on the PR.

— Genesis

): Promise<void> {
if (first.d.bad.length === 0) return;
const disagreement = await blurOffDisagreement(chrome(), f, region, t, first);
if (disagreement === undefined) {

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.

important — An unstable reference can hide a real defect. blurOffDisagreement compares fresh blur-off recaptures only with first.reference, which comes from the reused referencePage. It never checks first.onFrame. If the reused page and a fresh page differ, which is plausible for once-guard, tl.call or onUpdate seeds after earlier seeks, every failure on that seed becomes "unstable" and goes to the summary only, however far off the blur-on frame is. Suggested fix: in the recapture loop, also compare first.onFrame with each fresh again under first.mask. Treat the result as noise only if blur-on agrees with at least one fresh blur-off. Otherwise keep it in problems.

};
}

const seedTitle = (seed) => `motion blur nightly: seed ${seed}`;

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.

important — The title dedupe never matches on scheduled runs. Each day's seeds are days*200…+199, so seed N titles never repeat. A persistent defect that hits 1–5 seeds a night opens 1–5 new issues every night. systemic failure <date> also opens a new issue daily. Suggested fix: dedupe on the sorted feature set, or keep one open tracking issue per title family and comment on it instead of creating new issues.

openIssue(gh, titles, systemicTitle(date), systemicBody(failing, date, runUrl), log);
return;
}
const entries = failing.slice(0, maxIssues);

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.

nit — The default maxIssues (25) can't take effect. This branch runs only when failing.length <= systemicAbove (5), so the cap and its warning on L206–207 only work when someone overrides --systemic-above.

flavour: [pin, stable]
chunk: ${{ fromJSON(needs.plan.outputs.chunks) }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

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.

nit — This checkout also runs third-party code (bunx vitest and @puppeteer/browsers). Add persist-credentials: false here as well, so the contents: read token isn't left in .git/config while the job runs. The repo is public, so the risk is low, but this would match the report job.

…a fixpoint

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 resolved, 4 open (non-blocking), 0 new · aebfb99 → cdba44b. The delta only addresses the CodeQL alerts: inline() now escapes backslashes first, and a new p\|q table row covers it. The test helper strips tags until nothing changes. CodeQL and all other CI checks are green on this head.

@vanceingalls
vanceingalls added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit 7bf19ab Nov 9, 2026
97 checks passed
@vanceingalls
vanceingalls deleted the test/blur-generated-nightly branch November 9, 2026 21:55
@miguel-heygen miguel-heygen mentioned this pull request Nov 9, 2026
3 of 4 tasks
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 9, 2026
…ve more; advance watermark

Twelve commits, PRs 7453-7468, issues 7457-7465 and eleven pin moves are
recorded with their evidence in the continued ledger, DECISIONS and UPSTREAM.
Adopted: heygen-com#7338, heygen-com#7459, heygen-com#7463, heygen-com#7454, heygen-com#7456 and the heygen-com#7439 docs; heygen-com#6732 was
already ported. Deferred: heygen-com#7455, heygen-com#7437. Not applicable: heygen-com#7453, heygen-com#7269, heygen-com#7464.
The watermark moves to 9e346f9, PR heygen-com#7468, issue heygen-com#7465; four new PRs are
pinned and the eleven moved pins re-pinned at their current heads. FORK.md
records the fork-only differences in the heygen-com#7338 port; the roadmap tracks the
standalone scripts that keep a bare mkdtemp and issue heygen-com#7458.

Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
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.

2 participants