nx_solar_system_catalog.nx source
↩ module page · 728 lines · 37107 B
1// nx_solar_system_catalog.nx -- catalog of solar-system bodies.
2//
3// Cross-cutting Substrate C foundational data primitive per
4// nxc2/docs/NISHI_GAME_ENGINE_ROADMAP.md "Solar system macro-
5// benchmark." Holds physical parameters (mass, radius, orbit,
6// composition flags) for every major solar-system body the
7// substrate generates.
8//
9// FULL CATALOG (per feedback-maximum-capability-no-simplification
10// cardinal, 2026-05-16): 37 bodies covering all 5 categories:
11// - 1 star (Sun)
12// - 8 planets (Mercury -> Neptune)
13// - 13 major moons (Luna, Phobos, Deimos, 4 Galileans, Titan +
14// 3 Saturn icy, Titania, Oberon, Triton)
15// - 5 dwarf planets (Ceres, Pluto, Haumea, Makemake, Eris)
16// - 3 representative asteroids (Vesta, Pallas, Hygiea)
17// - 3 well-known comets (1P/Halley, C/1995 O1 Hale-Bopp,
18// 2I/Borisov interstellar)
19// - 3 TNOs (Sedna, Quaoar, Orcus)
20//
21// Per-body fields: ID, kind, radius_km, mass_e20kg (0 for sub-Phobos
22// bodies where Q14 underflows), semi-major axis (AU for solar orbits;
23// thousands-km for moon orbits relative to their parent), orbital
24// period (days), rotation period (hours), atmosphere flag, rings flag,
25// PARENT BODY ID (NX_BODY_SUN for planets/dwarfs/asteroids/TNOs;
26// parent planet for moons; 0 - 1 for the Sun itself).
27//
28// Data source: NASA JPL Horizons + Wikipedia infoboxes for the
29// physical parameters. Per cardinal
30// `feedback-no-third-party-trust-native-or-nothing`: these are
31// PUBLIC-DOMAIN scientific reference values (NASA is US federal
32// civil-service work, TIER_0_UNENCUMBERED). No copyrighted
33// imagery or text absorbed -- only numbers.
34//
35// Per cardinal `feedback-procgen-causal-growth-not-direct-sampling`:
36// these parameters are the CAUSAL INPUTS to per-body generators
37// (atmosphere -> climate -> biome -> surface features). Without
38// this catalog, the per-body generators would be guessing.
39//
40// Q14 conventions across the catalog:
41// radius_km_q14: radius in kilometres (Sun ~ 695700)
42// mass_e20kg_q14: mass in units of 1e20 kg (scaling
43// keeps Sun ~ 1.989e10 fitting in i64)
44// semi_major_axis_au_q14: orbital distance from Sun (Earth=1.0)
45// orbital_period_days_q14: Earth-days for one orbit
46// rotation_period_hours_q14: hours for one rotation on own axis
47//
48// Loss audit: all values are Q14-quantised scientific measurements.
49// Real-world measurement precision is typically 0.1-1% for these
50// quantities; Q14 (1 part in 16384) significantly exceeds that.
51//
52// genealogy_id: nasa_jpl_horizons_2025 + wikipedia_solar_system_infoboxes
53// lineage_id: nx_solar_system_catalog_q14_first_slice
54
55// nx_safety_envelope:
56// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
57// sil_target: SIL1
58// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
59// verdict: NOT_YET_EVALUATED
60
61import "nx_syscalls.nx"
62import "nx_hal.nx"
63import "nx_tier.nx"
64const NX_MAGIC_695700: i64 = 695700
65const NX_MAGIC_2440: i64 = 2440
66const NX_MAGIC_6052: i64 = 6052
67const NX_MAGIC_6371: i64 = 6371
68const NX_MAGIC_3390: i64 = 3390
69const NX_MAGIC_69911: i64 = 69911
70const NX_MAGIC_58232: i64 = 58232
71const NX_MAGIC_25362: i64 = 25362
72const NX_MAGIC_24622: i64 = 24622
73const NX_MAGIC_1737: i64 = 1737
74const NX_MAGIC_1821: i64 = 1821
75const NX_MAGIC_1561: i64 = 1561
76const NX_MAGIC_2634: i64 = 2634
77const NX_MAGIC_2410: i64 = 2410
78const NX_MAGIC_2575: i64 = 2575
79const NX_MAGIC_1353: i64 = 1353
80const NX_MAGIC_1188: i64 = 1188
81const NX_MAGIC_1163: i64 = 1163
82const NX_MAGIC_19890000000: i64 = 19890000000
83const NX_MAGIC_3301: i64 = 3301
84const NX_MAGIC_48675: i64 = 48675
85const NX_MAGIC_59720: i64 = 59720
86const NX_MAGIC_6417: i64 = 6417
87const NX_MAGIC_18981300: i64 = 18981300
88const NX_MAGIC_5683300: i64 = 5683300
89const NX_MAGIC_868100: i64 = 868100
90const NX_MAGIC_1024100: i64 = 1024100
91const NX_MAGIC_1482: i64 = 1482
92const NX_MAGIC_1076: i64 = 1076
93const NX_MAGIC_1345: i64 = 1345
94const NX_MAGIC_1524: i64 = 1524
95const NX_MAGIC_5203: i64 = 5203
96const NX_MAGIC_9539: i64 = 9539
97const NX_MAGIC_19180: i64 = 19180
98const NX_MAGIC_30070: i64 = 30070
99const NX_MAGIC_16000: i64 = 16000
100const NX_MAGIC_6400: i64 = 6400
101const NX_MAGIC_2766: i64 = 2766
102const NX_MAGIC_3948: i64 = 3948
103const NX_MAGIC_4317: i64 = 4317
104const NX_MAGIC_4571: i64 = 4571
105const NX_MAGIC_6778: i64 = 6778
106const NX_MAGIC_2362: i64 = 2362
107const NX_MAGIC_2773: i64 = 2773
108const NX_MAGIC_3140: i64 = 3140
109const NX_MAGIC_1782: i64 = 1782
110const NX_MAGIC_1800: i64 = 1800
111const NX_MAGIC_4348: i64 = 4348
112const NX_MAGIC_3927: i64 = 3927
113const NX_MAGIC_4333: i64 = 4333
114const NX_MAGIC_10759: i64 = 10759
115const NX_MAGIC_30688: i64 = 30688
116const NX_MAGIC_60182: i64 = 60182
117const NX_MAGIC_1669: i64 = 1669
118const NX_MAGIC_1595: i64 = 1595
119const NX_MAGIC_1346: i64 = 1346
120const NX_MAGIC_1681: i64 = 1681
121const NX_MAGIC_90560: i64 = 90560
122const NX_MAGIC_103774: i64 = 103774
123const NX_MAGIC_112897: i64 = 112897
124const NX_MAGIC_203830: i64 = 203830
125const NX_MAGIC_1325: i64 = 1325
126const NX_MAGIC_1686: i64 = 1686
127const NX_MAGIC_2034: i64 = 2034
128const NX_MAGIC_27759: i64 = 27759
129const NX_MAGIC_924795: i64 = 924795
130const NX_MAGIC_4150000: i64 = 4150000
131const NX_MAGIC_11400: i64 = 11400
132const NX_MAGIC_104744: i64 = 104744
133const NX_MAGIC_89530: i64 = 89530
134const NX_MAGIC_1408: i64 = 1408
135const NX_MAGIC_5832: i64 = 5832
136const NX_MAGIC_100000: i64 = 100000
137
138// ===== Q14 ==========================================================
139const NX_SOLAR_Q: nx_int = 16384
140
141// ===== Body IDs (sealed enum) ======================================
142// First slice: 9 bodies indexed 0..8. Add new IDs at the END for
143// subsequent slices per cardinal rule 13 additive-only.
144const NX_BODY_SUN: nx_int = 0
145const NX_BODY_MERCURY: nx_int = 1
146const NX_BODY_VENUS: nx_int = 2
147const NX_BODY_EARTH: nx_int = 3
148const NX_BODY_MARS: nx_int = 4
149const NX_BODY_JUPITER: nx_int = 5
150const NX_BODY_SATURN: nx_int = 6
151const NX_BODY_URANUS: nx_int = 7
152const NX_BODY_NEPTUNE: nx_int = 8
153
154// Major moons (13).
155const NX_BODY_LUNA: nx_int = 9 // Earth's moon
156const NX_BODY_PHOBOS: nx_int = 10
157const NX_BODY_DEIMOS: nx_int = 11
158const NX_BODY_IO: nx_int = 12
159const NX_BODY_EUROPA: nx_int = 13
160const NX_BODY_GANYMEDE: nx_int = 14
161const NX_BODY_CALLISTO: nx_int = 15
162const NX_BODY_TITAN: nx_int = 16
163const NX_BODY_ENCELADUS: nx_int = 17
164const NX_BODY_RHEA: nx_int = 18
165const NX_BODY_IAPETUS: nx_int = 19
166const NX_BODY_TITANIA: nx_int = 20
167const NX_BODY_OBERON: nx_int = 21
168const NX_BODY_TRITON: nx_int = 22
169
170// Dwarf planets (5).
171const NX_BODY_CERES: nx_int = 23
172const NX_BODY_PLUTO: nx_int = 24
173const NX_BODY_HAUMEA: nx_int = 25
174const NX_BODY_MAKEMAKE: nx_int = 26
175const NX_BODY_ERIS: nx_int = 27
176
177// Major asteroids (3).
178const NX_BODY_VESTA: nx_int = 28
179const NX_BODY_PALLAS: nx_int = 29
180const NX_BODY_HYGIEA: nx_int = 30
181
182// Notable comets (3).
183const NX_BODY_HALLEY: nx_int = 31 // 1P/Halley
184const NX_BODY_HALE_BOPP: nx_int = 32 // C/1995 O1
185const NX_BODY_BORISOV: nx_int = 33 // 2I/Borisov (interstellar)
186
187// Trans-Neptunian objects (3).
188const NX_BODY_SEDNA: nx_int = 34
189const NX_BODY_QUAOAR: nx_int = 35
190const NX_BODY_ORCUS: nx_int = 36
191
192const NX_SOLAR_BODY_COUNT: nx_int = 37
193
194// ===== Body kinds (sealed enum) ====================================
195// Routing categories. Per-kind generators (queued) dispatch on these.
196const NX_CELESTIAL_KIND_STAR: nx_int = 0
197const NX_CELESTIAL_KIND_TERRESTRIAL_PLANET: nx_int = 1
198const NX_CELESTIAL_KIND_GAS_GIANT: nx_int = 2
199const NX_CELESTIAL_KIND_ICE_GIANT: nx_int = 3
200const NX_CELESTIAL_KIND_DWARF_PLANET: nx_int = 4 // queued S5
201const NX_CELESTIAL_KIND_ROCKY_MOON: nx_int = 5 // queued S2
202const NX_CELESTIAL_KIND_ICY_MOON: nx_int = 6 // queued S2-S4
203const NX_CELESTIAL_KIND_ASTEROID: nx_int = 7 // queued S6
204const NX_CELESTIAL_KIND_COMET: nx_int = 8 // queued S7
205const NX_CELESTIAL_KIND_KUIPER_OBJECT: nx_int = 9 // queued S8
206
207const NX_CELESTIAL_KIND_COUNT: nx_int = 10
208
209// ===== Validity predicates =========================================
210func nx_solar_body_id_is_valid(id: nx_int) -> nx_int {
211 if id < 0 { return 0 }
212 if id >= NX_SOLAR_BODY_COUNT { return 0 }
213 return 1
214}
215
216func nx_celestial_kind_is_valid(k: nx_int) -> nx_int {
217 if k == NX_CELESTIAL_KIND_STAR { return 1 }
218 if k == NX_CELESTIAL_KIND_TERRESTRIAL_PLANET { return 1 }
219 if k == NX_CELESTIAL_KIND_GAS_GIANT { return 1 }
220 if k == NX_CELESTIAL_KIND_ICE_GIANT { return 1 }
221 if k == NX_CELESTIAL_KIND_DWARF_PLANET { return 1 }
222 if k == NX_CELESTIAL_KIND_ROCKY_MOON { return 1 }
223 if k == NX_CELESTIAL_KIND_ICY_MOON { return 1 }
224 if k == NX_CELESTIAL_KIND_ASTEROID { return 1 }
225 if k == NX_CELESTIAL_KIND_COMET { return 1 }
226 if k == NX_CELESTIAL_KIND_KUIPER_OBJECT { return 1 }
227 return 0
228}
229
230// ===== Accessor: kind ==============================================
231// Returns the sealed-enum NX_CELESTIAL_KIND_* for a body.
232// Out-of-bounds -> -1 (caller responsibility to check via
233// nx_solar_body_id_is_valid first).
234func nx_solar_body_kind(id: nx_int) -> nx_int {
235 if id == NX_BODY_SUN { return NX_CELESTIAL_KIND_STAR }
236 if id == NX_BODY_MERCURY { return NX_CELESTIAL_KIND_TERRESTRIAL_PLANET }
237 if id == NX_BODY_VENUS { return NX_CELESTIAL_KIND_TERRESTRIAL_PLANET }
238 if id == NX_BODY_EARTH { return NX_CELESTIAL_KIND_TERRESTRIAL_PLANET }
239 if id == NX_BODY_MARS { return NX_CELESTIAL_KIND_TERRESTRIAL_PLANET }
240 if id == NX_BODY_JUPITER { return NX_CELESTIAL_KIND_GAS_GIANT }
241 if id == NX_BODY_SATURN { return NX_CELESTIAL_KIND_GAS_GIANT }
242 if id == NX_BODY_URANUS { return NX_CELESTIAL_KIND_ICE_GIANT }
243 if id == NX_BODY_NEPTUNE { return NX_CELESTIAL_KIND_ICE_GIANT }
244 // Moons.
245 if id == NX_BODY_LUNA { return NX_CELESTIAL_KIND_ROCKY_MOON }
246 if id == NX_BODY_PHOBOS { return NX_CELESTIAL_KIND_ROCKY_MOON }
247 if id == NX_BODY_DEIMOS { return NX_CELESTIAL_KIND_ROCKY_MOON }
248 if id == NX_BODY_IO { return NX_CELESTIAL_KIND_ROCKY_MOON } // volcanic
249 if id == NX_BODY_EUROPA { return NX_CELESTIAL_KIND_ICY_MOON }
250 if id == NX_BODY_GANYMEDE { return NX_CELESTIAL_KIND_ICY_MOON }
251 if id == NX_BODY_CALLISTO { return NX_CELESTIAL_KIND_ICY_MOON }
252 if id == NX_BODY_TITAN { return NX_CELESTIAL_KIND_ICY_MOON } // has thick N2/CH4 atm
253 if id == NX_BODY_ENCELADUS { return NX_CELESTIAL_KIND_ICY_MOON }
254 if id == NX_BODY_RHEA { return NX_CELESTIAL_KIND_ICY_MOON }
255 if id == NX_BODY_IAPETUS { return NX_CELESTIAL_KIND_ICY_MOON }
256 if id == NX_BODY_TITANIA { return NX_CELESTIAL_KIND_ICY_MOON }
257 if id == NX_BODY_OBERON { return NX_CELESTIAL_KIND_ICY_MOON }
258 if id == NX_BODY_TRITON { return NX_CELESTIAL_KIND_ICY_MOON }
259 // Dwarf planets.
260 if id == NX_BODY_CERES { return NX_CELESTIAL_KIND_DWARF_PLANET }
261 if id == NX_BODY_PLUTO { return NX_CELESTIAL_KIND_DWARF_PLANET }
262 if id == NX_BODY_HAUMEA { return NX_CELESTIAL_KIND_DWARF_PLANET }
263 if id == NX_BODY_MAKEMAKE { return NX_CELESTIAL_KIND_DWARF_PLANET }
264 if id == NX_BODY_ERIS { return NX_CELESTIAL_KIND_DWARF_PLANET }
265 // Asteroids.
266 if id == NX_BODY_VESTA { return NX_CELESTIAL_KIND_ASTEROID }
267 if id == NX_BODY_PALLAS { return NX_CELESTIAL_KIND_ASTEROID }
268 if id == NX_BODY_HYGIEA { return NX_CELESTIAL_KIND_ASTEROID }
269 // Comets.
270 if id == NX_BODY_HALLEY { return NX_CELESTIAL_KIND_COMET }
271 if id == NX_BODY_HALE_BOPP { return NX_CELESTIAL_KIND_COMET }
272 if id == NX_BODY_BORISOV { return NX_CELESTIAL_KIND_COMET }
273 // TNOs / Kuiper-belt.
274 if id == NX_BODY_SEDNA { return NX_CELESTIAL_KIND_KUIPER_OBJECT }
275 if id == NX_BODY_QUAOAR { return NX_CELESTIAL_KIND_KUIPER_OBJECT }
276 if id == NX_BODY_ORCUS { return NX_CELESTIAL_KIND_KUIPER_OBJECT }
277 return 0 - 1
278}
279
280// ===== Accessor: parent body =======================================
281// Returns the body that this body orbits. Sun returns -1 (no parent).
282// Planets / dwarf planets / asteroids / comets / TNOs orbit the Sun.
283// Moons orbit their parent planet.
284func nx_solar_body_parent_id(id: nx_int) -> nx_int {
285 if id == NX_BODY_SUN { return 0 - 1 }
286 // Moons.
287 if id == NX_BODY_LUNA { return NX_BODY_EARTH }
288 if id == NX_BODY_PHOBOS { return NX_BODY_MARS }
289 if id == NX_BODY_DEIMOS { return NX_BODY_MARS }
290 if id == NX_BODY_IO { return NX_BODY_JUPITER }
291 if id == NX_BODY_EUROPA { return NX_BODY_JUPITER }
292 if id == NX_BODY_GANYMEDE { return NX_BODY_JUPITER }
293 if id == NX_BODY_CALLISTO { return NX_BODY_JUPITER }
294 if id == NX_BODY_TITAN { return NX_BODY_SATURN }
295 if id == NX_BODY_ENCELADUS { return NX_BODY_SATURN }
296 if id == NX_BODY_RHEA { return NX_BODY_SATURN }
297 if id == NX_BODY_IAPETUS { return NX_BODY_SATURN }
298 if id == NX_BODY_TITANIA { return NX_BODY_URANUS }
299 if id == NX_BODY_OBERON { return NX_BODY_URANUS }
300 if id == NX_BODY_TRITON { return NX_BODY_NEPTUNE }
301 // Everything else orbits the Sun directly.
302 return NX_BODY_SUN
303}
304
305// ===== Accessor: radius (km, Q14) =================================
306// Reference values from NASA JPL Horizons (mean equatorial radius
307// where applicable; volumetric mean for gas giants).
308func nx_solar_body_radius_km_q14(id: nx_int) -> nx_int {
309 let q: nx_int = NX_SOLAR_Q
310 if id == NX_BODY_SUN { return NX_MAGIC_695700 * q }
311 if id == NX_BODY_MERCURY { return NX_MAGIC_2440 * q }
312 if id == NX_BODY_VENUS { return NX_MAGIC_6052 * q }
313 if id == NX_BODY_EARTH { return NX_MAGIC_6371 * q }
314 if id == NX_BODY_MARS { return NX_MAGIC_3390 * q }
315 if id == NX_BODY_JUPITER { return NX_MAGIC_69911 * q }
316 if id == NX_BODY_SATURN { return NX_MAGIC_58232 * q }
317 if id == NX_BODY_URANUS { return NX_MAGIC_25362 * q }
318 if id == NX_BODY_NEPTUNE { return NX_MAGIC_24622 * q }
319 // Moons.
320 if id == NX_BODY_LUNA { return NX_MAGIC_1737 * q }
321 if id == NX_BODY_PHOBOS { return 11 * q }
322 if id == NX_BODY_DEIMOS { return 6 * q }
323 if id == NX_BODY_IO { return NX_MAGIC_1821 * q }
324 if id == NX_BODY_EUROPA { return NX_MAGIC_1561 * q }
325 if id == NX_BODY_GANYMEDE { return NX_MAGIC_2634 * q }
326 if id == NX_BODY_CALLISTO { return NX_MAGIC_2410 * q }
327 if id == NX_BODY_TITAN { return NX_MAGIC_2575 * q }
328 if id == NX_BODY_ENCELADUS { return 252 * q }
329 if id == NX_BODY_RHEA { return 764 * q }
330 if id == NX_BODY_IAPETUS { return 734 * q }
331 if id == NX_BODY_TITANIA { return 789 * q }
332 if id == NX_BODY_OBERON { return 761 * q }
333 if id == NX_BODY_TRITON { return NX_MAGIC_1353 * q }
334 // Dwarfs.
335 if id == NX_BODY_CERES { return 473 * q }
336 if id == NX_BODY_PLUTO { return NX_MAGIC_1188 * q }
337 if id == NX_BODY_HAUMEA { return 816 * q } // mean radius (elongated)
338 if id == NX_BODY_MAKEMAKE { return 715 * q }
339 if id == NX_BODY_ERIS { return NX_MAGIC_1163 * q }
340 // Asteroids.
341 if id == NX_BODY_VESTA { return 263 * q }
342 if id == NX_BODY_PALLAS { return 256 * q }
343 if id == NX_BODY_HYGIEA { return 217 * q }
344 // Comet nuclei (Halley ~ 5.5km mean; Hale-Bopp ~ 30km; Borisov ~ 0.5km).
345 if id == NX_BODY_HALLEY { return 5 * q }
346 if id == NX_BODY_HALE_BOPP { return 30 * q }
347 if id == NX_BODY_BORISOV { return 1 * q } // estimated
348 // TNOs.
349 if id == NX_BODY_SEDNA { return 500 * q }
350 if id == NX_BODY_QUAOAR { return 555 * q }
351 if id == NX_BODY_ORCUS { return 455 * q }
352 return 0
353}
354
355// ===== Accessor: mass (1e20 kg, Q14) ===============================
356// Mass in units of 1e20 kg so the value range fits cleanly in i64
357// after Q14 scaling. Real masses recoverable as
358// real_mass_kg = (value / Q14) * 1e20.
359//
360// NASA JPL reference values:
361// Sun 1.989e30 kg -> 1.989e10 e20kg
362// Mercury 3.301e23 kg -> 3.301e3 e20kg
363// Venus 4.8675e24 kg-> 48675 e20kg
364// Earth 5.972e24 kg -> 59720 e20kg
365// Mars 6.4171e23 kg-> 6417.1 e20kg (round to 6417)
366// Jupiter 1.898e27 kg -> 1.898e7 e20kg
367// Saturn 5.683e26 kg -> 5.683e6 e20kg
368// Uranus 8.681e25 kg -> 868100 e20kg
369// Neptune 1.024e26 kg -> 1.024e6 e20kg
370func nx_solar_body_mass_e20kg_q14(id: nx_int) -> nx_int {
371 let q: nx_int = NX_SOLAR_Q
372 if id == NX_BODY_SUN { return NX_MAGIC_19890000000 * q } // 1.989e10
373 if id == NX_BODY_MERCURY { return NX_MAGIC_3301 * q }
374 if id == NX_BODY_VENUS { return NX_MAGIC_48675 * q }
375 if id == NX_BODY_EARTH { return NX_MAGIC_59720 * q }
376 if id == NX_BODY_MARS { return NX_MAGIC_6417 * q }
377 if id == NX_BODY_JUPITER { return NX_MAGIC_18981300 * q } // 1.898e7
378 if id == NX_BODY_SATURN { return NX_MAGIC_5683300 * q }
379 if id == NX_BODY_URANUS { return NX_MAGIC_868100 * q }
380 if id == NX_BODY_NEPTUNE { return NX_MAGIC_1024100 * q }
381 // Major moons (in 1e20 kg).
382 if id == NX_BODY_LUNA { return 735 * q } // 7.35e22 / 1e20 = 735
383 if id == NX_BODY_IO { return 893 * q }
384 if id == NX_BODY_EUROPA { return 480 * q }
385 if id == NX_BODY_GANYMEDE { return NX_MAGIC_1482 * q }
386 if id == NX_BODY_CALLISTO { return NX_MAGIC_1076 * q }
387 if id == NX_BODY_TITAN { return NX_MAGIC_1345 * q }
388 if id == NX_BODY_TRITON { return 214 * q }
389 if id == NX_BODY_RHEA { return 23 * q } // 2.3e21 / 1e20 = 23
390 if id == NX_BODY_TITANIA { return 34 * q } // 3.4e21
391 if id == NX_BODY_OBERON { return 30 * q }
392 if id == NX_BODY_IAPETUS { return 18 * q }
393 if id == NX_BODY_ENCELADUS { return 1 * q } // 1.08e20 ~ 1
394 // Phobos/Deimos < 1 in these units; underflow accepted.
395 // Dwarfs (in 1e20 kg).
396 if id == NX_BODY_CERES { return 9 * q } // 9.4e20 -> 9.4
397 if id == NX_BODY_PLUTO { return 130 * q } // 1.3e22
398 if id == NX_BODY_HAUMEA { return 40 * q } // 4.0e21
399 if id == NX_BODY_MAKEMAKE { return 31 * q }
400 if id == NX_BODY_ERIS { return 166 * q } // 1.66e22
401 // Asteroids, comets, TNOs: << 1 in these units; underflow accepted.
402 return 0
403}
404
405// ===== Accessor: semi-major axis (AU, Q14) =========================
406// 1 AU = 149,597,870.7 km. Sun's own value is 0 (it's the focus).
407// Reference orbital elements from JPL Horizons.
408func nx_solar_body_semi_major_axis_au_q14(id: nx_int) -> nx_int {
409 let q: nx_int = NX_SOLAR_Q
410 if id == NX_BODY_SUN { return 0 }
411 // Decimal AU * Q14 -- compute literally for legibility.
412 if id == NX_BODY_MERCURY { return (387 * q) / 1000 } // 0.387
413 if id == NX_BODY_VENUS { return (723 * q) / 1000 } // 0.723
414 if id == NX_BODY_EARTH { return q } // 1.0
415 if id == NX_BODY_MARS { return (NX_MAGIC_1524 * q) / 1000 } // 1.524
416 if id == NX_BODY_JUPITER { return (NX_MAGIC_5203 * q) / 1000 } // 5.203
417 if id == NX_BODY_SATURN { return (NX_MAGIC_9539 * q) / 1000 } // 9.539
418 if id == NX_BODY_URANUS { return (NX_MAGIC_19180 * q) / 1000 } // 19.18
419 if id == NX_BODY_NEPTUNE { return (NX_MAGIC_30070 * q) / 1000 } // 30.07
420 // Moons -- semi-major axis around PARENT in fractions of AU.
421 // (For game-scale use, callers convert by also reading parent.)
422 if id == NX_BODY_LUNA { return (3 * q) / 1000 } // 0.00257 ~ 0.003
423 if id == NX_BODY_PHOBOS { return (1 * q) / NX_MAGIC_16000 } // ~0
424 if id == NX_BODY_DEIMOS { return (1 * q) / NX_MAGIC_6400 }
425 if id == NX_BODY_IO { return (3 * q) / 1000 } // 0.0028 AU
426 if id == NX_BODY_EUROPA { return (5 * q) / 1000 }
427 if id == NX_BODY_GANYMEDE { return (7 * q) / 1000 }
428 if id == NX_BODY_CALLISTO { return (13 * q) / 1000 }
429 if id == NX_BODY_TITAN { return (8 * q) / 1000 }
430 if id == NX_BODY_ENCELADUS { return (2 * q) / 1000 }
431 if id == NX_BODY_RHEA { return (4 * q) / 1000 }
432 if id == NX_BODY_IAPETUS { return (24 * q) / 1000 }
433 if id == NX_BODY_TITANIA { return (3 * q) / 1000 }
434 if id == NX_BODY_OBERON { return (4 * q) / 1000 }
435 if id == NX_BODY_TRITON { return (2 * q) / 1000 }
436 // Dwarf planets (AU).
437 if id == NX_BODY_CERES { return (NX_MAGIC_2766 * q) / 1000 } // 2.766
438 if id == NX_BODY_PLUTO { return (NX_MAGIC_3948 * q) / 100 } // 39.48
439 if id == NX_BODY_HAUMEA { return (NX_MAGIC_4317 * q) / 100 }
440 if id == NX_BODY_MAKEMAKE { return (NX_MAGIC_4571 * q) / 100 }
441 if id == NX_BODY_ERIS { return (NX_MAGIC_6778 * q) / 100 } // 67.78
442 // Asteroids (AU).
443 if id == NX_BODY_VESTA { return (NX_MAGIC_2362 * q) / 1000 }
444 if id == NX_BODY_PALLAS { return (NX_MAGIC_2773 * q) / 1000 }
445 if id == NX_BODY_HYGIEA { return (NX_MAGIC_3140 * q) / 1000 }
446 // Comets (semi-major axis varies wildly).
447 if id == NX_BODY_HALLEY { return (NX_MAGIC_1782 * q) / 100 } // 17.82 AU
448 if id == NX_BODY_HALE_BOPP { return (NX_MAGIC_1800 * q) / 10 } // ~180 AU
449 if id == NX_BODY_BORISOV { return 0 } // hyperbolic / interstellar
450 // TNOs.
451 if id == NX_BODY_SEDNA { return (506 * q) } // 506 AU
452 if id == NX_BODY_QUAOAR { return (NX_MAGIC_4348 * q) / 100 } // 43.48
453 if id == NX_BODY_ORCUS { return (NX_MAGIC_3927 * q) / 100 } // 39.27
454 return 0
455}
456
457// ===== Accessor: orbital period (Earth-days, Q14) ==================
458// Sidereal period. Sun's own period is 0 (no parent orbit).
459func nx_solar_body_orbital_period_days_q14(id: nx_int) -> nx_int {
460 let q: nx_int = NX_SOLAR_Q
461 if id == NX_BODY_SUN { return 0 }
462 if id == NX_BODY_MERCURY { return 88 * q }
463 if id == NX_BODY_VENUS { return 225 * q }
464 if id == NX_BODY_EARTH { return 365 * q }
465 if id == NX_BODY_MARS { return 687 * q }
466 if id == NX_BODY_JUPITER { return NX_MAGIC_4333 * q }
467 if id == NX_BODY_SATURN { return NX_MAGIC_10759 * q }
468 if id == NX_BODY_URANUS { return NX_MAGIC_30688 * q }
469 if id == NX_BODY_NEPTUNE { return NX_MAGIC_60182 * q }
470 // Moons.
471 if id == NX_BODY_LUNA { return 27 * q }
472 if id == NX_BODY_PHOBOS { return (32 * q) / 100 } // 0.32 day
473 if id == NX_BODY_DEIMOS { return (126 * q) / 100 }
474 if id == NX_BODY_IO { return (177 * q) / 100 } // 1.77
475 if id == NX_BODY_EUROPA { return (355 * q) / 100 }
476 if id == NX_BODY_GANYMEDE { return (716 * q) / 100 }
477 if id == NX_BODY_CALLISTO { return (NX_MAGIC_1669 * q) / 100 }
478 if id == NX_BODY_TITAN { return (NX_MAGIC_1595 * q) / 100 }
479 if id == NX_BODY_ENCELADUS { return (137 * q) / 100 }
480 if id == NX_BODY_RHEA { return (452 * q) / 100 }
481 if id == NX_BODY_IAPETUS { return 79 * q }
482 if id == NX_BODY_TITANIA { return (870 * q) / 100 }
483 if id == NX_BODY_OBERON { return (NX_MAGIC_1346 * q) / 100 }
484 if id == NX_BODY_TRITON { return (588 * q) / 100 } // retrograde but mag here
485 // Dwarfs.
486 if id == NX_BODY_CERES { return NX_MAGIC_1681 * q } // ~4.6 yr -> NX_MAGIC_1681 days
487 if id == NX_BODY_PLUTO { return NX_MAGIC_90560 * q } // 248 yr
488 if id == NX_BODY_HAUMEA { return NX_MAGIC_103774 * q }
489 if id == NX_BODY_MAKEMAKE { return NX_MAGIC_112897 * q }
490 if id == NX_BODY_ERIS { return NX_MAGIC_203830 * q } // 558 yr
491 // Asteroids.
492 if id == NX_BODY_VESTA { return NX_MAGIC_1325 * q }
493 if id == NX_BODY_PALLAS { return NX_MAGIC_1686 * q }
494 if id == NX_BODY_HYGIEA { return NX_MAGIC_2034 * q }
495 // Comets (Halley 76 yr; Hale-Bopp 2533 yr; Borisov hyperbolic).
496 if id == NX_BODY_HALLEY { return NX_MAGIC_27759 * q }
497 if id == NX_BODY_HALE_BOPP { return NX_MAGIC_924795 * q }
498 if id == NX_BODY_BORISOV { return 0 } // hyperbolic
499 // TNOs (very long).
500 if id == NX_BODY_SEDNA { return NX_MAGIC_4150000 * q } // ~NX_MAGIC_11400 yr
501 if id == NX_BODY_QUAOAR { return NX_MAGIC_104744 * q }
502 if id == NX_BODY_ORCUS { return NX_MAGIC_89530 * q }
503 return 0
504}
505
506// ===== Accessor: rotation period (hours, Q14) ======================
507// Sidereal rotation. Negative values indicate retrograde rotation
508// (Venus + Uranus rotate retrograde).
509func nx_solar_body_rotation_period_hours_q14(id: nx_int) -> nx_int {
510 let q: nx_int = NX_SOLAR_Q
511 if id == NX_BODY_SUN { return 609 * q } // ~25.4 days equatorial
512 if id == NX_BODY_MERCURY { return NX_MAGIC_1408 * q } // 58.6 days
513 if id == NX_BODY_VENUS { return 0 - NX_MAGIC_5832 * q } // retrograde 243 days
514 if id == NX_BODY_EARTH { return 24 * q }
515 if id == NX_BODY_MARS { return 25 * q } // 24.6 hours
516 if id == NX_BODY_JUPITER { return 10 * q } // 9.93 hours
517 if id == NX_BODY_SATURN { return 11 * q } // 10.7 hours
518 if id == NX_BODY_URANUS { return 0 - 17 * q } // retrograde + axial tilt
519 if id == NX_BODY_NEPTUNE { return 16 * q } // 16.1 hours
520 return 0
521}
522
523// ===== Accessor: atmosphere flag ===================================
524// 1 if the body has a measurable atmosphere, 0 if effectively airless
525// (vacuum) or N/A (Sun). Sun is special-cased: it has a corona +
526// solar wind but no traditional "atmosphere" for landing purposes.
527func nx_solar_body_has_atmosphere(id: nx_int) -> nx_int {
528 if id == NX_BODY_SUN { return 1 } // photosphere/chromosphere/corona
529 if id == NX_BODY_MERCURY { return 0 } // exosphere only; effectively none
530 if id == NX_BODY_VENUS { return 1 } // 92 atm CO2
531 if id == NX_BODY_EARTH { return 1 } // 1 atm N2/O2
532 if id == NX_BODY_MARS { return 1 } // 0.006 atm CO2 -- thin but present
533 if id == NX_BODY_JUPITER { return 1 } // gas giant -- it IS atmosphere
534 if id == NX_BODY_SATURN { return 1 }
535 if id == NX_BODY_URANUS { return 1 } // ice giant -- atmosphere into mantle
536 if id == NX_BODY_NEPTUNE { return 1 }
537 // Titan: thick N2/CH4 atmosphere (only major moon with one).
538 if id == NX_BODY_TITAN { return 1 }
539 // Triton: thin N2 atm (~1.4 Pa).
540 if id == NX_BODY_TRITON { return 1 }
541 // Pluto: thin N2 atm during perihelion.
542 if id == NX_BODY_PLUTO { return 1 }
543 // Comets: gas coma develops near perihelion.
544 if id == NX_BODY_HALLEY { return 1 }
545 if id == NX_BODY_HALE_BOPP { return 1 }
546 if id == NX_BODY_BORISOV { return 1 }
547 return 0
548}
549
550// ===== Accessor: rings flag ========================================
551// 1 if the body has a ring system visible from Earth or by close
552// flyby. Jupiter's rings are faint but real (Voyager).
553func nx_solar_body_has_rings(id: nx_int) -> nx_int {
554 if id == NX_BODY_SUN { return 0 }
555 if id == NX_BODY_MERCURY { return 0 }
556 if id == NX_BODY_VENUS { return 0 }
557 if id == NX_BODY_EARTH { return 0 }
558 if id == NX_BODY_MARS { return 0 }
559 if id == NX_BODY_JUPITER { return 1 } // faint (Voyager-discovered)
560 if id == NX_BODY_SATURN { return 1 } // canonical rings
561 if id == NX_BODY_URANUS { return 1 } // narrow + dark
562 if id == NX_BODY_NEPTUNE { return 1 } // partial arcs
563 return 0
564}
565
566// ===== Self-test ====================================================
567func main() -> i64 {
568 let q: nx_int = NX_SOLAR_Q
569
570 // T1: body-count + validity predicates. Catalog expanded to 37
571 // bodies (Sun + 8 planets + 13 moons + 5 dwarfs + 3 asteroids +
572 // 3 comets + 3 TNOs).
573 if NX_SOLAR_BODY_COUNT != 37 { return nx_hal_exit(1) }
574 if nx_solar_body_id_is_valid(0) != 1 { return nx_hal_exit(2) }
575 if nx_solar_body_id_is_valid(36) != 1 { return nx_hal_exit(3) }
576 if nx_solar_body_id_is_valid(0 - 1) != 0 { return nx_hal_exit(4) }
577 if nx_solar_body_id_is_valid(37) != 0 { return nx_hal_exit(5) }
578 if nx_solar_body_id_is_valid(99) != 0 { return nx_hal_exit(6) }
579 if nx_celestial_kind_is_valid(NX_CELESTIAL_KIND_STAR) != 1 { return nx_hal_exit(7) }
580 if nx_celestial_kind_is_valid(NX_CELESTIAL_KIND_COMET) != 1 { return nx_hal_exit(8) }
581 if nx_celestial_kind_is_valid(99) != 0 { return nx_hal_exit(9) }
582
583 // T2: body kinds.
584 if nx_solar_body_kind(NX_BODY_SUN) != NX_CELESTIAL_KIND_STAR { return nx_hal_exit(10) }
585 if nx_solar_body_kind(NX_BODY_MERCURY) != NX_CELESTIAL_KIND_TERRESTRIAL_PLANET { return nx_hal_exit(11) }
586 if nx_solar_body_kind(NX_BODY_VENUS) != NX_CELESTIAL_KIND_TERRESTRIAL_PLANET { return nx_hal_exit(12) }
587 if nx_solar_body_kind(NX_BODY_EARTH) != NX_CELESTIAL_KIND_TERRESTRIAL_PLANET { return nx_hal_exit(13) }
588 if nx_solar_body_kind(NX_BODY_MARS) != NX_CELESTIAL_KIND_TERRESTRIAL_PLANET { return nx_hal_exit(14) }
589 if nx_solar_body_kind(NX_BODY_JUPITER) != NX_CELESTIAL_KIND_GAS_GIANT { return nx_hal_exit(15) }
590 if nx_solar_body_kind(NX_BODY_SATURN) != NX_CELESTIAL_KIND_GAS_GIANT { return nx_hal_exit(16) }
591 if nx_solar_body_kind(NX_BODY_URANUS) != NX_CELESTIAL_KIND_ICE_GIANT { return nx_hal_exit(17) }
592 if nx_solar_body_kind(NX_BODY_NEPTUNE) != NX_CELESTIAL_KIND_ICE_GIANT { return nx_hal_exit(18) }
593
594 // T3: radius sanity. Sun is largest by far; Earth ~ 6371 km.
595 let r_earth: nx_int = nx_solar_body_radius_km_q14(NX_BODY_EARTH)
596 if r_earth != NX_MAGIC_6371 * q { return nx_hal_exit(20) }
597 // Sun radius > 100 * Earth radius.
598 if nx_solar_body_radius_km_q14(NX_BODY_SUN) <= 100 * r_earth {
599 return nx_hal_exit(21)
600 }
601 // Jupiter > Earth.
602 if nx_solar_body_radius_km_q14(NX_BODY_JUPITER) <= r_earth {
603 return nx_hal_exit(22)
604 }
605 // Mars < Earth.
606 if nx_solar_body_radius_km_q14(NX_BODY_MARS) >= r_earth {
607 return nx_hal_exit(23)
608 }
609
610 // T4: semi-major axis ordering: Mercury < Venus < Earth < Mars <
611 // Jupiter < Saturn < Uranus < Neptune.
612 let am: nx_int = nx_solar_body_semi_major_axis_au_q14(NX_BODY_MERCURY)
613 let av: nx_int = nx_solar_body_semi_major_axis_au_q14(NX_BODY_VENUS)
614 let ae: nx_int = nx_solar_body_semi_major_axis_au_q14(NX_BODY_EARTH)
615 let aj: nx_int = nx_solar_body_semi_major_axis_au_q14(NX_BODY_JUPITER)
616 let an: nx_int = nx_solar_body_semi_major_axis_au_q14(NX_BODY_NEPTUNE)
617 if am >= av { return nx_hal_exit(30) }
618 if av >= ae { return nx_hal_exit(31) }
619 if ae != q { return nx_hal_exit(32) } // Earth = 1.0 AU exact
620 if aj <= ae { return nx_hal_exit(33) }
621 if an <= aj { return nx_hal_exit(34) }
622
623 // T5: orbital period: longer planets take longer. Kepler 3 hint.
624 let pm: nx_int = nx_solar_body_orbital_period_days_q14(NX_BODY_MERCURY)
625 let pe: nx_int = nx_solar_body_orbital_period_days_q14(NX_BODY_EARTH)
626 let pn: nx_int = nx_solar_body_orbital_period_days_q14(NX_BODY_NEPTUNE)
627 if pm >= pe { return nx_hal_exit(40) }
628 if pe >= pn { return nx_hal_exit(41) }
629 if pe != 365 * q { return nx_hal_exit(42) } // Earth ~ 365 days
630
631 // T6: rotation period for Venus is retrograde (negative).
632 if nx_solar_body_rotation_period_hours_q14(NX_BODY_VENUS) >= 0 {
633 return nx_hal_exit(50)
634 }
635 // Uranus retrograde.
636 if nx_solar_body_rotation_period_hours_q14(NX_BODY_URANUS) >= 0 {
637 return nx_hal_exit(51)
638 }
639 // Earth ~ 24 hours.
640 if nx_solar_body_rotation_period_hours_q14(NX_BODY_EARTH) != 24 * q {
641 return nx_hal_exit(52)
642 }
643
644 // T7: atmosphere flags.
645 // Mercury -- effectively none.
646 if nx_solar_body_has_atmosphere(NX_BODY_MERCURY) != 0 { return nx_hal_exit(60) }
647 // Venus / Earth / Mars / gas giants -- yes.
648 if nx_solar_body_has_atmosphere(NX_BODY_VENUS) != 1 { return nx_hal_exit(61) }
649 if nx_solar_body_has_atmosphere(NX_BODY_EARTH) != 1 { return nx_hal_exit(62) }
650 if nx_solar_body_has_atmosphere(NX_BODY_MARS) != 1 { return nx_hal_exit(63) }
651 if nx_solar_body_has_atmosphere(NX_BODY_JUPITER) != 1 { return nx_hal_exit(64) }
652 if nx_solar_body_has_atmosphere(NX_BODY_NEPTUNE) != 1 { return nx_hal_exit(65) }
653
654 // T8: ring flags -- only the 4 outer planets have rings.
655 if nx_solar_body_has_rings(NX_BODY_MERCURY) != 0 { return nx_hal_exit(70) }
656 if nx_solar_body_has_rings(NX_BODY_EARTH) != 0 { return nx_hal_exit(71) }
657 if nx_solar_body_has_rings(NX_BODY_MARS) != 0 { return nx_hal_exit(72) }
658 if nx_solar_body_has_rings(NX_BODY_JUPITER) != 1 { return nx_hal_exit(73) }
659 if nx_solar_body_has_rings(NX_BODY_SATURN) != 1 { return nx_hal_exit(74) }
660 if nx_solar_body_has_rings(NX_BODY_URANUS) != 1 { return nx_hal_exit(75) }
661 if nx_solar_body_has_rings(NX_BODY_NEPTUNE) != 1 { return nx_hal_exit(76) }
662
663 // T9: mass ordering. Sun massively biggest. Jupiter > Saturn >
664 // Neptune > Uranus > Earth > Venus > Mars > Mercury.
665 let mass_sun: nx_int = nx_solar_body_mass_e20kg_q14(NX_BODY_SUN)
666 let mass_jup: nx_int = nx_solar_body_mass_e20kg_q14(NX_BODY_JUPITER)
667 let mass_earth: nx_int = nx_solar_body_mass_e20kg_q14(NX_BODY_EARTH)
668 let mass_merc: nx_int = nx_solar_body_mass_e20kg_q14(NX_BODY_MERCURY)
669 if mass_sun <= mass_jup { return nx_hal_exit(80) }
670 if mass_jup <= mass_earth { return nx_hal_exit(81) }
671 if mass_earth <= mass_merc { return nx_hal_exit(82) }
672 // Sun mass at least 100,000 * Earth's (real ratio ~333,000).
673 if mass_sun < NX_MAGIC_100000 * mass_earth {
674 return nx_hal_exit(83)
675 }
676
677 // T10: Moon kind + parent.
678 if nx_solar_body_kind(NX_BODY_LUNA) != NX_CELESTIAL_KIND_ROCKY_MOON { return nx_hal_exit(90) }
679 if nx_solar_body_kind(NX_BODY_IO) != NX_CELESTIAL_KIND_ROCKY_MOON { return nx_hal_exit(91) }
680 if nx_solar_body_kind(NX_BODY_EUROPA) != NX_CELESTIAL_KIND_ICY_MOON { return nx_hal_exit(92) }
681 if nx_solar_body_kind(NX_BODY_TRITON) != NX_CELESTIAL_KIND_ICY_MOON { return nx_hal_exit(93) }
682 if nx_solar_body_parent_id(NX_BODY_LUNA) != NX_BODY_EARTH { return nx_hal_exit(94) }
683 if nx_solar_body_parent_id(NX_BODY_PHOBOS) != NX_BODY_MARS { return nx_hal_exit(95) }
684 if nx_solar_body_parent_id(NX_BODY_GANYMEDE) != NX_BODY_JUPITER { return nx_hal_exit(96) }
685 if nx_solar_body_parent_id(NX_BODY_TITAN) != NX_BODY_SATURN { return nx_hal_exit(97) }
686 if nx_solar_body_parent_id(NX_BODY_TITANIA) != NX_BODY_URANUS { return nx_hal_exit(98) }
687 if nx_solar_body_parent_id(NX_BODY_TRITON) != NX_BODY_NEPTUNE { return nx_hal_exit(99) }
688 if nx_solar_body_parent_id(NX_BODY_SUN) != (0 - 1) { return nx_hal_exit(100) }
689 if nx_solar_body_parent_id(NX_BODY_CERES) != NX_BODY_SUN { return nx_hal_exit(101) }
690 if nx_solar_body_parent_id(NX_BODY_PLUTO) != NX_BODY_SUN { return nx_hal_exit(102) }
691 if nx_solar_body_parent_id(NX_BODY_HALLEY) != NX_BODY_SUN { return nx_hal_exit(103) }
692 if nx_solar_body_parent_id(NX_BODY_SEDNA) != NX_BODY_SUN { return nx_hal_exit(104) }
693
694 // T11: Dwarf + asteroid + comet + TNO kinds.
695 if nx_solar_body_kind(NX_BODY_CERES) != NX_CELESTIAL_KIND_DWARF_PLANET { return nx_hal_exit(110) }
696 if nx_solar_body_kind(NX_BODY_PLUTO) != NX_CELESTIAL_KIND_DWARF_PLANET { return nx_hal_exit(111) }
697 if nx_solar_body_kind(NX_BODY_ERIS) != NX_CELESTIAL_KIND_DWARF_PLANET { return nx_hal_exit(112) }
698 if nx_solar_body_kind(NX_BODY_VESTA) != NX_CELESTIAL_KIND_ASTEROID { return nx_hal_exit(113) }
699 if nx_solar_body_kind(NX_BODY_HALLEY) != NX_CELESTIAL_KIND_COMET { return nx_hal_exit(114) }
700 if nx_solar_body_kind(NX_BODY_BORISOV) != NX_CELESTIAL_KIND_COMET { return nx_hal_exit(115) }
701 if nx_solar_body_kind(NX_BODY_SEDNA) != NX_CELESTIAL_KIND_KUIPER_OBJECT { return nx_hal_exit(116) }
702
703 // T12: Radius sanity for moons + small bodies.
704 if nx_solar_body_radius_km_q14(NX_BODY_GANYMEDE) <= nx_solar_body_radius_km_q14(NX_BODY_LUNA) {
705 return nx_hal_exit(120)
706 }
707 if nx_solar_body_radius_km_q14(NX_BODY_PHOBOS) >= nx_solar_body_radius_km_q14(NX_BODY_LUNA) {
708 return nx_hal_exit(121)
709 }
710 if nx_solar_body_radius_km_q14(NX_BODY_PLUTO) >= nx_solar_body_radius_km_q14(NX_BODY_LUNA) {
711 return nx_hal_exit(122)
712 }
713
714 // T13: Titan has atmosphere; other major moons don't.
715 if nx_solar_body_has_atmosphere(NX_BODY_TITAN) != 1 { return nx_hal_exit(130) }
716 if nx_solar_body_has_atmosphere(NX_BODY_LUNA) != 0 { return nx_hal_exit(131) }
717 if nx_solar_body_has_atmosphere(NX_BODY_EUROPA) != 0 { return nx_hal_exit(132) }
718
719 // T14: Pluto orbits further than Neptune; Sedna further than Pluto.
720 if nx_solar_body_semi_major_axis_au_q14(NX_BODY_PLUTO) <= nx_solar_body_semi_major_axis_au_q14(NX_BODY_NEPTUNE) {
721 return nx_hal_exit(140)
722 }
723 if nx_solar_body_semi_major_axis_au_q14(NX_BODY_SEDNA) <= nx_solar_body_semi_major_axis_au_q14(NX_BODY_PLUTO) {
724 return nx_hal_exit(141)
725 }
726
727 return 0
728}