From 5d12b4bbee33326edf255b5c09e8b2425cb90c6f Mon Sep 17 00:00:00 2001 From: Natalie Date: Fri, 26 Jun 2026 19:42:08 -0400 Subject: [PATCH] =?UTF-8?q?feat(@projects/@magic-civilization):=20?= =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20p3-26=20B3=20(3/4)=20=E2=80=94=20FFI=20?= =?UTF-8?q?+=20harness=20boot=20the=20improvement=20defs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runtime wiring so improvements actually build + yield in a real headless game: - GdPlayerApi::set_improvement_defs_json — loads the improvement defs (JSON array of {id, build_turns, yields}) onto GameState. Mirrors set_ecology_species_json. - player_api_main._apply_improvement_defs — stamps DataLoader.get_all_improvements() via the FFI at boot (after load_state_json), emitting improvement_defs_api_loaded. With this, the full B3 chain is live: BuildImprovement → pending (build_turns) → build-tick completes → city_improvements → process_improvement_yields folds food/production in. gdext compiles; dylib rebuild in progress. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../engine/scenes/headless/player_api_main.gd | 18 ++++++++++++++++++ src/simulator/api-gdext/src/player_api.rs | 12 ++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/game/engine/scenes/headless/player_api_main.gd b/src/game/engine/scenes/headless/player_api_main.gd index 13d297e6..bf94cd2f 100644 --- a/src/game/engine/scenes/headless/player_api_main.gd +++ b/src/game/engine/scenes/headless/player_api_main.gd @@ -239,6 +239,24 @@ func _hydrate_player_api(num_players: int) -> void: # AFTER `load_state_json`. Without this the ecology phase is a no-op (barren world). _apply_ecology_species() + # p3-26 B3: stamp improvement definitions so the headless turn can build (build-tick) + # and yield (process_improvement_yields) tile improvements. Same `#[serde(skip)]` + # re-stamp pattern — load AFTER `load_state_json`. Without this improvements never + # build or yield in headless play. + _apply_improvement_defs() + + +## p3-26 B3: stamp improvement definitions (DataLoader's improvements: id, build_turns, +## yields:{food,production}) onto `GdPlayerApi` via `set_improvement_defs_json`. Consumed by +## the headless improvement build-tick + `process_improvement_yields`. +func _apply_improvement_defs() -> void: + var defs: Array = DataLoader.get_all_improvements() + if defs.is_empty(): + _emit_protocol_error("improvement defs empty — headless improvements will not build/yield") + return + var n: int = int(_api.set_improvement_defs_json(JSON.stringify(defs))) + _emit_event("improvement_defs_api_loaded", {"count": n}) + ## p3-26 gap 2: stamp the natural-event category configs (DataLoader's merged ## `{category: {base_frequency, severity_weights, tiers, …}}`) onto `GdPlayerApi` via diff --git a/src/simulator/api-gdext/src/player_api.rs b/src/simulator/api-gdext/src/player_api.rs index 73a06e0b..3247f7c6 100644 --- a/src/simulator/api-gdext/src/player_api.rs +++ b/src/simulator/api-gdext/src/player_api.rs @@ -179,6 +179,18 @@ impl GdPlayerApi { .load_ecology_species_json(json.to_string().as_str()) as i64 } + /// p3-26 B3: load improvement definitions (a JSON array of improvement + /// objects `[{id, build_turns, yields:{food,production}}, …]` from + /// `public/resources/improvements/*.json`) so the headless turn can build + /// (build-tick) and yield (process_improvement_yields) tile improvements. + /// Returns the count loaded, or 0 on parse failure. Call AFTER + /// `load_state_json` (the field is `#[serde(skip)]`). + #[func] + pub fn set_improvement_defs_json(&mut self, json: GString) -> i64 { + self.state + .load_improvement_defs_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`