diff --git a/src/game/engine/scenes/headless/player_api_main.gd b/src/game/engine/scenes/headless/player_api_main.gd index a95e726b..13d297e6 100644 --- a/src/game/engine/scenes/headless/player_api_main.gd +++ b/src/game/engine/scenes/headless/player_api_main.gd @@ -234,6 +234,11 @@ func _hydrate_player_api(num_players: int) -> void: # `load_state_json`. Without this `events_config` is empty and no events fire. _apply_events_config() + # p3-27 biosphere: stamp the fauna species library so the headless ecology phase + # can seed + emerge a living world. Same `#[serde(skip)]` re-stamp pattern — load + # AFTER `load_state_json`. Without this the ecology phase is a no-op (barren world). + _apply_ecology_species() + ## p3-26 gap 2: stamp the natural-event category configs (DataLoader's merged ## `{category: {base_frequency, severity_weights, tiers, …}}`) onto `GdPlayerApi` via @@ -247,6 +252,33 @@ func _apply_events_config() -> void: _emit_event("events_config_api_loaded", {"categories": n}) +## p3-27 biosphere: read every fauna species JSON file and stamp the array onto +## `GdPlayerApi` via `set_ecology_species_json`. Consumed by `mc-player-api`'s +## `process_ecology_phase` (apply_end_turn) to build the EcologyEngine species +## library. Mirrors the live `EcologyState` species load. +func _apply_ecology_species() -> void: + const SPECIES_DIR: String = "res://public/resources/ecology/fauna/species" + var dir: DirAccess = DirAccess.open(SPECIES_DIR) + if dir == null: + _emit_protocol_error("ecology species dir not found — headless ecology will not tick") + return + var species_jsons: Array = [] + dir.list_dir_begin() + var name: String = dir.get_next() + while name != "": + if not dir.current_is_dir() and name.ends_with(".json"): + var raw: String = FileAccess.get_file_as_string("%s/%s" % [SPECIES_DIR, name]) + if raw != "": + species_jsons.append(raw) + name = dir.get_next() + dir.list_dir_end() + if species_jsons.is_empty(): + _emit_protocol_error("ecology species dir empty — headless ecology will not tick") + return + var n: int = int(_api.set_ecology_species_json(JSON.stringify(species_jsons))) + _emit_event("ecology_species_api_loaded", {"species": n}) + + ## p3-25: build the resource id→category map ("luxury" | "strategic" | "bonus") ## from DataLoader and stamp it onto `GdPlayerApi` via ## `set_resource_categories_json`. Consumed by `mc-turn::process_trade_phase`. diff --git a/src/simulator/api-gdext/src/player_api.rs b/src/simulator/api-gdext/src/player_api.rs index c3a844d7..73a06e0b 100644 --- a/src/simulator/api-gdext/src/player_api.rs +++ b/src/simulator/api-gdext/src/player_api.rs @@ -168,6 +168,17 @@ impl GdPlayerApi { .load_events_config_json(json.to_string().as_str()) as i64 } + /// p3-27: load the fauna species library (a JSON array of per-species file + /// contents, e.g. `["{…grey_wolf…}","{…rabbit…}"]`) so the headless ecology + /// phase can build the `EcologyEngine` species library and emerge a living + /// biosphere. Returns the count loaded, or 0 on parse failure. Call AFTER + /// `load_state_json` (the field is `#[serde(skip)]`, not restored by a load). + #[func] + pub fn set_ecology_species_json(&mut self, json: GString) -> i64 { + self.state + .load_ecology_species_json(json.to_string().as_str()) as i64 + } + /// Stamp the runtime `UnitsCatalog` (id → `UnitStats`) onto the held /// `GameState`. Distinct from `set_units_catalog_json` (which loads the /// tactical `ai_unit_catalog`): this is the same `mc_units::UnitsCatalog`