Ignore late shards from completed Muninn frames

This commit is contained in:
Meta Vens 2026-07-17 02:34:51 +01:00
commit 02c615670a
3 changed files with 43 additions and 13 deletions

View file

@ -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<std::string, std::vector<MuninnVideoParityCacheEntry>> video_parity_cache_;
std::deque<std::string> expired_video_frame_order_;
std::set<std::string> expired_video_frame_keys_;
std::deque<std::string> completed_video_frame_order_;
std::set<std::string> completed_video_frame_keys_;
muninn_rudp_fragments::FragmentAssembler packet_fragments_;
muninn_rudp_fragments::FragmentAssembler audio_packet_fragments_;
muninn_rudp_ack::AckTracker ack_tracker_;

View file

@ -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."}

View file

@ -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.