aqua-feel sync: read a browser capture's frame rate and stalls from its frame times #33

Merged
aqua merged 2 commits from aqua/sync-vfr-fps into main 2026-11-11 02:59:52 +00:00
Member

Found by running aqua-feel sync on a real Perlines v2 capture (headless Chromium, MediaRecorder, the app's own audio output plus its canvas).

Bug. A browser webm declares no frame rate (1k tbr). readVideo's regex missed it and fell back to 30 fps silently, so a 36-60 fps capture was downsampled and every interval was reported with a 33 ms period. Separately, the interval used the resampled period, not the capture's real frame gaps; the captures stall to 34-50 ms per frame (software GL), so a stalled capture read as tightly bounded as a 60 fps one.

Fix. sourceFrames() reads the frame times (showinfo): rate = median gap (24-120), gapMs = 95th-percentile gap. syncAnalyze takes visualGapMs; the interval period is the larger of resampled period and that gap, and the report line says when the gap set it. SYNC.md updated.

Tests. sourceFrames unit test (CFR, VFR with stalls, degenerate, clamps) in the no-ffmpeg run; with ffmpeg, a 0.5 MB Perlines capture fixture asserts rate >= 50 (not the 30 fallback) and every interval at least 1.5x the capture's stall. 38 checks without ffmpeg, 48 with. No mutation pass (small standalone tool; proportional verification).

Nobody has watched or listened to any of these captures. The metric is not the verdict.

🤖 Generated with Claude Code

Found by running `aqua-feel sync` on a real Perlines v2 capture (headless Chromium, MediaRecorder, the app's own audio output plus its canvas). **Bug.** A browser webm declares no frame rate (`1k tbr`). `readVideo`'s regex missed it and fell back to 30 fps silently, so a 36-60 fps capture was downsampled and every interval was reported with a 33 ms period. Separately, the interval used the resampled period, not the capture's real frame gaps; the captures stall to 34-50 ms per frame (software GL), so a stalled capture read as tightly bounded as a 60 fps one. **Fix.** `sourceFrames()` reads the frame times (`showinfo`): rate = median gap (24-120), `gapMs` = 95th-percentile gap. `syncAnalyze` takes `visualGapMs`; the interval period is the larger of resampled period and that gap, and the report line says when the gap set it. SYNC.md updated. **Tests.** `sourceFrames` unit test (CFR, VFR with stalls, degenerate, clamps) in the no-ffmpeg run; with ffmpeg, a 0.5 MB Perlines capture fixture asserts rate >= 50 (not the 30 fallback) and every interval at least 1.5x the capture's stall. 38 checks without ffmpeg, 48 with. No mutation pass (small standalone tool; proportional verification). Nobody has watched or listened to any of these captures. The metric is not the verdict. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A MediaRecorder webm declares no frame rate (`1k tbr`), so readVideo fell back to
30 fps without saying so, and the truth interval used the resampled period, not
the capture's own gaps. A Perlines v2 capture stalling to 34-50 ms per frame read
as tightly bounded as a 60 fps one. The rate now comes from the frame times, the
interval's period is at least the 95th-percentile source gap, and the report says
when the gap sets it. Adds a 0.5 MB Perlines capture fixture and tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Soul found that an ENOBUFS-truncated showinfo list gave a plausible rate with the
stalls measured only on the file's start, and that deleting the visualGapMs line
in feel.mjs left every check green. Both now fail a test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
aqua merged commit 84aa12d36d into main 2026-11-11 02:59:52 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/AquaSynth!33
No description provided.