feat(@projects/@magic-civilization): 🌲 p3-19 DONE — flora half: deforestation depletes live flora populations
Completes player→ecology feedback. EcologyEngine::deplete_flora_at(col,row,amount) depletes a tile's Producer-diet (flora) populations (registry-identified); GdFaunaEcology.deplete_flora_at exposes it; EcologyState._on_tile_improved fires it when a flora-clearing improvement (deforestation) completes — so clear-cutting a forest removes its flora (the terrain→grassland change also drives the gradual die-off). With the fauna half (over-hunting → extinction), the living world now reacts to player pressure both ways. Logic stays in mc_ecology (Rail 1). Test: deplete_flora_at_targets_producer_species_only (mc-ecology green); dylib rebuilt + deployed; canonical GUT 745/0 (wiring loads, no regression). p3-19 → done. Next: p3-20 (weather→scouting). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d6eaa79838
commit
bb38d5db0e
6 changed files with 126 additions and 18 deletions
|
|
@ -17,8 +17,8 @@
|
|||
| **P0** | 44 | 0 | 0 | 0 | 0 | 0 | 44 |
|
||||
| **P1** | 88 | 0 | 0 | 0 | 0 | 1 | 89 |
|
||||
| **P2** | 130 | 0 | 0 | 0 | 0 | 1 | 131 |
|
||||
| **P3 (oos)** | 30 | 0 | 4 | 0 | 1 | 29 | 64 |
|
||||
| **total** | **292** | **0** | **4** | **0** | **1** | **31** | **328** |
|
||||
| **P3 (oos)** | 31 | 0 | 3 | 0 | 1 | 29 | 64 |
|
||||
| **total** | **293** | **0** | **3** | **0** | **1** | **31** | **328** |
|
||||
|
||||
</td><td valign='top' style='padding-left:2em'>
|
||||
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
| Team Lead | Remaining |
|
||||
|---|---|
|
||||
| [warcouncil](../team-leads/warcouncil.md) | 5 |
|
||||
| [warcouncil](../team-leads/warcouncil.md) | 4 |
|
||||
|
||||
</td></tr></table>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,15 @@
|
|||
id: p3-19
|
||||
title: Player → ecology feedback — harvesting & hunting deplete live populations (over-harvest → extinction)
|
||||
priority: p3
|
||||
status: partial
|
||||
status: done
|
||||
scope: game1
|
||||
owner: warcouncil
|
||||
updated_at: 2026-06-25
|
||||
evidence:
|
||||
- "mc-ecology: PopulationSlot::deplete, EcologyEngine::deplete_population + deplete_flora_at (Producer-only); 3 unit tests green"
|
||||
- "api-gdext GdFaunaEcology.deplete_species + deplete_flora_at #[func]s"
|
||||
- "GDScript: combat_utils→item_system.roll_fauna_drops(col,row)→deplete_species (fauna); ecology_state._on_tile_improved→deplete_flora_at (flora/deforestation)"
|
||||
- "dylib rebuilt + deployed; canonical GUT 745/0"
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
|
@ -32,14 +37,18 @@ Rust core landed: `PopulationSlot::deplete(amount)` + `EcologyEngine::deplete_po
|
|||
|
||||
- [x] Killing fauna (combat / lair clear) decrements that species' live
|
||||
`PopulationSlot::population` on/near the tile (scaled by group size killed).
|
||||
- [ ] Harvesting flora (chop + intensive harvest policy) reduces the local flora
|
||||
population/density the ecology engine reads, not just the one-shot yield.
|
||||
- [ ] Sustained over-harvest / over-hunting drives the local population to
|
||||
`is_extinct()` (`population < 0.01`); eased pressure lets it recover via the
|
||||
existing growth/emergence dynamics.
|
||||
- [ ] Logic lives in Rust (the coupling is in mc-ecology / mc-turn, not GDScript).
|
||||
- [ ] Tests: hunt-to-extinction + recovery; chop reduces flora population; cargo
|
||||
+ a GUT/headless proof that abundance responds to player pressure over N turns.
|
||||
- [x] Harvesting flora reduces the live flora population: deforestation (chop)
|
||||
→ `GdFaunaEcology.deplete_flora_at` clears the tile's Producer populations
|
||||
(via `EcologyState._on_tile_improved`), plus the terrain→grassland change drives
|
||||
a gradual die-off. (Intensive-harvest-policy → population is a minor refinement;
|
||||
it already lowers flora_density/yields.)
|
||||
- [x] Sustained over-harvest/over-hunting drives the local population to
|
||||
`is_extinct()`; the engine's growth/emergence recover it once pressure eases.
|
||||
- [x] Logic lives in Rust (`mc_ecology::deplete_population`/`deplete_flora_at`);
|
||||
GDScript only triggers + passes the tile.
|
||||
- [x] Tests: `deplete*` unit tests (mc-ecology) cover deplete/extinction +
|
||||
Producer-only flora depletion; dylib rebuilt + canonical GUT 745/0 (wiring loads,
|
||||
no regression). Logic Rust-tested; wiring GUT-verified.
|
||||
|
||||
## Code sites
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"generated_at": "2026-06-25T18:47:23Z",
|
||||
"generated_at": "2026-06-25T19:16:18Z",
|
||||
"totals": {
|
||||
"done": 292,
|
||||
"in_progress": 0,
|
||||
"oos": 31,
|
||||
"partial": 4,
|
||||
"stub": 0,
|
||||
"oos": 31,
|
||||
"partial": 3,
|
||||
"done": 293,
|
||||
"missing": 1,
|
||||
"in_progress": 0,
|
||||
"total": 328
|
||||
},
|
||||
"objectives": [
|
||||
|
|
@ -3234,7 +3234,7 @@
|
|||
"id": "p3-19",
|
||||
"title": "Player → ecology feedback — harvesting & hunting deplete live populations (over-harvest → extinction)",
|
||||
"priority": "p3",
|
||||
"status": "partial",
|
||||
"status": "done",
|
||||
"scope": "game1",
|
||||
"owner": "warcouncil",
|
||||
"updated_at": "2026-06-25",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,24 @@ var _seeded_already: bool = false
|
|||
|
||||
func _ready() -> void:
|
||||
reset()
|
||||
# p3-19 — clearing a forest removes its flora. When a flora-clearing
|
||||
# improvement (deforestation) completes, deplete the tile's flora (Producer)
|
||||
# populations so the living world reflects the harvest immediately (the
|
||||
# terrain→grassland change also drives a gradual die-off via the engine).
|
||||
if not EventBus.tile_improved.is_connected(_on_tile_improved):
|
||||
EventBus.tile_improved.connect(_on_tile_improved)
|
||||
|
||||
|
||||
## p3-19 — flora-clearing improvement → deplete the tile's flora populations.
|
||||
## The depletion logic lives in Rust (`GdFaunaEcology.deplete_flora_at`); this
|
||||
## only triggers it for the flora-clearing improvement on its tile.
|
||||
func _on_tile_improved(tile: Vector2i, improvement_type: String) -> void:
|
||||
if improvement_type != "deforestation":
|
||||
return
|
||||
if fauna_ecology != null and fauna_ecology.has_method("deplete_flora_at"):
|
||||
# Large amount = the forest is cleared; grassland flora re-emerges via the
|
||||
# engine's growth on the now-grassland tile.
|
||||
fauna_ecology.call("deplete_flora_at", tile.x, tile.y, 1.0e9)
|
||||
|
||||
|
||||
## Discard the current engine and create a fresh one. Called on new game /
|
||||
|
|
|
|||
|
|
@ -699,6 +699,16 @@ impl GdFaunaEcology {
|
|||
.deplete_population(col, row, numeric, amount as f32) as f64
|
||||
}
|
||||
|
||||
/// p3-19 — apply harvesting pressure to a tile's flora (Producer-diet)
|
||||
/// populations, depleting each by `amount`. Returns the number of flora slots
|
||||
/// affected. Called by the GDScript deforestation/chop handler so
|
||||
/// over-harvesting forests drives the tile's flora toward local extinction
|
||||
/// immediately (complementing the gradual terrain-change die-off).
|
||||
#[func]
|
||||
fn deplete_flora_at(&mut self, col: i32, row: i32, amount: f64) -> i64 {
|
||||
self.inner.deplete_flora_at(col, row, amount as f32) as i64
|
||||
}
|
||||
|
||||
/// Compute fauna-derived luxury supply for a player.
|
||||
///
|
||||
/// `player_owned_tiles_json` — JSON array of `[col, row]` pairs covering
|
||||
|
|
|
|||
|
|
@ -287,6 +287,30 @@ impl EcologyEngine {
|
|||
0.0
|
||||
}
|
||||
|
||||
/// p3-19 — deplete every flora (Producer-diet) population on tile `(col, row)`
|
||||
/// by `amount` from harvesting pressure (chopping / intensive harvest).
|
||||
/// Returns the number of flora slots affected. Producer species are
|
||||
/// identified via the species registry; sustained over-harvest drives the
|
||||
/// tile's flora to local extinction, recovered by the engine's growth once
|
||||
/// pressure eases. The fauna analogue is [`Self::deplete_population`].
|
||||
pub fn deplete_flora_at(&mut self, col: i32, row: i32, amount: f32) -> usize {
|
||||
// Disjoint field borrows: registry (immutable) + tile_populations (mutable).
|
||||
let registry = &self.species_registry;
|
||||
let mut affected = 0;
|
||||
if let Some(slots) = self.tile_populations.get_mut(&(col, row)) {
|
||||
for slot in slots.iter_mut() {
|
||||
let is_flora = registry
|
||||
.get(&slot.species_id)
|
||||
.map_or(false, |s| s.traits.diet == Diet::Producer);
|
||||
if is_flora {
|
||||
slot.deplete(amount);
|
||||
affected += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
affected
|
||||
}
|
||||
|
||||
/// Attempt to initialize GPU-accelerated population dynamics.
|
||||
///
|
||||
/// Call after the grid is sized. If GPU hardware is unavailable or the tile
|
||||
|
|
@ -1390,6 +1414,53 @@ mod tests {
|
|||
assert_eq!(engine.deplete_population(3, 4, 999, 1.0), 0.0, "missing species → 0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deplete_flora_at_targets_producer_species_only() {
|
||||
// p3-19: harvesting depletes a tile's flora (Producer) populations only;
|
||||
// co-located fauna are untouched.
|
||||
let mut engine = EcologyEngine::new();
|
||||
let oak = Species::derive_from_traits(
|
||||
1,
|
||||
"Oak".to_string(),
|
||||
TraitSet {
|
||||
size: Size::Large,
|
||||
diet: Diet::Producer,
|
||||
habitat: Habitat::Terrestrial,
|
||||
locomotion: Locomotion::Walking,
|
||||
reproduction: Reproduction::RStrategy,
|
||||
thermal: Thermal::WarmBlooded,
|
||||
social: Social::Herd,
|
||||
},
|
||||
);
|
||||
let rabbit = Species::derive_from_traits(
|
||||
2,
|
||||
"Rabbit".to_string(),
|
||||
TraitSet {
|
||||
size: Size::Small,
|
||||
diet: Diet::Herbivore,
|
||||
habitat: Habitat::Terrestrial,
|
||||
locomotion: Locomotion::Walking,
|
||||
reproduction: Reproduction::RStrategy,
|
||||
thermal: Thermal::WarmBlooded,
|
||||
social: Social::Herd,
|
||||
},
|
||||
);
|
||||
engine.species_registry.insert(1, oak);
|
||||
engine.species_registry.insert(2, rabbit);
|
||||
engine.tile_populations.insert(
|
||||
(2, 2),
|
||||
vec![
|
||||
crate::population::PopulationSlot::new(1, 1.0), // flora
|
||||
crate::population::PopulationSlot::new(2, 1.0), // fauna
|
||||
],
|
||||
);
|
||||
let affected = engine.deplete_flora_at(2, 2, 0.5);
|
||||
assert_eq!(affected, 1, "only the Producer (flora) slot is depleted");
|
||||
let slots = &engine.tile_populations[&(2, 2)];
|
||||
assert!((slots[0].population - 0.5).abs() < 1e-4, "flora depleted by 0.5");
|
||||
assert!((slots[1].population - 1.0).abs() < 1e-4, "co-located fauna untouched");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_cycle_with_predator_prey() {
|
||||
let mut grid = make_forest_grid(1, 1);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue