use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum FireStrategy {
Sensitive,
Resprouter,
ThickBarked,
Serotinous,
}
#[must_use]
#[inline]
pub fn bark_protection(strategy: FireStrategy) -> f32 {
let protection = match strategy {
FireStrategy::Sensitive => 0.1, FireStrategy::Resprouter => 0.3, FireStrategy::ThickBarked => 0.8, FireStrategy::Serotinous => 0.2, };
tracing::trace!(?strategy, protection, "bark_protection");
protection
}
#[must_use]
pub fn resprout_vigor(strategy: FireStrategy, fire_intensity: f32) -> f32 {
let intensity = fire_intensity.clamp(0.0, 1.0);
let base_vigor = match strategy {
FireStrategy::Sensitive => 0.0, FireStrategy::Resprouter => 0.9, FireStrategy::ThickBarked => 0.4, FireStrategy::Serotinous => 0.1, };
let vigor = base_vigor * (1.0 - 0.3 * intensity);
tracing::trace!(
?strategy,
fire_intensity,
base_vigor,
vigor,
"resprout_vigor"
);
vigor.max(0.0)
}
#[must_use]
pub fn serotinous_release(strategy: FireStrategy, seed_bank: f32, fire_intensity: f32) -> f32 {
if strategy != FireStrategy::Serotinous || seed_bank <= 0.0 {
return 0.0;
}
let intensity = fire_intensity.clamp(0.0, 1.0);
let released = seed_bank * (0.5 + 0.5 * intensity);
tracing::trace!(seed_bank, fire_intensity, released, "serotinous_release");
released
}
#[must_use]
pub fn post_fire_establishment(strategy: FireStrategy, fire_intensity: f32) -> f32 {
let intensity = fire_intensity.clamp(0.0, 1.0);
let base = match strategy {
FireStrategy::Sensitive => 0.5, FireStrategy::Resprouter => 1.5, FireStrategy::ThickBarked => 1.2, FireStrategy::Serotinous => 2.0, };
let advantage = base * (1.0 + 0.5 * intensity);
tracing::trace!(
?strategy,
fire_intensity,
base,
advantage,
"post_fire_establishment"
);
advantage
}
#[must_use]
#[inline]
pub fn fire_return_interval_years(is_fire_prone: bool) -> f32 {
if is_fire_prone {
15.0 } else {
200.0 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn thick_bark_most_protected() {
assert!(
bark_protection(FireStrategy::ThickBarked) > bark_protection(FireStrategy::Sensitive)
);
assert!(
bark_protection(FireStrategy::ThickBarked) > bark_protection(FireStrategy::Resprouter)
);
assert!(
bark_protection(FireStrategy::ThickBarked) > bark_protection(FireStrategy::Serotinous)
);
}
#[test]
fn protection_in_range() {
for s in [
FireStrategy::Sensitive,
FireStrategy::Resprouter,
FireStrategy::ThickBarked,
FireStrategy::Serotinous,
] {
let p = bark_protection(s);
assert!((0.0..=1.0).contains(&p), "{s:?}: {p}");
}
}
#[test]
fn sensitive_cannot_resprout() {
assert_eq!(resprout_vigor(FireStrategy::Sensitive, 0.5), 0.0);
}
#[test]
fn resprouter_high_vigor() {
let v = resprout_vigor(FireStrategy::Resprouter, 0.3);
assert!(v > 0.5, "resprouter should have high vigor, got {v}");
}
#[test]
fn intense_fire_reduces_vigor() {
let low = resprout_vigor(FireStrategy::Resprouter, 0.2);
let high = resprout_vigor(FireStrategy::Resprouter, 0.9);
assert!(low > high, "intense fire should reduce resprout vigor");
}
#[test]
fn vigor_never_negative() {
let v = resprout_vigor(FireStrategy::Serotinous, 1.0);
assert!(v >= 0.0);
}
#[test]
fn only_serotinous_releases() {
assert_eq!(
serotinous_release(FireStrategy::Resprouter, 1000.0, 1.0),
0.0
);
assert_eq!(
serotinous_release(FireStrategy::ThickBarked, 1000.0, 1.0),
0.0
);
}
#[test]
fn serotinous_releases_seeds() {
let released = serotinous_release(FireStrategy::Serotinous, 1000.0, 0.8);
assert!(released > 500.0, "should release majority of seed bank");
}
#[test]
fn hotter_fire_more_seeds() {
let cool = serotinous_release(FireStrategy::Serotinous, 1000.0, 0.2);
let hot = serotinous_release(FireStrategy::Serotinous, 1000.0, 0.9);
assert!(hot > cool);
}
#[test]
fn serotinous_zero_bank() {
assert_eq!(serotinous_release(FireStrategy::Serotinous, 0.0, 1.0), 0.0);
}
#[test]
fn serotinous_best_post_fire() {
let sero = post_fire_establishment(FireStrategy::Serotinous, 0.5);
let sens = post_fire_establishment(FireStrategy::Sensitive, 0.5);
assert!(sero > sens);
}
#[test]
fn establishment_increases_with_intensity() {
let low = post_fire_establishment(FireStrategy::Resprouter, 0.2);
let high = post_fire_establishment(FireStrategy::Resprouter, 0.8);
assert!(high > low);
}
#[test]
fn all_establishments_positive() {
for s in [
FireStrategy::Sensitive,
FireStrategy::Resprouter,
FireStrategy::ThickBarked,
FireStrategy::Serotinous,
] {
assert!(post_fire_establishment(s, 0.5) > 0.0, "{s:?}");
}
}
#[test]
fn fire_prone_shorter_interval() {
assert!(fire_return_interval_years(true) < fire_return_interval_years(false));
}
#[test]
fn intervals_positive() {
assert!(fire_return_interval_years(true) > 0.0);
assert!(fire_return_interval_years(false) > 0.0);
}
}