feat(@projects/@magic-civilization): add fauna tile count tracking

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-06-07 21:04:21 -07:00
parent 4bf1d054a9
commit c297a7fc13
3 changed files with 37 additions and 5 deletions

View file

@ -2721,13 +2721,18 @@ func _snapshot_ecology() -> Dictionary:
## mean + delta from Climate.get_canopy_summary, which wraps the Rust
## GdEcologyPhysics bridge. Returns zeros before TurnManager has produced
## a climate tick so the first seeded line still schema-validates.
# p2-80: fauna populated-tile count from the live ecology engine — the count
# the fauna overlay renders. >0 on a real worldgen game proves world-seeding
# bootstrapped the living world end-to-end.
var fauna_tiles: int = EcologyState.tile_densities().size()
var climate: RefCounted = TurnManager.climate as RefCounted
if climate == null or not climate.has_method("get_canopy_summary"):
return {"flora_canopy_mean": 0.0, "flora_canopy_delta": 0.0}
return {"flora_canopy_mean": 0.0, "flora_canopy_delta": 0.0, "fauna_tiles": fauna_tiles}
var summary: Dictionary = climate.get_canopy_summary() as Dictionary
return {
"flora_canopy_mean": float(summary.get("mean", 0.0)),
"flora_canopy_delta": float(summary.get("delta_since_last_turn", 0.0)),
"fauna_tiles": fauna_tiles,
}

View file

@ -65,10 +65,23 @@ func clear() -> void:
func _draw() -> void:
if not visible or _densities.is_empty():
return
# Fog gate: fauna is only drawn on tiles the viewing player has explored, so
# the lens never leaks creature positions on unseen terrain. Mirrors the fog
# treatment of hex_renderer's flora layer. FORCE_DISABLE_FOGOFWAR (autoplay /
# proof scenes) shows everything.
var fog_off: bool = EnvConfig.get_bool("FORCE_DISABLE_FOGOFWAR")
var game_map: RefCounted = GameState.get_game_map()
var view_idx: int = _view_player_index()
for pos: Vector2i in _densities:
var density: float = float(_densities[pos])
if density <= 0.0:
continue
if not fog_off and game_map != null:
var tile: RefCounted = game_map.get_tile(pos)
# Unexplored (visibility 0) tiles stay dark — no fauna leak. Explored
# tiles (visible or fogged) show the density; the player has been there.
if tile == null or int(tile.get_visibility(view_idx)) < 1:
continue
var t: float = clampf(density / _peak, 0.0, 1.0)
# Lift off the floor so the dimmest populated tile is still legible.
var fill_t: float = MIN_FILL_T + (1.0 - MIN_FILL_T) * t
@ -76,6 +89,13 @@ func _draw() -> void:
draw_colored_polygon(_hex_at(pos), color)
## The player whose vision gates the overlay — the local/current player viewing
## the map. Falls back to slot 0 if no current player is resolvable.
func _view_player_index() -> int:
var p: RefCounted = GameState.get_current_player()
return int(p.index) if p != null else 0
## Translate the shared flat-top hex polygon to the given tile's pixel origin.
func _hex_at(pos: Vector2i) -> PackedVector2Array:
var origin: Vector2 = HexUtilsScript.axial_to_pixel(pos)

View file

@ -259,15 +259,22 @@ pub fn seed_base_trophic(
seed: u64,
) -> Vec<PopulationSlot> {
let mut slots = Vec::new();
if tile.habitat_suitability < config.habitat_abandon_threshold {
return slots; // too hostile to bootstrap any starter population
// Gate on terrain QUALITY, not `habitat_suitability`: the latter is
// flora-derived (`canopy·0.4 + undergrowth·0.3 + fungi·0.3`, see
// mc_climate::ecology::tick_habitat) and is ≈0 at genesis before any flora
// has grown — gating seeding on it would reject the entire map at world
// start (the chicken-and-egg that left the live world barren). `quality` is
// a stable worldgen property (the same habitability proxy the original
// EcologyInitializer used); quality 0 marks terrain too hostile for life.
if tile.quality <= 0 {
return slots;
}
let col = tile.col;
let row = tile.row;
let biome = tile.biome_label_id.as_str();
// Starter cohort: comfortably above min_viable so the first dynamics tick
// doesn't immediately extinguish it; scaled by habitat quality.
let starter = (8.0 + 10.0 * tile.habitat_suitability).max(config.min_viable_population);
// doesn't immediately extinguish it; scaled by terrain quality.
let starter = (6.0 + 2.0 * tile.quality as f32).max(config.min_viable_population);
// Pick a biome-appropriate species from the library; fall back to a
// procedurally-generated one (library picks are filtered on flora structure