earths 0.0.4

High-fidelity Earth simulation engine — orbit, atmosphere, geology, hydrology, biosphere, terrain, lighting, rendering, satellites, and temporal systems with full scientific coupling
Documentation
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CloudType {
    Cumulus,
    Stratus,
    Cirrus,
    Cumulonimbus,
    Altocumulus,
    Stratocumulus,
    Nimbostratus,
}

pub struct CloudLayer {
    pub cloud_type: CloudType,
    pub base_altitude_m: f64,
    pub thickness_m: f64,
    pub coverage: f64,
    pub density: f64,
    pub wind_speed_m_s: f64,
    pub wind_direction: [f64; 2],
    pub droplet_radius_um: f64,
    pub ice_fraction: f64,
}

impl CloudLayer {
    pub fn cumulus() -> Self {
        Self {
            cloud_type: CloudType::Cumulus,
            base_altitude_m: 2_000.0,
            thickness_m: 1_500.0,
            coverage: 0.4,
            density: 0.3,
            wind_speed_m_s: 8.0,
            wind_direction: [1.0, 0.0],
            droplet_radius_um: 10.0,
            ice_fraction: 0.0,
        }
    }
    pub fn stratus() -> Self {
        Self {
            cloud_type: CloudType::Stratus,
            base_altitude_m: 500.0,
            thickness_m: 500.0,
            coverage: 0.7,
            density: 0.5,
            wind_speed_m_s: 5.0,
            wind_direction: [0.7, 0.7],
            droplet_radius_um: 8.0,
            ice_fraction: 0.0,
        }
    }
    pub fn cirrus() -> Self {
        Self {
            cloud_type: CloudType::Cirrus,
            base_altitude_m: 8_000.0,
            thickness_m: 1_000.0,
            coverage: 0.3,
            density: 0.05,
            wind_speed_m_s: 30.0,
            wind_direction: [1.0, 0.2],
            droplet_radius_um: 25.0,
            ice_fraction: 1.0,
        }
    }
    pub fn cumulonimbus() -> Self {
        Self {
            cloud_type: CloudType::Cumulonimbus,
            base_altitude_m: 1_000.0,
            thickness_m: 12_000.0,
            coverage: 0.15,
            density: 0.8,
            wind_speed_m_s: 15.0,
            wind_direction: [0.5, 0.5],
            droplet_radius_um: 14.0,
            ice_fraction: 0.3,
        }
    }
    pub fn stratocumulus() -> Self {
        Self {
            cloud_type: CloudType::Stratocumulus,
            base_altitude_m: 600.0,
            thickness_m: 800.0,
            coverage: 0.6,
            density: 0.4,
            wind_speed_m_s: 6.0,
            wind_direction: [0.8, 0.3],
            droplet_radius_um: 9.0,
            ice_fraction: 0.0,
        }
    }
    pub fn nimbostratus() -> Self {
        Self {
            cloud_type: CloudType::Nimbostratus,
            base_altitude_m: 1_500.0,
            thickness_m: 3_000.0,
            coverage: 0.85,
            density: 0.6,
            wind_speed_m_s: 10.0,
            wind_direction: [0.6, 0.4],
            droplet_radius_um: 12.0,
            ice_fraction: 0.1,
        }
    }
}

pub struct CloudSystemEndpoint {
    pub layers: Vec<CloudLayer>,
}

impl CloudSystemEndpoint {
    pub fn earth_default() -> Self {
        Self {
            layers: vec![
                CloudLayer::cumulus(),
                CloudLayer::stratus(),
                CloudLayer::cirrus(),
                CloudLayer::stratocumulus(),
            ],
        }
    }
    pub fn earth_stormy() -> Self {
        Self {
            layers: vec![
                CloudLayer::cumulonimbus(),
                CloudLayer::nimbostratus(),
                CloudLayer::stratus(),
            ],
        }
    }

    pub fn sample_density(&self, sample_alt: f64) -> f64 {
        self.layers
            .iter()
            .map(|l| {
                let alt_dist = (sample_alt - l.base_altitude_m).abs();
                let sigma = l.thickness_m * 0.5;
                let weight = (-0.5 * (alt_dist / sigma.max(1.0)).powi(2)).exp();
                let mie_size_factor = l.droplet_radius_um / 10.0;
                let phase_contrib = l.ice_fraction * 0.7 + (1.0 - l.ice_fraction) * 1.0;
                l.density * l.coverage * weight * mie_size_factor * phase_contrib
            })
            .sum::<f64>()
            / self.layers.len().max(1) as f64
    }

    pub fn wind_transport(&self) -> f64 {
        self.layers
            .iter()
            .map(|l| {
                let wind_dir_len = l.wind_direction[0].hypot(l.wind_direction[1]);
                l.wind_speed_m_s * wind_dir_len.max(0.01) * l.coverage
            })
            .sum::<f64>()
            / self.layers.len().max(1) as f64
    }

    pub fn optical_depth(&self) -> f64 {
        self.layers
            .iter()
            .map(|l| {
                let lwc = 0.3 * l.density;
                let r_eff = l.droplet_radius_um * 1e-6;
                let rho_w = 1000.0;
                let tau = 1.5 * 2.0 * lwc * l.thickness_m * l.coverage / (rho_w * r_eff);
                let ice_correction = 1.0 - 0.15 * l.ice_fraction;
                tau * ice_correction
            })
            .sum()
    }

    pub fn total_thickness(&self) -> f64 {
        self.layers.iter().map(|l| l.thickness_m * l.coverage).sum()
    }
}