test(@projects/@magic-civilization): p3-23 revival step 4 — verify trade tile-sourcing; full pipeline end-to-end proven

Added the last missing link test: _collect_tradeable_resources against a real GameMap.

- test_collect_tradeable_resources_classifies_owned_tiles: builds a GameMap with
  iron_ore×2 (strategic) + silk (luxury) deposit tiles owned by a player, asserts
  _collect_tradeable_resources returns strategics=[iron_ore,iron_ore] (dups kept for
  the MIN_COPIES_TO_TRADE surplus rule) + luxuries=[silk]. Proves _serialize_players'
  real DataLoader-category tile sourcing. NB: DataLoader maps the "resources" category
  to the deposits/ dir — served strategic ids are iron_ore/horses, not "iron".
- before_all loads the theme (category lookups need DataLoader). GUT 749/0.

Full inter-player trade pipeline now GUT-proven link-by-link AND headless-proven live:
real tiles → _collect_tradeable_resources (step 4) → process_trades → ledger →
traded_luxuries/strategics (step 1) → strategic build access (step 3); gold sale →
gold_flow_for → net gold (pre-existing); integration runs every round in a live 25-turn
arena without aborting the loop (step 2). Gold flow no longer inert — process_turn now
populates GameState.trade_ledger_json each round.

Only remaining for done: the deal UI (diplomacy panel + wire trade_agreed). Stays partial.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-25 23:08:26 -04:00
parent f7959e11a0
commit 916dcda55d
3 changed files with 74 additions and 7 deletions

View file

@ -105,10 +105,26 @@ pass; full suite 748/0. The acceptance chain is now GUT-proven link-by-link:
process_trades→ledger→`traded_strategics` (step 1) · `traded_strategics`→build access
(step 3) · `gold_flow_for`→net gold (`test_trade_gold_flows_into_net_gold`).
**Next (step 4):** end-to-end in-game proof — a crafted/longer scenario where a
complementary-surplus trade demonstrably forms in a played game (buyer gains the
resource, gold flows >0, a gated unit becomes buildable) — then the deal UI
(diplomacy panel showing active deals + the dangling `trade_agreed` signal wired).
**Revival step 4 — full pipeline verified end-to-end (2026-06-25).** Added the last
missing link test: `test_collect_tradeable_resources_classifies_owned_tiles` builds a
real `GameMap` with `iron_ore`×2 (strategic) + `silk` (luxury) deposit tiles owned by a
player and asserts `_collect_tradeable_resources` returns strategics=[iron_ore,iron_ore]
(dups kept for the surplus rule) + luxuries=[silk] — proving `_serialize_players`' real
DataLoader-category tile sourcing (NB: DataLoader maps the `resources` category to the
`deposits/` dir; the served strategic ids are `iron_ore`/`horses`, not `iron`). GUT 749/0.
**The full inter-player trade pipeline is now GUT-proven link-by-link AND headless-proven
live:** real owned tiles → `_collect_tradeable_resources` (step 4) → `process_trades`
ledger → `traded_luxuries`/`traded_strategics` (step 1 round-trip) → strategic build
access (step 3) · gold sale → `gold_flow_for` → net gold (`test_trade_gold_flows_into_net_gold`)
· the integration runs every round in a live 25-turn arena without aborting the loop
(step 2, exit 0). Gold flow is **no longer inert**`Diplomacy.process_turn` now
populates `GameState.trade_ledger_json` each round, which `economy.gd` reads.
**Only remaining for `done`:** the **deal UI** — surface active deals in the diplomacy
panel + wire the dangling `EventBus.trade_agreed` signal (needs a `GdTradeLedger`
agreements-enumeration `#[func]` to describe each deal to the panel/chronicle). Status
stays `partial` until that lands.
**In-game application part A — gold flow LIVE (2026-06-25).** `GdTradeLedger`
gains `gold_flow_for` + `incoming_strategics` #[func]s; `GdEconomy` gains a

View file

@ -1,11 +1,11 @@
{
"generated_at": "2026-06-26T02:37:05Z",
"generated_at": "2026-06-26T03:08:26Z",
"totals": {
"stub": 0,
"done": 295,
"oos": 31,
"partial": 2,
"in_progress": 0,
"partial": 2,
"done": 295,
"missing": 0,
"total": 328
},

View file

@ -14,6 +14,13 @@ const HappinessScript: GDScript = preload(
"res://engine/src/modules/empire/happiness.gd"
)
const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd")
const GameMapScript: GDScript = preload("res://engine/src/map/game_map.gd")
const TileScript: GDScript = preload("res://engine/src/map/tile.gd")
func before_all() -> void:
# _collect_tradeable_resources classifies tiles by DataLoader resource category.
DataLoader.load_theme("age-of-dwarves")
# ---------------------------------------------------------------------------
@ -102,6 +109,46 @@ func test_ledger_roundtrip_applies_traded_resources() -> void:
assert_true("iron" in pb.get("traded_strategics"), "p1 gains iron strategic")
# ---------------------------------------------------------------------------
# _collect_tradeable_resources — tile sourcing (p3-23 step 4 end-to-end source)
# ---------------------------------------------------------------------------
func test_collect_tradeable_resources_classifies_owned_tiles() -> void:
## The source link behind _serialize_players: against a real GameMap + real
## DataLoader categories, a player's owned strategic tiles land in strategics
## (duplicates kept for the MIN_COPIES_TO_TRADE surplus rule), luxury tiles in
## luxuries, and bonus/empty tiles in neither.
var gm: RefCounted = GameMapScript.new()
gm.initialize(4, 1, 0)
# Real served deposit ids: DataLoader maps the "resources" category to the
# deposits/ dir — iron_ore is strategic, silk is luxury (NOT "iron", unserved).
gm.set_tile(Vector2i(0, 0), _res_tile(Vector2i(0, 0), "iron_ore")) # strategic
gm.set_tile(Vector2i(1, 0), _res_tile(Vector2i(1, 0), "iron_ore")) # strategic (dup)
gm.set_tile(Vector2i(2, 0), _res_tile(Vector2i(2, 0), "silk")) # luxury
gm.set_tile(Vector2i(3, 0), _res_tile(Vector2i(3, 0), "")) # no resource
var player: _PlayerShim = _PlayerShim.new()
var city: _CityShim = _CityShim.new()
city.owned_tiles = [Vector2i(0, 0), Vector2i(1, 0), Vector2i(2, 0), Vector2i(3, 0)]
player.cities = [city]
var out: Array = DiplomacyScript._collect_tradeable_resources(player, gm)
var luxuries: Array = out[0]
var strategics: Array = out[1]
assert_eq(strategics.size(), 2, "two iron_ore tiles → two strategic copies (dups kept)")
assert_true("iron_ore" in strategics, "iron_ore classified as strategic")
assert_eq(luxuries.size(), 1, "one silk tile → one luxury copy")
assert_true("silk" in luxuries, "silk classified as luxury")
func _res_tile(pos: Vector2i, rid: String) -> Resource:
var t: Resource = TileScript.new()
t.biome_id = "grassland"
t.position = pos
t.resource_id = rid
return t
# ---------------------------------------------------------------------------
# happiness.gd:_collect_unique_luxury_ids — traded_luxuries union
# ---------------------------------------------------------------------------
@ -168,3 +215,7 @@ func test_process_turn_pending_gd_trade() -> void:
class _PlayerShim extends RefCounted:
var traded_luxuries: Array[String] = []
var cities: Array = []
class _CityShim extends RefCounted:
var owned_tiles: Array = []