feat(@projects/@magic-civilization): p3-23 DONE — trade richness complete; deal-UI screenshot-proven

Phase-gate proof for the deal UI (revival step 5 verification): diplomacy_deal_proof.tscn
instantiates the REAL diplomacy_panel.tscn with a crafted GameState (human + 2 AI rivals,
ledger holding one LuxurySwap + one StrategicSwap + one ResourceSale) and self-captures.

Screenshot reviewed in-conversation — the panel renders, in the correct per-rival rows:
- AI 1 (Ironhold): "Luxury Trade: Receiving Silk for Furs" + "Resource Sale: Buying
  Horses (−2 gold/turn)"
- AI 2 (Goldvein): "Strategic Trade: Receiving Coal Seam for Iron Ore"
All three deal types render with correct direction, resources, and gold flow.

p3-23 status partial → done. Every acceptance bullet now met with evidence: gold↔resource
+ strategic swaps + luxury swaps (mc-trade) · AI evaluation · in-game pipeline revived
end-to-end (steps 1-4, GUT 750/0 + 25-turn arena exit 0) · deal UI (step 5, screenshot).
tribute.rs stays Game-2 deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-26 00:07:43 -04:00
parent 99e0a4447f
commit e6b7c9b2ce
5 changed files with 117 additions and 27 deletions

View file

@ -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** |
</td><td valign='top' style='padding-left:2em'>
@ -26,7 +26,7 @@
| Team Lead | Remaining |
|---|---|
| [warcouncil](../team-leads/warcouncil.md) | 2 |
| [warcouncil](../team-leads/warcouncil.md) | 1 |
</td></tr></table>

View file

@ -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 15).**
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 15 (see Progress); GUT 750/0 + panel
screenshot reviewed in-conversation. The `tribute.rs` path remains Game-2 deferred.

View file

@ -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<PlayerTradeInput>` (`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."
},
{

View file

@ -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()

View file

@ -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")