use super::encoder_hook::MvdPositionMeta;
use super::{Axis, PositionKey, SyntaxPath};
pub const MAX_MVD_POSITIONS_PER_GOP: usize = 200_000;
pub fn check_mvd_budget(
mvd_count: usize,
gop_label: &str,
) -> Result<(), crate::stego::error::StegoError> {
if mvd_count > MAX_MVD_POSITIONS_PER_GOP {
return Err(crate::stego::error::StegoError::InvalidVideo(format!(
"MVD position count {mvd_count} exceeds per-GOP streaming \
budget {MAX_MVD_POSITIONS_PER_GOP} at {gop_label}: \
encoder mode-decision is producing pathologically fine \
partitioning (see §6E-A6.0 working-set guard rail)",
)));
}
Ok(())
}
pub fn analyze_safe_mvd_subset(
meta: &[MvdPositionMeta],
mb_w: u32,
mb_h: u32,
) -> Vec<bool> {
let n = meta.len();
let mut safe = vec![false; n];
if n == 0 || mb_w == 0 || mb_h == 0 {
return safe;
}
let mut shift_bound: Vec<u32> = vec![0; n];
let mut order: Vec<usize> = (0..n).collect();
order.sort_by_key(|&i| {
(meta[i].frame_idx, meta[i].mb_addr, meta[i].partition, meta[i].axis)
});
for &i in &order {
let p = &meta[i];
let p_mx = p.mb_addr % mb_w;
let p_my = p.mb_addr / mb_w;
let mut pred_a = true;
for &j in &order {
if j == i { break; } if !safe[j] { continue; }
let q = &meta[j];
if q.frame_idx != p.frame_idx { continue; }
let q_mx = q.mb_addr % mb_w;
let q_my = q.mb_addr / mb_w;
if mb_propagates(q_mx, q_my, p_mx, p_my) {
pred_a = false;
break;
}
}
if !pred_a {
continue;
}
let two_m_p = 2u32.saturating_mul(p.magnitude);
let mut pred_b = true;
let mut tentative_updates: Vec<usize> = Vec::new();
for &j in &order {
if j == i { continue; }
let q = &meta[j];
if q.frame_idx != p.frame_idx { continue; }
let q_mx = q.mb_addr % mb_w;
let q_my = q.mb_addr / mb_w;
if !mb_strictly_later(p_mx, p_my, q_mx, q_my) { continue; }
if !mb_propagates(p_mx, p_my, q_mx, q_my) { continue; }
let new_bound = shift_bound[j].saturating_add(two_m_p);
if new_bound >= q.magnitude {
pred_b = false;
break;
}
tentative_updates.push(j);
}
if pred_b {
safe[i] = true;
for j in tentative_updates {
shift_bound[j] = shift_bound[j].saturating_add(two_m_p);
}
}
}
safe
}
#[inline]
pub fn mb_propagates(sx: u32, sy: u32, qx: u32, qy: u32) -> bool {
if qx == sx.saturating_add(1) && qy == sy { return true; }
if qx == sx && qy == sy.saturating_add(1) { return true; }
if sx > 0 && qx == sx - 1 && qy == sy.saturating_add(1) { return true; }
if qx == sx.saturating_add(1) && qy == sy.saturating_add(1) { return true; }
false
}
#[inline]
fn mb_strictly_later(sx: u32, sy: u32, qx: u32, qy: u32) -> bool {
qy > sy || (qy == sy && qx > sx)
}
pub fn derive_msl_safe_from_msb(
msb_positions: &[PositionKey],
safe_msb: &[bool],
msl_positions: &[PositionKey],
) -> Vec<bool> {
type SlotKey = (u32, u32, u8, u8, Axis);
fn slot_key(k: PositionKey) -> Option<SlotKey> {
match k.syntax_path() {
SyntaxPath::Mvd { list, partition, axis, .. } =>
Some((k.frame_idx(), k.mb_addr(), list, partition, axis)),
_ => None,
}
}
let n = msb_positions.len().min(safe_msb.len());
let mut map: std::collections::HashMap<SlotKey, bool> =
std::collections::HashMap::with_capacity(n);
for i in 0..n {
if let Some(key) = slot_key(msb_positions[i]) {
map.insert(key, safe_msb[i]);
}
}
msl_positions
.iter()
.map(|&k| {
slot_key(k)
.and_then(|sk| map.get(&sk).copied())
.unwrap_or(false)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn meta(frame_idx: u32, mb_addr: u32, partition: u8, axis: u8, magnitude: u32) -> MvdPositionMeta {
MvdPositionMeta { frame_idx, mb_addr, partition, axis, magnitude }
}
#[test]
fn check_mvd_budget_passes_at_cap() {
assert!(check_mvd_budget(MAX_MVD_POSITIONS_PER_GOP, "test").is_ok());
assert!(check_mvd_budget(0, "test").is_ok());
assert!(check_mvd_budget(MAX_MVD_POSITIONS_PER_GOP - 1, "test").is_ok());
}
#[test]
fn check_mvd_budget_rejects_above_cap() {
let r = check_mvd_budget(MAX_MVD_POSITIONS_PER_GOP + 1, "gop[5]");
assert!(r.is_err());
let msg = format!("{:?}", r.unwrap_err());
assert!(msg.contains("gop[5]"), "missing gop label: {msg}");
assert!(msg.contains(&MAX_MVD_POSITIONS_PER_GOP.to_string()),
"missing cap value: {msg}");
}
#[test]
fn check_mvd_budget_const_is_nonzero_and_realistic() {
assert!(MAX_MVD_POSITIONS_PER_GOP >= 50_000);
assert!(MAX_MVD_POSITIONS_PER_GOP <= 1_000_000);
}
#[test]
fn empty_input_yields_empty_safe_set() {
let r = analyze_safe_mvd_subset(&[], 4, 3);
assert!(r.is_empty());
}
#[test]
fn single_position_in_last_mb_is_safe() {
let m = vec![meta(0, 11, 0, 0, 1)];
let r = analyze_safe_mvd_subset(&m, 4, 3);
assert_eq!(r, vec![true]);
}
#[test]
fn cascade_blocks_when_downstream_magnitude_is_small() {
let m = vec![
meta(0, 0, 0, 0, 5), meta(0, 1, 0, 0, 3), ];
let r = analyze_safe_mvd_subset(&m, 4, 3);
assert_eq!(r[0], false, "P should be unsafe (cascade exceeds Q's magnitude)");
assert_eq!(r[1], true);
}
#[test]
fn cascade_safe_upstream_blocks_downstream_via_predicate_a() {
let m = vec![
meta(0, 0, 0, 0, 1),
meta(0, 1, 0, 0, 5),
];
let r = analyze_safe_mvd_subset(&m, 4, 3);
assert_eq!(r, vec![true, false]);
}
#[test]
fn non_propagating_pair_both_safe() {
let m = vec![
meta(0, 0, 0, 0, 1), meta(0, 2 + 2 * 4, 0, 0, 1), ];
let r = analyze_safe_mvd_subset(&m, 4, 3);
assert_eq!(r, vec![true, true]);
}
#[test]
fn cumulative_shift_predicate_b_blocks_p0_when_p1_magnitude_low() {
let m = vec![
meta(0, 0, 0, 0, 1),
meta(0, 4, 0, 0, 2), ];
let r = analyze_safe_mvd_subset(&m, 4, 3);
assert_eq!(r[0], false, "P0's flip would zero-cross P1");
}
#[test]
fn different_frames_are_independent() {
let m = vec![
meta(0, 0, 0, 0, 1),
meta(1, 0, 0, 0, 1),
];
let r = analyze_safe_mvd_subset(&m, 4, 3);
assert_eq!(r, vec![true, true]);
}
#[test]
fn mb_propagates_exhaustive_grid() {
assert!(mb_propagates(1, 1, 2, 1)); assert!(mb_propagates(1, 1, 1, 2)); assert!(mb_propagates(1, 1, 0, 2)); assert!(mb_propagates(1, 1, 2, 2)); assert!(!mb_propagates(1, 1, 1, 1)); assert!(!mb_propagates(1, 1, 0, 1)); assert!(!mb_propagates(1, 1, 1, 0)); assert!(!mb_propagates(1, 1, 3, 1)); assert!(!mb_propagates(1, 1, 0, 0)); }
use super::super::{BinKind, EmbedDomain};
#[test]
fn derive_msl_safe_maps_sign_to_suffix_via_position_tuple() {
let mk_path = |kind: BinKind| SyntaxPath::Mvd {
list: 0, partition: 0, axis: Axis::X, kind,
};
let sign_keys = [
PositionKey::new(0, 0, EmbedDomain::MvdSignBypass, mk_path(BinKind::Sign)),
PositionKey::new(0, 4, EmbedDomain::MvdSignBypass, mk_path(BinKind::Sign)),
PositionKey::new(0, 8, EmbedDomain::MvdSignBypass, mk_path(BinKind::Sign)),
];
let safe_sign = vec![true, false, true];
let suffix_keys = [
PositionKey::new(0, 4, EmbedDomain::MvdSuffixLsb, mk_path(BinKind::SuffixLsb)),
];
let safe_suffix = derive_msl_safe_from_msb(&sign_keys, &safe_sign, &suffix_keys);
assert_eq!(safe_suffix.len(), 1);
assert_eq!(safe_suffix[0], false, "suffix slot 1 should match sign safe[1] = false");
}
#[test]
fn derive_msl_safe_handles_orthogonal_axes() {
let mk_path = |axis: Axis, kind: BinKind| SyntaxPath::Mvd {
list: 0, partition: 0, axis, kind,
};
let sign_keys = [
PositionKey::new(0, 0, EmbedDomain::MvdSignBypass, mk_path(Axis::X, BinKind::Sign)),
PositionKey::new(0, 0, EmbedDomain::MvdSignBypass, mk_path(Axis::Y, BinKind::Sign)),
];
let safe_sign = vec![true, false];
let suffix_keys = [
PositionKey::new(0, 0, EmbedDomain::MvdSuffixLsb, mk_path(Axis::Y, BinKind::SuffixLsb)),
PositionKey::new(0, 0, EmbedDomain::MvdSuffixLsb, mk_path(Axis::X, BinKind::SuffixLsb)),
];
let safe_suffix = derive_msl_safe_from_msb(&sign_keys, &safe_sign, &suffix_keys);
assert_eq!(safe_suffix, vec![false, true]);
}
#[test]
fn derive_msl_safe_returns_empty_for_empty_msl() {
let safe = derive_msl_safe_from_msb(&[], &[], &[]);
assert!(safe.is_empty());
}
}