leodos_protocols/network/isl/
shell.rs1use 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#[derive(Debug, Clone, Copy)]
23pub struct Shell {
24 pub torus: Torus,
26 pub altitude_m: f32,
28 pub inclination_rad: f32,
30}
31
32impl Shell {
33 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 pub fn orbital_radius(&self) -> f32 {
44 EARTH_RADIUS_M + self.altitude_m
45 }
46
47 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 pub fn within_orb_distance(&self) -> f32 {
63 chord_length(self.orbital_radius(), self.sat_spacing_angle())
64 }
65
66 pub fn cross_orb_base_distance(&self) -> f32 {
68 chord_length(self.orbital_radius(), self.orb_spacing_angle())
69 }
70
71 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 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}