Test the playhead while it is running - #33
Merged
Conversation
The render tests cover what lands in the file: one pass over a project with the whole of it known in advance. This is the other half. Playing is a loop that wakes every twenty-five milliseconds, reads where the video has got to, works out what audio time that corresponds to now, and queues whatever falls in the next fraction of a second. Nothing about that is a function of its inputs — it is two clocks being kept together, and it goes wrong in three ways: a cue fired twice, a cue never fired, or a playhead that stops when the picture does. Seventeen tests, driven through fake timers with the video and the audio clock advanced together: - each cue is queued once, however many ticks pass over it, and ahead of the playhead rather than on it - a cue already behind the playhead is not queued, and they arrive in the order they are placed - a muted layer stays silent while it runs - the position is published as it goes, and the transport says once that it started and once that it stopped - it stops itself at the end of the piece, landing exactly on it, and at the end of the piece rather than the end of the clip when the piece is shorter - past the last frame it keeps running on the audio clock, queues a tail that falls out there, still stops at the end of the piece, and can be seeked into and back out of with the picture held on the frame it ran out on The clock is testable because it takes all three things it depends on. The video is stubbed at the six members it reads, the engine at the three. The context is a real one with currentTime overridden in front of it: the nodes have to be real, since the clock builds a layer's graph as it plays, but the passing of time has to be ours. A live AudioContext advances on its own and was the obvious first idea; it opens an audio device, and on a machine with none — a CI runner, this container — that is a pile of ALSA errors rather than a context. Fault injection earned its keep twice here. Four faults put back — forgetting how far it had queued, dropping the handover past the picture, taking the end of the piece from the clip — failed ten of the tests between them. But removing the tick's own catch-up for a position that has jumped backwards failed nothing at all: the test I had written for it went through seek, and seek puts that marker back itself. The guard is for a position moved without going through the clock, which is exactly what the floating window's scrub bar does (video-window.ts writes currentTime directly). That case now has its own test, and it is the only thing that fails when the guard goes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ux1kydvUkLRoMbHp82ofDi
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
CI on Node 20 came back with two renders of the same project differing by 5e-22 while the same test matched exactly on Node 22 and on the machine it was written on. That is denormal-sized noise in the convolver's tail, four hundred decibels below anything that is a sound. The assertion was the problem, not the renderer. "Sample for sample the same" is a claim about a third-party implementation's arithmetic, and it is not one that implementation makes. What the test is actually for is our own seeding: that a placed sound is one sound rather than a new one each time it is heard. A billionth is about a hundred and eighty decibels down, far below the smallest step a float can take at any level anybody could hear. The fault it guards against — the voice drawing fresh noise instead of drawing it from the cue's id — moves samples by six tenths, checked by putting it back, so there are nine orders of magnitude between the threshold and the thing it has to catch. A test that passes on one machine and fails on another is worse than no test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ux1kydvUkLRoMbHp82ofDi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The render tests cover what lands in the file: one pass over a project with the whole of it known in advance. This is the other half, and the harder one.
Playing is a loop that wakes every twenty-five milliseconds, reads where the video has got to, works out what audio time that corresponds to now, and queues whatever falls in the next fraction of a second. Nothing about that is a function of its inputs — it is two clocks being kept together, and it goes wrong in three ways: a cue fired twice, a cue never fired, or a playhead that stops when the picture does.
What is checked
Seventeen tests, driven through fake timers with the video and the audio clock advanced together.
Queueing while it runs
Where it stops
Past the end of the clip
How it is testable
The clock takes all three things it depends on, so all three can be stood in for. The video is stubbed at the six members it actually reads; the engine at the three.
onCuereports every cue queued and the audio time it was given, which is the claim worth checking.The context is a real one with
currentTimeoverridden on an object in front of it. The nodes have to be real, because the clock builds a layer's graph as it plays and a stub would not catch it building the wrong one; the passing of time has to be ours. A liveAudioContextadvances on its own and was the obvious first idea — it opens an audio device, and on a machine with none (a CI runner, the container this was written in) that is a pile of ALSA errors rather than a context.Fault injection earned its keep twice
Four faults put back — forgetting how far it had queued, dropping the handover past the picture, taking the end of the piece from the clip — failed ten of the tests between them.
But removing the tick's own catch-up for a position that has jumped backwards failed nothing at all. The test I had written for it went through
seek, andseekputs that marker back itself, so the guard was never reached. It is there for a position moved without going through the clock — which is exactly what the floating window's scrub bar does, writingcurrentTimedirectly (video-window.ts:106). That case now has its own test, and it is the only thing that fails when the guard goes. The original test was renamed to say what it actually covers.231 unit tests (was 214), 55 browser tests, build clean.
The README's "what is not tested yet" now names the mixer and master chain, which the render tests exercise only as far as "something came out".
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ux1kydvUkLRoMbHp82ofDi
Generated by Claude Code