//! WHY does `resolve_movie_voice_region` start inside the first stream? //! //! [`voice-region-starts-late.md`] establishes that it does — 238 packets late for //! `ADV`, 8 of 10 multichannel regions disc-wide — but not why, and a fix guessed //! from one movie would be worse than a documented defect. This reproduces the //! resolver's own steps and prints each candidate, so the failing branch is visible //! rather than inferred. //! //! The suspicion the code itself raises: the start is filtered by //! `end - s < 1_500_000` — "only within one bank" — and `ADV`'s region has to span //! **3.6 MB**. If that filter rejects the real predecessor, `start` silently falls //! back to `anchor`, which is a TOC offset and not a stream boundary at all. //! //! cargo run -p sylpheed-formats --example voice_region_start_why use sylpheed_formats::hash::name_hash; use sylpheed_formats::media::{DirectorySource, DiscSource}; use sylpheed_formats::pak::PakArchive; use sylpheed_formats::slb::VoiceLang; use sylpheed_formats::{movie_manifest, movie_voice}; fn main() { let disc = std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"); let src = DirectorySource::new(std::path::PathBuf::from(&disc)); let code = VoiceLang::English.code_pub(); let tpak = src.open_pak("dat/tables.pak").expect("tables.pak"); let manifest = tpak .entries() .iter() .find_map(|e| tpak.read(e).ok().filter(|b| movie_manifest::is_manifest(b))) .expect("manifest"); let marker = format!("{code}\\Movie\\VOICE_ADV.slb"); let registry = tpak .entries() .iter() .find_map(|e| { tpak.read(e) .ok() .filter(|b| b.windows(marker.len()).any(|w| w == marker.as_bytes())) }) .expect("registry"); let ids = movie_voice::registry_voice_ids(®istry); let stoc = src.read_file("dat/sound.pak").expect("sound.pak"); let entries = PakArchive::parse_toc(&stoc).expect("toc"); println!( "{:10} {:>6} {:>12} {:>12} {:>12} {:>10} {:>9} {}", "movie", "id", "anchor", "pred(id-1)", "pred(before)", "span", "chosen", "note" ); for m in movie_manifest::parse(&manifest) { let movie = m.movie; let Some(token) = movie_manifest::voice_token(&manifest, &movie) else { continue; }; let Some(&id) = ids.get(&token) else { continue }; let Some(anchor) = ["Movie", "etc", "Voice"].iter().find_map(|dir| { let h = name_hash(&format!("{code}\\{dir}\\{token}.slb")); entries .binary_search_by_key(&h, |e| e.name_hash) .ok() .map(|i| entries[i].offset as u64) }) else { continue; }; let win_start = anchor.saturating_sub(2 * 1024 * 1024) & !3; let Ok(window) = src.read_segment_range("dat/sound", win_start, 8 * 1024 * 1024) else { continue; }; let Some(end_local) = movie_voice::find_descriptor(&window, id) else { continue; }; let end = win_start + end_local as u64; let p1 = movie_voice::find_descriptor(&window, id.wrapping_sub(1)).map(|o| win_start + o as u64); let pb = movie_voice::find_descriptor_before(&window, end_local).map(|o| win_start + o as u64); let cand = p1.or(pb); // the resolver's own filter let kept = cand.filter(|&s| s < end && end - s < 1_500_000); let chosen = kept.unwrap_or(anchor); let span = cand.map(|s| end.saturating_sub(s)).unwrap_or(0); let note = match (cand, kept) { (Some(_), None) => "REJECTED by the 1.5 MB filter -> fell back to anchor", (Some(_), Some(_)) => "predecessor kept", (None, _) => "no predecessor found -> anchor", }; println!( "{movie:10} {id:>6} {anchor:>12} {:>12} {:>12} {span:>10} {:>9} {note}", p1.map(|v| v.to_string()).unwrap_or("-".into()), pb.map(|v| v.to_string()).unwrap_or("-".into()), if chosen == anchor { "anchor" } else { "pred" } ); } }