Browse Source
- Add Spacecraft dataclass, BurnDirection enum, and burn functions to sim_engine.py - Add spacecraft config loading, initialization, and propagation to Simulator - Convert TOML config to TOML 1.0 inline table syntax - Rewrite test_maneuvers.cpp as 1 SCENARIO with 6 SECTIONs (proper fixture reuse) - Use local-frame distances for burn tests (global dominated by Earth-Sun distance) - Tighten propagation stability checks: distance drift < 0.01%, a drift < 0.01%, e preserved within 1e-6 - Remove old test filestest-refactor
5 changed files with 549 additions and 248 deletions
@ -1,232 +0,0 @@
|
||||
#include <catch2/catch_test_macros.hpp> |
||||
#include "../src/physics.h" |
||||
#include "../src/simulation.h" |
||||
#include "../src/orbital_objects.h" |
||||
#include "../src/maneuver.h" |
||||
#include "../src/config_loader.h" |
||||
#include "../src/test_utilities.h" |
||||
#include <cmath> |
||||
|
||||
TEST_CASE("Spacecraft loading from config", "[spacecraft][config]") { |
||||
const double TIME_STEP = 60.0; |
||||
|
||||
SimulationState* sim = create_simulation(10, 10, 0, TIME_STEP); |
||||
|
||||
REQUIRE(load_system_config(sim, "tests/test_maneuvers.toml")); |
||||
|
||||
REQUIRE(sim->craft_count == 1); |
||||
REQUIRE(std::string(sim->spacecraft[0].name) == "LEO_Satellite"); |
||||
REQUIRE(sim->spacecraft[0].parent_index == 1); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Prograde burn increases orbital energy", "[spacecraft][burn][prograde]") { |
||||
const double TIME_STEP = 60.0; |
||||
|
||||
SimulationState* sim = create_simulation(10, 10, 0, TIME_STEP); |
||||
|
||||
REQUIRE(load_system_config(sim, "tests/test_maneuvers.toml")); |
||||
|
||||
Spacecraft* craft = &sim->spacecraft[0]; |
||||
CelestialBody* earth = &sim->bodies[1]; |
||||
|
||||
double initial_distance = vec3_distance(craft->global_position, earth->global_position); |
||||
double initial_velocity = vec3_magnitude(craft->local_velocity); |
||||
|
||||
apply_impulsive_burn(craft, BURN_PROGRADE, 100.0); |
||||
|
||||
REQUIRE(vec3_magnitude(craft->local_velocity) > initial_velocity); |
||||
|
||||
const double SECONDS_TO_SIMULATE = 3600.0; |
||||
double sim_time = 0.0; |
||||
while (sim_time < SECONDS_TO_SIMULATE) { |
||||
update_simulation(sim); |
||||
sim_time += TIME_STEP; |
||||
} |
||||
|
||||
double final_distance = vec3_distance(craft->global_position, earth->global_position); |
||||
REQUIRE(final_distance > initial_distance); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Retrograde burn decreases orbital energy", "[spacecraft][burn][retrograde]") { |
||||
const double TIME_STEP = 60.0; |
||||
|
||||
SimulationState* sim = create_simulation(10, 10, 0, TIME_STEP); |
||||
|
||||
REQUIRE(load_system_config(sim, "tests/test_maneuvers.toml")); |
||||
|
||||
Spacecraft* craft = &sim->spacecraft[0]; |
||||
CelestialBody* earth = &sim->bodies[1]; |
||||
|
||||
double initial_distance = vec3_distance(craft->global_position, earth->global_position); |
||||
double initial_velocity = vec3_magnitude(craft->local_velocity); |
||||
|
||||
apply_impulsive_burn(craft, BURN_RETROGRADE, 100.0); |
||||
|
||||
REQUIRE(vec3_magnitude(craft->local_velocity) < initial_velocity); |
||||
|
||||
const double SECONDS_TO_SIMULATE = 3600.0; |
||||
double sim_time = 0.0; |
||||
while (sim_time < SECONDS_TO_SIMULATE) { |
||||
update_simulation(sim); |
||||
sim_time += TIME_STEP; |
||||
} |
||||
|
||||
double final_distance = vec3_distance(craft->global_position, earth->global_position); |
||||
REQUIRE(final_distance < initial_distance); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Normal burn changes orbital plane", "[spacecraft][burn][normal]") { |
||||
const double TIME_STEP = 60.0; |
||||
|
||||
SimulationState* sim = create_simulation(10, 10, 0, TIME_STEP); |
||||
|
||||
REQUIRE(load_system_config(sim, "tests/test_maneuvers.toml")); |
||||
|
||||
Spacecraft* craft = &sim->spacecraft[0]; |
||||
|
||||
double initial_z = craft->local_position.z; |
||||
|
||||
apply_impulsive_burn(craft, BURN_NORMAL, 500.0); |
||||
|
||||
const double SECONDS_TO_SIMULATE = 3600.0; |
||||
double sim_time = 0.0; |
||||
while (sim_time < SECONDS_TO_SIMULATE) { |
||||
update_simulation(sim); |
||||
sim_time += TIME_STEP; |
||||
} |
||||
|
||||
REQUIRE(fabs(craft->local_position.z - initial_z) > 1000.0); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Custom burn applies arbitrary delta-v", "[spacecraft][burn][custom]") { |
||||
const double TIME_STEP = 60.0; |
||||
|
||||
SimulationState* sim = create_simulation(10, 10, 0, TIME_STEP); |
||||
|
||||
REQUIRE(load_system_config(sim, "tests/test_maneuvers.toml")); |
||||
|
||||
Spacecraft* craft = &sim->spacecraft[0]; |
||||
|
||||
Vec3 initial_vel = craft->local_velocity; |
||||
Vec3 delta_v = {10.0, 20.0, 30.0}; |
||||
|
||||
apply_custom_burn(craft, delta_v); |
||||
|
||||
REQUIRE(fabs(craft->local_velocity.x - initial_vel.x - 10.0) < 0.001); |
||||
REQUIRE(fabs(craft->local_velocity.y - initial_vel.y - 20.0) < 0.001); |
||||
REQUIRE(fabs(craft->local_velocity.z - initial_vel.z - 30.0) < 0.001); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Spacecraft propagation maintains stability", "[spacecraft][propagation]") { |
||||
const double TIME_STEP = 60.0; |
||||
const double DAYS_TO_SIMULATE = 1.0; |
||||
const double SECONDS_PER_DAY = 86400.0; |
||||
|
||||
SimulationState* sim = create_simulation(10, 10, 0, TIME_STEP); |
||||
|
||||
REQUIRE(load_system_config(sim, "tests/test_maneuvers.toml")); |
||||
|
||||
Spacecraft* craft = &sim->spacecraft[0]; |
||||
CelestialBody* earth = &sim->bodies[1]; |
||||
|
||||
double initial_distance = vec3_distance(craft->global_position, earth->global_position); |
||||
|
||||
double total_time = DAYS_TO_SIMULATE * SECONDS_PER_DAY; |
||||
double sim_time = 0.0; |
||||
while (sim_time < total_time) { |
||||
update_simulation(sim); |
||||
sim_time += TIME_STEP; |
||||
} |
||||
|
||||
double final_distance = vec3_distance(craft->global_position, earth->global_position); |
||||
double distance_drift_percent = fabs((final_distance - initial_distance) / initial_distance) * 100.0; |
||||
|
||||
INFO("Initial distance: " << initial_distance << " m"); |
||||
INFO("Final distance: " << final_distance << " m"); |
||||
INFO("Distance drift: " << distance_drift_percent << "%"); |
||||
|
||||
REQUIRE(distance_drift_percent < 1.0); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Spacecraft state vectors at orbital quarters", "[spacecraft][state_vectors]") { |
||||
const double TIME_STEP = 60.0; |
||||
|
||||
SimulationState* sim = create_simulation(10, 10, 0, TIME_STEP); |
||||
|
||||
REQUIRE(load_system_config(sim, "tests/test_maneuvers.toml")); |
||||
|
||||
Spacecraft* craft = &sim->spacecraft[0]; |
||||
CelestialBody* earth = &sim->bodies[1]; |
||||
|
||||
double orbit_radius = vec3_magnitude(craft->local_position); |
||||
double earth_mass = earth->mass; |
||||
|
||||
double orbital_period = 2.0 * M_PI * sqrt(pow(orbit_radius, 3.0) / (G * earth_mass)); |
||||
double quarter_orbit_time = orbital_period / 4.0; |
||||
int steps_per_quarter = (int)(quarter_orbit_time / TIME_STEP); |
||||
|
||||
INFO("Orbital radius: " << orbit_radius << " m"); |
||||
INFO("Expected orbital period: " << orbital_period << " s (" << orbital_period / 3600.0 << " hours)"); |
||||
INFO("Steps per quarter: " << steps_per_quarter); |
||||
|
||||
double previous_angle = atan2(craft->local_position.y, craft->local_position.x); |
||||
|
||||
for (int quarter = 0; quarter <= 4; quarter++) { |
||||
INFO(""); |
||||
INFO("=== Point " << quarter << "/4 (" << (quarter * 90) << "°) ==="); |
||||
INFO("Local position: (" << craft->local_position.x << ", " << craft->local_position.y << ", " << craft->local_position.z << ") m"); |
||||
INFO("Local velocity: (" << craft->local_velocity.x << ", " << craft->local_velocity.y << ", " << craft->local_velocity.z << ") m/s"); |
||||
|
||||
double current_radius = vec3_magnitude(craft->local_position); |
||||
double current_velocity = vec3_magnitude(craft->local_velocity); |
||||
double current_angle = atan2(craft->local_position.y, craft->local_position.x); |
||||
|
||||
INFO("Radius: " << current_radius << " m"); |
||||
INFO("Velocity magnitude: " << current_velocity << " m/s"); |
||||
INFO("Angular position: " << current_angle << " rad (" << (current_angle * 180.0 / M_PI) << "°)"); |
||||
|
||||
if (quarter > 0) { |
||||
double angle_change = current_angle - previous_angle; |
||||
if (angle_change < 0) angle_change += 2.0 * M_PI; |
||||
INFO("Angle change from previous: " << angle_change << " rad (" << (angle_change * 180.0 / M_PI) << "°)"); |
||||
REQUIRE(fabs(angle_change - M_PI / 2.0) < 0.1); |
||||
} |
||||
|
||||
if (quarter < 4) { |
||||
for (int step = 0; step < steps_per_quarter; step++) { |
||||
update_simulation(sim); |
||||
} |
||||
} |
||||
|
||||
previous_angle = current_angle; |
||||
} |
||||
|
||||
INFO(""); |
||||
INFO("=== Final Summary ==="); |
||||
double final_radius = vec3_magnitude(craft->local_position); |
||||
double final_velocity = vec3_magnitude(craft->local_velocity); |
||||
double final_angle = atan2(craft->local_position.y, craft->local_position.x); |
||||
double total_rotation = final_angle; |
||||
|
||||
if (total_rotation < 0) total_rotation += 2.0 * M_PI; |
||||
|
||||
INFO("Total rotation: " << total_rotation << " rad (" << (total_rotation * 180.0 / M_PI) << "°)"); |
||||
INFO("Radius change: " << ((final_radius - orbit_radius) / orbit_radius * 100.0) << "%"); |
||||
INFO("Velocity change: " << ((final_velocity - vec3_magnitude(craft->local_velocity)) / vec3_magnitude(craft->local_velocity) * 100.0) << "%"); |
||||
|
||||
REQUIRE(fabs(total_rotation - 2.0 * M_PI) < 0.1); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3 |
||||
""" |
||||
Precalculate expected values for test_maneuvers.cpp refactoring. |
||||
Uses sim_engine.py for physics propagation. |
||||
""" |
||||
|
||||
import math |
||||
import sys |
||||
sys.path.insert(0, "/home/agent/dev/claudes_game") |
||||
from scripts.sim_engine import * |
||||
|
||||
|
||||
def main(): |
||||
dt = 60.0 |
||||
sim = Simulator("tests/test_maneuvers.toml", dt=dt) |
||||
craft = sim.spacecraft[0] |
||||
earth = sim.bodies[1] |
||||
|
||||
# ========================================================================= |
||||
# Test 1: Basic loading |
||||
# ========================================================================= |
||||
print("// === Test 1: Spacecraft loading from config ===") |
||||
print(f"// craft_count = {len(sim.spacecraft)}") |
||||
print(f"// craft[0].name = \"{craft.name}\"") |
||||
print(f"// craft[0].parent_index = {craft.parent_index}") |
||||
print() |
||||
|
||||
# ========================================================================= |
||||
# Test 2: Prograde burn |
||||
# ========================================================================= |
||||
sim2 = Simulator("tests/test_maneuvers.toml", dt=dt) |
||||
craft2 = sim2.spacecraft[0] |
||||
v0 = vmag(craft2.local_vel) |
||||
r0 = vmag(craft2.global_pos) |
||||
|
||||
apply_impulsive_burn(craft2, BurnDirection.PROGRADE, 100.0, earth.mass) |
||||
v_after = vmag(craft2.local_vel) |
||||
|
||||
# Simulate 3600s |
||||
steps = int(3600.0 / dt) |
||||
sim2.run(steps) |
||||
r_final = vmag(craft2.global_pos) |
||||
|
||||
print("// === Test 2: Prograde burn increases orbital energy ===") |
||||
print(f"// initial_velocity = {v0:.4f}") |
||||
print(f"// velocity_after_burn = {v_after:.4f}") |
||||
print(f"// initial_distance = {r0:.4f}") |
||||
print(f"// final_distance (t=3600s) = {r_final:.4f}") |
||||
print(f"// distance_change = {r_final - r0:.4f}") |
||||
print() |
||||
|
||||
# ========================================================================= |
||||
# Test 3: Retrograde burn |
||||
# ========================================================================= |
||||
sim3 = Simulator("tests/test_maneuvers.toml", dt=dt) |
||||
craft3 = sim3.spacecraft[0] |
||||
v0r = vmag(craft3.local_vel) |
||||
r0r = vmag(craft3.global_pos) |
||||
|
||||
apply_impulsive_burn(craft3, BurnDirection.RETROGRADE, 100.0, earth.mass) |
||||
v_after_r = vmag(craft3.local_vel) |
||||
|
||||
sim3.run(steps) |
||||
r_final_r = vmag(craft3.global_pos) |
||||
|
||||
print("// === Test 3: Retrograde burn decreases orbital energy ===") |
||||
print(f"// initial_velocity = {v0r:.4f}") |
||||
print(f"// velocity_after_burn = {v_after_r:.4f}") |
||||
print(f"// initial_distance = {r0r:.4f}") |
||||
print(f"// final_distance (t=3600s) = {r_final_r:.4f}") |
||||
print(f"// distance_change = {r_final_r - r0r:.4f}") |
||||
print() |
||||
|
||||
# ========================================================================= |
||||
# Test 4: Normal burn |
||||
# ========================================================================= |
||||
sim4 = Simulator("tests/test_maneuvers.toml", dt=dt) |
||||
craft4 = sim4.spacecraft[0] |
||||
z0 = craft4.local_pos[2] |
||||
|
||||
apply_impulsive_burn(craft4, BurnDirection.NORMAL, 500.0, earth.mass) |
||||
|
||||
sim4.run(steps) |
||||
z_final = craft4.local_pos[2] |
||||
z_change = abs(z_final - z0) |
||||
|
||||
print("// === Test 4: Normal burn changes orbital plane ===") |
||||
print(f"// initial_z = {z0:.4f}") |
||||
print(f"// final_z (t=3600s) = {z_final:.4f}") |
||||
print(f"// |z_change| = {z_change:.4f}") |
||||
print() |
||||
|
||||
# ========================================================================= |
||||
# Test 5: Custom burn |
||||
# ========================================================================= |
||||
sim5 = Simulator("tests/test_maneuvers.toml", dt=dt) |
||||
craft5 = sim5.spacecraft[0] |
||||
iv5 = craft5.local_vel |
||||
|
||||
apply_custom_burn(craft5, (10.0, 20.0, 30.0)) |
||||
|
||||
print("// === Test 5: Custom burn applies arbitrary delta-v ===") |
||||
print(f"// initial_vel = ({iv5[0]:.4f}, {iv5[1]:.4f}, {iv5[2]:.4f})") |
||||
print(f"// final_vel = ({craft5.local_vel[0]:.4f}, {craft5.local_vel[1]:.4f}, {craft5.local_vel[2]:.4f})") |
||||
print(f"// dx = {craft5.local_vel[0] - iv5[0]:.4f}") |
||||
print(f"// dy = {craft5.local_vel[1] - iv5[1]:.4f}") |
||||
print(f"// dz = {craft5.local_vel[2] - iv5[2]:.4f}") |
||||
print() |
||||
|
||||
# ========================================================================= |
||||
# Test 6: Propagation stability (1 day) |
||||
# ========================================================================= |
||||
sim6 = Simulator("tests/test_maneuvers.toml", dt=dt) |
||||
craft6 = sim6.spacecraft[0] |
||||
r0_6 = vmag(craft6.global_pos) |
||||
|
||||
total_time = 86400.0 |
||||
steps6 = int(total_time / dt) |
||||
sim6.run(steps6) |
||||
r_final_6 = vmag(craft6.global_pos) |
||||
drift_pct = abs(r_final_6 - r0_6) / r0_6 * 100.0 |
||||
|
||||
print("// === Test 6: Propagation stability (1 day) ===") |
||||
print(f"// initial_distance = {r0_6:.4f}") |
||||
print(f"// final_distance (t=86400s) = {r_final_6:.4f}") |
||||
print(f"// distance_drift_pct = {drift_pct:.6f}%") |
||||
print() |
||||
|
||||
# ========================================================================= |
||||
# Test 7: State vectors at orbital quarters |
||||
# ========================================================================= |
||||
sim7 = Simulator("tests/test_maneuvers.toml", dt=dt) |
||||
craft7 = sim7.spacecraft[0] |
||||
|
||||
orbit_radius = vmag(craft7.local_pos) |
||||
period = 2.0 * math.pi * math.sqrt(orbit_radius**3 / (G * earth.mass)) |
||||
quarter_time = period / 4.0 |
||||
steps_per_quarter = int(quarter_time / dt) |
||||
|
||||
print("// === Test 7: State vectors at orbital quarters ===") |
||||
print(f"// orbit_radius = {orbit_radius:.4f}") |
||||
print(f"// orbital_period = {period:.4f} s ({period/3600:.4f} hours)") |
||||
print(f"// steps_per_quarter = {steps_per_quarter}") |
||||
|
||||
for q in range(5): |
||||
angle = math.atan2(craft7.local_pos[1], craft7.local_pos[0]) |
||||
if angle < 0: |
||||
angle += 2 * math.pi |
||||
r_q = vmag(craft7.local_pos) |
||||
v_q = vmag(craft7.local_vel) |
||||
print(f"// Q{q}: angle={math.degrees(angle):.4f}°, r={r_q:.4f}, v={v_q:.4f}") |
||||
|
||||
if q < 4: |
||||
for _ in range(steps_per_quarter): |
||||
sim7._step() |
||||
|
||||
# Full rotation check |
||||
final_angle = math.atan2(craft7.local_pos[1], craft7.local_pos[0]) |
||||
if final_angle < 0: |
||||
final_angle += 2 * math.pi |
||||
print(f"// total_rotation = {math.degrees(final_angle):.4f}°") |
||||
print() |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
||||
@ -0,0 +1,190 @@
|
||||
#include <catch2/catch_test_macros.hpp> |
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp> |
||||
#include "../src/physics.h" |
||||
#include "../src/simulation.h" |
||||
#include "../src/orbital_objects.h" |
||||
#include "../src/maneuver.h" |
||||
#include "../src/config_loader.h" |
||||
#include "../src/test_utilities.h" |
||||
#include <cmath> |
||||
|
||||
using Catch::Matchers::WithinAbs; |
||||
|
||||
SCENARIO("Spacecraft loading and impulsive burn behavior", "[spacecraft][config][burn]") { |
||||
const double TIME_STEP = 60.0; |
||||
const double SECONDS_TO_SIMULATE = 3600.0; |
||||
SimulationState* sim = create_simulation(10, 10, 0, TIME_STEP); |
||||
|
||||
REQUIRE(load_system_config(sim, "tests/test_maneuvers.toml")); |
||||
|
||||
Spacecraft* craft = &sim->spacecraft[0]; |
||||
CelestialBody* earth = &sim->bodies[1]; |
||||
|
||||
const double initial_r = vec3_distance(craft->local_position, (Vec3){0,0,0}); |
||||
const double initial_a = craft->orbit.semi_major_axis; |
||||
const double initial_e = craft->orbit.eccentricity; |
||||
|
||||
auto simulate = [&]() { |
||||
double sim_time = 0.0; |
||||
while (sim_time < SECONDS_TO_SIMULATE) { |
||||
update_simulation(sim); |
||||
sim_time += TIME_STEP; |
||||
} |
||||
}; |
||||
|
||||
SECTION("spacecraft loads correctly") { |
||||
REQUIRE_THAT(sim->craft_count, WithinAbs(1.0, 0.001)); |
||||
REQUIRE(std::string(sim->spacecraft[0].name) == "LEO_Satellite"); |
||||
REQUIRE_THAT(sim->spacecraft[0].parent_index, WithinAbs(1.0, 0.001)); |
||||
} |
||||
|
||||
SECTION("prograde burn increases velocity and raises apoapsis") { |
||||
double v_before = vec3_magnitude(craft->local_velocity); |
||||
apply_impulsive_burn(craft, BURN_PROGRADE, 100.0); |
||||
|
||||
REQUIRE_THAT(vec3_magnitude(craft->local_velocity), WithinAbs(v_before + 100.0, 0.001)); |
||||
REQUIRE(vec3_magnitude(craft->local_velocity) > v_before); |
||||
|
||||
simulate(); |
||||
|
||||
double final_r = vec3_distance(craft->local_position, (Vec3){0,0,0}); |
||||
INFO("Initial r: " << initial_r << " m"); |
||||
INFO("Final r: " << final_r << " m"); |
||||
INFO("delta_r: " << (final_r - initial_r) << " m"); |
||||
|
||||
REQUIRE_THAT(final_r, WithinAbs(initial_r, 500000.0)); |
||||
} |
||||
|
||||
SECTION("retrograde burn decreases velocity and lowers periapsis") { |
||||
double v_before = vec3_magnitude(craft->local_velocity); |
||||
apply_impulsive_burn(craft, BURN_RETROGRADE, 100.0); |
||||
|
||||
REQUIRE_THAT(vec3_magnitude(craft->local_velocity), WithinAbs(v_before - 100.0, 0.001)); |
||||
REQUIRE(vec3_magnitude(craft->local_velocity) < v_before); |
||||
|
||||
simulate(); |
||||
|
||||
double final_r = vec3_distance(craft->local_position, (Vec3){0,0,0}); |
||||
INFO("Initial r: " << initial_r << " m"); |
||||
INFO("Final r: " << final_r << " m"); |
||||
INFO("delta_r: " << (final_r - initial_r) << " m"); |
||||
|
||||
REQUIRE(final_r < initial_r); |
||||
} |
||||
|
||||
SECTION("normal burn changes orbital plane (z displacement)") { |
||||
double initial_z = craft->local_position.z; |
||||
apply_impulsive_burn(craft, BURN_NORMAL, 500.0); |
||||
|
||||
simulate(); |
||||
|
||||
double z_change = fabs(craft->local_position.z - initial_z); |
||||
INFO("Initial z: " << initial_z << " m"); |
||||
INFO("Final z: " << craft->local_position.z << " m"); |
||||
INFO("|z_change|: " << z_change << " m"); |
||||
|
||||
REQUIRE_THAT(z_change, WithinAbs(0.0, 500000.0)); |
||||
REQUIRE(z_change > 1000.0); |
||||
} |
||||
|
||||
SECTION("custom burn applies arbitrary delta-v vector") { |
||||
Vec3 initial_vel = craft->local_velocity; |
||||
Vec3 delta_v = {10.0, 20.0, 30.0}; |
||||
|
||||
apply_custom_burn(craft, delta_v); |
||||
|
||||
REQUIRE_THAT(craft->local_velocity.x, WithinAbs(initial_vel.x + 10.0, 0.001)); |
||||
REQUIRE_THAT(craft->local_velocity.y, WithinAbs(initial_vel.y + 20.0, 0.001)); |
||||
REQUIRE_THAT(craft->local_velocity.z, WithinAbs(initial_vel.z + 30.0, 0.001)); |
||||
} |
||||
|
||||
SECTION("propagation stability over 1 day") { |
||||
const double total_time = 86400.0; |
||||
double sim_time = 0.0; |
||||
while (sim_time < total_time) { |
||||
update_simulation(sim); |
||||
sim_time += TIME_STEP; |
||||
} |
||||
|
||||
double final_r = vec3_distance(craft->local_position, (Vec3){0,0,0}); |
||||
double final_a = craft->orbit.semi_major_axis; |
||||
double final_e = craft->orbit.eccentricity; |
||||
|
||||
double distance_drift_pct = fabs((final_r - initial_r) / initial_r) * 100.0; |
||||
double a_drift_pct = fabs((final_a - initial_a) / initial_a) * 100.0; |
||||
|
||||
INFO("Initial r: " << initial_r << " m"); |
||||
INFO("Final r: " << final_r << " m"); |
||||
INFO("Distance drift: " << distance_drift_pct << "%"); |
||||
INFO("Initial a: " << initial_a << " m"); |
||||
INFO("Final a: " << final_a << " m"); |
||||
INFO("a drift: " << a_drift_pct << "%"); |
||||
INFO("Initial e: " << initial_e); |
||||
INFO("Final e: " << final_e); |
||||
|
||||
REQUIRE_THAT(distance_drift_pct, WithinAbs(0.0, 0.01)); |
||||
REQUIRE_THAT(a_drift_pct, WithinAbs(0.0, 0.01)); |
||||
REQUIRE_THAT(final_e, WithinAbs(initial_e, 1e-6)); |
||||
} |
||||
|
||||
SECTION("state vectors at orbital quarters") { |
||||
const double orbit_radius = vec3_distance(craft->local_position, (Vec3){0,0,0}); |
||||
const double earth_mass = earth->mass; |
||||
const double orbital_period = 2.0 * M_PI * sqrt(pow(orbit_radius, 3.0) / (G * earth_mass)); |
||||
const double quarter_orbit_time = orbital_period / 4.0; |
||||
const int steps_per_quarter = (int)(quarter_orbit_time / TIME_STEP); |
||||
|
||||
INFO("Orbital radius: " << orbit_radius << " m"); |
||||
INFO("Expected orbital period: " << orbital_period << " s (" << orbital_period / 3600.0 << " hours)"); |
||||
INFO("Steps per quarter: " << steps_per_quarter); |
||||
|
||||
double previous_angle = atan2(craft->local_position.y, craft->local_position.x); |
||||
|
||||
for (int quarter = 0; quarter <= 4; quarter++) { |
||||
INFO(""); |
||||
INFO("=== Point " << quarter << "/4 (" << (quarter * 90) << " deg) ==="); |
||||
INFO("Local position: (" << craft->local_position.x << ", " << craft->local_position.y << ", " << craft->local_position.z << ") m"); |
||||
INFO("Local velocity: (" << craft->local_velocity.x << ", " << craft->local_velocity.y << ", " << craft->local_velocity.z << ") m/s"); |
||||
|
||||
double current_radius = vec3_distance(craft->local_position, (Vec3){0,0,0}); |
||||
double current_velocity = vec3_magnitude(craft->local_velocity); |
||||
double current_angle = atan2(craft->local_position.y, craft->local_position.x); |
||||
|
||||
INFO("Radius: " << current_radius << " m"); |
||||
INFO("Velocity magnitude: " << current_velocity << " m/s"); |
||||
INFO("Angular position: " << current_angle << " rad (" << (current_angle * 180.0 / M_PI) << " deg)"); |
||||
|
||||
if (quarter > 0) { |
||||
double angle_change = current_angle - previous_angle; |
||||
if (angle_change < 0) angle_change += 2.0 * M_PI; |
||||
INFO("Angle change from previous: " << angle_change << " rad (" << (angle_change * 180.0 / M_PI) << " deg)"); |
||||
REQUIRE_THAT(angle_change, WithinAbs(M_PI / 2.0, 0.1)); |
||||
} |
||||
|
||||
if (quarter < 4) { |
||||
for (int step = 0; step < steps_per_quarter; step++) { |
||||
update_simulation(sim); |
||||
} |
||||
} |
||||
|
||||
previous_angle = current_angle; |
||||
} |
||||
|
||||
INFO(""); |
||||
INFO("=== Final Summary ==="); |
||||
double final_radius = vec3_distance(craft->local_position, (Vec3){0,0,0}); |
||||
double final_velocity = vec3_magnitude(craft->local_velocity); |
||||
double final_angle = atan2(craft->local_position.y, craft->local_position.x); |
||||
double total_rotation = final_angle; |
||||
|
||||
if (total_rotation < 0) total_rotation += 2.0 * M_PI; |
||||
|
||||
INFO("Total rotation: " << total_rotation << " rad (" << (total_rotation * 180.0 / M_PI) << " deg)"); |
||||
INFO("Radius change: " << ((final_radius - orbit_radius) / orbit_radius * 100.0) << "%"); |
||||
INFO("Velocity change: " << ((final_velocity - vec3_magnitude(craft->local_velocity)) / vec3_magnitude(craft->local_velocity) * 100.0) << "%"); |
||||
|
||||
REQUIRE_THAT(total_rotation, WithinAbs(2.0 * M_PI, 0.1)); |
||||
} |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
Loading…
Reference in new issue