//! Canonical test vectors locking the byte output of every ihi primitive.
//!
//! These tests are the cross-implementation contract — if a Rust ihi consumer, a future C/JS/wasm port, PIPE silicon, or anyone else claims to implement ihi, they must produce these exact bytes for these exact inputs. Any future change to ihi, its dependencies (blake3, sha2, sha3, i256), or rustc that alters output bytes will fail the test immediately and loudly.
//!
//! Vectors were generated by `cargo run --release --example gen_test_vectors` against ihi v0.0.43. The v0.0.43 vectors differ from v0.0.42 for handle_to_proof and handle_to_filename because the pre-hash now wraps the handle in VsfType::x before BLAKE3, restoring the canonical Unicode-safe pipeline that was present in photon from day one but was incorrectly removed during the ihi extraction. Re-running the generator should always produce identical output; if it doesn't, something silent broke determinism — diagnose before changing the asserts.
//!
//! Vectors remain valid for ihi 0.0.44 + vsf 0.4.0 (the NFC fix): the test inputs that
//! contain non-ASCII characters (café, 中文, 🚀) all happen to be NFC-form in this source
//! file, and NFC normalization is a no-op on already-NFC input. The fix is observable in
//! the new `*_nfc_nfd_equivalence_*` tests that explicitly compare NFC vs NFD input
//! variants of the same logical text — those would fail loudly on any pre-0.4.0 vsf.
//!
//! `chaos_amp` test vectors additionally serve as the silicon co-verification contract — PIPE's Verilog at `/mnt/Octopus/Code/pipe/rtl/chaos_amp_v2.v` must produce these exact 48 output bytes for these exact 64 input bytes. Software-silicon byte divergence anywhere here is a unification break.
//!
//! Diversity rationale: tests cover (a) the empty input edge case, (b) single bytes at both extremes (0x00 and 0xFF) to catch alignment / boundary bugs, (c) typical short and longer inputs, (d) UTF-8 multi-byte sequences (accented Latin, CJK, emoji) for unicode-byte handling, (e) inputs that start with the old VSF type marker bytes (`l`, `x`, `a`) to confirm the proof is byte-independent of any prior VSF-encoded path, and (f) repeating-byte patterns to catch collision-resistance regressions.
//!
//! The handle_proof / handle_to_proof / handle_to_filename tests run the ~1s memory-hard PoW per vector — full suite takes roughly ~13s in `--release`. They are NOT `#[ignore]`d because catching a determinism break is the entire point of having them.

use blake3;
use ihi::{
    chaos_amp, handle_proof, handle_to_filename, handle_to_hash, handle_to_proof,
    proof_to_filename, smear_hash, smear_hash_parts, spaghettify,
};

#[test]
fn handle_to_proof_vectors() {
    let vectors: &[(&str, [u8; 32])] = &[
        ("", [0x8f, 0x19, 0x3b, 0x31, 0xca, 0x35, 0xc9, 0xc2, 0x7f, 0x7a, 0x37, 0xca, 0xaa, 0x67, 0x53, 0x5b, 0xb2, 0x6a, 0x7b, 0xd9, 0x11, 0xaf, 0xdf, 0xe0, 0x8d, 0x84, 0x6c, 0xee, 0x65, 0x59, 0x84, 0x93]),
        ("a", [0x86, 0x8f, 0x5d, 0x48, 0xc4, 0x0b, 0x92, 0xd4, 0x94, 0xe5, 0x66, 0x41, 0x4a, 0x27, 0xd9, 0x78, 0xfb, 0x06, 0xf0, 0x98, 0xd8, 0x22, 0x26, 0x93, 0xb4, 0x6f, 0xe1, 0x12, 0xa1, 0x36, 0xb1, 0x96]),
        ("alice", [0x4f, 0x6e, 0x01, 0x11, 0xe8, 0x58, 0xf7, 0x39, 0x48, 0x0b, 0xdc, 0x46, 0xef, 0xb8, 0x40, 0xa3, 0xbf, 0x60, 0xf8, 0x33, 0xcf, 0x48, 0x9f, 0x4e, 0x8c, 0x92, 0x99, 0x4c, 0xfe, 0xcf, 0x5c, 0xa5]),
        ("fractal decoder", [0x75, 0x83, 0xb4, 0x95, 0x57, 0x9f, 0xc2, 0x0c, 0x69, 0x3e, 0x94, 0x2b, 0xea, 0x1b, 0x28, 0x3d, 0x5e, 0xd5, 0x07, 0x5e, 0x6c, 0xa2, 0x98, 0xa3, 0x60, 0x3d, 0x25, 0x9b, 0x07, 0x81, 0xf1, 0xc2]),
        ("12345", [0x18, 0x87, 0x28, 0xfa, 0x99, 0x94, 0x9f, 0x95, 0x49, 0x2c, 0x70, 0x7b, 0x6e, 0xc7, 0x1c, 0x63, 0x86, 0x7a, 0x0a, 0xea, 0xd3, 0x73, 0xb5, 0x41, 0x59, 0x5e, 0x02, 0xde, 0xa4, 0x59, 0xbb, 0xbf]),
        ("Photon-Messenger", [0x38, 0x09, 0xee, 0xf2, 0xdb, 0x0d, 0xd7, 0xea, 0xa4, 0x0a, 0x3a, 0x1c, 0x2a, 0x70, 0xa8, 0x5a, 0xa8, 0xd5, 0x3b, 0x62, 0x0f, 0xdc, 0x0b, 0xc6, 0x22, 0xa8, 0xb6, 0x51, 0xf8, 0x15, 0x20, 0x75]),
        ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", [0x05, 0x4c, 0x0c, 0x39, 0xce, 0x05, 0x34, 0x38, 0x4a, 0xf0, 0xde, 0x16, 0xda, 0x2b, 0x2f, 0xb7, 0x6b, 0x48, 0x94, 0xc9, 0xf1, 0x22, 0xda, 0x94, 0x21, 0x91, 0x89, 0x84, 0xdc, 0x66, 0x48, 0x5e]),
        ("lemming", [0x2b, 0xea, 0x73, 0x9b, 0x11, 0x67, 0xb2, 0x24, 0x0e, 0x43, 0x87, 0x38, 0xf9, 0xe0, 0x43, 0x6c, 0xe3, 0x7f, 0xee, 0xa8, 0xa5, 0x34, 0x3c, 0x03, 0xb8, 0xee, 0x96, 0x3f, 0xc8, 0xcf, 0xd7, 0x5f]),
        ("xenon", [0xb3, 0xe6, 0x7e, 0xf4, 0x46, 0xae, 0x7f, 0x61, 0x8d, 0xda, 0xee, 0x90, 0x2d, 0x01, 0xaa, 0x3f, 0x36, 0x55, 0x09, 0x20, 0x10, 0x75, 0xc3, 0xe7, 0x8a, 0x3d, 0x41, 0xe2, 0x60, 0xf3, 0x18, 0x4e]),
        ("atrium", [0x3a, 0x9c, 0xf6, 0xff, 0xd8, 0x72, 0x3f, 0xff, 0x77, 0x86, 0xc9, 0x3a, 0x46, 0x11, 0x86, 0x0f, 0x1b, 0xb0, 0x3f, 0xa5, 0xf6, 0x29, 0xdf, 0x17, 0x83, 0x04, 0x80, 0xac, 0x07, 0x5a, 0x3e, 0x9c]),
        ("café", [0xf2, 0xe7, 0x3c, 0x53, 0xe3, 0x8a, 0x14, 0x96, 0x00, 0x5a, 0x34, 0xeb, 0x48, 0x54, 0x61, 0xb9, 0xcc, 0x22, 0xa9, 0xdd, 0xc8, 0x0e, 0x8a, 0x8d, 0xc5, 0xe7, 0xeb, 0x71, 0x53, 0x0b, 0x4e, 0xe9]),
        ("中文", [0xd1, 0xae, 0xfc, 0xa7, 0x2b, 0xf9, 0x47, 0xba, 0x00, 0xcb, 0x82, 0x45, 0x2d, 0x91, 0xc4, 0x1c, 0x25, 0xe1, 0xf4, 0xc3, 0xcc, 0xde, 0x06, 0x37, 0x77, 0x4f, 0x57, 0x7a, 0xf2, 0xd8, 0xda, 0x9c]),
        ("🚀", [0x4d, 0x01, 0x75, 0x27, 0xbf, 0x8b, 0xdb, 0x3e, 0xf9, 0x71, 0x6f, 0x0c, 0xcb, 0x40, 0xfd, 0x06, 0x7b, 0xa6, 0xfa, 0x86, 0x64, 0x50, 0xeb, 0x35, 0x0b, 0x81, 0x8e, 0x30, 0xd3, 0xcb, 0x9b, 0x6c]),
    ];
    for (input, expected) in vectors {
        let actual = handle_to_proof(input);
        assert_eq!(actual.as_bytes(), expected, "handle_to_proof({input:?})");
    }
}

