diff --git a/.project/objectives/README.md b/.project/objectives/README.md
index 1808608d..9b4ffa1a 100644
--- a/.project/objectives/README.md
+++ b/.project/objectives/README.md
@@ -17,8 +17,8 @@
| **P0** | 44 | 0 | 0 | 0 | 0 | 0 | 44 |
| **P1** | 88 | 0 | 0 | 0 | 0 | 1 | 89 |
| **P2** | 130 | 0 | 0 | 0 | 0 | 1 | 131 |
-| **P3 (oos)** | 33 | 0 | 2 | 0 | 0 | 29 | 64 |
-| **total** | **295** | **0** | **2** | **0** | **0** | **31** | **328** |
+| **P3 (oos)** | 34 | 0 | 1 | 0 | 0 | 29 | 64 |
+| **total** | **296** | **0** | **1** | **0** | **0** | **31** | **328** |
@@ -26,7 +26,7 @@
| Team Lead | Remaining |
|---|---|
-| [warcouncil](../team-leads/warcouncil.md) | 2 |
+| [warcouncil](../team-leads/warcouncil.md) | 1 |
|
diff --git a/.project/objectives/p3-23-trade-richness-gold-strategic.md b/.project/objectives/p3-23-trade-richness-gold-strategic.md
index 9a1f33a5..9858c8ff 100644
--- a/.project/objectives/p3-23-trade-richness-gold-strategic.md
+++ b/.project/objectives/p3-23-trade-richness-gold-strategic.md
@@ -2,10 +2,10 @@
id: p3-23
title: Trade richness — gold & strategic-resource trades with opponents
priority: p3
-status: partial
+status: done
scope: game1
owner: warcouncil
-updated_at: 2026-06-25
+updated_at: 2026-06-26
---
## Summary
@@ -46,19 +46,19 @@ is all **inert in-game until the diplomacy turn-integration is revived**. The
(`evaluate_trades` is the shared evaluation surface). *(In-game: the FFI caller
must source each player's `tile_strategics` — forward-compatible via
`#[serde(default)]`. Wiring follow-up.)*
-- [~] Logic in Rust (`mc-trade`): gold-for-luxury sale + strategic-for-strategic
+- [x] Logic in Rust (`mc-trade`): gold-for-luxury sale + strategic-for-strategic
swap + strategic sale all evaluate + activate (8 cargo tests; mc-trade 66/0).
- **In-game application — part A (gold flow) wired but INERT:** `GdTradeLedger.gold_flow_for`
- + `GdEconomy` `trade_gold` param + `economy.gd` sourcing are correct and GUT-tested
- (`test_trade_gold_flows_into_net_gold`, seller +/buyer −) — but they read
- `GameState.trade_ledger_json`, which is **never populated in-game** (the only writer is
- the disabled `Diplomacy.process_turn`). So gold flow works the instant the trade
- integration is revived, but produces 0 until then.
- **[ ] REAL remaining work — revive the diplomacy trade turn-integration** (see the
- DISCOVERY note): reconcile the 3 `diplomacy.gd ↔ process_trades` contract drifts,
- re-enable `Diplomacy.process_turn` in `turn_manager.gd` (carefully — disabled modules
- there can abort the arena turn loop), add `PlayerState.traded_strategics` + unit-gating,
- verify the full pipeline headless + GUT. Then the deal UI. → status stays `partial`.
+ **In-game application — REVIVED + verified end-to-end (2026-06-25/26, steps 1–5).**
+ The diplomacy trade turn-integration is live: (1) `diplomacy.gd ↔ process_trades`
+ contract reconciled (`_serialize_players` emits PlayerTradeInput, `process_turn`
+ consumes `{ledger}`); (2) `Diplomacy.process_turn` re-enabled in `turn_manager.gd`,
+ loop-survival proven (25-turn arena, exit 0); (3) `PlayerState.traded_strategics` +
+ unit-gating (`_player_owns_resource` honors traded strategics); (4) tile-sourcing
+ + full chain GUT-proven; (5) **deal UI** — active swaps/sales render in the diplomacy
+ panel (`diplomacy_deal_proof.tscn`, screenshot reviewed: luxury/strategic/sale rows
+ with correct direction + gold). Gold flow is no longer inert — `process_turn` now
+ populates `GameState.trade_ledger_json` each round, which `economy.gd` reads. Full
+ GUT suite 750/0.
## Progress (2026-06-25)
@@ -163,6 +163,8 @@ reads `incoming_strategics`, happiness reads sale luxuries; (c) GDScript deal UI
## Notes
-Verified 2026-06-25. Status `partial`: luxury swaps work; gold + strategic trades
-are the missing half. (Strategic-resource trade may be a deliberate Game-1 scope
-limit — confirm before building if it conflicts with the strategic-scarcity design.)
+Verified 2026-06-26. Status `done`: gold sales + strategic swaps + luxury swaps all
+form in `mc-trade`, the diplomacy turn-integration is revived and runs each round, and
+deals apply in-game (gold flow, happiness luxuries, strategic unit-gating) and render in
+the diplomacy panel. Closed across revival steps 1–5 (see Progress); GUT 750/0 + panel
+screenshot reviewed in-conversation. The `tribute.rs` path remains Game-2 deferred.
diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json
index 49d2d159..43bdf58c 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-26T03:38:21Z",
+ "generated_at": "2026-06-26T04:07:21Z",
"totals": {
"oos": 31,
- "missing": 0,
- "done": 295,
- "partial": 2,
- "in_progress": 0,
+ "done": 296,
+ "partial": 1,
"stub": 0,
+ "missing": 0,
+ "in_progress": 0,
"total": 328
},
"objectives": [
@@ -3274,10 +3274,10 @@
"id": "p3-23",
"title": "Trade richness — gold & strategic-resource trades with opponents",
"priority": "p3",
- "status": "partial",
+ "status": "done",
"scope": "game1",
"owner": "warcouncil",
- "updated_at": "2026-06-25",
+ "updated_at": "2026-06-26",
"summary": "> **DISCOVERY (2026-06-25, verify-first):** the original premise was wrong. The\n> inter-player trade turn-integration is **DISABLED in the played game**, not\n> \"luxury-only\". `turn_manager.gd:287` has `Diplomacy.process_turn()` commented out\n> with a stale \"empty stub module\" note (diplomacy.gd was rebuilt but never\n> re-enabled). The *only* writer of `GameState.trade_ledger_json` is diplomacy.gd:32\n> inside that disabled call, so **no inter-player trades run at all** (luxury, strategic,\n> or gold). Worse, the `diplomacy.gd ↔ GdTrade.process_trades` contract has drifted\n> in 3 places: (1) `_serialize_players` emits `{index, traded_luxuries, personality}`\n> but `process_trades` deserializes `Vec` (`player_index`,\n> `tile_luxuries`, `tile_strategics`, `trade_willingness`); (2) `process_trades`\n> returns `{ledger}` but diplomacy.gd reads `result[\"trade_ledger_json\"]`,\n> `[\"relation_changes\"]`, `[\"new_trades\"]`, `[\"broken_trades\"]`; (3) relations\n> advancement isn't returned. So enabling is NOT a one-line uncomment.\n\nThe **simulation logic is complete + cargo-tested** (luxury/strategic swaps + gold\nsales in `mc-trade`, 66/0) and the gold-flow application is wired (part A) — but it\nis all **inert in-game until the diplomacy turn-integration is revived**. The\n`tribute.rs` path stays Game-2 deferred."
},
{
diff --git a/src/game/engine/scenes/tests/diplomacy_deal_proof.gd b/src/game/engine/scenes/tests/diplomacy_deal_proof.gd
new file mode 100644
index 00000000..dcc3e011
--- /dev/null
+++ b/src/game/engine/scenes/tests/diplomacy_deal_proof.gd
@@ -0,0 +1,80 @@
+extends Control
+## p3-23 proof: the REAL diplomacy panel rendering ACTIVE INTER-PLAYER TRADE
+## DEALS (luxury swap, strategic swap, gold sale) in its per-rival agreement
+## section. Crafts a GameState with a human + two AI rivals and a trade ledger
+## holding one of each deal type, instantiates the real diplomacy_panel.tscn
+## (exercising get_active_agreements + _make_agreement_section), self-captures.
+
+const OUTPUT_DIR: String = "user://screenshots"
+const VIEWPORT_SIZE: Vector2i = Vector2i(1280, 720)
+const CAPTURE_DELAY: float = 0.8
+const PANEL_SCENE: PackedScene = preload("res://engine/scenes/hud/diplomacy_panel.tscn")
+const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd")
+
+const LEDGER_JSON: String = (
+ '{"agreements":['
+ + '{"LuxurySwap":{"partners":[0,1],'
+ + '"gives_a":"furs","gives_b":"silk","turn_started":1}},'
+ + '{"ResourceSale":{"partners":[0,1],"seller":1,"buyer":0,'
+ + '"resource":"horses","strategic":true,"gold_per_turn":2,"turn_started":1}},'
+ + '{"StrategicSwap":{"partners":[0,2],'
+ + '"gives_a":"iron_ore","gives_b":"coal_seam","turn_started":1}}'
+ + '],"next_agreement_id":0}'
+)
+
+var _captured: bool = false
+
+
+func _ready() -> void:
+ set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ get_viewport().size = VIEWPORT_SIZE
+ DisplayServer.window_set_size(VIEWPORT_SIZE)
+ RenderingServer.set_default_clear_color(Color(0.06, 0.07, 0.10))
+ DataLoader.load_theme("age-of-dwarves")
+ ThemeAssets.set_theme("age-of-dwarves")
+ ThemeVocabulary.load_vocabulary("age-of-dwarves")
+ _setup_state()
+ var panel: Control = PANEL_SCENE.instantiate() as Control
+ add_child(panel)
+ panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ # The first frame(s) come back blank on this renderer — let the panel lay out
+ # and render several frames before grabbing the viewport texture.
+ for _i: int in range(8):
+ await get_tree().process_frame
+ await get_tree().create_timer(CAPTURE_DELAY).timeout
+ _capture_and_quit()
+
+
+func _setup_state() -> void:
+ var human: RefCounted = PlayerScript.new(0, "You", "dwarf")
+ human.is_human = true
+ var r1: RefCounted = PlayerScript.new(1, "Ironhold", "dwarf")
+ r1.is_human = false
+ r1.clan_id = "ironhold"
+ var r2: RefCounted = PlayerScript.new(2, "Goldvein", "dwarf")
+ r2.is_human = false
+ r2.clan_id = "goldvein"
+ GameState.players = [human, r1, r2]
+ GameState.diplomacy = {}
+ GameState.trade_ledger_json = LEDGER_JSON
+
+
+func _capture_and_quit() -> void:
+ if _captured:
+ return
+ _captured = true
+ DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(OUTPUT_DIR))
+ var image: Image = get_viewport().get_texture().get_image()
+ if image == null:
+ push_error("diplomacy_deal_proof: viewport image null")
+ get_tree().quit(1)
+ return
+ var rel_path: String = "%s/diplomacy_deal_proof.png" % OUTPUT_DIR
+ var abs_path: String = ProjectSettings.globalize_path(rel_path)
+ var err: Error = image.save_png(abs_path)
+ if err == OK:
+ print("SCREENSHOT_PATH:%s" % abs_path)
+ print("diplomacy_deal_proof: %dx%d saved" % [image.get_width(), image.get_height()])
+ else:
+ push_error("diplomacy_deal_proof: save failed: %s" % error_string(err))
+ get_tree().quit()
diff --git a/src/game/engine/scenes/tests/diplomacy_deal_proof.tscn b/src/game/engine/scenes/tests/diplomacy_deal_proof.tscn
new file mode 100644
index 00000000..244f1a02
--- /dev/null
+++ b/src/game/engine/scenes/tests/diplomacy_deal_proof.tscn
@@ -0,0 +1,8 @@
+[gd_scene load_steps=2 format=3]
+
+[ext_resource type="Script" path="res://engine/scenes/tests/diplomacy_deal_proof.gd" id="1"]
+
+[node name="DiplomacyDealProof" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+script = ExtResource("1")