diff --git a/src/game/engine/scenes/tests/auto_play.gd b/src/game/engine/scenes/tests/auto_play.gd index 94d06bce..51474679 100644 --- a/src/game/engine/scenes/tests/auto_play.gd +++ b/src/game/engine/scenes/tests/auto_play.gd @@ -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, } diff --git a/src/game/engine/src/rendering/fauna_overlay_renderer.gd b/src/game/engine/src/rendering/fauna_overlay_renderer.gd index 689446b5..65e7fe26 100644 --- a/src/game/engine/src/rendering/fauna_overlay_renderer.gd +++ b/src/game/engine/src/rendering/fauna_overlay_renderer.gd @@ -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) diff --git a/src/simulator/crates/mc-ecology/src/emergence.rs b/src/simulator/crates/mc-ecology/src/emergence.rs index d096b983..bda40c4e 100644 --- a/src/simulator/crates/mc-ecology/src/emergence.rs +++ b/src/simulator/crates/mc-ecology/src/emergence.rs @@ -259,15 +259,22 @@ pub fn seed_base_trophic( seed: u64, ) -> Vec { 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