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.
 
 
 
 
 

292 lines
11 KiB

#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "../src/physics.h"
#include "../src/orbital_mechanics.h"
#include "../src/simulation.h"
#include "../src/orbital_objects.h"
#include "../src/rendezvous_hohmann.h"
#include "../src/config_loader.h"
#include <cmath>
#include <cstring>
using Catch::Matchers::WithinAbs;
// ============================================================================
// Helper Functions
// ============================================================================
// TODO: Add find_spacecraft_by_name() to simulation.h interface
// FIXME: This helper should be part of the public simulation API for testability
static int find_spacecraft_by_name(SimulationState* sim, const char* name) {
for (int i = 0; i < sim->craft_count; i++) {
if (strcmp(sim->spacecraft[i].name, name) == 0) {
return i;
}
}
return -1;
}
TEST_CASE("Config loading for Hohmann transfer", "[rendezvous_hohmann][config]") {
const double TIME_STEP = 30.0;
SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP);
REQUIRE(load_system_config(sim, "tests/test_rendezvous_hohmann.toml"));
REQUIRE(sim->body_count == 1);
REQUIRE(std::string(sim->bodies[0].name) == "Earth");
REQUIRE(sim->craft_count == 3);
REQUIRE(std::string(sim->spacecraft[0].name) == "Target_Satellite");
REQUIRE(std::string(sim->spacecraft[1].name) == "Chaser_Lower");
REQUIRE(std::string(sim->spacecraft[2].name) == "Chaser_Higher");
REQUIRE(sim->spacecraft[0].parent_index == 0);
REQUIRE(sim->spacecraft[1].parent_index == 0);
REQUIRE(sim->spacecraft[2].parent_index == 0);
// Verify initial orbits
REQUIRE_THAT(sim->spacecraft[0].orbit.semi_major_axis,
WithinAbs(6.771e6, 1.0)); // 400 km altitude
REQUIRE_THAT(sim->spacecraft[1].orbit.semi_major_axis,
WithinAbs(6.671e6, 1.0)); // 300 km altitude
REQUIRE_THAT(sim->spacecraft[2].orbit.semi_major_axis,
WithinAbs(6.871e6, 1.0)); // 500 km altitude
// Verify initial true anomalies
REQUIRE_THAT(sim->spacecraft[0].orbit.true_anomaly,
WithinAbs(0.0, 0.001));
REQUIRE_THAT(sim->spacecraft[1].orbit.true_anomaly,
WithinAbs(4.71238898038469, 0.001)); // 270° = 3π/2
REQUIRE_THAT(sim->spacecraft[2].orbit.true_anomaly,
WithinAbs(1.5707963267948966, 0.001)); // 90° = π/2
destroy_simulation(sim);
}
TEST_CASE("Calculate wait time for Hohmann transfer (lower to higher)", "[rendezvous_hohmann][phasing]") {
const double TIME_STEP = 30.0;
SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP);
REQUIRE(load_system_config(sim, "tests/test_rendezvous_hohmann.toml"));
int target_idx = find_spacecraft_by_name(sim, "Target_Satellite");
int chaser_lower_idx = find_spacecraft_by_name(sim, "Chaser_Lower");
REQUIRE(target_idx >= 0);
REQUIRE(chaser_lower_idx >= 0);
Spacecraft* target = &sim->spacecraft[target_idx];
Spacecraft* chaser = &sim->spacecraft[chaser_lower_idx];
CelestialBody* earth = &sim->bodies[0];
initialize_orbital_objects(sim);
double r1 = vec3_magnitude(chaser->local_position);
double r2 = vec3_magnitude(target->local_position);
SECTION("Zero angular separation - immediate transfer not possible") {
// If chaser is directly behind target, need to wait for target to move ahead
double angular_separation = 0.0;
double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass);
INFO("Wait time: " << wait_time << " s");
// Since lower orbit is faster, chaser will catch up, so wait time should be positive
REQUIRE_THAT(wait_time, WithinAbs(1358.16, 1.0));
}
SECTION("Small angular separation") {
double angular_separation = 0.5; // ~29 degrees
double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass);
INFO("Angular separation: " << angular_separation << " rad");
INFO("Wait time: " << wait_time << " s");
REQUIRE_THAT(wait_time, WithinAbs(-18192.7, 1.0));
}
SECTION("Large angular separation (near 2π)") {
double angular_separation = 6.0; // ~344 degrees
double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass);
INFO("Angular separation: " << angular_separation << " rad");
INFO("Wait time: " << wait_time << " s");
REQUIRE_THAT(wait_time, WithinAbs(12431.2, 1.0));
}
destroy_simulation(sim);
}
TEST_CASE("Calculate wait time for Hohmann transfer (higher to lower)", "[rendezvous_hohmann][phasing]") {
const double TIME_STEP = 30.0;
SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP);
REQUIRE(load_system_config(sim, "tests/test_rendezvous_hohmann.toml"));
int target_idx = find_spacecraft_by_name(sim, "Target_Satellite");
int chaser_higher_idx = find_spacecraft_by_name(sim, "Chaser_Higher");
REQUIRE(target_idx >= 0);
REQUIRE(chaser_higher_idx >= 0);
Spacecraft* target = &sim->spacecraft[target_idx];
Spacecraft* chaser = &sim->spacecraft[chaser_higher_idx];
CelestialBody* earth = &sim->bodies[0];
initialize_orbital_objects(sim);
double r1 = vec3_magnitude(chaser->local_position);
double r2 = vec3_magnitude(target->local_position);
SECTION("Zero angular separation - target must catch up") {
// Higher orbit is slower, so target must catch up
double angular_separation = 0.0;
double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass);
INFO("Wait time: " << wait_time << " s");
REQUIRE_THAT(wait_time, WithinAbs(1414.46, 1.0));
}
SECTION("Small angular separation") {
double angular_separation = 0.3; // ~17 degrees
double wait_time = calculate_wait_time_for_hohmann(r1, r2, angular_separation, earth->mass);
INFO("Angular separation: " << angular_separation << " rad");
INFO("Wait time: " << wait_time << " s");
REQUIRE_THAT(wait_time, WithinAbs(13586.2, 1.0));
}
destroy_simulation(sim);
}
TEST_CASE("Calculate required separation for Hohmann transfer", "[rendezvous_hohmann][phasing]") {
const double TIME_STEP = 30.0;
SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP);
REQUIRE(load_system_config(sim, "tests/test_rendezvous_hohmann.toml"));
int target_idx = find_spacecraft_by_name(sim, "Target_Satellite");
int chaser_lower_idx = find_spacecraft_by_name(sim, "Chaser_Lower");
int chaser_higher_idx = find_spacecraft_by_name(sim, "Chaser_Higher");
REQUIRE(target_idx >= 0);
REQUIRE(chaser_lower_idx >= 0);
REQUIRE(chaser_higher_idx >= 0);
Spacecraft* target = &sim->spacecraft[target_idx];
Spacecraft* chaser_lower = &sim->spacecraft[chaser_lower_idx];
Spacecraft* chaser_higher = &sim->spacecraft[chaser_higher_idx];
CelestialBody* earth = &sim->bodies[0];
initialize_orbital_objects(sim);
double r_lower = vec3_magnitude(chaser_lower->local_position);
double r_target = vec3_magnitude(target->local_position);
double r_higher = vec3_magnitude(chaser_higher->local_position);
SECTION("Lower to higher transfer") {
double required_separation = calculate_required_separation_for_hohmann(r_lower, r_target, earth->mass);
INFO("Required separation: " << required_separation << " rad");
INFO("Required separation (deg): " << required_separation * 180.0 / M_PI << "°");
REQUIRE_THAT(required_separation, WithinAbs(0.034734, 0.001));
}
SECTION("Higher to lower transfer") {
double required_separation = calculate_required_separation_for_hohmann(r_higher, r_target, earth->mass);
INFO("Required separation: " << required_separation << " rad");
INFO("Required separation (deg): " << required_separation * 180.0 / M_PI << "°");
REQUIRE_THAT(required_separation, WithinAbs(-0.0348625, 0.001));
}
SECTION("Equal radii - no transfer needed") {
double required_separation = calculate_required_separation_for_hohmann(r_target, r_target, earth->mass);
INFO("Required separation: " << required_separation << " rad");
REQUIRE_THAT(required_separation, WithinAbs(0.0, 0.001));
}
destroy_simulation(sim);
}
SCENARIO("Complete Hohmann transfer phasing workflow", "[rendezvous_hohmann][workflow]") {
const double TIME_STEP = 10.0;
SimulationState* sim = create_simulation(3, 5, 10, TIME_STEP);
REQUIRE(load_system_config(sim, "tests/test_rendezvous_hohmann.toml"));
int target_idx = find_spacecraft_by_name(sim, "Target_Satellite");
int chaser_lower_idx = find_spacecraft_by_name(sim, "Chaser_Lower");
REQUIRE(target_idx >= 0);
REQUIRE(chaser_lower_idx >= 0);
Spacecraft* target = &sim->spacecraft[target_idx];
Spacecraft* chaser = &sim->spacecraft[chaser_lower_idx];
CelestialBody* earth = &sim->bodies[0];
initialize_orbital_objects(sim);
SECTION("Calculate and verify phasing for lower-to-higher transfer") {
double r1 = vec3_magnitude(chaser->local_position);
double r2 = vec3_magnitude(target->local_position);
INFO("Chaser orbit radius: " << r1 << " m");
INFO("Target orbit radius: " << r2 << " m");
// Calculate current angular separation
double current_sep = angular_distance(chaser->orbit.true_anomaly, target->orbit.true_anomaly);
INFO("Current angular separation: " << current_sep << " rad");
INFO("Current angular separation (deg): " << current_sep * 180.0 / M_PI << "°");
// Calculate required separation for Hohmann
double required_sep = calculate_required_separation_for_hohmann(r1, r2, earth->mass);
INFO("Required separation: " << required_sep << " rad");
INFO("Required separation (deg): " << required_sep * 180.0 / M_PI << "°");
// Calculate wait time
double wait_time = calculate_wait_time_for_hohmann(r1, r2, current_sep, earth->mass);
INFO("Wait time: " << wait_time << " s");
REQUIRE_THAT(wait_time, WithinAbs(-60062.651728, 0.1));
// Verify orbits are circular
REQUIRE_THAT(chaser->orbit.eccentricity, WithinAbs(0.0, 0.001));
REQUIRE_THAT(target->orbit.eccentricity, WithinAbs(0.0, 0.001));
}
SECTION("Calculate Hohmann transfer parameters") {
double r1 = vec3_magnitude(chaser->local_position);
double r2 = vec3_magnitude(target->local_position);
// Use existing calculate_hohmann_transfer from maneuver.h
HohmannTransfer hohmann = calculate_hohmann_transfer(r1, r2, earth->mass);
INFO("First burn delta-v: " << hohmann.dv1 << " m/s");
INFO("Second burn delta-v: " << hohmann.dv2 << " m/s");
INFO("Transfer time: " << hohmann.transfer_time << " s");
INFO("Target true anomaly: " << hohmann.true_anomaly_2 << " rad");
REQUIRE_THAT(hohmann.dv1, WithinAbs(28.699077, 0.01));
REQUIRE_THAT(hohmann.dv2, WithinAbs(28.592521, 0.01));
REQUIRE_THAT(hohmann.transfer_time, WithinAbs(2741.813778, 0.1));
}
destroy_simulation(sim);
}