Skip to content

test(audio-quality): grade steady-state lag, the player's own trims, and recovery from congestion - #8459

Merged
kixelated merged 4 commits into
mainfrom
quest/m1/audio-jitter-target/grading
Nov 10, 2026
Merged

kixelated merged 4 commits into
mainfrom
quest/m1/audio-jitter-target/grading

Conversation

@kixelated

@kixelated kixelated commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Some of the audio harness's gates reward a player for moving away from the epic's model, where audio rests on the shared clock, trails it through a stall, and catches up after:

  • skip_aheads and discarded_ms, on all 29 rows, infer a skip from a step in the playhead's lag. A time stretch catching up reads as dropped audio.
  • The 24 browser and native rows cap underruns but put no ceiling on lag, so latency could buy fewer underruns unnoticed. Native settles 9 to 95 ms behind its clock.
  • The replay note said any change that settles audio later fails, which rules out lag through a stall.
  • No row measured a stall longer than the target followed by a backlog.

Completes quest/m1/audio-jitter-target/grading.md (deleted here).

Approach

  • Dropped audio is the player's own count. Deleted skip_aheads and discarded_ms (schema, analyzer, tests, grade table, every budget). Every row now budgets player_skips_per_min and trimmed_ms_per_min.
  • Lag. Every browser and native row budgets av_lag_ms p50 (steady state) and p95 (through a stall). The ceilings have measured headroom, and none is fixed at zero.
  • Recovery. New recover_ms (max): the longest stretch audio spends stalled or more than one 20 ms bucket behind the window's median lag. It is reported, not budgeted, because it is the time a stall costs, and a player may trade that time against the audio it keeps.
  • Congestion, twice.
    • The congestion live profile (congestion.toml) is 1 Mbit/s down behind a 1 s queue. The rate collapses to 16 kbit/s from 25 s to 26.5 s, so the queue fills and then drops, per the bufferbloat rule. When the rate comes back, the transport delivers the backlog. This adds 4 rows (browser and native, Opus and AAC).
    • The relay-mic-stall replay trace is relay-mic with nothing arriving from 15 s to 16.5 s. The held frames then trickle in at twice real time until 18 s.
  • Budget note and README rewritten. A re-measured move is a regression to fix when the steady-state lag rises or the player trims audio. A p95 lag that rises through a stall, or an underrun count that moves, is re-measured in. Replay rows stay exact.
  • Also updated: the push-to-talk comments and the README link to the deleted quest, row counts in test/justfile and the nightly.yml comments, and the run.sh duration guard, which now covers congestion too.

How the new ceilings were measured

All ceilings use the note's rule: the worst run times 1.25, plus the documented hiccup, rounded up to two significant figures. av_lag_ms is signed, so its ceiling is the worst run plus a quarter of its size plus one 20 ms bucket. All new ceilings are provisional until calibrate re-measures every row from seven Nightlies with the model.

  • Browser rows: from the only two scheduled Nightlies on main that have today's browser player and its trim counter: 2026-11-09 (42659882487) and 2026-11-10 (42842827718). The skip and trim ceilings take the old skip hiccup: 3.3 skips/min and 280 ms/min.
  • Native rows and every congestion row: three local runs each on a loaded x86_64 developer host (debug build, seed 7, 60 s, no voids), after moq-dev/moq#8361 made native report its lag. No Nightly has measured native lag yet.
  • Replay rows: exact, re-run with --replays --enforce. The existing five did not move.

Measured av_lag_ms p50 / p95 (ms):

row browser nights 11-09, 11-10 native runs 1, 2, 3
opus near-zero -7.5/0.4, -10.3/-2.1 20.6/28.3, 21.2/29.3, 21.6/28.6
opus mild -6.7/1.7, -7/2.3 22.1/40.9, 23/30.5, 22.1/28.6
opus wide 4.5/39.8, 5.7/38 37.1/51.9, 35.6/50.7, 37.7/55.5
opus bursty -4.4/9.3, -6.6/5.9 44.2/63.1, 44.9/62.7, 47.2/64.8
opus step 39.6/58.4, 40/55.6 22.2/34.1, 23.5/57.6, 21.5/59.2
opus fixed-250 -10.2/-2.1, -7.3/0.4 12.6/20.7, 10.6/18, 8.8/14.9
aac near-zero -16.7/-8.3, -16.7/-8.1 87.8/97.1, 86.8/94.2, 86.6/94.3
aac mild -13.9/-5.6, -13.3/-4.7 85.9/96.2, 84.7/93.6, 84.6/95.1
aac wide -5.3/28.2, 3.7/51.3 71/84.8, 67.5/82.4, 71.1/89.5
aac bursty 130.3/138.8, 116/124.8 95.3/140.7, 89.4/104.3, 90.5/107.9
aac step 32.2/49, 31.1/44.8 89.5/115.1, 87.7/102, 87.8/101.6
aac fixed-250 -16.7/-8.4, -16/-7.8 72.4/92.5, 66.3/73.6, 66.3/74

Congestion rows, three local runs each:

row trimmed ms/min player skips/min underruns/min target p95 lag p50 / p95 recover ms
chromium opus 612, 1626, 1407 5.4, 6.6, 26.1 0, 0, 17.4 380, 960, 940 -23.6/6.6, -22.6/9.6, -15.7/7.3 2752, 2758, 2502
chromium aac 1562, 1330, 1610 16.4, 3.3, 16.4 2.2, 1.1, 9.8 663 ×3 -27.4/8.3, -25.7/8, -25/19 4003, 3503, 4255
native opus 1129, 1868, 217 1.1, 4.3, 1.1 1021, 877, 1086 840, 940, 2000 23.5/278, 20.9/418, 23.7/1101 3500, 5750, 17750
native aac 2043, 2269, 2169 3.3, 4.3, 3.3 867, 898, 923 2000, 663, 2000 80.5/434, 76.9/297, 82.1/430 12750, 3750, 6250

relay-mic-stall replay: today's browser player trims 2152.5 ms/min and skips 6.1 times a minute. Its target p95 rises to 1540 ms, and audio takes 3000 ms to return to the clock.

What the numbers already show:

  • Today's players drop most of a congestion backlog rather than play it late.
  • Native has no stalled state, so the congestion stall counts as about 900 underruns a minute.
  • The browser step rows rest 30 to 40 ms behind the clock after the step. That is the stale-reference effect the deferred clock-step decision is about.

Impact

  • Public API: none. Wire: none. No player behavior changes.
  • Summary metrics: skip_aheads_* and discarded_ms_* are gone, and recover_ms_max is new. Anything reading old summary.json keys must switch to player_skips and trimmed_ms.
  • The nightly matrix grows from 24 to 28 live rows (about 5 minutes), and the replays from 5 to 6.

Choices made without asking

  • The two inferred metrics are deleted outright rather than left reported. The player's own counters measure the same thing without misreading a catch-up.
  • recover_ms is reported, not budgeted. A ceiling on it would reward trimming over stretching.
  • The browser congestion rows are enforced nightly with ceilings from a developer host, like the native rows' first budgets. They have the usual headroom, and calibrate moves them onto runner numbers. If they breach before then, the first breach says how far the runner differs.
  • Existing underrun, stall, silence and target ceilings are left as they were. Only new keys and new rows were measured. On the local host, native Opus mild and step exceeded their old provisional underrun ceilings once each in three runs. Native rows are report-only, so this is noted here, not re-budgeted.
  • The congestion profile lives in test/audio-quality/ rather than as a moq-shaper built-in, since only this harness uses it.
  • relay-mic-stall is derived from relay-mic rather than recorded, so the stall is exact and repeatable. Its source field describes the rewrite.

Alternatives

  • Budgeting recover_ms: rejected (see above).
  • Calibrating the browser lag ceilings on the developer host: rejected. Two runner nights are closer to what Nightly enforces.

Follow-ups

  • None to plan. Calibrate already covers re-measuring every provisional ceiling.

Models

Size: M
Implementer: Claude Code, Claude Opus 5.5, medium effort

(Written by Claude Opus 5.5 with medium)

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1c784-d75c-7152-a74e-d95404a8ca20, round 1
  • Head: c008a8c
  • Base: origin/main at 3523290
  • Score: 9/10, The grading changes and congestion coverage appear coherent from source review with no actionable defects identified; builds and tests were not run as instructed.
  • Findings: none

kixelated and others added 4 commits November 10, 2026 11:54
…tion rows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title The audio harness grades the stretch model test(audio-quality): grade steady-state lag, the player's own trims, and recovery from congestion Nov 10, 2026
@kixelated
kixelated marked this pull request as ready for review November 10, 2026 22:13
@coderabbitai

coderabbitai Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 169cca39-41d9-45b7-b016-5d8d91759158
Commits

Reviewing files that changed from the base of the PR and between 5ed2774 and c008a8c.

Files selected for processing: 17

  • .github/workflows/nightly.yml
  • quest/m1/audio-jitter-target/README.md
  • quest/m1/audio-jitter-target/grading.md
  • quest/m1/audio-jitter-target/stretch.md
  • test/audio-quality/README.md
  • test/audio-quality/budgets.json
  • test/audio-quality/clients/js/grade.ts
  • test/audio-quality/clients/js/ptt-analyze.test.ts
  • test/audio-quality/clients/js/ptt-analyze.ts
  • test/audio-quality/clients/js/src/analyze.test.ts
  • test/audio-quality/clients/js/src/analyze.ts
  • test/audio-quality/clients/js/src/run.test.ts
  • test/audio-quality/clients/js/src/schema.ts
  • test/audio-quality/congestion.toml
  • test/audio-quality/run.sh
  • test/audio-quality/traces/relay-mic-stall.json
  • test/justfile
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed commit c008a8c. No actionable correctness findings.

