vibe coding an orbital mechanics simulation to try out claude code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

360 lines
9.5 KiB

#!/usr/bin/env python3
"""
Precalculate moon orbit TOML config from planetary_data.md values.
Converts mean anomaly (M) at J2000 to true anomaly (nu) via Kepler's equation,
then outputs a complete test TOML config with correct planetary masses,
eccentricities, inclinations, and orbital elements.
Usage:
python3 scripts/precalc_moon_orbits.py
Outputs:
- TOML config to stdout (redirect to tests/test_moon_orbits.toml)
- Console summary of computed values
"""
import sys, math
sys.path.insert(0, "scripts")
from sim_engine import (
solve_kepler_elliptical,
orbital_to_cartesian,
vmag,
G,
normalize_angle,
OrbitalElements,
)
# Planetary data from docs/planetary_data.md
SUN_MASS = 1.989e30
SUN_RADIUS = 6.96e8
PLANETS = [
{
"name": "Venus",
"mass": 4.87e24,
"radius": 6.052e6,
"parent": 0,
"a_au": 0.723,
"e": 0.007,
"inc_deg": 3.39,
"Omega_deg": 76.68,
"omega_deg": 54.92,
"M_deg": 50.38,
},
{
"name": "Earth",
"mass": 5.97e24,
"radius": 6.378e6,
"parent": 0,
"a_au": 1.000,
"e": 0.017,
"inc_deg": 0.00,
"Omega_deg": 0.00,
"omega_deg": 102.94,
"M_deg": -2.47,
},
{
"name": "Mars",
"mass": 6.42e23,
"radius": 3.396e6,
"parent": 0,
"a_au": 1.524,
"e": 0.093,
"inc_deg": 1.85,
"Omega_deg": 49.56,
"omega_deg": 286.50,
"M_deg": 19.39,
},
{
"name": "Jupiter",
"mass": 1.898e27,
"radius": 71.492e6,
"parent": 0,
"a_au": 5.203,
"e": 0.049,
"inc_deg": 1.31,
"Omega_deg": 100.47,
"omega_deg": 274.25,
"M_deg": 19.67,
},
{
"name": "Saturn",
"mass": 5.683e26,
"radius": 60.268e6,
"parent": 0,
"a_au": 9.537,
"e": 0.057,
"inc_deg": 2.49,
"Omega_deg": 113.66,
"omega_deg": 338.94,
"M_deg": -42.64,
},
{
"name": "Uranus",
"mass": 8.68e25,
"radius": 25.559e6,
"parent": 0,
"a_au": 19.19,
"e": 0.046,
"inc_deg": 0.77,
"Omega_deg": 74.02,
"omega_deg": 96.94,
"M_deg": 142.28,
},
{
"name": "Neptune",
"mass": 1.02e26,
"radius": 24.764e6,
"parent": 0,
"a_au": 30.07,
"e": 0.010,
"inc_deg": 1.77,
"Omega_deg": 131.78,
"omega_deg": 273.18,
"M_deg": -100.08,
},
]
AU = 1.496e11 # meters
MOONS = [
{
"name": "Moon",
"mass": 7.35e22,
"radius": 1.738e6,
"parent": "Earth",
"a_km": 384400,
"e": 0.055,
"inc_deg": 5.16,
"Omega_deg": 125.08,
"omega_deg": 318.15,
"M_deg": 135.27,
},
{
"name": "Io",
"mass": 8.93e23,
"radius": 1.822e6,
"parent": "Jupiter",
"a_km": 421800,
"e": 0.004,
"inc_deg": 0.00,
"Omega_deg": 0.0,
"omega_deg": 49.1,
"M_deg": 330.9,
},
{
"name": "Europa",
"mass": 4.80e23,
"radius": 1.561e6,
"parent": "Jupiter",
"a_km": 671100,
"e": 0.009,
"inc_deg": 0.50,
"Omega_deg": 184.0,
"omega_deg": 45.0,
"M_deg": 345.4,
},
{
"name": "Ganymede",
"mass": 1.48e24,
"radius": 2.631e6,
"parent": "Jupiter",
"a_km": 1070400,
"e": 0.001,
"inc_deg": 0.20,
"Omega_deg": 58.5,
"omega_deg": 198.3,
"M_deg": 324.8,
},
{
"name": "Callisto",
"mass": 1.08e24,
"radius": 2.410e6,
"parent": "Jupiter",
"a_km": 1882700,
"e": 0.007,
"inc_deg": 0.30,
"Omega_deg": 309.1,
"omega_deg": 43.8,
"M_deg": 87.4,
},
{
"name": "Titan",
"mass": 1.35e24,
"radius": 2.575e6,
"parent": "Saturn",
"a_km": 1221900,
"e": 0.029,
"inc_deg": 0.30,
"Omega_deg": 78.6,
"omega_deg": 78.3,
"M_deg": 11.7,
},
]
# Kepler conversion: M -> E -> nu
def mean_to_true_anomaly(M_deg, e):
"""Convert mean anomaly (degrees) to true anomaly (radians) via Kepler's equation."""
M = math.radians(M_deg)
E = solve_kepler_elliptical(M, e)
# tan(nu/2) = sqrt((1+e)/(1-e)) * tan(E/2)
tan_half_e = math.tan(E / 2.0)
tan_half_nu = math.sqrt((1.0 + e) / (1.0 - e)) * tan_half_e
nu = 2.0 * math.atan(tan_half_nu)
return normalize_angle(nu)
# Print TOML config
def print_toml():
print("# Moon Orbits Test Configuration")
print("# Auto-generated by scripts/precalc_moon_orbits.py")
print("# Data source: docs/planetary_data.md (JPL planetary facts)")
print("# Mean anomaly converted to true anomaly via Kepler's equation")
print()
# Sun
print('[[bodies]]')
print('name = "Sun"')
print(f"mass = {SUN_MASS}")
print(f"radius = {SUN_RADIUS}")
print("parent_index = -1")
print('color = { r = 1.0, g = 1.0, b = 0.0 }')
print("orbit.semi_major_axis = 0.0")
print("orbit.eccentricity = 0.0")
print("orbit.true_anomaly = 0.0")
print()
# Planets
for p in PLANETS:
a_m = p["a_au"] * AU
inc = math.radians(p["inc_deg"])
Omega = math.radians(p["Omega_deg"])
omega = math.radians(p["omega_deg"])
nu = mean_to_true_anomaly(p["M_deg"], p["e"])
print('[[bodies]]')
print(f'name = "{p["name"]}"')
print(f'mass = {p["mass"]}')
print(f'radius = {p["radius"]}')
print(f'parent_index = {p["parent"]}')
print('color = { r = 0.5, g = 0.5, b = 0.5 }')
print(f"orbit.semi_major_axis = {a_m:.6e}")
print(f"orbit.eccentricity = {p['e']}")
print(f"orbit.inclination = {inc:.15f}")
print(f"orbit.longitude_of_ascending_node = {Omega:.15f}")
print(f"orbit.argument_of_periapsis = {omega:.15f}")
print(f"orbit.true_anomaly = {nu:.15f}")
print()
# Moons
for m in MOONS:
a_m = m["a_km"] * 1000.0
inc = math.radians(m["inc_deg"])
Omega = math.radians(m["Omega_deg"])
omega = math.radians(m["omega_deg"])
nu = mean_to_true_anomaly(m["M_deg"], m["e"])
print('[[bodies]]')
print(f'name = "{m["name"]}"')
print(f'mass = {m["mass"]}')
print(f'radius = {m["radius"]}')
parent_idx = {"Earth": 2, "Jupiter": 4, "Saturn": 5}[m["parent"]]
print(f'parent_index = {parent_idx}')
print('color = { r = 0.7, g = 0.7, b = 0.7 }')
print(f"orbit.semi_major_axis = {a_m:.6e}")
print(f"orbit.eccentricity = {m['e']}")
print(f"orbit.inclination = {inc:.15f}")
print(f"orbit.longitude_of_ascending_node = {Omega:.15f}")
print(f"orbit.argument_of_periapsis = {omega:.15f}")
print(f"orbit.true_anomaly = {nu:.15f}")
print()
# Print computed values summary (for verification)
def print_summary():
print("# === Computed True Anomalies ===")
print()
for m in MOONS:
nu = mean_to_true_anomaly(m["M_deg"], m["e"])
nu_deg = nu * 180.0 / math.pi
a_m = m["a_km"] * 1000.0
mu = G * eval(f"{m['parent']}_MASS") if m["parent"] in globals() else 0
# Compute period
parent_mass = {"Earth": 5.97e24, "Jupiter": 1.898e27, "Saturn": 5.683e26}[m["parent"]]
mu = G * parent_mass
T = 2.0 * math.pi * math.sqrt(a_m**3 / mu)
T_days = T / 86400.0
print(
f'{m["name"]:10s}: M={m["M_deg"]:7.2f}deg -> nu={nu_deg:7.2f}deg '
f"a={m['a_km']:>8.0f}km e={m['e']:.3f} "
f"T={T_days:.3f}d"
)
print()
print("# === Initial positions (from true anomaly) ===")
print("# Format: name, r (m), nu (deg)")
parent_masses = {
"Earth": 5.97e24,
"Jupiter": 1.898e27,
"Saturn": 5.683e26,
}
for m in MOONS:
a_m = m["a_km"] * 1000.0
nu = mean_to_true_anomaly(m["M_deg"], m["e"])
pm = parent_masses[m["parent"]]
el = OrbitalElements(
a=a_m, e=m["e"], nu=nu,
inc=math.radians(m["inc_deg"]),
Omega=math.radians(m["Omega_deg"]),
omega=math.radians(m["omega_deg"]),
)
pos, vel = orbital_to_cartesian(el, pm)
r = vmag(pos)
print(f'{m["name"]:10s}: r={r:.3f} m, nu={nu*180/math.pi:.2f}deg')
def print_cpp_expected_values():
"""Print C++-style comments with expected values for embedding in test."""
print("# === Expected period values (from precalc) ===")
print("# Format: name -> period_seconds, tolerance_seconds")
parent_masses = {
"Earth": 5.97e24, "Jupiter": 1.898e27, "Saturn": 5.683e26,
}
for m in MOONS:
a_m = m["a_km"] * 1000.0
pm = parent_masses[m["parent"]]
mu = G * pm
T = 2.0 * math.pi * math.sqrt(a_m**3 / mu)
T_days = T / 86400.0
# Tolerance: ~0.5% of period (simulation drift over multiple orbits)
tol = 0.005 * T
print(f'// {m["name"]:10s} T={T:>14.2f}s tol={tol:>8.1f}s ({T_days:.3f}d)')
print()
if __name__ == "__main__":
print_summary()
print()
print("=" * 60)
print("# TOML CONFIG (copy below this line)")
print("=" * 60)
print()
print_toml()
print()
print("=" * 60)
print("# CPP EXPECTED VALUES (copy into test fixture)")
print("=" * 60)
print()
print_cpp_expected_values()