
class Episode:
    def __init__(self,cfg):
        self.cfg=cfg;self.latched=False;self.last_event=None;self.last_hit=None
        self.last_valid=None;self.last_time=None;self.previous_quiet=False;self.quiet=0.

    def step(self,t,hit,quiet_valid,allow_event=True):
        cfg=self.cfg;dt=0 if self.last_time is None else t-self.last_time
        contiguous=0<dt<=cfg.max_step_gap+1e-8
        self.last_time=t;event=False;transition=None
        if hit or quiet_valid:self.last_valid=t
        if hit:
            self.last_hit=t;self.quiet=0.;self.previous_quiet=False
            if not self.latched and allow_event and (self.last_event is None or t-self.last_event>=cfg.event_cooldown-1e-8):
                self.latched=True;self.last_event=t;event=True;transition='NEW_TEST_EVENT'
        else:
            self.quiet=self.quiet+dt if quiet_valid and self.previous_quiet and contiguous else 0.
            self.previous_quiet=quiet_valid
            if self.latched and self.quiet>=cfg.rearm_quiet-1e-8:
                self.latched=False;transition='REARMED_OBSERVED_QUIET'
            elif self.latched and self.last_valid is not None and t-self.last_valid>cfg.pair_retention:
                self.latched=False;transition='EXPIRED_NOT_NORMAL'
        display=hit or (self.latched and self.last_hit is not None and t-self.last_hit<=cfg.display_hold+1e-8)
        status=('ALERT' if event else 'RULE_HIT_LATCHED' if hit and self.latched else 'RULE_HIT_COOLDOWN' if hit
                else 'DISPLAY_HOLD' if display else 'UNKNOWN_LATCHED' if self.latched and not quiet_valid
                else 'QUIET_REARM_WAIT' if self.latched else 'MONITORING' if quiet_valid else 'INSUFFICIENT')
        return dict(status=status,event=event,display=display,latched=self.latched,quiet_seconds=self.quiet,transition=transition)
