# Orbital Mechanics Simulation - Conceptual Reference ## Overview 2-body orbital mechanics simulator using **analytical propagation** for precise Keplerian trajectories. Supports elliptical, parabolic, and hyperbolic orbits with dynamic Sphere of Influence (SOI) transitions, impulsive burns, and 3D visualization via Raylib. ## Architecture Modular C-style C++ (structs/functions, no classes). Module dependencies: ``` Main → Simulation → Orbital Mechanics → Physics → Maneuver → Rendezvous → Config Loader ``` Renderer and UI Renderer depend on Config Loader for display data. Test Utilities are standalone. ## Coordinate Frame System **Local Frame**: Position/velocity relative to parent body. Primary system for orbital mechanics calculations. Maintains double-precision accuracy for small orbits (LEO, lunar). **Global Frame**: Position/velocity from simulation origin (Sun at 0,0,0). Computed as `global = parent.global + local`. Used for SOI distance calculations and rendering. **Conversion**: Simple vector addition/subtraction. All bodies and spacecraft store both local and global coordinates. ## Core Data Structures ### Vec3 3D vector (x, y, z). Operations: add, sub, cross, scale, magnitude, distance, normalize, dot. ### Mat3 3x3 row-major matrix. Used for orbital plane rotations (z-x-z Euler angles). ### OrbitalElements Keplerian elements with union for parabolic/hyperbolic distinction: ```cpp struct OrbitalElements { union { double semi_major_axis; // elliptical (e<1), hyperbolic (e>1) double semi_latus_rectum; // parabolic (e≈1) }; double eccentricity; double true_anomaly; double inclination; double longitude_of_ascending_node; double argument_of_periapsis; }; ``` ### CelestialBody Planet/moon with mass, radius, parent_index, color, orbital elements, local/global coordinates, SOI radius. ### Spacecraft Similar to CelestialBody but without radius or SOI. Supports standalone (parent_index = -1) and orbiting spacecraft. ### Maneuver Impulsive burn: name, craft_index, direction, delta_v, trigger_type, trigger_value, scheduled_dt, executed flag, executed_time. ### HohmannTransfer Hohmann transfer calculation result: dv1 (first burn delta-v), dv2 (second burn delta-v), transfer_time (half-period of transfer ellipse), true_anomaly_2 (true anomaly at second burn, typically π). ### SimulationState Top-level container: arrays of bodies/spacecraft/maneuvers (with counts and capacities), time, dt, config_name. ## Orbital Propagation Algorithm Analytical propagation solves Kepler's equations exactly (no integration error, perfect energy conservation). **Common Pattern**: 1. Compute mean motion: n = √(μ/|a|³) 2. Convert true anomaly to appropriate anomaly form 3. Advance mean anomaly: M_new = M + n·dt 4. Solve Kepler's equation for new anomaly 5. Convert back to true anomaly 6. Update elements **Elliptical (e < 1)**: Newton-Raphson on E - e·sin(E) = M. Tolerance: 1e-10, max 50 iterations. **Parabolic (|e-1| < PARABOLIC_TOLERANCE)**: Barker's equation D + D³/3 = M with closed-form solution using cubic roots. Mean motion: n = √(μ/p³) where p = semi_latus_rectum. **Hyperbolic (e > 1)**: Newton-Raphson on H - e·sinh(H) = M. Mean motion: n = √(μ/(-a)³) using negative semi-major axis. Initial guess: H = log(2M/e) when e·sinh(M) > M, else H = M. ## Orbital Element Reconstruction Elements are dynamically maintained and reconstructed from Cartesian state when: - **SOI transitions**: Parent changes, local coordinates update - **Burns**: Velocity changes impulsively - **Velocity drift**: |v_local - v_expected| > 1e-6 m/s before propagation Reconstruction uses `cartesian_to_orbital_elements()` which handles edge cases (near-circular e < 1e-10, near-parabolic). ## Coordinate Transformations Orbital plane orientation via z-x-z Euler rotation: ``` R_total = R_z(Ω) · R_x(i) · R_z(ω) ``` Sequence: argument_of_periapsis (ω) → inclination (i) → longitude_of_ascending_node (Ω). **Forward**: `orbital_elements_to_cartesian()` applies rotation. **Reverse**: `cartesian_to_orbital_elements()` computes angles from angular momentum and eccentricity vectors. ## Sphere of Influence Mechanics **SOI Radius**: Hill sphere approximation r_soi = a × (m/M)^(2/5). **SOI Transitions**: 1. `find_dominant_body()` called before each physics update 2. For non-root bodies: stay if distance < parent.soi_radius, else switch to root 3. For root bodies: find closest body where distance < body.soi_radius 4. On parent change: - Compute global position/velocity from old parent - Update parent_index - Compute new local coordinates - Reconstruct orbital elements **SOI in Loop**: Checked for all bodies and spacecraft before physics propagation. ## Maneuver System ### Burn Directions - BURN_PROGRADE: normalized velocity vector - BURN_RETROGRADE: opposite velocity - BURN_NORMAL: normalized (position × velocity) - BURN_ANTINORMAL: opposite normal - BURN_RADIAL_IN: -normalized position - BURN_RADIAL_OUT: normalized position - BURN_CUSTOM: user-specified vector ### Exact Position Execution Both trigger types use sub-step propagation to execute burns at the exact trigger time, eliminating quantization error: 1. **TRIGGER_TIME**: `check_maneuver_trigger()` computes `dt_to_burn = trigger_value - sim->time` (sim->time is step start). If the trigger falls within `[sim->time, sim->time + sim->dt]`, `scheduled_dt` is set to this offset. 2. **TRIGGER_TRUE_ANOMALY**: `check_maneuver_trigger()` converts current and target true anomaly to mean anomaly, computes delta-M, divides by mean motion to get `dt_needed`. 3. If `0 < scheduled_dt <= sim->dt`, trigger fires and `scheduled_dt` is set. 4. In `update_spacecraft_physics()`, for each spacecraft: check all pending maneuvers for that craft. 5. If a maneuver fires: propagate by `burn_dt` (scheduled_dt), execute burn, propagate remaining (`sim->dt - burn_dt`). 6. No separate maneuver execution step — all inline in the spacecraft propagation loop. **TRIGGER_TIME**: `scheduled_dt` is 0 only when the trigger time has already passed (clamped to fire immediately). Otherwise it is the exact sub-step offset from step start. **TRIGGER_TRUE_ANOMALY**: Sub-step timing via analytical mean anomaly calculation. **Future TODO**: Parabolic (Barker's equation) and hyperbolic branches for `check_maneuver_trigger()`. ### Hohmann Transfer `calculate_hohmann_transfer()` computes optimal two-burn transfer between two circular orbits using the vis-viva equation. Transfer time equals half the period of the transfer ellipse. ## Rendezvous Module Handles orbital rendezvous planning and execution via Hohmann transfers and phasing maneuvers. **Hohmann Transfer Planning**: - `calculate_required_separation_for_hohmann()` - computes ideal angular separation between chaser and target at first burn - `calculate_wait_time_for_hohmann()` - determines wait time before starting transfer (positive = wait, negative = late) - `calculate_next_hohmann_wait_time()` - like above but always returns non-negative by advancing to next phasing opportunity - `calculate_relative_orbit_period()` - time between consecutive phasing opportunities **Maneuver Creation**: - `create_hohmann_departure_maneuver()` - adds prograde burn to enter transfer orbit (supports true anomaly or immediate trigger) - `create_hohmann_arrival_maneuver()` - adds circularization burn to match target orbit (triggered at target radius) **Verification**: - `verify_hohmann_transfer_orbit()` - checks if current orbit matches expected Hohmann transfer parameters - `validate_hohmann_transfer_parameters()` - validates transfer parameters before calculation - `hohmann_transfer_complete()` - checks if transfer time has elapsed and spacecraft is at target radius ## Simulation Loop **Initialization**: 1. create_simulation() 2. load_system_config() - parse TOML 3. run_all_config_validations() 4. initialize_orbital_objects() - convert elements to Cartesian, compute globals, calculate SOI 5. Main loop begins **Main Loop Order**: 1. update_bodies_physics() - SOI checks, drift detection, propagation 2. compute_global_coordinates() 3. update_spacecraft_physics() - maneuver checking, propagation, burns 4. compute_spacecraft_globals() 5. time += dt **Spacecraft Physics Per-Frame** (`update_spacecraft_physics`): - For each spacecraft: 1. Validate local velocity against expected Keplerian velocity; if vel_diff > 1e-6, recalculate orbital elements 2. Check all pending maneuvers for this craft — if a trigger fires: a. Propagate by `burn_dt` (scheduled sub-step offset) b. Execute the maneuver (apply delta-v) c. Propagate remaining (`sim->dt - burn_dt`) 3. If no maneuver: propagate full `sim->dt` **Body Physics Per-Frame** (`update_bodies_physics`): - Check SOI via find_dominant_body() - Handle transitions (compute global, update parent, compute local, reconstruct elements) - Check velocity drift (> 1e-6 m/s) and reconstruct if needed - Propagate via propagate_orbital_elements() - Update local position/velocity ## Orbit Types **Elliptical (e < 1)**: ε < 0, bound periodic motion. a = -μ/(2ε) > 0. **Parabolic (|e-1| < tolerance)**: ε ≈ 0, escape trajectory. p = h²/μ. **Hyperbolic (e > 1)**: ε > 0, unbound. a = -μ/(2ε) < 0. All satisfy vis-viva: v² = μ(2/r - 1/a). ## Configuration **TOML Format**: [[bodies]], [[spacecraft]], [[maneuvers]] arrays. **Validation Rules**: - Parent index ordering (parents before children) - Orbital element consistency (e ≥ 0, parabolic p > 0, etc.) - True anomaly ranges for hyperbolic orbits - Mass ratios (MIN_MASS_RATIO = 1000.0 for large-radius bodies) - SOI overlap (siblings must not overlap) - Nested orbits (grandchild semi-major axis ≤ 5× parent SOI) - Initial positions (no overlap with parent) ## Testing Utilities **OrbitalMetrics**: kinetic_energy, potential_energy, total_energy, orbital_radius, velocity_magnitude, angular_position. **OrbitTracker**: Tracks orbit completion via quadrant transitions and total rotation. 3D mode uses orbital elements for inclined orbits. **Energy Functions**: - KE = 0.5 × m × v² - PE = -G × m1 × m2 / r (min distance clamped to 1.0) - Total = ΣKE + ΣPE_pairs ## Visualization **Renderer (Raylib)**: - XY simulation plane → XZ render plane (90° rotation around X) - Scale: 1e-9 (1 unit = 1 billion meters) - Relative rendering when body selected - Orbit paths with appropriate segment counts - Wireframe spheres, child indicators **UI Renderer (raygui)**: - Bottom-left: Sim time, body count, FPS, controls, config - Top-left: Objects list with selection - Top-right: Selected object details - Below info: Maneuver list ## Build System **Targets**: make / make all (build + ctags), make raylib, make test, make test-build, make clean, make clean-all, make rebuild. **Dependencies**: g++ (C++14), raylib (git submodule), raygui (header-only), tomlc17, Catch2 (tests only). **Testing**: ```bash make test # Build and run all tests make test-build # Rebuild test executable only ``` ### Running Specific Tests The test binary is at `./build/orbit_test`. Catch2 v3.7.1 supports multiple filtering strategies: ```bash ./build/orbit_test '[analytical]' # By tag ./build/orbit_test "*barker*" # By name (wildcard) ./build/orbit_test '*burn*' [hybrid] # Combined: name AND tag ./build/orbit_test '[hohmann]~[energy]' # Exclude tag (~) ./build/orbit_test -s '[tag]' # With debug/INFO output ``` Tags are the most common filter. Multiple tags on the command line use AND logic (test must have all tags). The `~` operator excludes a tag. ### SCENARIO Sections SCENARIO tests group related assertions under SECTION sub-tests. Each SECTION is an independent test case — setup code before all SECTIONs runs once per SECTION, so fixtures are recreated. This allows drilling into specific parts: ```bash ./build/orbit_test '[hohmann]' --section "First burn at perigee raises apogee" ``` In `--list-tests` output, SCENARIO tests display with "Scenario: " prefix. When filtering by name, wildcards handle the prefix transparently. Use `WithinAbs(expected, tolerance)` for floating-point comparisons (NOT `Approx()`). ## Hybrid Documentation Strategy This document provides high-level concepts and architecture. Per-module summary files (`src/*.summary.md`) contain detailed function documentation including signatures, parameters, formulas, and edge cases. Use both: this for the big picture, summaries for implementation details.