feat(@projects/@magic-civilization): 🔥 p3-26 gap 2 — wildfire effect (apply_wildfire) ported
The first per-category event EFFECT, pure + testable. apply_wildfire burns forest tiles in a hex disk around a center (offset coords via offset_to_axial/hex_spiral/axial_to_offset): transforms matching terrain to `becomes`, drops quality (min 1) + moisture (min 0); returns tiles burned. Mirrors GDScript process_wildfire's per-tile effect. Test: a forest patch + apply_wildfire(radius 2, becomes grassland, quality_loss 2) → forest → grassland, moisture/quality dropped, non-forest tiles untouched. mc-climate events 6/6. Next: the dispatch (process_events — category_fires gate + roll_severity + deterministic center pick → apply_wildfire) + config-on-GameState + wire into the mc-turn climate phase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4e82b322cb
commit
fa93e59425
1 changed files with 62 additions and 0 deletions
|
|
@ -111,6 +111,40 @@ pub fn load_event_configs(
|
|||
out
|
||||
}
|
||||
|
||||
/// Burn the forest tiles within `radius` (hex disk) of `center` (offset col,row):
|
||||
/// transform matching terrain to `becomes`, drop quality (min 1) + moisture (min 0).
|
||||
/// Returns the count burned. Pure — the dispatch chooses the center. Mirrors the per-tile
|
||||
/// effect of GDScript `process_wildfire` (forest-only, radius/loss/`becomes` by tier).
|
||||
pub fn apply_wildfire(
|
||||
grid: &mut mc_core::grid::GridState,
|
||||
center: (i32, i32),
|
||||
radius: i32,
|
||||
moisture_loss: f32,
|
||||
quality_loss: i32,
|
||||
becomes: Option<&str>,
|
||||
target_terrain: &[String],
|
||||
) -> i32 {
|
||||
use mc_core::algorithms::hex::{axial_to_offset, hex_spiral, offset_to_axial};
|
||||
let (cq, cr) = offset_to_axial(center.0, center.1);
|
||||
let mut burned = 0;
|
||||
for (q, r) in hex_spiral(cq, cr, radius) {
|
||||
let (col, row) = axial_to_offset(q, r);
|
||||
if let Some(t) = grid.tile_mut(col, row) {
|
||||
if target_terrain.iter().any(|b| b.as_str() == t.biome_label_id) {
|
||||
if let Some(b) = becomes {
|
||||
t.biome_label_id = b.to_string();
|
||||
}
|
||||
if quality_loss > 0 {
|
||||
t.quality = (t.quality - quality_loss).max(1);
|
||||
}
|
||||
t.moisture = (t.moisture - moisture_loss).max(0.0);
|
||||
burned += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
burned
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -159,6 +193,34 @@ mod tests {
|
|||
assert!(!category_fires(0.0, 10.0, 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_wildfire_burns_forest_in_radius() {
|
||||
use mc_core::grid::GridState;
|
||||
let mut grid = GridState::new(12, 12);
|
||||
for t in &mut grid.tiles {
|
||||
t.biome_label_id = "grassland".into();
|
||||
t.moisture = 0.5;
|
||||
t.quality = 3;
|
||||
}
|
||||
for c in 4..7 {
|
||||
for r in 4..7 {
|
||||
if let Some(t) = grid.tile_mut(c, r) {
|
||||
t.biome_label_id = "forest".into();
|
||||
}
|
||||
}
|
||||
}
|
||||
let target = vec!["forest".to_string()];
|
||||
let burned = apply_wildfire(&mut grid, (5, 5), 2, 0.15, 2, Some("grassland"), &target);
|
||||
assert!(burned >= 1, "should burn forest tiles in radius");
|
||||
let center = grid.tile(5, 5).unwrap();
|
||||
assert_eq!(center.biome_label_id, "grassland", "forest → grassland");
|
||||
assert!(center.moisture < 0.5, "moisture dropped");
|
||||
assert_eq!(center.quality, 1, "quality 3 - 2 = 1 (floored at 1)");
|
||||
// A pre-existing grassland tile (not target terrain) is untouched.
|
||||
let far = grid.tile(0, 0).unwrap();
|
||||
assert_eq!(far.moisture, 0.5, "non-forest tile untouched");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_event_configs_parses_real_categories() {
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue