use phasm_core::det_math::{det_sin, det_cos, det_sincos, det_atan2, det_hypot};
use phasm_core::stego::permute::select_and_permute;
use phasm_core::stego::cost::CostMap;
use phasm_core::stego::armor::fft2d;
use phasm_core::stego::armor::template::generate_template_peaks;
use phasm_core::{ghost_encode, ghost_decode, armor_encode, armor_decode, smart_decode};
fn load_test_image(name: &str) -> Vec<u8> {
std::fs::read(format!("test-vectors/image/{name}")).unwrap()
}
fn all_finite_map(bw: usize, bt: usize) -> CostMap {
let mut map = CostMap::new(bw, bt);
for br in 0..bt {
for bc in 0..bw {
for i in 0..8 {
for j in 0..8 {
if i == 0 && j == 0 {
continue; }
map.set(br, bc, i, j, 1.0);
}
}
}
}
map
}
#[test]
fn pin_known_values_4x4_seed42() {
let map = all_finite_map(4, 4);
let seed = [42u8; 32];
let positions = select_and_permute(&map, &seed);
assert_eq!(
positions.len(),
1008,
"4x4 blocks should have 1008 AC positions"
);
let first_20: Vec<u32> = positions.iter().take(20).map(|p| p.flat_idx).collect();
let expected: Vec<u32> = vec![
258, 980, 673, 988, 76, 41, 725, 301, 438, 872, 667, 574, 867, 881, 46, 240, 965, 56,
339, 941,
];
assert_eq!(
first_20, expected,
"Permutation output changed! This breaks WASM/native compatibility.\n\
If you intentionally changed the shuffle algorithm, update these \
pinned values AND verify that existing stego images can still be decoded."
);
}
#[test]
fn permutation_is_deterministic() {
let map = all_finite_map(4, 4);
let seed = [42u8; 32];
let a = select_and_permute(&map, &seed);
let b = select_and_permute(&map, &seed);
let a_idx: Vec<u32> = a.iter().map(|p| p.flat_idx).collect();
let b_idx: Vec<u32> = b.iter().map(|p| p.flat_idx).collect();
assert_eq!(a_idx, b_idx, "Same seed must produce identical permutation");
}
#[test]
fn different_seeds_produce_different_permutations() {
let map = all_finite_map(4, 4);
let seed_a = [1u8; 32];
let seed_b = [2u8; 32];
let a = select_and_permute(&map, &seed_a);
let b = select_and_permute(&map, &seed_b);
let a_idx: Vec<u32> = a.iter().map(|p| p.flat_idx).collect();
let b_idx: Vec<u32> = b.iter().map(|p| p.flat_idx).collect();
assert_ne!(
a_idx, b_idx,
"Different seeds must produce different permutations"
);
}
#[test]
fn u32_range_invariant_large_map() {
let map = all_finite_map(64, 64); let seed = [99u8; 32];
let positions = select_and_permute(&map, &seed);
assert_eq!(positions.len(), 4096 * 63);
let mut indices: Vec<u32> = positions.iter().map(|p| p.flat_idx).collect();
indices.sort();
indices.dedup();
assert_eq!(
indices.len(),
4096 * 63,
"All positions must be unique after shuffle — u32 cast may be corrupting indices"
);
}
#[test]
fn ghost_roundtrip_cross_platform() {
let cover = load_test_image("photo_320x240_q75_420.jpg");
let message = "Cross-platform Ghost test";
let passphrase = "cross-platform-key-123";
let stego = ghost_encode(&cover, message, passphrase).unwrap();
let decoded = ghost_decode(&stego, passphrase).unwrap();
assert_eq!(decoded.text, message);
}
#[test]
fn armor_roundtrip_cross_platform() {
let cover = load_test_image("photo_320x240_q75_420.jpg");
let message = "Cross-platform Armor test";
let passphrase = "cross-platform-key-456";
let stego = armor_encode(&cover, message, passphrase).unwrap();
let (decoded, quality) = armor_decode(&stego, passphrase).unwrap();
assert_eq!(decoded.text, message);
assert_eq!(quality.mode, 0x02);
assert!(quality.integrity_percent >= 85,
"Pristine Armor integrity should be high: {}%", quality.integrity_percent);
}
#[test]
fn smart_decode_ghost_cross_platform() {
let cover = load_test_image("photo_320x240_q75_420.jpg");
let message = "Ghost via smart_decode";
let passphrase = "smart-ghost-xplat";
let stego = ghost_encode(&cover, message, passphrase).unwrap();
let (decoded, quality) = smart_decode(&stego, passphrase).unwrap();
assert_eq!(decoded.text, message);
assert_eq!(quality.mode, 0x01, "smart_decode should detect Ghost mode");
}
#[test]
fn smart_decode_armor_cross_platform() {
let cover = load_test_image("photo_320x240_q75_420.jpg");
let message = "Armor via smart_decode";
let passphrase = "smart-armor-xplat";
let stego = armor_encode(&cover, message, passphrase).unwrap();
let (decoded, quality) = smart_decode(&stego, passphrase).unwrap();
assert_eq!(decoded.text, message);
assert_eq!(quality.mode, 0x02, "smart_decode should detect Armor mode");
assert!(quality.integrity_percent >= 85,
"Pristine Armor integrity should be high: {}%", quality.integrity_percent);
}
#[test]
fn pin_det_sincos_bits() {
let cases: &[(f64, u64, u64)] = &[
(0.1, 0x3fb98eaecb8bcb2c, 0x3fefd712f9a817c0),
(0.5, 0x3fdeaee8744b05f0, 0x3fec1528065b7d50),
(1.0, 0x3feaed548f090cee, 0x3fe14a280fb5068c),
(2.5, 0x3fe326af0dcfcab0, 0xbfe9a2f7ef858b7d),
(-1.5, 0xbfefeb7a9b2c6d8b, 0x3fb21bd54fc5f9a7),
(5.0, 0xbfeeaf81f5e09933, 0x3fd22785706b4ada),
];
for &(x, expected_sin, expected_cos) in cases {
assert_eq!(
det_sin(x).to_bits(), expected_sin,
"det_sin({x}) bit mismatch: got {:#018x}, expected {:#018x}",
det_sin(x).to_bits(), expected_sin
);
assert_eq!(
det_cos(x).to_bits(), expected_cos,
"det_cos({x}) bit mismatch: got {:#018x}, expected {:#018x}",
det_cos(x).to_bits(), expected_cos
);
}
}
#[test]
fn pin_det_sincos_consistency() {
for i in 0..50 {
let x = (i as f64 - 25.0) * 0.37;
let (s, c) = det_sincos(x);
assert_eq!(s.to_bits(), det_sin(x).to_bits(), "sincos sin mismatch at x={x}");
assert_eq!(c.to_bits(), det_cos(x).to_bits(), "sincos cos mismatch at x={x}");
}
}
#[test]
fn pin_det_atan2_bits() {
let cases: &[(f64, f64, u64)] = &[
(1.0, 1.0, 0x3fe921fb54442d18),
(1.0, -1.0, 0x4002d97c7f3321d2),
(-3.0, 4.0, 0xbfe4978fa3269ee1),
(0.5, 2.0, 0x3fcf5b75f92c80dd),
];
for &(y, x, expected) in cases {
assert_eq!(
det_atan2(y, x).to_bits(), expected,
"det_atan2({y},{x}) bit mismatch: got {:#018x}", det_atan2(y, x).to_bits()
);
}
}
#[test]
fn pin_det_hypot_bits() {
let cases: &[(f64, f64, u64)] = &[
(3.0, 4.0, 0x4014000000000000), (1.0, 1.0, 0x3ff6a09e667f3bcd), (0.1, 0.2, 0x3fcc9f25c5bfedda),
];
for &(x, y, expected) in cases {
assert_eq!(
det_hypot(x, y).to_bits(), expected,
"det_hypot({x},{y}) bit mismatch: got {:#018x}", det_hypot(x, y).to_bits()
);
}
}
#[test]
fn pin_idct_cosine_table() {
let entries: &[(usize, usize, u64)] = &[
(0, 0, 0x3ff0000000000000), (0, 4, 0x3ff0000000000000), (1, 0, 0x3fef6297cff75cb0),
(1, 1, 0x3fea9b66290ea1a3),
(2, 3, 0xbfed906bcf328d46),
(3, 7, 0xbfea9b66290ea1a2),
(5, 2, 0x3fc8f8b83c69a60b),
(7, 7, 0xbfc8f8b83c69a5d6),
];
for &(u, x, expected) in entries {
let val = det_cos((2 * x + 1) as f64 * u as f64 * std::f64::consts::PI / 16.0);
assert_eq!(
val.to_bits(), expected,
"cosine[{u}][{x}] bit mismatch: got {:#018x}", val.to_bits()
);
}
}
#[test]
fn pin_fft_2x2_output() {
let pixels = vec![1.0, 2.0, 3.0, 4.0];
let spectrum = fft2d::fft2d(&pixels, 2, 2);
let expected_re = [0x41200000u32, 0xc0000000, 0xc0800000, 0x00000000];
let expected_im = [0x00000000u32, 0x00000000, 0x00000000, 0x00000000];
for (i, c) in spectrum.data.iter().enumerate() {
assert_eq!(
c.re.to_bits(), expected_re[i],
"FFT[{i}].re bit mismatch: got {:#010x}", c.re.to_bits()
);
assert_eq!(
c.im.to_bits(), expected_im[i],
"FFT[{i}].im bit mismatch: got {:#010x}", c.im.to_bits()
);
}
}
#[test]
fn fft_roundtrip_deterministic() {
let w = 16;
let h = 12; let pixels: Vec<f64> = (0..w * h).map(|i| 100.0 + (i as f64) * 0.73).collect();
let spectrum = fft2d::fft2d(&pixels, w, h);
let recovered = fft2d::ifft2d(&spectrum);
for i in 0..pixels.len() {
assert!(
(pixels[i] - recovered[i]).abs() < 0.1,
"FFT roundtrip mismatch at [{i}]: {:.6} vs {:.6}",
pixels[i], recovered[i]
);
}
}
#[test]
fn pin_template_peaks() {
let peaks = generate_template_peaks("determinism-test", 256, 256).unwrap();
assert_eq!(peaks.len(), 32);
let expected: &[(u64, u64)] = &[
(0x4030685798345b94, 0xc02cf16958a18bcc),
(0xc020723de9ad4fd4, 0xc023ddccdc1eb324),
(0x403504dc96d68438, 0x402bab49b7c2b4d2),
(0x403b7eb6a3415fe5, 0x4033d431bc2293d7),
(0x40366bc957e159d9, 0x403a9705f70d67ef),
];
for (i, &(eu, ev)) in expected.iter().enumerate() {
assert_eq!(
peaks[i].u.to_bits(), eu,
"peak[{i}].u bit mismatch: got {:#018x}", peaks[i].u.to_bits()
);
assert_eq!(
peaks[i].v.to_bits(), ev,
"peak[{i}].v bit mismatch: got {:#018x}", peaks[i].v.to_bits()
);
}
}
#[test]
fn template_peaks_deterministic() {
let a = generate_template_peaks("det-test-2", 512, 384).unwrap();
let b = generate_template_peaks("det-test-2", 512, 384).unwrap();
for i in 0..a.len() {
assert_eq!(a[i].u.to_bits(), b[i].u.to_bits(), "peak[{i}].u differs");
assert_eq!(a[i].v.to_bits(), b[i].v.to_bits(), "peak[{i}].v differs");
}
}
#[test]
fn sincos_pythagorean_identity_full_range() {
for i in 0..1000 {
let x = (i as f64 - 500.0) * 0.013;
let (s, c) = det_sincos(x);
let err = (s * s + c * c - 1.0).abs();
assert!(err < 1e-14, "sin²+cos² = {} at x={x} (err={err})", s * s + c * c);
}
}