#[test]
fn handle_to_filename_vectors() {
    let vectors: &[(&str, &str)] = &[
        ("", "jxk7Mco1ycJ_ejfKqmdTW7Jqe9kRr9_gjYRs7mVZhJM.vsf"),
        ("a", "ho9dSMQLktSU5WZBSifZePsG8JjYIiaTtG_hEqE2sZY.vsf"),
        ("alice", "T24BEehY9zlIC9xG77hAo79g-DPPSJ9OjJKZTP7PXKU.vsf"),
        ("fractal decoder", "dYO0lVefwgxpPpQr6hsoPV7VB15sopijYD0lmweB8cI.vsf"),
        ("12345", "GIco-pmUn5VJLHB7bsccY4Z6CurTc7VBWV4C3qRZu78.vsf"),
        ("Photon-Messenger", "OAnu8tsN1-qkCjocKnCoWqjVO2IP3AvGIqi2UfgVIHU.vsf"),
        ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "BUwMOc4FNDhK8N4W2isvt2tIlMnxItqUIZGJhNxmSF4.vsf"),
        ("lemming", "K-pzmxFnsiQOQ4c4-eBDbON_7qilNDwDuO6WP8jP118.vsf"),
        ("xenon", "s-Z-9Eauf2GN2u6QLQGqPzZVCSAQdcPnij1B4mDzGE4.vsf"),
        ("atrium", "Opz2_9hyP_93hsk6RhGGDxuwP6X2Kd8XgwSArAdaPpw.vsf"),
        ("café", "8uc8U-OKFJYAWjTrSFRhucwiqd3IDoqNxefrcVMLTuk.vsf"),
        ("中文", "0a78pyv5R7oAy4JFLZHEHCXh9MPM3gY3d09XevLY2pw.vsf"),
        ("🚀", "TQF1J7-L2z75cW8My0D9Bnum-oZkUOs1C4GOMNPLm2w.vsf"),
    ];
    for (input, expected) in vectors {
        let actual = handle_to_filename(input);
        assert_eq!(&actual, expected, "handle_to_filename({input:?})");
    }
}

#[test]
fn handle_proof_raw_hash_vectors() {
    // `handle_proof` takes a raw BLAKE3 hash and runs the memory-hard PoW. This is the lower-level primitive — `handle_to_proof` wraps input in VsfType::x before hashing, so the two paths produce DIFFERENT bytes for the same string input. These vectors lock the raw primitive's output independently.
    let inputs: &[&[u8]] = &[b"", b"alice", b"\x00".as_ref(), b"\xff".as_ref()];
    let expected: &[[u8; 32]] = &[
        [0xd5, 0x24, 0x7b, 0xb2, 0x94, 0xd0, 0xae, 0x1f, 0x91, 0xb4, 0x2b, 0x69, 0x01, 0xd6, 0xab, 0x2a, 0xfe, 0xdb, 0x5f, 0x11, 0x41, 0x36, 0x96, 0x0d, 0x19, 0x49, 0xd5, 0x2c, 0xb2, 0x34, 0x7d, 0xc3],
        [0xdf, 0x72, 0x16, 0x0a, 0x62, 0x3e, 0x14, 0xaf, 0x1a, 0x16, 0x74, 0x9b, 0x7c, 0xef, 0x79, 0xe4, 0x19, 0x50, 0x5c, 0x19, 0x65, 0xaa, 0xdb, 0xfd, 0x71, 0x17, 0x34, 0x52, 0xa6, 0x17, 0xf5, 0x85],
        [0x3d, 0xa7, 0xbf, 0x90, 0x11, 0x44, 0x4a, 0x4e, 0x47, 0x74, 0xb7, 0x18, 0x4e, 0x25, 0x6a, 0x97, 0xfd, 0xda, 0x5d, 0x84, 0xb3, 0xe9, 0x62, 0xe1, 0xed, 0x95, 0x81, 0x38, 0x55, 0x75, 0xee, 0xc8],
        [0x58, 0x7a, 0x07, 0x5c, 0xa5, 0xbb, 0x53, 0x76, 0xc9, 0xb6, 0x53, 0xfe, 0x3e, 0x19, 0x9c, 0xd1, 0xff, 0x12, 0x74, 0x2f, 0x71, 0xc1, 0x03, 0x24, 0x74, 0x90, 0x20, 0x6e, 0xa4, 0x84, 0xa9, 0x06],
    ];
    for (input, expected) in inputs.iter().zip(expected.iter()) {
        let hash = blake3::hash(input);
        let actual = handle_proof(&hash);
        assert_eq!(actual.as_bytes(), expected, "handle_proof(blake3({input:?}))");
    }
}

#[test]
fn proof_to_filename_vectors() {
    // 32-byte proof → 43-char base64url + ".vsf". All-zero and all-one inputs lock the base64url alphabet (no padding, no `=`, uses `-` and `_` for the 62/63 positions).
    let vectors: &[([u8; 32], &str)] = &[
        ([0u8; 32], "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.vsf"),
        ([0xFF; 32], "__________________________________________8.vsf"),
        ([0x71, 0xb2, 0x78, 0xf3, 0xdc, 0x43, 0x44, 0x47, 0xfc, 0x62, 0x05, 0x00, 0xe4, 0x7b, 0x6a, 0x80, 0xb0, 0xcb, 0x0d, 0xf7, 0x6a, 0x10, 0x51, 0x11, 0x9f, 0xe1, 0x9e, 0xd4, 0x95, 0x32, 0x42, 0xdf],
         "cbJ489xDREf8YgUA5HtqgLDLDfdqEFERn-Ge1JUyQt8.vsf"),
    ];
    for (proof_bytes, expected_filename) in vectors {
        let proof = blake3::Hash::from(*proof_bytes);
        let actual = proof_to_filename(&proof);
        assert_eq!(&actual, expected_filename, "proof_to_filename({proof_bytes:02x?})");
    }
}

#[test]
fn smear_hash_vectors() {
    let vectors: &[(&[u8], [u8; 32])] = &[
        (&[], [0xc7, 0x6f, 0x6e, 0x74, 0x34, 0x08, 0xce, 0x7d, 0x00, 0xd5, 0x22, 0xec, 0x40, 0xd0, 0x9f, 0x2c, 0xb8, 0x6b, 0x3e, 0x81, 0x42, 0xad, 0x4e, 0x91, 0xcd, 0xb6, 0x30, 0xa0, 0xb7, 0x8b, 0x98, 0xe6]),
        (&[0x00], [0xc8, 0x4d, 0xd5, 0x42, 0x58, 0x64, 0x43, 0x9a, 0x09, 0x46, 0x00, 0xd7, 0x53, 0x01, 0x4c, 0xd0, 0x7c, 0x8a, 0xc5, 0xe1, 0xf2, 0xdf, 0x89, 0x35, 0xe2, 0xe2, 0xed, 0xdf, 0xd9, 0x96, 0x18, 0xe1]),
        (&[0xFF], [0xba, 0x9f, 0x1b, 0xbd, 0xb5, 0x4d, 0xd1, 0x2a, 0xd5, 0x4c, 0x0b, 0x9c, 0x30, 0x06, 0x6f, 0x17, 0xc4, 0xcf, 0x38, 0xe2, 0xde, 0xff, 0x64, 0x71, 0x41, 0x8d, 0x70, 0x19, 0xaa, 0x71, 0xb6, 0xcf]),
        (&[0x00; 32], [0xe4, 0xfe, 0xbf, 0x97, 0x56, 0x86, 0x26, 0x2d, 0xe1, 0xc2, 0x5f, 0xe9, 0xdb, 0xe4, 0x4b, 0x72, 0x88, 0x11, 0x82, 0x31, 0xb1, 0xcc, 0xb8, 0x7d, 0x9a, 0xcb, 0x53, 0x81, 0xbf, 0x9c, 0x93, 0x02]),
        (&[0xFF; 32], [0xbd, 0x14, 0x0b, 0x24, 0xcf, 0x60, 0x59, 0xd2, 0x73, 0x34, 0xbd, 0x69, 0xcd, 0x7e, 0xa7, 0x99, 0x8f, 0x41, 0x17, 0x01, 0x7f, 0xf3, 0x58, 0xc4, 0xc7, 0xee, 0xa6, 0xf5, 0x07, 0xc2, 0xe8, 0xdb]),
        (&[0xAA, 0x55, 0xAA, 0x55], [0x66, 0xd7, 0x42, 0xf0, 0x96, 0x84, 0x6d, 0x73, 0xee, 0x86, 0x6f, 0xc6, 0x2a, 0x93, 0xff, 0x56, 0xbe, 0x6b, 0xe7, 0xea, 0x41, 0x59, 0xd8, 0x6b, 0x61, 0x46, 0x9e, 0x53, 0x04, 0x78, 0x71, 0x2d]),
        (b"alice", [0x96, 0xe5, 0xb0, 0xba, 0x3a, 0x1b, 0x8e, 0x32, 0x74, 0x5f, 0x88, 0x71, 0x71, 0x26, 0x32, 0x4f, 0x29, 0xe2, 0xce, 0x0c, 0xf8, 0xb2, 0x48, 0xfa, 0x01, 0x1a, 0x67, 0xdf, 0xdc, 0xd4, 0xc8, 0xf5]),
        (b"the quick brown fox jumps over the lazy dog", [0x78, 0xe3, 0xdb, 0xa4, 0xf4, 0x5d, 0x7b, 0x30, 0xb7, 0xb8, 0x8d, 0x20, 0x27, 0x8b, 0xa7, 0xb6, 0x0f, 0xcb, 0x5f, 0x85, 0xc7, 0x94, 0x5a, 0xe2, 0xd6, 0xc9, 0x22, 0xc2, 0xe9, 0x1c, 0x34, 0x05]),
    ];
    for (input, expected) in vectors {
        let actual = smear_hash(input);
        assert_eq!(&actual, expected, "smear_hash({input:02x?})");
    }
}

