venuss 0.0.3

Venus celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use venuss::rendering::atmosphere_scattering::*;
use venuss::rendering::clouds::*;
use venuss::rendering::materials::*;
use venuss::rendering::shaders::*;
use venuss::rendering::surface_haze::*;

// === AtmosphereEndpoint ===

#[test]
fn atmosphere_endpoint_venus() {
    let a = AtmosphereEndpoint::venus();
    assert!(a.planet_radius_m > 0.0);
    assert!(a.atmosphere_height_m > 0.0);
    assert!(a.sea_level_pressure_pa > 9e6);
    assert!(!a.species.is_empty());
}

#[test]
fn atmosphere_species_co2_dominant() {
    let a = AtmosphereEndpoint::venus();
    let co2 = a.species.iter().find(|s| s.symbol == "CO2").unwrap();
    assert!(
        co2.volume_fraction > 0.9,
        "CO2 dominant: {}",
        co2.volume_fraction
    );
}

#[test]
fn atmosphere_has_so2() {
    let a = AtmosphereEndpoint::venus();
    let so2 = a.species.iter().find(|s| s.symbol == "SO2");
    assert!(so2.is_some(), "Venus must have SO2");
}

#[test]
fn atmosphere_species_molar_mass_positive() {
    let a = AtmosphereEndpoint::venus();
    for s in &a.species {
        assert!(
            s.molar_mass_kg_mol > 0.0,
            "{} molar mass: {}",
            s.name,
            s.molar_mass_kg_mol
        );
    }
}

#[test]
fn rayleigh_density_decreases_with_alt() {
    let a = AtmosphereEndpoint::venus();
    let d0 = a.rayleigh_density(0.0);
    let d10 = a.rayleigh_density(10_000.0);
    assert!(d0 > d10);
}

#[test]
fn sky_color_rgb_range() {
    let a = AtmosphereEndpoint::venus();
    let c = a.sky_color(45.0);
    assert!(c[0] >= 0.0 && c[0] <= 1.0);
    assert!(c[1] >= 0.0 && c[1] <= 1.0);
    assert!(c[2] >= 0.0 && c[2] <= 1.0);
}

#[test]
fn sky_color_orange_tint() {
    let a = AtmosphereEndpoint::venus();
    let c = a.sky_color(45.0);
    assert!(c[0] > c[2], "Venus sky orange: r={} > b={}", c[0], c[2]);
}

#[test]
fn direct_transmission_very_low() {
    let a = AtmosphereEndpoint::venus();
    let t = a.direct_transmission(45.0);
    assert!(t < 0.5, "Venus thick clouds block most light: {t}");
}

#[test]
fn direct_transmission_bounded() {
    let a = AtmosphereEndpoint::venus();
    let t = a.direct_transmission(90.0);
    assert!((0.0..=1.0).contains(&t));
}

#[test]
fn sky_luminance_positive() {
    let a = AtmosphereEndpoint::venus();
    let l = a.sky_luminance(0.0, 0.7);
    assert!(l >= 0.0);
}

#[test]
fn rayleigh_coefficients_positive() {
    let a = AtmosphereEndpoint::venus();
    assert!(a.rayleigh_coefficients_rgb[0] > 0.0);
    assert!(a.rayleigh_coefficients_rgb[1] > 0.0);
    assert!(a.rayleigh_coefficients_rgb[2] > 0.0);
}

#[test]
fn cloud_optical_depth_high() {
    let a = AtmosphereEndpoint::venus();
    assert!(
        a.cloud_optical_depth > 10.0,
        "Venus cloud OD: {}",
        a.cloud_optical_depth
    );
}

// === CloudSystemEndpoint ===

#[test]
fn cloud_system_three_layers() {
    let cs = CloudSystemEndpoint::venus_default();
    assert_eq!(cs.layers.len(), 3);
}

#[test]
fn cloud_system_total_od() {
    let cs = CloudSystemEndpoint::venus_default();
    assert!(cs.total_optical_depth > 20.0);
}

#[test]
fn cloud_top_above_base() {
    let cs = CloudSystemEndpoint::venus_default();
    assert!(cs.cloud_top_altitude_m() > cs.cloud_base_altitude_m());
}

#[test]
fn cloud_base_in_atmosphere() {
    let cs = CloudSystemEndpoint::venus_default();
    assert!(cs.cloud_base_altitude_m() > 40_000.0);
}

