1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! Error type returned by stepping methods on [`Simulation`](crate::Simulation).
//!
//! [`Simulation::step`](crate::Simulation::step),
//! [`Simulation::step_n`](crate::Simulation::step_n), and
//! [`Simulation::step_until`](crate::Simulation::step_until) all return
//! `Result<(), StepError>`. Per #172 L2, mission-runtime conditions inside
//! `step_internal` (ephemeris lookup failure, frame-switch target out of
//! range) propagate as a typed `StepError` rather than aborting the process
//! via `panic!`. This lets mission programmes catch the failure (Monte
//! Carlo, batch propagation, resilient long-running services) without
//! resorting to `catch_unwind`.
//!
//! Programmer / configuration errors (bad source index passed to an
//! accessor method, typestate violations) remain `panic!`s — they signal a
//! bug in the calling code and recovering from them is not meaningful.
//!
//! # State semantics on `Err`
//!
//! When a stepping method returns `Err`, the simulation has been
//! **partially advanced** for the failing step. Specifically:
//!
//! - [`StepError::EphemerisLookup`] fires after time has advanced but
//! before integration runs, so simulation time is ahead by `dt` while
//! body states are still at the previous step.
//! - [`StepError::FrameSwitchTargetMissing`] fires after both time advance
//! and integration, but before derived-state computation. Body states
//! correspond to the new time but derived states (orbital elements,
//! Euler angles, …) lag by one step.
//!
//! Either way the state is **not safe to step from again**. Mission code
//! that wants to recover should reconstruct the simulation from a saved
//! checkpoint (or from a fresh `SimulationBuilder`) rather than retry the
//! failing step.
use fmt;
use EphemerisBody;
/// Reasons a [`Simulation`](crate::Simulation) step can fail at runtime.
///
/// Construct via the variant constructors (the kernel returns these from
/// `step_internal` on the relevant failure paths). Inspect via pattern
/// match. Implements [`core::error::Error`] so it composes with `?` and
/// the rest of the standard error-handling ecosystem.
///
/// # Reading the carried context
///
/// All variant fields are part of the public surface (an enum variant's
/// fields inherit the enum's visibility, so no per-field `pub` qualifier
/// is needed or accepted by the language). Downstream recovery logic can
/// destructure freely:
///
/// ```
/// use astrodyn_runner::StepError;
///
/// fn handle(err: StepError) {
/// match err {
/// StepError::EphemerisLookup {
/// source_idx,
/// tdb_jd,
/// ..
/// } => {
/// eprintln!("source {source_idx} missing at TDB JD {tdb_jd}");
/// }
/// StepError::FrameSwitchTargetMissing {
/// body_idx,
/// target_source,
/// num_sources,
/// } => {
/// eprintln!(
/// "body {body_idx}: target {target_source} >= {num_sources}"
/// );
/// }
/// }
/// }
/// ```