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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
//! # astrodyn_quantities
//!
//! Dimensional-analysis and phantom-tag foundation for `astrodyn_bevy`.
//!
//! This crate sits at the bottom of the workspace dependency graph. Every other
//! `astrodyn_*` crate, plus `astrodyn`, `astrodyn_runner`, and the `astrodyn_bevy` Bevy glue
//! all depend on it for typed quantities and phantom frame / time-scale tags.
//!
//! ## Three-layer facade
//!
//! ```text
//! ┌──────────────────────────────────────────────────────────┐
//! │ Facade (astrodyn_bevy::prelude, astrodyn::recipes) │
//! │ F64Ext: 400.0.km(), 51.6.deg(), 420_000.0.kg() │
//! │ Concrete Component wrappers (no visible generics) │
//! │ Custom #[diagnostic::on_unimplemented] messages │
//! ├──────────────────────────────────────────────────────────┤
//! │ Typed astrodyn_* siblings │
//! │ Position<F: Frame>, SecondsSince<S: TimeScale>, │
//! │ Quat<L, T>, NormalizedQuat, FrameTransform<From, To> │
//! ├──────────────────────────────────────────────────────────┤
//! │ astrodyn_quantities (you are here) │
//! │ uom re-exports, Qty3<D, F>, phantom frames/scales, │
//! │ F64Ext / Vec3Ext / Array3Ext │
//! └──────────────────────────────────────────────────────────┘
//! ```
//!
//! Mission-crate code consumes the facade layer and never sees `PhantomData`
//! or `uom::si::*` paths. Internal physics kernels drop down to raw
//! `glam::DVec3`/`f64` for arithmetic density via `.raw_si()` and re-wrap on
//! exit. See the Type-System and Strategy wiki pages
//! (<https://github.com/simnaut/astrodyn/wiki>) for architecture.
//!
//! ## What this crate provides
//!
//! - Reference-frame and time-scale phantom markers (`RootInertial`, `Ecef`,
//! `PlanetFixed<P>`, `BodyFrame<V>`, `Lvlh<Chief>`, `TAI`, `TT`, …)
//! - `uom`-backed componentwise 3-vectors `Qty3<D, F>` with aliases
//! `Position<F>`, `Velocity<F>`, `Acceleration<F>`, `Force<F>`, `Torque<F>`, …
//! - Quaternion convention tags (`ScalarFirst`/`ScalarLast`,
//! `LeftTransform`/`RightTransform`) plus a `NormalizedQuat`
//! constructor-gated witness
//! - Typed `FrameTransform<From, To>` composing only when inner frames match
//! - The `F64Ext` facade (`400.0.km()`, `51.6.deg()`, `420_000.0.kg()`)
//! - Compiler error messages in physics language via
//! `#[diagnostic::on_unimplemented]`
//!
//! ## Compile-time guards
//!
//! Beyond `uom`'s built-in dimensional analysis, this crate adds layered
//! type-system guards specific to orbital-mechanics conventions. The
//! actively-wired set:
//!
//! - **Dimensional mismatch** (uom-native): `Position + Mass` is rejected
//! because `Qty3`'s `Add` impl requires identical dimension `D`.
//! - **Frame mismatch on `+`/`-`/`+=`/`-=`** — `Position<Ecef> +
//! Position<RootInertial>` fails with a tailored
//! `#[diagnostic::on_unimplemented]` message pointing at
//! `FrameTransform`. Wired via the
//! [`diagnostics::CompatibleFrames`]`<Fl, Fr>` bound in [`ops`].
//! - **Time-scale separation** — `SecondsSince<S>` has no `Add`/`Sub`
//! impl across distinct scales, so direct arithmetic between (e.g.)
//! `SecondsSince<TAI>` and `SecondsSince<TT>` is rejected. The intended
//! way to combine scales is via `TimeConverter::apply`. The raw
//! `SecondsSince::from_seconds` / `as_seconds` boundaries can still
//! relabel an `f64` across scales without applying the offset — see
//! "Where the guards stop" below.
//! - **Quaternion convention separation** — layout (`ScalarFirst` vs
//! `ScalarLast`), transform convention (`LeftTransform` vs
//! `RightTransform`), and normalization status are distinct phantom
//! tags. `NormalizedQuat<L, T>` is a separate type from `Quat<L, T>`,
//! so a raw `Quat` cannot stand in where a unit quaternion is required.
//! - **`FrameTransform<From, To>` composition** only typechecks when the
//! two inner frames align (`A→B ∘ B→C`); the identity is only defined
//! for `From = To`.
//! - **Cross-dimension multiply / divide on `Qty3`** uses `typenum`
//! exponent arithmetic, so `Velocity × Time → Position` and
//! `Acceleration × Time → Velocity` are type-safe by construction; a
//! bad combination produces a dimension that won't unify with the
//! target type.
//! - **Inertial-flavor distinctions** (issue #255 / `RF.10`):
//! `RootInertial`, `PlanetInertial<P>`, and `IntegrationFrame` are
//! kind-distinct phantoms. Body integration-frame state cannot silently
//! flow into root-inertial-only consumers (gravity, SRP, relativistic);
//! the only safe transition is via [`IntegOrigin`].
//!
//! - **Vehicle-phantom mismatch** on the Act-5 phantom-wrapped types
//! (`FlatPlate<V>`, `AttachEvent<VParent, VChild>`,
//! `RelativeState<Subject, Reference>`,
//! `RelativeTranslation<Reference>`, `LvlhRelativeState<Chief>`) is
//! surfaced via the [`diagnostics::CompatibleVehicles`] /
//! [`diagnostics::CompatibleVehiclePair`] markers. Each type exposes
//! a zero-cost `assert_*` witness method whose `where` bound resolves
//! only when the vehicle phantoms agree; on mismatch the diagnostic
//! names the expected and found vehicles in physics language instead
//! of a `PhantomData<…>` wall. Mission code reaches for the witness
//! at the boundary that hands the value to a slot of a specific
//! identity (e.g. `plate.assert_vehicle::<Iss>()`).
//!
//! ### Scaffolded but not currently wired
//!
//! [`diagnostics::IntoLength`], [`diagnostics::IntoAngle`],
//! [`diagnostics::IntoGravParam`], [`diagnostics::CompatibleGravParam`],
//! [`diagnostics::CompatibleTimeScales`],
//! [`diagnostics::CompatibleQuatLayouts`],
//! [`diagnostics::CompatibleQuatTransforms`],
//! [`diagnostics::RequiresNormalizedQuat`],
//! [`diagnostics::InertialOnly`], and [`diagnostics::NoVectorVectorMul`]
//! carry tailored diagnostic messages but are not currently used as
//! `where` bounds by any impl in the workspace. Today, for example,
//! passing `400_000.0` where a `Length` is expected produces uom's stock
//! "mismatched types" error rather than the `IntoLength` hint, and
//! `Qty3 * Qty3` produces a default "no `Mul` impl" rather than the
//! `NoVectorVectorMul` hint. `F64Ext` discoverability today comes from
//! the prelude and worked examples. These scaffolds let a future
//! contributor flip on an active guard without touching call sites — the
//! diagnostic message is already in place.
//!
//! ### Where the guards stop
//!
//! The crate has several public raw-value boundaries where the
//! type-system guards end and the caller takes responsibility for
//! correctness:
//!
//! - [`Qty3::raw_si`] / [`Qty3::from_raw_si`] — into and out of raw
//! `glam::DVec3` (frame tag and dimension are erased on `raw_si`,
//! reattached without check on `from_raw_si`).
//! - `SecondsSince::from_seconds` / `as_seconds` — into and out of raw
//! `f64` seconds (time-scale tag is erased on `as_seconds`, reattached
//! without check on `from_seconds`, which is how a `TAI` reading can
//! be relabeled as `TT` without applying the 32.184 s offset).
//! - `JeodQuat::from_array` — accepts a raw `[f64; 4]` without
//! normalization or convention checks. Use `NormalizedQuat::new(q)?`
//! to recover the unit-norm witness.
//! - `FrameTransform::from_matrix` — accepts a raw `DMat3` without
//! checking orthogonality. The validating sibling is
//! `FrameTransform::from_matrix_validated`.
//!
//! These boundaries are deliberate: inside the `_inner` / `_impl`
//! kernels of `astrodyn_*` crates the math runs on `f64` / `DVec3` /
//! `DMat3` for arithmetic density. Unit, frame, time-scale, and
//! quaternion-convention slips inside a kernel are caught only at the
//! `F64Ext` and typed-API ingestion boundary — once you've crossed into
//! a raw representation the caller is responsible.
//!
//! See the [Type-System wiki page] for the contributor primer (phantom-tag
//! pattern, adding a new frame/scale/quantity, reading compiler errors,
//! escape hatches) and `examples/typed_mission.rs` for the canonical
//! worked example.
//!
//! [Type-System wiki page]: https://github.com/simnaut/astrodyn/wiki/Type-System
//!
//! ## Quick start
//!
//! ```
//! use astrodyn_quantities::prelude::*;
//!
//! let altitude = 400.0.km();
//! let inclination = 51.6.deg();
//! let mass = 420_000.0.kg();
//! ```
pub use *;
pub use BodyAttitude;
pub use *;
pub use *;
pub use *;
pub use IntegOrigin;
pub use *;
pub use *;
pub use *;
// `inertia::InertiaTensor` is re-exported via `aliases::*`.
/// Internal re-exports used by the `define_vehicle!` / `define_planet!`
/// macros to satisfy the per-domain sealed-trait bound at the downstream
/// call site.
///
/// Only `VehicleSealed` and `PlanetSealed` are re-exported. The other
/// three seal traits (`FrameSealed`, `TimeScaleSealed`, `QuatSealed`)
/// stay private to the crate, so `Frame`, `TimeScale`, `Layout`, and
/// `Transform` remain sealed at the type-system level — downstream code
/// cannot impl them at all.
///
/// **Do not import items from this module directly.** It exists only so
/// that macro expansions can satisfy the seal bounds. A direct
/// `impl VehicleSealed for X` outside the `define_vehicle!` macro is
/// technically possible but unsupported and may break in any release.
// allowed: macro infrastructure for define_vehicle!/define_planet!