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}