The jitter ring only ever learned from clicks — it now grows on near-misses, un-does refused shrinks, and cashes growth on the click it already paid
#111
Merged
enricobuehlermerged 1 commits from worktree-audio-jitter-lowwater into main2026-08-08 10:11:11 +00:00
The 0.25.0 MacBook field report — audio jitter 'at certain points' — is the
jitter policy learning exclusively from audible failures, on both of its
sides. Growth needed THREE audible underruns before deepening the ring; the
A/V sync loop re-tested a shallower ring every five quiet seconds and paid an
audible starvation event every time it was wrong, forever; and a grown target
was never re-banked — growth raises a threshold, only a re-prime deepens the
ring — so a bunching link rode the knife edge, clicking once per bunching
period with the 'grown' target sitting inert. A ten-minute simulation of the
Wi-Fi power-save pattern (25 ms gaps / 300 ms, −50 ppm skew) measured ~2000
audible events under the shipped policy.
Three mechanisms, in JitterPolicy (Linux/Windows/Android) and mirrored in the
Swift AudioRing:
- NEAR-MISS: a read served with less than one protocol frame left over is the
same evidence as an underrun, heard by no one. It grows the target one step
per window, BEFORE the click — waiting for the third audible underrun means
the user heard two.
- SHRINK PROBES: every shrink is armed for five seconds; answered by an
underrun or near-miss it is undone on the spot, and a failed sync-driven
shrink is not retried for a doubling backoff (60 s → 8 min). A probe that
survives resets the backoff. Continuity outranks sync, now with a memory.
- HOLLOW RE-PRIME: an underrun while the depth AVERAGE runs more than a step
below the target re-primes immediately, spending the click it already cost
on the whole refill instead of limping. The average, not the instant, is
what separates a hollow ring from one late packet, and it is seeded on
prime so a fresh ring is never spuriously hollow.
Same simulation after: 9 audible events, tail clean but for the clock-skew
re-anchor (a genuinely slow host must re-bank every few minutes; only rate
adaptation would remove that, and no client has it). Neutralising the three
constants reproduces the ~2000 — the convergence tests fail against the old
behaviour.
Verified: 203 punktfunk-core tests, 254 Swift tests (5 skipped), clippy -D
warnings on punktfunk-core --all-features, cargo fmt --all --check.