Skip to main content

proof_engine/ecology/
evolution.rs

1//! Evolution simulation — mutation, selection, genetic drift.
2
3use crate::worldgen::Rng;
4
5/// Simplified trait for evolution (not full genome — see worldgen::genetics for that).
6#[derive(Debug, Clone)]
7pub struct EvolvingTrait {
8    pub name: String,
9    pub value: f64,
10    pub heritability: f64,
11    pub mutation_variance: f64,
12}
13
14/// Evolve a trait over one generation given selection pressure.
15pub fn evolve_trait(trait_val: &mut EvolvingTrait, selection_optimum: f64, pop_size: usize, rng: &mut Rng) {
16    // Selection: move toward optimum
17    let selection_diff = selection_optimum - trait_val.value;
18    let selection_response = selection_diff * trait_val.heritability * 0.1;
19
20    // Genetic drift: random fluctuation inversely proportional to pop size
21    let drift = rng.gaussian() * (1.0 / (pop_size as f64).sqrt()) * trait_val.mutation_variance;
22
23    // Mutation
24    let mutation = rng.gaussian() * trait_val.mutation_variance * 0.01;
25
26    trait_val.value += selection_response + drift + mutation;
27}
28
29/// Simulate speciation check: if trait divergence exceeds threshold.
30pub fn check_speciation(pop_a_trait: f64, pop_b_trait: f64, threshold: f64) -> bool {
31    (pop_a_trait - pop_b_trait).abs() > threshold
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37
38    #[test]
39    fn test_evolve_trait_toward_optimum() {
40        let mut rng = Rng::new(42);
41        let mut t = EvolvingTrait {
42            name: "size".to_string(), value: 0.5, heritability: 0.8, mutation_variance: 0.1,
43        };
44        for _ in 0..1000 {
45            evolve_trait(&mut t, 0.9, 100, &mut rng);
46        }
47        assert!(t.value > 0.6, "trait should move toward optimum: {}", t.value);
48    }
49}