Root cause of the field "H265 freezes on every loss, AV1 fine" class on AMD hosts (2026-08-11 logs, RX 9070 XT + iPhone/Deck clients).
The race: every unrecoverable loss armed the client's freeze gate twice — instantly at frame-index-gap detection (which fires the RFI), and ~120 ms later when the reassembler ages the lost frame into frames_dropped and ReanchorGate::poll() re-armed unconditionally. An LTR-RFI recovery anchor lands in ~60 ms — between the two signals — so the stale climb re-froze a bit-exact-healed stream, the host swallowed the re-ask as an RFI echo, and the picture stayed frozen until the overdue backstop extracted a full IDR (0.5–3 s per loss event). AMF is the only LTR-RFI backend → AMD-specific; the slower IDR path usually lands after the climb → AV1 dodged it. The encoder bitstream was exonerated separately (cut-stream framemd5 heals bit-exact on software, AMD-hw and NV-hw decode).
The fix: the gap-arm pre-credits the expected climb (ReanchorGate::arm_expecting_drops; credit expires after DROP_CREDIT_WINDOW = 1 s so a straggler-filled gap can't mask a later real loss), and poll() consumes credited climbs instead of re-arming. Plumbed through every embedder: pf-client-core's session pump, Android's sync/async loops (note_frame_index now returns the gap width), and the Swift client via additive ABI exports punktfunk_connection_note_frame_index_ex + punktfunk_reanchor_gate_arm_expecting_drops (the bool ABI stays).
Validation: 4 new gate tests incl. the exact field timeline; punktfunk-core 382/383 green (Mac all-features + Linux .25), pf-client-core 188 green (.25), clippy clean, Android crate check clean. A live harness against the real gate reproduces the field sequence: pre-fix 30/30 healthy post-anchor frames withheld until a forced IDR; post-fix 0/30 — the healed stream keeps presenting.
Root cause of the field "H265 freezes on every loss, AV1 fine" class on AMD hosts (2026-08-11 logs, RX 9070 XT + iPhone/Deck clients).
**The race:** every unrecoverable loss armed the client's freeze gate twice — instantly at frame-index-gap detection (which fires the RFI), and ~120 ms later when the reassembler ages the lost frame into `frames_dropped` and `ReanchorGate::poll()` re-armed unconditionally. An LTR-RFI recovery anchor lands in ~60 ms — *between* the two signals — so the stale climb re-froze a bit-exact-healed stream, the host swallowed the re-ask as an RFI echo, and the picture stayed frozen until the overdue backstop extracted a full IDR (0.5–3 s per loss event). AMF is the only LTR-RFI backend → AMD-specific; the slower IDR path usually lands after the climb → AV1 dodged it. The encoder bitstream was exonerated separately (cut-stream framemd5 heals bit-exact on software, AMD-hw and NV-hw decode).
**The fix:** the gap-arm pre-credits the expected climb (`ReanchorGate::arm_expecting_drops`; credit expires after `DROP_CREDIT_WINDOW` = 1 s so a straggler-filled gap can't mask a later real loss), and `poll()` consumes credited climbs instead of re-arming. Plumbed through every embedder: pf-client-core's session pump, Android's sync/async loops (`note_frame_index` now returns the gap width), and the Swift client via additive ABI exports `punktfunk_connection_note_frame_index_ex` + `punktfunk_reanchor_gate_arm_expecting_drops` (the bool ABI stays).
**Validation:** 4 new gate tests incl. the exact field timeline; punktfunk-core 382/383 green (Mac all-features + Linux .25), pf-client-core 188 green (.25), clippy clean, Android crate check clean. A live harness against the real gate reproduces the field sequence: pre-fix 30/30 healthy post-anchor frames withheld until a forced IDR; post-fix 0/30 — the healed stream keeps presenting.
Every unrecoverable loss armed the client's freeze gate twice: instantly at
frame-index-gap detection (which fires the RFI), and ~120 ms later when the
reassembler ages the lost frame into frames_dropped and poll() re-armed
unconditionally. An LTR-RFI recovery anchor lands in ~60 ms — between the two
signals — so the stale climb re-froze a bit-exact-healed stream, the host
swallowed the re-ask as an RFI echo, and the picture stayed frozen until the
overdue backstop extracted a full IDR: the field 'H265 freezes on every loss,
AV1 fine' signature on AMD hosts (AMF is the only LTR-RFI backend; the slower
IDR path usually lands after the climb and dodged the race).
The gap-arm now pre-credits the expected climb (ReanchorGate::arm_expecting_drops;
credit expires after DROP_CREDIT_WINDOW so a straggler-filled gap can't mask a
later real loss), and poll() consumes credited climbs instead of re-arming.
Plumbed through every embedder: pf-client-core's session pump, Android's
sync/async loops (note_frame_index now returns the gap width), and the Swift
client via new ABI exports punktfunk_connection_note_frame_index_ex +
punktfunk_reanchor_gate_arm_expecting_drops (additive; the bool ABI stays).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Root cause of the field "H265 freezes on every loss, AV1 fine" class on AMD hosts (2026-08-11 logs, RX 9070 XT + iPhone/Deck clients).
The race: every unrecoverable loss armed the client's freeze gate twice — instantly at frame-index-gap detection (which fires the RFI), and ~120 ms later when the reassembler ages the lost frame into
frames_droppedandReanchorGate::poll()re-armed unconditionally. An LTR-RFI recovery anchor lands in ~60 ms — between the two signals — so the stale climb re-froze a bit-exact-healed stream, the host swallowed the re-ask as an RFI echo, and the picture stayed frozen until the overdue backstop extracted a full IDR (0.5–3 s per loss event). AMF is the only LTR-RFI backend → AMD-specific; the slower IDR path usually lands after the climb → AV1 dodged it. The encoder bitstream was exonerated separately (cut-stream framemd5 heals bit-exact on software, AMD-hw and NV-hw decode).The fix: the gap-arm pre-credits the expected climb (
ReanchorGate::arm_expecting_drops; credit expires afterDROP_CREDIT_WINDOW= 1 s so a straggler-filled gap can't mask a later real loss), andpoll()consumes credited climbs instead of re-arming. Plumbed through every embedder: pf-client-core's session pump, Android's sync/async loops (note_frame_indexnow returns the gap width), and the Swift client via additive ABI exportspunktfunk_connection_note_frame_index_ex+punktfunk_reanchor_gate_arm_expecting_drops(the bool ABI stays).Validation: 4 new gate tests incl. the exact field timeline; punktfunk-core 382/383 green (Mac all-features + Linux .25), pf-client-core 188 green (.25), clippy clean, Android crate check clean. A live harness against the real gate reproduces the field sequence: pre-fix 30/30 healthy post-anchor frames withheld until a forced IDR; post-fix 0/30 — the healed stream keeps presenting.