use crate::envelope::AdmissibilityEnvelope;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EnvelopeMode {
Fixed,
Widening,
Tightening,
RegimeSwitched,
Aggregate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RfRegime {
Preamble,
Payload,
Acquisition,
InLock,
}
#[derive(Debug, Clone, Copy)]
pub struct RegimeEnvelopeParams {
pub rho_base: f32,
pub rho_max: f32,
pub widen_alpha: f32,
pub tighten_alpha: f32,
pub boundary_band_frac: f32,
pub slew_threshold_frac: f32,
}
impl RegimeEnvelopeParams {
pub const fn default_sdr(rho_base: f32) -> Self {
Self {
rho_base,
rho_max: rho_base * 3.0,
widen_alpha: 0.10,
tighten_alpha: 0.05,
boundary_band_frac: 0.04, slew_threshold_frac: 0.08, }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GrammarTrustScalar {
pub value: f32,
pub margin: f32,
}
impl GrammarTrustScalar {
pub fn compute(norm: f32, rho_eff: f32, band_frac: f32) -> Self {
if rho_eff <= 1e-30 {
return Self { value: 0.0, margin: 0.0 };
}
let margin = (rho_eff - norm) / rho_eff;
let value = if band_frac < 1e-12 {
if margin >= 0.0 { 1.0 } else { 0.0 }
} else {
let raw = margin / band_frac;
raw.max(0.0).min(1.0)
};
Self { value, margin }
}
#[inline]
pub fn is_fully_trusted(&self) -> bool { self.value >= 1.0 - 1e-6 }
#[inline]
pub fn is_suppressed(&self) -> bool { self.value <= 1e-6 }
}
pub struct RegimeEnvelope {
rho_eff: f32,
mode: EnvelopeMode,
params: RegimeEnvelopeParams,
consecutive_boundary: u8,
last_slew: bool,
}
impl RegimeEnvelope {
pub const fn new(params: RegimeEnvelopeParams) -> Self {
Self {
rho_eff: params.rho_base,
mode: EnvelopeMode::Fixed,
params,
consecutive_boundary: 0,
last_slew: false,
}
}
pub fn from_envelope(env: &AdmissibilityEnvelope) -> Self {
let params = RegimeEnvelopeParams::default_sdr(env.rho);
Self::new(params)
}
pub fn set_mode(&mut self, mode: EnvelopeMode) {
self.mode = mode;
}
#[inline]
pub fn rho_eff(&self) -> f32 { self.rho_eff }
#[inline]
pub fn mode(&self) -> EnvelopeMode { self.mode }
pub fn update(
&mut self,
norm: f32,
regime: RfRegime,
other_rho: &[f32],
) -> EnvelopeUpdateResult {
self.rho_eff = self.compute_rho_eff(regime, other_rho);
let band = self.params.boundary_band_frac * self.rho_eff;
let in_boundary_band = norm > (self.rho_eff - band).max(0.0) && norm <= self.rho_eff;
let above_envelope = norm > self.rho_eff;
if in_boundary_band {
self.consecutive_boundary = self.consecutive_boundary.saturating_add(1);
} else {
self.consecutive_boundary = 0;
}
let recurrent_boundary_grazing = self.consecutive_boundary >= 2;
let trust = GrammarTrustScalar::compute(norm, self.rho_eff, self.params.boundary_band_frac);
EnvelopeUpdateResult {
rho_eff: self.rho_eff,
mode: self.mode,
grammar_trust: trust,
in_boundary_band,
above_envelope,
recurrent_boundary_grazing,
}
}
fn compute_rho_eff(&self, regime: RfRegime, other_rho: &[f32]) -> f32 {
match self.mode {
EnvelopeMode::Fixed => self.params.rho_base,
EnvelopeMode::Widening => {
let a = self.params.widen_alpha;
let r = a * self.params.rho_max + (1.0 - a) * self.rho_eff;
r.max(self.params.rho_base).min(self.params.rho_max)
}
EnvelopeMode::Tightening => {
let a = self.params.tighten_alpha;
let r = a * self.params.rho_base + (1.0 - a) * self.rho_eff;
r.max(self.params.rho_base).min(self.params.rho_max)
}
EnvelopeMode::RegimeSwitched => match regime {
RfRegime::Preamble | RfRegime::Acquisition => self.params.rho_max,
RfRegime::Payload | RfRegime::InLock => self.params.rho_base,
},
EnvelopeMode::Aggregate => {
let mut max_rho = self.params.rho_base;
for &r in other_rho {
if r > max_rho { max_rho = r; }
}
max_rho
}
}
}
pub fn update_with_slew(
&mut self,
norm: f32,
regime: RfRegime,
other_rho: &[f32],
delta_norm: f32,
) -> (EnvelopeUpdateResult, bool) {
let result = self.update(norm, regime, other_rho);
let slew_threshold = self.params.slew_threshold_frac * self.rho_eff;
let abrupt_slew = delta_norm.abs() > slew_threshold;
self.last_slew = abrupt_slew;
(result, abrupt_slew)
}
pub fn reset(&mut self) {
self.rho_eff = self.params.rho_base;
self.mode = EnvelopeMode::Fixed;
self.consecutive_boundary = 0;
self.last_slew = false;
}
}
#[derive(Debug, Clone, Copy)]
pub struct EnvelopeUpdateResult {
pub rho_eff: f32,
pub mode: EnvelopeMode,
pub grammar_trust: GrammarTrustScalar,
pub in_boundary_band: bool,
pub above_envelope: bool,
pub recurrent_boundary_grazing: bool,
}
#[cfg(test)]
mod tests {
use super::*;
fn params() -> RegimeEnvelopeParams {
RegimeEnvelopeParams {
rho_base: 0.10,
rho_max: 0.30,
widen_alpha: 0.20,
tighten_alpha: 0.10,
boundary_band_frac: 0.04,
slew_threshold_frac: 0.08,
}
}
#[test]
fn fixed_mode_constant_rho() {
let mut env = RegimeEnvelope::new(params());
for _ in 0..50 {
let r = env.update(0.05, RfRegime::InLock, &[]);
assert!((r.rho_eff - 0.10).abs() < 1e-6);
}
}
#[test]
fn widening_mode_expands() {
let mut env = RegimeEnvelope::new(params());
env.set_mode(EnvelopeMode::Widening);
let mut rho_prev = env.rho_eff();
for _ in 0..30 {
let r = env.update(0.05, RfRegime::Acquisition, &[]);
assert!(r.rho_eff >= rho_prev - 1e-9, "rho must not decrease in widening mode");
rho_prev = r.rho_eff;
}
assert!(rho_prev > 0.10, "rho should have grown above rho_base");
}
#[test]
fn tightening_mode_contracts() {
let mut env = RegimeEnvelope::new(params());
env.rho_eff = 0.29; env.set_mode(EnvelopeMode::Tightening);
let mut rho_prev = env.rho_eff();
for _ in 0..40 {
let r = env.update(0.05, RfRegime::InLock, &[]);
assert!(r.rho_eff <= rho_prev + 1e-6, "rho must not increase in tightening mode");
rho_prev = r.rho_eff;
}
assert!(rho_prev < 0.29, "rho should have contracted");
}
#[test]
fn regime_switched_snaps() {
let mut env = RegimeEnvelope::new(params());
env.set_mode(EnvelopeMode::RegimeSwitched);
let r_acq = env.update(0.05, RfRegime::Acquisition, &[]);
assert!((r_acq.rho_eff - 0.30).abs() < 1e-6);
let r_lock = env.update(0.05, RfRegime::InLock, &[]);
assert!((r_lock.rho_eff - 0.10).abs() < 1e-6);
}
#[test]
fn aggregate_mode_takes_max() {
let mut env = RegimeEnvelope::new(params());
env.set_mode(EnvelopeMode::Aggregate);
let r = env.update(0.05, RfRegime::InLock, &[0.15, 0.25, 0.20]);
assert!((r.rho_eff - 0.25).abs() < 1e-6);
}
#[test]
fn grammar_trust_full_inside() {
let p = params(); let mut env = RegimeEnvelope::new(p);
let r = env.update(0.0, RfRegime::InLock, &[]);
assert!(r.grammar_trust.is_fully_trusted());
}
#[test]
fn grammar_trust_zero_at_boundary() {
let p = params();
let mut env = RegimeEnvelope::new(p);
let r = env.update(0.10, RfRegime::InLock, &[]);
assert!(r.grammar_trust.is_suppressed(), "trust={}", r.grammar_trust.value);
}
#[test]
fn recurrent_boundary_grazing_after_two() {
let mut env = RegimeEnvelope::new(params());
let r1 = env.update(0.098, RfRegime::InLock, &[]);
assert!(!r1.recurrent_boundary_grazing, "only 1 sample — not recurring yet");
let r2 = env.update(0.097, RfRegime::InLock, &[]);
assert!(r2.recurrent_boundary_grazing, "2 consecutive should trigger grazing");
}
#[test]
fn abrupt_slew_detection() {
let mut env = RegimeEnvelope::new(params());
let (_, slew) = env.update_with_slew(0.05, RfRegime::InLock, &[], 0.001);
assert!(!slew, "0.001 < 0.008: no slew");
let (_, slew) = env.update_with_slew(0.05, RfRegime::InLock, &[], 0.02);
assert!(slew, "0.02 > 0.008: abrupt slew detected");
}
}