Skip to main content

dsfb_rf/
platform.rs

1//! Platform context: waveform transitions, SNR floor, regime suppression.
2//!
3//! This module provides the context information that allows the grammar layer
4//! to suppress escalation during known-good transient windows (waveform
5//! transitions, frequency hops, calibration periods) — analogous to the
6//! `MaintenanceHysteresis` guard in the semiconductor domain.
7//!
8//! ## Failure Mode Mitigation (paper §XIV-C)
9//!
10//! Deliberate waveform transitions produce residual signatures that are
11//! structurally indistinguishable from interference onset. The correct
12//! integration contract includes a waveform-schedule context channel that
13//! suppresses grammar escalation during flagged transition windows.
14//!
15//! Setting `WaveformState::Transition` returns `f32::INFINITY` from
16//! `admissibility_multiplier()`, making envelope violations structurally
17//! impossible during the transition window.
18
19/// SNR floor marker. Observations below this floor are flagged as
20/// sub-threshold and have drift/slew forced to zero.
21///
22/// Default: −10 dB (paper §L10, §IX-C).
23#[derive(Debug, Clone, Copy, PartialEq)]
24pub struct SnrFloor {
25    /// SNR floor in dB. Default −10.0.
26    pub db: f32,
27}
28
29impl SnrFloor {
30    /// Construct an SNR floor at `db` dB.
31    pub const fn new(db: f32) -> Self {
32        Self { db }
33    }
34
35    /// Returns true if the given SNR (dB) is below the floor.
36    #[inline]
37    pub fn is_sub_threshold(&self, snr_db: f32) -> bool {
38        snr_db < self.db
39    }
40}
41
42impl Default for SnrFloor {
43    fn default() -> Self {
44        Self { db: -10.0 }
45    }
46}
47
48/// Current waveform/signal regime state.
49///
50/// Used to suppress grammar escalation during planned transitions,
51/// preventing false episodes at every scheduled frequency hop,
52/// modulation change, or burst boundary (paper §XIV-C, §IX-E).
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum WaveformState {
55    /// Normal signal operation. Grammar evaluation proceeds.
56    Operational,
57    /// Deliberate waveform transition window (hop, burst boundary,
58    /// modulation change). Grammar escalation suppressed.
59    /// Admissibility multiplier → +∞.
60    Transition,
61    /// Post-transition hysteresis guard window. Grammar accumulation
62    /// suppressed for `guard_runs` remaining observations.
63    /// Post-transition guard: grammar suppressed for `remaining` more observations.
64    PostTransitionGuard {
65        /// Observations remaining in guard window before returning to Operational.
66        remaining: u16
67    },
68    /// Calibration window. Grammar evaluation suppressed.
69    Calibration,
70    /// Known co-site transmit-inhibit window.  The platform's own transmitter
71    /// is active; structural violations during this period are expected
72    /// artefacts of co-site interference, not external threats.
73    ///
74    /// The integration layer must set this state for the duration of every
75    /// local transmit burst (see paper §L, item 17: Aperture Co-site
76    /// Interference defence).  Grammar escalation is suppressed while this
77    /// state is active; the platform context transition back to
78    /// [`WaveformState::PostTransitionGuard`] automatically when the operator
79    /// clears the inhibit.
80    ///
81    /// Admissibility multiplier → `+∞` (no violation possible).
82    TransmitInhibit,
83}
84
85impl WaveformState {
86    /// Returns the admissibility multiplier for this state.
87    ///
88    /// - `Operational`: 1.0 (normal envelope)
89    /// - `Transition` / `Calibration`: f32::INFINITY (no violation possible)
90    /// - `PostTransitionGuard`: f32::INFINITY until guard expires
91    #[inline]
92    pub fn admissibility_multiplier(&self) -> f32 {
93        match self {
94            WaveformState::Operational => 1.0,
95            WaveformState::Transition => f32::INFINITY,
96            WaveformState::PostTransitionGuard { .. } => f32::INFINITY,
97            WaveformState::Calibration => f32::INFINITY,
98            WaveformState::TransmitInhibit => f32::INFINITY,
99        }
100    }
101
102    /// Returns true if grammar state assignment is suppressed.
103    #[inline]
104    pub fn is_suppressed(&self) -> bool {
105        matches!(
106            self,
107            WaveformState::Transition
108                | WaveformState::PostTransitionGuard { .. }
109                | WaveformState::Calibration
110                | WaveformState::TransmitInhibit
111        )
112    }
113
114    /// Advance the state by one observation tick.
115    /// `PostTransitionGuard { remaining: 0 }` transitions to `Operational`.
116    #[must_use]
117    pub fn tick(self) -> Self {
118        match self {
119            WaveformState::PostTransitionGuard { remaining: 0 } => WaveformState::Operational,
120            WaveformState::PostTransitionGuard { remaining } => {
121                WaveformState::PostTransitionGuard { remaining: remaining - 1 }
122            }
123            // TransmitInhibit persists until the integration layer explicitly
124            // clears it (transitions to Operational or PostTransitionGuard).
125            other => other,
126        }
127    }
128}
129
130/// Complete platform context passed to the engine on each observe() call.
131///
132/// This is the read-only context channel described in paper §XIV-C.
133/// It is populated by the integration layer (e.g., the GNU Radio sink block)
134/// and consumed by the engine. DSFB does not write to this struct.
135#[derive(Debug, Clone, Copy)]
136pub struct PlatformContext {
137    /// Current SNR estimate in dB. Use `f32::NAN` if unknown.
138    pub snr_db: f32,
139    /// Current waveform state (transition suppression context).
140    pub waveform_state: WaveformState,
141    /// Post-transition guard duration (observations). Default: 5.
142    pub post_transition_guard: u16,
143}
144
145impl PlatformContext {
146    /// Create a nominal operational context (no suppression, SNR = +20 dB).
147    pub const fn operational() -> Self {
148        Self {
149            snr_db: 20.0,
150            waveform_state: WaveformState::Operational,
151            post_transition_guard: 5,
152        }
153    }
154
155    /// Create a context with specified SNR and operational state.
156    pub const fn with_snr(snr_db: f32) -> Self {
157        Self {
158            snr_db,
159            waveform_state: WaveformState::Operational,
160            post_transition_guard: 5,
161        }
162    }
163
164    /// Create a transition-suppressed context.
165    pub const fn transition() -> Self {
166        Self {
167            snr_db: f32::NAN,
168            waveform_state: WaveformState::Transition,
169            post_transition_guard: 5,
170        }
171    }
172}
173
174impl Default for PlatformContext {
175    fn default() -> Self {
176        Self::operational()
177    }
178}
179
180// ---------------------------------------------------------------
181// Tests
182// ---------------------------------------------------------------
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn snr_floor_sub_threshold() {
189        let floor = SnrFloor::new(-10.0);
190        assert!(floor.is_sub_threshold(-15.0));
191        assert!(!floor.is_sub_threshold(-5.0));
192        assert!(!floor.is_sub_threshold(0.0));
193    }
194
195    #[test]
196    fn transition_multiplier_is_infinite() {
197        assert!(WaveformState::Transition.admissibility_multiplier().is_infinite());
198        assert!(WaveformState::Calibration.admissibility_multiplier().is_infinite());
199        assert_eq!(WaveformState::Operational.admissibility_multiplier(), 1.0);
200    }
201
202    #[test]
203    fn guard_ticks_to_operational() {
204        let s = WaveformState::PostTransitionGuard { remaining: 2 };
205        let s1 = s.tick();
206        assert_eq!(s1, WaveformState::PostTransitionGuard { remaining: 1 });
207        let s2 = s1.tick();
208        assert_eq!(s2, WaveformState::PostTransitionGuard { remaining: 0 });
209        let s3 = s2.tick();
210        assert_eq!(s3, WaveformState::Operational);
211    }
212
213    #[test]
214    fn suppressed_states() {
215        assert!(WaveformState::Transition.is_suppressed());
216        assert!(WaveformState::Calibration.is_suppressed());
217        assert!(WaveformState::PostTransitionGuard { remaining: 3 }.is_suppressed());
218        assert!(!WaveformState::Operational.is_suppressed());
219    }
220}