Skip to content

quest: plan the audio stretch model and its grading - #8389

Merged
kixelated merged 1 commit into
mainfrom
quest-plan/audio-stretch-model
Nov 9, 2026
Merged

kixelated merged 1 commit into
mainfrom
quest-plan/audio-stretch-model

Conversation

@kixelated

@kixelated kixelated commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The audio jitter epic encodes a rule the maintainer replaced on 2026-11-09: "the browser only plays faster, and audio never settles later". Under it, a congestion stall drops the audio that arrives late, the two players disagree, and the tests reward the wrong thing:

  • Native slows down whenever raw depth is under the target, so it settled 60 to 88 ms behind. NetEq compares a filtered level against max(3/4 target, target - 85 ms); the port kept neither.
  • The browser never slows down, fast-accelerates at 20 ms of excess, refills to the whole target after an underrun, and turns the stretch off in buffered mode.
  • budgets.json budgets replay lag exactly at -19 to -22 ms, counts a catch-up as dropped audio, and caps underruns on 24 rows with no ceiling on lag. ptt-analyze.ts fails on any underrun.

Approach

Rewrites quest/m1/audio-jitter-target to the model: in steady state audio plays on the shared clock at the playout delay; it slows down when its buffer runs low, and speeds up once the buffer refills past the target. Video stays on the clock and shows late frames as they arrive.

New quests, in landing order:

  • Grading [M]: the harness grades steady-state lag, dropped audio, and recovery from a stall, on today's player.
  • Stretch model [XL]: one rule and one set of vectors for moq play, live <moq-watch>, and buffered <moq-watch>.
  • The stretch setting [M]: how far audio may trail before it skips; zero turns the stretch off.
  • Calibrate [S]: every ceiling re-measured from seven Nightlies with the model.

Changed: native-av-lag.md shrinks to the metrics half of moq-dev/moq#8361; opus-stalled-share.md and push-to-talk-latency.md stop treating zero stalls or underruns as the goal.

Deleted: native-stretch.md and browser-ceilings.md (their Nightly waits calibrate a player the model changes; calibrate.md carries their decisions) and buffered-converge.md (folded into the model).

Decisions

Settled in /quest-plan on 2026-11-09 by the maintainer.

  • Goal scope: ✅ all players on one rule (rejected: live only; "needs rewording").
  • Target formula: ✅ unchanged, max over subscribed renditions of (delay + target) with audio's target max(measured, jitter) + frame (rejected: a network term on every rendition; the plain sum delay + jitter + frame + measured; catalog only). The maintainer first proposed the sum, then narrowed the network term to audio.
  • Video clock: ✅ the same clock as audio; audio alone speeds up and slows down (rejected: video at its catalog target with audio trailing).
  • Target moves: ✅ the estimator is unchanged and audio glides to each new target (rejected: a slew limit on the measured term; deleting the estimator).
  • Lag bound: ✅ configurable, one duration where zero turns it off, default 2 s for auto and fixed delays (rejected: an on/off switch; no new setting; NetEq's four times the target).
  • Setting name: ✅ stretch (rejected: catchup). At zero: ✅ no stretching at all (rejected: only falling behind is off).
  • Refill after an underrun: ✅ resume on a small cushion (rejected: refill to the full target; measure both and pick in the PR).
  • Speed: ✅ NetEq's thresholds and rates, untuned (rejected: a few percent both ways; a capped slow-down with NetEq's catch-up).
  • moq-dev/moq#8360: ✅ fold buffered mode into the model quest and close the PR with a comment (rejected: merge as is and unify after; fix its four audit items first; leave it open as a draft).
  • Order: ✅ native backpressure before the model (rejected: the model first; one quest for both).
  • Clock follows a path-delay step: ✅ deferred until the model's step rows are measured (rejected: plan it now; drop it).
  • Milestone: ✅ all four new quests in m1, in the epic (rejected: model and grading in m0; the setting in m2).
  • Tests: the maintainer asked that no test or benchmark push the wrong way, so grading lands before the model.

Impact

Quests only. Public API: none. Wire: none.

Alternatives

Keeping native-stretch.md and browser-ceilings.md and adding a third re-measure was rejected: both wait on seven Nightlies of a player the model replaces.

Follow-ups

  • stretch.md waits on moq-dev/moq#8380, which plans native backpressure. Once it merges, that quest moves into stretch.md's Required. Recommendation: do it in the next /quest-audit.
  • A quest for the shared clock to follow a lasting path-delay step. Recommendation: drop for now; plan it from the model PR's step numbers.

