Skip to main content

dsfb_gpu_debug_core/
grammar.rs

1//! Four-state grammar used by the episode collapser.
2//!
3//! Modeled after the `dsfb-debug` grammar (Admissible / Boundary /
4//! Violation) plus a `Recovery` state we use specifically for the
5//! shock-and-recovery motif. The state machine is intentionally tiny and
6//! deterministic — there is no learned transition, no probability, just
7//! a closed-form function of the consensus cell's axis values.
8
9/// The grammar's discrete states. Ordering is meaningful: higher values
10/// indicate more severity, so a single `max()` over a window yields the
11/// peak grammar state for an interval.
12#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Default)]
13#[repr(u8)]
14pub enum GrammarState {
15    /// Cell is within the deadband: no axis lit up.
16    #[default]
17    Admissible = 0,
18    /// One or more axes elevated but no single axis at violation level.
19    Boundary = 1,
20    /// At least one axis crossed the violation threshold; episode-eligible.
21    Violation = 2,
22    /// Drift descending while norm still above the recovery floor.
23    Recovery = 3,
24}
25
26/// Reason codes attached to a grammar transition. Carried into the
27/// `Episode` so an operator audit can see *why* the grammar flagged a
28/// cell, not just that it did.
29#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
30#[repr(u8)]
31pub enum ReasonCode {
32    /// Sentinel: no reason because the cell is admissible.
33    #[default]
34    Admissible = 0,
35    /// Boundary entered because residual or drift is elevated.
36    BoundaryApproach = 1,
37    /// Drift sustained above the violation threshold for several windows.
38    SustainedOutwardDrift = 2,
39    /// Single-window slew shock.
40    AbruptSlewViolation = 3,
41    /// Multiple boundary cells with no clear violation — graze.
42    RecurrentBoundaryGrazing = 4,
43    /// Envelope-magnitude violation (norm itself crossed the high band).
44    EnvelopeViolation = 5,
45    /// Drift descending after a peak — recovery edge.
46    DriftWithRecovery = 6,
47    /// One-shot boundary crossing that did not re-enter on the next
48    /// cell.
49    SingleCrossing = 7,
50}
51
52impl ReasonCode {
53    /// Severity rank used for deterministic tie-break when several
54    /// reasons coexist. Higher = more severe.
55    #[must_use]
56    pub const fn severity(self) -> u8 {
57        match self {
58            Self::Admissible => 0,
59            Self::SingleCrossing => 1,
60            Self::BoundaryApproach => 2,
61            Self::RecurrentBoundaryGrazing => 3,
62            Self::DriftWithRecovery => 4,
63            Self::AbruptSlewViolation => 5,
64            Self::SustainedOutwardDrift => 6,
65            Self::EnvelopeViolation => 7,
66        }
67    }
68}