diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 556734d..f309ac6 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -137,6 +137,35 @@ covers internals). When you ship a feature, add it here. --- +## Known bugs + +Defects found by code review, not yet fixed. + +1. **Adaptive playout delay never shrinks back in real conversation** + (`src/core/jitter.rs`) — `target_delay` grows +1 per disruption up to + `MAX_DELAY_FRAMES` (12 frames = 240 ms) but only shrinks after + `CLEAN_RUN_TO_SHRINK` = 250 consecutive cleanly-played frames, i.e. **5 s of + unbroken audio**. `clean_run` is reset in five places; two of them fire on + every natural pause in speech: the benign-underrun branch (`jitter.rs:201`, + talker went quiet) and the subsequent re-prime (`jitter.rs:181`). Because the + sender skips transmitting entirely while the noise gate is closed + (`src/core/mod.rs:2041`), a pause between sentences *always* underruns the + receiver and zeroes the clean run — twice. + + Net effect: the controller is a one-way ratchet. A single burst of jitter + early in a call pins up to 240 ms of extra playout latency for the rest of + the session, because no conversational speaker talks for 5 continuous + seconds without the gate closing. The AIMD "decrease" half is effectively + unreachable under the workload the app is built for. + + Likely fix: let `clean_run` survive a benign idle→re-prime transition rather + than resetting it. Silence is not evidence the link is bad, so it should not + count against the clean run. Distinguish "talker stopped" (benign) from + "playout broke" (real) at `jitter.rs:195-202`. + + Found 2026-07-31 by code review. Not yet reproduced in a live call — pairs + with field-test debt item 5 below. + ## Known field-test debt (the 🧪 rows above, collected) Re-run on a real desktop ↔ dopedart call before calling these done: