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}