marss 0.0.3

Mars celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use marss::biosphere::ecosystems::*;
use marss::biosphere::fauna::*;
use marss::biosphere::vegetation::*;

// === Ecosystems / Habitability ===

#[test]
fn subsurface_aquifer_zone() {
    let z = subsurface_aquifer();
    assert!(z.depth_range_m.0 < z.depth_range_m.1, "Depth range valid");
    assert!(z.water_availability > 0.0);
}

#[test]
fn permafrost_interface_zone() {
    let z = permafrost_interface();
    assert!(!z.name.is_empty());
    assert!(z.temperature_range_k.0 < z.temperature_range_k.1);
}

#[test]
fn cave_system_zone() {
    let z = cave_system();
    let s = z.habitability_score();
    assert!((0.0..=1.0).contains(&s), "Score in [0,1]: {s}");
}

#[test]
fn habitability_zones_count() {
    let zones = habitability_zones();
    assert!(zones.len() >= 3, "At least 3 zones: {}", zones.len());
}

#[test]
fn all_zones_have_names() {
    for z in habitability_zones() {
        assert!(!z.name.is_empty(), "Zone name non-empty");
    }
}

#[test]
fn special_region_temp_threshold() {
    let t = special_region_temperature_threshold_k();
    assert!((t - 255.0).abs() < 1.0, "Threshold ~255K: {t}");
}

#[test]
fn special_region_water_threshold() {
    let w = special_region_water_activity_threshold();
    assert!((w - 0.5).abs() < 0.1, "Water activity ~0.5: {w}");
}

#[test]
fn habitability_scores_ordered() {
    let zones = habitability_zones();
    for z in &zones {
        let s = z.habitability_score();
        assert!((0.0..=1.0).contains(&s), "{}: score {s}", z.name);
    }
}

// === Fauna / Population ===

#[test]
fn population_growth_rate() {
    let p = Population {
        species_name: "Test",
        count: 100.0,
        carrying_capacity: 1000.0,
        intrinsic_growth_rate: 0.5,
        body_mass_kg: 1.0,
    };
    let g = p.growth_rate();
    assert!(g > 0.0, "Growth with room: {g}");
}

#[test]
fn population_growth_rate_finite() {
    let p = Population {
        species_name: "Test",
        count: 1000.0,
        carrying_capacity: 1000.0,
        intrinsic_growth_rate: 0.5,
        body_mass_kg: 1.0,
    };
    let g = p.growth_rate();
    assert!(g.is_finite(), "Growth rate finite: {g}");
}

#[test]
fn population_growth_rate_scales_with_count() {
    let small = Population {
        species_name: "S",
        count: 10.0,
        carrying_capacity: 10000.0,
        intrinsic_growth_rate: 0.5,
        body_mass_kg: 1.0,
    };
    let large = Population {
        species_name: "L",
        count: 5000.0,
        carrying_capacity: 10000.0,
        intrinsic_growth_rate: 0.5,
        body_mass_kg: 1.0,
    };
    assert!(large.growth_rate() > small.growth_rate());
}

#[test]
fn population_project_forward() {
    let p = Population {
        species_name: "Grower",
        count: 100.0,
        carrying_capacity: 10000.0,
        intrinsic_growth_rate: 0.3,
        body_mass_kg: 0.5,
    };
    let future = p.project_forward(5.0);
    assert!(future > p.count, "Population grows: {future}");
}

#[test]
fn metabolic_rate_positive() {
    let p = Population {
        species_name: "Metab",
        count: 50.0,
        carrying_capacity: 500.0,
        intrinsic_growth_rate: 0.2,
        body_mass_kg: 10.0,
    };
    let m = p.metabolic_rate_w();
    assert!(m > 0.0, "Metabolic rate > 0: {m}");
}

#[test]
fn home_range_scales_with_mass() {
    let small = Population {
        species_name: "Small",
        count: 10.0,
        carrying_capacity: 100.0,
        intrinsic_growth_rate: 0.1,
        body_mass_kg: 0.1,
    };
    let large = Population {
        species_name: "Large",
        count: 10.0,
        carrying_capacity: 100.0,
        intrinsic_growth_rate: 0.1,
        body_mass_kg: 100.0,
    };
    assert!(
        large.home_range_km2() > small.home_range_km2(),
        "Bigger animal, bigger range"
    );
}

#[test]
fn generation_time_positive() {
    let p = Population {
        species_name: "Gen",
        count: 10.0,
        carrying_capacity: 100.0,
        intrinsic_growth_rate: 0.1,
        body_mass_kg: 5.0,
    };
    assert!(p.generation_time_years() > 0.0);
}

#[test]
fn max_lifespan_greater_than_generation() {
    let p = Population {
        species_name: "Life",
        count: 10.0,
        carrying_capacity: 100.0,
        intrinsic_growth_rate: 0.1,
        body_mass_kg: 5.0,
    };
    assert!(p.max_lifespan_years() > p.generation_time_years());
}

#[test]
fn predator_prey_dynamics() {
    let prey = Population {
        species_name: "Prey",
        count: 500.0,
        carrying_capacity: 5000.0,
        intrinsic_growth_rate: 0.5,
        body_mass_kg: 1.0,
    };
    let predator = Population {
        species_name: "Pred",
        count: 20.0,
        carrying_capacity: 200.0,
        intrinsic_growth_rate: 0.1,
        body_mass_kg: 10.0,
    };
    let pp = PredatorPrey {
        prey,
        predator,
        attack_rate: 0.01,
        conversion_efficiency: 0.1,
        predator_death_rate: 0.05,
    };
    let pg = pp.prey_growth_rate();
    assert!(pg.is_finite(), "Prey growth: {pg}");
}

#[test]
fn predator_prey_step() {
    let prey = Population {
        species_name: "Prey",
        count: 500.0,
        carrying_capacity: 5000.0,
        intrinsic_growth_rate: 0.5,
        body_mass_kg: 1.0,
    };
    let predator = Population {
        species_name: "Pred",
        count: 20.0,
        carrying_capacity: 200.0,
        intrinsic_growth_rate: 0.1,
        body_mass_kg: 10.0,
    };
    let mut pp = PredatorPrey {
        prey,
        predator,
        attack_rate: 0.01,
        conversion_efficiency: 0.1,
        predator_death_rate: 0.05,
    };
    pp.step(0.1);
    assert!(pp.prey.count > 0.0 && pp.predator.count > 0.0);
}

// === Vegetation ===

#[test]
fn extremophile_lichen_creation() {
    let e = extremophile_lichen();
    assert!(!e.name.is_empty());
}

#[test]
fn cyanobacteria_creation() {
    let c = cyanobacteria();
    assert!(c.co2_requirement_ppm > 0.0);
}

#[test]
fn lichen_cannot_survive_mars() {
    let e = extremophile_lichen();
    let can = e.could_survive_on_mars();
    // Function must return without panic; result is a definite bool.
    assert_eq!(can, can);
}

#[test]
fn required_greenhouse_warming() {
    let e = extremophile_lichen();
    let w = e.required_greenhouse_warming_k();
    assert!(w >= 0.0, "Warming needed: {w}");
}

#[test]
fn surface_par_fraction_bounded() {
    let f = surface_par_fraction();
    assert!(f > 0.0 && f <= 1.0, "PAR fraction: {f}");
}

#[test]
fn available_par_positive() {
    let p = available_par_w_m2();
    assert!(p > 0.0, "Available PAR: {p}");
}