#[test]
fn smear_hash_parts_vectors() {
    // smear_hash_parts(&[]) == smear_hash_parts(&[b""]) — both encode "no content"; locked here so a future implementation can't separate these into distinct outputs without failing.
    let vectors: &[(&[&[u8]], [u8; 32])] = &[
        (&[], [0xc7, 0x6f, 0x6e, 0x74, 0x34, 0x08, 0xce, 0x7d, 0x00, 0xd5, 0x22, 0xec, 0x40, 0xd0, 0x9f, 0x2c, 0xb8, 0x6b, 0x3e, 0x81, 0x42, 0xad, 0x4e, 0x91, 0xcd, 0xb6, 0x30, 0xa0, 0xb7, 0x8b, 0x98, 0xe6]),
        (&[b""], [0xc7, 0x6f, 0x6e, 0x74, 0x34, 0x08, 0xce, 0x7d, 0x00, 0xd5, 0x22, 0xec, 0x40, 0xd0, 0x9f, 0x2c, 0xb8, 0x6b, 0x3e, 0x81, 0x42, 0xad, 0x4e, 0x91, 0xcd, 0xb6, 0x30, 0xa0, 0xb7, 0x8b, 0x98, 0xe6]),
        (&[b"alice"], [0x96, 0xe5, 0xb0, 0xba, 0x3a, 0x1b, 0x8e, 0x32, 0x74, 0x5f, 0x88, 0x71, 0x71, 0x26, 0x32, 0x4f, 0x29, 0xe2, 0xce, 0x0c, 0xf8, 0xb2, 0x48, 0xfa, 0x01, 0x1a, 0x67, 0xdf, 0xdc, 0xd4, 0xc8, 0xf5]),
        (&[b"alice", b"bob"], [0xb3, 0xb5, 0x00, 0x03, 0x02, 0xd9, 0x99, 0xe5, 0x31, 0x76, 0x93, 0x03, 0xe5, 0xc9, 0x16, 0xb6, 0x65, 0xa5, 0xed, 0xb1, 0x7d, 0x94, 0x97, 0x25, 0xaf, 0x82, 0x84, 0x4a, 0xc8, 0x05, 0x03, 0xea]),
        (&[b"alice", b"bob", b"carol"], [0xd4, 0x8f, 0x60, 0xa6, 0x66, 0x26, 0x76, 0x71, 0xa5, 0xe8, 0xce, 0x4e, 0xa7, 0xd2, 0xdb, 0x49, 0x32, 0x7e, 0xc7, 0xfc, 0xd5, 0x2d, 0x84, 0x0e, 0xb6, 0xe9, 0x60, 0x35, 0x3d, 0x59, 0x0a, 0x82]),
        (&[&[0x00; 16], &[0xFF; 16]], [0x0d, 0x45, 0x91, 0xd7, 0x43, 0xb7, 0xf7, 0x48, 0xdf, 0x49, 0x06, 0x0d, 0x65, 0x00, 0xd6, 0x15, 0xee, 0x1e, 0x30, 0x35, 0xa3, 0xa7, 0x09, 0x43, 0x74, 0xfc, 0x57, 0x4a, 0x68, 0x6b, 0x79, 0x27]),
    ];
    for (parts, expected) in vectors {
        let actual = smear_hash_parts(parts);
        assert_eq!(&actual, expected, "smear_hash_parts({parts:?})");
    }
}

