feat(#58): trade & embargoes — TurnResult.economy ops (trade/embargo, declare/lift) with a deterministic per-turn effect and war auto-suspension
CI / build + unit + e2e (pull_request) Successful in 2m3s

This commit is contained in:
2026-10-15 17:26:24 +00:00
parent ed91735c8a
commit b5ea8ba5a4
15 changed files with 776 additions and 23 deletions
+67
View File
@@ -363,6 +363,59 @@ export interface HybridOp {
outcome?: 'success' | 'exposed';
}
// --- Trade & embargo ops (v0.35.0, #58) --------------------------------------
/**
* (v0.35.0, #58) A bilateral TRADE op in `TurnResult.economy.trade`: `a` and
* `b` are the two parties (each a nation's id or unique name — resolved by
* the engine's `resolveNationRef`; a pair that resolves to the same nation,
* or contains an unknown/ambiguous ref, is dropped defensively). Without
* `lift` the op DECLARES the agreement (idempotent — an already-active
* agreement is not duplicated); `lift: true` ENDS it.
*/
export interface EconomyTradeOp {
/** First party's id or unique name. */
a: string;
/** Second party's id or unique name (distinct from `a` after resolution). */
b: string;
/** `true` → end this bilateral trade; default `false` = declare it. */
lift?: boolean;
}
/**
* (v0.35.0, #58) A unilateral EMBARGO op in `TurnResult.economy.embargo`:
* `from` blocks trade with `target` (each a nation's id or unique name —
* resolved by `resolveNationRef`; an unknown/ambiguous ref or
* `from === target` is dropped defensively). Without `lift` the op imposes
* the embargo (idempotent — an already-active one is not duplicated);
* `lift: true` LIFTS it.
*/
export interface EconomyEmbargoOp {
/** The imposing nation's id or unique name. */
from: string;
/** The embargoed nation's id or unique name (distinct from `from`). */
target: string;
/** `true` → lift this embargo; default `false` = impose it. */
lift?: boolean;
}
/**
* (v0.35.0, #58) The turn's trade-and-embargo operations, nested under
* `TurnResult.economy`. Both arrays are required and default to `[]`. The
* engine applies the ops to `GameState.trades` / `GameState.embargoes`
* (add = declare/impose, remove = lift) and, on every committed turn, applies
* the per-turn economic effect of the FULL active lists (trade: both
* parties' economy up a little; embargo: the target's economy down a
* little). Active trades/embargoes between nations on opposite sides of an
* active war (v0.33, #56) are suspended (removed) automatically.
*/
export interface EconomyOps {
/** Bilateral trades (declare or lift). */
trade: EconomyTradeOp[];
/** Unilateral embargoes (impose or lift). */
embargo: EconomyEmbargoOp[];
}
/** The exact JSON object the world-simulator LLM must return for a turn. */
export interface TurnResult {
date: string;
@@ -439,6 +492,20 @@ export interface TurnResult {
* `{ ops: [] }`).
*/
hybrid: { ops: HybridOp[] };
/**
* (v0.35.0, #58) Trade and embargo ops — the economy layer. `trade`
* declares (or, with `lift: true`, ends) bilateral trade agreements
* between two nations; `embargo` imposes (or, with `lift: true`, lifts)
* a unilateral embargo on a target. Both arrays are required and default
* to `[]`. Applied directly by `applyTurnResult` (add to / remove from
* `GameState.trades` / `GameState.embargoes`), and the per-turn economic
* effect of the FULL active lists is applied on every committed turn (a
* trade lifts both parties' economy a little; an embargo lowers the
* target's economy a little — clamped, deterministic). Active
* trades/embargoes between nations on opposite sides of an active war are
* suspended automatically.
*/
economy: EconomyOps;
/**
* (v0.27, #44) Map-marker ops this turn. `add` places new named markers
* (the engine positions each at its region's centroid + a small