Models

Size: M
Implementer: Claude Code, claude-fable-5-2, high effort

🤖 Generated with Claude Code

(Written by Claude Fable 5 with high effort)

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1c2a0-7047-7c93-8142-ce4785b5c026, round 1
  • Head: 02d5834
  • Base: origin/main at bae5ef2
  • Score: 10/10, The planning documents consistently record the revised model, dependencies, and calibration work, with no actionable defects identified.
  • Findings: none

Audio slows down when its buffer runs low and speeds up once it refills, in
every player, replacing the rule that the browser only plays faster. The
harness is regraded first so no gate rewards zero underruns or zero lag.

Co-Authored-By: Claude <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 9, 2026 23:24
@coderabbitai

coderabbitai Bot commented Nov 9, 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 17 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: 0e72328b-627e-4a14-81e6-bbd54660ca1a
Commits

Reviewing files that changed from the base of the PR and between bae5ef2 and 02d5834.

Files selected for processing: 13

  • quest/m1/README.md
  • quest/m1/audio-jitter-target/README.md
  • quest/m1/audio-jitter-target/browser-ceilings.md
  • quest/m1/audio-jitter-target/buffered-converge.md
  • quest/m1/audio-jitter-target/calibrate.md
  • quest/m1/audio-jitter-target/grading.md
  • quest/m1/audio-jitter-target/native-av-lag.md
  • quest/m1/audio-jitter-target/native-stretch.md
  • quest/m1/audio-jitter-target/opus-stalled-share.md
  • quest/m1/audio-jitter-target/stretch-setting.md
  • quest/m1/audio-jitter-target/stretch.md
  • quest/m1/push-to-talk-latency.md
  • quest/m4/audio-bufferbloat-av.md
  • 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 head: 02d58349898c5a8c96aab13f507a44660eee2d6b.

No actionable findings in this planning-only change. The direction is coherent: one stretch rule for native, live browser, and buffered browser playback, with grading corrected before the behavior changes. The native metrics prerequisite, backpressure wait, configurable bound, and post-model calibration are recorded separately without retaining the superseded buffered-only approach.

Scope: reviewed all 13 changed plans and checked relevant target, clock, ring, native stretch, and budget source. This was a GitHub-only static review; no local checks, playback tests, benchmarks, or listening validation were run. It does not validate the future implementation or its calibration results.

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

ℹ️ One substantive inconsistency with #8361's recorded root cause, plus two smaller gaps in the new grading quest.

Reviewed changes

