feat(@projects/@magic-civilization): 🦌 p3-19 (fauna) — over-hunting depletes live populations → local extinction
Wires the player→ecology coupling for fauna (the USP: the living world reacts to the player). GdFaunaEcology.deplete_species #[func] resolves the string species id → numeric via the species library and calls EcologyEngine::deplete_population. combat_utils._roll_wild_creature_loot now passes the slain creature's tile into item_system.roll_fauna_drops, which calls EcologyState.fauna_ecology.deplete_species on every fauna kill — so sustained hunting drives a species to local extinction (is_extinct), and the engine's growth/emergence recover it once pressure eases. Logic stays in mc_ecology (Rail 1); GDScript only triggers + passes the tile. Verified: mc-ecology cargo green; dylib rebuilt + deployed; canonical GUT 745/0 (new roll_fauna_drops signature + caller load cleanly, deplete_species callable). p3-19 stays partial — flora-harvest half (chop/intensive → flora population) next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eb2cf18c2d
commit
d6eaa79838
5 changed files with 43 additions and 6 deletions
|
|
@ -26,11 +26,11 @@ and abundance doesn't respond to player pressure — which undercuts the
|
|||
|
||||
## Progress (2026-06-25)
|
||||
|
||||
Rust core landed: `PopulationSlot::deplete(amount)` + `EcologyEngine::deplete_population(col,row,species_id,amount)` (floors at 0, drives local extinction; missing tile/species = safe 0.0 no-op). Tests green (mc-ecology). **Remaining (the coupling/wiring):** a `GdEcologyEngine` `#[func]` exposing deplete + the GDScript kill (item_system.gd / lair clear) and intensive-harvest paths calling it for the victim/flora species at the tile + dylib rebuild + GUT proof that abundance responds to player pressure.
|
||||
Rust core landed: `PopulationSlot::deplete(amount)` + `EcologyEngine::deplete_population(col,row,species_id,amount)` (floors at 0, drives local extinction; missing tile/species = safe 0.0 no-op). **Fauna half DONE + verified:** `GdFaunaEcology.deplete_species` #[func] (string→u32 via species library); `combat_utils._roll_wild_creature_loot` → `item_system.roll_fauna_drops(col,row)` → `EcologyState.fauna_ecology.deplete_species` on every fauna kill (over-hunting → local extinction). Dylib rebuilt + deployed; GUT 745/0 (loads + no regression). **Remaining (flora half):** wire chop/intensive-harvest (`mc-city::harvest` / harvest_policy) → deplete the tile's flora (Producer) population.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] Killing fauna (combat / lair clear) decrements that species' live
|
||||
- [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.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"generated_at": "2026-06-25T18:06:31Z",
|
||||
"generated_at": "2026-06-25T18:47:23Z",
|
||||
"totals": {
|
||||
"done": 292,
|
||||
"in_progress": 0,
|
||||
"missing": 1,
|
||||
"oos": 31,
|
||||
"partial": 4,
|
||||
"stub": 0,
|
||||
"done": 292,
|
||||
"missing": 1,
|
||||
"total": 328
|
||||
},
|
||||
"objectives": [
|
||||
|
|
|
|||
|
|
@ -182,7 +182,12 @@ static func _roll_wild_creature_loot(victim: RefCounted, killer: RefCounted) ->
|
|||
var turn_seed: int = GameState.game_rng.seed ^ GameState.turn_number
|
||||
var killer_id: int = hash(killer.id)
|
||||
var victim_id: int = hash(victim.id)
|
||||
ItemSystemScript.roll_fauna_drops(creature_type, killer_player, turn_seed, killer_id, victim_id)
|
||||
# p3-19: pass the slain creature's tile so its kill depletes the live ecology
|
||||
# population there (over-hunting → local extinction).
|
||||
var vpos: Vector2i = victim.position
|
||||
ItemSystemScript.roll_fauna_drops(
|
||||
creature_type, killer_player, turn_seed, killer_id, victim_id, vpos.x, vpos.y
|
||||
)
|
||||
|
||||
|
||||
## Destroy the High Archon of a player (capital capture penalty).
|
||||
|
|
|
|||
|
|
@ -145,9 +145,24 @@ static func roll_fauna_drops(
|
|||
turn_seed: int,
|
||||
killer_id: int,
|
||||
victim_id: int,
|
||||
col: int = -1,
|
||||
row: int = -1,
|
||||
deplete_amount: float = 1.0,
|
||||
) -> void:
|
||||
if killer_player == null:
|
||||
return
|
||||
# p3-19 player→ecology feedback: killing fauna reduces its LIVE population on
|
||||
# the tile (logic in mc_ecology via GdFaunaEcology.deplete_species), so
|
||||
# sustained hunting drives local extinction; the engine's growth/emergence
|
||||
# recover abundance once the pressure eases. Fires for any valid fauna kill
|
||||
# with a known tile, independent of whether loot drops.
|
||||
if col >= 0 and row >= 0:
|
||||
var tree: SceneTree = Engine.get_main_loop() as SceneTree
|
||||
var eco_state: Node = tree.root.get_node_or_null("/root/EcologyState") if tree else null
|
||||
if eco_state != null:
|
||||
var eco: RefCounted = eco_state.get("fauna_ecology") as RefCounted
|
||||
if eco != null and eco.has_method("deplete_species"):
|
||||
eco.deplete_species(col, row, victim_species_id, deplete_amount)
|
||||
var fauna: Dictionary = _lookup_fauna(victim_species_id)
|
||||
if fauna.is_empty():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -682,6 +682,23 @@ impl IRefCounted for GdFaunaEcology {
|
|||
|
||||
#[godot_api]
|
||||
impl GdFaunaEcology {
|
||||
/// p3-19 — apply player kill/harvest pressure to a tile's live population of
|
||||
/// `species_id` (string id, e.g. `"grey_wolf"`), reducing it by `amount`.
|
||||
/// Resolves the string id → numeric via the species library and returns the
|
||||
/// post-depletion population (`0.0` when the species/tile is absent). The
|
||||
/// GDScript fauna-kill + intensive-harvest handlers call this so over-harvest
|
||||
/// drives local extinction; the engine's growth/emergence recover it once
|
||||
/// pressure eases. Source of truth stays in `mc_ecology` (Rail 1).
|
||||
#[func]
|
||||
fn deplete_species(&mut self, col: i32, row: i32, species_id: GString, amount: f64) -> f64 {
|
||||
let sid = species_id.to_string();
|
||||
let Some(numeric) = self.inner.species_library.get(&sid).map(|s| s.id) else {
|
||||
return 0.0;
|
||||
};
|
||||
self.inner
|
||||
.deplete_population(col, row, numeric, amount as f32) as f64
|
||||
}
|
||||
|
||||
/// Compute fauna-derived luxury supply for a player.
|
||||
///
|
||||
/// `player_owned_tiles_json` — JSON array of `[col, row]` pairs covering
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue