Skip to content

test(scripts): anchor the render-smoke overlap paint to the parent's clock - #2181

Merged
cliffhall merged 1 commit into
v2/fix/2177-render-smoke-startup-budgetfrom
v2/fix/2180-render-smoke-startup-independent
Aug 28, 2026
Merged

test(scripts): anchor the render-smoke overlap paint to the parent's clock#2181
cliffhall merged 1 commit into
v2/fix/2177-render-smoke-startup-budgetfrom
v2/fix/2180-render-smoke-startup-independent

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Closes #2180

Stacked on #2178 (v2/fix/2177-render-smoke-startup-budget), which touches the same test case. Merge that first; this PR's diff is only the second commit.

scripts/lib/render-smoke.test.mjs"a paint landing just under the deadline is not failed by the render timer" buys the overlap it tests with a startup allowance, and that is what makes it flake.

Why widening didn't settle it

paintAt is measured from the child's first JS tick; renderTimer is armed in the parent, at spawn. So a plain setTimeout(paint, N) lands at N + startup, and timeoutMs has to cover whatever startup turns out to be:

const paintAt = 250;
timeoutMs: paintAt + 150,   // the whole budget for spawn + Node boot

#2178 widened that allowance from 150ms to 950ms. That moves the threshold rather than removing it — the case still fails, as did not render, on any machine loaded enough to blow the new number. Which is precisely the misdiagnosis this test exists to catch, and precisely what #2180 asked to stop depending on.

The fix

Anchor the paint to the parent's clock. The child is told the wall-clock instant to paint at and sleeps for whatever is left of it when it gets there:

const spawnedAt = Date.now();
const PAINT_AT = 300;
// in the child:
const at = <spawnedAt + PAINT_AT>;
setTimeout(paint, Math.max(0, at - Date.now()));

Startup is now absorbed by the sleep instead of added to it, so the paint lands at ~300ms measured from spawn however slow the boot was.

The constants then close the remaining gap. The shape under test is an overlap, and only an overlap:

actual paint  <  timeoutMs  <  actual paint + surviveMs

timeoutMs: 600 / surviveMs: 900 satisfies the right-hand side for any paint time, including 0 — so a boot slow enough to swallow the anchor entirely still reproduces the overlap rather than degrading into some other case. The one startup assumption left is that boot completes before the deadline at all, an order of magnitude over the 46-59ms measured idle here.

It can no longer pass vacuously

A widened timing test can go green while testing nothing, so passing is not taken as evidence on its own. The first-paint time is read back out of the verdict and the overlap asserted:

const paintedAt = Number(/ at (\d+)ms/.exec(r.message)?.[1]);
assert.ok(paintedAt < timeoutMs && timeoutMs < paintedAt + surviveMs, ...);

A paint landing early enough to put the deadline outside the survival window now fails loudly instead of passing.

Verification

Mutation. Removing the clearTimeout(renderTimer) on first paint (render-smoke.mjs:267) — the line this case guards:

✖ a paint landing just under the deadline is not failed by the render timer (607.493125ms)
ℹ pass 14
ℹ fail 1

It fails at 607ms, i.e. the render timer still fires inside the survival window. Restored, 15/15 pass.

Under load. 8 consecutive runs with 16 busy loops pinning all 8 cores: 8/8 pass. (The old fixture is what failed three gate runs in a row on a far lighter machine.)

Gate. npm run local:gate green end to end.

Cost. Runtime drops, ~1.75s → ~1.2s.

Acceptance criteria

  • The case pins the deadline-vs-survival-window overlap it was written for, without depending on process startup fitting in a fixed budget
  • It passes under concurrent load, not only on an idle machine
  • The other cases in the file keep their current meaning (forbidOutput, the exit-before-paint case, the survival assertions) — untouched, 15/15

🤖 Generated with Claude Code

https://claude.ai/code/session_013hzwzS8UvBsr4yZm7o7sev

@cliffhall cliffhall added the v2 Issues and PRs for v2 label Aug 28, 2026
@cliffhall
cliffhall requested a balanced review from Copilot August 28, 2026 03:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Stabilizes the render-smoke overlap regression test by aligning child paint timing with the parent clock.

Changes:

  • Uses an absolute paint target to absorb child startup delay.
  • Explicitly verifies the deadline falls within the survival window.
  • Reduces test runtime while preserving mutation coverage.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

…clock

The case reproduces an overlap — the render deadline expiring while the
survival window is still open — and it had to buy that overlap with a
startup allowance. `paintAt` is measured from the child's first JS tick
while `renderTimer` is armed in the parent at spawn, so a plain
`setTimeout(paint, N)` lands at `N + startup` and the deadline has to
cover whatever startup turns out to be. #2177 widened that allowance
(150ms to 950ms), which moves the threshold rather than removing it: the
case still fails, as "did not render", on any machine loaded enough to
blow the new number.

The child is now told the wall-clock instant to paint at and sleeps for
whatever is left of it on arrival, so startup is absorbed by the sleep
instead of added to it and the paint lands at ~300ms from spawn however
slow the boot was. `timeoutMs: 600` / `surviveMs: 900` then satisfies
the right-hand side of the overlap for ANY paint time including 0, so a
boot slow enough to swallow the anchor entirely still reproduces the
shape rather than degrading into a different case. The one startup
assumption left is that boot completes before the deadline at all, an
order of magnitude over the 46-59ms measured idle here.

Passing is no longer taken as evidence on its own: the verdict's
first-paint time is read back and the overlap asserted, so a paint
landing early enough to put the deadline outside the survival window
fails loudly instead of passing while testing nothing.

Verified by mutation — removing the `clearTimeout(renderTimer)` this
case guards fails it at 607ms, i.e. the timer still fires inside the
survival window. Verified under load — 8/8 with 16 busy loops pinning
all 8 cores. Runtime drops from ~1.75s to ~1.2s.

Closes #2180

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hzwzS8UvBsr4yZm7o7sev
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall
cliffhall force-pushed the v2/fix/2180-render-smoke-startup-independent branch from b912308 to 2e4e76a Compare August 28, 2026 03:51
@cliffhall
cliffhall merged commit e2de318 into v2/main Aug 28, 2026
4 checks passed
@cliffhall
cliffhall deleted the v2/fix/2180-render-smoke-startup-independent branch August 28, 2026 04:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

render-smoke's under-the-deadline test is timing-marginal, and aborts the whole gate when it flakes

2 participants