Files
punktfunk/include
enricobuehler 26d06195f5 fix(client): a host recovery anchor lifted the freeze onto a grey picture, unchecked
Field report: the native-vulkan HEVC stream goes grey with moving artifacts after a
host context change (starting a game), and recovers by itself in 0.5-2 s.

Of the three signals that lift the post-loss freeze, two are self-evident to the
client and one is pure hearsay. An IDR predicts from nothing. A recovery mark is
half a re-anchor and the gate says so by requiring two. But USER_FLAG_RECOVERY_ANCHOR
is the HOST asserting a fact about the CLIENT's decoder -- "the picture I coded this
P-frame against is one you still hold, intact" -- and the gate took it on faith, on
the first occurrence, with no scrutiny at all.

The host derives that claim from bookkeeping that tracks what the client RECEIVED,
not what it managed to DECODE. Those diverge exactly when the client had to conceal,
and then the anchor lifts the freeze onto a picture predicted from damage AND LEAVES
IT LIFTED -- so the grey plate reaches the screen and keeps reaching it until some
later signal re-arms and the 500 ms backstop extracts a real IDR. That is the
observed 0.5-2 s, and it is the worst-shaped failure in the module: a re-anchor claim
the client can refute is worse than no claim, because no claim merely holds.

So corroborate it. pf-bitstream's planners now carry a per-picture clean bit --
damage propagates down the prediction chain, because the descendants of a concealed
picture raise no warning of their own -- surfaced as PicturePlan::references_clean
and carried to the consumer on DecodedVkFrame. The gate gains AnchorEvidence and
on_decoded_corroborated, and refuses an anchor whose references the client can prove
were damaged. Refusing can only ever hold LONGER: the freeze stays up, the backstop
fires on its ORIGINAL deadline, and the client escalates to the IDR the anchor failed
to be. Lanes with no local parser pass Unavailable and are bit-for-bit unchanged --
all 29 pre-existing reanchor tests pass untouched.

Also brings the H.264 decoder to parity with its two siblings, found on the way. It
was the only one of the three that failed OPEN: a DPB slot with no bound image was
traced and decoded anyway (H.265 and AV1 return UnboundReferenceSlot), reference_count
was computed after the held-slot loop so a dropped reference silently took an
unrelated slot's picture in its place, and there was no RecoveryLatch, so a failure
left the planner DPB, the slot map and the image bindings disagreeing forever. None
of it raised a warning, so the frame was shipped, presented, and cleared the demotion
streak on its way past. Both latches landed with the HEVC and AV1 decoders in August;
H.264 predates them and was never retro-fitted.

The damage classification moves onto the warning enums so the planner's ledger and
the client's concealment test cannot drift apart -- still exactly one list, now in
the crate that owns the enum, with pf_vkdecode::is_integrity_warning* delegating.
Every copy stays an exhaustive match with no wildcard: a future variant must stop the
compiler, never default to clean.
2026-08-16 01:14:30 +02:00
..