#[test]
fn cloud_top_below_100km() {
    let cs = CloudSystemEndpoint::venus_default();
    assert!(cs.cloud_top_altitude_m() < 100_000.0);
}

#[test]
fn cloud_transmission_very_low() {
    let cs = CloudSystemEndpoint::venus_default();
    let t = cs.transmission();
    assert!(t < 1e-5, "Cloud transmission near zero: {t}");
}

#[test]
fn cloud_layer_at_55km() {
    let cs = CloudSystemEndpoint::venus_default();
    let layer = cs.layer_at_altitude(55_000.0);
    assert!(layer.is_some());
    assert_eq!(layer.unwrap().name, "middle_cloud");
}

#[test]
fn cloud_h2so4_composition() {
    let cs = CloudSystemEndpoint::venus_default();
    assert!(cs.layers.iter().any(|l| l.composition.contains("H2SO4")));
}

#[test]
fn backward_compat_cloud_fns() {
    assert!(cloud_top_altitude_m() > cloud_base_altitude_m());
}

// === SurfaceHazeEndpoint ===

#[test]
fn surface_haze_venus() {
    let h = SurfaceHazeEndpoint::venus_default();
    assert!(h.air_density_kg_m3 > 50.0);
    assert!(h.visibility_km > 0.0 && h.visibility_km < 50.0);
}

#[test]
fn extinction_coefficient_positive() {
    let h = SurfaceHazeEndpoint::venus_default();
    assert!(h.extinction_coefficient_per_km() > 0.0);
}

#[test]
fn transmittance_decreases_with_range() {
    let h = SurfaceHazeEndpoint::venus_default();
    let t1 = h.transmittance_at_range(1.0);
    let t5 = h.transmittance_at_range(5.0);
    assert!(t1 > t5);
}

#[test]
fn backward_compat_visibility() {
    assert!(near_surface_visibility_km() > 0.0);
}

// === Materials ===

#[test]
fn basaltic_plain_albedo_valid() {
    let m = basaltic_plain();
    for c in &m.albedo {
        assert!(*c >= 0.0 && *c <= 1.0);
    }
}

#[test]
fn basaltic_plain_roughness_valid() {
    let m = basaltic_plain();
    assert!(m.roughness >= 0.0 && m.roughness <= 1.0);
}

#[test]
fn tessera_highland_different_from_basaltic() {
    let b = basaltic_plain();
    let t = tessera_highland();
    let diff = (b.roughness - t.roughness).abs() + (b.albedo[0] - t.albedo[0]).abs();
    assert!(diff > 0.0);
}

#[test]
fn volcanic_flow_emissive_nonzero() {
    let v = volcanic_flow();
    let sum: f32 = v.emissive.iter().sum();
    assert!(sum >= 0.0);
}

#[test]
fn weathered_plain_metallic_low() {
    let w = weathered_plain();
    assert!(w.metallic < 0.5);
}

#[test]
fn all_materials_normal_strength_positive() {
    for m in [
        basaltic_plain(),
        tessera_highland(),
        volcanic_flow(),
        weathered_plain(),
    ] {
        assert!(m.normal_strength >= 0.0);
    }
}

// === ShaderEndpoint ===

#[test]
fn terrain_shader_endpoint() {
    let s = ShaderEndpoint::terrain();
    assert!(!s.name.is_empty());
    assert!(!s.uniforms.is_empty());
    assert!(s.uniforms.iter().any(|u| u.name == "u_planet_radius"));
}

#[test]
fn atmosphere_shader_endpoint() {
    let s = ShaderEndpoint::atmosphere();
    assert!(!s.name.is_empty());
    assert!(s.uniforms.iter().any(|u| u.name == "u_cloud_optical_depth"));
}

#[test]
fn surface_haze_shader_endpoint() {
    let s = ShaderEndpoint::surface_haze();
    assert!(s.uniforms.iter().any(|u| u.name == "u_visibility_km"));
}

#[test]
fn shader_uniform_mutation() {
    let mut s = ShaderEndpoint::terrain();
    s.uniforms[0].value = UniformValue::Float(1.0);
    match &s.uniforms[0].value {
        UniformValue::Float(v) => assert_eq!(*v, 1.0),
        _ => panic!("Expected Float"),
    }
}