use crate::types::{GrammarState, ReasonCode, SignTuple};
use crate::config::EngineConfig;
use crate::envelope;
#[inline]
pub fn evaluate_raw_grammar(
sign: &SignTuple,
rho: f64,
config: &EngineConfig,
drift_persistence: f64,
) -> (GrammarState, ReasonCode) {
if envelope::is_violation(sign.norm, rho) {
if sign.slew > config.slew_delta || sign.slew < -config.slew_delta {
return (GrammarState::Violation, ReasonCode::AbruptSlewViolation);
}
return (GrammarState::Violation, ReasonCode::EnvelopeViolation);
}
if envelope::is_boundary_zone(sign.norm, rho, config.boundary_fraction) {
if sign.drift > 0.0 && drift_persistence > 0.5 {
return (GrammarState::Boundary, ReasonCode::SustainedOutwardDrift);
}
if sign.slew > config.slew_delta || sign.slew < -config.slew_delta {
return (GrammarState::Boundary, ReasonCode::AbruptSlewViolation);
}
return (GrammarState::Boundary, ReasonCode::BoundaryApproach);
}
(GrammarState::Admissible, ReasonCode::Admissible)
}
#[inline]
pub fn hysteresis_confirm(
recent_raw_states: &[GrammarState],
n_confirm: usize,
) -> GrammarState {
if recent_raw_states.len() < n_confirm {
return GrammarState::Admissible;
}
let latest = recent_raw_states[recent_raw_states.len() - 1];
if latest == GrammarState::Violation {
return GrammarState::Violation;
}
if latest == GrammarState::Boundary {
let start = recent_raw_states.len() - n_confirm;
let mut all_boundary_or_higher = true;
let mut i = start;
while i < recent_raw_states.len() {
if recent_raw_states[i] == GrammarState::Admissible {
all_boundary_or_higher = false;
}
i += 1;
}
if all_boundary_or_higher {
return GrammarState::Boundary;
}
}
GrammarState::Admissible
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_violation_detection() {
let sign = SignTuple { norm: 4.0, drift: 0.1, slew: 0.0 };
let config = EngineConfig::default();
let (state, _) = evaluate_raw_grammar(&sign, 3.0, &config, 0.5);
assert_eq!(state, GrammarState::Violation);
}
#[test]
fn test_boundary_with_drift() {
let sign = SignTuple { norm: 2.0, drift: 0.5, slew: 0.0 };
let config = EngineConfig::default();
let (state, reason) = evaluate_raw_grammar(&sign, 3.0, &config, 0.8);
assert_eq!(state, GrammarState::Boundary);
assert_eq!(reason, ReasonCode::SustainedOutwardDrift);
}
#[test]
fn test_admissible() {
let sign = SignTuple { norm: 0.5, drift: 0.0, slew: 0.0 };
let config = EngineConfig::default();
let (state, _) = evaluate_raw_grammar(&sign, 3.0, &config, 0.0);
assert_eq!(state, GrammarState::Admissible);
}
#[test]
fn test_hysteresis_blocks_transient() {
let states = [GrammarState::Admissible, GrammarState::Boundary];
assert_eq!(hysteresis_confirm(&states, 2), GrammarState::Admissible);
}
#[test]
fn test_hysteresis_confirms_sustained() {
let states = [GrammarState::Boundary, GrammarState::Boundary];
assert_eq!(hysteresis_confirm(&states, 2), GrammarState::Boundary);
}
#[test]
fn test_violation_bypasses_hysteresis() {
let states = [GrammarState::Admissible, GrammarState::Violation];
assert_eq!(hysteresis_confirm(&states, 2), GrammarState::Violation);
}
}