forked from unom/punktfunk
PW6 was gated on one question: what happens to the client's newest-wins draining when a PyroWave AU arrives in pieces, given that `Session::set_deliver_frame_parts` refuses to combine with an all-intra stream. The answer is that the doc and the plan conflated two different axes, and the question never applied to this package. Host STREAMED_AU chunks change only the WIRE shape. The reassembler completes such a frame exactly like a whole one (`block_count != 0 && blocks_ok == block_count`) and hands up ONE Frame, so the frame channel still sees one entry per AU and the drain is untouched. What newest-wins genuinely cannot survive is the client's SEPARATE prefix delivery, and the mechanism is sharper than "assumes whole AUs" said: `FrameChannel::pop` counts QUEUE ENTRIES and takes one entry to be one AU. With parts on, one AU pushes several, so `len > 1` stops meaning "the consumer is behind" — the drain fires mid-AU, returns a SUFFIX and clears that same AU's prefixes. For PyroWave that is fatal rather than lossy: the sequence header lives in window 0 of every AU (`au_dims` reads it there), so every frame would arrive headerless, and `FramePart`'s own orphan contract would have a correct consumer abandon essentially all of them. Written into `pop`, `set_deliver_frame_parts` and the handshake, together with what a fix would take (skip whole SUPERSEDED AUs, never split one). That answer shrinks what this package may claim, so the code says so plainly. `encode_frame` is synchronous: the whole AU exists before the first chunk can be polled, so `poll_chunk` is not "emit as produced" and there is no encode/send overlap here (PW6 ⟂ PW5, confirmed). And with the client still receiving one whole Frame there is no decode-while-arriving either — the "~7 ms, decouple e2e latency from AU size" framing needs client work this commit does not do. What IS left is real and host-side: the whole-AU path FEC-protects, packetizes and seals the entire ~830 KB AU before its first datagram may leave the socket, while the streamed path seals and paces each FEC block as it completes. All of the cutting lives in the shared `pyrowave_wire` helper, which compiles and unit-tests on every platform, so both backends' `poll_chunk` / `supports_chunked_poll` are thin delegations — the Windows backend cannot be compiled from a Linux box, and logic written into it directly would ship unverified. Chunks are whole numbers of framing windows because `build_au` gives each window exactly ONE kind; that also makes them shard-aligned for free, which is what the sealer's sentinel bases require. Dense mode never streams (no window framing to cut on). `poll()` now errors while a chunk cursor is live — the trait's one-drain-method-per-AU contract, where double-emitting would put the same bytes on the wire twice under one frame index — and `reset()` drops the cursor so a rebuild cannot splice a dead AU's tail onto a fresh one. No new Encoder trait method, so neither the TrackedEncoder forwarding trap nor the EncoderCaps default trap is in play. Shipped OFF: `PUNKTFUNK_PYROWAVE_STREAMED_AU=1` arms it, `PUNKTFUNK_PYROWAVE_CHUNK_KIB` tunes the 256 KiB target. The pre-registered partial-delivery trap is real and now has a named cost — an unpinned streamed frame (final block lost) is excluded from partial delivery, where the whole-AU path still hands the consumer a usable blur, and PyroWave clients opt into partials unconditionally. The netem loss-harness leg is the prerequisite for default-on and has not been run.