#![cfg(feature = "std")]
use dsfb_debug::error::DsfbError;
use dsfb_debug::types::*;
use dsfb_debug::DsfbDebugEngine;
use dsfb_debug::heuristics_bank::HeuristicsBank;
const LCG_SEED: u64 = 0x9E3779B97F4A7C15;
struct Lcg {
state: u64,
}
impl Lcg {
fn new(seed: u64) -> Self {
Self { state: seed }
}
fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.state
}
fn next_f64_signed(&mut self) -> f64 {
let bits = (self.next_u64() >> 11) as f64;
let frac = bits * (1.0 / (1u64 << 53) as f64); 2.0 * frac - 1.0 }
}
fn blank_eval() -> SignalEvaluation {
SignalEvaluation {
window_index: 0,
signal_index: 0,
residual_value: 0.0,
sign_tuple: SignTuple::ZERO,
raw_grammar_state: GrammarState::Admissible,
confirmed_grammar_state: GrammarState::Admissible,
reason_code: ReasonCode::Admissible,
motif: None,
semantic_disposition: SemanticDisposition::Unknown,
dsa_score: 0.0,
policy_state: PolicyState::Silent,
was_imputed: false,
drift_persistence: 0.0,
}
}
fn blank_episode() -> DebugEpisode {
DebugEpisode {
episode_id: 0,
start_window: 0,
end_window: 0,
peak_grammar_state: GrammarState::Admissible,
primary_reason_code: ReasonCode::Admissible,
matched_motif: SemanticDisposition::Unknown,
policy_state: PolicyState::Silent,
contributing_signal_count: 0,
structural_signature: StructuralSignature {
dominant_drift_direction: DriftDirection::None,
peak_slew_magnitude: 0.0,
duration_windows: 0,
signal_correlation: 0.0,
},
root_cause_signal_index: None,
}
}
#[test]
fn theorem9_holds_over_pseudorandom_inputs() {
const NUM_SAMPLES: usize = 32;
let engine = DsfbDebugEngine::<32, 64>::paper_lock()
.expect("engine creation should succeed");
let mut lcg = Lcg::new(LCG_SEED);
for sample in 0..NUM_SAMPLES {
let num_signals = (((lcg.next_u64() >> 32) % 8) + 2) as usize; let num_windows = (((lcg.next_u64() >> 32) % 200) + 110) as usize;
let total = num_signals * num_windows;
let mut data = vec![0.0_f64; total];
for cell in data.iter_mut() {
*cell = 100.0 + 10.0 * lcg.next_f64_signed();
}
let labels = vec![false; num_windows];
let mut eval1 = vec![blank_eval(); total];
let mut ep1 = vec![blank_episode(); 64];
let mut eval2 = vec![blank_eval(); total];
let mut ep2 = vec![blank_episode(); 64];
let r1 = engine.run_evaluation(
&data, num_signals, num_windows, &labels, num_windows / 4,
&mut eval1, &mut ep1, "theorem9_sample",
);
let r2 = engine.run_evaluation(
&data, num_signals, num_windows, &labels, num_windows / 4,
&mut eval2, &mut ep2, "theorem9_sample",
);
let (c1, m1) = r1.expect("first run should succeed");
let (c2, m2) = r2.expect("second run should succeed");
assert_eq!(c1, c2,
"Theorem 9 violated on sample {}: episode_count differs ({} vs {})",
sample, c1, c2);
assert_eq!(m1.dsfb_episode_count, m2.dsfb_episode_count,
"Theorem 9 violated on sample {}: dsfb_episode_count differs", sample);
assert_eq!(m1.raw_anomaly_count, m2.raw_anomaly_count,
"Theorem 9 violated on sample {}: raw_anomaly_count differs", sample);
for j in 0..c1 {
assert_eq!(ep1[j], ep2[j],
"Theorem 9 violated on sample {} episode {}", sample, j);
}
for j in 0..total {
assert_eq!(eval1[j], eval2[j],
"Theorem 9 violated on sample {} eval[{}]", sample, j);
}
}
}
#[test]
fn buffer_too_small_always_returned_for_oversized_inputs() {
let engine = DsfbDebugEngine::<512, 64>::paper_lock()
.expect("engine creation should succeed");
let mut lcg = Lcg::new(LCG_SEED ^ 0xDEADBEEF);
let combos: &[(usize, usize)] = &[
(16, 1024), (32, 256), (32, 257), (33, 256), (256, 33), (64, 130), (128, 65), (200, 50), (40, 256), (10, 1000), (4, 4096), (300, 50), ];
for &(num_signals, num_windows) in combos {
let total = num_signals * num_windows;
let mut data = vec![0.0_f64; total];
for cell in data.iter_mut() {
*cell = 100.0 + 10.0 * lcg.next_f64_signed();
}
let labels = vec![false; num_windows];
let mut eval = vec![blank_eval(); 1];
let mut ep = vec![blank_episode(); 1];
let r = engine.run_evaluation(
&data, num_signals, num_windows, &labels, num_windows / 4,
&mut eval, &mut ep, "oversized",
);
if total > 8192 {
match r {
Err(DsfbError::BufferTooSmall { needed, available }) => {
assert_eq!(needed, total,
"BufferTooSmall.needed should be num_signals*num_windows");
assert_eq!(available, 8192,
"BufferTooSmall.available should be 8192");
}
other => panic!(
"({}, {}): expected BufferTooSmall, got {:?}",
num_signals, num_windows, other,
),
}
} else {
assert!(
!matches!(r, Err(DsfbError::BufferTooSmall { .. })),
"({}, {}): {} <= 8192 should not trip BufferTooSmall",
num_signals, num_windows, total,
);
}
}
}
#[test]
fn no_orphan_reason_codes_in_canonical_bank() {
let bank = HeuristicsBank::<64>::with_canonical_motifs();
let testable: &[ReasonCode] = &[
ReasonCode::BoundaryApproach,
ReasonCode::SustainedOutwardDrift,
ReasonCode::AbruptSlewViolation,
ReasonCode::RecurrentBoundaryGrazing,
ReasonCode::EnvelopeViolation,
ReasonCode::DriftWithRecovery,
];
for rc in testable {
let got = bank.lookup(*rc, 1.0, 1.0);
match got {
SemanticDisposition::Named(_) => { }
SemanticDisposition::Unknown => panic!(
"Reason code {:?} matches no motif in canonical bank \
even at high drift + high slew. Motif coverage gap.",
rc,
),
}
}
}
#[test]
fn bank_lookup_is_deterministic() {
let bank = HeuristicsBank::<64>::with_canonical_motifs();
let mut lcg = Lcg::new(LCG_SEED ^ 0xCAFEBABE);
for _ in 0..256 {
let drift = lcg.next_f64_signed().abs(); let slew = lcg.next_f64_signed().abs(); let rc_idx = (lcg.next_u64() >> 56) as usize % 7;
let rc = match rc_idx {
0 => ReasonCode::BoundaryApproach,
1 => ReasonCode::SustainedOutwardDrift,
2 => ReasonCode::AbruptSlewViolation,
3 => ReasonCode::RecurrentBoundaryGrazing,
4 => ReasonCode::EnvelopeViolation,
5 => ReasonCode::DriftWithRecovery,
_ => ReasonCode::Admissible,
};
let r1 = bank.lookup(rc, drift, slew);
let r2 = bank.lookup(rc, drift, slew);
assert_eq!(r1, r2,
"bank.lookup non-deterministic on (rc={:?}, drift={}, slew={})",
rc, drift, slew);
}
}