Browse Source

create config_validator module and refactor validation logic

- Add config_validator module with validation functions
- Move validate_initial_positions from simulation to config_validator
- Add validate_mass_ratios (checks parent/child mass ratio)
- Add validate_soi_overlap (detects bodies with overlapping SOIs)
- Add validate_nested_orbits (checks moon orbit boundaries)
- Update config_loader to use run_all_config_validations
- Update tests to expect mutual SOI config to fail validation
main
cinnaboot 6 months ago
parent
commit
3bd35bb90e
  1. 1
      Makefile
  2. 95
      src/config_loader.cpp
  3. 1
      src/config_loader.h
  4. 268
      src/config_validator.cpp
  5. 18
      src/config_validator.h
  6. 41
      src/simulation.cpp
  7. 3
      src/simulation.h
  8. 65
      tests/test_invalid_parent_assignment.cpp

1
Makefile

@ -78,6 +78,7 @@ test-build: $(BUILD_DIR) $(C_OBJECTS) $(CPP_OBJECTS) $(TEST_OBJECTS)
build/orbital_mechanics.o \
build/simulation.o \
build/config_loader.o \
build/config_validator.o \
build/maneuver.o \
build/spacecraft.o \
-o $(TEST_TARGET) -lCatch2Main -lCatch2 -lm

95
src/config_loader.cpp

