mercurys 0.0.3

Mercury celestial simulation crate for the MilkyWay SolarSystem workspace
Documentation
use mercurys::rendering::atmosphere_scattering::*;
use mercurys::rendering::clouds::{cloud_coverage, cloud_optical_depth, has_clouds};
use mercurys::rendering::materials::{PbrMaterial, albedo, roughness};
use mercurys::rendering::ocean_rendering::{
    has_oceans as render_has_oceans, ocean_fraction, specular_reflection, wave_amplitude,
};
use mercurys::rendering::shaders::*;

// === ExosphereEndpoint ===

#[test]
fn exosphere_endpoint_mercury() {
    let e = ExosphereEndpoint::mercury();
    assert!(e.planet_radius_m > 0.0);
    assert!(e.surface_pressure_pa < 1e-5);
    assert!(!e.species.is_empty());
}

#[test]
fn exosphere_has_sodium() {
    let e = ExosphereEndpoint::mercury();
    let na = e.species.iter().find(|s| s.symbol == "Na");
    assert!(na.is_some(), "Mercury must have Na");
}

#[test]
fn exosphere_sodium_dominant_column() {
    let e = ExosphereEndpoint::mercury();
    let na = e.species.iter().find(|s| s.symbol == "Na").unwrap();
    let k = e.species.iter().find(|s| s.symbol == "K").unwrap();
    assert!(na.column_density_cm2 > k.column_density_cm2);
}

#[test]
fn exosphere_species_molar_mass_positive() {
    let e = ExosphereEndpoint::mercury();
    for s in &e.species {
        assert!(
            s.molar_mass_kg_mol > 0.0,
            "{} molar: {}",
            s.name,
            s.molar_mass_kg_mol
        );
    }
}

#[test]
fn exosphere_density_decreases() {
    let e = ExosphereEndpoint::mercury();
    let d0 = e.density_at_altitude("Na", 0.0);
    let d100 = e.density_at_altitude("Na", 100.0);
    assert!(d0 > d100, "Na density drops: {d0} > {d100}");
}

#[test]
fn exosphere_density_unknown_species_zero() {
    let e = ExosphereEndpoint::mercury();
    assert_eq!(e.density_at_altitude("Xe", 0.0), 0.0);
}

#[test]
fn sodium_glow_stronger_closer() {
    let e = ExosphereEndpoint::mercury();
    let close = e.sodium_glow_intensity(0.307);
    let far = e.sodium_glow_intensity(0.467);
    assert!(close > far);
}

#[test]
fn total_column_density_positive() {
    let e = ExosphereEndpoint::mercury();
    assert!(e.total_column_density() > 0.0);
}

#[test]
fn backward_compat_scattering_coefficients() {
    let (r, m) = scattering_coefficients();
    assert!(r >= 0.0 && m >= 0.0);
}

#[test]
fn backward_compat_rayleigh() {
    let c = rayleigh_coefficients();
    for v in &c {
        assert!(v.is_finite());
    }
}

#[test]
fn backward_compat_mie() {
    assert!(mie_coefficient().is_finite());
}

#[test]
fn backward_compat_sky_color() {
    let c = sky_color(0.5);
    for v in &c {
        assert!(v.is_finite());
    }
}

#[test]
fn backward_compat_sodium_glow() {
    let close = sodium_glow_intensity(0.307);
    let far = sodium_glow_intensity(0.467);
    assert!(close >= far);
}

// === Clouds (Mercury has none) ===

#[test]
fn no_clouds_on_mercury() {
    assert!(!has_clouds());
}

#[test]
fn cloud_coverage_zero() {
    assert_eq!(cloud_coverage(), 0.0);
}

#[test]
fn cloud_optical_depth_zero() {
    assert_eq!(cloud_optical_depth(), 0.0);
}

// === Materials ===

#[test]
fn default_regolith_albedo_dark() {
    let m = PbrMaterial::default_regolith();
    assert!(m.albedo[0] < 0.3);
}

