Skip to main content

rockets_engine/
guidance.rs

1use crate::{FlightState, Result, RocketSample, Vec3};
2
3/// World-frame controls held constant for one caller-requested simulation step.
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct ControlInput {
6    /// Nonzero thrust-axis vector. The solver normalizes it before applying thrust.
7    /// This is an externally supplied orientation, not an instantaneous attitude solver.
8    pub thrust_direction: Vec3,
9    /// Additional acceleration requested by an external steering/actuator model.
10    /// Its magnitude is capped by [`crate::IntegratorSettings::max_steering_accel_mps2`].
11    /// It can act during coast, allowing callers to model fins or other control authority.
12    pub steering_acceleration_mps2: Vec3,
13}
14
15impl ControlInput {
16    /// Specify a thrust axis with no additional steering acceleration.
17    /// Inputs are validated when the simulator steps.
18    pub fn along(thrust_direction: Vec3) -> Self {
19        Self {
20            thrust_direction,
21            steering_acceleration_mps2: Vec3::zeros(),
22        }
23    }
24}
25
26impl Default for ControlInput {
27    fn default() -> Self {
28        Self::along(Vec3::x())
29    }
30}
31
32/// Snapshot supplied once to guidance for each [`crate::Simulator::step_guided`] call.
33#[derive(Debug, Clone, Copy)]
34pub struct GuidanceContext<'a> {
35    /// State at the start of the requested step.
36    pub state: &'a FlightState,
37    /// Mass and motor phase at the start of the requested step.
38    pub rocket: RocketSample,
39    /// Caller-requested duration, not the smaller internal RK4 timestep.
40    pub step_duration_s: f64,
41    /// Solver-enforced cap on additional steering acceleration.
42    pub max_steering_accel_mps2: f64,
43}
44
45/// Application-owned controller hook, independent of entities, targets and render timing.
46///
47/// Controllers are called once per public step, never inside RK4 stages. The command
48/// is then held constant over every internal substep. Stateful controllers should save
49/// their own state alongside [`FlightState`] and use a fixed update cadence for replay.
50/// Controller mutations are not rolled back if subsequent integration fails.
51pub trait Guidance {
52    /// Produce a finite control command, or explain why control cannot be computed.
53    fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput>;
54}
55
56impl<F> Guidance for F
57where
58    F: FnMut(GuidanceContext<'_>) -> Result<ControlInput>,
59{
60    fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
61        self(context)
62    }
63}
64
65/// Unguided orientation: thrust always follows the specified fixed world axis.
66/// This does not simulate passive body alignment or attitude dynamics.
67#[derive(Debug, Clone, Copy)]
68pub struct FixedDirection(pub Vec3);
69
70impl Guidance for FixedDirection {
71    fn command(&mut self, _context: GuidanceContext<'_>) -> Result<ControlInput> {
72        Ok(ControlInput::along(self.0))
73    }
74}