Conversation
03a21ec to
0b2e4ca
Compare
be1fc2c to
0617b44
Compare
|
This comment is the measurement behind findings 36 to 44, which the PR body only summarises: what the What the first ten seconds looked like, before and afterOne publisher event per row, on a USB microphone at 48 kHz through the demo page, a Chromium watcher Publisher-side mute and unmute (
The mechanism is that the endpoint was re-applied to the run that resumed after it, so every resumed Publisher reload (
The mechanism is the demo page, not the player: it removes the Watcher reload (
The mechanism is that the convicted group had already been played. The widened warn line shows it: A closed group holding one frame, Hidden camera then mute (
The mechanism is the video watchdog reporting a stall whenever it has no rendition to decode. A Fresh publish (
There is no before row for this shape. The mechanism is that a republished broadcast on an element How it was measuredThe bench is a two-tab Playwright driver: one publisher session and one watcher session on one clock, Ten rules decide what a window is answerable for, and each replaced a rule that was charging a
Rule 10 is the largest: 58 of about 96 failing checks were one-second windows, which is what a burst A second chain of the same gates is set up on a temporary Debian 13 x64 host, as confirmation rather The congestion-control campaign, as designedThe invariant every row would grade against is the stated design: the algorithm WebRTC uses, What the tree exposes today, read rather than assumed. The only knob is
The grade per row is the hierarchy: audio never underruns and never skips a group after convergence This is test work only. No congestion control, scheduler or delivery rule is touched, and a proven Questions for you
(Written by Claude Fable 5.1) |
d05ddae to
6baa498
Compare
|
Upstream merged Not re-run yet at this tip: the full gates. They follow, and the results will be posted here. The (Written by Claude Fable 5.1) |
0b16fcb to
86e9818
Compare
A tile that was rebuilt, or whose node a page moved, played audio and never asked for video again. The relay restart row reproduced it (the watcher re-subscribed to catalog.json, meta.json and audio, never to video) and the device switch rows show the same thing once the publisher's announcement flaps. The gate is `Renderer`'s IntersectionObserver on the `<canvas>`, armed by an effect keyed on the canvas and the configured distance. Neither changes when the element's node leaves a document and comes back, so nothing re-evaluated it. A custom element may not touch its children in its constructor, so `createElement`, `appendChild(canvas)`, then insert is the ordinary order, and that arms the gate on a canvas that is not in a document yet. A page that re-appends its tiles to reorder them is one disconnect and one connect in the same task, which a boolean input would coalesce away; the gate kept whatever it last decided. With `visible` false the decoder's `enabled` is false, so `#runPending` never opens a subscription and `#runBuffering` never even labels the tile stalled: no picture, no spinner, nothing in the log. The element now republishes the canvas on every connect, forcing the notification because the object is unchanged and its place in the document is not, and drops it on disconnect so a removed tile stops downloading. Public API: none. `<moq-watch>` gains no attribute or property; the renderer's inputs are unchanged. No wire change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
A Firefox publisher put the picture 86 to 109 ms behind the sound at Chromium, Brave and Firefox watchers, where every Chromium publisher was inside a frame at every watcher. The watcher paints a frame when the playhead reaches its timestamp, so a picture that has not arrived by then is painted as soon as it decodes, which is late: sound leads picture. Nothing measured it. Each track's spread is measured against that track's own fastest recent arrival, so a track uniformly later than the other reads a spread of zero and the shared delay cannot see the difference. `Sync` now measures the arrival floor of each track (the windowed minimum of arrival minus timestamp) and publishes their difference as `out.offset`: how much later the picture arrives than the sound for the same media timestamp. It covers both halves of the cause at once, the path and a publisher that stamps its two timelines differently, because both move a frame's arrival relative to its timestamp. One-directional, deliberately: audio is the clock and video is painted when the playhead reaches it, so a picture that arrives early is already held for free and only a late one needs a term. Delaying the earlier track is what WebRTC does in `modules/video_coding/stream_synchronization.cc`. Two rotating 2 s windows, so a muted or hidden track drops out rather than holding the sound deep for the session; cleared on a rewind, as the estimator clears its own reference; quantised to a whole bucket, with less than a bucket reading zero. Spent in two places that have to move together: the audio ring holds `delay + offset + chunk`, and the age budget reaches back over `delay + offset + buffer`, because a budget sized without it convicts the group the deeper hold is waiting for. `Sync.out.delay` is untouched. It stays the estimator's answer, which is what the corpus describes and what both languages are held to; this is a property of a pair of tracks and `moq-audio` has only one, so Rust needs no change. Public API: `Sync.out.offset` is new and additive; `Sync.out.delay`, `out.jitter` and `out.maxAge` keep their meanings, with `maxAge` now derived from the offset as well. `<moq-watch>` gains no attribute. The player's "total buffer" row and the live badge report the third term. No wire change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
Measured on the mild shaper profile: during tune-in the term reached 2 s and held there for 25 s. It was not wrong about the world. The video track was still replaying the span between the last keyframe and the live edge while the shaper's queue built, so every arrival honestly looked that late. Holding the sound to match it is the wrong trade: a picture two seconds behind is out of sync whatever the sound does, and the hold just makes everything late. 200 ms, which covers the window a viewer notices (ITU-R BT.1359 puts that at 45 ms of picture-ahead and 125 ms of picture-behind) with margin over every publisher offset the matrix measured, the worst of them 109 ms. Public API: none, `Sync.out.offset` keeps its meaning and is simply bounded. No wire change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
A relay draining on SIGTERM kept accepting for its whole window and waved every new session away with an empty-URI GOAWAY. A reconnecting client took each one as the replacement, retired the predecessor that was still carrying its groups, called it a redirect, and spent its give-up budget on an endpoint that was leaving. Both native file publishers died 10s into a 20s restart with "reconnect timed out after 10s: peer redirected immediately", while the handover window they should have ridden was thrown away 56ms in. The relay now refuses a new session once the drain has started, on every transport, with the 503 a client retries. A GOAWAY naming no URI is reported as the drain it is rather than a redirect nobody asked for, and a replacement waved away before it served never displaces a predecessor that is still serving. The give-up window moves from 10s to 60s in both languages. This changes a documented default deliberately: the shipped client could not survive the shipped relay's own graceful restart, since the relay drains for 10s before its process even exits, and the browser only scraped through on timing, riding the drained session to the force-close and reconnecting on its last in-flight attempt. Past a restart the window is still deliberately short, so a failure that has not cleared is still reported rather than retried in silence. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
MoqBackoff mirrors moq_tokio::Backoff, so its window moves with it. A binding caller who never set timeout_ms could not survive a relay restart either, and the record's doc claimed to mirror a default it no longer matched. The Go wrapper resolves its own unset fields before the FFI call, since a Go zero means "retry forever" on the wire, so it carries the number and moves with it. The Dart bindings are generated but checked in; uniffi_bindgen_dart reproduces exactly this line, and the rest of its output is left alone rather than taking a formatter's churn along for the ride. The Swift, Kotlin and Python bindings are generated into gitignored paths at build time and pick up both the default and the doc line on the next build. Judgement item: the FFI default follows the native one rather than staying put, so a binding caller who never set timeout_ms now gets 60s. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
…ults The cross-browser matrix ran: 49 rows over five engines, 24 passing, and the 25 failures sorted into five product defects and the rest bench or grading artefacts. Each defect was fixed, the affected rows re-run, and the result is six new findings with the commits that answer them: the age budget convicting a group for being long rather than late, the video download gate armed once on a canvas not yet in a document, the cross-track hold that measures a picture arriving later than its sound, the Firefox publisher's blank picture on the wire, the announce that un-announced itself while a device was replaced, and the native client that could not survive the relay's own graceful restart. Two things the numbers need said beside them, and both are: the chromium publisher's picture is 117x heavier in the re-run than in the matrix it is compared against, and every timing number since mid-morning was taken under a foreign stream this session cannot pause. The relay-restart verdict is corrected here rather than left standing: the player recovers a restart completely, and what the matrix recorded as lost video was the demo page leaving the tile above the viewport. The give-up default moving from 10s to 60s is called out as a documented default change in five places, with the reasoning and the question back. One Rust test is failing at this tip and is named in "Gates on the final tree" rather than left to be found. doc/lib/js/publish.md gains the sentence the announce latch owes it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
The reconnect test for the give-up window mixed a paused clock with a real socket, and a paused clock jumps straight to the next deadline whenever the runtime waits on that socket. So the dial the peer's return had to complete raced a deadline in virtual time that could pass while the handshake was still in flight: the test read that as the client giving up and failed. It passed in the run that landed it because the whole suite was running, which changed how the polls and the jumps interleaved; alone it failed every time. The outage still runs on the paused clock, so 20s of it costs nothing, and it is still the shipped give-up default under test. What the outage proves is now read off that same clock: after 20s the loop has not stopped, which the window this default used to carry could not manage, and which no dial's progress can affect. The clock is then resumed before the peer returns, so the reconnect is real work measured against real time. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
Ten of the resilience rows ran again on a machine with nothing else on it, and the Evidence section carries that run beside the loaded one: every resilience target is met in every row, and the four rows that still read FAIL fail against the grader's single LAN envelope and its definition of a stall rather than against the player. Both file publishers rode a relay restart on their own pids, which is what finding 31's 60 s budget is for. The gates at the tip replace the red ones: the Rust failure a commit ago was the reconnect test measuring a paused clock's jumps rather than the outage, and it is fixed in the test. Finding 29's blank Firefox picture did not reproduce quiet, so it is stated as observed once. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
The user listened and watched on the bench built from the code tip on 2026-09-17, on both served pages with a real microphone and camera, and reported it clean: no stutter, no spinner on an unmute, video and audio together, and hide and show and self-publish working. The four places that said the round had not happened now say it has, with what it is worth stated plainly: one listener, one machine, one session, nothing recorded, so the measured rows stay the evidence and the ear is the confirmation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
`CORPUS_PATH` in the jitter vectors, `MIN_LAG` and `PASSIVE_GATE` in the time stretch, and `QUANTUM` and `frameSamples` in the replay harness all had no importer outside the file that defined them, so they are module-private now where they are still used and gone where they are not. The Rust side already keeps its `MIN_LAG` and `PASSIVE_GATE` `pub(crate)`. The shared ring buffer lost one latency-skip case: it filled 700 samples and asserted the read lands on the hold level, which is what the surviving 600-sample case already brackets from the other side of the stretch band. One case per side of the rule is the rule. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
Each of these described the code as it was before a later commit on this branch changed it, and nothing but the prose moves here. `js/net/src/broadcast.ts`: `register` is not the subscribing side's flag. Both wire layers reach a broadcast through a `Consumer`, so the caching is shared, and `Producer.subscribe` and `Producer.resolveTrackInfo` are local read handles rather than the publishing wire. `js/publish/src/rendition.ts`, `js/publish/src/video/encoder.ts`: the rendition track is not a demand gate. It stays open after the last subscriber leaves, so "encode only while a subscriber is attached" was never what the code did. `js/watch/src/audio/config.ts`: the advertised jitter is where the estimator starts, not a floor under it. The first resampled arrival replaces it. `js/watch/src/audio/replay.ts`, `js/watch/src/audio/replay.test.ts`: the floor is the replay harness's own term, not something `Sync` holds, so the comments attribute it to the harness. `js/hang/src/container/consumer.test.ts`: a zero max age skips a group once its successor has started, which is one group here rather than everything that is not the latest. The test name follows. `test/audio-quality/clients/js/src/probe.ts`, `test/audio-quality/clients/js/src/schema.ts`, `test/audio-quality/clients/js/analyze.ts`: the pending strings pointed at stages that have since landed. The counters exist; the browser probe does not read `audio.out.debug`, and the replay lane does. `test/audio-quality/README.md`: the cold start is not why the `auto` rows keep the marker. The target follows the path for the whole window, so where the path's worst stretch falls inside sixty seconds moves the percentiles. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
The opening's numbers are recomputed against the rebase base `877a561d8`: 111 commits and 203 files, the two extra files being `js/net/src/broadcast.ts` and `js/publish/src/rendition.ts`, which the sweep is the first thing on this branch to touch. The commit table gains the two sweep commits, and the row that said "this one" is resolved to its hash along with the listening-round commit above it, so every hash `git log --oneline upstream/dev..HEAD` prints is in the table again. "Verification and limitations" records the sweep itself: net 9 lines, no behaviour change, gates re-run green on that tree. "Open items" gains the replay harness's `floorMs`, a floor the player no longer has, which is either dropped with the replay budgets re-recorded or kept as the harness's own term. The quest bullet that quoted the demand-gate comment is corrected, because the sweep is what removed that comment. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
Two terms restart when a viewer tunes in or unmutes, and both used to land on the audio ring in one step. The cross-track hold now subtracts the lead a viewer cannot notice (ITU-R BT.1359 puts that at 45ms of sound-ahead), caps at 100ms rather than 200ms because past that a call would rather have the latency than the sync, and moves one bucket per second. At tune-in the video floor is set by the camera's warm-up frames, so the old term stood at the ceiling until both windows had rotated past them, which is the few seconds of held sound a publisher heard at the start of their own broadcast. The sound's window is carried across a mute rather than re-derived from a cold one on the unmute, the way the estimator already carries its own measurement. The ring sheds a fall in the latency target one bucket per second too. A browser publisher declares no flush span, so the estimator holds its 80ms guess until the first measurement replaces it with 20ms; spent at once, the reader compressed six pitch periods inside half a second. A fall past the reader's stretch bound still lands at once, since there it skips rather than stretches. Measured on a self-publish through the local relay, Chromium watcher: the compression in the first four seconds fell from 37ms to 16ms and from 40ms to 0.8ms across two pairs of rows, and the five rapid mute/unmute cycles from 14 accelerates and 125ms to 6 and 69ms. Zero underruns throughout. A single unmute after a three second mute is unchanged at 7 accelerates: that excess comes from the backlog the resubscription admits, not from a term moving, and is left for its own change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
One browser-style publisher (a real Broadcast with a video and an audio rendition through fake encoders) against three watchers that join and leave the two media tracks at staggered moments: A mutes audio for three seconds while B keeps it, C hides its video tile, A unmutes, B closes the tab, and D arrives after all of that. Every watcher subscribes through its own `net.consume()` handle, which is the shape the wire has: repeat subscriptions to a track share one producer, so one watcher leaving must not end the track under the ones that stayed. Asserts every live subscriber keeps reaching the same live edge on both tracks, that each newcomer is seeded with a catalog carrying both renditions, that no subscription is ever closed under a watcher that still wants it, and that the broadcast is announced exactly once for the whole run. A stability pin rather than a guard on one fix. The single-watcher regressions already have their own tests: a rendition kept open across a hide, a catalog released and re-seeded for the next viewer, a repeat subscription fanning out from the live track. Reverting either of those fixes leaves this one green, because the publisher never stops encoding here and no track ever loses its last subscriber, which is the point. It is sensitive to the multi-watcher hazard itself: dropping the weak-dedup fast path in `net.subscribe` fails it at the first drain, with B reading nothing while A reads the group. Public API: unchanged. No wire impact: a test only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
A viewer unmuting is served the live edge and the relay then follows it with every group it still has inside the age budget, whose headroom is a stretch bound wide on purpose. All of it decodes before the reader's next block, so the ring started playing 65ms deep against the 40ms it holds and the reader spent the next seconds compressing the difference away. Measured on a self-publish through the local relay, Chromium watcher: 8 accelerates and 50ms of stretched speech in the three seconds after one unmute, and 15 and 144ms across five rapid mute/unmute pairs. That is the stutter a listener hears after every unmute, and none of it was audio anyone was waiting for. Nothing has been played at that moment, so where the playhead starts is still ours to choose. It now starts at the newest audio less the level the ring holds rather than at the oldest sample buffered, and what it steps over is counted as `trimmed`, its own term beside the reader's `skipped` and the writer's `discarded`, because it is the one drop no listener can hear. NetEq reaches its target the same way at the start of a stream, by the position playout begins at rather than by accelerating into it (`decision_logic.cc`, `delay_manager.cc`). The first fill only, armed by a re-anchor and disarmed the moment the reader takes a sample: from there a surplus is on its way to being heard, and a publisher's flush burst in particular inflates the ring and drains again before the next one, so the reader's own time stretch stays what closes it. A surplus under one chunk is left alone, because a fill lands a chunk at a time. Both phases now cost 0 accelerates and no compression at all, with 80ms and 400ms trimmed, zero underruns and the playhead continuous across each unmute. The settled mic row is unchanged: target 20ms, held 40ms, level p50 20ms, zero underruns. The Rust twin is `rs/moq-audio/src/playout/engine.rs`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
A receiver tuning in is served the live edge and the relay then follows it with
every group it still has inside the age budget, so playout is handed far more
than it holds before it has played anything. The decision loop then compressed
the difference away, which is a run of time stretches a listener hears as a
fast-forward at the start of a stream.
Nothing has been played at that moment, so where the playhead starts is still
ours to choose: it now starts at the newest audio less the level playout holds
rather than at the oldest frame buffered, and the frames dropped are counted as
`trimmed`. NetEq reaches its target the same way at the start of a stream, by the
position playout begins at rather than by accelerating into it
(`decision_logic.cc`, `delay_manager.cc`).
The first fill only, armed by a re-anchor and disarmed the moment playout takes
audio: from there a surplus is on its way to being heard, and a publisher's flush
burst in particular drains again before the next one, so the decision loop's own
stretch stays what closes it. `Buffer::flush` keeps its multiple of the target
underneath, because it is protecting a cushion that is already being played out
of; the trim has no cushion to protect and lands on the hold exactly.
The browser twin is in `js/watch/src/audio/{ring-buffer,shared-ring-buffer}.ts`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
…endition A publisher hiding its camera takes the rendition out of the catalog for a few hundred milliseconds and puts the same one back. The estimator lives in the effect keyed on that rendition, so it went with it and the replacement started over at the publisher's declaration, which for video is the estimator's own 80ms guess. Sync holds every track to the widest reading, so one track's guess deepens the buffer every other track has already measured: the audio ring is resized mid-playback, parks to refill, and the listener hears the gap. Measured on the bench with five Chromium watchers on one browser publisher: at a 300ms camera hide the video reading went 20ms -> none -> 80ms within one sample grid, the shared delay followed it, and three of five watchers took an underrun and a spinner. The audio estimator never moved, which is what separated this from the unmute it happened to land beside. The estimator now outlives the effect, keyed by the rendition compared by value, so a rendition that leaves and comes back unchanged resumes what it measured. A rendition whose catalog entry changes is still a different path with a different declaration, and still starts over. Both decoders take the same rule; audio has the same defect when a microphone is hidden. Public API: none. `Estimator` is `@internal` to @moq/watch. No wire impact. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VNmEeNK6WH3V642hW7zXNw
Every relay subscription runs its own front and resume subscriber, so their fixed parts are paid once per viewer. The front's track table reserves several inline TrackIo slots, and each subscriber's segment list reserves four SegmentSubs, each carrying an inline terminal cursor and warm state. Box the table entries, keep a completed segment's cursor in the box its active cursor already had, and box the warm state only one segment of a track ever has. SegmentSub shrinks from 816 to 368 bytes and each table slot from 824 to 24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every plain cursor poll cloned its shared drift anchor under the channel's lock, although only a wrapping splice ever pushes an edge or successor into it. Without one the anchor is just the cursor's own caps, so rebuild it locally, and rule out a live edge that cannot convict a candidate before revalidating it on the track. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A poll that finds nothing to deliver no longer takes the subscription and track locks for a drift it never uses, and the drift is assembled outside the track lock. Every fanout delivery ends with such a poll. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Segment ranges ascend (a switch caps its predecessor at its own start), so the newest segment with a servable edge in range holds the logical edge. Stop there instead of locking every segment's track on each poll. The new tests pin the range, shared-group and fallback cases this relies on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A splice re-derives every segment's anchor on each poll, and pushing it costs the cursor a channel lock to find out nothing changed. The splice's own copy of what it last pushed answers that without the lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Held-group expiry already had this test; delivery did not, so dropping the revalidation in `PlainSubscriber::poll_stale` failed nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every judged delivery chooses between at most two edges; the chain, filter and max_by_key around them cost about 0.7% of a replayed delivery's instructions. Ties still go to the pushed edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A group sequence that restarts lower behind a declared break (a new upstream behind a relay) was skipped until the recovery rebuild. Only a break ahead of everything decoded resets the guard; lost content and a break behind live media still keep older GOPs out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…roup Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: GPT-6 <noreply@openai.com>
Sample query gaps once per tick so frames delivered together cannot replace the gap between source bursts. Keep the timer-only fallback and real task overrun detection, with fake-clock coverage for both probe modes. Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
State the ambiguity of a quiet source interval and the possible false flag from a wider batch, alongside direct queued-task timing. Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
Keep adaptive AAC rows on the original PES-packed source. Publish a separate single-frame AAC PES source for the fixed 250 ms control without changing its encoded audio or silence reference. Cover publisher routing and the wide-burst underrun with deterministic tests. Co-Authored-By: GPT-6 <noreply@openai.com>
Confirm a finished warm group from frame zero so the shared upstream cache retains its head for a front that has no warm copy. Preserve the splice boundary and next-frame continuation for open groups. Reproduce a second front reading the resumed catalog through the real Origin APIs. Co-Authored-By: GPT-6 <noreply@openai.com>
Use yoke-derive 0.8.4 while preserving the existing dependency edges and workspace package versions. Co-Authored-By: GPT-6 <noreply@openai.com>
Track weak references to the catch-result promises held by the effect instead of counting every Promise in the process. Preserve the 10,000-task workload and retention bound, and close the effect even if the assertion fails. Co-Authored-By: GPT-6 <noreply@openai.com>
Paused Tokio time can exhaust real loopback network timeouts before I/O completes. Keep setup, recheck, close, and end-report waits on a running clock, and explicitly advance the existing lease intervals between them. Use request notifications to await the recheck and end reports. Co-Authored-By: GPT-6 <noreply@openai.com>
Advance the existing lease cadence in 10-second steps and prove a literal frame crosses both sessions between steps. This lets real transport activity catch up before its idle deadline, while retaining the 120-second expiry and both literal Expired end reasons. Keep the media handles only for the outage test; other callers still drop them after their first round trip. Co-Authored-By: GPT-6 <noreply@openai.com>
Own the Chromium context until post-measurement trace serialization so external grade failures retain their page evidence. Verify actual pagehide delivery and successful page closure inside a saved Playwright trace through the Interop JS setup lane. Co-Authored-By: GPT-6 <noreply@openai.com>
Keep the stage-catalog assertions independent of real socket scheduling within the production completion deadline. Serve the same snapshots over local TCP with a stationary mocked clock and close both real sessions before teardown. Co-Authored-By: GPT-6 <noreply@openai.com>
Observe process-owned socket identities instead of the namespace-wide UDP table. Isolate the fixture from parallel libtest cases and require a child completion receipt, both socket identities through handle drops, and port release after shutdown. Co-Authored-By: GPT-6 <noreply@openai.com>
Attempt every browser close and fail the media verdict on unexpected teardown errors. Cover swallowed and multiple failures without changing playback timing or thresholds. Co-Authored-By: GPT-6 <noreply@openai.com>
Merge the verified fork fixes into debug-findings-solution. Co-Authored-By: GPT-6 <noreply@openai.com>
Co-Authored-By: GPT-6 <noreply@openai.com>
Observe the HTTP end event instead of sampling the request log after a fixed delay. Keep the existing assertions and bound a missing event with a test failure. Co-Authored-By: GPT-6 <noreply@openai.com>
PR #3 results after the PR #57 mergePR #57 source The authorized issue closure set is #31–#53 and #56: 22 fixes, #34 by the user's decision, and #52 through the description rewrite. The comments below are published before those issue closes. #55 gets a fix note and stays open; #54 stays untouched. Historical relay resume CPU cost increased about 21%, and both original and changed builds retain the departing-viewer RAM spike. Safari with 50 players remains unresolved; the two measured windows do not establish a fair regression verdict. Authorized fork issue closure planThe PR description retains each proving test; issue comments retain the file mapping.
Every issue test remains in pushed PR #57 source Lock and retention. Auth expiry Trace retention Shared finished-cache confirmation. Catalog browser follow-up at Historical relay CPU and RAMThe VPS relay rows measured the Linux, Miami VPS (2 vCPU): 3 alternating rounds per cell, 60 s runs, the last 30 s measured. Median [min..max].
CPU and VmHWM ranges overlap in every 3-round cell. 4x100 would saturate the live box, so it runs on the Mac. Mac M4, 4 publishers x 100 viewers: 4 alternating pairs under a CPU lease, 60 s runs, the last 30 s measured. Two more runs were discarded for foreign load on the Mac.
Idle RSS after the drain: 0.945x. Live heap after the drain: equal (2.2 MiB). Peak live heap: 102.4 to 102.6 MiB (final) against 105.2 to 115.6 MiB (original); the rest of RSS is allocator reservation. Criterion and instruction counts, moq-net benches, Mac: each family ran original then final back to back under the lease; the reversed order agrees.
2 h soak, VPS, 4x10: original, then final, each 2 h of load then 300 s idle. RSS sampled every 10 s.
1x100 teardown, VPS: 3 rounds each, RSS sampled once a second through the end of load and 15 s after.
One final round spiked to 97,200 KiB VmHWM as the load left, and settled back to 51,748 KiB within 15 s. Both builds have the upstream teardown spike: each departed front copies cached groups, about0.4 MiB/30 s. Five Mac rounds with viewers leaving first raised live heap45.0 MiB final/44.2 MiB original. Reducing it is separate work. AudioLinux importer lag, AAC fixed-250 row. From FFmpeg writing a PES into the pipe to
The Audio quality workflow, Linux runner, Chromium.
Receiver stall cadence. The Stall target passed all 18 fake-clock tests, and the full hang package passed 293 tests. In the private changing-cadence sweep, normal burst heads went from 42 to 45 false flags out of 46 to zero in all ten timer phases. Wider-burst heads stayed at 11 to 12 false flags out of 12. Real 150 and 400 ms freezes after 33 ms frame pacing still flag. A busy receiver that keeps serving reads starts sustained task-overrun flags at 132 ms after frame pacing and 198 ms after burst pacing, as before. Without a task probe, continuously served reads do not expose that busy queue. These results do not establish that every Safari AAC problem is explained. Fixed AAC source control. An observed 800 ms quiet stretch remains valid evidence. Replaying the exact quiet packet shape through the original and final playout engines produced identical underrun, skipped and concealed counters. The original matrix window missed that quiet phase, so the stretch cannot be attributed to PR #57. The quiet source batches 16 AAC frames/371.52 ms; Readrate pin. Relinking The silence proof. The silence-share metric and its ceilings are unchanged. A row over its ceiling now passes only when every quiet window is proven quiet in the source: the reference is the publisher's own encode, replayed offline and byte-exact.
Safari AAC. Plan18 original/20 actual BrowsersClosed audio graphs, Chromium 153. Codex's heap harness, 100 detach and reattach cycles per ring, before and after the teardown fix (
Reopen and reattach, the Interop media recipe with paint probes, local.
Relay rejoin. A returning lite-06/07 viewer of a paused publisher got groups from before the pause: 4 of 4 lite cases failed in Linux, VPS, headless Chromium 153 and Firefox 155, the freeze set. Original against final
30 of 30 rows passed on each build, with no voids. The comparison script flags three cells at its smallest step, one more underrun: Chromium c4, and Firefox c2 over the row and during the freezes. Two main-thread cells are worse but under its +50% threshold: Chromium c1 (17 to 20 underruns) and Firefox c3 (1440 to 1880 ms concealed). A row's browser cost the same on both builds (Chromium 0.59 cores, Firefox 0.79). The resumed Linux coverage and guarded interruption are stated below. Takeover coverage. Mac memory 12/12 on original/0df passed. The six 480 s original Actual-head long rows, original /
All six raw graders are OK; these retain the nonzero events and low FPS. Source windows differ across browsers; both Chromium windows include the known quiet passage. These are quality observations, without CPU performance attribution. Earlier 0df rows remain separately attributed. At CIAQ scope: replay 172 checks/14 rows, worker 36/24, fixed-250 isolated main 24/two rows where those lanes pass. Exclusions are non-enforced breaches, playout voids and silence exceedances accepted only with complete quiet-source proof; raw details are below.
Overall 2a Nightly 36831620398 failed: 75 successful jobs, one AQ failure, 15 skipped. The failure owner is unknown; production-source equality does not relabel any measurement as AB/a99 or erase a failure. Actual AB Interop 36835086137/job 110280467248 failed: 24 pairs passed; all eight Python-publisher pairs failed at 30 s. Python connected, announced catalog and produced audio; browser audioBytes19514/audioTS16860 advanced with running worker audio, but videoFrames0/paintedfalse. Native video reads reset Old The source/artifact census found no recurring healthy five-second actor in the reviewed app, AQ harness or Playwright code. The starvation initiator remains unknown. Worker/main comparisons differ in feeding thread/session, ring backend and source phase, so a worker PASS does not establish causation. Local movie timing does not prove CI source timing.
Actual AB Nightly remains AB, not AC evidence; AB PR Interop failed separately. The interrupted VPS diagnostic retained five 540.8–568.7 ms page gaps with zero sampled underruns/concealment. Explicit executed-block markers were lost; Opus controls never ran. The unchanged pressure guard stopped the attempt, so it has no completed quality verdict and does not explain the earlier Nightly starvation. Before the merge, PR #3 at
DF Audio quality: excluded diagnostics and complete quiet-source proofsThese raw rows remain separate from enforced-check success. No reported void is regraded or hidden.
Stopped once-only real Safari continuation: all retained outcomesBefore V6:31 coordinator rows/two partials/five untouched. After five once-only V6 rows:36 coordinated (22 OK/four strict-CSP voids/ten problems),two partial raws/all38 cells observed; none untouched. Every measured head stays original1217 or actualDF. This supplements earlier 0df coverage; it is not a full final-head matrix.
Actual 2a AQ: manual success, Nightly failure and all scoped exclusionsManual 36831617235 passed. Nightly job 110269314921 failed main control after replay/worker passed; main void count is zero. Worker voids are separate. These remain actual2a, without AB relabeling or a proven owner.
Actual AB manual AQ: all excluded readings and accepted quiet-source casesRun 36835459388/job 110281681508 passed at
Actual AB Nightly AQ: all scoped exclusions and accepted quiet-source casesRun 36835462958/job 110281696946 passed at AB: worker 10 breaches/four rows, four voids, four quiet accepts; main zero breaches/voids and one quiet accept. Whole run: 76 success/15 skip. This is AB, not AC.
Known limitsActual AC PR Test36846958575/job110319278310 FAIL: 4,769 pass/one fail/six skip/318 unrun; Linux
Not tested
V6 original50:all50 input reads complete,28 rawthreshold/22 flags (two null),onScreen18;13 video stalls/3153ms,four rebuilds,video-input FPS17.8/0fps. Audioadvance40402.2/45000ms,PCMoutput45322.4ms. Both480s long rows:audioadvance464703/464677 original and464736.3/464764 DF;PCMoutput465008.6/465023.1ms,each two audio-stalled samples. Input rates do not prove output/readiness; no subjective sound proof. DifferentDF50/original50 windows/phases prevent a regression verdict. All owned processes/ports gone; no repeated partial. Deployed build and live playbackOn October 2, the VPS installed exact D593 from the verified native bundle. Installation and separate health checks passed without rollback. The live source, native executable hashes, lite-07 publisher, unchanged configuration, 35-page build, served bundles and audio worker were verified. The first page build lacked Node; the next Astro attempt rejected an absolute configuration path. Both failed attempts remain recorded. Pinned Node and the corrected relative path passed. Cleanup removed the backed-up test tree and exactly eight test packages. Its command exited zero, but the original receipt failed after systemd reset ExecStart bookkeeping during reload. Fresh read-only reconciliation passed with unchanged original processes and configuration. The failed receipt remains unchanged; cleanup was not rerun. The guard stayed active through cleanup and stopped immediately before intentional installation. Each live Chromium path received one valid 12-second observation. Plain/isolated/site painted FPS means were 24.07/23.99/22.82, minima 23.19/23.39/7.86, maximum frame gaps 51.8/67.6/299.7 ms, and A/V skew p95 61.0/68.9/51.7 ms. Plain/isolated had zero sampled stalls, underruns or concealment; discards were 127/852 samples and jumps 2/3. Site had two underruns, 620 ms concealment, one stalled audio sample, one skip of 15,360 samples, 9,900 discarded samples and five jumps. PCM was nonzero in 25/25, 25/25 and 24/25 snapshots. Playback advanced on all paths with the worker and WebTransport; site audio quality remains adverse and its cause unknown. The site's first attempt stopped before sampling because the observer expected a full name instead of scoped The separate relay RAM experiment remains local and is not included in this PR. Shared finished prefixes reduced the measured heap growth, but the full paired CPU comparison failed: 37 of 49 cases had slower means with disjoint 95% confidence intervals. A later helper extraction improved the focused fanout checks, while two focused resume cases were still slower. Its full CPU gate remains pending; no RAM PR or upstream changes have been made. Drafted by Claude Opus 5.5. Edited and posted by Codex. (Written by GPT-6) |
|
Delivery checkpoint, 2026-10-02: the verified D593 build is deployed to the VPS and separate health checks passed. The full results and updated description are published. Issues #31 through #53 and #56 are closed; #54 and #55 remain open. PR #58 is closed without merging. Backed-up branch and clean-worktree cleanup is complete; the two dirty diagnostic checkouts are preserved. PR #3 remains open and draft at D593. The local RAM prototype remains excluded from this PR and deployment. Its recovered phase5 full comparison finished after the main results post: 33 of 49 cells were clearly slower, so the performance gate failed. A source correction is still under investigation. Safari many-player lag and the adverse site-card frame/audio observation remain unresolved; no perceptual sound or pixel-fidelity proof is claimed. (Written by GPT-6) |
|
PR #57 has measured wins and costs. The 50-player Safari workload remains at about 7 renderer updates per second on both heads. These results compare all 125 commits in the compared range; they do not identify individual commit causes or approve a new merge. PR #57 was already merged on October 1 into BEFORE: Mac results use macOS 26.6.1 on ARM64, 10 logical CPUs and 24 GiB RAM. Native browsers were Safari 26.6, Chrome 154.0.8037.97, Brave 1.96.61 and Firefox 156.0.1. Linux used an x86-64 VPS with native Chrome 154.0.8037.97, Brave 1.96.61 and Firefox 157.0. Playwright 1.63.0 used its Chromium 153, Firefox 155 and WebKit runtimes. Native Safari is unavailable on Linux. Browser names below retain the native versus Playwright distinction. Mac scaling uses three matched pairs, one shared publisher and 1/10/50 players, with 60-second rows, 15-second warmup and a 1400 by 1000 viewport. The displayed BEFORE/AFTER values are side medians. CPU changes are separately the median matched-pair ratios. Updates count public renderer timestamps, not compositor FPS; decoder-input counters count received frames, not decoded output. CPU is process CPU seconds per observed sampler second. RSS is sampled process memory, not physical footprint or energy.
Chromium, Chrome and Brave deliver similar updates while single-player CPU rises. Firefox delivers more updates at 50 players and uses more RSS. Safari was foreground; the other browser rows were headless. Safari's within-row console-lock continuity was not recorded. Its selected detailed A/V-skew p95 is 131.4 → 172.4 ms with unequal windows. Known console changes qualify affected headless pairs. The later Safari admission refusal is separate evidence and is not applied retroactively to the historical 50-player rates. All-player video observations do not establish all-player sound quality. Linux scaling from saved frozen resource reductions. Values are side medians over the same admitted matched rounds. CPU uses actual monotonic sampler spans inside the 45-second post-warmup window. Charged memory is not RSS. Public renderer updates do not establish compositor FPS. All capacity and non-admitted observations are retained separately.
Non-admitted observations remain product outcomes. These pairs have advancing complete all-player aggregates and available frozen resource windows, but a public functional oracle reports problems. The listed values remain observations, without an admission or an equal-delivery cost win.
Linux memory values above are the browser-plus-driver cgroup charge; the frozen precise reducer does not supply an RSS comparison. The maximum is sampled inside the observation window, not the cumulative invocation peak. CPU deltas cover actual monotonic sample spans inside the declared 45-second window, without endpoint interpolation or a new coverage threshold. Five main identities use WebTransport; the WebKit pair uses WebSocket. The WebKit all-player rates above (0.181 → 0.046) differ from the selected one-second smoothness mean (0.2 → 0.1); both show severe lag. Resource cost is not normalized by delivered media. Native timings/resources below use three pairs unless marked otherwise. Warm replay is one deterministic byte comparison. Model wall time, relay process CPU, requested heap storage and RSS remain separate measurements.
The unique50 stress has partial resource windows and more delivered media AFTER. It does not establish an equal-work CPU or net-cost win; source PTS at navigation is unavailable. Native 44.1 kHz gap progress improves from about 390 to 990 ms with extra pulls, so the progress repair also does different work. Sound observations below are single pairs. Controlled AAC nonzero percentages cover whole selected mono captures; underruns are post-warmup counters. These are captured signal-activity and continuity observations; the listed percentages and counters do not establish source alignment. Separate Mac sampled reference-window results follow. Linux controlled-reference diagnostics and startup/window timeline reductions are completed. No listening quality, full-capture fidelity, stereo or MOS grade is claimed.
Mac native Firefox and Playwright WebKit AAC align none of the sampled reference windows on either head. Chromium Opus bursty captures retain boundary breaks. The separate Playwright Firefox Opus step reports 0 → 1 underruns on Linux; Mac reports 1 → 1 with concealment 40 → 60 ms. Context recreation can reset counters. Memory/detach reports zero sound counters on both heads but captures no PCM; it establishes neither sound quality nor absence of a leak. Planned mute/outages and capture-clock breaks remain separate from product concealment. The BBB long pair has no matching controlled reference or listening grade. Linux controlled reference diagnostics are now completed for all 32 saved captures. They map each emitted WAV and find some matching sampled source windows, while preserving the ten original tap/context admission limits. In the near-zero AAC pair, native Firefox has 18 of 24 sampled seconds matching the reference BEFORE and 6 of 24 AFTER; Playwright Firefox has 16 of 24 BEFORE and 4 of 24 AFTER. Whole-capture RMS is −28.817 → −31.488 dBFS and −28.762 → −32.192 dBFS, respectively. Each of these four captures has one mapped segment and zero tap timeline breaks. These diagnostics sample two seconds every five seconds and include startup; they do not measure full-capture fidelity, MOS or a source-relative loss percentage. The completed timeline reductions distinguish startup from the steady observation window. The existing adverse nonzero-sample and underrun results remain. The completed timeline audit isolates nine fully steady two-second reference windows per capture: 18 sampled seconds within each declared 50-second steady interval. Native Firefox AAC has 16 → 6 matched steady seconds; Playwright Firefox AAC has 14 → 2. Two startup windows and one straddling window remain separate. These four captures record 44.1 kHz consistently for encoded input, decoded-stream signals, AudioContext and WAV, with no reported metadata reasons, timeline gaps or segment-clock residuals. This adverse sampled steady diagnostic is not confined to startup; it is not an extrapolated whole-row loss percentage, perceptual score or individual-commit causal result. Whole-WAV RMS and nonzero-sample percentages still include startup. Linux browser playback kept the same fixed cgroup limits on both heads: browser plus driver 1,536 MiB, relay 512 MiB, each FFmpeg/importer publisher 256 MiB, each page server 64 MiB, controller/samplers 128 MiB and private Pulse 128 MiB. Shaped audio additionally used a separate 128 MiB shaper. Scopes used Nice 19, zero allowed workload swap and no CPU quota. Native VPS audio/video and WAN relay tests used 1 GiB relay scopes; the native/WAN load clients had a common 512 MiB bound. Those limits and offered-link overload prevent an unrestricted host or relay-only capacity claim. The two hosts used different pinned FFmpeg versions, Mac 9.0.1 and Linux 9.0.2; each host kept its executable identical across both heads. Linux lifecycle24 completed all 24 original cases exactly once, with 96 commands and all measurements admitted. Each browser/action comparison is one pair, using 80-second definitions with 15-second warmup. Fresh-name replacement deliberately stops the old publisher for five seconds, publishes a new content name and switches the public watcher. Relay-only recovery retains the original publisher; all 12 relay-only cases have unchanged publisher member PIDs/start ticks and advancing, nondecreasing source PTS. Group-sequence continuity was not observed. Playwright WebKit used WebSocket; the other five identities used WebTransport.
The recovery field is the reported gap from a stall/rebuild to the next renderer update for events starting after warmup; it is not total wall time from the planned outage. The five non-WebKit identities each have one such recovery event per row. Relay-only recovery counters improve for Chromium and both Firefox variants, but worsen for Chrome and Brave. Fresh-name recovery counters are lower AFTER in these pairs. Neither observation establishes a repeated recovery or overall cost win. Playwright WebKit remains poor: fresh-name stalled share is 86.7% → 91.4% and relay-only is 85.9% → 91.9%. Its reported stalled-time counters are 50,981 → 53,727 ms and 53,829 → 57,663 ms, respectively. Its shorter recovery p50 values cover different event populations (12 → 7 fresh-name, 14 → 8 relay-only) and do not indicate better overall playback. Intentional outages and player recreation remain in the summaries. Negative counters are reset-qualified, not negative physical defects. No lifecycle PCM capture, listening grade or sound-quality pass is available.
The original Linux main240 definitions were attempted exactly once across the preserved 177 + 31 + 32 starts. Failures at 177 and 208 remain evidence. The final32 has 22 admissions and 10 functional/capture limitations, with no independent infrastructure failure. Linux controlled audio has 32 attempted cases, 22 capture-integrity admissions, 10 limitations and six held definitions. Admissions do not mean clean sound. Mac and Linux timing are ended, and their actual owned clean boundaries are reviewed. The explicit identity register and source-bound saved Linux resource/reference results are retained below; all final startup/window timeline reductions are completed. The strongest supported candidates for selective follow-up are Rust allocation removal, some native model/resource improvements and the favorable unique50 delivery observation. Browser CPU increases, slower resume/scan, added replay bytes and several AAC continuity results remain costs. An isolated comparison is needed before attributing a win to particular commits or calling a cherry-pick safe. These measurements provide no merge approval or new deployment. Linux unique4 has no paired playback result. The original n10 AFTER controller/source startup was attempted once and refused before browser launch because a numeric mux PTS snapshot was unavailable. Outer and controller exit were 2; the observer and source cleanup returned 0. No driver row or current proving OOM counter pair exists, so this is a source-admission/infrastructure limitation, not measured playback or a capacity conclusion. n10 BEFORE and both n50 definitions remain unattempted. No retry or continuation was run; all four planned Linux unique playback rows, forming two before/after pairs, remain unavailable. The completed Mac audit evidence is retained without repeating analysis: 384 registered playback resource-audit outputs, including 372 available windows and 12 unavailable windows, plus two supplemental stage diagnostics. There are 110 saved PCM/analyzer/timeline audits: 42 controlled-source diagnostics and 68 BBB captures with intrinsic/timeline scope. Capture integrity, sampled source alignment and continuity counters stay separate; none is an overall sound-quality pass. The 11 setup actors and original Safari prelaunch actor retain their stages and do not acquire playback jobs. The six Linux browser identities at 1, 10 and 50 players are shown above from source-bound saved resource reductions. Admitted windows and observed public functional failures remain separate. The unchanged driver caps, missing original177 outer completion, original208 runtime-verification halt and missing windows remain explicit. The final explicit evidence register conserves all 834 original definitions: 442 Mac and 392 Linux. It attaches 734 registered attempts to 723 original identities, with 111 identities having no registered actor (57 Mac and 54 Linux). Mac has 396 registered attempts: 384 playback, 11 setup and one original Safari prelaunch, plus five supplemental invocations outside the register. Linux has 338 registered attempts. These are identity and attempt counts, not passed-test or sound-quality counts. Unknown Linux stage authority remains unknown. The 58 Safari definitions have no new playback result; the original prelaunch attempt is retained separately from the 57 never-started records. Linux's unattempted identities include the stopped WebKit suffix, held WebKit multi-player definitions, held audio and the three untouched unique definitions. The long Brave BEFORE inner receipt is present. Its raw receipt has no strict admission outcome. The stopped ledger records infrastructure-unavailable admission, and the outer completion is absent. The earlier twelve Linux engine records retain unknown outer/source-stage authority. The explicit evidence register preserves these facts; private strict normalization and a newly executed all834 batch are not claimed. ROOT and an independent reviewer checked the report and complete explicit register. The raw and audit measurements retain their original authority. The exact-source API review adds Rust (Written by GPT-6) The benchmark VPS had 2 logical CPUs and 4 GB RAM. All owned test services have stopped, and the protected production services remain unchanged. |
Base: upstream moq-dev/moq@75d615dfd (moq-dev#4016, 2026-09-25), merged by
d633fc176. Includes all 67 commits since moq-dev/moq@ffa5b81b3; sync history. Later imports: moq-dev#4104, moq-dev#4103, moq-dev#4150, moq-dev#4287.Pushed
d5937f527e382fbeeb5be82973de77ec03cd77bc: 128 commits/184 files/+12,860/-8,833 from base1217ea74b; hosted proof and VPS installation/health passed; live playback observations are below. Parent C11c11d5cb0dfa1fa36d6eb4620b4abcf260dddeff8is the cache checkpoint. PR #57 source09a783a788b4354a59d1a517b6a24dd9784e9c60(125 commits) merged as9b2d051bc5661f024e59684a730d9ff4014abf99. Base contents: PR #44c7a652f3, 22 worker commits throughd8a3e898d, upstreamd633fc176through75d615dfd, 10 shaper-port commits/7 options. Inventory: upstream339 non-merge/8 current merges (9 after #57)/313 files; prior19b124/338/311. Six maintainer commits are two moq-dev#3517 fixes/four imports.HANDOFF.mdfindings1-47 remain at1217ea74b.TL;DR
offload="false"uses the page; unsupported playback falls back.moq playgets jitter buffering.releaseLock(), Firefox156 shared-memory loss, Auto-step silence (f5506ad9d), hidden-publisher freezes (91b439643) and 500 to 600 KB/s audio-thread garbage (98f475ce7). PR fix: recover playout timing and frame presentation #4 graded 71 transitions/40 camera rows with zero findings. Upstream75d615dfdadds moq-lite-07; worker re-checks had 0 freeze underruns in Chrome/Firefox versus main 14 to 15 and 10. No wire change; JS expired groups use Old0x34like Rust.Problem
Goal: continuous Auto conferencing audio on https without isolation headers, graded across tune-in, unmute, reload, rendition, hiding and page load in Chromium, Firefox and Safari. Targets are 20 to 100 ms jitter, under 150 ms LAN/200 ms relay, and one-frame A/V.
moq-dev#2812/moq-dev#3477 traced five causes: ring skip/no restart, RTT missing
bbb.hangbursts (7 frames/about162 ms), age budget reused as skip threshold, one held-target frame missing, and doubled advertised jitter (100mswaited402 ms).Approach
Why
Network, container, estimator,
AudioDecoderand ring writes ran on-page, so page blocks emptied the short ring. AudioWorklets cannot network or decode, and WebTransport cannot move threads; the worker connects separately.What it does
AudioDecoder, ring writesSync: cross-track delay, the clock video paces fromAudioContext, gesture unlock, worklet nodeOne worker/page serves every player, with one worker relay session per URL and per-player streams.
Supplymoved verbatim in561127767; replay stayed byte-identical from4c7a652f3to1217ea74b. The worker writes message rings; isolated main-thread audio retains SharedArrayBuffer. The page keepsSync, video, captions, catalog and outputs; 50 ms/ring-change reports pair arrival and output clocks, and new epochs drop stale reports.offload="false",el.offload = false, oroffload: falsekeeps a player/decoder on-page; other present values keep the default worker.When the audio stays on the page
No relay
url,Workeror Web Audio keeps audio on-page. One warning permanently falls back the page if worker start/import exceeds 5 s (ee9d7cdc2, #42), CSP blocksworker-src blob:, its realm lacksAudioDecoder/transport, it errors, or a playing worker reports nothing for 2 s. Rendition refusal, player error, unreadable worklet message or 5 s without playable output falls back that player. The warning names the stage. Fallback costs a tune-in-sized gap and was not separately measured. The worklet also needsscript-src blob:, including main-thread fallback.What it costs
Results: page freezes, local
Local macOS/Cubic/real
<moq-watch>/bbb.hang, Auto.?stall=120/1500&stallAt=32&stallFor=30makes twenty 120 ms freezes. Chrome153/Firefox156 use WebTransport; Safari26.6 uses WebSocket. Three interleaved repeats/cell, median and range, builda07f3c70a; Firefox isolated-worker is its fix check.Lifecycle, local
Same host/page, one row each, worker unless named.
?csp=1(worker-src 'none')Safari relay restart rebuilt the tile (finding 38) but its new
AudioContextstayed suspended on both paths. This predates the worker; a missing resume-result/activation trace leaves the browser-policy cause unproven.The audio-quality lane, worker against main
Headless Chromium, seeded shaper, Opus48 kHz source, 60 s rows/5 s warmup, two runs/path on
a07f3c70a; no voids.The Chromium rows grade against recorded ceilings, not enforced budgets, so these are readings. Skip-aheads in the first row of a run, which is always an isolated one, were later traced to the harness: each run relinks the
moqbinary, the next launch stalls about 2.9 s on macOS, and FFmpeg's-rethen runs about 5% fast for about 40 s. PR #57 pins-readrate_catchup 1.The trade-off: A/V sync on isolated pages
Skew p95 compares painted-frame time with the audio playhead over each row, median of three.
On plain pages, which most embeddings are, the worker path is as tight or tighter. On isolated pages it is looser in Chrome only, outside the freezes, and under the 80 ms the real-camera matrix gates on; during the freezes the main-thread path is no better (69.1 to 95.2 ms per row in Chrome). Why Chrome's worker is looser there is not established;
offload="false"makes the other choice per player.Over the public internet
After sync at
1217ea74b: same cells/freezes, Chrome153/Firefox156 WebTransport, two interleaved repeats/cell on 2026-09-26, all 36 sessions moq-lite-07. Values are freeze underruns/concealed ms, then pre-sync.The main-thread cells cut somewhat more than before the sync; with 2 rows a side on a different night, this is not attributed to the sync. Safari was not rerun on
1217ea74b.Pre-sync
d8a3e898d, same host/remote relay, Safari26.6 WebSocket, two interleaved repeats/cell on 2026-09-25, median of two.Browser findings
WebKit
releaseLock()deadlock. Safari froze 3/3: per-stream release ofincomingBidirectionalStreamscan collide with WebKit GC; a no-MoQ page hangs after about4,000 worker reads.0a5da4045keeps one session reader; regression passes, no API/wire change. Analysis; WebKit draft.Firefox156 just-handed-port loss. Isolated worklets read no shared-ring data in 6/6 busy-page attempts; Chrome153/Safari26.6 delivered all.
b8746c441uses message writes;c24fbb505falls back an unreadable player;d8a3e898dcompletes it. Firefox draft.Harness break. After the 2026-09-18 rebase, moq-dev#3719 refused
[server], so AQ Chromium never started; PRs run replay only and Nightly runs only on the default branch.76c83b472uses[listen]/bind.PR #57: fork-only fixes
PR #57 answers the 24 fork-only issues a review of
1217ea74bfiled (#31 to #53, #56): 22 fixed, #34 to close by decision and #52 by this description; it also fixes #55 in the fork. The issue fixes include targeted regression tests. No budget, timeout or duration was relaxed. The measurements are in the results comment, and each closed issue names its commit and test.340f3cdf4,227faa489,0667ad6a9,9ed7494e9,4c5a1bf5d,ee9d7cdc2,09941b9bcf3e2f8a78,10d875182,057e01bbf,ad40bf861,ad1380ba6c6c842c44,2fe47e458,294ee0306(upstream),930027366,38f75ef85,a17fe6d79,4844bc2dd,738f9f10426f71e4e7,f47fcee74,b3da59228(upstream),722f22a0de8e61e701,1ee78246d,1dfa19719,6a6ee9dd3Returning viewers. Imported fixes skip stale lite-06+/IETF caches;
break_at/breakAtmarks pauses without wire change. Reopened tracks reject pictures older than the last shown catalog time and retain that picture until their own paints (js/watch: a video track reopened after a gap paints pictures older than the one on screen #55).Lock/retention proof.
75584b85fonly replaces yankedyoke-derive0.8.3 with 0.8.4/checksum; no package/API/wire change.df3d2b444weakly counts 10,000 effect-owned catch results: normal code retains fewer than100; the no-delete mutant keeps all10,000. Signals81 tests/types/lint pass on Bun1.3.13. Production is unchanged; ARM's 8,194 extra global Promises remain unattributed.Auth test
ab075bbb0. Real-media roundtrips between 10 s virtual steps retain the 120 s lease/30 s slack and 100 ms/default timeouts. Default/jemalloc auth12 pass each; relay232/two skip; broader339/two skip; literalExpired.2a78f7425remains red:282 pass/1 fail/56 unrun, transport idle versus expiry, plus DF publisher timeout. Test-only; no API/wire change.Trace retention
a99d83dbd. Save after actualpage.close()/pagehide flush, before context/browser close. Old AB: exit0/beacon/no ZIP; fixed: DOM snapshots/completed close. CI regression only; no playback, budget, production, API/wire or cause claim.Finished-cache confirmation.
a3f1a74f8replays the newest FINISHED group from frame0 to another front, retransferring its full payload; OPEN splice is unchanged. Paused real-Origin test: oldLagged, fixed literal bytes; 1,277 moq-net tests pass. No API/wire change.Linux audio.
moq import's 64-byte blocking-pool reads delayed audio 200 to 400 ms behind Linux keyframes; now 64 KiB (ca4817459). Harness catchup is 1 (84fac1cfe); silence breaches pass only if every quiet window is quiet in the publisher encode.Receiver stall cadence.
ba8cc67capreserves source bursts under timer rationing: stable seven-frame/162 ms bursts avoid false stalls, while 371 ms gaps remain detectable. A learned 162 ms cadence can excuse a 150 ms freeze only without a pending task; a pending-task probe measures receiver wait. No public API/wire change.Fixed AAC control.
bc2680217gives fixed-250 one frame/PES; adaptive keeps seven-/sixteen-frame packing. Quiet input batches16 AAC frames/371.52 ms;-pes_payload_size 0permits13/about302 ms, while-max_delay 0gives one/PES. Packed/paced bytes match. Ceilings, timeouts and durations are unchanged; hosted outcomes follow.Browser memory. Chromium detaches retained closed contexts/worklets. Stop watch render/publish capture worklets before context close (
6b25efc3c,78923c1ea): 100 cycles retain 1, previously about 100.Relay CPU. Imported fixes caused live-edge recomputation on nearly every poll after a lite-05+ viewer parked (+18% instructions at4x10). Nine
perf(net)commits fix it and shrink per-subscriber splice state from584 to368 bytes.Cleanup. 22 unslop commits, net -959 lines: shorter comments, merged duplicate tests and fakes, wall-clock bench assertions turned into work counts plus nightly benchmarks, and no test-only files in the published
@moq/watchand@moq/hang.The relay pairs, soak and Criterion measurements below predate the finished-cache correction; they do not quantify its added full-snapshot wire payload.
1217ea74b0df56d52a: first run passed, repeat failed the fixed-250 source case; new-head gates belowEverything else in this PR
Playout target.
doc/concept/playout.mdspecifies NetEqunderrun_optimizer,histogramandpacket_arrival_historyinf64. Both languages replay 17 traces inrs/moq-audio/tests/playout-01.json(033b32070,3fb13d119,c7671d812). Auto has no RTT term/floor: rise immediately, fall once/second by one sixth or one bucket (f6bcb3ad1,1228b7308,300f9d69e); forget 0.9 per 500 ms (a5c95b91c). Exclude receiver blocks (d0296a562,e0057319d,acaee0cf6,fde9bc329,2716a1848). Expired groups (b245cd6d4) travel as Old0x34since26f71e4e7, as Rust does; count censored groups, retain audio headroom (7d83a4f19,fc3d7b484).Playout engines. Browser: maintainer commits moq-dev#3517 (
989be93de,502e93aa3); time stretch/concealment,<moq-watch conceal>default on (265789996,d43703e49,0cd6e5953,19be96d42); one extra chunk (9cbd22e0b).f5506ad9dremoves about 96 ms silence on Auto steps;98f475ce7removes audio-quantum allocations, including typed-array views sinceee66ffa88. Native: NetEq DSP/estimator/engine throughdecode::Options::delay, withmoq play --delayas floor (cd424b82e,6f25cffa0,9136e8352).A/V clock and video. The audio playhead drives the sync clock (
7e973174e,be1b87583);Sync.out.offsetholds the sound for a later picture, less a 45 ms lip-sync tolerance, capped at 100 ms (54ac2aced,dd9edf857,31d10542a); video is paced from the audio output clock on exact deadlines (802561d03,09dcd26c8), overdue groups are released against the audio playhead (06940d019,038a6f6e9,bc95d26ea), and stats report displayed FPS (98f72ad8f).Publisher,
js/publish. Captured audio is stamped on the context clock (9c15c2f73) and survives input gaps (992e2f2f5). The advertised jitter is upstream's measured value (moq-dev#3940), which replaced the static frame-duration hint, and this fork's rule that kept it from falling (6e91d86fa), in the sync. A muted track declares its endpoint (f4e5014d3), and since38f75ef85the break where new subscriptions start; a busy camera is asked again (7aecfdd9b); a hidden Firefox publisher keeps capturing video (91b439643).Watcher robustness (findings 15 to 44). The audio context is keyed on its rate, built on the first gesture and released by a detached element (
7d3e98f77,e20ed1a20,d320df497,ba83dc2f8); the arrival estimate outlives a mute (6453a0a3e,7d70c51ed,6f8ab6190); the spinner shows interruptions only (4d097eac6) and no longer takes the pointer (75280d49e); tune-in, reload and a republished broadcast happen without a conviction (44681275e); a track that stopped producing is rebuilt at the live edge (9193bcb45).Transport, relay and client. A draining relay refuses new sessions with
503, on the io_uring workers too sincef47fcee74, and the reconnect give-up moves from 10 s to 60 s in Rust, JS, the FFI and Go (e98b696b0,6d7c7efd3). Rust publishers keep a group's expiry and priority until its FIN is acknowledged (5a708ada2,55da4b4d7). A JS stream cannot be reset once its FIN is queued, so since26f71e4e7JS keeps only the priority through the FIN, on moq-lite (#44).Harness and CI. Options on upstream's seeded UDP shaper, and
test/audio-qualitywith a Chromium matrix, a real-Safari lane and a deterministic replay lane (a7b742ad7,ebe96d1d5,5b524ac0b). TheAudio qualityworkflow runs the replay lane under--enforceon pull requests that touch the audio paths, and the Chromium matrix on demand and from the Nightly (7293200c1,b4addef64); since0251594baa second pass runs the shared ring, which the worker's message ring had kept out of every row.Upstream syncs. Merged without rebase since 2026-09-20:
d2f881bbb,a93fdb8e8,953b79ebc,7ee2b0241,ffa5b81b3,75d615dfd. Last two: missing-group moq-dev#3973, warm-group moq-dev#3977, demand-gap moq-dev#3982, moq-lite-07 moq-dev#4060, catalog jitter moq-dev#3940,rs/moq-shapermoq-dev#4054. Overlaps use upstream. Playout/rings/worklet/audio-worker/js/hang/src/container/rs/moq-audio/src/playout/corpus matchd8a3e898dexceptsupply.tstoeffect.race; PR #57 later changes some.The shaper. The merge took upstream's crate, and what the audio-quality lane needs came back as options, each off by default: TCP passthrough, a gaussian jitter that keeps datagram order, a shared link, batches, steps, TOML profiles and a JSON report. Against the old binary on the same traffic, delays, loss, ordering and batch sizes matched within the difference between two seeds, except about 0.05 ms more on
near-zero. Sincee8e61e701it refuses--report-interval 0sand verifies each step against its own traffic (#47).Earlier measurements
Before the audio worker: one macOS host, a loopback relay, headed Chrome, speech through the fake microphone with processing off, WebTransport, the plain page; medians of 3.
91b439643andf5506ad9d)Publish start and word onsets (build
91b439643): after a publisher reload, the listener's audio graph opened after the first utterance began in 13 of 21 starts; with Opus DTX on, Auto sat at a median 660 ms and 18 of 79 word starts were clipped; browser microphone processing at Auto clipped 37 of 83 word starts at capture. Older rounds are in the edit history and inHANDOFF.mdat1217ea74b.Alternatives
91d215091/c0bbc9070(reverted): 20 underruns became 7 or 1, but median targets rose to 60/40 ms for a 20 ms path.c1f899ee4(reverted by5928fe324): fix(hang): play the head group once a missing group is proven too old moq-dev/moq#3973 owns the measured case; no browser measurement covers its nothing-presented and later-proven-hole edges.moq_bg.wasm562 KB gzip), decode-only worker (network stays on page), and worklet WASM playout (cannot fetch/decode).breakhandles it without wire change.Notes for the maintainer
moq-dev#4067 moves watch/
Syncinto a worker and refuses missing APIs; this PR keepsSync/fallback on-page for https without special headers. Maintainer choices remain: public Rustbreak_at/JSbreakAtcannot inform already-subscribed relays without wire change; AQ pins-readrate_catchup 1while upstream demo uses-re; publicdecode::Consumer::{delay,playhead}have no callers.Estimator versus upstream's planned spec (
doc/concept/audio-jitter.md, moq-dev#3883, and thequest/m0/audio-jitter-targetquests). This requires a deliberate choice:doc/concept/playout.md)a5c95b91c)max(measured, advertised)kStartDelayMs) or the declared spanstalledinput drops the arrival referencers/moq-audio/tests/playout-01.jsondoc/concept/audio-jitter/Judgement calls: concealment differs from the quests (behind
conceal); audio age headroom (fc3d7b484) can be dropped independently; finding 13 could live injs/net; the fall bound and the forget factor are not NetEq's; reconnect give-up changes10 s to60 s in five places for the relay's10 s drain; the worker is on by default and falls back instead of refusing;Synctakes the widest spread across tracks; and #34's player wait is kept on purpose.Follow-ups
blob:worklet (js/watch: an AudioWorklet from a blob: URL is refused under script-src 'self', so no audio plays #5, upstream code, proven in headless Chromium): underscript-src 'self'the render worklet never loads, so no audio plays. The worker's ownblob:is handled.AudioContextand worklet node per detach in Chromium; every other lifecycle state leaves nothing behind.-f mpegts -pes_payload_size 0publish line holds the fade to digital silence at 461.2 to 461.9 s for up to 0.35 s;-muxdelay 0bounds it to one frame (docs, demo: the recommendedffmpeg ... -f mpegts -pes_payload_size 0 - | moq import tsline holds quiet audio back for up to 0.35 s #54, upstream docs and demo).Test changes, stated openly
33c827c7c):a_peer_draining_every_session_names_the_drain,rs/moq-tokio/tests/reconnect.rs, advances after drain. Keep give-up assertions; reject post-budget dials, require Connected then Migrating, retain 50 ms backoff. Real time failed 4/100; paused passed 300/300. Product quirk rs/moq-tokio: when the retry budget ends mid-dial, the give-up error names the dial timeout instead of the cause that used the budget #14 remains.f777ea1db): retain p999 below 0.67 ms for both paths; remove stretching/concealing ratios below 30 times plain. Allocation-free plain quanta made the ratio a load reading (37 to 67). PR fix: resolve the fork-only issues on PR #3 #57 moves the same budget to a nightly benchmark below.f5506ad9d(ceb21e9ce,063a14d0c): plain-ring ceilings rose when silence became stretching; stale ceilings fell to measured values.Retry.refusedretries busy devices (fix(publish): report capture refusal and withhold partial broadcasts moq-dev/moq#3934); refused capture warns (7655be24b) because upstream media fails on console errors. Revert5928fe324keeps the shared fix(hang): play the head group once a missing group is proven too old moq-dev/moq#3973 test. Syncd633fc176removes two static-jitter tests with their hint andunlock.test.tswithunlock.ts; zero-timestamp capture asserts at/after gesture. Old shaper: 36 tests removed; new: 40, including upstream's 8.5c88f1f4c); isolated cases use message rings (b8746c441); host tests use fake timers (5dbb5e33c).buffer.test.tsimports freshreportTransport, requiring exactly one info line instead of at most one. It andsync.replay.test.tssetshared: falseinstead of deletingSharedArrayBuffer.75d615dfdto1217ea74b). API/design changes above explain most.js/hang/src/container/consumer.test.ts: "Consumer skips groups via PTS-span when over the max age" is now "Consumer plays every group under a zero budget when the reader keeps up", and assertsskipped == 0where it expected a skip to the latest group (44681275e). Two other tests gained assertions: no warning on a covered skip, and the endpoint delivered on its run's playhead.js/net/src/error.test.ts: "fromTransport: a stream reset keeps the peer's code verbatim" uses code0x34(Old) instead of2(DeliveryTimeout), because DeliveryTimeout now decodes toExpired(b245cd6d4). Two other tests gainedExpiredassertions.js/net/src/{ietf,lite}/publisher.test.ts: "a blocked group header is reset when the group expires" is atest.eachover header and FIN (5a708ada2).js/watch/src/audio/config.test.ts:playbackJitterexpectations drop the 128-sample quantum (23 to 20, 63 to 60, AAC 25 to 22 and 49 to 43, MP3 27 to 24 and 30 to 24); "an unknown codec without advertised jitter only reserves the worklet quantum" (3 ms) is now "... reserves nothing" (0 ms).js/watch/src/audio/latency.test.ts: bothreanchorFloortests went with the function.js/watch/src/audio/ring-buffer.test.ts: "should wrap around when buffer is full" removed, 21 changed.js/watch/src/audio/shared-ring-buffer.test.ts: "should skip READ when buffered exceeds LATENCY" and "should not skip when buffered is within LATENCY" removed (the ring no longer skips;4e6d70e82), "should un-stall when buffer reaches LATENCY" renamed "should un-stall when buffer reaches LATENCY plus a chunk", 27 changed.js/watch/src/sync.test.ts: "includes registered decoder jitter until the decoder unregisters" and "unregisters duplicate jitter inputs independently" went withSync.register.rs/moq-cli/src/play/args.rs"the_delay_is_bounded_by_the_speakers_ring" is replaced by "the_delay_is_bounded_by_what_playout_can_hold" (9136e8352); twoplay/timeline.rstests lose theirsilenceassertions with the field;rs/moq-tokio/src/connection.rstest_backoff_defaultexpects 60 s.js/watch/src/media.test.ts,video/renderer.test.ts,js/publish/src/audio/capture.test.ts(its second changed test),source/retry.test.ts.1217ea74btoa3f1a74f8) changed these tests again and added others:js/net/src/error.test.ts: the verbatim-code test now uses0x35(Evicted), since0x34decodes toExpired(26f71e4e7, js/net: an expired group is reset with DELIVERY_TIMEOUT (0x2) where Rust sends Old (0x34), and @moq/json treats 0x2 as fatal #46); theExpiredassertions follow Old, and a new test pins that0x2is notExpired.js/net/src/{ietf,lite}/publisher.test.ts: the FIN case went with the FIN guard (26f71e4e7, js/net: the expiry guard held through a group's FIN never resets anything, and its per-group subscriptions trip the dev subscriber cap #44), leaving a one-casetest.eachover the header; the lite test lost its "lite draft-05:" prefix.js/net/src/track.test.ts: "a guarded write handles its rejection when the group ..." became "a guarded write never starts when the group ...".js/watch/src/audio/rings.test.ts(393928889).shared-ring-buffer.test.ts"truncates to the newest samples when shrinking below the unread span" became "refuses shrinking without changing buffered media": the shared ring only grows (74aaab832).stretch.bench.test.tsandworker/remote.bench.test.tsasserted wall-clock costs and failed under load; they became count-based tests plus benchmark scripts the Nightly runs (63f82dfad,a10efb9cd).allocation.test.tsnow also holds the stretching run to zero typed-array views (84 before,ee66ffa88).rs/moq-muxTS import tests and themoq-videoseam test subscribe before the declared break, as a live viewer would, because a subscription made after it starts at the marker (a17fe6d79).a_declared_endpoint_then_resumed_media_decodesis no longer ignored (rs/moq-audio: native playout conceals through a declared pause instead of parking on the endpoint #33);decoder.test.tsruns on fake timers (js/watch, js/publish: three PR #3 tests race the real clock and fail under load #53); js/watch, rs/moq-mux: low-severity efficiency and latent defects (clock wakes, partial output timestamp, spurious waker, worker-mode state traffic, ring built before timing) #39's worker-state test was folded into the port test with the same assertion (faea8b975); fakes are shared (1203d1f31), the stall monitor is stubbed instead of spinning the CPU (e62a519da), and fake worklet processors stop after close as an engine does (36bcd4b89).Impact
Wire. No format change over upstream, whose moq-lite-07 (moq-dev#4060) the base speaks. The hang draft and
doc/concept/hang.mdadd semantics only: a publisher that stops capturing SHOULD declare an audio endpoint and SHOULD keep the rendition in the catalog meanwhile, and an endpoint alone in its group is the discontinuity marker. A draining relay refuses new sessions with503on every transport, io_uring included. Rust publishers hold a group's expiry and priority until its FIN is acknowledged; JS publishers hold only its priority, on moq-lite. A JS publisher resets an expired group with Old (0x34) on moq-lite, like Rust, where it sent DELIVERY_TIMEOUT (0x2); IETF drafts have no Old, so there it now sends INTERNAL (the moq-lite draft says so). The audio worker changes nothing on the wire; a page opens one more session per relay URL.New in PR #57 (additive)
moq-net:track::Producer::break_at(sequence), and@moq/net:Track.Producer.breakAt(sequence). A subscription made afterwards starts atsequenceat the earliest, however far back its budget reaches; a fetch still reaches the groups before it. A break only moves forward.@moq/hang:Container.Legacy.writeMarker(group, end)writes the endpoint marker as the whole group and closes it.moq-tokio:Client::close()closes the client's QUIC connections once each peer was sent the close (fix(cli): close the relay connection on SIGINT and SIGTERM moq-dev/moq#4287).moq-muxcontainer::Producer::discontinuitydeclares the track's break, so a late joiner starts at the marker;Container.Consumercounts a stale group dropped behind returned media inskipped, with no discontinuity;moq-shaper --report-interval 0sis a usage error;@moq/watchand@moq/hangship no test-only files.New with the audio worker (additive, except that the default is on)
<moq-watch offload>andMoqWatch.offload: on by default;"false"or"0"turns it off (b1f593d6f).PlayerInput.urlandPlayerInput.offload,Audio.DecoderInput.urlandAudio.DecoderInput.offload(86a88a099);offloaddefaults totrue(5c88f1f4c), and aPlayerwithout aurlkeeps its audio on the page.Sync.received(timestamp, label?, at?): the optionalatis when the frame arrived, and without it nothing changes (7aeb42c41).Audio.Decoder.out.thread,@internal:{kind: "worker"; transport} | {kind: "main"; reason?}, undefined while the worker starts (5c88f1f4c).audio/supply.ts,audio/worker/*,RingTarget,audio/supported.ts, and the worklet's second port andunreadablemessage.0a5da4045changes no@moq/netAPI.Breaking, or a changed default, relative to upstream
main(earlier in this PR)@moq/watch:Sync.register(jitter)is replaced bySync.track(name);SyncInputlosesprobe, since Auto no longer reads the round trip; a numericdelayis the whole delay, with no advertised flush span added to it.moq play --delayis a floor capped at 2 s, where it was the delay capped at 10 s.@moq/netReloadDelay.timeout(60000 ms),moq_tokio::Backoff::timeout(with--backoff-timeout,MOQ_BACKOFF_TIMEOUTandconnect.backoff.timeout),MoqBackoff.timeout_usin the FFI (60000000, regenerated for Dart) and the Go wrapper. A GOAWAY naming no URI is reported aspeer is drainingand redialed, not treated as a redirect.announce="source"latches after the first live track until the selected source changes, so a page that relied on an unannounce when every track stopped no longer gets one.Additive (earlier in this PR)
@moq/net:Expired(Moq.Error.Expired), for a group the age budget gave up on; a peer's Old (0x34) reset decodes to it (a peer's DELIVERY_TIMEOUT did, before PR fix: resolve the fork-only issues on PR #3 #57).@moq/hang:Container.Jitter(new Jitter({ start }),observe, staticBUCKETandCEILING);ConsumerProps.jitter: Time.Milli | Jitter;Consumer.spreadandConsumer.skipped.ConsumerProps.maxAgekeeps its type and takes the wire budget's meaning.Container.Jitteris new relative to upstreammain; relative to the closed fix(watch): size the auto latency from measured arrivals and re-buffer the audio ring moq-dev/moq#3517, where it came from (989be93de),observe's third argument became{ reordered, stalled }(fde9bc329).@moq/watch:Sync.track(),Sync.out.clock,Sync.out.offset, and theClock,SyncTrackandSyncClockTrackexports;Audio.Decoder.out.{spread, underruns, skipped, interrupted};Video.Decoder.out.{spread, skipped};Audio.DecoderInput.concealand<moq-watch conceal>;Audio.DecoderInput.attached.@moq/publish:Fanout.ended,Video.Capture.out.stopped,MoqPublish.errorsand the.control--errorclass.moq-audio:decode::Options::{delay, conceal, DELAY_MAX}(Optionsis#[non_exhaustive]) anddecode::Consumer::{delay, playhead}. Withdelayset,Consumer::readreturns 10 ms playout blocks instead of decoder packets.moq-relay:Shutdown::draining().moq-shaper(upstream's crate,publish = false):Setup,Options,Jitter,Batch,StepandPreset, and the flags--tcp-passthrough,--jitter-model,--shared,--batch,--batch-window,--profile,--reportand--report-interval.@moq/audio-qualityworkspace.Internal, or behaviour only:
Audio.Decoder.out.debug(@internal, its snapshot carriestrimmed), the stall monitor (not exported from@moq/hang), the per-renditionEstimatorandsubscribeMedia'sstart(both@internal),audioMaxAgeandmaxAgeHeadroom(module-level), the rings' internals (the shared ring only grows since PR #57), the worklets'closeandstoppedmessages,moq-tokio's crate-privateNoqClient::close(), and the MPEG-TS importer, whose emitted timestamps change by the fractional sample remainder it now carries and which reads stdin 64 KiB at a time.Where the 311 files are:
js/watch81,js/publish31,test/audio-quality30,rs/moq-audio20,js/net17,js/hang15,doc14,rs/moq-shaper13,rs/moq-net12, and 78 elsewhere.rs/hangis untouched.The commits
Audio worker, 22 commits on
4c7a652f3: second port271715448; WebKit0a5da4045; verbatimSupplymove561127767; protocol/pooled worker971c74143,464e8dd27; default/fallback5c88f1f4c; Firefoxb8746c441,c24fbb505,d8a3e898d.Sync: a merge/10 commits on
d8a3e898d.d633fc176merges through75d615dfd, recording conflict resolutions. Seven shaper options:bbc2822b9,8ace4b8b4,2a0432d71,f684aa6f4,34c6ce052,da0c96f23,7c09168f6; then5cc94fb59,6485da9f7,1217ea74b.PR #57, 125 commits, linear on
1217ea74b, merged by9b2d051bc5661f024e59684a730d9ff4014abf99: four imports (c6c842c44,2fe47e458,294ee0306,b3da59228); issue fixes (1ee78246dto9ed7494e9,057e01bbf,97d712ea8,14bd9dd25); reattach/publisher break (7f02213e7toa17fe6d79,2c34a8b20,0df56d52a); Linux audio/quality (ca4817459,84fac1cfe); worklet teardown (6b25efc3c,78923c1ea); nineperf(net)commits; 22 unslop commits (6d179bf26toe9ce90f3b).Thirteen takeover commits: CSP docs (
57bdcc8dd), Stall cadence (ba8cc67ca), heuristic limits (6656034c6), fixed-250 source (bc2680217), finished-cache fix (a3f1a74f8), audit lock (75584b85f), task-retention proof (df3d2b444), auth-clock candidate (2a78f7425), active expiry fixture (ab075bbb0) trace retention (a99d83dbd) selection clock fixture (ac6461ae2) socket oracle (19bcd9d63) and cleanup errors (09a783a78).Gates
Each gate retains its actual head:
Before the merge, PR #3 at
1217ea74bconflicted withmain; its old Interop failure remains historical. PR #3 stays open and draft at D593 and conflicts with currentmain. Final checks verify D593 against the original1217ea74bbase through PR58; they do not prove a merge into currentmain.AQ: replay172 checks/14 rows,worker36/24,fixed-250 isolated main24/two,where passing. Excluded metric breaches/voids and complete quiet-source accepts are distinct; raw details in results comment.
bc2680217manuala3f1a74f8Nightly 36819237894 AQyoke-derive0.8.3, fixed by lock update.df3d2b444manual 368226586622a78f7425manualAQ 36831617235ab075bbb0manualAQ 368354593882a Nightly 36831620398 failed: 75 successful jobs, one AQ failure, 15 skipped. Owner unknown; source equality does not relabel measurements as AB/a99 or erase failures.
AB Interop 36835086137/110280467248 FAIL:24 pass/eight Python-publisher pairs fail30 s. Python connected/catalog/audio alive. Browser audioBytes19514/audioTS16860,context running/worker,videoFrames0/paintedfalse. Native Old
0x34at frame0/later groups throughout30 s. Owner unknown; localAB32 pairs/priorDF PASS,Python source identical.No healthy five-second actor found in app/AQ/Playwright; initiator unknown. Thread/session,ring backend,source phase differ: PASS cannot establish cause/local movie cannot prove CI timing.
ac6461ae2: test-only TCP/Origin/parser selects["hd"]/["stereo"]at ZERO (.070 s); sessions/drivers/server join before clock release. BUDGET500/CEILING1500/SETTLE30 ms/TLS/QUIC tests unchanged. A99 red:838 pass/one fail/three skip/4,127 unrun,[]versus["stereo"]. Original .103 s PASS; paused-only .099 s PASS (stasis unproved); frozen QUIC60.008 s TIMEOUT discarded. No CPU/product-cause claim.19bcd9d63: test-only owned sockets survive drops/release at shutdown; bind controls verify it. Docker count3 to2 keeps owned IDs. CI owner unproved; Linux Test PASS below. No production/API/wire change.09a783a78:3 harness files(+105/-2) surface browser-close errors after all attempts. Mutant1 PASS/2 FAIL;fix3 tests/6 assertions/package6 PASS. Midwindow owner unknown;no playback/API/wire/budget change. DF differs in12 test/docs/harness objects;2,705 others/eight AQ inputs match. Binaries remainDF,no CPU/binary equivalence.VPS diagnostic:pressure guard interrupted;five540.8–568.7 ms page gaps,zero sampled underruns/concealment;markers lost/no Opus controls. No quality verdict/owner.
just checkandinterop --alldf3d2b444,ab075bbb0df3d2b444auth_lifetime::an_outage_keeps_the_session_until_expires, publisher connect timeout; AQ job passes.ac6461ae219bcd9d6309a783a78pushed09a783a7819bcd9d6319bcd9d63ac6461ae2dropping_a_server_keeps_its_socket: 1 versus 2 after both drops. Unchanged from1217ea74b; cause unknown.ac6461ae20df56d52a0df56d52a0df56d52abc2680217supplies paced control, results below.9b2d051bc/ #58aws-lc-rsbuild-script ENOENT before any pair/media case; no playback observations. Fallback target from72adc66a2crossed different flake/nixpkgs/rust-overlay/crane pins. Missing cached Nix ELF loader is an inference; exact cached ELF unavailable. Final snapshot: seven required SUCCESS and one Interop FAILURE, all before media playback.9b2d051bcbdc6de62113292da71d6caddb3a32d6a9ea999b9cd9355d2145e95b8211e2a9c. Preserved build evidence, not the final corrected-head bundle.c11d5cb0dfa1fa36d6eb4620b4abcf260dddeff8just checkpass. No production runtime, public API or wire change.c11d5cb0dfa1fa36d6eb4620b4abcf260dddeff8/ #58client.rs:361expected droppedEndbut sawConnectafter fixed50 ms. The earlier C11 native checkpoint is not the final D593 bundle result. C11 Nightly 36898128337 ended CANCELLED: 91 jobs, 67 SUCCESS/11 SKIP/13 CANCELLED, 53 artifacts. Its AQ work retained 20 of 24 observations; four AAC observations and the consolidated core table are absent. Cancellation after the fresh D593 dispatch is inferred, not proven; this is not whole-Nightly success.d5937f527e382fbeeb5be82973de77ec03cd77bc, treee97ed04a8f0c02974c8183c6c93b38b5e9358092rs/moq-auth/src/client.rs, +26/-9. Both end tests wait on the actualEnd; a 3-second bound fails a missing event. Pinnedjust checkpassed 525 tests/2 skips plus fmt, clippy, docs, workflow, Nix and media; source stayed unchanged and the tree is clean. No runtime/API/wire or quality-budget change.d5937f527e382fbeeb5be82973de77ec03cd77bc/ #58204736fb5552d26730c1166571c743b08cab0673, D593 merged into the original1217ea74bbase. Test reported 5,088 passes and six skips. Interop 36904140229 passed all 32 language pairs and four negative controls. Lifecycle presentation ranged from 27.8 to 46.2 FPS; tone estimates ranged from 149 to 153. PCR jitter warnings remain; the log lacks underrun/concealment counters and pixel-quality metrics, and browser-output audio is not speaker listening. Native bundle 36904618272 succeeded on attempt 1 with pinned workflowbc68327bb8bd82732dbf5cc24b951f85281fe245; artifact11185930440was fetched once and validated. Nightly 36904623964 succeeded on attempt 1: 76 successful jobs, 15 skipped, no failed or cancelled jobs, and 56 artifacts. Replay passed 172 enforced checks across 14 rows, core passed 36 checks across 24 rows, and raw fixed passed 24 checks across two rows. Core still reported nine non-enforced ceiling exceedances and four non-enforced playout-state voids: AAC bursty isolated, AAC high-RTT plain, and Opus bursty isolated/plain. Raw isolated fixed AAC silence share was 0.259 above 0.171; acceptance required all 57/57 quiet windows to prove quiet source. VPS deployment and live observations are recorded below.e9ce90f3b,b38f8f4c0,0df56d52aAt previous head
1217ea74b, all check, language, draft, flake, bench and quest steps passed. Rust had 4913 passes, 4 skips and 84 media-feature passes. JS had 2414 passes across 17 packages and threejs/commonchecks:@moq/watch554,@moq/net870,@moq/hang275 and@moq/publish181. Python had 71. The playout corpus covered 17 cases/7359 arrivals with 91 Rust, 51 estimator and 29 replay tests. Replay enforcement passed 172 checks/14 rows, media passed 6/6 windows twice and Interop passed 32/32 moq-lite-07 pairs. CI Interop still failed media reattach; every other check passed.Coverage limits
1217ea74b/actualdf3d2b444long rows were grader OK: Chromium underruns 1/1, concealment 780/760 ms, frame gaps 602/601.8 ms; WebKit 2/2, 200/220 ms; Firefox 0/0. Neither 20 ms difference proves improvement/regression. Chromium PTS includes quiet at 462.6 s; publisher elapsed is unknown without connection logs. These supplement 0df, not a full candidate matrix. Linux: 40 recorded rows, one guarded tiles interruption, 11 absent, two older catalog-join failures; sevena3f1a74f8rows give five OK/two sampler failures below. Firefox's 800 ms quiet stretch remains valid but source-phase confounded: original missed it; matched replay has identical underrun/skip/concealment counters, with no PR attribution.bc2680217, three Playwright-owned real-task fixtures passed: 0/23 healthy burst heads flagged with and without the probe; actual 150 ms freezes and 660 ms busy tasks flagged. Stall source is byte-identical ata3f1a74f8anddf3d2b444. Six playback rows retain five oversize-metadata voids and one compact-metadata WebKit main pass, later one underrun/760 ms concealed. Chromium had 20 hidden samples/row; Firefox/WebKit had none despite tab actions. No retry/regrade; bc268 diagnostics do not verify the catalog fix or real Safari.a3f1a74f8. Seven once-only Linux rows: five grader OK/two sampler failures. Chromium r1 reattach: 2,078.6 ms sampler gap after attach, one underrun/1,160 ms concealed; r2: 1,748.8 ms warmup gap, zero underruns/concealment. Other whole-window counts: r3 reattach 2/120 ms, r3 hidden 1/120 ms, Firefox hidden 2/40 ms, r1/r2 hidden 0/0. Matched r2/r3 hidden catalog/media 61.2/64.4 ms, click/ready 145/179 ms; Firefox 22/1,341 ms. Relay has noLagged/no-route. Warm browser joins do not force the native real-Origin test's two-front overlap. Production Rust/JS matchesdf3d2b444; actual a3 binaries keep their prior lock. AB adds auth tests; a99 adds trace/test/docs, not production.df3d2b444. Prior31/38 coordinator rows:18 OK/four strict-CSP voids/nine problems; prior32 raws one partial/six untouched, V5raw33/two partials/five untouched. V6 ran those five once:four OK/one problem. Final36 coordinator rows:22 OK/four CSP voids/ten problems;38 raws include two interrupted partials, none untouched. Six worker-only CSP PASS; four script-header refusals,two/build. Six restarts FAIL suspended/time0 despite input/video; resume/activation cause unproved. Both DF memory rows fail all10 recoveries like original r1/r2; contexts/nodes10/1 are not heap proof. DFtilesweep1:three players/expected one, BSDseq 1 0emitted1,0. DF10 partial SIGTERM:painted0,sampler1,001>1,000ms,missing second input read. V5DF50 guard SIGTERM/driver VOID: designated paint6.9median/4.7minfps,video-input FPS24.1median;5.4s,all50 readiness unknown,user severe lag/possible regression. Before V6 original50 never ran; V6original50 FAIL:paint5.6/0fps,gap6745ms,skew5537.5ms,5underruns/4460ms concealment;45s versus partial5.4s cannot grade a regression. Original/DF480s long OK each1underrun/560ms concealment,gap616ms,paint minima13.5/9.5fps. Raw decoded/decodeFps observes compressed frames before submission, not proof of submission/output; paint measures one designated renderer. Prior lock/V4 refusal retained; no retry/regrade. Checkpoint75/17cells/disjoint21names retained. Other2,713 objects/Cargo.lock matchab075bbb0; metadata DF. AAC13-/16-frame source explanation/receiver ambiguity retained; WebKit separate.84fac1cfe(4 and 5 of 24); their individual causes remain unproven. The enforced fixed-250 source failure is diagnosed above.f5506ad9dthrough a remote relay and heard no chops at Auto; nobody has listened to a later build yet.How to reproduce
The demo watch page takes bench params, off unless the query asks for them:
?offload=0keeps every tile's audio on the main thread,?stall=120/1500&stallAt=32&stallFor=30freezes the main thread as the rows above do, and?csp=1refuses every worker so the fallback can be seen.Raw media, logs and screenshots stay private; every number above is an aggregate. Nothing has been posted upstream.
Deployed build and live playback
On October 2, the VPS installed exact D593 from the verified native bundle. Installation and separate health checks passed without rollback. The live source, native executable hashes, lite-07 publisher, unchanged configuration, 35-page build, served bundles and audio worker were verified. The first page build lacked Node; the next Astro attempt rejected an absolute configuration path. Both failed attempts remain recorded. Pinned Node and the corrected relative path passed.
Cleanup removed the backed-up test tree and exactly eight test packages. Its command exited zero, but the original receipt failed after systemd reset ExecStart bookkeeping during reload. Fresh read-only reconciliation passed with unchanged original processes and configuration. The failed receipt remains unchanged; cleanup was not rerun. The guard stayed active through cleanup and stopped immediately before intentional installation.
Each live Chromium path received one valid 12-second observation. Plain/isolated/site painted FPS means were 24.07/23.99/22.82, minima 23.19/23.39/7.86, maximum frame gaps 51.8/67.6/299.7 ms, and A/V skew p95 61.0/68.9/51.7 ms. Plain/isolated had zero sampled stalls, underruns or concealment; discards were 127/852 samples and jumps 2/3. Site had two underruns, 620 ms concealment, one stalled audio sample, one skip of 15,360 samples, 9,900 discarded samples and five jumps. PCM was nonzero in 25/25, 25/25 and 24/25 snapshots. Playback advanced on all paths with the worker and WebTransport; site audio quality remains adverse and its cause unknown. The site's first attempt stopped before sampling because the observer expected a full name instead of scoped
/demoplusbbb.hang; that failure is retained, and only the site assertion was corrected. Speaker listening and pixel-quality scores remain unverified. Safari with 50 players and the historical +21% multi-segment CPU cost remain unresolved.The separate relay RAM experiment remains local and is not included in this PR. Shared finished prefixes reduced the measured heap growth, but the full paired CPU comparison failed: 37 of 49 cases had slower means with disjoint 95% confidence intervals. A later helper extraction improved the focused fanout checks, while two focused resume cases were still slower. Its full CPU gate remains pending; no RAM PR or upstream changes have been made.
Authored by Claude Fable 5.1; revised by Claude Opus 5.5 and Codex (GPT-6).
(Written by GPT-6)