Skip to main content

leodos_protocols/network/isl/
shell.rs

1//! Walker Delta constellation shell with physical ISL distances.
2//!
3//! A shell consists of orbital planes at the same altitude and inclination.
4//! This module extends the logical Torus topology with physical parameters
5//! to compute actual ISL distances in meters.
6//!
7//! Within-plane distances are constant. Cross-plane distances vary cyclically
8//! as planes converge near poles and diverge at the equator.
9
10use core::f32::consts::PI;
11
12use super::torus::Torus;
13
14const EARTH_RADIUS_M: f32 = 6_371_000.0;
15const GRAVITATIONAL_PARAM: f32 = 3.986e14;
16
17fn chord_length(radius: f32, angle_rad: f32) -> f32 {
18    radius * libm::sqrtf(2.0 * (1.0 - libm::cosf(angle_rad)))
19}
20
21/// A Walker Delta constellation shell with physical ISL parameters.
22#[derive(Debug, Clone, Copy)]
23pub struct Shell {
24    /// The logical toroidal grid topology.
25    pub torus: Torus,
26    /// Orbital altitude above Earth's surface in meters.
27    pub altitude_m: f32,
28    /// Orbital inclination in radians.
29    pub inclination_rad: f32,
30}
31
32impl Shell {
33    /// Creates a new shell from topology, altitude, and inclination (in degrees).
34    pub const fn new(torus: Torus, altitude_m: f32, inclination_deg: f32) -> Self {
35        Self {
36            torus,
37            altitude_m,
38            inclination_rad: inclination_deg * PI / 180.0,
39        }
40    }
41
42    /// Returns the orbital radius (Earth radius + altitude) in meters.
43    pub fn orbital_radius(&self) -> f32 {
44        EARTH_RADIUS_M + self.altitude_m
45    }
46
47    /// Returns the orbital period in seconds (Kepler's third law).
48    pub fn orbital_period_s(&self) -> f32 {
49        let r = self.orbital_radius();
50        2.0 * PI * libm::sqrtf(r * r * r / GRAVITATIONAL_PARAM)
51    }
52
53    fn sat_spacing_angle(&self) -> f32 {
54        2.0 * PI / self.torus.num_orbs as f32
55    }
56
57    fn orb_spacing_angle(&self) -> f32 {
58        2.0 * PI / self.torus.num_sats as f32
59    }
60
61    /// Returns the chord distance between adjacent satellites in the same plane.
62    pub fn within_orb_distance(&self) -> f32 {
63        chord_length(self.orbital_radius(), self.sat_spacing_angle())
64    }
65
66    /// Returns the equatorial chord distance between adjacent orbital planes.
67    pub fn cross_orb_base_distance(&self) -> f32 {
68        chord_length(self.orbital_radius(), self.orb_spacing_angle())
69    }
70
71    /// Returns the cross-orbit ISL distance at a given orbital phase (0.0-1.0).
72    pub fn cross_orb_distance(&self, phase: f32) -> f32 {
73        let theta = 2.0 * PI * phase;
74        let cos_theta = libm::cosf(theta);
75        let sin_theta = libm::sinf(theta);
76        let cos_inc = libm::cosf(self.inclination_rad);
77
78        let factor = libm::sqrtf(cos_theta * cos_theta + cos_inc * cos_inc * sin_theta * sin_theta);
79        self.cross_orb_base_distance() * factor
80    }
81
82    /// Returns the cross-plane ISL distance for a satellite.
83    pub fn cross_orb_distance_at_sat(&self, sat: u8) -> f32 {
84        self.cross_orb_distance(sat as f32 / self.torus.num_orbs as f32)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    fn approx_eq(a: f32, b: f32, epsilon: f32) -> bool {
93        (a - b).abs() < epsilon
94    }
95
96    #[test]
97    fn test_orbital_period() {
98        let torus = Torus::new(22, 72);
99        let shell = Shell::new(torus, 550_000.0, 53.0);
100        let period = shell.orbital_period_s();
101        assert!(approx_eq(period, 5700.0, 100.0), "period={}", period);
102    }
103
104    #[test]
105    fn test_within_orb_distance() {
106        let torus = Torus::new(22, 72);
107        let shell = Shell::new(torus, 550_000.0, 53.0);
108        let dist = shell.within_orb_distance();
109        assert!(dist > 1_000_000.0 && dist < 2_500_000.0, "dist={}", dist);
110    }
111
112    #[test]
113    fn test_cross_orb_varies() {
114        let torus = Torus::new(22, 72);
115        let shell = Shell::new(torus, 550_000.0, 87.0);
116        let at_equator = shell.cross_orb_distance(0.0);
117        let at_pole = shell.cross_orb_distance(0.25);
118        assert!(
119            at_pole < at_equator,
120            "pole {} < equator {}",
121            at_pole,
122            at_equator
123        );
124    }
125
126    #[test]
127    fn test_high_inclination_more_variation() {
128        let torus = Torus::new(22, 72);
129        let low = Shell::new(torus, 550_000.0, 53.0);
130        let high = Shell::new(torus, 550_000.0, 87.0);
131
132        let low_ratio = low.cross_orb_distance(0.0) / low.cross_orb_distance(0.25);
133        let high_ratio = high.cross_orb_distance(0.0) / high.cross_orb_distance(0.25);
134
135        assert!(
136            high_ratio > low_ratio,
137            "high {} > low {}",
138            high_ratio,
139            low_ratio
140        );
141    }
142}