use crate::det_math::det_exp;
#[derive(Debug, Clone)]
pub struct EncodeQuality {
pub score: u8,
pub hint_key: String,
pub mode: u8,
}
const ALPHA_MAX: f64 = 0.5;
pub struct GhostMetrics {
pub num_modifications: usize,
pub n_used: usize,
pub w: usize,
pub total_cost: f64,
pub median_cost: f32,
pub is_si: bool,
pub shadow_modifications: usize,
pub total_coefficients: usize,
}
pub fn ghost_stealth_score(m: &GhostMetrics) -> EncodeQuality {
let alpha = if m.n_used > 0 {
m.num_modifications as f64 / m.n_used as f64
} else {
1.0
};
let rate_ratio = (alpha / ALPHA_MAX).min(1.0);
let rate_score = 100.0 * (1.0 - rate_ratio) * (1.0 - rate_ratio);
let cost_score = 100.0 * (1.0 - ((m.median_cost as f64 - 3.0).max(0.0) / 17.0).min(1.0));
let cost_score = 100.0 - cost_score;
let w_f = m.w as f64;
let width_score = 100.0 * det_tanh(w_f / 5.0);
let avg_cost = if m.num_modifications > 0 {
m.total_cost / m.num_modifications as f64
} else {
0.0
};
let distort_score = 100.0 * (1.0 - ((avg_cost - 3.0) / 17.0).clamp(0.0, 1.0));
let si_score = if m.is_si { 100.0 } else { 0.0 };
let base_stealth = 0.30 * rate_score
+ 0.25 * cost_score
+ 0.20 * width_score
+ 0.15 * distort_score
+ 0.10 * si_score;
let shadow_penalty = if m.shadow_modifications > 0 && m.total_coefficients > 0 {
let shadow_mod_ratio = m.shadow_modifications as f64 / m.total_coefficients as f64;
15.0 * (shadow_mod_ratio / 0.01).min(1.0)
} else {
0.0
};
let stealth = (base_stealth - shadow_penalty).clamp(0.0, 100.0);
let score = stealth.round() as u8;
let factors = [
(rate_score, "hint_high_rate"),
(cost_score, "hint_low_texture"),
(width_score, "hint_low_width"),
(distort_score, "hint_high_distortion"),
];
let hint_key = if shadow_penalty > 5.0 {
"hint_shadow_penalty"
} else if m.is_si && score >= 70 {
"hint_si_bonus"
} else {
factors.iter()
.min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
.map_or("hint_high_rate", |(_, key)| *key)
};
EncodeQuality {
score,
hint_key: hint_key.to_string(),
mode: 1,
}
}
pub struct ArmorMetrics {
pub repetition_factor: usize,
pub parity_symbols: usize,
pub fortress: bool,
pub mean_qt: f64,
pub fill_ratio: f64,
pub delta: f64,
}
pub fn armor_robustness_score(m: &ArmorMetrics) -> EncodeQuality {
let rep_score = 100.0 * (m.repetition_factor as f64 / 7.0).min(1.0);
let parity_score = 100.0 * (m.parity_symbols as f64 / 240.0).min(1.0);
let fortress_score = if m.fortress { 100.0 } else { 0.0 };
let qt_score = 100.0 * (m.mean_qt / 20.0).min(1.0);
let fill_score = 100.0 * (1.0 - m.fill_ratio.clamp(0.0, 1.0));
let delta_score = 100.0 * (m.delta / 40.0).min(1.0);
let robustness = if m.fortress {
0.30 * rep_score
+ 0.20 * parity_score
+ 0.15 * fortress_score
+ 0.15 * qt_score
+ 0.10 * fill_score
+ 0.10 * delta_score
} else {
(0.30 / 0.85) * rep_score
+ (0.20 / 0.85) * parity_score
+ (0.15 / 0.85) * qt_score
+ (0.10 / 0.85) * fill_score
+ (0.10 / 0.85) * delta_score
};
let score = robustness.round().clamp(0.0, 100.0) as u8;
let hint_key = if m.fortress {
"hint_fortress_active"
} else {
let factors = [
(rep_score, "hint_low_repetition"),
(parity_score, "hint_low_parity"),
(qt_score, "hint_high_qf"),
(fill_score, "hint_high_fill"),
(delta_score, "hint_low_delta"),
];
factors.iter()
.min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
.map_or("hint_low_repetition", |(_, key)| *key)
};
EncodeQuality {
score,
hint_key: hint_key.to_string(),
mode: 2,
}
}
fn det_tanh(x: f64) -> f64 {
if x > 10.0 { return 1.0; }
if x < -10.0 { return -1.0; }
let e2x = det_exp(2.0 * x);
(e2x - 1.0) / (e2x + 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ghost_perfect_stealth() {
let m = GhostMetrics {
num_modifications: 0,
n_used: 10000,
w: 10,
total_cost: 0.0,
median_cost: 20.0,
is_si: true,
shadow_modifications: 0,
total_coefficients: 100000,
};
let q = ghost_stealth_score(&m);
assert_eq!(q.mode, 1);
assert!(q.score >= 90, "expected >= 90 for perfect stealth, got {}", q.score);
}
#[test]
fn ghost_worst_case() {
let m = GhostMetrics {
num_modifications: 5000,
n_used: 10000,
w: 1,
total_cost: 100000.0,
median_cost: 1.0,
is_si: false,
shadow_modifications: 0,
total_coefficients: 100000,
};
let q = ghost_stealth_score(&m);
assert!(q.score <= 30, "expected <= 30 for worst case, got {}", q.score);
}
#[test]
fn ghost_shadow_penalty() {
let base = GhostMetrics {
num_modifications: 100,
n_used: 10000,
w: 7,
total_cost: 500.0,
median_cost: 15.0,
is_si: false,
shadow_modifications: 0,
total_coefficients: 100000,
};
let no_shadow = ghost_stealth_score(&base);
let with_shadow = GhostMetrics {
num_modifications: 100,
n_used: 10000,
w: 7,
total_cost: 500.0,
median_cost: 15.0,
is_si: false,
shadow_modifications: 1000,
total_coefficients: 100000,
};
let shadow_q = ghost_stealth_score(&with_shadow);
assert!(shadow_q.score < no_shadow.score, "shadow penalty should reduce score");
assert_eq!(shadow_q.hint_key, "hint_shadow_penalty");
}
#[test]
fn ghost_si_bonus() {
let without_si = GhostMetrics {
num_modifications: 50,
n_used: 10000,
w: 8,
total_cost: 200.0,
median_cost: 15.0,
is_si: false,
shadow_modifications: 0,
total_coefficients: 100000,
};
let with_si = GhostMetrics {
num_modifications: 50,
n_used: 10000,
w: 8,
total_cost: 200.0,
median_cost: 15.0,
is_si: true,
shadow_modifications: 0,
total_coefficients: 100000,
};
let q1 = ghost_stealth_score(&without_si);
let q2 = ghost_stealth_score(&with_si);
assert!(q2.score > q1.score, "SI should increase score");
assert_eq!(q2.hint_key, "hint_si_bonus");
}
#[test]
fn armor_fortress_high_score() {
let m = ArmorMetrics {
repetition_factor: 15,
parity_symbols: 240,
fortress: true,
mean_qt: 10.0,
fill_ratio: 0.3,
delta: 12.0,
};
let q = armor_robustness_score(&m);
assert_eq!(q.mode, 2);
assert!(q.score >= 75, "expected >= 75 for fortress, got {}", q.score);
assert_eq!(q.hint_key, "hint_fortress_active");
}
#[test]
fn armor_phase1_low_score() {
let m = ArmorMetrics {
repetition_factor: 1,
parity_symbols: 64,
fortress: false,
mean_qt: 3.0,
fill_ratio: 0.9,
delta: 5.0,
};
let q = armor_robustness_score(&m);
assert!(q.score <= 40, "expected <= 40 for phase1 near capacity, got {}", q.score);
}
#[test]
fn armor_phase2_medium_score() {
let m = ArmorMetrics {
repetition_factor: 5,
parity_symbols: 192,
fortress: false,
mean_qt: 15.0,
fill_ratio: 0.5,
delta: 20.0,
};
let q = armor_robustness_score(&m);
assert!(q.score >= 50 && q.score <= 85, "expected 50-85, got {}", q.score);
}
#[test]
fn armor_qf50_max_resilience() {
let m = ArmorMetrics {
repetition_factor: 7,
parity_symbols: 240,
fortress: false,
mean_qt: 25.0,
fill_ratio: 0.2,
delta: 40.0,
};
let q = armor_robustness_score(&m);
assert!(q.score >= 80, "expected >= 80 for QF50 + r=7, got {}", q.score);
}
#[test]
fn score_clamped_0_100() {
let q = ghost_stealth_score(&GhostMetrics {
num_modifications: 100000,
n_used: 1,
w: 0,
total_cost: 999999.0,
median_cost: 0.0,
is_si: false,
shadow_modifications: 100000,
total_coefficients: 1,
});
assert!(q.score <= 100);
let q = armor_robustness_score(&ArmorMetrics {
repetition_factor: 100,
parity_symbols: 500,
fortress: true,
mean_qt: 50.0,
fill_ratio: -1.0,
delta: 100.0,
});
assert!(q.score <= 100);
}
#[test]
fn det_tanh_basics() {
assert!((det_tanh(0.0)).abs() < 1e-10);
assert!((det_tanh(1.0) - 0.7615941559557649).abs() < 1e-10);
assert!((det_tanh(20.0) - 1.0).abs() < 1e-10);
assert!((det_tanh(-20.0) + 1.0).abs() < 1e-10);
}
}