Skip to content

fix(core): preserve fresh host-driven timeline updates - #5159

Merged
miguel-heygen merged 4 commits into
mainfrom
perf/runtime-single-seek-per-frame
Oct 7, 2026
Merged

miguel-heygen merged 4 commits into
mainfrom
perf/runtime-single-seek-per-frame

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

The runtime and the parent player both drive playback. A tick-count token could discard a host tick after the iframe had rendered an older timeline time. Cross-process scheduling and iframe throttling made that token stale.

Host ticks now refresh the clock and skip their seek only when the same timeline instance has already rendered that exact requested time. The shared seek function owns the render receipt. End-of-playback handling still runs when a seek is redundant, and explicit seeks remain unconditional.

Both frame paths use one clock-source refresh function. This also refreshes the WebAudio time snapshot when the iframe's own animation frames stop, while preserving the native-audio buffering policy.

Validation

  • Real Chromium cross-process fixture with separate page and iframe targets, native animation frames, real parent messages, and 8x CPU throttling applied only to the iframe. The fixture transitions from visible to offscreen while the parent continues ticking. Before: 112 of 241 host ticks skipped with newer timeline time, maximum stale gap 21.8 ms. Final head: 0 of 241 skipped with newer time, maximum skipped stale gap 0 ms. This is controlled Linux browser evidence.
  • Decoded-buffer audio variant uses a real AudioContext and selects the runtime decode fallback. At the final head: 242 host ticks, 27 exact-time duplicate skips, 0 skipped with newer raw time. Runtime and AudioContext progression match at 3.6687528344671207 s. Raw seek arguments avoid GSAP getter rounding; no tolerance is used.
  • Newer-host-time and WebAudio-snapshot witnesses both fail on the preceding implementation. All 42 transport tests pass three consecutive runs with no skipped tests. All 220 runtime-init tests pass.
  • Core type check, repository lint, formatting, skill lint and comment checks pass.

Exact-time equality is intentionally strict. A later clock sample must render even if an earlier seek occurred in the same display frame. This does not promise one seek per frame or retain the tick-token implementation's earlier performance claim.

Limits

The direct-timeline path does not start the parent tick clock. macOS, Windows and the desktop host have not been exercised.

Known gate red

The Studio timeline viewport gate has a known overscan regression addressed by #5151. That change is tracked separately.

No visible change

The player edit is a comment. The runtime preserves continuous host-driven timeline updates while avoiding seeks for an already rendered time.

@miguel-heygen miguel-heygen changed the title perf(core): render a playing frame once when the host also ticks it fix(core): preserve fresh host-driven timeline updates Oct 7, 2026
@miguel-heygen
miguel-heygen marked this pull request as ready for review October 7, 2026 06:58
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Edit accuracy: accurate 2059 (base branch 2059), smooth 1528 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 (0)

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at 34f726b4.

What I checked

  • Receipt in one place: the "rendered this exact time on this timeline" receipt now lives in seekTimelineAndAdapters (init.ts:4337-4339), not in hand-kept copies at the transport tick and the paused explicit seek. Every seek now records it. The paused transport tick and the host onTick both read it.
  • Where skipping can happen: the host tick (init.ts:5048) skips only on an exact t and the same capturedTimeline. The playing transport tick still seeks unconditionally, and explicit seeks never consult the receipt. So skips are limited to the duplicate case.
  • New behaviour after applySeek: it now records the receipt, so the first paused transport tick after an explicit seek no longer repeats it. The old code already did that for the capture-wait seek (the removed lines at 3941), so this generalizes an existing rule rather than adding one.
  • Clock-source extraction: refreshTransportClockSource is a straight move of the three-tier audio-clock block, which I compared line by line. Calling it from onTick too fixes the stale WebAudio snapshot when the iframe's own frames stop.
  • Reuse and simplicity: this removes duplicated logic rather than adding machinery. One receipt and one clock-source function serve both frame paths.

Tests, run locally

  • At this head, transportPark.test.ts and init.test.ts pass 262/262.
  • With main's init.ts swapped in, the exact-time dedup witness and the WebAudio-snapshot witness both fail. The rest still pass.

The PR body is honest that this does not promise one seek per frame. With a free-running clock, the host and iframe ticks usually sample different times, so both still render. That matches the stated goal of never skipping a newer time.

Verdict: APPROVE
Reasoning: The skip is limited to an exact duplicate on the same timeline, the clock-source code is a verified move, and both new tests fail without the change.

— Rames

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit 8738726 Oct 7, 2026
168 of 169 checks passed
@miguel-heygen
miguel-heygen deleted the perf/runtime-single-seek-per-frame branch October 7, 2026 08:21
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