From 02c615670acf263ca96d80329fe8bc7374b3dcef Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 17 Jul 2026 02:34:51 +0100 Subject: [PATCH] Ignore late shards from completed Muninn frames --- .../mimir_obs_muninn_source.cpp | 33 +++++++++++++++---- state/evidence.jsonl | 1 + state/map.yaml | 22 +++++++++---- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/native/obs_stem_source/mimir_obs_muninn_source.cpp b/native/obs_stem_source/mimir_obs_muninn_source.cpp index 9743073..03f3564 100644 --- a/native/obs_stem_source/mimir_obs_muninn_source.cpp +++ b/native/obs_stem_source/mimir_obs_muninn_source.cpp @@ -67,6 +67,7 @@ constexpr uint64_t MuninnReceiverKeyframeRequestCooldownMs = 500; constexpr uint64_t MuninnReceiverChunkRepairFirstWaitMs = 64; constexpr uint64_t MuninnVideoAssemblyExpiryScanMs = 10; constexpr size_t MuninnExpiredVideoFrameRememberCount = 4096; +constexpr size_t MuninnCompletedVideoFrameRememberCount = 4096; constexpr size_t MuninnVideoParityCacheMaxEntries = 4096; constexpr uint16_t MuninnCatalogDiscoveryPort = 17874; constexpr uint32_t MuninnCatalogDiscoveryConnectionId = 0x6d750003; @@ -1515,15 +1516,15 @@ private: log_bridge_pressure("dropped invalid video chunk metadata"); return std::nullopt; } - if (media.chunk_count == 1) { - return media.payload; - } - const std::string key = media.stream_id + ":" + media.session_id + ":" + std::to_string(media.frame_id); - if (expired_video_frame_keys_.find(key) != expired_video_frame_keys_.end()) { + if (video_frame_is_settled(key)) { return std::nullopt; } + if (media.chunk_count == 1) { + remember_completed_video_frame(key); + return media.payload; + } auto &assembly = video_assemblies_[key]; if (assembly.chunks.empty()) { assembly.stream_id = media.stream_id; @@ -1579,7 +1580,7 @@ private: } const std::string key = media.stream_id + ":" + media.session_id + ":" + std::to_string(media.frame_id); - if (expired_video_frame_keys_.find(key) != expired_video_frame_keys_.end()) { + if (video_frame_is_settled(key)) { ++video_parity_ignored_; return std::nullopt; } @@ -1683,9 +1684,27 @@ private: } video_assemblies_.erase(key); video_parity_cache_.erase(key); + remember_completed_video_frame(key); return assembled; } + bool video_frame_is_settled(const std::string &key) const + { + return expired_video_frame_keys_.find(key) != expired_video_frame_keys_.end() || + completed_video_frame_keys_.find(key) != completed_video_frame_keys_.end(); + } + + void remember_completed_video_frame(const std::string &key) + { + if (completed_video_frame_keys_.insert(key).second) { + completed_video_frame_order_.push_back(key); + } + while (completed_video_frame_order_.size() > MuninnCompletedVideoFrameRememberCount) { + completed_video_frame_keys_.erase(completed_video_frame_order_.front()); + completed_video_frame_order_.pop_front(); + } + } + bool try_recover_video_assembly_from_parity(MuninnVideoFrameAssembly &assembly) { if (assembly.parity_count == 0 || @@ -2037,6 +2056,8 @@ private: std::map> video_parity_cache_; std::deque expired_video_frame_order_; std::set expired_video_frame_keys_; + std::deque completed_video_frame_order_; + std::set completed_video_frame_keys_; muninn_rudp_fragments::FragmentAssembler packet_fragments_; muninn_rudp_fragments::FragmentAssembler audio_packet_fragments_; muninn_rudp_ack::AckTracker ack_tracker_; diff --git a/state/evidence.jsonl b/state/evidence.jsonl index c3936c3..c35ff33 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -197,3 +197,4 @@ {"ts": "2026-07-17T01:37:22+01:00", "type": "verification", "status": "partial", "note": "Muninn video parity v3 uses 16 Cauchy GF(256) shards; native receiver burst tests 1..16 and Odin 180-test suite pass. Direct Raven LAN at 250 ms recovery deadline ran over one minute with no video assembly expiry or A/V failure; full 10-minute and impairment matrix remain open."} {"ts": "2026-07-17T01:46:28+01:00", "type": "scar", "status": "fixed", "note": "Per-byte Gaussian elimination for production-sized video FEC can stall the single OBS receive owner and cascade socket loss. Receiver now inverts coefficients once per frame and uses table-driven GF(256); active Release assertions prove every 1..16 consecutive erasure case on 64x1400-byte shards under 50 ms."} {"ts": "2026-07-17T01:59:33+01:00", "type": "scar", "status": "open", "note": "Direct Raven LAN falsified trailing-parity sufficiency: a 24-datagram sequence gap left a 56-chunk keyframe at 52/56 with parity_present=false because all 16 V3 parity shards followed the data and fell in the same tail burst. Next cut must disperse parity/adapt block layout; deadline increases and repair flooding are not owners."} +{"ts": "2026-07-17T02:34:05+01:00", "type": "verification", "status": "ok", "note": "Receiver completed-frame tombstones eliminated false post-FEC ghost assemblies: a 7-minute LAN run forwarded 54,000 payloads with zero video assembly expiry before an experimental weighted sender scheduler independently caused audio queue eviction; the scheduler was deleted. A later genuine 24-datagram tail burst left a frame 18/33 with only the leading 8 of 16 bookended parity shards available, proving V3 frame-wide bookends still need Moonlight-style multi-block FEC."} diff --git a/state/map.yaml b/state/map.yaml index 7de8d14..edb3707 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -88,7 +88,7 @@ invariants: accepted_design: - Muninn video FEC is now typed as `muninn.media_video_parity_shard.v3`. - Odin emits 16 Cauchy GF(256) parity shards after each multi-chunk access + Odin emits 16 Cauchy GF(256) parity shards around each multi-chunk access unit, so any erasure set up to the received parity count is recoverable; Mimir's OBS receiver owns the matching bounded matrix recovery before the assembly deadline. V2 stripe parity is never reinterpreted as V3. Native @@ -97,12 +97,20 @@ accepted_design: recovery bound. Odin's 180 Muninn tests pass. A per-byte elimination draft was rejected after one recovery stalled the receiver thread; production inverts the coefficient matrix once per frame and uses table-driven field - arithmetic. Direct Raven LAN evidence then exposed a remaining layout fault: - a 24-datagram tail burst erased four final data chunks plus all sixteen - trailing parity shards, so no parity survived to repair the otherwise small - data loss. Repair retransmission is now capped behind fresh media and video - datagrams use a 50 microsecond spin pacer, but parity dispersion/adaptive - block sizing remains required before the clean ten-minute gate can pass. + arithmetic. Direct Raven LAN evidence exposed two distinct faults. First, + successful early FEC completion did not tombstone the frame, so late + original chunks resurrected a partial ghost assembly and manufactured loss + feedback; the receiver now remembers completed frame keys and ignores that + residue. A seven-minute proof then kept every video failure counter at zero + while recovering 12,504 frames. Second, a genuine 24-datagram tail burst + left one frame at 18/33 chunks: the leading parity bookend supplied only + eight equations and the trailing eight were lost with fifteen data shards. + Repair retransmission is capped behind fresh media, IDR requests are + coalesced to 500 ms, and video datagrams use a 50 microsecond spin pacer, + but Moonlight-style independently recoverable FEC blocks remain required + before the clean ten-minute gate can pass. An experimental weighted audio/ + video scheduler was deleted after its accumulated audio debt caused bounded + queue eviction; audio retains strict latency priority. Normal frames are forwarded immediately; the recovery deadline is not a playout buffer. The ten-minute and impaired acceptance gates remain open. - Muninn now closes receiver congestion pressure into the live NVENC context.