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
//! [`AstrodynSet`] — the Bevy `SystemSet` partition that mirrors JEOD's
//! per-step pipeline.
//!
//! # Schedule as integration group
//!
//! JEOD's `JeodIntegrationGroup` collects a set of bodies that advance
//! together under a single shared time step, with one integrator stage
//! that consumes the group's force/torque accumulators and produces the
//! next state for every member. In `astrodyn_bevy`, the Bevy `FixedUpdate`
//! schedule **is** that integration group:
//!
//! - One `FixedUpdate` tick = one group advance at the schedule's fixed
//! `dt`. Every entity matched by the integrating system's query
//! shares that `dt` by construction — no per-body bookkeeping.
//! - The seven [`AstrodynSet`] stages below run once per tick, in the order
//! declared, mirroring JEOD's init/update pipeline (force collection →
//! integration → derived state).
//! - **Multi-stage integrators (RK4, etc.) are an inner loop inside
//! [`AstrodynSet::Integration`], not multiple schedule passes.** A four-
//! stage RK4 evaluates four sub-steps within the integration system
//! for the same outer `FixedUpdate` tick.
//! - Multi-body sims (Apollo, Earth–Moon) need no extra wiring: every
//! body registered in the schedule is automatically a member of the
//! "group" in JEOD's sense.
//!
//! # Multiple integration groups
//!
//! When a scenario genuinely needs *separate* groups — different bodies
//! integrating at different cadences — register a second Bevy schedule
//! and run it on its own tick rate. This is plain Bevy mechanics, not
//! a JEOD-specific construct, and no current scenario in this workspace
//! uses it; the single-`FixedUpdate` model covers every shipped sim.
use TypeId;
use *;
/// One per-planet `SystemSet` per registered [`astrodyn::Planet`] type,
/// keyed by [`TypeId`].
///
/// Every `<P>`-typed system in `AstrodynPlugin::build` (Earth) and in
/// [`crate::register_planet_systems::<P>`] (every other planet) is added
/// to its planet's `PerPlanetSet`. Cross-planet sets are declared
/// `.ambiguous_with` each other inside `register_planet_systems`,
/// which walks [`crate::body_action::RegisteredPlanetsR`] to find every
/// prior planet and marks the cross-set pair safe.
///
/// `TypeId` keying (rather than a generic
/// `PerPlanetSet<P>(PhantomData<P>)`) is what makes the cross-planet
/// walk expressible. Reading a `TypeId` out of `RegisteredPlanetsR`
/// does not give us the concrete planet type back, so a generic
/// `PerPlanetSet<P>` would not be constructible from runtime data.
/// `TypeId` is `Send + Sync + 'static + Debug + Clone + Eq + Hash`, so
/// the `SystemSet` derive accepts the newtype.
///
/// Intra-planet ordering inside one `PerPlanetSet` continues to be
/// expressed with the existing `.before` / `.after` edges — set
/// membership is solely a cross-planet structural-disjointness
/// declaration, not an ordering construct.
///
/// See issue #562 / PR #565 for the motivation: the multi-planet unit
/// tests in `astrodyn_bevy` register both Earth and Mars and the static
/// schedule audit (under the `schedule_audit` Cargo feature) flagged
/// cross-planet pairs of per-planet systems racing on shared
/// non-generic component types (`RotationalStateC`, `FrameTransC`,
/// `MassPropertiesC`, …). The cross-planet pairs are runtime-disjoint
/// by entity, but the *mechanism* of disjointness varies by system,
/// and `PerPlanetSet` is how we communicate either route to Bevy:
///
/// 1. **Required-query-item filtering.** Systems like
/// [`crate::systems::integration_system::<P>`] and
/// [`crate::systems::flat_plate_srp_system::<P>`] take
/// `&mut TranslationalStateC<P>` as a required query item. Their
/// query only matches entities that carry the `<P>`-tagged
/// storage, which by construction is a single planet's. Bodies
/// carry `TranslationalStateC<Earth>` *or*
/// `TranslationalStateC<Mars>`, never both, so the `<Earth>` and
/// `<Mars>` instantiations of these systems iterate disjoint
/// entity subsets.
///
/// 2. **Queue partitioning.** Systems like
/// [`crate::body_action::body_action_system::<P>`] take
/// `Option<&mut TranslationalStateC<P>>` and filter only by
/// `With<DynamicsConfigC>`, so the query itself *could* match a
/// body of either planet. The runtime disjointness comes from the
/// per-planet `BodyActionsR<P>` queue: actions are routed to
/// `<P>`'s queue at submit time via
/// `BodyActionCommandsExt::add_body_action_for::<P>` and the
/// `body_action_unregistered_planet_fence_system` panics on a
/// `<P>` whose `TypeId` isn't in `RegisteredPlanetsR`. The
/// `<Mars>` apply pass walks `BodyActionsR<Mars>` and reaches
/// only Mars-tagged bodies — Earth-tagged bodies are reached
/// only via `BodyActionsR<Earth>` from the `<Earth>` apply pass.
///
/// Both routes produce the same runtime invariant (no two
/// per-planet `<P>` systems write the same entity in the same tick)
/// but Bevy's static analysis sees only the shared mutable component
/// access. `.ambiguous_with` is the right mechanism for both
/// because the structural justification is symmetric: we know
/// runtime-disjoint, Bevy doesn't.
;
/// Bevy system-set partition mirroring JEOD's per-step pipeline. Stages
/// run in declaration order; `AstrodynPlugin::build` configures the ordering
/// when the plugin is added to the app.
///
/// All seven stages execute together inside one `FixedUpdate` tick at
/// the schedule's shared `dt`, defining a single JEOD-style integration
/// group; see the module-level docs for the full mapping.
// JEOD_INV: DM.04 — system set ordering mirrors JEOD init/update pipeline
// JEOD_INV: DM.13 — EphemerisUpdate before Environment ensures ephemeris is current for gravity