Reviewed the whole PR at 02d5834 (single commit, 13 files), the quest tree it edits, and every code path the plan cites.

  • Epic rewritten — quest/m1/audio-jitter-target/README.md replaces "the browser only plays faster" with audio that slows down when its buffer runs low and catches up on the shared clock; the quest/m1/README.md bullet matches.
  • Four new quests — grading.md (gates first), stretch.md (one rule for moq play and live/buffered <moq-watch>; closes #8031), stretch-setting.md (stretch, 2 s default, zero off), calibrate.md (seven-night re-measure after the model).
  • Three quests deleted — browser-ceilings.md, native-stretch.md, buffered-converge.md; a tree-wide grep finds no dangling links.
  • Three adjusted — native-av-lag.md keeps only #8361's metrics half, opus-stalled-share.md and push-to-talk-latency.md stop treating zero stalls or underruns as the goal, and quest/m4/audio-bufferbloat-av.md gates dropped audio instead of late audio.

I checked the numbers the plan cites against the tree and the PRs: budgets.json's 29 rows, its five exact replay av_lag_ms ceilings (‑22.4, ‑22.2, ‑22.7, ‑19, ‑20.1) and their note, AUTO_MAX_DELAY, the native decide order, the browser's park and 20 ms fast path, and ptt-analyze.ts. Also #8360 closed, #8380 open, #8364 open, #8308 merged. Findings are inline.

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

Comment on lines +75 to +78
- [moq-dev/moq#8361](https://github.com/moq-dev/moq/pull/8361) sped up into a
thin buffer and took AAC `step` from 0 to 614 underruns a minute. A
speed-up is admitted only while the buffer stays above the target by the
lag it removes.

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.

The admission rule credited here is the one #8361 already implemented (lag <= excess), and it still measured 613.6 underruns/min on AAC step. #8361's own postmortem puts that on the shared clock's reference never moving later — the follow-up this plan defers — so the step rows should be expected to underrun until it lands, not described as prevented by this rule. Note native-av-lag.md in this PR gives a third cause ("removed native's only slow-down").

Technical details
# #8361 attribution vs its recorded root cause

## Affected sites
- `quest/m1/audio-jitter-target/stretch.md:75-78` — attributes the AAC `step` regression to "sped up into a thin buffer" and credits "A speed-up is admitted only while the buffer stays above the target by the lag it removes".
- `quest/m1/audio-jitter-target/native-av-lag.md:15-16` — attributes the same regression to "removed native's only slow-down".
- `quest/m1/audio-jitter-target/grading.md:69-72` — already says the `step` rows' lag is budgeted as measured and the clock follow-up is deferred.

## Evidence
- #8361 at `b93e1d12`, "Why AAC `step` underruns": "The clock's reference is the earliest arrival ever and never moves later ... After the 5 -> 60 ms delay step, the clock-aligned depth stays about 55 ms short of the estimator's target, which is below AAC's ~160 ms PES burst spacing. `main`'s decision hid this by settling a burst behind."
- Same body, recommended follow-up: "the shared clock's reference ... follows a delay increase that every arrival in the estimator's window shows, which clears the AAC `step` underruns".
- Same body, Changes: "Any excess accelerates (lag <= excess) ... It expands only when every arrival over the last 1 to 2 s rendered fell short" — the rule credited above, measured at 613.6/min anyway.
- `stretch.md:58-61` defers the clock follow-up, and the model keeps the target formula and `moq-cli`'s `due(end) - now` sink target, so the stale-reference condition is unchanged. Slow-down may reduce the count, but nothing in the plan restores the missing ~55 ms.

## Required outcome
- Do not present the admission rule as preventing the `step` regression.
- Say the `step` rows are expected to underrun until the clock follow-up lands (consistent with `grading.md`'s deferral), or reconcile the deferral with the fix #8361 already identified.
- Reconcile the two different causes this PR names for the same regression, and #8361's own.

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.

Agreed, and corrected in moq-dev/moq#8380 at fd940ae, since this PR had already been queued: stretch.md and native-av-lag.md now give #8361's recorded cause (the clock's reference never moves later, leaving the depth about 55 ms short after the step) and say the admission rule alone prevents nothing there. Under the model the slow-down lets audio settle behind the stale clock rather than underrun, which is the lag the deferred clock decision is about.

(Written by Claude Fable 5 with high effort)

regression.
- Dropped audio is the player's own count (`trimmed_ms`, `player_skips`).
Speeding up is not a skip.
- No gate is fixed at zero underruns or zero stalls.

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.

This Goal cannot be met when this quest lands: test/audio-quality/clients/js/ptt-analyze.ts still fails on any underrun or stall, and lines 46-49 leave that change to push-to-talk-latency.md in #8364, still open, without a ## Required link. Since this quest is meant to land first so no gate pushes the wrong way, add Push-to-talk latency to ## Required or scope the Goal to the rows the harness budgets.

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.

Agreed, corrected in moq-dev/moq#8380 at fd940ae: the Goal is scoped to the harness's budgeted rows, and push-to-talk's zero gate goes with its own quest. Requiring that quest would put the whole chain behind its wait on the maintainer's laptop.

(Written by Claude Fable 5 with high effort)

Comment on lines +59 to +62
- Replay rows stay exact. Rewrite the note to say which direction is a
regression: a steady-state p50 lag that rises, or audio the player
trimmed. A p95 lag that rises through a stall, or an underrun count that
moves, is not one by itself.

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.

These two sentences disagree: with replay ceilings kept exact, any p95 rise through a stall fails them, so the note cannot also say such a rise is not a regression by itself. The Goal gives headroom only to the browser and native rows, so scope this p95 clause to those rows, or give the replay p95 ceilings headroom too.

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.

Agreed, corrected in moq-dev/moq#8380 at fd940ae: an exact replay row still fails on any change until re-measured, and the note says which re-measured moves are regressions to fix (steady-state p50, trimmed audio) and which are re-measured in (p95 through a stall, underrun counts).

(Written by Claude Fable 5 with high effort)

Merged via the queue into main with commit 02c79f5 Nov 9, 2026
10 checks passed
@kixelated
kixelated deleted the quest-plan/audio-stretch-model branch November 9, 2026 23:51
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