Skip to content

feat(watch): add adaptive audio playout and worker offload - #3

Draft
fperex wants to merge 350 commits into
mainfrom
debug-findings-solution
Draft

fperex wants to merge 350 commits into
mainfrom
debug-findings-solution

Conversation

@fperex

@fperex fperex commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner

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.

Fork draft. No moq-dev/moq PR intended; slices can be picked separately.

Pushed d5937f527e382fbeeb5be82973de77ec03cd77bc: 128 commits/184 files/+12,860/-8,833 from base 1217ea74b; hosted proof and VPS installation/health passed; live playback observations are below. Parent C11 c11d5cb0dfa1fa36d6eb4620b4abcf260dddeff8 is the cache checkpoint. PR #57 source 09a783a788b4354a59d1a517b6a24dd9784e9c60 (125 commits) merged as 9b2d051bc5661f024e59684a730d9ff4014abf99. Base contents: PR #4 4c7a652f3, 22 worker commits through d8a3e898d, upstream d633fc176 through 75d615dfd, 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.md findings1-47 remain at 1217ea74b.

TL;DR

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.hang bursts (7 frames/about162 ms), age budget reused as skip threshold, one held-target frame missing, and doubled advertised jitter (100ms waited402 ms).

Approach

Why

Network, container, estimator, AudioDecoder and 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

Stage Before Now, by default
Network reads, container consumer, stall monitor, arrival estimator main thread the audio worker, on its own session
WebCodecs AudioDecoder, ring writes main thread the audio worker
Sync: cross-track delay, the clock video paces from main thread main thread, fed by the worker's reports
AudioContext, gesture unlock, worklet node main thread main thread
Playout engine: stretch, concealment, render AudioWorklet AudioWorklet
Video decode and paint main thread main thread

One worker/page serves every player, with one worker relay session per URL and per-player streams. Supply moved verbatim in 561127767; replay stayed byte-identical from 4c7a652f3 to 1217ea74b. The worker writes message rings; isolated main-thread audio retains SharedArrayBuffer. The page keeps Sync, 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, or offload: false keeps a player/decoder on-page; other present values keep the default worker.

When the audio stays on the page

No relay url, Worker or Web Audio keeps audio on-page. One warning permanently falls back the page if worker start/import exceeds 5 s (ee9d7cdc2, #42), CSP blocks worker-src blob:, its realm lacks AudioDecoder/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 needs script-src blob:, including main-thread fallback.

What it costs

  • One extra relay session per URL/page, shared by its players. Audio priority applies within sessions, not between them.
  • Bundle: lazy worker414.84 kB minified/114.72 kB gzip; player+3.6 kB gzip (54.89 to58.52 kB).
  • Main thread: reports cost10 to13 microseconds (1 to16 players);16 players/50 ms cost3.3 ms/second.
  • Safari holds a deeper Auto target on the worker path (below), and Chrome looser A/V sync on isolated pages (the trade-off).

Results: page freezes, local

Local macOS/Cubic/real <moq-watch>/bbb.hang, Auto. ?stall=120/1500&stallAt=32&stallFor=30 makes twenty 120 ms freezes. Chrome153/Firefox156 use WebTransport; Safari26.6 uses WebSocket. Three interleaved repeats/cell, median and range, build a07f3c70a; Firefox isolated-worker is its fix check.

Browser Page Main thread: underruns during the freezes Concealed Worker: underruns during the freezes Concealed
Chrome 153 plain 14 (10 to 15) 460 ms 0 (0 to 1) 0 ms
Chrome 153 isolated 15 (11 to 16) 660 ms 0 in all 3 0 ms
Firefox 156 plain 15 (13 to 18) 500 ms 0 in all 3 0 ms
Firefox 156 isolated 15 (15 to 16) 580 ms 0 in all 3 (fix check) 0 ms
Safari 26.6 plain 16 (9 to 17) 540 ms 1 (0 to 1) 20 ms
Safari 26.6 isolated 15 (15 to 18) 620 ms 1 in all 3 20 ms
  • Auto held a 60 ms median target on both paths in Chrome and Firefox. Safari's worker path ran at 120 ms on the plain page and 140 ms on the isolated one; its main-thread path stayed at 60 ms and cut instead.
  • Video still paints on the main thread, at about 24 fps, the source's rate, on both paths; the longest frame gap, 161 to 169 ms per cell, is a 120 ms freeze plus about one frame.
  • Controls without freezes, worker path: 0 underruns in the window in all three browsers; four rows took 1 or 2 later, each with a long video gap (283 to 612 ms). Not investigated.

Lifecycle, local

Same host/page, one row each, worker unless named.

Check Chrome 153 Firefox 156 Safari 26.6
Mute, then unmute audio back 221 ms after the unmute, 0 underruns after 224 ms, 0 226 ms, 0
Tab hidden for about 10 s 0 underruns hidden or after; picture back 114 ms after show 0; 109 ms 0; 105 ms
Relay restart, down about 1.1 s audio back on the worker 2.6 s after the relay went, 0 underruns after 2.8 s, 0 audio never came back, on the main thread either (below)
Tune-in, navigation to first audio, worker / main 456 / 433 ms 430 / 554 ms 1260 / 1263 ms
Nine tiles on one page (Chrome only) one worker, two relay sessions, 0 underruns in 75 s not run not run
?csp=1 (worker-src 'none') falls back at start with one warning; audio covered 100% of the wall same, 100% same, 99.9%

Safari relay restart rebuilt the tile (finding 38) but its new AudioContext stayed 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.

Row Underruns, worker runs Underruns, main runs Target p50, worker runs Target p50, main runs Skip-aheads a minute, median, worker / main
bursty, plain 0, 0 8, 2 620, 300 ms 840, 620 ms 2.2 / 8.2
bursty, isolated 0, 0 2, 2 220, 340 ms 460, 360 ms 6.5 / 6.5
near-zero, plain 0, 0 0, 0 60, 80 ms 80, 80 ms 0 / 0
near-zero, isolated 1, 0 0, 0 60, 60 ms 60, 60 ms 14.2 / 14.2

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 moq binary, the next launch stalls about 2.9 s on macOS, and FFmpeg's -re then 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.

Browser Plain page, worker / main Isolated page, worker / main
Chrome 153 58.8 / 69.6 ms 73.3 / 44.0 ms; on the fixed code 69.1 / 46.5 ms
Firefox 156 43.3 / 44.8 ms 42.3 ms on the fixed code / 51.4 ms
Safari 26.6 39.5 / 51.2 ms 84.0 / 41.3 ms before the fix; on the fixed code 39.2 / 40.7 ms

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.

Cell Chrome, now / pre-sync Firefox, now / pre-sync
c0 worker, plain 1, 130 ms / 0, 0 ms 0.5, 40 ms / 0.5, 20 ms
c1 main, plain 14.5, 520 ms / 9.5, 390 ms 17, 710 ms / 13.5, 480 ms
c2 worker, isolated 0.5, 10 ms / 0, 0 ms 1, 30 ms / 0.5, 10 ms
c3 main, isolated 14, 680 ms / 14, 570 ms 16, 830 ms / 13.5, 560 ms
c4 worker, no freeze 0, 0 ms / 0, 0 ms 0.5, 10 ms / 1, 50 ms
Project site, worker 0, 0 ms / 2, 260 ms 0, 0 ms / 1, 20 ms

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 Page Main thread: underruns during the freezes Worker: underruns during the freezes Skew p95, worker / main
Chrome 153 plain 9.5, 390 ms concealed 0, 0 ms concealed 63.9 / 68.7 ms
Chrome 153 isolated 14, 570 ms concealed 0, 0 ms concealed 64.0 / 50.9 ms
Firefox 156 plain 13.5, 480 ms concealed 0.5, 20 ms concealed 43.1 / 47.8 ms
Firefox 156 isolated 13.5, 560 ms concealed 0.5, 10 ms concealed 41.8 / 46.0 ms
Safari 26.6 plain 9, 320 ms concealed 0, 0 ms concealed 44.1 / 50.3 ms
Safari 26.6 isolated 10.5, 360 ms concealed 0, 0 ms concealed 42.5 / 44.1 ms
  • Safari's worker trades latency for continuity: an Auto target p50 of 230 ms (plain) and 190 ms (isolated) against 70 and 80 ms on the main thread, and longer video frame gaps (250 to 583 ms against 161 to 173 ms). The likely cause, inferred and not measured: WebKit routes a worker's WebSocket through the page's main thread, so arrivals come in bursts.
  • The project website's own watch page, built against this branch: one 60 s row per browser, counted after a 15 s warmup. All three played on the worker to the end, with 2 underruns (260 ms concealed) in Chrome, 1 (20 ms) in Firefox and none in Safari, whose target sat at 240 ms (60 ms in the other two).

Browser findings

WebKit releaseLock() deadlock. Safari froze 3/3: per-stream release of incomingBidirectionalStreams can collide with WebKit GC; a no-MoQ page hangs after about4,000 worker reads. 0a5da4045 keeps 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. b8746c441 uses message writes; c24fbb505 falls back an unreadable player; d8a3e898d completes 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. 76c83b472 uses [listen]/bind.

PR #57: fork-only fixes

PR #57 answers the 24 fork-only issues a review of 1217ea74b filed (#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.

Area Issues Main commits
Audio playout, JS and Rust #31, #32, #33, #37, #39, #40, #41, #42, #43, #56 340f3cdf4, 227faa489, 0667ad6a9, 9ed7494e9, 4c5a1bf5d, ee9d7cdc2, 09941b9bc
Video and the container consumer #35, #36, #38, #49 f3e2f8a78, 10d875182, 057e01bbf, ad40bf861, ad1380ba6
Returning viewers and reattach #34 (closed by decision), #51, #55 c6c842c44, 2fe47e458, 294ee0306 (upstream), 930027366, 38f75ef85, a17fe6d79, 4844bc2dd, 738f9f104
Transport and relay #44, #45, #46 26f71e4e7, f47fcee74, b3da59228 (upstream), 722f22a0d
Tests and tooling #47, #48, #50, #53 e8e61e701, 1ee78246d, 1dfa19719, 6a6ee9dd3
  • Returning viewers. Imported fixes skip stale lite-06+/IETF caches; break_at/breakAt marks 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. 75584b85f only replaces yanked yoke-derive 0.8.3 with 0.8.4/checksum; no package/API/wire change. df3d2b444 weakly 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; literal Expired. 2a78f7425 remains 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 actual page.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. a3f1a74f8 replays the newest FINISHED group from frame0 to another front, retransferring its full payload; OPEN splice is unchanged. Paused real-Origin test: old Lagged, 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. ba8cc67ca preserves 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. bc2680217 gives fixed-250 one frame/PES; adaptive keeps seven-/sixteen-frame packing. Quiet input batches16 AAC frames/371.52 ms; -pes_payload_size 0 permits13/about302 ms, while -max_delay 0 gives 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/watch and @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.

Against 1217ea74b Result
Relay, Linux VPS pairs, CPU at 1x1 / 1x10 / 4x1 / 4x10 +0.0% / -3.5% / -0.3% / +1.7%, ranges overlap, zero loss
Relay, Mac 4x100 CPU 0.961x, peak RSS 0.948x
Relay, 2 h soak at 4x10 RSS flat in both builds
Criterion time fanout -4.6%, 1-segment resume +0.7%, 2 to 3 segment resume +21%
Linux importer lag, max 192 to 464 ms before, 6 ms after (runner); 177.8 ms before, 4.4 ms after (VPS)
Audio quality workflow, Linux original: 4 of 36 enforced checks failed; 0df56d52a: first run passed, repeat failed the fixed-250 source case; new-head gates below

Everything else in this PR

Playout target. doc/concept/playout.md specifies NetEq underrun_optimizer, histogram and packet_arrival_history in f64. Both languages replay 17 traces in rs/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 Old 0x34 since 26f71e4e7, 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). f5506ad9d removes about 96 ms silence on Auto steps; 98f475ce7 removes audio-quantum allocations, including typed-array views since ee66ffa88. Native: NetEq DSP/estimator/engine through decode::Options::delay, with moq play --delay as floor (cd424b82e, 6f25cffa0, 9136e8352).

