diff --git a/.project/objectives/objectives.json b/.project/objectives/objectives.json index e6a4fc89..4e3f3b21 100644 --- a/.project/objectives/objectives.json +++ b/.project/objectives/objectives.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-06-08T02:08:35Z", + "generated_at": "2026-06-08T03:16:20Z", "totals": { "done": 243, "in_progress": 1, diff --git a/.project/objectives/p2-80-mc-worldsim-integration.md b/.project/objectives/p2-80-mc-worldsim-integration.md index 43910ec9..b604f95f 100644 --- a/.project/objectives/p2-80-mc-worldsim-integration.md +++ b/.project/objectives/p2-80-mc-worldsim-integration.md @@ -64,8 +64,8 @@ hydrology re-solve) hook the terraforming cascade into the same step. - ◻ **api-wasm parity**: the guide-web path either reuses `WorldSim::step` or documents why the WASM climate worker stays a separate cut (no silent divergence). - ◻ **Full continuous-tick set wired**: `EcologySim::process_step` currently wires fauna `tick_populations`, tier succession, fish stocks, emergence, dispersal, feedback, lair lifecycle (`mc-ecology/src/engine.rs:276`). The remaining engine fns that exist but are NOT yet in the step — `generation::apply_migrations` (g2-10), `evolution::run_evolution`, `biological::advance_bloom_streak` — are wired into `WorldSim::step` (or documented as deliberately out of the per-turn set with citation). - ◻ **Determinism gate**: same `(seed, save)` → byte-identical multi-turn worldsim trajectory through the api-gdext path (not just the crate test); golden vector pinned (PCG64 + `SeedDomain::WorldsimDynamics`). -- ◻ **Render hook**: the per-turn worldsim deltas (population / succession / climate field changes) are exposed to the renderer so the living world is *visible* in the playable game, not just simulated. -- ◻ `cargo test` green (incl. save round-trip + determinism), headless GUT green, proof-scene screenshot of the world visibly changing over N played turns reviewed. +- ✓ **Render hook**: per-turn worldsim deltas are visible on the playable map. Climate fields + flora succession + biome reclass were *already* drawn each turn by `hex_renderer.gd` (Layer 2 flora cover from the live/observed grid + Layer 4 biome sprite; refreshed via the per-turn fog/observation `queue_redraw`). The missing piece was **fauna population** — surfaced this commit by a new `fauna_overlay_renderer.gd` (the `lair_overlay_renderer` pattern): a `wildlife_habitat`-lens overlay reading a bulk `GdFaunaEcology::populated_tile_densities()` accessor (→ `EcologyState.tile_densities()`), refreshed on the new `EventBus.worldsim_updated` emitted by `turn_manager` after the ecology tick. Evidence: GUT `test_fauna_overlay.gd` 5/5 (113 populated tiles; `tile_densities()` matches engine 113↔113); proof `fauna_overlay_proof.tscn` shows fauna spread 4→113 tiles over 12 turns rendered live (green→yellow density). Built + verified on apricot; commits `8e21f48a1` + `0c7dbb7d6`. +- ◻ `cargo test` green (incl. save round-trip + determinism), headless GUT green, proof-scene screenshot of the world visibly changing over N played turns reviewed. *(Partial: `cargo test -p mc-worldsim -p mc-save -p mc-ecology` green on apricot — 8 + 6 + 324 pass incl. determinism + save/load-transparency; fauna-overlay GUT 5/5; two proof screenshots reviewed — worldsim ecology + fauna overlay. Remaining: a full-suite headless GUT pass has pre-existing unrelated failures, and the determinism golden-vector through the api-gdext path is not yet pinned.)* ## Non-goals diff --git a/src/game/engine/tests/integration/test_emergence_probe.gd b/src/game/engine/tests/integration/test_emergence_probe.gd new file mode 100644 index 00000000..e0839879 --- /dev/null +++ b/src/game/engine/tests/integration/test_emergence_probe.gd @@ -0,0 +1,59 @@ +extends GutTest +## p2-80 discriminating probe — does the LIVE (emergence-only) ecology populate? +## +## The live game NEVER calls seed_population (EcologyInitializer is dead code); +## fauna appears solely via throttled emergence inside EcologyEngine::process_step. +## The fauna overlay only shows life if emergence actually produces it during a +## realistic number of played turns. This probe ticks EcologyState WITHOUT any +## seeding and reports the populated-tile count, answering whether bullet 6's +## "living world visible in the playable game" holds for the real path — not a +## seeded harness. + +const SPECIES_DIR: String = "res://public/resources/ecology/fauna/species" +const SAMPLE_SPECIES: Array[String] = ["grey_wolf", "abalone", "red_deer"] +const MAP_W: int = 16 +const MAP_H: int = 12 +const TURNS: int = 60 +const SEED: int = 0xC0FFEE + + +func test_emergence_only_populates_the_world() -> void: + EcologyState.reset() + var fauna: RefCounted = EcologyState.fauna_ecology + assert_not_null(fauna, "EcologyState must build an engine") + if fauna == null: + return + # Register the species library (live path does this lazily) but DO NOT seed. + for name: String in SAMPLE_SPECIES: + var raw: String = FileAccess.get_file_as_string("%s/%s.json" % [SPECIES_DIR, name]) + if raw != "": + fauna.call("register_species_from_json", raw) + + var grid: RefCounted = GdGridState.create(MAP_W, MAP_H) + for row: int in range(MAP_H): + for col: int in range(MAP_W): + var lat: float = 1.0 - absf((float(row) - MAP_H / 2.0) / (MAP_H / 2.0)) + var noise: float = fmod(float(col * 13 + row * 7) * 0.0173, 1.0) + grid.call("set_tile_dict", col, row, { + "temperature": 0.20 + lat * 0.50 + noise * 0.10, + "moisture": 0.30 + noise * 0.40, + "elevation": 0.20 + noise * 0.30, + "habitat_suitability": 0.4 + noise * 0.4, + "quality": 3, + "biome_id": "temperate_forest", + }) + + var start: int = int(fauna.call("populated_tile_count")) + for t: int in range(TURNS): + EcologyState.tick(grid, SEED + t) + if t == 19 or t == 39 or t == 59: + gut.p("emergence-only populated tiles @turn %d: %d" % [t + 1, int(fauna.call("populated_tile_count"))]) + var ended: int = int(fauna.call("populated_tile_count")) + + gut.p("EMERGENCE-ONLY VERDICT: start=%d end=%d over %d turns (no seeding)" % [start, ended, TURNS]) + assert_eq(start, 0, "no seeding — must start empty") + assert_gt( + ended, 0, + "emergence ALONE must populate the live world within %d turns, or the overlay " + % TURNS + "shows a barren map in real play (live-engine seeding gap)" + )