From c1a0a329369b926c28437815cfec58ca799ea914 Mon Sep 17 00:00:00 2001 From: Mauricio Gomes Date: Wed, 2 Sep 2026 22:22:59 -0400 Subject: [PATCH] Abort ladder: name the abort mode and print its safing sequence --- docs/RUNBOOK.md | 11 ++++++++ src/main.rs | 1 + src/sequencer.rs | 66 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/docs/RUNBOOK.md b/docs/RUNBOOK.md index 2d9f3d8..2a44680 100644 --- a/docs/RUNBOOK.md +++ b/docs/RUNBOOK.md @@ -35,6 +35,17 @@ The sequencer prints one line per second. Watch for `HOLD`. > A hold at T-01:00 for LOX ullage usually clears itself in under two > minutes. Do not recycle the count for it. +## Aborts + +The sequencer prints the abort mode and the safing sequence for it. Read the +safing line on the loop, then hand over to the pad crew. + +| Mode | Safing | +| ----------------- | ----------------------------------- | +| Chamber pressure | close main valves, purge chamber | +| Ullage | vent LOX to 2.0 bar, hold RP-1 | +| Bus | swap to ground power, disarm FTS | + ## After liftoff - [ ] Confirm MECO on the dashboard diff --git a/src/main.rs b/src/main.rs index e447aed..1017a97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ fn main() -> anyhow::Result<()> { Ok(phase) => println!("{:<6} T{:+}", phase.label(), seq.t()), Err(abort) => { eprintln!("ABORT T{:+} {abort}", seq.t()); + eprintln!("SAFING {}", abort.mode.safing()); return Err(abort.into()); } } diff --git a/src/sequencer.rs b/src/sequencer.rs index 9dc5aed..103f6bc 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -15,6 +15,26 @@ pub enum Phase { Hold { at: i64, reason: String }, Ignition, Liftoff, + /// Terminal: the count stopped inside the auto-abort window. + Aborted { at: i64, mode: AbortMode }, +} + +/// What tripped inside T-10. Each mode has its own safing sequence. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum AbortMode { + ChamberPressure, + Ullage, + Bus, +} + +impl AbortMode { + pub fn safing(&self) -> &'static str { + match self { + AbortMode::ChamberPressure => "close main valves, purge chamber", + AbortMode::Ullage => "vent LOX to 2.0 bar, hold RP-1", + AbortMode::Bus => "swap to ground power, disarm FTS", + } + } } impl Phase { @@ -26,6 +46,7 @@ impl Phase { Phase::Hold { .. } => "HOLD", Phase::Ignition => "IGN", Phase::Liftoff => "LIFT", + Phase::Aborted { .. } => "ABORT", } } } @@ -33,12 +54,13 @@ impl Phase { #[derive(Debug)] pub struct Abort { pub t: i64, + pub mode: AbortMode, pub reason: String, } impl fmt::Display for Abort { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} at T{:+}", self.reason, self.t) + write!(f, "{} at T{:+}; {}", self.reason, self.t, self.mode.safing()) } } @@ -116,10 +138,12 @@ impl Sequencer { /// Advance one second. Returns the new phase, or the abort that stopped /// the count. pub fn step(&mut self, frame: &Frame) -> Result { - if let Some(reason) = self.redline(frame) { + if let Some((mode, reason)) = self.redline(frame) { if self.manifest.sequencer.auto_abort && self.t > -10 { + self.phase = Phase::Aborted { at: self.t, mode }; return Err(Abort { t: self.t, + mode, reason, }); } @@ -140,19 +164,36 @@ impl Sequencer { Ok(self.phase.clone()) } - fn redline(&self, frame: &Frame) -> Option { + fn redline(&self, frame: &Frame) -> Option<(AbortMode, String)> { if frame.chamber_pressure > MAX_CHAMBER_PRESSURE_BAR { - return Some(format!("chamber pressure {:.1} bar", frame.chamber_pressure)); + return Some(( + AbortMode::ChamberPressure, + format!("chamber pressure {:.1} bar", frame.chamber_pressure), + )); } if frame.lox_pressure < MIN_LOX_PRESSURE_BAR && self.t > -120 { - return Some(format!("LOX ullage {:.2} bar", frame.lox_pressure)); + return Some(( + AbortMode::Ullage, + format!("LOX ullage {:.2} bar", frame.lox_pressure), + )); } if frame.battery_v < MIN_BATTERY_V { - return Some(format!("bus voltage {:.1} V", frame.battery_v)); + return Some((AbortMode::Bus, format!("bus voltage {:.1} V", frame.battery_v))); } None } + /// Release a hold and resume the count from where it stopped. + pub fn release_hold(&mut self) -> bool { + match self.phase { + Phase::Hold { .. } => { + self.phase = Phase::TerminalCount; + true + } + _ => false, + } + } + pub fn holds_taken(&self) -> usize { self.holds_taken } @@ -181,4 +222,17 @@ mod tests { } assert_eq!(seq.holds_taken(), 0); } + + #[test] + fn low_bus_holds_early_and_aborts_late() { + let mut seq = Sequencer::from_manifest("mission.toml").unwrap(); + let mut frame = nominal(); + frame.battery_v = 26.0; + assert!(matches!(seq.step(&frame), Ok(Phase::Hold { .. }))); + assert!(seq.release_hold()); + seq.t = -5; + let err = seq.step(&frame).unwrap_err(); + assert_eq!(err.mode, AbortMode::Bus); + assert!(matches!(seq.phase(), Phase::Aborted { .. })); + } }