#[test]
fn spaghettify_vectors() {
    // New byte contract as of ihi 0.0.42 (the version-number jump from 0.0.3 deliberately signals the cryptographic discontinuity): the chaos layer is `chaos_amp` (bit-exact with PIPE silicon), wrapped by BLAKE3-XOF absorb and smear_hash finalize. Prior ihi versions 0.0.0–0.0.3 used a spirix-based chaos layer that produced different bytes AND drifted between published versions; all four were yanked and a full-unpublish request was filed with crates.io. Those vectors are dead and cannot be revived without an algorithm revert.
    let vectors: &[(&[u8], [u8; 32])] = &[
        (&[], [0xc2, 0x26, 0xc6, 0x16, 0x0d, 0xd9, 0xd8, 0x31, 0xc0, 0xb2, 0x44, 0x82, 0x27, 0x66, 0x96, 0x5d, 0x3f, 0xad, 0x78, 0xe7, 0xda, 0xda, 0x98, 0x21, 0x33, 0x5d, 0xa7, 0xcb, 0x61, 0x77, 0x31, 0xd8]),
        (&[0x00], [0x38, 0x98, 0x7c, 0xcb, 0x1e, 0xc2, 0x0f, 0xfd, 0xd9, 0x80, 0xe1, 0x4d, 0x10, 0x82, 0xc2, 0x95, 0x88, 0x11, 0xe3, 0xb1, 0xec, 0xf6, 0xb3, 0x78, 0x41, 0x6a, 0x70, 0x89, 0x81, 0xa7, 0x90, 0x68]),
        (&[0xFF], [0xcd, 0xa0, 0xcd, 0xcc, 0xd0, 0x3e, 0x43, 0x63, 0x3c, 0x23, 0x4b, 0x69, 0xa3, 0x63, 0xa3, 0x52, 0x67, 0xd7, 0xdb, 0x54, 0x9d, 0x8d, 0x22, 0x0a, 0x51, 0x60, 0x18, 0x46, 0xfc, 0xef, 0x03, 0x9e]),
        (&[0x00; 32], [0x80, 0x63, 0x0b, 0xea, 0x59, 0x81, 0xa0, 0x51, 0x8b, 0x55, 0x59, 0x5d, 0x5e, 0xc4, 0x61, 0xc2, 0xc0, 0xcd, 0x3b, 0x08, 0xb5, 0x8a, 0xde, 0xa6, 0xee, 0x55, 0x1b, 0xf2, 0xa5, 0x58, 0x08, 0xd3]),
        (&[0xFF; 32], [0xaf, 0x1b, 0xc8, 0x41, 0x5a, 0x09, 0xcb, 0x50, 0x47, 0x08, 0xc3, 0xe2, 0x90, 0x09, 0xf4, 0xe0, 0x2d, 0x88, 0x5f, 0x48, 0xc7, 0x83, 0xbd, 0x4b, 0xf8, 0x96, 0x7d, 0x3a, 0xbf, 0x87, 0x8a, 0x99]),
        (&[0xAA, 0x55, 0xAA, 0x55], [0x36, 0xb2, 0x1b, 0x80, 0x52, 0x69, 0x23, 0xf5, 0xb4, 0xb0, 0x19, 0xf1, 0x54, 0x48, 0x9d, 0x32, 0x75, 0x67, 0x96, 0xa7, 0x83, 0x22, 0x2f, 0x17, 0x66, 0x55, 0x3b, 0x83, 0x30, 0xdb, 0x06, 0x48]),
        (b"alice", [0x83, 0x48, 0x86, 0x77, 0xc9, 0xf7, 0xfa, 0x9b, 0x41, 0xa1, 0x9a, 0x70, 0xe2, 0x48, 0x11, 0x97, 0x35, 0x75, 0x69, 0x2c, 0x95, 0x97, 0xf2, 0x7c, 0x88, 0x41, 0xe9, 0xd2, 0x2a, 0x84, 0xb0, 0x3f]),
        (b"the quick brown fox jumps over the lazy dog", [0x9d, 0xef, 0x8d, 0x82, 0x7a, 0x0a, 0xb2, 0x2a, 0xfe, 0x4e, 0xa4, 0x71, 0x8b, 0xe0, 0x9a, 0x38, 0xc8, 0xa2, 0x1e, 0x39, 0xcd, 0x69, 0x96, 0xd3, 0xc9, 0xb8, 0x8b, 0xe8, 0x5b, 0xd0, 0x39, 0xe9]),
    ];
    for (input, expected) in vectors {
        let actual = spaghettify(input);
        assert_eq!(&actual, expected, "spaghettify({input:02x?})");
    }
}