A/V clock and video. The audio playhead drives the sync clock (7e973174e, be1b87583); Sync.out.offset holds 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 since 38f75ef85 the 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 since f47fcee74, 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 since 26f71e4e7 JS keeps only the priority through the FIN, on moq-lite (#44).

Harness and CI. Options on upstream's seeded UDP shaper, and test/audio-quality with a Chromium matrix, a real-Safari lane and a deterministic replay lane (a7b742ad7, ebe96d1d5, 5b524ac0b). The Audio quality workflow runs the replay lane under --enforce on pull requests that touch the audio paths, and the Chromium matrix on demand and from the Nightly (7293200c1, b4addef64); since 0251594ba a 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-shaper moq-dev#4054. Overlaps use upstream. Playout/rings/worklet/audio-worker/js/hang/src/container/rs/moq-audio/src/playout/corpus match d8a3e898d except supply.ts to effect.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. Since e8e61e701 it refuses --report-interval 0s and 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.

Watcher load for 30 s, today's path (builds 91b439643 and f5506ad9d) Auto target p50 Underruns per row Concealed per row
none 20 ms 0 0
freeze 60 ms every 700 ms 60 ms 1 in 8 of 9 rows, at the first freeze 20 ms
freeze 120 ms every 1.5 s 20 ms, unmoved 20, one per freeze 1620 to 1700 ms

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 in HANDOFF.md at 1217ea74b.

Alternatives

  1. Run-dry safety net 91d215091/c0bbc9070 (reverted): 20 underruns became 7 or 1, but median targets rose to 60/40 ms for a 20 ms path.
  2. Fork lite-06 rejoin rule c1f899ee4 (reverted by 5928fe324): 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.
  3. Rust/WASM worker (no WebSocket fallback; moq_bg.wasm 562 KB gzip), decode-only worker (network stays on page), and worklet WASM playout (cannot fetch/decode).
  4. Player GOP guess for js/hang consumer: a kept group that never finishes holds delivery for about one age budget, and video then waits for a keyframe #34: a 9.99 s frame is dropped by one at10.0 s; relay fix retained, player waits.
  5. Resume-floor wire field: publisher break handles it without wire change.
  6. Late render-node construction (E/F/G): removes the last worklet leak but adds 5 to43 ms before first sound.

Notes for the maintainer

moq-dev#4067 moves watch/Sync into a worker and refuses missing APIs; this PR keeps Sync/fallback on-page for https without special headers. Maintainer choices remain: public Rust break_at/JS breakAt cannot inform already-subscribed relays without wire change; AQ pins -readrate_catchup 1 while upstream demo uses -re; public decode::Consumer::{delay,playhead} have no callers.

Estimator versus upstream's planned spec (doc/concept/audio-jitter.md, moq-dev#3883, and the quest/m0/audio-jitter-target quests). This requires a deliberate choice:

This PR (doc/concept/playout.md) Upstream's spec
Forget factor per 500 ms observation 0.9 (a5c95b91c) 0.983, a half-life of about 20 s
A delay of 2 s or more dropped, as NetEq does clamped into the last bucket
An interval with no arrival folds a zero-delay observation (at most 60) folds the prior (at most 1024)
Rise and fall rise at once; fall once a second by a sixth of the distance or one bucket the histogram alone
Frame duration not in the target; the ring holds the target plus the last chunk added to the target
Declared flush span the cold-start prior, replaced by the first measurement a floor: max(measured, advertised)
Cold start 80 ms (NetEq's kStartDelayMs) or the declared span 100 ms plus one frame
A blocked receiver a stalled input drops the arrival reference not covered
Corpus 17 cases in rs/moq-audio/tests/playout-01.json 9 cases under doc/concept/audio-jitter/
Convergence time stretch and concealment skip-ahead and silence; time stretch is a separate quest

Judgement calls: concealment differs from the quests (behind conceal); audio age headroom (fc3d7b484) can be dropped independently; finding 13 could live in js/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; Sync takes the widest spread across tracks; and #34's player wait is kept on purpose.

Follow-ups

Test changes, stated openly

  • Drain reconnect (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.
  • Stretch bench (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.
  • Replay budgets re-recorded after f5506ad9d (ceb21e9ce, 063a14d0c): plain-ring ceilings rose when silence became stretching; stale ceilings fell to measured values.
  • Upstream changes: two encoder tests pin demand-gap feat(publish): mark a discontinuity when an encoder pauses for demand moq-dev/moq#3982; Retry.refused retries 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. Revert 5928fe324 keeps the shared fix(hang): play the head group once a missing group is proven too old moq-dev/moq#3973 test. Sync d633fc176 removes two static-jitter tests with their hint and unlock.test.ts with unlock.ts; zero-timestamp capture asserts at/after gesture. Old shaper: 36 tests removed; new: 40, including upstream's 8.
  • Worker tests: pool expects worker error last (5c88f1f4c); isolated cases use message rings (b8746c441); host tests use fake timers (5dbb5e33c). buffer.test.ts imports fresh reportTransport, requiring exactly one info line instead of at most one. It and sync.replay.test.ts set shared: false instead of deleting SharedArrayBuffer.
  • Upstream tests changed with the design (75d615dfd to 1217ea74b). 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 asserts skipped == 0 where 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 code 0x34 (Old) instead of 2 (DeliveryTimeout), because DeliveryTimeout now decodes to Expired (b245cd6d4). Two other tests gained Expired assertions.
    • js/net/src/{ietf,lite}/publisher.test.ts: "a blocked group header is reset when the group expires" is a test.each over header and FIN (5a708ada2).
    • js/watch/src/audio/config.test.ts: playbackJitter expectations 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: both reanchorFloor tests 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 with Sync.register.
    • Rust: 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); two play/timeline.rs tests lose their silence assertions with the field; rs/moq-tokio/src/connection.rs test_backoff_default expects 60 s.
    • Harness-only or strengthened: 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.
  • PR fix: resolve the fork-only issues on PR #3 #57 (1217ea74b to a3f1a74f8) changed these tests again and added others:

Impact

Wire. No format change over upstream, whose moq-lite-07 (moq-dev#4060) the base speaks. The hang draft and doc/concept/hang.md add 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 with 503 on 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 at sequence at 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).
  • Behaviour: moq-mux container::Producer::discontinuity declares the track's break, so a late joiner starts at the marker; Container.Consumer counts a stale group dropped behind returned media in skipped, with no discontinuity; moq-shaper --report-interval 0s is a usage error; @moq/watch and @moq/hang ship no test-only files.

New with the audio worker (additive, except that the default is on)

  • <moq-watch offload> and MoqWatch.offload: on by default; "false" or "0" turns it off (b1f593d6f).
  • PlayerInput.url and PlayerInput.offload, Audio.DecoderInput.url and Audio.DecoderInput.offload (86a88a099); offload defaults to true (5c88f1f4c), and a Player without a url keeps its audio on the page.
  • Sync.received(timestamp, label?, at?): the optional at is 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).
  • Internal, not exported: audio/supply.ts, audio/worker/*, RingTarget, audio/supported.ts, and the worklet's second port and unreadable message. 0a5da4045 changes no @moq/net API.

Breaking, or a changed default, relative to upstream main (earlier in this PR)

  • @moq/watch: Sync.register(jitter) is replaced by Sync.track(name); SyncInput loses probe, since Auto no longer reads the round trip; a numeric delay is the whole delay, with no advertised flush span added to it.
  • moq play --delay is a floor capped at 2 s, where it was the delay capped at 10 s.
  • The reconnect give-up is 60 s instead of 10 s: @moq/net ReloadDelay.timeout (60000 ms), moq_tokio::Backoff::timeout (with --backoff-timeout, MOQ_BACKOFF_TIMEOUT and connect.backoff.timeout), MoqBackoff.timeout_us in the FFI (60000000, regenerated for Dart) and the Go wrapper. A GOAWAY naming no URI is reported as peer is draining and 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, static BUCKET and CEILING); ConsumerProps.jitter: Time.Milli | Jitter; Consumer.spread and Consumer.skipped. ConsumerProps.maxAge keeps its type and takes the wire budget's meaning. Container.Jitter is new relative to upstream main; 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 the Clock, SyncTrack and SyncClockTrack exports; Audio.Decoder.out.{spread, underruns, skipped, interrupted}; Video.Decoder.out.{spread, skipped}; Audio.DecoderInput.conceal and <moq-watch conceal>; Audio.DecoderInput.attached.
  • @moq/publish: Fanout.ended, Video.Capture.out.stopped, MoqPublish.errors and the .control--error class.
  • moq-audio: decode::Options::{delay, conceal, DELAY_MAX} (Options is #[non_exhaustive]) and decode::Consumer::{delay, playhead}. With delay set, Consumer::read returns 10 ms playout blocks instead of decoder packets.
  • moq-relay: Shutdown::draining().
  • moq-shaper (upstream's crate, publish = false): Setup, Options, Jitter, Batch, Step and Preset, and the flags --tcp-passthrough, --jitter-model, --shared, --batch, --batch-window, --profile, --report and --report-interval.
  • New: the private @moq/audio-quality workspace.

Internal, or behaviour only: Audio.Decoder.out.debug (@internal, its snapshot carries trimmed), the stall monitor (not exported from @moq/hang), the per-rendition Estimator and subscribeMedia's start (both @internal), audioMaxAge and maxAgeHeadroom (module-level), the rings' internals (the shared ring only grows since PR #57), the worklets' close and stopped messages, moq-tokio's crate-private NoqClient::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/watch 81, js/publish 31, test/audio-quality 30, rs/moq-audio 20, js/net 17, js/hang 15, doc 14, rs/moq-shaper 13, rs/moq-net 12, and 78 elsewhere. rs/hang is untouched.

The commits

Audio worker, 22 commits on 4c7a652f3: second port 271715448; WebKit 0a5da4045; verbatim Supply move 561127767; protocol/pooled worker 971c74143, 464e8dd27; default/fallback 5c88f1f4c; Firefox b8746c441, c24fbb505, d8a3e898d.

Sync: a merge/10 commits on d8a3e898d. d633fc176 merges through 75d615dfd, recording conflict resolutions. Seven shaper options: bbc2822b9, 8ace4b8b4, 2a0432d71, f684aa6f4, 34c6ce052, da0c96f23, 7c09168f6; then 5cc94fb59, 6485da9f7, 1217ea74b.

PR #57, 125 commits, linear on 1217ea74b, merged by 9b2d051bc5661f024e59684a730d9ff4014abf99: four imports (c6c842c44, 2fe47e458, 294ee0306, b3da59228); issue fixes (1ee78246d to 9ed7494e9, 057e01bbf, 97d712ea8, 14bd9dd25); reattach/publisher break (7f02213e7 to a17fe6d79, 2c34a8b20, 0df56d52a); Linux audio/quality (ca4817459, 84fac1cfe); worklet teardown (6b25efc3c, 78923c1ea); nine perf(net) commits; 22 unslop commits (6d179bf26 to e9ce90f3b).

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 1217ea74b conflicted with main; its old Interop failure remains historical. PR #3 stays open and draft at D593 and conflicts with current main. Final checks verify D593 against the original 1217ea74b base through PR58; they do not prove a merge into current main.

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.

Actual head/run Enforced result Excluded breaches/rows; voids; accepted quiet-source cases
bc2680217 manual PASS eight/six; three; two
bc268 Nightly FAIL: 12 AAC and four main Opus metrics Opus: 6.5 underruns/min, 0.032 stalled share, 6.5 skips/min, 905.7 skipped ms/min; one main AAC void
a3f1a74f8 Nightly 36819237894 AQ PASS 11/seven; four; worker Opus one/main AAC one. Main Nightly failed yanked yoke-derive 0.8.3, fixed by lock update.
df3d2b444 manual 36822658662 PASS 12/eight; one; worker one/main AAC one
DF Nightly AQ job 110241399977 PASS five/four; five; worker two/main AAC one. Overall Nightly 36822661339 failed Mac auth publisher-connect.
2a78f7425 manualAQ 36831617235 PASS worker 23/seven; five; worker three/main one
2a Nightly AQ job 110269314921 Replay/worker PASS; eight enforced main metrics FAIL, zero main voids worker 17/six; six; worker three/main one. Both main codecs: 1.1 underruns/min, 0.009 stalled share, 1.1 skips/min; skipped ms/min AAC90.6/Opus370.9, all ceilings zero.
ab075bbb0 manualAQ 36835459388 PASS worker 11/five; one; six. Main zero breaches/voids; one quiet-source accept.
AB Nightly 36835462958 AQjob 110281696946 PASS worker 10/four; four; four. Main zero breaches/voids; one quiet-source accept. Whole run: 76 successes/15 skipped.

2a 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 Old0x34 at 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.

Gate At Result
Scoped Nix just check and interop --all df3d2b444, ab075bbb0 pass; AB scoped from DF: 339 relay tests/two skipped, 32 Interop pairs
Historical PR #57 checks and Nightly 36822661339 df3d2b444 Eight PR checks pass. Nightly fails macOS auth_lifetime::an_outage_keeps_the_session_until_expires, publisher connect timeout; AQ job passes.
Exact AC gates ac6461ae2 Local: 4,966 pass/three skip + 84 pass/zero skip; JS/build/lint; Interop 32/32 PASS. Seven PR checks pass; Test fails. Manual AQ36847001121 PASS; Nightly36847004709 PASS:76 success/15 skip.
19b historical gates 19bcd9d63 Local check/Interop32/32 PASS. Linux Test36853755318/110341245926 PASS:5,088 pass/6 skip78.659s; socket0.052s PASS; Mac skips it.
Candidate gates 09a783a78 pushed Local 4,966 passed/three skipped (72.071s) + 84 passed/zero skipped (4.513s); JS/build/lint; 32/32 Interop PASS. Eight actual PR checks SUCCESS.
09a hosted 09a783a78 Manual 36874501140 SUCCESS (2 jobs: 1 skipped/1 success); replay 172/14 SUCCESS; worker 36/24 SUCCESS, reported 10 metrics/6 rows/3 voids/3 quiet accepts; main 24/2 SUCCESS, reported 0 metrics/0 rows/0 voids/1 quiet accepts. Nightly 36874506339 SUCCESS (91 jobs: 15 skipped/76 success); replay 172/14 SUCCESS; worker 36/24 SUCCESS, reported 11 metrics/9 rows/4 voids/0 quiet accepts; main 24/2 SUCCESS, reported 0 metrics/0 rows/0 voids/1 quiet accepts.
19b hosted 19bcd9d63 Manual36853751819 PASS172/14+36/24+24/2; worker11 excluded metrics/6 rows/4 voids/no quiet. Nightly36853756326 FAIL75 success/1 AQ fail/15 skip; replay172/14,main24/2 PASS; worker3 enforced fixed250/plain AAC:1.1 underruns/min,1.1 episodes/min,220.5 underrun ms/min,ceilings zero;26 excluded/10 rows/2 voids. Each main0/0+1 quiet. Nightly timing outside merge gates;19b Interop failed.
19b PR Interop36853755161/110341245092 19bcd9d63 FAIL:32 pairs/worker lifecycle40s PASS; main14s failed resumed tone67/81. Four negative controls PASS; owner unknown.
AC PR Test 36846958575/110319278310 ac6461ae2 FAIL: 4,769 pass/1 fail/6 skip/318 unrun; worker dropping_a_server_keeps_its_socket: 1 versus 2 after both drops. Unchanged from 1217ea74b; cause unknown.
AC AQ36847001121/manual and36847004709/Nightly ac6461ae2 PASS172/14+36/24+24/two. Worker breaches/rows/voids/quiet:manual14/six/four/five; Nightly9/seven/four/three. Main0/0/one each. Nightly76success/15skip;56 release artifacts/no AQ archive.
Earlier PR #57 hosted checks: Android, Audio quality (replay), Check, Interop, OBS, Swift, Test, WASM 0df56d52a 8 pass; the full Audio quality job, Auto-merge and Release JS Packages skip, as on every pull request
Nightly, run 36683809519 0df56d52a passed; the saved run list has no green Nightly on the fork from 2026-09-15 to 2026-09-28
Audio quality workflow, runs 36683796849 and 36689433474 0df56d52a First PASS; second FAIL: 0 over budget/1 enforced fixed-250 AAC void. Source 16 frames/371.52 ms exceeds delay; bc2680217 supplies paced control, results below.
Merged verification attempt 9b2d051bc / #58 Interop FAILURE: cached aws-lc-rs build-script ENOENT before any pair/media case; no playback observations. Fallback target from 72adc66a2 crossed 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.
Earlier native bundle 9b2d051bc 36890958823 SUCCESS; artifact11178741993, SHA256 bdc6de62113292da71d6caddb3a32d6a9ea999b9cd9355d2145e95b8211e2a9c. Preserved build evidence, not the final corrected-head bundle.
CI-only cache boundary c11d5cb0dfa1fa36d6eb4620b4abcf260dddeff8 Four files, +69/-7: Interop and Nightly use the existing flake-pinned local Rust cache; a CI-wired structural regression checks the cache/restore key identity and architecture. Local strict TypeScript, all three YAML/cache tests, Biome and just check pass. No production runtime, public API or wire change.
C11 cache checkpoint c11d5cb0dfa1fa36d6eb4620b4abcf260dddeff8 / #58 Final required snapshot: seven SUCCESS and one Test FAILURE. Interop 36897932579/job 110489548849 passed 32 pairs. Worker phase FPS: 29.0, 29.3, 29.0, 45.2, 29.1, 29.6; main: 29.8, 29.2, 29.9, 30.0, 30.2, 30.2. TS WARN jitter was 24.67/24.34 ms and 132.66/84.80 ms against 0.5 ms. Counters, PSNR, SSIM and VMAF are absent; browser tone is not speaker listening. Test 36897932613/job 110489546888 failed: 560 pass/1 fail/6 skip/4,527 unrun; byte-identical upstream client.rs:361 expected dropped End but saw Connect after 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.
Test-only end synchronization d5937f527e382fbeeb5be82973de77ec03cd77bc, tree e97ed04a8f0c02974c8183c6c93b38b5e9358092 Sole parent C11; only cfg(test) rs/moq-auth/src/client.rs, +26/-9. Both end tests wait on the actual End; a 3-second bound fails a missing event. Pinned just check passed 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.
Final delivery verification d5937f527e382fbeeb5be82973de77ec03cd77bc / #58 All eight required contexts succeeded on attempt 1 against synthetic checkout 204736fb5552d26730c1166571c743b08cab0673, D593 merged into the original 1217ea74b base. 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 workflow bc68327bb8bd82732dbf5cc24b951f85281fe245; artifact 11185930440 was 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.
Earlier local gates e9ce90f3b, b38f8f4c0, 0df56d52a JS passed: net 877, json 139, hang 288, watch 618, publish 188, signals 81 and audio-quality 50; replay 172. Rust fmt/clippy/doc and 1762 pass/3 skip. Later Rust 2844 pass/3 skip and rejoin 3/3; decoder 23/backwards 10 pass.

At 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 three js/common checks: @moq/watch 554, @moq/net 870, @moq/hang 275 and @moq/publish 181. 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

  • Phones, Windows and Android; Safari before 26.6 and Firefox before 155.
  • Retained browsers. Mac memory: 12/12 passed, original/0df. Six original 1217ea74b/actual df3d2b444 long 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; seven a3f1a74f8 rows 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.
  • Focused Stall verification. At 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 at a3f1a74f8 and df3d2b444. 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.
  • Catalog at 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 no Lagged/no-route. Warm browser joins do not force the native real-Origin test's two-front overlap. Production Rust/JS matches df3d2b444; actual a3 binaries keep their prior lock. AB adds auth tests; a99 adds trace/test/docs, not production.
  • Real Safari. Plan18 original/20 actual 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, BSD seq 1 0 emitted1,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 match ab075bbb0; metadata DF. AAC13-/16-frame source explanation/receiver ambiguity retained; WebKit separate.
  • The worker with a real microphone or camera publisher, loss and congestion with audio and video on separate sessions, and the worker's CPU and memory cost.
  • Other historical void rows. The original harness lacked the comparable void classifier. Keep the non-enforced voids from 84fac1cfe (4 and 5 of 24); their individual causes remain unproven. The enforced fixed-250 source failure is diagnosed above.
  • The two edge cases of the reverted rejoin rule.
  • Listening. The bench grades counters, timestamps and aligned audio taps, not perceived quality. The author listened to f5506ad9d through a remote relay and heard no chops at Auto; nobody has listened to a later build yet.

How to reproduce

nix develop --command just check                 # lint and compile what the branch changed
nix develop --command just test                  # test what the branch changed

# the estimator and its corpus, both languages
bun test js/hang/src/container/jitter.test.ts js/hang/src/container/jitter.vectors.test.ts
nix develop --command just rs test -p moq-audio

# the browser engine, the rings, the worker host and the offload tests
bun test js/watch/src/audio

# the audio-quality lanes: the replay lane in seconds, the Chromium matrix in about half an hour
nix develop --command just test audio-quality --runtime replay --enforce
nix develop --command just test audio-quality
nix develop --command just test audio-quality --offload false   # the audio on the main thread

# the media lane (a worker pass and a main-thread pass) and cross-language interop
nix develop --command just test media
nix develop --command just test interop --all

# Criterion and the relay workloads against the original head
nix develop --command just bench 1217ea74b

The demo watch page takes bench params, off unless the query asks for them: ?offload=0 keeps every tile's audio on the main thread, ?stall=120/1500&stallAt=32&stallFor=30 freezes the main thread as the rows above do, and ?csp=1 refuses 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 /demo plus bbb.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)

@fperex
fperex force-pushed the debug-findings-solution branch 2 times, most recently from 03a21ec to 0b2e4ca Compare September 16, 2026 17:54
@fperex fperex changed the title Realtime audio: NetEq-style playout engine, harness, and fixes (POC, draft) Realtime audio and video: NetEq-style playout engine, publisher clock, catalog and capture fixes, Safari audio context, harness (POC, draft) Sep 17, 2026
@fperex
fperex force-pushed the debug-findings-solution branch from be1fc2c to 0617b44 Compare September 18, 2026 04:27
@fperex fperex changed the title Realtime audio and video: NetEq-style playout engine, publisher clock, catalog and capture fixes, Safari audio context, harness (POC, draft) Realtime audio and video: NetEq-style playout in both engines, publisher clock, endpoint and tune-in fixes, harness (POC, draft) Sep 18, 2026
@fperex

fperex commented Sep 18, 2026

Copy link
Copy Markdown
Owner Author

This comment is the measurement behind findings 36 to 44, which the PR body only summarises: what the
first ten seconds after each event looked like before and after, read out of the row JSON; how the
bench takes those numbers and what it refuses to grade; the congestion-control campaign as designed,
which is planned test work with no row run yet; and the questions that are yours rather than mine.
Every number here is a counter. The sampler cannot hear, so a listening round on this tip is still
the thing the branch is waiting for.

What the first ten seconds looked like, before and after

One publisher event per row, on a USB microphone at 48 kHz through the demo page, a Chromium watcher
unless the row names another engine. Before rows are on the tip the fixes start from; after rows are
on the final tip unless stated.

Publisher-side mute and unmute (mute-basic, mute at about 15.3 s, unmute three seconds later)

before, auto / 100ms after, auto / 100ms
interrupted over the mute window 6686 / 6724 ms 0 / 0 ms
interrupted over the unmute window 9770 / 9724 ms 0 / 0 ms
spinner over the same two windows the same four numbers 0 ms each
playhead after the unmute never returns +453.6 / +454.7 ms
silence to the end of the row 38.7 s none
convictions / slow-group notes 0 / 0 0 / 0
trimmed / discarded 0 / 0 ms 0 / 0 ms
concealed delta at 100ms -960 0
graded at all no: 0 of 72 and 0 of 73 tail samples carry a playhead yes

The mechanism is that the endpoint was re-applied to the run that resumed after it, so every resumed
sample was trimmed to a zero-length span and closed silently. The two before rows have no steady
state to grade against, which is itself the finding. Through the declared pause the playhead is now
present and frozen, 3234 ms held against 113 ms of nothing.

Publisher reload (pub-reload, the publisher page reloaded at about 15.3 s)

before, auto / 100ms after, auto / 100ms
total silence 2565 / 2480 ms 2419 / 2358 ms (noTile + player)
noTileMs, the page with no element to sample not measured 1875 / 1919 ms
playerMs, the element to the first advancing playhead not measured 544.4 / 438.6 ms
convictions / slow-group notes 0 / none 0 / none
trimmed / discarded / skipped / concealed 0 0
underruns 0 0
tile swaps, context after the swap 1, running 1, running, no second click

The mechanism is the demo page, not the player: it removes the <moq-watch> when the broadcast
unannounces and builds a brand new one when it comes back, so four fifths of what a listener hears
as a cut happens before any audio code runs. Only playerMs is graded. The page also built the new
tile with delay="auto" whatever the viewer had chosen, and re-picked the first broadcast in the
list; both are fixed, and page-keeps-choice-100ms proves it by reading delayAttr: "100ms" off all
197 post-swap samples of a rebuilt tile.

Watcher reload (watch-reload-auto, the watcher page reloaded at about 15.5 s)

before Fix 1 and Fix 2 after
groups convicted 1 1 0
slow-group warn lines 1, at 15852 ms 1 none
trimmed / discarded, before the start point 80 / 5 ms 80 / 80 ms 100 / 80 ms
audible lost (skipped + concealed) 0 ms 0 ms 0 ms
interrupted / spinner 0 / 0 ms 0 / 0 ms 0 / 0 ms
noTileMs not measured not measured 51 ms
playerMs not measured 323 ms 328.8 ms

The mechanism is that the convicted group had already been played. The widened warn line shows it:

skipping slow group: track=audio 758 -> 759
  first=18060666 last=18060666 closed reach=18080666 live=18300666 budget=215000

A closed group holding one frame, live - reach 220000 us against a budget of 215000 us: convicted
by 5 ms. On legacy audio, where frames carry no duration, its contiguous successor then reads as a
hole, a hole is a playhead event, and a playhead event resets the ring. Rust never sees it, because
the mux consumer pops a spent group before the age check runs, and the JS rule is now Rust's.

Hidden camera then mute (hide-mute, the camera hidden at about 15.3 s, the microphone muted two
seconds later for three seconds).

before, auto / 100ms after, auto / 100ms
spinner after the fill, over the mute window 4578 / 4493 ms 0 / 0 ms
spinner over the hide window 6678 / 6676 ms 0 / 0 ms
spinner over the unmute window 2121 / 2112 ms 0 / 0 ms
overlay titles seen Buffering (audio clock), Buffering none
spinner left anywhere in the row those three windows one span, the opening tune-in
interrupted, convictions, underruns, trimmed 0 everywhere 0 everywhere

The mechanism is the video watchdog reporting a stall whenever it has no rendition to decode. A
publisher that hides its camera takes the rendition out of the catalog on purpose, which is not a
stall, so a three second mute was showing four and a half seconds of overlay over audio that was
playing perfectly. Across all 27 regression rows interrupted is true on zero samples and
spinner === videoStalled && !paused on every sample, so the overlay was video's alone. Not fixed:
mute-basic-2000ms still shows 1420 ms of it, the parked fresh fill holding video for the fill.

Fresh publish (pub-restart, the publisher's source removed at 15.3 s and re-added two seconds
later; the bench pinned the tile, so the page did not rebuild it).

pub-restart-auto, stop pub-restart-auto, start
convictions / slow-group notes 0 / 0 0 / 0
interrupted 0 ms 0 ms
spinner 1459 ms 432 ms
underruns / trimmed / concealed 0 / 0 ms / 0 0 / 0 ms / 0
noTileMs / playerMs 0 ms / 113 ms 0 ms / 103.3 ms
audible lost, ceiling 70.1 ms 0 ms 0 ms
missing playhead after the fill none one gap, 18524.9 to 18629.4 ms

There is no before row for this shape. The mechanism is that a republished broadcast on an element
that keeps its tile is a tune-in and nothing treated it as one: the arrival estimator is keyed by
content identity, so a new publisher's near-zero timestamps are discarded against the dead
publisher's reading. This row restarts one publisher's source; the case where a publisher dies and a
different one republishes the same broadcast under a pinned tile has no bench row at all and is
guarded by two tests. The 104.5 ms gap above is the residual described below, and it is the row's
only failing check.

How it was measured

The bench is a two-tab Playwright driver: one publisher session and one watcher session on one clock,
with a scripted event schedule (mute, unmute, reload, restart, hide, show, device switch, crash,
relay restart, preset change) whose marks are stamped on the watcher page at the instant the
publisher action is issued. An in-page sampler reads the element's public signals on a 250 ms grid
and switches to a 100 ms burst grid for twelve seconds after each mark. A grader compares the ten
seconds after each mark to the row's own last twenty seconds, so every threshold is a multiple of
something measured in that row: the target it settled on, the chunk its ring holds, the level it
carried, its own per-second rates. No constant is tied to a 20 ms frame or a 48 kHz device.

Ten rules decide what a window is answerable for, and each replaced a rule that was charging a
window for something it did not own:

  1. noTile, the page having no element to sample, is reported and never graded.
  2. player, the element existing to the first advancing playhead, is what a tune-in is held to,
    with a ceiling of one second.
  3. The player clock stops through a declared pause.
  4. Between a mute and its unmute the playhead-rate and missing-playhead checks do not apply.
  5. On a tune-in the missing-playhead check opens at that tune-in's own first playhead.
  6. A counter delta taken where the row had no ring, and any field a row predates, are NOT MEASURED.
  7. A window grades only the region no later event owns, and says so when that differs from the ten
    seconds it reports.
  8. On a tune-in what counts as lost is what a listener could hear missing, skipped plus
    concealed; trimmed and discarded are reported beside the grade as loss before the start
    point.
  9. A resume after a declared pause refills from empty exactly as a tune-in does and gets the tune-in
    stretch allowance.
  10. A check whose window is too short to hold it is NOT MEASURED.

Rule 10 is the largest: 58 of about 96 failing checks were one-second windows, which is what a burst
of mute pairs produces, and a per-second question asked inside one measures the schedule rather than
the player. The set is 27 graded rows across Chromium, Brave, Firefox and WebKit, on the mute,
reload, restart, hide, device, crash and relay-restart schedules at auto, 100ms, 2000ms and
instant, with a Bluetooth headset microphone at 16 kHz still untested because the headset was not
connected. Its outcome: zero group convictions and zero slow-group warn lines on every row, and
twenty rapid mute and unmute pairs one second apart all recovering. Two residuals are left. One is a
playhead gap of about 110 ms on every resume in the non-Chromium engines and in the restart rows,
19128 to 19230 ms on the Firefox row and 21161 to 21268 ms and 21194 to 21314 ms on the two
hide-mute rows; it is the fresh fill landing at a deeper target, it is inside the acceptance bar,
and nothing explains why it is so consistent. The other is a picture gap across an unmute: on the
Brave, Firefox and WebKit rows painted stops advancing for 580, 584 and 555 ms from about 220 ms
after the unmute, where the Chromium row's longest flat run there is 23 ms. The publisher was
Chromium on all four rows, so on this evidence it is watcher-side, not publisher-side as
HANDOFF.md has it; it has no row of its own either way.

A second chain of the same gates is set up on a temporary Debian 13 x64 host, as confirmation rather
than as the gate. The deployment is proven (the units, a real certificate, two reboots, QUIC from a
public address, the WebSocket fallback distinguishable in the relay log) and it found three defects a
local bench cannot: the packaged relay unit's --file, a LogsDirectory that does not exist when
append: wants it, and a nix build that wants more memory than a small host has. The chain itself is
memory-bound there and had no results when this was written, so every gate quoted on this PR is the Mac's.

The congestion-control campaign, as designed

The invariant every row would grade against is the stated design: the algorithm WebRTC uses,
implemented on QUIC, with a strict hierarchy under congestion of audio first, then retransmissions,
then video. The campaign measures whether the relay and the backends honour it today, not whether
they should.

What the tree exposes today, read rather than assumed. The only knob is quic.congestion_control,
loss for CUBIC or delay for BBR, and the resolved default is delay on every backend, BBRv3
under noq, BBRv1 under quinn, BBRv2 under quiche; the field's own doc comment says otherwise and is
stale. There are no pacing, initial-RTT or initial-cwnd knobs. The relay exports
moq_relay_stale_{groups,frames,bytes}_total per tier, which proves something was shed but not which
track, and no RTT, cwnd or loss at all. qlog is behind a cargo feature the default set does not
carry, so the campaign builds its own relay. In the browser the send hint is hard-coded to
low-latency, and no browser reports a transport RTT.

Dimension Values
CC family x backend delay and loss x noq, quinn, quiche
Shaper profile 26 files: caps 0.5, 1, 2, 4 and 8 Mb/s and two uplink caps; RTT 50, 100, 200, 300; jitter 20 and 50; loss 1, 2, 5, 10 %; reorder; two bunching profiles; bad-wifi; mobile; four step profiles with a step back
Preset auto, instant, 100ms, 2000ms
Quality and codec 360p to 1080p at 30 and 60, avc1 baseline and high, vp8, vp09, av01, hev1
Publisher browser camera, a file publisher, an HLS ladder for rendition switching, and a cross-traffic row
Path LAN, a real wide-area path, and headless on the remote host
Watcher the real browsers as first-class rows, the bundled engines labelled as engines and never as Safari

The grade per row is the hierarchy: audio never underruns and never skips a group after convergence
while video still delivers; video is what yields, by group expiry, frame drops or a lower rendition,
and recovers within ten seconds of the path recovering; and a row is VOID rather than PASS when the
shaper's own counters prove no impairment, when the transport fell back to WebSocket, when the codec
was refused or when the row never converged. Each set's reference is its own unimpaired row on the
same path and cell, never a LAN constant.

This is test work only. No congestion control, scheduler or delivery rule is touched, and a proven
defect would be written up with its evidence and a proposal rather than fixed in the same pass. The
one tree change it needs is already on this branch: the shaper gaining a list of steps and a rate
bucket that can drop, so a cap profile can be a lossy link and not only a bufferbloat one. No
campaign row has run yet
; the relay build and the calibration step wait on the listening round.

Questions for you

  • skipped at the instant preset now counts only media that was actually lost. It used to
    include a finished group the reader had already played and popped from. Any harness, budget or
    dashboard treating the old number as a ceiling will see it fall. Recommended: keep the new meaning,
    because a counter that says media was lost when none was makes a real loss during a mute invisible.
  • The native engine conceals through a declared pause and counts every concealed block as an
    underrun
    , 49 of them in 500 ms at 10 ms blocks, because the marker never reaches the decode
    consumer. The resumed audio is correct and the counter is not. The test asserting zero is
    #[ignore] with that number in its reason, so it fails loudly the day the marker arrives. I would
    take it as one quest: surface the marker to the decode consumer, and add an Engine::end() so a
    native caller can declare the same pause a browser can.
  • The two container consumers now agree on the marker and on the budget, and the one divergence
    left is the native one above.
    A marker group raises the playhead when the group closes in both
    languages, and neither convicts a finished head the cursor has reached. Rust is the specification
    and JS is now written against it.
  • moq-relay takes its config path positionally and moq-bench takes the same thing as
    --file.
    The packaged unit said --file and so could never have started the relay; that is a
    one-line fix in this PR, and upstream/dev carries the same line, so it applies upstream
    unchanged. Which spelling wins across the workspace is yours. Recommended: positional everywhere,
    since the documentation already says so.
  • skipping covered group is a console.warn on the happy path. It fires every time a group is
    dropped because its successor has already started, which is ordinary on any jittery path, and the
    watcher-reload row alone prints it twice in forty seconds on an unimpaired LAN. It belongs at debug,
    and was left alone here only to keep the budget commits about the budget.
  • The demo page rebuilds its tile on a re-announce rather than keeping the element. That is
    1875 ms of the roughly 2.4 s a listener hears as a cut, against 544 ms for the tune-in that
    follows. Keeping the element is what would take those two seconds out, and the question underneath
    it is how long a tile should hold a broadcast that has gone away before it admits it is gone.
  • Whether the audio clock should be nominated while a ring is parked for a fresh fill. With the
    watchdog fixed, auto and 100ms show no overlay through a mute with the camera hidden, and
    2000ms still shows 1420 ms of it, because the parked fresh fill holds video for the length of the
    fill. The overlay tells the truth about the clock and the wrong thing to the viewer, and that is a
    rule about what the A/V clock means rather than a predicate in a watchdog.
  • Whether the browser publisher's priority reaches QUIC send order. The tiers are declared and
    the send-side allocator is strict priority, but nothing shows the ordering arriving at the socket.
  • Per-track shedding visibility at the relay. moq_relay_stale_* is per tier, so a row can prove
    something was shed and not that audio was spared, which is exactly the claim the hierarchy makes.
    A track label behind a flag, or one debug line per expiry carrying track, sequence and code, would
    make it gradable. The same gap covers retransmitted video against fresh audio, where qlog carries
    stream ids and not tracks.

(Written by Claude Fable 5.1)

@fperex
fperex force-pushed the debug-findings-solution branch 2 times, most recently from d05ddae to 6baa498 Compare September 20, 2026 17:29
@fperex
fperex changed the base branch from dev to main September 20, 2026 17:29
@fperex

fperex commented Sep 20, 2026

Copy link
Copy Markdown
Owner Author

Upstream merged dev into main (moq-dev#3793) and deleted dev on 2026-09-20, so this branch
is now rebased onto main at f31ae9826, new tip 6baa4989f, 132 commits, and the pull request is
retargeted to main; the fork's main was fast-forwarded and its dev removed. The rebase was
clean: all 132 commits replayed unchanged (git range-diff all equal), the tree delta equals
upstream's ten commits, and one Cargo.lock entry was corrected inside the shaper commit.

Not re-run yet at this tip: the full gates. They follow, and the results will be posted here. The
known open problem still being worked on is video stability after a tab return and a swinging auto
target on a jittery path; audio continuity is confirmed locally by ear only.

(Written by Claude Fable 5.1)

@fperex
fperex force-pushed the debug-findings-solution branch 2 times, most recently from 0b16fcb to 86e9818 Compare September 21, 2026 07:31
fperex and others added 17 commits September 21, 2026 05:05
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
fperex and others added 24 commits September 29, 2026 19:35
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>
fperex and others added 2 commits October 1, 2026 13:04
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>
@fperex

fperex commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

PR #3 results after the PR #57 merge

PR #57 source 09a783a788b4354a59d1a517b6a24dd9784e9c60 merged as 9b2d051bc5661f024e59684a730d9ff4014abf99. PR #3 now points to d5937f527e382fbeeb5be82973de77ec03cd77bc. All eight final required checks and the whole Nightly run passed. The latest two commits correct the CI cache and test synchronization; they change no production API or wire format.

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 plan

The PR description retains each proving test; issue comments retain the file mapping.

Issue Commit pins and state
#31 340f3cdf4, 227faa489
#32 0667ad6a9, 9ed7494e9
#33 0667ad6a9
#34 relay c6c842c44, 2fe47e458, 294ee0306, 930027366, 38f75ef85; player 97d712ea8, 14bd9dd25; close by decision, player wait retained
#35 f3e2f8a78
#36 10d875182, 057e01bbf
#37 4c5a1bf5d
#38 10d875182, ad40bf861, f3e2f8a78
#39 2c607c97c, 4c5a1bf5d, 6e419705e, 2e8bd11e4, f8ee37a2c
#40 66c393048, d209e30ea
#41 4c5a1bf5d
#42 ee9d7cdc2
#43 09941b9bc, 6b25efc3c
#44 26f71e4e7
#45 f47fcee74
#46 26f71e4e7
#47 e8e61e701, 88bba04fe
#48 1ee78246d
#49 ad1380ba6
#50 f3e2f8a78, 1dfa19719
#51 c6c842c44, 2fe47e458, 294ee0306, b3da59228, 722f22a0d, a3f1a74f8
#52 description correction
#53 6a6ee9dd3, ad1380ba6
#54 untouched upstream TS publish line; open
#55 4844bc2dd, 738f9f104, 0df56d52a; fixed in fork, open upstream
#56 4c5a1bf5d

Every issue test remains in pushed PR #57 source 09a783a78.

Lock and retention. 75584b85f moves yanked yoke-derive 0.8.3 to 0.8.4 plus checksum, with no manifest/package, API or wire change. df3d2b444 weakly counts 10,000 effect-owned catch-result promises; explicit WeakRef root release and synchronous sweeping keep fewer than 100, and finally closes the effect. A no-deletion mutant retains all 10,000 and fails; 81 Signals tests, types and lint pass on Bun 1.3.13. Production Signals is unchanged. ARM CI's earlier 8,194 extra Promises remain unattributed.

Auth expiry ab075bbb0. Media roundtrips between 10 s virtual steps keep QUIC active through the unchanged 120 s lease/30 s slack; cadence 100 ms and default timeouts stay unchanged. Auth default/jemalloc each pass 12; default relay 232 pass/two skip; broader relay 339 pass/two skip. The test requires literal Expired. Prior 2a78f7425 red remains 282 pass/1 fail/56 unrun, transport idle versus Expired; DF publisher timeout also remains. Test-only, no API/wire change.

Trace retention a99d83dbd. Save after page.close() and pagehide flush, before context/browser close. Old AB exited 0 with beacon and no ZIP; fixed retains DOM snapshots and completed Page.close. The CI regression changes no playback, budgets, production, package, API or wire behavior; starvation remains unexplained.

Shared finished-cache confirmation. a3f1a74f8 requests the newest FINISHED group at frame 0 so a second front gets its head. It retransfers the full finished payload; open continuation/splice stay unchanged. The paused real-Origin test goes from Lagged to literal snapshot bytes; 1,277 moq-net tests pass. No API/wire change.

Catalog browser follow-up at a3f1a74f8: seven once-only Linux rows, five grader OK/two sampler failures. Chromium reattach r1: gap 2,078.6 ms after attach, one whole-window underrun/1,160 ms concealment; r2: 1,748.8 ms warmup gap, zero underruns/concealment. Other 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-to-media 61.2/64.4 ms, click-ready 145/179 ms; Firefox 22/1,341 ms. Relay log has no Lagged/no-route. Browser joins do not force the internal two-front overlap; the native real-Origin test does. Production Rust/JS matches df3d2b444, with a3's prior lock. ab075bbb0 adds auth tests; ac6461ae2 trace/docs/completion tests; 19bcd9d63 the socket oracle. Native binaries stay labeled a3f1a74f8 with its lock.

Historical relay CPU and RAM

The VPS relay rows measured the 0df56d52a binary (sha256 ffa082fe...), before the final catalog correction. The Mac rows and Criterion ran the relay branch head 23354da3d, before the publisher break and the finished-cache confirmation correction. Loads are real QUIC sessions from moq-bench, 50 single-frame groups per second per publisher.

Linux, Miami VPS (2 vCPU): 3 alternating rounds per cell, 60 s runs, the last 30 s measured. Median [min..max].

Cell CPU cores, original CPU cores, final CPU VmHWM KiB, original VmHWM KiB, final VmHWM p99 ms Loss
1x1 0.0185 [0.0180..0.0202] 0.0185 [0.0185..0.0202] +0.0% 29192 29240 +0.2% 1 / 1 0 / 0
1x10 0.0540 [0.0521..0.0541] 0.0521 [0.0442..0.0542] -3.5% 32724 34764 +6.2% 2 / 2 0 / 0
4x1 0.0578 [0.0568..0.0596] 0.0576 [0.0555..0.0597] -0.3% 45840 43596 -4.9% 1 / 1 0 / 0
4x10 0.1845 [0.1803..0.1898] 0.1877 [0.1697..0.1912] +1.7% 54240 57240 +5.5% 2 / 2 0 / 0
1x100, one round 0.3170 0.3152 -0.6% 50652 96016 see teardown 8 / 8 0 / 0

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.

Pair CPU cores, original / final Instructions, final / original Peak RSS MiB, original / final Loss %, original / final p99 ms, original / final
1 1.750 / 1.617 0.978 195.5 / 192.9 0.527 / 0.008 34 / 6
2 1.706 / 1.640 0.965 189.2 / 178.0 0.014 / 0.007 6 / 5
3 1.637 / 1.575 0.948 208.7 / 194.0 0 / 0 5 / 6
4 1.637 / 1.655 0.981 208.5 / 199.1 0 / 0 5 / 6
Median ratio 0.961 [0.924..1.011] 0.971 [0.948..0.981] 0.948 [0.930..0.987]

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.

Case family Instructions per delivery, final / original Criterion time, final against original, median [range]
Fanout, 1 to 512 subscribers 0.87 to 0.94 -4.6% [-5.6..-3.6]
Resume, 1 segment 0.98 to 1.01 +0.7% [-4.3..+6.3]
Warm cache 1.00 to 1.01 +3.2% [+2.4..+7.3]
Resume, 2 segments 1.14 to 1.16 +21.5% [+19.9..+25.7]
Resume, 3 segments 1.13 to 1.17 +21.2% [+18.1..+22.7]

2 h soak, VPS, 4x10: original, then final, each 2 h of load then 300 s idle. RSS sampled every 10 s.

Original Final
RSS at 5 min 53268 KiB 51312 KiB
RSS at the end of load (median of the last 5 min) 55648 KiB 53536 KiB
RSS growth over the last steady hour +0.0 KiB/h +17.9 KiB/h (53524 to 53536 KiB)
Peak (VmHWM) 56392 KiB 54180 KiB
RSS after 300 s idle 56392 KiB 54160 KiB
Relay CPU after 5 min, mean 0.1866 cores 0.1835 cores
Groups lost 0 of 14,398,982 1 of 14,398,928
Latency p99 (max over loads) / max 2 / 21 ms 3 / 30 ms

1x100 teardown, VPS: 3 rounds each, RSS sampled once a second through the end of load and 15 s after.

Original, median [range] Final, median [range]
Steady RSS 49280 KiB 49636 KiB
RSS peak at teardown 51212 [49516..52552] KiB 52440 [51624..67468] KiB
VmHWM 52808 [49516..54848] KiB 53520 [52440..97200] KiB
Settled RSS 51212 KiB 51748 KiB
Rounds with VmHWM over 30 MB above steady 0 of 3 1 of 3

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.

Audio

Linux importer lag, AAC fixed-250 row. From FFmpeg writing a PES into the pipe to moq import publishing it. The old importer read stdin 64 bytes at a time, so audio waited behind 200 to 400 KB keyframes.

Host Old reads 64 KiB reads (ca4817459)
GitHub runner, max 192 ms; 254 ms with 8 MiB socket buffers; 464 ms with loss-based congestion control 6 ms
GitHub runner, reads per PES (p50) 604 4
VPS, max / p99 177.8 / 43.6 ms 4.4 / 3.0 ms

The Audio quality workflow, Linux runner, Chromium.

Build Run Matrix, 24 rows Shared-ring lane, 2 rows Replay, 14 rows
Original 1217ea74b 36488034724 failed 4 of 36 enforced checks (AAC fixed-250 isolated underruns) not in that version 172 checks pass (PR check)
84fac1cfe (#57) 36597148161 36 of 36 pass; 4 rows void 24 of 24 pass 172 of 172 pass
84fac1cfe (#57) 36603907247 36 of 36 pass; 5 rows void 24 of 24 pass 172 of 172 pass
Earlier 0df56d52a 36683796849 passed passed passed
Earlier 0df56d52a 36689433474 failed with 0 over budget and 1 enforced void on fixed-250 AAC; the quiet source packs 16 frames into 371.52 ms, exceeding that delay. bc2680217 supplies a separately paced control; the new-head hosted result is reported below
  • Void rows have changed graphs/timelines/counters; recorded budgets are not enforced. Original harness lacks this classifier, so apparent zero cannot compare. Fixed-250 source failure is explained above; other voids unproved.
  • Non-enforced recorded ceilings: 13 in first 84fac1cfe run, 4 in second.
  • test/audio-quality/budgets.json is byte-identical to original.

Receiver stall cadence. ba8cc67ca learns burst cadence with rationed timers: stable seven-frame/162 ms bursts stop false flags; 371 ms gaps still flag. A 150 ms freeze after learned 162 ms with no pending task may be excused; outstanding probe directly measures receiver wait. No API/wire-format change.

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. bc2680217 gives fixed-250 AAC its own one-frame-per-PES publisher, so the source can sustain that delay. Adaptive AAC retains the seven- and sixteen-frame packed source. The metric ceilings, timeouts and run durations stay unchanged.

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; -pes_payload_size 0 still permits 13/about-302 ms, while -max_delay 0 gives one frame/PES. Packed and paced sources have byte-identical AAC access units. Fixed-250 uses the paced broadcast; adaptive keeps seven-/sixteen-frame packing. Ceilings, timeouts and durations are unchanged. Source pacing makes fixed-250 attainable; exact hosted outcomes appear below.

Readrate pin. Relinking moq stalls the next Mac launch about 2.9 s. FFmpeg -re catches up at 1.05x for 40 s; fixed 250 ms rightly drops a 5% fast source. Both publishers pin -readrate_catchup 1: first Chromium 0.9998x/0 skips versus 16.4/min; Safari 1.0007x/0 jumps versus 22.7 to 23.7/min. Upstream demo/pub/justfile has the same -re pattern; its pin is the maintainer's call.

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.

  • CI traces: an over-ceiling row (0.261 against 0.191) is proven 58 of 58 windows and passes; the original importer's row (0.239) stays failed, because its refused window is a real underrun.
  • In the two 84fac1cfe runs, four rows over a raw ceiling were proven window by window: 23 of 23, 58 of 58, 48 of 48 and 52 of 52.
  • By design it still refuses a rare row where Chromium's AnalyserNode runs one fftSize behind with too few audible windows after it: 1 of 17 local rows, 0 of 6 CI rows.

Safari AAC. Plan18 original/20 actual 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, BSD seq 1 0 emitted1,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; V6 measurements are tabulated below;45s versus partial5.4s cannot grade a regression. 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 match ab075bbb0; metadata DF. AAC13-/16-frame source explanation/receiver ambiguity retained; WebKit separate.

Browsers

Closed audio graphs, Chromium 153. Codex's heap harness, 100 detach and reattach cycles per ring, before and after the teardown fix (6b25efc3c, 78923c1ea).

Before (f21282589) Final
Playback resumed 100 of 100 cycles per ring 100 of 100 cycles per ring
Native AudioContext and AudioWorkletNode alive, cycle 10 to 99 11 to 99 (plain), 11 to 100 (isolated) 1 to 1 on both rings
close() after detach, median, 20-cycle quiet pair 0.20 ms 3.30 ms
Every tracked resource released after 2061.5 ms 2060 ms
  • Firefox 155, WebKit 26.6 and real Safari 26.6, worker on and off, 5 cycles each: 30 of 30 with every context closed and every node stopped.
  • Lifecycle census, 20 cycles each in Chromium: paused, muted, hidden and republished tiles leave nothing behind. An unmuted tile torn down before any user gesture still keeps one context and node per detach (known limit below).

Reopen and reattach, the Interop media recipe with paint probes, local.

Check Original 1217ea74b #57 before the fix With the fix
Chromium, black frame on resume not runnable 10 of 10, 50 to 85 ms 0 of 10
Chromium, black frame on reattach 10 of 10, 33 to 100 ms 10 of 10, 34 to 116 ms 0 of 10
Firefox, black frame on resume / reattach not run 5 of 5 / 5 of 5 0 of 5 / 0 of 5
WebKit, black frame on resume / reattach not run 5 of 5 / 5 of 5 0 of 5 / 0 of 5
Chromium, runs that painted an older picture (#55) not measured 5 of 30 0 of 30
Firefox, runs that painted an older picture (#55) not measured 4 of 10 0 of 10
  • "Before the fix" is fix: resolve the fork-only issues on PR #3 #57 before 738f9f104 for the black frame, and f21282589 (before 4844bc2dd) for older pictures. These loops ran on the fix branches; at 0df56d52a the same logic passes decoder.test.ts (23 of 23) and decoder.backwards.test.ts (10 of 10). The takeover coverage is below; the catalog follow-up is measured at a3f1a74f8, with source-equivalence limits below.
  • On 1217ea74b the resume case cannot run: its buffering spinner catches the pause click (fixed by 75280d49e).

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 rejoin.rs before the publisher break, IETF passed. A js/net publisher through the real relay went from 2 of 3 stale runs to 12 of 12 clean.

Linux, VPS, headless Chromium 153 and Firefox 155, the freeze set. Original against final 0df56d52a, 3 rows per cell, medians over the row.

Cell Chromium underruns Chromium concealed ms Firefox underruns Firefox concealed ms
c0 worker, plain, freezes 2 / 0 180 / 0 3 / 1 160 / 40
c1 main thread, plain, freezes 17 / 20 1260 / 1220 21 / 20 1400 / 1520
c2 worker, isolated, freezes 1 / 0 80 / 0 2 / 3 160 / 60
c3 main thread, isolated, freezes 21 / 20 1420 / 1160 21 / 21 1440 / 1880
c4 worker, plain, no freezes 0 / 1 0 / 20 2 / 2 140 / 40

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 1217ea74b/actual df3d2b444 rows are below; all graders pass. The 20 ms differences are unattributed. Both Chromium source-PTS windows include quiet 462.6 s; publisher elapsed is unknown without a connection log. This supplements0df and is not a full final-head matrix.

Actual-head long rows, original / df3d2b444:

Browser Whole underruns Concealed ms Maximum frame gap ms Minimum painted FPS Minimum video-input FPS
Chromium 1 / 1 780 / 760 602 / 601.8 8.2 / 7.4 22 / 22.4
Firefox 0 / 0 0 / 0 84 / 83 22.2 / 22.6 22.6 / 22.8
Playwright WebKit 2 / 2 200 / 220 169 / 166 19.9 / 17 22.1 / 21.5

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.
Linux completed 40 rows, a coverage count: one guarded tiles cell interrupted, 11 absent and two older catalog joins failed. The real-Origin regression fixes their shared-cache mechanism; seven once-only a3f1a74f8 rows retained five grader OK and two sampler failures, detailed below. Firefox's 800 ms quiet stretch is valid but source-phase confounded: original missed that phase, and matched original/final replay has identical underrun, skip and concealment counters. It cannot be attributed to this PR.

At bc2680217, three Playwright real-task fixtures PASS: healthy burst heads0/23 flagged with/without probe; actual150 ms freezes/660 ms busy tasks flag. Stall source matches a3f1a74f8/df3d2b444. Six playback rows: five metadata-provenance voids/one compact WebKit main pass, later underrun/760 ms concealment. Chromium20 hidden samples per row, Firefox/WebKit none; tab actions do not prove hiding. No retry/regrade; these prove neither catalog fix nor real Safari.

CI

AQ 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.

Actual head/run Enforced result Excluded breaches/rows; voids; accepted quiet-source cases
bc2680217 manual PASS eight/six; three; two
bc268 Nightly FAIL: 12 AAC and four main Opus metrics Opus: 6.5 underruns/min, 0.032 stalled share, 6.5 skips/min, 905.7 skipped ms/min; one main AAC void
a3f1a74f8 Nightly 36819237894 AQ PASS 11/seven; four; worker Opus one/main AAC one. Main Nightly failed yanked yoke-derive 0.8.3, fixed by lock update.
df3d2b444 manual 36822658662 PASS 12/eight; one; worker one/main AAC one
DF Nightly AQ job 110241399977 PASS five/four; five; worker two/main AAC one. Overall Nightly 36822661339 failed Mac auth publisher-connect.

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 0x34 at frame0 and later groups throughout 30 s. Cause is unresolved. All 32 local AB pairs and earlier hosted DF Interop passed; Python source matches DF.

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.

ac6461ae2: test-only full parser/real TCP/Origin selection ["hd"]/["stereo"] at elapsed ZERO (.070 s); sessions/drivers/server join before clock release. Production BUDGET500/CEILING1500/SETTLE30 ms/other TLS/QUIC tests unchanged. A99 red: 838 pass/one fail/three skip/4,127 unrun, [] versus ["stereo"]. Focused original .103 s PASS; paused-only .099 s PASS without stasis proof; discarded frozen QUIC60.008 s TIMEOUT. AC local check/Interop passed; no CPU/product-cause claim.

Actual ac6461ae2 manual AQ36847001121 and Nightly36847004709 PASS; Nightly76 successful/15 skipped jobs. Each AQ: replay172/14, worker36/24, main24/two rows; zero enforced breaches/voids. Worker exclusions, manual/Nightly:14 breaches/six rows/four voids/five quiet accepts; nine/seven/four/three. Each main: zero breaches/voids, one quiet-source accept. Nightly has56 release artifacts, no AQ archive. Full raw rows remain in PR #57; no source-cause or19b result follows.

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 1217ea74b conflicted with main; its old Interop failure remains historical. PR #3 stays open and draft at D593 and conflicts with current main. Final checks verify D593 against the original 1217ea74b base through PR58; they do not prove a merge into current main.

What Head Result
Scoped Nix just check and interop --all df3d2b444, ab075bbb0 pass; AB scoped from DF: 339 relay tests/two skipped, 32 Interop pairs
Historical PR #57 checks/Nightly36822661339 df3d2b444 Eight contexts passed. Nightly failed the macOS auth-lifetime publisher-connect timeout; its AQ job passed.
Exact AC gates ac6461ae2 Local 4,966 pass/three skip +84/zero skip; JS/build/lint and Interop32/32 pass. Seven PR checks pass; Test fails below. AQ details are above.
19b historical gates 19bcd9d63 pushed Local 4,966 pass/three skip + 84 pass/zero skip; JS/build/lint; Interop 32/32 PASS. Linux Test36853755318/110341245926 PASS:5,088 pass/6 skip78.659s; socket0.052s PASS; Mac skips it.
Candidate gates 09a783a78 pushed Local 4,966 passed/three skipped (72.071s) + 84 passed/zero skipped (4.513s); JS/build/lint; 32/32 Interop PASS. Eight actual PR checks SUCCESS.
09a hosted 09a783a78 Manual 36874501140 SUCCESS (2 jobs: 1 skipped/1 success); replay 172/14 SUCCESS; worker 36/24 SUCCESS, reported 10 metrics/6 rows/3 voids/3 quiet accepts; main 24/2 SUCCESS, reported 0 metrics/0 rows/0 voids/1 quiet accepts. Nightly 36874506339 SUCCESS (91 jobs: 15 skipped/76 success); replay 172/14 SUCCESS; worker 36/24 SUCCESS, reported 11 metrics/9 rows/4 voids/0 quiet accepts; main 24/2 SUCCESS, reported 0 metrics/0 rows/0 voids/1 quiet accepts.
19b hosted 19bcd9d63 Manual36853751819 PASS: replay172/14, worker36/24, main24/2; worker11 excluded breaches/6 rows/4 voids/no quiet allowance. Nightly36853756326 FAIL:75 success/1 AQ fail/15 skip; replay172/14, main24/2 PASS; worker3 enforced fixed250/plain AAC breaches:1.1 underruns/min,1.1 episodes/min,220.5 underrun ms/min, zero ceilings;26 excluded/10 rows/2 voids. Each main0/0+1 quiet. Nightly timing stays outside merge gates; 19b Interop failed.
19b PR Interop36853755161/110341245092 19bcd9d63 FAIL after all32 pairs and worker lifecycle40s PASS: main lifecycle14s, resumed tone67/81 above noise floor. Four negative controls PASS; cause unknown.
AC PR Test 36846958575/110319278310 ac6461ae2 FAIL: 4,769 pass/1 fail/6 skip/318 unrun; worker dropping_a_server_keeps_its_socket: 1 versus 2 after both drops. Unchanged from 1217ea74b; cause unknown.
Earlier PR #57 checks 0df56d52a 8 pass (Android, Audio quality replay, Check, Interop, OBS, Swift, Test, WASM); 3 skipped, as on every pull request (the full Audio quality job, Auto-merge, Release JS Packages)
Nightly, run 36683809519 0df56d52a passed
Merged verification attempt 9b2d051bc / #58 Interop FAILURE: cached aws-lc-rs build-script ENOENT before any pair/media case; no playback observations. Fallback target from 72adc66a2 crossed 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.
Earlier native bundle 9b2d051bc 36890958823 SUCCESS; artifact11178741993, SHA256 bdc6de62113292da71d6caddb3a32d6a9ea999b9cd9355d2145e95b8211e2a9c. Preserved build evidence, not the final corrected-head bundle.
CI-only cache boundary c11d5cb0dfa1fa36d6eb4620b4abcf260dddeff8 Four files, +69/-7: Interop and Nightly use the existing flake-pinned local Rust cache; a CI-wired structural regression checks the cache/restore key identity and architecture. Local strict TypeScript, all three YAML/cache tests, Biome and just check pass. No production runtime, public API or wire change.
C11 cache checkpoint c11d5cb0dfa1fa36d6eb4620b4abcf260dddeff8 / #58 Final required snapshot: seven SUCCESS and one Test FAILURE. Interop 36897932579/job 110489548849 passed 32 pairs. Worker phase FPS: 29.0, 29.3, 29.0, 45.2, 29.1, 29.6; main: 29.8, 29.2, 29.9, 30.0, 30.2, 30.2. TS WARN jitter was 24.67/24.34 ms and 132.66/84.80 ms against 0.5 ms. Counters, PSNR, SSIM and VMAF are absent; browser tone is not speaker listening. Test 36897932613/job 110489546888 failed: 560 pass/1 fail/6 skip/4,527 unrun; byte-identical upstream client.rs:361 expected dropped End but saw Connect after 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.
Test-only end synchronization d5937f527e382fbeeb5be82973de77ec03cd77bc, tree e97ed04a8f0c02974c8183c6c93b38b5e9358092 Sole parent C11; only cfg(test) rs/moq-auth/src/client.rs, +26/-9. Both end tests wait on the actual End; a 3-second bound fails a missing event. Pinned just check passed 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.
Final delivery verification d5937f527e382fbeeb5be82973de77ec03cd77bc / #58 All eight required contexts succeeded on attempt 1 against synthetic checkout 204736fb5552d26730c1166571c743b08cab0673, D593 merged into the original 1217ea74b base. 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 workflow bc68327bb8bd82732dbf5cc24b951f85281fe245; artifact 11185930440 was 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.
Before: PR #3 checks 1217ea74b Interop failed on media reattach; the rest passed
Before: Nightly no green run on the fork from 2026-09-15 to 2026-09-28; at 84e98b3a8 only Audio quality failed
DF Audio quality: excluded diagnostics and complete quiet-source proofs

These raw rows remain separate from enforced-check success. No reported void is regraded or hidden.

Run Lane Disposition Raw report
Manual worker non-enforced breach chromium-aac-44100-bursty-plain silence_share_share: 0.218 > 0.075; 47/48 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 71 audible windows); 1 of 48 quiet windows unproven, first at 42.50 s: audible source
Manual worker non-enforced breach chromium-aac-44100-high-rtt-isolated underrun_episodes_per_min: 1.1 > 0
Manual worker non-enforced breach chromium-aac-44100-high-rtt-isolated underrun_samples_per_min: 273 > 0
Manual worker non-enforced breach chromium-aac-44100-high-rtt-isolated converge_s_seconds: 56.5 > 11.6
Manual worker non-enforced breach chromium-aac-44100-near-zero-plain converge_s_seconds: 49.3 > 3
Manual worker non-enforced breach chromium-aac-44100-step-isolated silence_share_share: 0.214 > 0.061; 43/47 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 84 audible windows, 2 analyser lag segments); 4 of 47 quiet windows unproven, first at 16.00 s: audible source
Manual worker non-enforced breach chromium-opus-48000-bursty-isolated silence_share_share: 0.186 > 0.15; 38/41 quiet windows over quiet source (log RMS r 0.99997, level 1.000, 101 audible windows); 3 of 41 quiet windows unproven, first at 40.75 s: audible source
Manual worker non-enforced breach chromium-opus-48000-high-rtt-isolated silence_share_share: 0.036 > 0.007; 0/8 quiet windows over quiet source (log RMS r 0.99795, level 1.000, 148 audible windows); 8 of 8 quiet windows unproven, first at 54.25 s: alignment: log RMS correlation 0.99795 is under 0.999
Manual worker non-enforced breach chromium-opus-48000-high-rtt-plain underruns_per_min: 3.3 > 1.7
Manual worker non-enforced breach chromium-opus-48000-high-rtt-plain underrun_episodes_per_min: 2.2 > 0
Manual worker non-enforced breach chromium-opus-48000-high-rtt-plain underrun_samples_per_min: 817.3 > 0
Manual worker non-enforced breach chromium-opus-48000-step-isolated silence_share_share: 0.223 > 0.088; 46/49 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 123 audible windows); 3 of 49 quiet windows unproven, first at 43.00 s: audible source
Manual worker non-enforced void chromium-aac-44100-high-rtt-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Manual worker quiet-source proof accepted chromium-aac-44100-near-zero-plain silence_share_share: 0.348 > 0.075; 77/77 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 113 audible windows)
Manual shared/main quiet-source proof accepted chromium-aac-44100-fixed-250-isolated silence_share_share: 0.262 > 0.171; 58/58 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 162 audible windows)
Nightly AQ job worker non-enforced breach chromium-aac-44100-near-zero-plain converge_s_seconds: 3.7 > 3
Nightly AQ job worker non-enforced breach chromium-aac-44100-near-zero-plain silence_share_share: 0.345 > 0.075; 75/76 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 121 audible windows); 1 of 76 quiet windows unproven, first at 6.50 s: audible source
Nightly AQ job worker non-enforced breach chromium-aac-44100-step-isolated silence_share_share: 0.25 > 0.061; 4/55 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 84 audible windows, 2 analyser lag segments); 51 of 55 quiet windows unproven, first at 10.00 s: alignment: 37 audible windows prove its lag, fewer than 40
Nightly AQ job worker non-enforced breach chromium-opus-48000-high-rtt-isolated silence_share_share: 0.041 > 0.007; 0/9 quiet windows over quiet source (log RMS r 0.99996, level 1.000, 113 audible windows, 3 analyser lag segments); 9 of 9 quiet windows unproven, first at 52.50 s: lag change
Nightly AQ job worker non-enforced breach chromium-opus-48000-step-isolated silence_share_share: 0.217 > 0.088; 46/48 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 89 audible windows); 2 of 48 quiet windows unproven, first at 45.00 s: audible source
Nightly AQ job worker non-enforced void chromium-aac-44100-bursty-isolated: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly AQ job worker non-enforced void chromium-aac-44100-high-rtt-isolated: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly AQ job worker non-enforced void chromium-aac-44100-high-rtt-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly AQ job worker non-enforced void chromium-aac-44100-near-zero-isolated: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly AQ job worker non-enforced void chromium-opus-48000-bursty-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly AQ job worker quiet-source proof accepted chromium-aac-44100-bursty-plain silence_share_share: 0.222 > 0.075; 49/49 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 64 audible windows)
Nightly AQ job worker quiet-source proof accepted chromium-opus-48000-bursty-isolated silence_share_share: 0.159 > 0.15; 35/35 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 118 audible windows)
Nightly AQ job shared/main quiet-source proof accepted chromium-aac-44100-fixed-250-isolated silence_share_share: 0.268 > 0.171; 59/59 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 155 audible windows)
Stopped once-only real Safari continuation: all retained outcomes

Before 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.

Row Actual head Ownership Verdict Ready ms Catalog-to-media ms Whole underruns Concealed ms Max sampler gap ms Problems
csp-safari-r1-header-original 1217ea7 recorded void None 14.0 0 None 302 the page never reported both an audio playhead and a transport; no samples after the warmup window; csp: the audio covered null% of the wall time on the main thread
csp-safari-r1-meta-original 1217ea7 recorded ok 144 None 0 0 305
csp-safari-r2-header-original 1217ea7 recorded void None 12.0 0 None 305 the page never reported both an audio playhead and a transport; no samples after the warmup window; csp: the audio covered null% of the wall time on the main thread
csp-safari-r2-meta-original 1217ea7 recorded ok 56 10.0 0 0 304
csp-safari-r3-meta-original 1217ea7 recorded ok 272 7 0 0 307
rendition-safari-r1-original 1217ea7 recorded ok 293 None 0 0 372
restart-safari-r1-original 1217ea7 recorded problems 142 16 0 0 304 restart: the audio never played again after the relay came back
restart-safari-r2-original 1217ea7 recorded problems 258 19.0 0 0 365 restart: the audio never played again after the relay came back
restart-safari-r3-original 1217ea7 recorded problems 265 16.0 0 0 321 restart: the audio never played again after the relay came back
csp-safari-r1-header-final df3d2b4 recorded void None 13.0 0 None 304 the page never reported both an audio playhead and a transport; no samples after the warmup window; csp: the audio covered null% of the wall time on the main thread
csp-safari-r1-meta-final df3d2b4 recorded ok 18 10 0 0 307
csp-safari-r2-header-final df3d2b4 recorded void None 12.0 0 None 502 the page never reported both an audio playhead and a transport; no samples after the warmup window; csp: the audio covered null% of the wall time on the main thread
csp-safari-r2-meta-final df3d2b4 recorded ok 260 6 0 0 413
csp-safari-r3-meta-final df3d2b4 recorded ok 191 17 0 0 305
restart-safari-r1-final df3d2b4 recorded problems 295 15 0 0 301 restart: the audio never played again after the relay came back
restart-safari-r2-final df3d2b4 recorded problems 39 11 0 0 303 restart: the audio never played again after the relay came back
restart-safari-r3-final df3d2b4 recorded problems 246 7 0 0 305 restart: the audio never played again after the relay came back
rendition-safari-r2-original 1217ea7 recorded ok 176 15.0 0 0 301
rendition-safari-r3-original 1217ea7 recorded ok 118 12 0 0 304
tiles9-safari-r1-original 1217ea7 recorded ok 122 15.0 0 0 301
tiles9-safari-r2-original 1217ea7 recorded ok 280 15.0 0 0 366
tiles9-safari-r3-original 1217ea7 recorded ok 3 20.0 0 0 301
memory-safari-r1-final df3d2b4 recorded problems 289 39.0 0 0 304 All10 reattach events (1,2,3,4,5,6,7,8,9,10): audio never played again
memory-safari-r2-final df3d2b4 recorded problems 284 11 0 0 444 All10 reattach events (1,2,3,4,5,6,7,8,9,10): audio never played again
rendition-safari-r1-final df3d2b4 recorded ok 271 14 0 0 538
rendition-safari-r2-final df3d2b4 recorded ok 45 25.0 0 0 304
rendition-safari-r3-final df3d2b4 recorded ok 28 11 0 0 303
tiles9-safari-r1-final df3d2b4 recorded ok 261 14.0 0 0 398
tiles9-safari-r2-final df3d2b4 recorded ok 128 15.0 0 0 301
tiles9-safari-r3-final df3d2b4 recorded ok 35 13 0 0 301
tilesweep1-safari-final df3d2b4 recorded problems 210 None 0 0 303 tiles: the page carried 3 players, expected 1: bbb.hang, tile-0.hang, tile-1.hang
tilesweep10-safari-final df3d2b4 partial raw only void 117 None 0 0 1001 the row was stopped by SIGTERM before its end; no frame was painted after the warmup (painted 0); sampler gap of 1001ms, over the 1000ms ceiling; tiles: the per-tile decode counters were not read twice
tilesweep50-safari-final df3d2b4 V5 partial raw only void 278 None 0 0 806 guard SIGTERM/driver disconnect; missing second per-tile decode read; designated painted6.9fps median/4.7min vsinput24.1median; five untouched at V5
tilesweep1-safari 1217ea7 V6 ok 122 11.0 0 0 304 V6 once;paint24.1/23.1fps;video-input FPS24.1/22.8;paintgap67ms;skew39.2ms;inputreads1/1,threshold1,onScreen1
tilesweep10-safari 1217ea7 V6 ok 309 12 0 0 301 V6 once;paint24.1/22.8fps;video-input FPS24/22.8;paintgap67ms;skew33.5ms;inputreads10/10,threshold10,onScreen10
tilesweep50-safari 1217ea7 V6 problems 266 45 5 4460 784 V6 once;paint5.6/0fps;video-input FPS17.8/0;paintgap6745ms;skew5537.5ms;inputreads50/50,threshold28,onScreen18
long-safari-worker-plain 1217ea7 V6 ok 290 11 1 560 348 V6 once;paint24/13.5fps;video-input FPS24/22.6;paintgap616ms;skew40.8ms
long-safari-worker-plain df3d2b4 V6 ok 268 12.0 1 560 314 V6 once;paint24/9.5fps;video-input FPS24/22.6;paintgap616ms;skew39.3ms
Actual 2a AQ: manual success, Nightly failure and all scoped exclusions

Manual 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.

Run Lane Disposition Raw report
Manual2a worker non-enforced breach chromium-aac-44100-high-rtt-plain underruns_per_min: 3.3 > 0
Manual2a worker non-enforced breach chromium-aac-44100-high-rtt-plain underrun_episodes_per_min: 2.2 > 0
Manual2a worker non-enforced breach chromium-aac-44100-high-rtt-plain underrun_samples_per_min: 545.5 > 0
Manual2a worker non-enforced breach chromium-aac-44100-high-rtt-plain stalled_quanta_share: 0.005 > 0
Manual2a worker non-enforced breach chromium-aac-44100-mild-isolated underruns_per_min: 3.3 > 1.7
Manual2a worker non-enforced breach chromium-aac-44100-mild-isolated underrun_episodes_per_min: 3.3 > 0
Manual2a worker non-enforced breach chromium-aac-44100-mild-isolated underrun_samples_per_min: 748.7 > 0
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-isolated underruns_per_min: 2.2 > 0
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-isolated underrun_episodes_per_min: 1.1 > 0
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-isolated underrun_samples_per_min: 272.7 > 0
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-isolated skip_aheads_per_min: 3.3 > 0
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-isolated skipped_samples_per_min: 7.7 > 0
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-plain underruns_per_min: 1.1 > 0
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-plain underrun_episodes_per_min: 1.1 > 0
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-plain underrun_samples_per_min: 170.6 > 0
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-plain converge_s_seconds: 51.3 > 3
Manual2a worker non-enforced breach chromium-aac-44100-near-zero-plain silence_share_share: 0.25 > 0.075; 53/55 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 138 audible windows); 2 of 55 quiet windows unproven, first at 43.09 s: discontinuity
Manual2a worker non-enforced breach chromium-aac-44100-step-isolated underruns_per_min: 2.2 > 1.7
Manual2a worker non-enforced breach chromium-aac-44100-step-isolated stalled_quanta_share: 0.009 > 0.007
Manual2a worker non-enforced breach chromium-aac-44100-step-isolated converge_s_seconds: 57.8 > 54.8
Manual2a worker non-enforced breach chromium-aac-44100-step-isolated silence_share_share: 0.281 > 0.061; 60/62 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 78 audible windows); 2 of 62 quiet windows unproven, first at 49.00 s: audible source
Manual2a worker non-enforced breach chromium-aac-44100-step-plain underruns_per_min: 2.2 > 1.7
Manual2a worker non-enforced breach chromium-opus-48000-step-isolated silence_share_share: 0.177 > 0.088; 38/39 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 97 audible windows); 1 of 39 quiet windows unproven, first at 48.50 s: audible source
Manual2a worker non-enforced void chromium-aac-44100-bursty-isolated: playout (the audio graph, timeline, or counters changed during the measured window)
Manual2a worker non-enforced void chromium-aac-44100-bursty-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Manual2a worker non-enforced void chromium-aac-44100-high-rtt-isolated: playout (the audio graph, timeline, or counters changed during the measured window)
Manual2a worker non-enforced void chromium-aac-44100-mild-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Manual2a worker non-enforced void chromium-opus-48000-bursty-isolated: playout (the audio graph, timeline, or counters changed during the measured window)
Manual2a worker quiet-source proof accepted chromium-aac-44100-fixed-250-isolated silence_share_share: 0.208 > 0.171; 46/46 quiet windows over quiet source (log RMS r 0.99998, level 1.000, 174 audible windows)
Manual2a worker quiet-source proof accepted chromium-opus-48000-high-rtt-isolated silence_share_share: 0.009 > 0.007; 2/2 quiet windows over quiet source (log RMS r 0.99998, level 1.000, 169 audible windows)
Manual2a worker quiet-source proof accepted chromium-opus-48000-high-rtt-plain silence_share_share: 0.109 > 0.102; 24/24 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 105 audible windows)
Manual2a shared/main quiet-source proof accepted chromium-aac-44100-fixed-250-isolated silence_share_share: 0.25 > 0.171; 55/55 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 158 audible windows)
Nightly2a worker non-enforced breach chromium-aac-44100-high-rtt-plain underruns_per_min: 2.2 > 0
Nightly2a worker non-enforced breach chromium-aac-44100-high-rtt-plain stalled_quanta_share: 0.009 > 0
Nightly2a worker non-enforced breach chromium-aac-44100-mild-isolated underruns_per_min: 3.3 > 1.7
Nightly2a worker non-enforced breach chromium-aac-44100-mild-isolated underrun_episodes_per_min: 2.2 > 0
Nightly2a worker non-enforced breach chromium-aac-44100-mild-isolated underrun_samples_per_min: 395.8 > 0
Nightly2a worker non-enforced breach chromium-aac-44100-mild-isolated skip_aheads_per_min: 5.5 > 4.9
Nightly2a worker non-enforced breach chromium-aac-44100-near-zero-isolated underruns_per_min: 1.1 > 0
Nightly2a worker non-enforced breach chromium-aac-44100-near-zero-isolated skip_aheads_per_min: 1.1 > 0
Nightly2a worker non-enforced breach chromium-aac-44100-near-zero-isolated skipped_samples_per_min: 690.2 > 0
Nightly2a worker non-enforced breach chromium-aac-44100-step-isolated underruns_per_min: 2.2 > 1.7
Nightly2a worker non-enforced breach chromium-aac-44100-step-isolated underrun_episodes_per_min: 2.2 > 1.7
Nightly2a worker non-enforced breach chromium-aac-44100-step-isolated underrun_samples_per_min: 448.6 > 411.5
Nightly2a worker non-enforced breach chromium-aac-44100-step-isolated silence_share_share: 0.218 > 0.061; 47/48 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 82 audible windows); 1 of 48 quiet windows unproven, first at 38.75 s: audible source
Nightly2a worker non-enforced breach chromium-aac-44100-step-plain underruns_per_min: 2.2 > 1.7
Nightly2a worker non-enforced breach chromium-aac-44100-step-plain underrun_episodes_per_min: 2.2 > 1.7
Nightly2a worker non-enforced breach chromium-aac-44100-step-plain underrun_samples_per_min: 537.9 > 420.5
Nightly2a worker non-enforced breach chromium-opus-48000-near-zero-plain converge_s_seconds: 54.5 > 46.5
Nightly2a worker non-enforced void chromium-aac-44100-bursty-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly2a worker non-enforced void chromium-aac-44100-high-rtt-isolated: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly2a worker non-enforced void chromium-aac-44100-mild-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly2a worker non-enforced void chromium-aac-44100-near-zero-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly2a worker non-enforced void chromium-opus-48000-bursty-isolated: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly2a worker non-enforced void chromium-opus-48000-bursty-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly2a worker quiet-source proof accepted chromium-aac-44100-fixed-250-isolated silence_share_share: 0.209 > 0.171; 46/46 quiet windows over quiet source (log RMS r 0.99999, level 1.000, 171 audible windows)
Nightly2a worker quiet-source proof accepted chromium-opus-48000-high-rtt-plain silence_share_share: 0.113 > 0.102; 25/25 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 146 audible windows)
Nightly2a worker quiet-source proof accepted chromium-opus-48000-step-isolated silence_share_share: 0.141 > 0.088; 31/31 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 104 audible windows)
Nightly2a shared/main ENFORCED FAILURE chromium-aac-44100-fixed-250-isolated underruns_per_min: 1.1 > 0
Nightly2a shared/main ENFORCED FAILURE chromium-aac-44100-fixed-250-isolated stalled_quanta_share: 0.009 > 0
Nightly2a shared/main ENFORCED FAILURE chromium-aac-44100-fixed-250-isolated skip_aheads_per_min: 1.1 > 0
Nightly2a shared/main ENFORCED FAILURE chromium-aac-44100-fixed-250-isolated skipped_samples_per_min: 90.6 > 0
Nightly2a shared/main ENFORCED FAILURE chromium-opus-48000-fixed-250-isolated underruns_per_min: 1.1 > 0
Nightly2a shared/main ENFORCED FAILURE chromium-opus-48000-fixed-250-isolated stalled_quanta_share: 0.009 > 0
Nightly2a shared/main ENFORCED FAILURE chromium-opus-48000-fixed-250-isolated skip_aheads_per_min: 1.1 > 0
Nightly2a shared/main ENFORCED FAILURE chromium-opus-48000-fixed-250-isolated skipped_samples_per_min: 370.9 > 0
Nightly2a shared/main quiet-source proof accepted chromium-aac-44100-fixed-250-isolated silence_share_share: 0.241 > 0.171; 53/53 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 150 audible windows)
Actual AB manual AQ: all excluded readings and accepted quiet-source cases

Run 36835459388/job 110281681508 passed at ab075bbb0, not a99. Worker: 11 breaches/five rows, one void, six quiet accepts. Main: zero breaches/voids, one quiet accept.

Run Lane Disposition Raw report
Manual AB worker non-enforced breach chromium-aac-44100-bursty-isolated underrun_episodes_per_min: 1.1 > 0
Manual AB worker non-enforced breach chromium-aac-44100-bursty-isolated underrun_samples_per_min: 272.7 > 0
Manual AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated underruns_per_min: 2.2 > 1.7
Manual AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated underrun_episodes_per_min: 2.2 > 0
Manual AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated underrun_samples_per_min: 545.6 > 0
Manual AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated converge_s_seconds: 51.2 > 11.6
Manual AB worker non-enforced breach chromium-aac-44100-near-zero-plain converge_s_seconds: 13.3 > 3
Manual AB worker non-enforced breach chromium-opus-48000-high-rtt-plain silence_share_share: 0.123 > 0.102; 26/27 quiet windows over quiet source (log RMS r 0.99999, level 1.000, 142 audible windows); 1 of 27 quiet windows unproven, first at 51.25 s: audible source
Manual AB worker non-enforced breach chromium-opus-48000-mild-isolated skip_aheads_per_min: 1.1 > 0
Manual AB worker non-enforced breach chromium-opus-48000-mild-isolated skipped_samples_per_min: 109.4 > 0
Manual AB worker non-enforced breach chromium-opus-48000-mild-isolated target_ms_p95: 520 > 480
Manual AB worker non-enforced void chromium-aac-44100-mild-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Manual AB worker quiet-source proof accepted chromium-aac-44100-bursty-plain silence_share_share: 0.127 > 0.075; 28/28 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 121 audible windows)
Manual AB worker quiet-source proof accepted chromium-aac-44100-fixed-250-isolated silence_share_share: 0.231 > 0.171; 51/51 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 169 audible windows)
Manual AB worker quiet-source proof accepted chromium-aac-44100-near-zero-plain silence_share_share: 0.24 > 0.075; 53/53 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 151 audible windows)
Manual AB worker quiet-source proof accepted chromium-aac-44100-step-isolated silence_share_share: 0.323 > 0.061; 71/71 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 80 audible windows)
Manual AB worker quiet-source proof accepted chromium-opus-48000-bursty-isolated silence_share_share: 0.158 > 0.15; 35/35 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 120 audible windows)
Manual AB worker quiet-source proof accepted chromium-opus-48000-step-isolated silence_share_share: 0.114 > 0.088; 25/25 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 99 audible windows)
Manual AB main quiet-source proof accepted chromium-aac-44100-fixed-250-isolated silence_share_share: 0.241 > 0.171; 53/53 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 166 audible windows)
Actual AB Nightly AQ: all scoped exclusions and accepted quiet-source cases

Run 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.

Run Lane Disposition Raw report
Nightly AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated underruns_per_min: 4.4 > 1.7
Nightly AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated underrun_episodes_per_min: 1.1 > 0
Nightly AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated underrun_samples_per_min: 276.8 > 0
Nightly AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated stalled_quanta_share: 0.014 > 0.007
Nightly AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated skip_aheads_per_min: 2.2 > 1.7
Nightly AB worker non-enforced breach chromium-aac-44100-high-rtt-isolated converge_s_seconds: 56.3 > 11.6
Nightly AB worker non-enforced breach chromium-aac-44100-step-isolated converge_s_seconds: 55.2 > 54.8
Nightly AB worker non-enforced breach chromium-aac-44100-step-plain silence_share_share: 0.348 > 0.177; 69/77 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 75 audible windows, 2 analyser lag segments); 8 of 77 quiet windows unproven, first at 25.50 s: audible source
Nightly AB worker non-enforced breach chromium-opus-48000-bursty-plain underrun_episodes_per_min: 1.1 > 0
Nightly AB worker non-enforced breach chromium-opus-48000-bursty-plain underrun_samples_per_min: 272.6 > 0
Nightly AB worker non-enforced void chromium-aac-44100-bursty-isolated: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly AB worker non-enforced void chromium-aac-44100-bursty-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly AB worker non-enforced void chromium-aac-44100-mild-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly AB worker non-enforced void chromium-aac-44100-near-zero-plain: playout (the audio graph, timeline, or counters changed during the measured window)
Nightly AB worker quiet-source proof accepted chromium-aac-44100-fixed-250-plain silence_share_share: 0.214 > 0.191; 47/47 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 172 audible windows)
Nightly AB worker quiet-source proof accepted chromium-aac-44100-step-isolated silence_share_share: 0.205 > 0.061; 45/45 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 106 audible windows)
Nightly AB worker quiet-source proof accepted chromium-opus-48000-bursty-isolated silence_share_share: 0.164 > 0.15; 36/36 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 106 audible windows)
Nightly AB worker quiet-source proof accepted chromium-opus-48000-high-rtt-plain silence_share_share: 0.118 > 0.102; 26/26 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 91 audible windows)
Nightly AB main quiet-source proof accepted chromium-aac-44100-fixed-250-isolated silence_share_share: 0.273 > 0.171; 60/60 quiet windows over quiet source (log RMS r 1.00000, level 1.000, 159 audible windows)

Known limits

Actual AC PR Test36846958575/job110319278310 FAIL: 4,769 pass/one fail/six skip/318 unrun; Linux dropping_a_server_keeps_its_socket counts one versus two after both drops. Test unchanged from 1217ea74b; observer counts network namespace. Actual initiating owner unproved; no CPU/production-retainer attribution. This historical checkpoint is distinct from the final D593 checks above.

19bcd9d63: test-only owned sockets survive both drops, release at shutdown; bind controls verify it. Docker namespace count 3 to 2 leaves owned IDs stable. Historical CI cause unproved; Linux Test PASS above. No production/API/wire change. Child isolation/receipt prevents sibling or zero-test interference; Mac skips Linux fixture.

09a783a78: three harness files (+105/-2) surface browser-close errors after every close attempt. Swallow mutant:1 PASS/2 FAIL; fix:3 tests/6 assertions PASS, package6 PASS. Prior midwindow owner remains unknown. No playback/API/wire/budget change. DF differs only in12 test/doc/harness objects;2,705 others/eight AQ inputs match. Binaries stay DF; no compiled-binary/CPU equivalence.

Not tested

  • Phones, Windows and Android.
  • Brave and the installed Chrome and Firefox; takeover runs use Playwright-owned engines, with real Safari separately retained as36 coordinated results/two partial raws.
  • The Mac relay pairs below 4x100 on the final relay; the VPS covers 1x1 to 4x10 and 1x100.
  • Criterion and the Mac 4x100 pairs on the final head itself; they ran on 23354da3d (before the publisher break and finished-cache confirmation correction).
  • The cause of every non-enforced historical audio void. The original harness lacked the comparable void classifier.
  • Listening: every grade here is counters, timestamps and aligned audio taps.

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 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 /demo plus bbb.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.

Drafted by Claude Opus 5.5. Edited and posted by Codex.

(Written by GPT-6)

@fperex

fperex commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

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)

@fperex

fperex commented Oct 4, 2026

Copy link
Copy Markdown
Owner Author

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 debug-findings-solution; this benchmark makes no new merge or deployment. PR #3 remains a separate draft.

BEFORE: 1217ea74b3b2a5bbe3ae7beba4bf932ecfacc9c5. AFTER: 9b2d051bc5661f024e59684a730d9ff4014abf99. Each browser side used its own exact head build, with common instrumentation and media. Native relay and WAN comparisons used the same AFTER load client against both pinned relay heads. Failed and void attempts are retained without retries or regrading. Linux lifecycle24 results below are reviewed. Linux unique4 has no paired playback result. Both affected hosts have ended timing with reviewed clean boundaries; the final explicit identity register and completed Linux resource reductions are described below; all saved Linux resource, reference and timeline audits are completed.

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.

Mac browser 50-player updates/sec, BEFORE AFTER 1-player CPU sec/sec, BEFORE → AFTER Paired CPU change 50-player RSS MiB, BEFORE → AFTER
Safari 7.063 7.062 Unmeasured Unavailable 1,131.25 → 1,120.25
Playwright WebKit 6.542 6.541 0.556 → 0.574 +2.85%, mixed 1,070.00 → 1,058.50
Playwright Chromium 24.001 24.001 0.614 → 0.663 +7.10% 2,201.40 → 2,195.80
Chrome 24.000 24.003 0.629 → 0.676 +7.34% 2,216.65 → 2,211.70
Brave 24.001 24.001 0.627 → 0.683 +10.43% 2,070.90 → 2,059.35
Native Firefox 22.084 22.931 0.499 → 0.517 +4.48%, partial cohort 3,318.75 → 3,388.40
Playwright Firefox 17.925 18.650 0.530 → 0.558 +5.25% 2,739.15 → 2,840.70

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.

Browser Players Admitted pairs Updates/sec, BEFORE → AFTER CPU sec/sec, BEFORE → AFTER Sampled maximum memory charge MiB, BEFORE → AFTER Limits
Playwright Chromium 1 2/3 24.001 → 24.011 0.607 → 0.612 1011.672 → 1011.463 1 functional/capture or startup limits
Playwright Chromium 10 0/3 Unavailable Unavailable Unavailable 6 functional/capture or startup limits
Playwright Chromium 50 0/3 Unavailable Unavailable Unavailable 6 owned 1.5GiB cap failures
Chrome 1 3/3 24.091 → 23.998 0.504 → 0.512 973.422 → 969.105 3 original pairs; WT
Chrome 10 3/3 15.037 → 15.342 1.365 → 1.395 1392.309 → 1378.445 3 original pairs; WT
Chrome 50 0/3 Unavailable Unavailable Unavailable 6 owned 1.5GiB cap failures
Brave 1 3/3 24.000 → 24.000 0.512 → 0.505 959.566 → 959.910 3 original pairs; WT
Brave 10 3/3 14.830 → 15.202 1.359 → 1.388 1360.012 → 1365.406 3 original pairs; WT
Brave 50 0/3 Unavailable Unavailable Unavailable 6 owned 1.5GiB cap failures
Native Firefox 1 3/3 24.010 → 24.012 0.863 → 0.868 814.797 → 815.527 3 original pairs; WT
Native Firefox 10 3/3 10.724 → 12.918 1.485 → 1.504 1078.453 → 1093.141 3 original pairs; WT
Native Firefox 50 0/3 Unavailable Unavailable Unavailable 6 owned 1.5GiB cap failures
Playwright Firefox 1 3/3 24.003 → 24.004 0.806 → 0.788 716.734 → 719.691 3 original pairs; WT
Playwright Firefox 10 3/3 10.722 → 11.837 1.467 → 1.490 1072.273 → 1094.852 3 original pairs; WT
Playwright Firefox 50 0/3 Unavailable Unavailable Unavailable 6 owned 1.5GiB cap failures
Playwright WebKit 1 1/3 0.181 → 0.046 1.597 → 1.695 912.883 → 1213.246 1 functional/capture or startup limits; 3 unattempted; WS; severe stalls; partial phase
Playwright WebKit 10 0/3 Unavailable Unavailable Unavailable 6 unattempted
Playwright WebKit 50 0/3 Unavailable Unavailable Unavailable 6 unattempted

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.

Browser Players Problem pairs Observed updates/sec, BEFORE → AFTER Observed CPU sec/sec Observed sampled maximum charge MiB Status
Playwright Chromium 1 1 23.962 → 24.016 0.614 → 0.605 1041.629 → 1008.637 Public functional driver problems; measurements retained
Playwright Chromium 10 3 6.602 → 5.839 1.048 → 0.978 1474.789 → 1467.977 Public functional driver problems; measurements retained

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.

Test BEFORE AFTER Result and scope
Mac Chromium, 50 unique publishers, median updates/sec 11.511 23.953 Favorable single pair; different achieved delivery
Same stress, longest update gap / video skips 10.431 sec / 342 0.187 sec / 1 Shorter gaps and fewer skips; no PCM or pictures
Rust Noise, 6 channels 2,003.5 ns 1,734.9 ns Faster in every pair; native Rust only
Noise allocations/update, 1 / 2 / 6 channels 1 / 2 / 6 0 / 0 / 0 Allocation removal
Cached resume, 2 segments / 100 groups / 10 subscribers 1,025 µs 1,257 µs Slower in every pair; not network-hop latency
Aborted-track scan, 512 4.692 µs 5.965 µs Slower in every pair; includes subscription creation/drop
Fanout model, 64 18.53 µs 17.85 µs Faster in every pair
Parked requested heap, 100 aliases / 2,048 groups 50.288 MB 50.134 MB About 0.31% less requested storage; not RSS
Warm replay, same 1.2 MB payload 0 new downstream bytes 12,109 bytes/hop One deterministic pass; added MoQ stream bytes, excluding transport overhead
Mac relay audio CPU, 1 publisher × 100 subscribers 0.769 sec/sec 0.726 sec/sec Lower in every pair
Mac relay audio CPU, 4 × 1 0.105 sec/sec 0.109 sec/sec Higher in every pair
Mac relay audio CPU / cumulative p99, 4 × 100 1.786 sec/sec / 5 ms 1.727 sec/sec / 6 ms Lower CPU, higher p99 including startup
Mac relay video CPU, 1 × 1 0.0505 sec/sec 0.0607 sec/sec Higher in every pair
Mac relay video max steady RSS, 1 × 100 91.47 MiB 92.86 MiB Higher in every pair
Clean teardown sampled RSS peak, 1 × 100 51.39 MiB 46.64 MiB Lower in every pair; other teardown measures mixed
VPS native audio relay RSS, 1 × 10 31.84 MiB 30.14 MiB Lower in every pair; CPU mixed
VPS native video relay RSS, 1 × 1 54.43 MiB 52.77 MiB Lower in every pair; CPU mixed
WAN video delivered share, 1 × 100 13.31% 13.20% Poor on both; all WAN delivery gates fail

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.

Sound case BEFORE AFTER Result
Linux native Firefox, near-zero AAC 90.4% nonzero; 0 underruns 49.6%; 13 underruns Adverse continuity observation
Linux Playwright Firefox, near-zero AAC 90.6%; 0 underruns 40.7%; 7 underruns Adverse continuity observation
Linux Brave, near-zero AAC 0 underruns 3 underruns Adverse counter observation
Mac native Firefox, near-zero AAC 52 underruns 41 underruns Fewer reported interruptions
Mac Playwright Firefox, near-zero AAC 47 underruns 0 underruns Favorable counter observation
Mac Playwright WebKit, near-zero AAC 29 underruns 56 underruns More reported interruptions
Both Firefox installations on Mac/Linux, near-zero Opus 0 steady underruns 0 steady underruns Unchanged counters, not sound passes
Linux Playwright Firefox, 8-minute BBB over WebTransport 48 underruns / 4,500 ms concealment 44 / 3,620 ms Fewer interruptions; both still interrupt

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.

Linux browser Fresh-name source: mean updates/sec, BEFORE → AFTER Fresh-name source: reported recovery p50 ms Relay-only: mean updates/sec Relay-only: reported recovery p50 ms
Playwright Chromium 23.5 → 23.5 79.8 → 69.3 23.7 → 23.7 168.9 → 64.8
Chrome 23.6 → 23.6 89.8 → 48.4 23.9 → 23.8 77.2 → 139.7
Brave 23.6 → 23.9 87.3 → 73 23.8 → 23.8 26.8 → 109
Native Firefox 23.5 → 23.6 83 → 67 23.6 → 23.7 115 → 82
Playwright Firefox 23.6 → 23.6 94 → 88 23.8 → 23.8 262 → 119
Playwright WebKit 0.5 → 0.4 160 → 64 0.6 → 0.4 100 → 67

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.

Unchanged, limited or unavailable aspect Retained result
Linux WebKit, one admitted WebSocket pair Over 44.7 sec, input medians 24.1 → 24.0 frames/sec, mean paint updates 0.2 → 0.1/sec, stalled share 90.1% → 97.5% and paints 8 → 2. Poor on both; one pair, no PCM/pixels and no compositor or quality pass.
Linux WK coverage 3 starts: 2 admitted and 1 independent startup failure with zero samples; 31 unattempted. Receipt exit 124 and unit journal numeric 3/NOTIMPLEMENTED do not diagnose an unsupported API. No retry or forced suffix coverage was run.
Safari additional coverage The 58-case continuation was refused before playback. Historical 50-player results retain their own environment limits.
Fixed-cap browser/native cases All 30 attempted Linux main 50-player rows across five identities hit the same 1.5 GiB driver cap; WebKit 50-player rows are unattempted. High native video hits the common load-client cap; one AFTER case also records relay OOM. Missing windows remain unavailable.
Lifecycle and strict CSP Mac retains strict-header startup voids, intentional outages, reset limits and mute-oracle problems. Broad-tail mute failures can include planned muted cycles; they are neither new PR regressions nor silently regraded passes. Linux lifecycle24 results above preserve their outages, player resets and lack of PCM capture.
API, wire and interoperability break_at/breakAt, paused/resumed prior-group handling, OLD→Expired mapping and consuming moq_tokio::Client::close(self) affect public API and peer behavior. Exact-AFTER Mac data-path interop passed 32/32 once; same-head only, with eight native Node/Bun termination statuses unknown. Linux interop CI failed before tests.
Linux unique4 unavailable n10 AFTER stopped before browser launch on missing numeric mux PTS/source admission; outer/controller exit 2. n10 BEFORE and both n50 were unattempted. No retry or continuation was run.
Evidence limits ROOT and an independent reviewer checked the complete explicit coverage register, report and saved-data audits. No all834 strict normalization or quality pass is claimed.
Unmeasured claims Compositor FPS, energy, listening/MOS, all-player stereo quality and individual-commit causality.

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 track::Producer::break_at and JS track.Producer.breakAt. A subscription newly created at the producer starts no earlier than its last declared timeline break, while explicit fetches can still retrieve earlier groups. The break stays at that publisher. A relay already subscribed when the break is declared does not automatically learn it, so a viewer joining that relay during a pause may still start on the earlier group. These markers cover media paused and resumed by its publisher, where the last pre-pause group otherwise continues to look live until newer media arrives. Serving-cap pause semantics retain groups beyond end_group and resume them after SUBSCRIBE_UPDATE; this cap is not a subscription terminator. JS Expired now carries StreamCode.Old and maps a peer's OLD reset, where BEFORE used DeliveryTimeout. Native moq_tokio::Client::close(self) is a new consuming async shutdown for the shared noq QUIC endpoint, including connections held by its clones. WebSocket/TCP/UDS sessions instead end when their Connection is dropped; an externally supplied iroh endpoint remains its owner's responsibility. These are public API, error/reset and subscription behavior impacts, not a claim that unchanged message framing preserves identical peer behavior.

(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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

js/watch: a video track reopened after a gap paints pictures older than the one on screen

2 participants