#include #include "../src/physics.h" #include "../src/simulation.h" #include "../src/config_loader.h" #include #include struct ParentHistory { std::vector planet_a_parents; std::vector planet_b_parents; }; TEST_CASE("Invalid parent: Earth should not become child of spacecraft", "[init][parent][bug]") { const double TIME_STEP = 60.0; SimulationState* sim = create_simulation(10, 0, 0, TIME_STEP); REQUIRE(load_system_config(sim, "tests/configs/earth_mars_simple.toml")); const int EARTH_IDX = 1; const int SPACECRAFT_IDX = 3; const int SUN_IDX = 0; REQUIRE(sim->bodies[EARTH_IDX].parent_index == SUN_IDX); for (int step = 0; step < 10; step++) { update_simulation(sim); int earth_parent = sim->bodies[EARTH_IDX].parent_index; INFO("Step " << step << ": Earth's parent = " << earth_parent << " (" << (earth_parent == SPACECRAFT_IDX ? "SPACECRAFT" : earth_parent == SUN_IDX ? "Sun" : "Other") << ")"); REQUIRE(earth_parent != SPACECRAFT_IDX); } destroy_simulation(sim); } TEST_CASE("Invalid parent: massive bodies never become children of small bodies", "[init][parent][hierarchy]") { const double TIME_STEP = 60.0; const double MASS_THRESHOLD_RATIO = 1000.0; SimulationState* sim = create_simulation(10, 0, 0, TIME_STEP); REQUIRE(load_system_config(sim, "tests/configs/earth_mars_simple.toml")); for (int i = 0; i < sim->body_count; i++) { if (sim->bodies[i].parent_index >= 0) { int parent_idx = sim->bodies[i].parent_index; double parent_mass = sim->bodies[parent_idx].mass; double child_mass = sim->bodies[i].mass; double mass_ratio = parent_mass / child_mass; INFO("Body " << i << " (" << sim->bodies[i].name << ") parent " << parent_idx << " (" << sim->bodies[parent_idx].name << "): mass ratio = " << mass_ratio); REQUIRE(mass_ratio >= 1.0); if (strcmp(sim->bodies[parent_idx].name, "Spacecraft") != 0 && strcmp(sim->bodies[i].name, "Spacecraft") != 0) { REQUIRE(mass_ratio >= MASS_THRESHOLD_RATIO); } } } for (int step = 0; step < 100; step++) { update_simulation(sim); for (int i = 0; i < sim->body_count; i++) { if (sim->bodies[i].parent_index >= 0) { int parent_idx = sim->bodies[i].parent_index; double parent_mass = sim->bodies[parent_idx].mass; double child_mass = sim->bodies[i].mass; if (child_mass > 1e20) { REQUIRE(parent_mass > child_mass); } } } } destroy_simulation(sim); } TEST_CASE("Invalid parent: detect placeholder config values", "[init][config][validation]") { const double TIME_STEP = 60.0; SimulationState* sim = create_simulation(10, 0, 0, TIME_STEP); REQUIRE(load_system_config(sim, "tests/configs/earth_mars_simple.toml")); const int EARTH_IDX = 1; const int SPACECRAFT_IDX = 3; Vec3 craft_pos = sim->bodies[SPACECRAFT_IDX].position; Vec3 earth_pos = sim->bodies[EARTH_IDX].position; double distance = vec3_distance(craft_pos, earth_pos); double min_distance = sim->bodies[EARTH_IDX].radius + sim->bodies[SPACECRAFT_IDX].radius; INFO("Distance Earth <-> Spacecraft: " << distance << " m"); INFO("Earth position: (" << earth_pos.x << ", " << earth_pos.y << ", " << earth_pos.z << ")"); INFO("Spacecraft position: (" << craft_pos.x << ", " << craft_pos.y << ", " << craft_pos.z << ")"); INFO("Earth radius: " << sim->bodies[EARTH_IDX].radius << " m"); INFO("Spacecraft radius: " << sim->bodies[SPACECRAFT_IDX].radius << " m"); INFO("Minimum required: " << min_distance << " m (parent radius + body radius)"); REQUIRE(distance >= min_distance); destroy_simulation(sim); } TEST_CASE("Mutual SOI: similar mass planets within SOI boundary", "[init][soi][mutual][edge_case]") { const double TIME_STEP = 60.0; SimulationState* sim = create_simulation(10, 0, 0, TIME_STEP); REQUIRE(load_system_config(sim, "tests/configs/mutual_soi_close.toml")); const int PLANET_A_IDX = 1; const int PLANET_B_IDX = 2; const int SUN_IDX = 0; double planet_a_soi = sim->bodies[PLANET_A_IDX].soi_radius; double planet_b_soi = sim->bodies[PLANET_B_IDX].soi_radius; double separation = vec3_distance(sim->bodies[PLANET_A_IDX].position, sim->bodies[PLANET_B_IDX].position); INFO("PlanetA SOI: " << planet_a_soi / 1e9 << " million km"); INFO("PlanetB SOI: " << planet_b_soi / 1e9 << " million km"); INFO("Separation: " << separation / 1e9 << " million km"); REQUIRE(separation < planet_a_soi); REQUIRE(separation < planet_b_soi); ParentHistory history; for (int step = 0; step < 10000; step++) { update_simulation(sim); history.planet_a_parents.push_back(sim->bodies[PLANET_A_IDX].parent_index); history.planet_b_parents.push_back(sim->bodies[PLANET_B_IDX].parent_index); if (step > 0) { int prev_a = history.planet_a_parents[step-1]; int curr_a = history.planet_a_parents[step]; if (prev_a != curr_a) { INFO("Step " << step << ": PlanetA parent " << prev_a << " -> " << curr_a); } int prev_b = history.planet_b_parents[step-1]; int curr_b = history.planet_b_parents[step]; if (prev_b != curr_b) { INFO("Step " << step << ": PlanetB parent " << prev_b << " -> " << curr_b); } } } int final_parent_a = sim->bodies[PLANET_A_IDX].parent_index; int final_parent_b = sim->bodies[PLANET_B_IDX].parent_index; INFO("Final parent PlanetA: " << final_parent_a); INFO("Final parent PlanetB: " << final_parent_b); REQUIRE(final_parent_a == SUN_IDX); REQUIRE(final_parent_b == SUN_IDX); for (size_t i = 0; i < history.planet_a_parents.size(); i++) { REQUIRE(history.planet_a_parents[i] != PLANET_B_IDX); } for (size_t i = 0; i < history.planet_b_parents.size(); i++) { REQUIRE(history.planet_b_parents[i] != PLANET_A_IDX); } destroy_simulation(sim); }