audio: widen forward decode cushion 30/15->60/30s + add [BP-DIAG] back-pressure instrumentation

Byte cap (96MB) unchanged as the hard OOM bound; the wider time window only lets sparse Opus use existing memory headroom to ride out decode jitter. Diag logs pin whether the block is back-pressure or decode throughput.
This commit is contained in:
daniel-c-harvey
2026-06-25 21:52:20 -04:00
parent f0d1463619
commit 61e185a2f7
4 changed files with 126 additions and 12 deletions
@@ -441,6 +441,56 @@ test('clear and clearForSeek release the back-pressure latch (C2 latency parity)
assertEqual(s.evaluateProductionPause(), false, 'clearForSeek resets the latch');
});
// Production defaults (no setForwardWindow): the widened 60s/30s cushion. The byte cap is the
// UNCHANGED hard OOM bound; these defaults only govern the time window. buf(1) carries no byte
// fields, so getDecodedByteEstimate is NaN and the byte guard never fires — the time window alone
// governs, which is exactly what we want to pin here.
test('default forward window throttles at 60s and resumes at 30s (no setForwardWindow)', () => {
const cm = new FakeContextManager();
const s = makeScheduler(cm);
// Deliberately no setForwardWindow() — exercise the PRODUCTION defaults (high 60s / low 30s).
for (let i = 0; i < 70; i++) s.addBuffer(buf(1)); // 70s decoded, track [0,70)
cm.now = 0;
s.playFromPosition(0);
advanceCursorToEnd(s, cm);
cm.now = 0; // forward lookahead = 70s ≥ 60s high-water
assertEqual(s.evaluateProductionPause(), true, 'pauses at the 60s default high-water');
cm.now = 35; // lookahead 35s: inside the 30..60 band → stays paused (hysteresis)
assertEqual(s.evaluateProductionPause(), true, 'holds through the widened band');
cm.now = 45; // lookahead 25s ≤ 30s low-water → resume
assertEqual(s.evaluateProductionPause(), false, 'resumes at the 30s default low-water');
});
// Lookahead correctness in the underrun state + the prime block hypothesis directly refuted: when the
// playhead has drained the queue mid-stream, forward lookahead must read ~0 (not a stale-high value)
// so production is NOT throttled while decoded audio is genuinely low.
test('forward lookahead is exact during an underrun park and never trips a false pause', () => {
const cm = new FakeContextManager();
const s = makeScheduler(cm);
// 5s decoded, playback started, stream NOT complete.
for (let i = 0; i < 5; i++) s.addBuffer(buf(1));
cm.now = 0;
s.playFromPosition(0);
// Playhead advances past the decoded tail and the queue drains → mid-stream underrun park.
cm.now = 6;
drainAllSources(s, cm);
assertEqual(s.isActive(), false, 'parked in underrun');
// At the park the playhead sits at the decoded tail: forward lookahead is 0, so production must
// NOT be throttled (the "paused while decoded audio is low" hypothesis must not hold here).
assertClose(s.getForwardLookaheadSeconds(), 0, 'lookahead is 0 at the underrun tail');
assertEqual(s.evaluateProductionPause(), false, 'low decoded audio does not pause production');
// Refill arriving during the park grows the lead monotonically; lookahead reflects exactly it,
// measured against the FROZEN playhead — not a stale pre-underrun position.
s.addBuffer(buf(1));
s.addBuffer(buf(1));
assertClose(s.getForwardLookaheadSeconds(), 2, 'lookahead equals the freshly-accumulated lead');
assertEqual(s.evaluateProductionPause(), false, 'still unthrottled well below the 60s high-water');
});
// === False end-of-playback guard (Opus-startup misfire) ======================================
//
// The scheduler must distinguish a GENUINE end-of-track (stream complete AND queue drained) from a