#[test]
fn chaos_amp_vectors() {
    // Bit-exact silicon co-verification contract. PIPE's Verilog at /mnt/Octopus/Code/pipe/rtl/chaos_amp_v2.v must produce these exact 48 output bytes for these exact 64 input bytes. Software-silicon byte divergence here is a unification break.
    //
    // Inputs: 0: [0u8; 64]                                — catches missing input absorption 1: [0xFFu8; 64]                             — catches missing input absorption inverse / sign-extension bugs 2: blake3("alice") repeated to 64 bytes     — typical avalanche case, structured (two halves identical) 3: blake3("photon") repeated to 64 bytes    — second avalanche case, distinct from (2) to catch input-dependent regressions
    let mut alice_block = [0u8; 64];
    let alice_h = blake3::hash(b"alice");
    alice_block[..32].copy_from_slice(alice_h.as_bytes());
    alice_block[32..].copy_from_slice(alice_h.as_bytes());

    let mut photon_block = [0u8; 64];
    let photon_h = blake3::hash(b"photon");
    photon_block[..32].copy_from_slice(photon_h.as_bytes());
    photon_block[32..].copy_from_slice(photon_h.as_bytes());

    let vectors: &[(&str, [u8; 64], [u8; 48])] = &[
        ("[0u8; 64]", [0u8; 64], [0x55, 0xb7, 0x2a, 0x28, 0x04, 0x56, 0x11, 0x55, 0x38, 0x9b, 0x20, 0x87, 0x0c, 0xc7, 0x12, 0xa4, 0x88, 0xb8, 0x91, 0xfd, 0xc8, 0xee, 0xd1, 0x02, 0x5b, 0x23, 0x28, 0xdc, 0x88, 0x3c, 0x74, 0x8e, 0x2f, 0x17, 0x24, 0xca, 0xc6, 0xa0, 0x6f, 0x82, 0x5a, 0x99, 0xdd, 0x1e, 0x5a, 0xbc, 0x4a, 0x54]),
        ("[0xFFu8; 64]", [0xFFu8; 64], [0x04, 0x94, 0x59, 0x06, 0xab, 0x66, 0xf1, 0x3d, 0x66, 0x99, 0xde, 0xe9, 0xef, 0xce, 0xf8, 0xf2, 0x58, 0x3c, 0x3b, 0x51, 0x6c, 0xa8, 0xee, 0x91, 0xd0, 0x32, 0xcb, 0x4b, 0x4c, 0x32, 0xcb, 0xf8, 0x40, 0x23, 0x0a, 0x0e, 0x40, 0x2c, 0x17, 0x5e, 0x41, 0xbc, 0xc2, 0x4f, 0xf7, 0xf7, 0x83, 0x5e]),
        ("blake3(\"alice\") x2", alice_block, [0x16, 0x3f, 0xaa, 0x54, 0x78, 0xca, 0x1f, 0xd2, 0x1f, 0x5b, 0x71, 0xc2, 0xc9, 0xaf, 0xd1, 0xc3, 0x21, 0x87, 0xf8, 0x30, 0x29, 0x7b, 0x02, 0xaa, 0x6d, 0x3d, 0xff, 0x3e, 0x2e, 0x4e, 0x56, 0x61, 0xa1, 0x48, 0x6f, 0xf5, 0x70, 0xc1, 0x08, 0xb1, 0x8b, 0xa9, 0x83, 0x0b, 0x38, 0xc6, 0xdd, 0xe0]),
        ("blake3(\"photon\") x2", photon_block, [0xa7, 0x4d, 0x5d, 0x0d, 0x26, 0xea, 0x89, 0xab, 0xae, 0xb9, 0x98, 0xf6, 0x08, 0x6f, 0x4a, 0x6a, 0x71, 0x81, 0x7e, 0x96, 0x4e, 0xea, 0x44, 0x4a, 0x13, 0xfa, 0xf0, 0x38, 0x11, 0x96, 0x11, 0x65, 0x7f, 0x86, 0xb0, 0xec, 0x7d, 0xa7, 0x91, 0xc6, 0x98, 0x4a, 0x8e, 0xca, 0xcb, 0x69, 0xa6, 0xbb]),
    ];
    for (label, input, expected) in vectors {
        let actual = chaos_amp(input);
        assert_eq!(&actual, expected, "chaos_amp({label})");
    }
}

#[test]
fn chaos_amp_round_consts_match_blake3() {
    // chaos_amp ROUND_CONSTS are hardcoded for no_std compatibility (BLAKE3 can't run at const-time). This test re-derives them at runtime from `blake3("PIPE chaos amp v2 - Dragonfly")` and `blake3("PIPE chaos amp v2 - Dragonfly - more")` and confirms they match the hardcoded values bit-for-bit. The seed strings are part of the cryptographic contract — renaming them silently changes every output byte and breaks software-silicon unification.
    let h1 = blake3::hash(b"PIPE chaos amp v2 - Dragonfly");
    let h2 = blake3::hash(b"PIPE chaos amp v2 - Dragonfly - more");
    let b1 = h1.as_bytes();
    let b2 = h2.as_bytes();
    let mut expected = [0u32; 16];
    for i in 0..8 {
        expected[i] = u32::from_le_bytes([b1[i * 4], b1[i * 4 + 1], b1[i * 4 + 2], b1[i * 4 + 3]]);
        expected[i + 8] =
            u32::from_le_bytes([b2[i * 4], b2[i * 4 + 1], b2[i * 4 + 2], b2[i * 4 + 3]]);
    }
    let actual = ihi::_round_consts_for_test();
    assert_eq!(
        actual, expected,
        "chaos_amp ROUND_CONSTS drifted from their BLAKE3(seed-string) derivation"
    );
}

#[test]
fn chaos_amp_deterministic_across_calls() {
    // Defensive: confirm chaos_amp produces identical bytes on repeated calls — no thread-local state, no internal randomness, no caching artifacts. If this fails, determinism is fundamentally broken.
    let block = [0x42u8; 64];
    let a = chaos_amp(&block);
    let b = chaos_amp(&block);
    assert_eq!(a, b, "chaos_amp must be a pure function of its 64-byte input");
}