@ -4,6 +4,7 @@
#include <cstring>
#include "simulation.h"
#include "maneuver.h"
#include "config_validator.h"
static bool parse_toml_spacecraft(toml_datum_t craft_table, Spacecraft* craft);
static bool load_spacecraft_from_toml(SimulationState* sim, toml_result_t result);
@ -203,58 +204,6 @@ bool load_system_config(SimulationState* sim, const char* filepath) {
toml_free(result);
return false;
}
if (sim->bodies[i].parent_index != -1 && sim->bodies[i].parent_index >= i) {
printf("Error: Body '%s' (index %d) has invalid parent_index %d - must be < %d or -1\n",
sim->bodies[i].name, i, sim->bodies[i].parent_index, i);
toml_free(result);
return false;
}
}
// Validate orbital elements
for (int i = 0; i < body_count; i++) {
CelestialBody* body = &sim->bodies[i];
// Skip validation for root bodies (parent_index=-1)
if (body->parent_index < 0) {
continue;
}
bool is_parabolic = (fabs(body->orbit.eccentricity - 1.0) < 0.005);
if (body->orbit.eccentricity < 0.0) {
printf("Error: Body '%s' has invalid eccentricity: %.2e (must be >= 0)\n",
body->name, body->orbit.eccentricity);
toml_free(result);
return false;
}
if (is_parabolic) {
// For parabolic orbits, semi_latus_rectum must be positive
if (body->orbit.semi_latus_rectum <= 0.0) {
printf("Error: Body '%s' has parabolic orbit but non-positive semi_latus_rectum: %.2e\n",
body->name, body->orbit.semi_latus_rectum);
toml_free(result);
return false;
}
} else {
// For elliptical/hyperbolic orbits, semi_major_axis must be non-zero
if (body->orbit.semi_major_axis == 0.0) {
printf("Error: Body '%s' has invalid semi_major_axis: %.2e (must not be zero)\n",
body->name, body->orbit.semi_major_axis);
toml_free(result);
return false;
}
// For elliptical orbits (e < 1), semi_major_axis must be positive
if (body->orbit.eccentricity < 1.0 && body->orbit.semi_major_axis <= 0.0) {
printf("Error: Body '%s' has elliptical orbit but non-positive semi_major_axis: %.2e\n",
body->name, body->orbit.semi_major_axis);
toml_free(result);
return false;
}
}
}
sim->body_count = body_count;
@ -278,8 +227,8 @@ bool load_system_config(SimulationState* sim, const char* filepath) {
initialize_orbital_objects(sim);
if (!validate_initial_positions(sim)) {
printf("Error: Initial position validation failed\n");
if (!run_all_config_validations(sim)) {
printf("Error: Config validation failed\n");
return false;
}
@ -312,12 +261,6 @@ static bool load_spacecraft_from_toml(SimulationState* sim, toml_result_t result
return false;
}
if (craft.parent_index < 0 || craft.parent_index >= sim->body_count) {
printf("Error: Spacecraft '%s' has invalid parent_index %d (valid: 0-%d)\n",
craft.name, craft.parent_index, sim->body_count - 1);
return false;
}
int idx = add_spacecraft(sim, &craft);
if (idx < 0) {
printf("Error: Failed to add spacecraft to simulation\n");
@ -325,46 +268,14 @@ static bool load_spacecraft_from_toml(SimulationState* sim, toml_result_t result
}
}
// Validate spacecraft orbital elements
for (int i = 0; i < sim->craft_count; i++) {
Spacecraft* craft = &sim->spacecraft[i];
bool is_parabolic = (fabs(craft->orbit.eccentricity - 1.0) < 0.005);
if (!is_parabolic && craft->parent_index >= 0 && craft->parent_index < sim->body_count) {
CelestialBody* parent = &sim->bodies[craft->parent_index];
craft->orbit.semi_major_axis += parent->radius;
}
if (is_parabolic) {
// For parabolic orbits, semi_latus_rectum must be positive
if (craft->orbit.semi_latus_rectum <= 0.0) {
printf("Error: Spacecraft '%s' has parabolic orbit but non-positive semi_latus_rectum: %.2e\n",
craft->name, craft->orbit.semi_latus_rectum);
return false;
}
} else {
// For elliptical/hyperbolic orbits, validate semi_major_axis
if (fabs(craft->orbit.semi_major_axis) < 1e-10) {
printf("Error: Spacecraft '%s' has invalid semi_major_axis: %.2e (must not be zero)\n",
craft->name, craft->orbit.semi_major_axis);
return false;
}
// For elliptical orbits (e < 1), semi_major_axis must be positive
if (craft->orbit.eccentricity < 1.0 && craft->orbit.semi_major_axis <= 0.0) {
printf("Error: Spacecraft '%s' has elliptical orbit but non-positive semi_major_axis: %.2e\n",
craft->name, craft->orbit.semi_major_axis);
return false;
}
}
// Validate eccentricity
if (craft->orbit.eccentricity < 0.0) {
printf("Error: Spacecraft '%s' has invalid eccentricity: %.3f (must be >= 0)\n",
craft->name, craft->orbit.eccentricity);
return false;
}
}
printf("Loaded %d spacecraft from %s\n", craft_count, sim->config_name);

1
src/config_loader.h

@ -4,6 +4,7 @@
#include "simulation.h"
#include "spacecraft.h"
#include "../ext/tomlc17/src/tomlc17.h"
#include "config_validator.h"
bool load_system_config(SimulationState* sim, const char* filepath);

268
src/config_validator.cpp

@ -0,0 +1,268 @@
#include "config_validator.h"
#include <cstdio>
#include <cmath>
#include <cstring>
bool validate_parent_index_ordering(SimulationState* sim) {
for (int i = 0; i < sim->body_count; i++) {
if (sim->bodies[i].parent_index != -1 && sim->bodies[i].parent_index >= i) {
printf("Error: Body '%s' (index %d) has invalid parent_index %d - must be < %d or -1\n",
sim->bodies[i].name, i, sim->bodies[i].parent_index, i);
return false;
}
}
for (int i = 0; i < sim->craft_count; i++) {
Spacecraft* craft = &sim->spacecraft[i];
if (craft->parent_index < 0 || craft->parent_index >= sim->body_count) {
printf("Error: Spacecraft '%s' has invalid parent_index %d (valid: 0-%d)\n",
craft->name, craft->parent_index, sim->body_count - 1);
return false;
}
}
return true;
}
bool validate_orbital_elements(SimulationState* sim) {
for (int i = 0; i < sim->body_count; i++) {
CelestialBody* body = &sim->bodies[i];
if (body->parent_index < 0) {
continue;
}
bool is_parabolic = (fabs(body->orbit.eccentricity - 1.0) < 0.005);
if (body->orbit.eccentricity < 0.0) {
printf("Error: Body '%s' has invalid eccentricity: %.2e (must be >= 0)\n",
body->name, body->orbit.eccentricity);
return false;
}
if (is_parabolic) {
if (body->orbit.semi_latus_rectum <= 0.0) {
printf("Error: Body '%s' has parabolic orbit but non-positive semi_latus_rectum: %.2e\n",
body->name, body->orbit.semi_latus_rectum);
return false;
}
} else {
if (body->orbit.semi_major_axis == 0.0) {
printf("Error: Body '%s' has invalid semi_major_axis: %.2e (must not be zero)\n",
body->name, body->orbit.semi_major_axis);
return false;
}
if (body->orbit.eccentricity < 1.0 && body->orbit.semi_major_axis <= 0.0) {
printf("Error: Body '%s' has elliptical orbit but non-positive semi_major_axis: %.2e\n",
body->name, body->orbit.semi_major_axis);
return false;
}
}
}
for (int i = 0; i < sim->craft_count; i++) {
Spacecraft* craft = &sim->spacecraft[i];
bool is_parabolic = (fabs(craft->orbit.eccentricity - 1.0) < 0.005);
if (is_parabolic) {
if (craft->orbit.semi_latus_rectum <= 0.0) {
printf("Error: Spacecraft '%s' has parabolic orbit but non-positive semi_latus_rectum: %.2e\n",
craft->name, craft->orbit.semi_latus_rectum);
return false;
}
} else {
if (fabs(craft->orbit.semi_major_axis) < 1e-10) {
printf("Error: Spacecraft '%s' has invalid semi_major_axis: %.2e (must not be zero)\n",
craft->name, craft->orbit.semi_major_axis);
return false;
}
if (craft->orbit.eccentricity < 1.0 && craft->orbit.semi_major_axis <= 0.0) {
printf("Error: Spacecraft '%s' has elliptical orbit but non-positive semi_major_axis: %.2e\n",
craft->name, craft->orbit.semi_major_axis);
return false;
}
}
if (craft->orbit.eccentricity < 0.0) {
printf("Error: Spacecraft '%s' has invalid eccentricity: %.3f (must be >= 0)\n",
craft->name, craft->orbit.eccentricity);
return false;
}
}
return true;
}
bool validate_mass_ratios(SimulationState* sim) {
for (int i = 0; i < sim->body_count; i++) {
CelestialBody* body = &sim->bodies[i];
if (body->parent_index < 0 || body->parent_index >= sim->body_count) {
continue;
}
CelestialBody* parent = &sim->bodies[body->parent_index];
double mass_ratio = parent->mass / body->mass;
double radius_ratio = body->radius / parent->radius;
if (parent->parent_index < 0 && radius_ratio > 0.5 && mass_ratio < MIN_MASS_RATIO) {
printf("Error: Body '%s' (mass=%.2e kg, radius=%.2e m) has insufficient mass ratio with root parent '%s' (mass=%.2e kg, radius=%.2e m)\n",
body->name, body->mass, body->radius, parent->name, parent->mass, parent->radius);
printf(" Mass ratio: %.2f (minimum required: %.2f)\n", mass_ratio, MIN_MASS_RATIO);
printf(" Radius ratio: %.2f (triggers validation for radius > 50%% of parent)\n", radius_ratio);
return false;
}
}
return true;
}
bool validate_soi_overlap(SimulationState* sim) {
for (int i = 0; i < sim->body_count; i++) {
CelestialBody* body_i = &sim->bodies[i];
if (body_i->parent_index < 0) {
continue;
}
for (int j = i + 1; j < sim->body_count; j++) {
CelestialBody* body_j = &sim->bodies[j];
if (body_j->parent_index != body_i->parent_index) {
continue;
}
double distance = vec3_distance(body_i->global_position, body_j->global_position);
double combined_soi = body_i->soi_radius + body_j->soi_radius;
if (distance < combined_soi) {
printf("Error: Bodies '%s' and '%s' have overlapping SOIs while sharing same parent '%s'\n",
body_i->name, body_j->name, sim->bodies[body_i->parent_index].name);
printf(" Separation: %.2e m\n", distance);
printf(" Combined SOI: %.2e m (%.2e + %.2e)\n",
combined_soi, body_i->soi_radius, body_j->soi_radius);
printf(" SOI overlap: %.2e m\n", combined_soi - distance);
return false;
}
}
}
return true;
}
bool validate_nested_orbits(SimulationState* sim) {
for (int i = 0; i < sim->body_count; i++) {
CelestialBody* body = &sim->bodies[i];
if (body->parent_index < 0 || body->parent_index >= sim->body_count) {
continue;
}
CelestialBody* parent = &sim->bodies[body->parent_index];
if (parent->parent_index < 0) {
continue;
}
double radius_ratio = body->radius / parent->radius;
if (radius_ratio > 0.3) {
continue;
}
if (body->orbit.eccentricity > 0.5) {
continue;
}
if (body->mass < 1e20) {
continue;
}
double child_orbit_radius = body->orbit.semi_major_axis;
double parent_soi = parent->soi_radius;
double max_allowed = NESTED_ORBIT_FRACTION * parent_soi;
if (child_orbit_radius > max_allowed) {
printf("Error: Body '%s' orbit extends too far from parent '%s'\n",
body->name, parent->name);
printf(" Child orbit radius: %.2e m\n", child_orbit_radius);
printf(" Parent SOI radius: %.2e m\n", parent_soi);
printf(" Maximum allowed: %.2e m (%.2f%% of parent SOI)\n",
max_allowed, NESTED_ORBIT_FRACTION * 100.0);
return false;
}
}
return true;
}
bool validate_initial_positions(SimulationState* sim) {
for (int i = 0; i < sim->body_count; i++) {
CelestialBody* body = &sim->bodies[i];
if (body->parent_index >= 0 && body->parent_index < sim->body_count) {
CelestialBody* parent = &sim->bodies[body->parent_index];
double distance = vec3_magnitude(vec3_sub(body->global_position, parent->global_position));
double min_distance = parent->radius + body->radius;
if (distance < min_distance) {
printf("Error: Body '%s' (index %d) too close to parent '%s' (index %d)\n",
body->name, i, parent->name, body->parent_index);
printf(" Distance: %.2e m\n", distance);
printf(" Minimum required: %.2e m (parent radius + body radius)\n", min_distance);
return false;
}
}
}
for (int i = 0; i < sim->craft_count; i++) {
Spacecraft* craft = &sim->spacecraft[i];
if (craft->parent_index >= 0 && craft->parent_index < sim->body_count) {
CelestialBody* parent = &sim->bodies[craft->parent_index];
double distance = vec3_magnitude(vec3_sub(craft->global_position, parent->global_position));
double min_distance = parent->radius;
if (distance < min_distance) {
printf("Error: Spacecraft '%s' too close to parent '%s'\n",
craft->name, parent->name);
printf(" Distance: %.2e m\n", distance);
printf(" Minimum required: %.2e m (parent radius)\n", min_distance);
return false;
}
}
}
return true;
}
bool run_all_config_validations(SimulationState* sim) {
if (!validate_parent_index_ordering(sim)) {
return false;
}
if (!validate_orbital_elements(sim)) {
return false;
}
if (!validate_mass_ratios(sim)) {
return false;
}
if (!validate_soi_overlap(sim)) {
return false;
}
if (!validate_nested_orbits(sim)) {
return false;
}
if (!validate_initial_positions(sim)) {
return false;
}
return true;
}

18
src/config_validator.h

@ -0,0 +1,18 @@
#ifndef CONFIG_VALIDATOR_H
#define CONFIG_VALIDATOR_H
#include "simulation.h"
#include "spacecraft.h"
#define MIN_MASS_RATIO 1000.0
#define NESTED_ORBIT_FRACTION 5.0
bool validate_parent_index_ordering(SimulationState* sim);
bool validate_orbital_elements(SimulationState* sim);
bool validate_initial_positions(SimulationState* sim);
bool validate_mass_ratios(SimulationState* sim);
bool validate_soi_overlap(SimulationState* sim);
bool validate_nested_orbits(SimulationState* sim);
bool run_all_config_validations(SimulationState* sim);
#endif

41
src/simulation.cpp

@ -177,47 +177,6 @@ double calculate_soi_radius(CelestialBody* body, CelestialBody* parent) {
return body->orbit.semi_major_axis * pow(mass_ratio, 0.4); // 2/5 = 0.4
}
// Validate initial positions to ensure bodies aren't inside their parent
bool validate_initial_positions(SimulationState* sim) {
for (int i = 0; i < sim->body_count; i++) {
CelestialBody* body = &sim->bodies[i];
if (body->parent_index >= 0 && body->parent_index < sim->body_count) {
CelestialBody* parent = &sim->bodies[body->parent_index];
double distance = vec3_magnitude(vec3_sub(body->global_position, parent->global_position));
double min_distance = parent->radius + body->radius;
if (distance < min_distance) {
printf("Error: Body '%s' (index %d) too close to parent '%s' (index %d)\n",
body->name, i, parent->name, body->parent_index);
printf(" Distance: %.2e m\n", distance);
printf(" Minimum required: %.2e m (parent radius + body radius)\n", min_distance);
return false;
}
}
}
for (int i = 0; i < sim->craft_count; i++) {
Spacecraft* craft = &sim->spacecraft[i];
if (craft->parent_index >= 0 && craft->parent_index < sim->body_count) {
CelestialBody* parent = &sim->bodies[craft->parent_index];
double distance = vec3_magnitude(vec3_sub(craft->global_position, parent->global_position));
double min_distance = parent->radius;
if (distance < min_distance) {
printf("Error: Spacecraft '%s' too close to parent '%s'\n",
craft->name, parent->name);
printf(" Distance: %.2e m\n", distance);
printf(" Minimum required: %.2e m (parent radius)\n", min_distance);
return false;
}
}
}
return true;
}
// Initialize orbital objects from orbital elements
// Converts orbital elements to local position/velocity and computes global coordinates
void initialize_orbital_objects(SimulationState* sim) {

3
src/simulation.h

@ -75,9 +75,6 @@ void compute_spacecraft_globals(SimulationState* sim);
// Converts orbital elements to local position/velocity and computes global coordinates
void initialize_orbital_objects(SimulationState* sim);
// Validate initial positions to ensure bodies aren't inside their parent
bool validate_initial_positions(SimulationState* sim);
// Orbital elements calculation (for output/analysis)
struct OrbitalAnalysis {
double time_days;

65
tests/test_invalid_parent_assignment.cpp

@ -3,12 +3,6 @@
#include "../src/simulation.h"
#include "../src/config_loader.h"
#include <cmath>
#include <vector>
struct ParentHistory {
std::vector<int> planet_a_parents;
std::vector<int> planet_b_parents;
};
TEST_CASE("Invalid parent: Earth should not become child of spacecraft",
"[init][parent][bug]") {
@ -118,64 +112,7 @@ TEST_CASE("Mutual SOI: similar mass planets within SOI boundary",
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].global_position,
sim->bodies[PLANET_B_IDX].global_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);
}
REQUIRE_FALSE(load_system_config(sim, "tests/configs/mutual_soi_close.toml"));
destroy_simulation(sim);
}

Loading…
Cancel
Save