Overall direction: the grading changes consistently replace inferred skips with player counters, add lag gates, and report recovery without incentivizing discarded audio. Schema, analyzer, grade table, budgets, and harness matrix agree. The new congestion profile matches the shaper's rate-step behavior; the replay preserves every frame while applying the documented stall and twice-speed backlog. Provisional calibration and exact replay budgets remain explicitly documented.

Verification limits: GitHub-only static review, including the replay consumer and shaper configuration paths. No builds, tests, live audio runs, or independent reproduction of the reported calibration measurements.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ Two minor suggestions — nothing blocking.

Reviewed changes

  • Dropped audio is now the player's own count: skip_aheads/discarded_ms are deleted from the schema, analyze.ts (skipAheads, SKIP_MS, SKIP_WINDOW), its tests, the grade columns, and every budget; player_skips_per_min and trimmed_ms_per_min are budgeted on all 34 rows instead, with the exception of the new stall trace.
  • Steady-state and through-stall lag: av_lag_ms_p50 and av_lag_ms_p95 ceilings now exist on all 28 live rows (replay rows stay exact). I re-derived every new lag ceiling from the PR's measurement tables with the note's rule (worst × 1.25 + hiccup, plus one 20 ms bucket for the signed lag) and they all reproduce, as do the congestion rows' skip, trim, target, and underrun ceilings.
  • recover_ms: a new reported-only metric built on longestAway(): the longest run from a stalled sample, or one more than BUCKET_MS behind the window's median lag, to the next sample back. It is null only when no playing sample has a lag, which the clean() test covers.
  • Congestion, twice: congestion.toml (1 Mbit/s behind a one second queue, collapsing to 16 kbit/s for 25–26.5 s) adds four live rows, and the derived relay-mic-stall trace adds one replay row. The trace's rewrite is faithful: 151 arrivals moved from 15.0–18.0 s into 16.5–18.0 s, oldest first at twice real time, still sorted by at and preserving media timestamps.
  • Docs and counts: the audio-quality README, budget note, nightly.yml, test/justfile, ptt comments, and quest links move to the new metrics and 28 live rows / 6 replays; grading.md is deleted with no dangling links left.

I ran bun test test/audio-quality/clients/js/src/analyze.test.ts (14 pass) and src/run.test.ts (3 pass), and diffed the old and new budgets.json: 29 to 34 rows, no pre-existing ceiling moved apart from the removed keys.

ℹ️ The shaper README no longer lists every profile the lane uses

rs/moq-shaper/README.md describes the audio-quality lane as using bursty and step "plus its own flag profiles". This PR adds a third kind, a profile file at test/audio-quality/congestion.toml, so a reader of the shaper crate would not learn that --profile <path> is exercised in-tree or where the new treatment lives. That file is not in the diff, hence the note here.

Technical details
# Shaper README profile list

## Affected sites
- `rs/moq-shaper/README.md` — the paragraph under "Profiles" after the TOML example enumerates the lane's profiles and omits `congestion.toml`.

## Required outcome
- The sentence names the file profile (or points at `test/audio-quality/README.md`) so all three kinds the lane uses are accounted for.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Comment thread test/audio-quality/run.sh
valid "$p" profile "${ALL_PROFILES[@]}" "${ALL_TRACES[@]}"
if [[ "$p" == step ]] && ((DURATION <= 30)); then
echo "error: --duration must exceed the step at 30s" >&2
if [[ "$p" == step || "$p" == congestion ]] && ((DURATION <= 30)); then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

congestion is guarded like step, but its change is a 1.5 s stall ending at 26.5 s and the row exists to report the recovery after it. The PR's own measurements put that recovery at 2.5–4.3 s (browser) and 3.5–17.75 s (native), so a --duration 31 row stops before the backlog clears: recover_ms under-reports and av_lag_ms_p95 is sampled mid-recovery. Since the new test blesses 31 s as a valid congestion row, either require enough duration past the change for the recovery or state that only the 60 s default measures it.

Technical details
# Congestion rows stop before their own recovery

## Affected sites
- `test/audio-quality/run.sh:144` — the `DURATION <= 30` guard is shared with `step`, whose change is a single event at 30 s.
- `test/audio-quality/clients/js/src/run.test.ts:13` — asserts `--duration 31` is a valid `congestion` row.
- `test/audio-quality/README.md` — says `--duration` must exceed 30 s for `congestion`.

## Required outcome
- A congestion row's playback window covers the measured recovery (the default 60 s does), or the docs and test stop presenting 31 s as adequate for it.

## Suggested approach (optional)
- Give `congestion` its own lower bound (the change ends at 26.5 s, recovery up to ~18 s on native) and update the test's accepted duration, or keep 31 s and note that a narrowed congestion run only grades trimming, not recovery.

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.

1 participant