From 776a4fd6ebad4588b1997272e0164844a71e0ba3 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 7 Aug 2026 03:05:32 -0700 Subject: [PATCH] Say which frame and which marks caused a provenance drop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The heuristic path has always logged its first drop per track with the frame's timestamp and the clip marks it was judged against. The provenance path did not log at all, so a title that dropped MOST of its frames there failed the volume gate without a single line saying which frame, which clip, or which marks — a full-length rip produced zero drop events and an error, which is not a diagnosable failure. Also pins the all-identical-spans hazard as a test. Every PlayItem referencing one clip file gives every clip the same span, which passes the tiling check and is therefore "trusted" while carrying no information at all about which PlayItem a byte belongs to. That is not what the discs on hand do — theirs have one distinct span per clip — but the check conflates "the spans tile" with "the spans distinguish", and only the second justifies placing a frame by its byte offset. --- src/mux/timeline.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/mux/timeline.rs b/src/mux/timeline.rs index 6f9a4d9..aa98674 100644 --- a/src/mux/timeline.rs +++ b/src/mux/timeline.rs @@ -430,6 +430,25 @@ impl SeamPlan { // (a clip's file is not trimmed to its marks). Counted, so the // volume gates in the sinks can see it. self.dropped[track] = self.dropped[track].saturating_add(1); + // Once per track, on the FIRST drop only — a join legitimately + // drops a handful of frames and this must not become per-frame + // noise. The heuristic path below has always logged this; the + // provenance path did not, so a title that dropped MOST of its + // frames here produced a volume-gate failure and not one line + // saying which frame, which clip, or which marks it was judged + // against. That is not a diagnosable failure. + if self.dropped[track] == 1 { + tracing::info!( + target: "freemkv::mux", + track, + clip = found, + byte = b, + raw_ns, + in_ns = c.in_ns, + out_ns = c.out_ns, + "frame outside its clip's marks (by provenance); dropping" + ); + } return None; } return Some(raw_ns.saturating_add(c.offset_ns)); @@ -451,6 +470,17 @@ impl SeamPlan { let c = self.clips[clip]; if raw_ns < c.in_ns || raw_ns > c.out_ns { self.dropped[track] = self.dropped[track].saturating_add(1); + if self.dropped[track] == 1 { + tracing::info!( + target: "freemkv::mux", + track, + clip, + raw_ns, + in_ns = c.in_ns, + out_ns = c.out_ns, + "frame with no provenance outside its track's clip; dropping" + ); + } return None; } let out = raw_ns.saturating_add(c.offset_ns); @@ -1336,6 +1366,45 @@ mod tests { ); } + /// A title whose PlayItems all reference ONE clip file: every span is + /// identical, so the tiling check's "equal to previous is allowed" arm + /// matches every entry and the spans are TRUSTED — while carrying no + /// information at all about which PlayItem a byte belongs to. + /// + /// That combination is the dangerous one: provenance looks authoritative + /// and is actually blind, so every frame resolves to the FIRST PlayItem and + /// everything past its mark range is dropped. + #[test] + fn one_clip_file_behind_every_play_item_is_not_distinguishable_by_byte() { + const N: u32 = 8; + const SPAN: (u64, u64) = (0, 40_000_000_000); + const SEG: u32 = 600 * 45_000; // 10 min per sub-range + let clips: Vec = (0..N) + .map(|i| crate::disc::Clip { + clip_id: "00001".to_string(), // the SAME file every time + in_time: i * SEG, + out_time: (i + 1) * SEG, + duration_secs: 600.0, + source_packets: 0, + feed_span: Some(SPAN), + }) + .collect(); + let plan = SeamPlan::from_clips(&clips).expect("plan"); + assert!( + plan.spans_trusted, + "identical spans pass the tiling check -- this is the hazard" + ); + // Every byte in the file resolves to the FIRST play item, so a byte + // offset cannot say which of the 8 ranges a frame belongs to. + for b in [0u64, 1_000_000, 20_000_000_000, 39_999_999_999] { + assert_eq!( + plan.clip_at_byte(b), + Some(0), + "byte {b} resolves to the first play item, always" + ); + } + } + /// A playlist whose clips each restart their own STC is the common case on /// the branched discs in the hoard: every clip's marks cover the same low /// values, so the marks are NOT points on one title-wide clock.