#[test]
fn smooth_plains_different_from_regolith() {
    let reg = PbrMaterial::default_regolith();
    let sp = PbrMaterial::smooth_plains();
    assert!(
        (reg.albedo[0] - sp.albedo[0]).abs() > 0.001
            || (reg.roughness - sp.roughness).abs() > 0.001
    );
}

#[test]
fn crater_floor_material_valid() {
    let m = PbrMaterial::crater_floor();
    assert!(m.roughness >= 0.0 && m.roughness <= 1.0);
}

#[test]
fn crater_rim_material_valid() {
    let m = PbrMaterial::crater_rim();
    assert!(m.metallic >= 0.0 && m.metallic <= 1.0);
}

#[test]
fn hollows_material_bright() {
    let m = PbrMaterial::hollows();
    let reg = PbrMaterial::default_regolith();
    assert!(m.albedo[0] > reg.albedo[0], "Hollows are bright features");
}

#[test]
fn polar_ice_material_bright() {
    let m = PbrMaterial::polar_ice();
    assert!(m.albedo[0] > 0.5);
}

#[test]
fn thermal_emission_increases_with_temp() {
    let m = PbrMaterial::default_regolith();
    let e400 = m.thermal_emission(400.0);
    let e700 = m.thermal_emission(700.0);
    let sum400: f32 = e400.iter().sum();
    let sum700: f32 = e700.iter().sum();
    assert!(sum700 > sum400);
}

#[test]
fn global_albedo_value() {
    let a = albedo();
    assert!(a > 0.0 && a < 0.3);
}

#[test]
fn global_roughness_value() {
    let r = roughness();
    assert!(r > 0.0 && r <= 1.0);
}

// === Ocean Rendering (Mercury has none) ===

#[test]
fn no_oceans_render() {
    assert!(!render_has_oceans());
}

#[test]
fn ocean_fraction_zero_render() {
    assert_eq!(ocean_fraction(), 0.0);
}

#[test]
fn wave_amplitude_zero() {
    assert_eq!(wave_amplitude(), 0.0);
}

#[test]
fn specular_reflection_finite() {
    assert!(specular_reflection().is_finite());
}

// === ShaderEndpoint ===

#[test]
fn surface_shader_endpoint() {
    let s = ShaderEndpoint::surface();
    assert!(!s.name.is_empty());
    assert!(s.uniforms.iter().any(|u| u.name == "u_sun_direction"));
    assert!(s.uniforms.iter().any(|u| u.name == "u_surface_temp_k"));
}

#[test]
fn exosphere_shader_endpoint() {
    let s = ShaderEndpoint::exosphere();
    assert!(s.uniforms.iter().any(|u| u.name == "u_sodium_brightness"));
    assert!(s.uniforms.iter().any(|u| u.name == "u_sodium_color"));
}

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

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

// === Backward compat ShaderConfig ===

#[test]
fn mercury_surface_shader_no_atmosphere() {
    let cfg = ShaderConfig::mercury_surface();
    assert!(!cfg.has_atmosphere);
    assert!(!cfg.has_ocean);
    assert!(!cfg.has_clouds);
}

#[test]
fn mercury_nightside_thermal() {
    let cfg = ShaderConfig::mercury_nightside();
    assert!(cfg.thermal_overlay);
}

#[test]
fn active_features_surface() {
    let cfg = ShaderConfig::mercury_surface();
    let features = cfg.active_features();
    assert!(features.contains(&"normal_map"));
}

#[test]
fn shader_pass_variants_exist() {
    let passes = [
        ShaderPass::Depth,
        ShaderPass::Color,
        ShaderPass::Shadow,
        ShaderPass::Thermal,
    ];
    assert_eq!(passes.len(), 4);
    assert_ne!(passes[0], passes[1]);
}

#[test]
fn backward_compat_shader_id() {
    assert!(!shader_id().is_empty());
}