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}