forked from Keelrleek/SoulStream
A streamed first play is gained from a prefix; every later play is gained from the whole track. Measured across the 89 cached tracks, a 10s head asks for a median 3.4 dB too much gain and is more than 1 dB hot on 68 of them — that gap is the "first track is so much louder" complaint. The curve never flattens either: even a 60s head is still ~1 dB out with a 6 dB worst case, so no window size fixes it. Live traces then showed something worse. The hard deadline was anchored to the play REQUEST, so on a peer that took a moment to start sending, the 2.5s ran out before a single gating block existed and the track began at unity — no normalization at all, three plays out of four on one run. Releasing early bought nothing in exchange: playback is blocked on the same bytes, so the gate was discarding the measurement without making sound arrive sooner. - The deadline now starts at the first byte, with an absolute 4s ceiling from the request so a slow peer cannot hold audio that is ready to play. - The audio thread's slew drops 2.5 -> 0.25 dB/s. Mid-track correction was switched off in July because the glide was audible; at a tenth the rate it is not, and being 3 dB wrong for a whole track is the larger error. - The correction follows the running measurement from its first publish and may only ratchet DOWN until the track completes. A convergence test lived here and was deleted: an integrated average over 30s cannot move much when one more second is added, so "3 publishes within 0.3 dB" was measuring the inertia of a long average, fired at its floor every time, and declared settled while 1.5 and 2.1 dB from truth. The protection was never the gate — it is the slew rate and the ratchet. - Seeking lands the pending correction immediately. The slew is slow only because a gain change is audible against continuous music, and a seek is already a discontinuity. Observability: SOULSTREAM_LOUDNESS_TRACE=1 reports every commit with the seconds analyzed, which limit fired, the hold, and the bytes received; the debug overlay gains a SLEW line showing applied gain, target, distance and ETA, because one number cannot distinguish "settled" from "in flight". Also adds an #[ignore] corpus harness that sweeps head-window bias in 30s.