From 3f7a4e54423115c458e9596d192e3d111d78be89 Mon Sep 17 00:00:00 2001 From: Natalie Date: Fri, 26 Jun 2026 02:32:42 -0400 Subject: [PATCH] =?UTF-8?q?feat(@projects/@magic-civilization):=20?= =?UTF-8?q?=F0=9F=94=8C=20p3-25=20=E2=80=94=20wire=20headless=20harness=20?= =?UTF-8?q?to=20load=20resource=20categories=20(trade=20pipeline=20now=20L?= =?UTF-8?q?IVE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The headless trade pipeline was unit-proven but inert in real runs: nothing called set_resource_categories_json, so process_trade_phase saw empty categories and sourced nothing. Wire it in. - scenes/headless/player_api_main.gd::_apply_resource_categories builds the resource id→category map from DataLoader.get_all_resources() and stamps it onto GdPlayerApi via set_resource_categories_json, AFTER load_state_json (same #[serde(skip)] re-stamp pattern as units_runtime_catalog + tech_web). Now a real headless game classifies owned-tile collectibles → sources luxury/strategic surpluses → forms trades → view_json carries them. End-to-end LIVE. Verified: unit+integration GUT 750 (737 pass / 13 pending / 0 fail); the headless projection-roundtrip boot path (which exercises _apply_resource_categories) is green. GDScript-only change calling an existing FFI — no dylib rebuild needed. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...-model-unify-headless-view-completeness.md | 7 +++++- .../games/age-of-dwarves/data/objectives.json | 10 ++++---- .../engine/scenes/headless/player_api_main.gd | 24 +++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/.project/objectives/p3-25-rail1-city-model-unify-headless-view-completeness.md b/.project/objectives/p3-25-rail1-city-model-unify-headless-view-completeness.md index 8756ff47..18d9b824 100644 --- a/.project/objectives/p3-25-rail1-city-model-unify-headless-view-completeness.md +++ b/.project/objectives/p3-25-rail1-city-model-unify-headless-view-completeness.md @@ -88,7 +88,12 @@ to `state.trade_ledger`, (d) projecting it all. swap/sale agreements (`swap_deal_view`/`sale_deal_view`). **`view_json` now carries real inter-player trades** — the headless "simulator provides everything" goal is met (gate is the headless assertion, no UI screenshot). **Done 2026-06-26** — - `projection_surfaces_trade_deals_from_ledger` (mc-player-api 171/0). + `projection_surfaces_trade_deals_from_ledger` (mc-player-api 171/0). **Harness wiring:** + `scenes/headless/player_api_main.gd::_apply_resource_categories` now builds the + id→category map from DataLoader and calls `set_resource_categories_json` after + `load_state_json` (mirroring the units/tech catalog re-stamps), so a real headless run + actually sources trades — the pipeline is LIVE, not just unit-tested. (unit+integration + GUT 750: 737 pass / 13 pending / 0 fail.) - [ ] **Step 6 — live game adopts the unified `PlayerView` (large, separate).** The *headless* path is now complete (steps 1-5). Making the **live game** GDScript view-only for trades is a much bigger initiative: the live game reads `GameState` via diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json index 045950fb..1f99abaf 100644 --- a/public/games/age-of-dwarves/data/objectives.json +++ b/public/games/age-of-dwarves/data/objectives.json @@ -1,12 +1,12 @@ { - "generated_at": "2026-06-26T06:26:53Z", + "generated_at": "2026-06-26T06:32:42Z", "totals": { - "partial": 2, "done": 296, - "missing": 0, - "oos": 31, - "stub": 0, + "partial": 2, "in_progress": 0, + "missing": 0, + "stub": 0, + "oos": 31, "total": 329 }, "objectives": [ diff --git a/src/game/engine/scenes/headless/player_api_main.gd b/src/game/engine/scenes/headless/player_api_main.gd index 832a5930..a5860a96 100644 --- a/src/game/engine/scenes/headless/player_api_main.gd +++ b/src/game/engine/scenes/headless/player_api_main.gd @@ -222,6 +222,30 @@ func _hydrate_player_api(num_players: int) -> void: # stalemates. _apply_tech_web() + # p3-25: stamp the resource id→category map so the headless turn can classify + # owned-tile collectibles for trade sourcing (`process_trade_phase`). Same + # `#[serde(skip)]` constraint as the catalogs above — load AFTER + # `load_state_json`. Without this, `resource_categories` is empty and no + # inter-player trade ever sources. + _apply_resource_categories() + + +## 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`. +func _apply_resource_categories() -> void: + var map: Dictionary = {} + for res: Dictionary in DataLoader.get_all_resources(): + var rid: String = str(res.get("id", "")) + var cat: String = str(res.get("category", "")) + if not rid.is_empty() and not cat.is_empty(): + map[rid] = cat + if map.is_empty(): + _emit_protocol_error("resource categories empty — headless trades will not source") + return + var n: int = int(_api.set_resource_categories_json(JSON.stringify(map))) + _emit_event("resource_categories_api_loaded", {"resources": n}) + ## Stamp the playable race onto every player slot's simulation `PlayerState`. ## `GdGameState::set_player_presentation_json` mirrors `race_id` onto