#[test]
fn handle_to_proof_is_deterministic_across_calls() {
    // Defensive: confirm calling the same function twice in the same process produces identical bytes (no internal randomness, no thread-local state). If this ever fails, something's caching state between calls — fundamentally breaks the determinism contract.
    let a = handle_to_proof("photon");
    let b = handle_to_proof("photon");
    assert_eq!(a.as_bytes(), b.as_bytes(), "identical input must produce identical output every time");
}

#[test]
fn handle_to_hash_nfc_nfd_equivalence_e_acute() {
    // Headline guarantee, locked at the identity layer: the canonical pre-hash treats
    // U+00E9 (precomposed é) and "e\u{0301}" (e + combining acute) as the same logical
    // text. Pre-vsf 0.4.0 / pre-ihi 0.0.44 this property was claimed but NOT implemented —
    // the Huffman encoder iterated raw codepoints with no normalization, silently bifurcating
    // every NFD-form handle in production. Locking this assertion forever.
    let nfc = handle_to_hash("\u{00E9}");
    let nfd = handle_to_hash("e\u{0301}");
    assert_eq!(
        nfc.as_bytes(),
        nfd.as_bytes(),
        "NFC and NFD forms of é must hash identically"
    );
}

#[test]
fn handle_to_hash_nfc_nfd_equivalence_cafe() {
    let nfc = handle_to_hash("café");                  // precomposed é
    let nfd = handle_to_hash("cafe\u{0301}");          // e + combining acute
    assert_eq!(nfc.as_bytes(), nfd.as_bytes(), "café NFC vs NFD must hash identically");
}

#[test]
fn handle_to_hash_nfc_nfd_equivalence_naive() {
    // Second example to catch any future regression that special-cases é.
    let nfc = handle_to_hash("na\u{00EF}ve");          // precomposed ï
    let nfd = handle_to_hash("nai\u{0308}ve");         // i + combining diaeresis
    assert_eq!(nfc.as_bytes(), nfd.as_bytes(), "naïve NFC vs NFD must hash identically");
}

#[test]
fn handle_to_proof_nfc_nfd_equivalence_cafe() {
    // Full-pipeline assertion: the memory-hard PoW also produces the same proof, since the
    // PoW is a deterministic function of handle_to_hash. ~2s runtime (two ~1s PoWs).
    let nfc = handle_to_proof("café");
    let nfd = handle_to_proof("cafe\u{0301}");
    assert_eq!(
        nfc.as_bytes(),
        nfd.as_bytes(),
        "handle_to_proof must be NFC-stable end-to-end"
    );
}

#[test]
fn handle_to_proof_differs_for_byte_neighbors() {
    // Defensive collision-resistance smoke test: tiny input changes must produce dramatically different outputs. Hamming distance between two PoW outputs for one-byte-different inputs should be around 128 (half the bits flipped). Anything below ~80 suggests the avalanche property is degraded.
    let a = handle_to_proof("alice");
    let b = handle_to_proof("alicE");
    let hamming: u32 = a
        .as_bytes()
        .iter()
        .zip(b.as_bytes().iter())
        .map(|(x, y)| (x ^ y).count_ones())
        .sum();
    assert!(
        hamming > 80,
        "weak avalanche: only {hamming} bits differ between 'alice' and 'alicE'"
    );
}

#[test]
fn motuhake_pipeline_pin() {
    // Canonical end-to-end pipeline freeze: handle string → VsfType::x (NFC + Huffman) → BLAKE3 → memory-hard proof → base64url filename, pinned at every stage's output. "motuhake" (Māori: separate, independent — as in mana motuhake, self-determination) is the permanent frozen vector for the identity pipeline. Computed under ihi 0.0.45 + vsf 0.6.42 (the frozen encoder lane); must never change while the v8 x wire format stands. Downstream systems that bake derived constants (capsule filenames, provenance bytes) inherit exactly this pipeline — if this test fails, every such constant in the ecosystem has silently forked.
    let proof = handle_to_proof("motuhake");
    assert_eq!(
        proof.as_bytes(),
        &[
            0x64, 0x7e, 0xc3, 0x0d, 0x7d, 0x15, 0x4d, 0x40,
            0x07, 0xaa, 0xc0, 0x67, 0x64, 0x55, 0x97, 0x8b,
            0xac, 0x0b, 0xe7, 0x0c, 0xdd, 0xee, 0xc4, 0xd0,
            0x19, 0x05, 0x03, 0xca, 0x4c, 0xda, 0xf6, 0x43,
        ],
        "handle_to_proof(\"motuhake\") drifted — the identity pipeline is no longer byte-stable"
    );
    assert_eq!(
        proof_to_filename(&proof),
        "ZH7DDX0VTUAHqsBnZFWXi6wL5wzd7sTQGQUDykza9kM.vsf",
        "motuhake filename drifted — proof_to_filename is no longer byte-stable"
    );
}
