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