Completes WP-C1 of design/host-source-stutter-fixes.md. The three Rust clients
conceal a packet drought on their decode thread; Apple could not, and the reason
is structural rather than an oversight: its playout ring is Swift and its Opus
decoder lives behind the C ABI, so the drain thread had nothing to call. Standing
up a second decoder Swift-side would not have worked either — PLC extrapolates
from the LAST DECODED FRAME, so a fresh decoder conceals from empty state.
So the ABI grows one function. `punktfunk_connection_audio_plc` synthesizes a
single frame from the connection's own live decoder, returning no-frame when
nothing has decoded yet (there is no state to extrapolate from) and when libopus
declines to interpolate — a timeout's answer, not an error. Frames it returns
carry seq and pts of zero, because concealed audio was never on the wire and must
not reach an A/V-sync observation; the drain loop skips `av.observe` for them.
The double-conceal defence had to move. On the Rust clients the decode loop
subtracts frames it already concealed from what AudioGapTracker then asks for;
Swift cannot, because the tracker is behind the ABI. So `AudioPcmState` counts
its own concealment and `decode_packet` does the subtraction — same invariant,
enforced one layer lower, with a Rust test standing in for the Swift half.
Swift gets the policy port beside `AudioRing`'s existing one, in the same shape
and for the same reason: one budget, `deprimeMS * 2`, denominated in time and not
in callbacks. `plc_ms=` joins the 10 s line as it did on the other three.
ABI_VERSION 22 → 23. ⚠ That number is the one thing here worth a second look: 21
was claimed twice by concurrent worktrees once already, and 23 is correct only
against what is visible from this branch.