1use crate::det_math::det_exp;
13
14#[derive(Debug, Clone)]
16pub struct EncodeQuality {
17 pub score: u8,
19 pub hint_key: String,
21 pub mode: u8,
23}
24
25const ALPHA_MAX: f64 = 0.5;
28
29pub struct GhostMetrics {
31 pub num_modifications: usize,
33 pub n_used: usize,
35 pub w: usize,
37 pub total_cost: f64,
39 pub median_cost: f32,
41 pub is_si: bool,
43 pub shadow_modifications: usize,
45 pub total_coefficients: usize,
47}
48
49pub fn ghost_stealth_score(m: &GhostMetrics) -> EncodeQuality {
59 let alpha = if m.n_used > 0 {
60 m.num_modifications as f64 / m.n_used as f64
61 } else {
62 1.0
63 };
64
65 let rate_ratio = (alpha / ALPHA_MAX).min(1.0);
68 let rate_score = 100.0 * (1.0 - rate_ratio) * (1.0 - rate_ratio);
69
70 let cost_score = 100.0 * (1.0 - ((m.median_cost as f64 - 3.0).max(0.0) / 17.0).min(1.0));
74 let cost_score = 100.0 - cost_score;
76
77 let w_f = m.w as f64;
81 let width_score = 100.0 * det_tanh(w_f / 5.0);
82
83 let avg_cost = if m.num_modifications > 0 {
86 m.total_cost / m.num_modifications as f64
87 } else {
88 0.0
89 };
90 let distort_score = 100.0 * (1.0 - ((avg_cost - 3.0) / 17.0).clamp(0.0, 1.0));
91
92 let si_score = if m.is_si { 100.0 } else { 0.0 };
94
95 let base_stealth = 0.30 * rate_score
96 + 0.25 * cost_score
97 + 0.20 * width_score
98 + 0.15 * distort_score
99 + 0.10 * si_score;
100
101 let shadow_penalty = if m.shadow_modifications > 0 && m.total_coefficients > 0 {
103 let shadow_mod_ratio = m.shadow_modifications as f64 / m.total_coefficients as f64;
104 15.0 * (shadow_mod_ratio / 0.01).min(1.0)
105 } else {
106 0.0
107 };
108
109 let stealth = (base_stealth - shadow_penalty).clamp(0.0, 100.0);
110 let score = stealth.round() as u8;
111
112 let factors = [
114 (rate_score, "hint_high_rate"),
115 (cost_score, "hint_low_texture"),
116 (width_score, "hint_low_width"),
117 (distort_score, "hint_high_distortion"),
118 ];
119 let hint_key = if shadow_penalty > 5.0 {
120 "hint_shadow_penalty"
121 } else if m.is_si && score >= 70 {
122 "hint_si_bonus"
123 } else {
124 factors.iter()
126 .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
127 .map_or("hint_high_rate", |(_, key)| *key)
128 };
129
130 EncodeQuality {
131 score,
132 hint_key: hint_key.to_string(),
133 mode: 1,
134 }
135}
136
137pub struct ArmorMetrics {
139 pub repetition_factor: usize,
141 pub parity_symbols: usize,
143 pub fortress: bool,
145 pub mean_qt: f64,
148 pub fill_ratio: f64,
150 pub delta: f64,
152}
153
154pub fn armor_robustness_score(m: &ArmorMetrics) -> EncodeQuality {
164 let rep_score = 100.0 * (m.repetition_factor as f64 / 7.0).min(1.0);
167
168 let parity_score = 100.0 * (m.parity_symbols as f64 / 240.0).min(1.0);
171
172 let fortress_score = if m.fortress { 100.0 } else { 0.0 };
174
175 let qt_score = 100.0 * (m.mean_qt / 20.0).min(1.0);
179
180 let fill_score = 100.0 * (1.0 - m.fill_ratio.clamp(0.0, 1.0));
183
184 let delta_score = 100.0 * (m.delta / 40.0).min(1.0);
187
188 let robustness = if m.fortress {
192 0.30 * rep_score
193 + 0.20 * parity_score
194 + 0.15 * fortress_score
195 + 0.15 * qt_score
196 + 0.10 * fill_score
197 + 0.10 * delta_score
198 } else {
199 (0.30 / 0.85) * rep_score
201 + (0.20 / 0.85) * parity_score
202 + (0.15 / 0.85) * qt_score
203 + (0.10 / 0.85) * fill_score
204 + (0.10 / 0.85) * delta_score
205 };
206
207 let score = robustness.round().clamp(0.0, 100.0) as u8;
208
209 let hint_key = if m.fortress {
211 "hint_fortress_active"
212 } else {
213 let factors = [
214 (rep_score, "hint_low_repetition"),
215 (parity_score, "hint_low_parity"),
216 (qt_score, "hint_high_qf"),
217 (fill_score, "hint_high_fill"),
218 (delta_score, "hint_low_delta"),
219 ];
220 factors.iter()
222 .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
223 .map_or("hint_low_repetition", |(_, key)| *key)
224 };
225
226 EncodeQuality {
227 score,
228 hint_key: hint_key.to_string(),
229 mode: 2,
230 }
231}
232
233fn det_tanh(x: f64) -> f64 {
237 if x > 10.0 { return 1.0; }
238 if x < -10.0 { return -1.0; }
239 let e2x = det_exp(2.0 * x);
243 (e2x - 1.0) / (e2x + 1.0)
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn ghost_perfect_stealth() {
252 let m = GhostMetrics {
253 num_modifications: 0,
254 n_used: 10000,
255 w: 10,
256 total_cost: 0.0,
257 median_cost: 20.0,
258 is_si: true,
259 shadow_modifications: 0,
260 total_coefficients: 100000,
261 };
262 let q = ghost_stealth_score(&m);
263 assert_eq!(q.mode, 1);
264 assert!(q.score >= 90, "expected >= 90 for perfect stealth, got {}", q.score);
265 }
266
267 #[test]
268 fn ghost_worst_case() {
269 let m = GhostMetrics {
270 num_modifications: 5000,
271 n_used: 10000,
272 w: 1,
273 total_cost: 100000.0,
274 median_cost: 1.0,
275 is_si: false,
276 shadow_modifications: 0,
277 total_coefficients: 100000,
278 };
279 let q = ghost_stealth_score(&m);
280 assert!(q.score <= 30, "expected <= 30 for worst case, got {}", q.score);
281 }
282
283 #[test]
284 fn ghost_shadow_penalty() {
285 let base = GhostMetrics {
286 num_modifications: 100,
287 n_used: 10000,
288 w: 7,
289 total_cost: 500.0,
290 median_cost: 15.0,
291 is_si: false,
292 shadow_modifications: 0,
293 total_coefficients: 100000,
294 };
295 let no_shadow = ghost_stealth_score(&base);
296
297 let with_shadow = GhostMetrics {
298 num_modifications: 100,
299 n_used: 10000,
300 w: 7,
301 total_cost: 500.0,
302 median_cost: 15.0,
303 is_si: false,
304 shadow_modifications: 1000,
305 total_coefficients: 100000,
306 };
307 let shadow_q = ghost_stealth_score(&with_shadow);
308 assert!(shadow_q.score < no_shadow.score, "shadow penalty should reduce score");
309 assert_eq!(shadow_q.hint_key, "hint_shadow_penalty");
310 }
311
312 #[test]
313 fn ghost_si_bonus() {
314 let without_si = GhostMetrics {
315 num_modifications: 50,
316 n_used: 10000,
317 w: 8,
318 total_cost: 200.0,
319 median_cost: 15.0,
320 is_si: false,
321 shadow_modifications: 0,
322 total_coefficients: 100000,
323 };
324 let with_si = GhostMetrics {
325 num_modifications: 50,
326 n_used: 10000,
327 w: 8,
328 total_cost: 200.0,
329 median_cost: 15.0,
330 is_si: true,
331 shadow_modifications: 0,
332 total_coefficients: 100000,
333 };
334 let q1 = ghost_stealth_score(&without_si);
335 let q2 = ghost_stealth_score(&with_si);
336 assert!(q2.score > q1.score, "SI should increase score");
337 assert_eq!(q2.hint_key, "hint_si_bonus");
338 }
339
340 #[test]
341 fn armor_fortress_high_score() {
342 let m = ArmorMetrics {
343 repetition_factor: 15,
344 parity_symbols: 240,
345 fortress: true,
346 mean_qt: 10.0,
347 fill_ratio: 0.3,
348 delta: 12.0,
349 };
350 let q = armor_robustness_score(&m);
351 assert_eq!(q.mode, 2);
352 assert!(q.score >= 75, "expected >= 75 for fortress, got {}", q.score);
353 assert_eq!(q.hint_key, "hint_fortress_active");
354 }
355
356 #[test]
357 fn armor_phase1_low_score() {
358 let m = ArmorMetrics {
359 repetition_factor: 1,
360 parity_symbols: 64,
361 fortress: false,
362 mean_qt: 3.0,
363 fill_ratio: 0.9,
364 delta: 5.0,
365 };
366 let q = armor_robustness_score(&m);
367 assert!(q.score <= 40, "expected <= 40 for phase1 near capacity, got {}", q.score);
368 }
369
370 #[test]
371 fn armor_phase2_medium_score() {
372 let m = ArmorMetrics {
373 repetition_factor: 5,
374 parity_symbols: 192,
375 fortress: false,
376 mean_qt: 15.0,
377 fill_ratio: 0.5,
378 delta: 20.0,
379 };
380 let q = armor_robustness_score(&m);
381 assert!(q.score >= 50 && q.score <= 85, "expected 50-85, got {}", q.score);
382 }
383
384 #[test]
385 fn armor_qf50_max_resilience() {
386 let m = ArmorMetrics {
387 repetition_factor: 7,
388 parity_symbols: 240,
389 fortress: false,
390 mean_qt: 25.0,
391 fill_ratio: 0.2,
392 delta: 40.0,
393 };
394 let q = armor_robustness_score(&m);
395 assert!(q.score >= 80, "expected >= 80 for QF50 + r=7, got {}", q.score);
396 }
397
398 #[test]
399 fn score_clamped_0_100() {
400 let q = ghost_stealth_score(&GhostMetrics {
402 num_modifications: 100000,
403 n_used: 1,
404 w: 0,
405 total_cost: 999999.0,
406 median_cost: 0.0,
407 is_si: false,
408 shadow_modifications: 100000,
409 total_coefficients: 1,
410 });
411 assert!(q.score <= 100);
412
413 let q = armor_robustness_score(&ArmorMetrics {
414 repetition_factor: 100,
415 parity_symbols: 500,
416 fortress: true,
417 mean_qt: 50.0,
418 fill_ratio: -1.0,
419 delta: 100.0,
420 });
421 assert!(q.score <= 100);
422 }
423
424 #[test]
425 fn det_tanh_basics() {
426 assert!((det_tanh(0.0)).abs() < 1e-10);
427 assert!((det_tanh(1.0) - 0.7615941559557649).abs() < 1e-10);
428 assert!((det_tanh(20.0) - 1.0).abs() < 1e-10);
429 assert!((det_tanh(-20.0) + 1.0).abs() < 1e-10);
430 }
431}