1use crate::types::{GrammarState, ReasonCode, SignTuple};
24use crate::config::EngineConfig;
25use crate::envelope;
26
27#[inline]
33pub fn evaluate_raw_grammar(
34 sign: &SignTuple,
35 rho: f64,
36 config: &EngineConfig,
37 drift_persistence: f64,
38) -> (GrammarState, ReasonCode) {
39 if envelope::is_violation(sign.norm, rho) {
41 if sign.slew > config.slew_delta || sign.slew < -config.slew_delta {
42 return (GrammarState::Violation, ReasonCode::AbruptSlewViolation);
43 }
44 return (GrammarState::Violation, ReasonCode::EnvelopeViolation);
45 }
46
47 if envelope::is_boundary_zone(sign.norm, rho, config.boundary_fraction) {
49 if sign.drift > 0.0 && drift_persistence > 0.5 {
51 return (GrammarState::Boundary, ReasonCode::SustainedOutwardDrift);
52 }
53 if sign.slew > config.slew_delta || sign.slew < -config.slew_delta {
55 return (GrammarState::Boundary, ReasonCode::AbruptSlewViolation);
56 }
57 return (GrammarState::Boundary, ReasonCode::BoundaryApproach);
59 }
60
61 (GrammarState::Admissible, ReasonCode::Admissible)
63}
64
65#[inline]
71pub fn hysteresis_confirm(
72 recent_raw_states: &[GrammarState],
73 n_confirm: usize,
74) -> GrammarState {
75 if recent_raw_states.len() < n_confirm {
76 return GrammarState::Admissible;
77 }
78
79 let latest = recent_raw_states[recent_raw_states.len() - 1];
80
81 if latest == GrammarState::Violation {
83 return GrammarState::Violation;
84 }
85
86 if latest == GrammarState::Boundary {
88 let start = recent_raw_states.len() - n_confirm;
89 let mut all_boundary_or_higher = true;
90 let mut i = start;
91 while i < recent_raw_states.len() {
92 if recent_raw_states[i] == GrammarState::Admissible {
93 all_boundary_or_higher = false;
94 }
95 i += 1;
96 }
97 if all_boundary_or_higher {
98 return GrammarState::Boundary;
99 }
100 }
101
102 GrammarState::Admissible
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_violation_detection() {
111 let sign = SignTuple { norm: 4.0, drift: 0.1, slew: 0.0 };
112 let config = EngineConfig::default();
113 let (state, _) = evaluate_raw_grammar(&sign, 3.0, &config, 0.5);
114 assert_eq!(state, GrammarState::Violation);
115 }
116
117 #[test]
118 fn test_boundary_with_drift() {
119 let sign = SignTuple { norm: 2.0, drift: 0.5, slew: 0.0 };
120 let config = EngineConfig::default();
121 let (state, reason) = evaluate_raw_grammar(&sign, 3.0, &config, 0.8);
122 assert_eq!(state, GrammarState::Boundary);
123 assert_eq!(reason, ReasonCode::SustainedOutwardDrift);
124 }
125
126 #[test]
127 fn test_admissible() {
128 let sign = SignTuple { norm: 0.5, drift: 0.0, slew: 0.0 };
129 let config = EngineConfig::default();
130 let (state, _) = evaluate_raw_grammar(&sign, 3.0, &config, 0.0);
131 assert_eq!(state, GrammarState::Admissible);
132 }
133
134 #[test]
135 fn test_hysteresis_blocks_transient() {
136 let states = [GrammarState::Admissible, GrammarState::Boundary];
137 assert_eq!(hysteresis_confirm(&states, 2), GrammarState::Admissible);
138 }
139
140 #[test]
141 fn test_hysteresis_confirms_sustained() {
142 let states = [GrammarState::Boundary, GrammarState::Boundary];
143 assert_eq!(hysteresis_confirm(&states, 2), GrammarState::Boundary);
144 }
145
146 #[test]
147 fn test_violation_bypasses_hysteresis() {
148 let states = [GrammarState::Admissible, GrammarState::Violation];
149 assert_eq!(hysteresis_confirm(&states, 2), GrammarState::Violation);
150 }
151}