feat(@projects/@magic-civilization): add per-tile fauna density updates

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-06-07 19:58:36 -07:00
parent d0a74e5122
commit 27ade4cd43
4 changed files with 40 additions and 0 deletions

View file

@ -46,6 +46,17 @@ func tick(grid: RefCounted, seed: int) -> void:
fauna_ecology.call("tick_populations", grid, seed)
## Per-tile total fauna population for the live-map fauna overlay
## (`fauna_overlay_renderer.gd`). One bulk bridge read per refresh: a Dictionary
## keyed by `Vector2i(col, row)` → total population (float). Empty when no
## engine or no populations yet (emergence has not fired). The overlay reads
## this on each `EventBus.worldsim_updated` and normalizes for its heatmap.
func tile_densities() -> Dictionary:
if fauna_ecology == null:
return {}
return fauna_ecology.call("populated_tile_densities")
func _ensure_species_registered() -> void:
## Idempotent: a non-empty registry short-circuits via the engine's
## `population_slot_count` proxy on subsequent ticks (slots only appear

View file

@ -272,6 +272,11 @@ signal creature_died(pos: Vector2i, species_name: String, quality: int)
signal creature_born(pos: Vector2i, species_name: String)
## Tile crossed Q4 threshold — natural wonder emerged from ecology.
signal landmark_formed(pos: Vector2i, name: String, quality: int)
## Emitted once per full game turn after the continuous worldsim has advanced
## (climate physics + fauna `EcologyState.tick` + `WorldsimState.dispatch`).
## The fauna overlay subscribes to refresh its per-tile density heatmap so the
## evolving living world is visible on the playable map (p2-80 render hook).
signal worldsim_updated(turn: int)
# -- Natural event signals --
signal natural_event_spawned(event_type: String, position: Vector2i, intensity: float)

View file

@ -301,6 +301,10 @@ func next_player() -> void:
WorldsimState.dispatch(
fauna_grid, GameState.turn_number, GameState.map_seed
)
# p2-80 render hook: signal the fauna overlay to refresh its
# per-tile density heatmap so the evolving living world is
# visible on the playable map, not just simulated.
EventBus.worldsim_updated.emit(GameState.turn_number)
GameState.current_player_index = 0
GameState.turn_number += 1
# Check victory conditions after all players have moved

View file

@ -851,6 +851,26 @@ impl GdFaunaEcology {
fn populated_tile_count(&self) -> i64 {
self.inner.tile_populations.len() as i64
}
/// Per-tile total fauna population, as a `Dictionary` keyed by
/// `Vector2i(col, row)` → total population (`f64`, summed across all species
/// slots on the tile). One bulk read so the live-map fauna overlay
/// (`fauna_overlay_renderer.gd`) refreshes per turn with a single bridge
/// call instead of `populated_tile_count`×`populations_on_tile`. Tiles with
/// zero population are omitted (the map is sparse, mirroring the engine's
/// own `tile_populations`). Deterministic order is irrelevant here — the
/// renderer keys by position, not iteration order.
#[func]
fn populated_tile_densities(&self) -> Dictionary {
let mut out = Dictionary::new();
for (&(col, row), slots) in &self.inner.tile_populations {
let total: f32 = slots.iter().map(|s| s.population).sum();
if total > 0.0 {
out.set(Vector2i::new(col, row), f64::from(total));
}
}
out
}
}
// ── GdAtmosphericChemistry ──────────────────────────────────────────────