From 6b3b57180604cee47d5e31d7df60c5bf3a429d9f Mon Sep 17 00:00:00 2001 From: Natalie Date: Tue, 23 Jun 2026 20:08:56 -0400 Subject: [PATCH] =?UTF-8?q?docs(diplomacy):=20=F0=9F=93=9D=20reconcile=20s?= =?UTF-8?q?tart-state=20spec=20to=20courier=20model=20+=20track=20AI=20war?= =?UTF-8?q?-dec=20gap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two diplomacy models contradicted each other in the written record: p1-01 diplomacy-lite ('all pairs start at war, missing key → war') vs the newer courier-diplomacy (COMMUNICATIONS.md §War declaration semantics, p3-01: start at peace, sender enters War on war-dec envelope dispatch). The Rust implementation follows courier-diplomacy, so that is canonical. - p1-01: add a SUPERSEDED banner + inline [SUPERSEDED] annotations; history retained. Canonical rule is start-at-peace, war via dispatched war-dec. - COMMUNICATIONS.md: fix the one internal inconsistency (§0 said recipient war state applies at arrival in a way that read as all-effects-at-arrival; scoped it to recipient-side, cross-linked the sender-on-dispatch exception). - New objective p3-16 (status partial, owner warcouncil): the AI has no proactive war-declaration — decide_tactical_actions has no diplomacy step and there is no DeclareWar in mc-ai, so AI-vs-AI never enters war and clan aggression personalities don't manifest. Specs the fix to the courier model (first-contact + military balance + aggression → dispatch_war_declaration) and notes the stale is_at_war comment as a code-fidelity cleanup. - Register p3-16 under warcouncil; regen objectives dashboard. Co-Authored-By: Claude Opus 4.8 --- .project/objectives/DASHBOARD_CATEGORIES.md | 1 + .project/objectives/README.md | 6 +- .project/objectives/objectives.json | 26 +++- .project/objectives/p1-01-diplomacy-lite.md | 17 ++- .../p3-16-ai-proactive-war-declaration.md | 130 ++++++++++++++++++ .project/team-leads/warcouncil.md | 1 + .../docs/military/COMMUNICATIONS.md | 2 +- 7 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 .project/objectives/p3-16-ai-proactive-war-declaration.md diff --git a/.project/objectives/DASHBOARD_CATEGORIES.md b/.project/objectives/DASHBOARD_CATEGORIES.md index ad016b45..69355a25 100644 --- a/.project/objectives/DASHBOARD_CATEGORIES.md +++ b/.project/objectives/DASHBOARD_CATEGORIES.md @@ -521,4 +521,5 @@ | [p3-13d](p3-13d-anomalous-events.md) | ✅ done | P3 | Anomalous events — aurora, fog_bank, thermal_anomaly | [unassigned](../team-leads/unassigned.md) | 🟢 | | [p3-14](p3-14-game-start-script.md) | ✅ done | P3 | Declarative game-start script + runner — data-driven, moddable opening sequence | [shipwright](../team-leads/shipwright.md) | 🟢 | | [p3-15](p3-15-hotseat-multiplayer.md) | ✅ done | P3 | Local hotseat multiplayer — multiple humans alternating on one device | [shipwright](../team-leads/shipwright.md) | 🟢 | +| [p3-16](p3-16-ai-proactive-war-declaration.md) | 🟡 partial | P3 | AI proactive war-declaration via the courier system | [warcouncil](../team-leads/warcouncil.md) | 🟢 | diff --git a/.project/objectives/README.md b/.project/objectives/README.md index 832e1072..1701eb22 100644 --- a/.project/objectives/README.md +++ b/.project/objectives/README.md @@ -17,8 +17,8 @@ | **P0** | 0 | 0 | 0 | 0 | 0 | 44 | 44 | | **P1** | 0 | 0 | 0 | 0 | 1 | 88 | 89 | | **P2** | 0 | 0 | 0 | 0 | 1 | 132 | 133 | -| **P3 (oos)** | 0 | 0 | 0 | 0 | 29 | 26 | 55 | -| **total** | **0** | **0** | **0** | **0** | **31** | **290** | **321** | +| **P3 (oos)** | 0 | 1 | 0 | 0 | 29 | 26 | 56 | +| **total** | **0** | **1** | **0** | **0** | **31** | **290** | **322** | @@ -26,7 +26,7 @@ | Team Lead | Remaining | |---|---| -| — | 0 | +| [warcouncil](../team-leads/warcouncil.md) | 1 | diff --git a/.project/objectives/objectives.json b/.project/objectives/objectives.json index 602eb81f..d83387c3 100644 --- a/.project/objectives/objectives.json +++ b/.project/objectives/objectives.json @@ -1,13 +1,13 @@ { - "generated_at": "2026-06-23T17:17:04Z", + "generated_at": "2026-06-24T00:06:25Z", "totals": { "done": 290, "in_progress": 0, - "partial": 0, + "partial": 1, "stub": 0, "missing": 0, "oos": 31, - "total": 321 + "total": 322 }, "objectives": [ { @@ -549,7 +549,7 @@ "owner": "shipwright", "updated_at": "2026-04-17", "blocked_by": [], - "summary": "`mc-trade` now has a full diplomacy surface: `declare_war` / `offer_peace` / `evaluate_trade_offer` / `apply_trade_offer` free functions plus `DiplomacyEvent` enum and `TradeOffer` struct. `TurnProcessor` exposes `action_declare_war`, `action_offer_peace`, `action_offer_trade`, and `action_accept_trade_offer` as public methods callable from GDExtension. EA policy: AI always rejects player-initiated peace offers and gold-for-luxury offers; automated luxury swaps flow through the existing `evaluate_trades` path. Relation state machine (`Relation::Neutral/Peace/Friendly/War`) was already present in `mc-trade::relation`.\n\nAI attack decisions are gated on `Relation::War` via `_is_at_war` in `simple_heuristic_ai.gd`. `_collect_enemy_units`, `_collect_enemy_city_positions`, and `_enemy_within` all skip players whose relation is Peace or Friendly. Missing key defaults to War (EA: all pairs start at war). GUT coverage in `test_simple_heuristic_ai_war_gate.gd`." + "summary": "`mc-trade` now has a full diplomacy surface: `declare_war` / `offer_peace` / `evaluate_trade_offer` / `apply_trade_offer` free functions plus `DiplomacyEvent` enum and `TradeOffer` struct. `TurnProcessor` exposes `action_declare_war`, `action_offer_peace`, `action_offer_trade`, and `action_accept_trade_offer` as public methods callable from GDExtension. EA policy: AI always rejects player-initiated peace offers and gold-for-luxury offers; automated luxury swaps flow through the existing `evaluate_trades` path. Relation state machine (`Relation::Neutral/Peace/Friendly/War`) was already present in `mc-trade::relation`.\n\nAI attack decisions are gated on `Relation::War` via `_is_at_war` in `simple_heuristic_ai.gd`. `_collect_enemy_units`, `_collect_enemy_city_positions`, and `_enemy_within` all skip players whose relation is Peace or Friendly. ~~Missing key defaults to War (EA: all pairs start at war).~~ **[SUPERSEDED — see banner: canonical start state is PEACE; war begins on war-dec dispatch per COMMUNICATIONS.md / p3-01.]** GUT coverage in `test_simple_heuristic_ai_war_gate.gd`." }, { "id": "p1-02", @@ -3567,6 +3567,17 @@ "updated_at": "2026-06-19", "blocked_by": [], "summary": "" + }, + { + "id": "p3-16", + "title": "AI proactive war-declaration via the courier system", + "priority": "p3", + "status": "partial", + "scope": "game1", + "owner": "warcouncil", + "updated_at": "2026-06-23", + "blocked_by": [], + "summary": "The canonical courier-diplomacy model (`COMMUNICATIONS.md` §\"War declaration\nsemantics\") assumes a player **dispatches a war-dec envelope** to enter war: the\n**sender** enters `War` immediately on dispatch and its units can attack the same\nturn; the **recipient** flips to `War` on delivery/interception; **defenders\nalways retaliate when struck** regardless of formal `RelationState`. Pairs start\nat **peace** (`mc-player-api/src/projection.rs::project_tactical_relations` defaults\nunset pairs to 0 = peace; `mc-player-api/src/comms_dispatch.rs` flips the shared\n`RelationState` cell to `War` only on delivery).\n\n**The gap:** the AI has **no proactive war-declaration logic**. `mc-ai`'s tactical\nentry point `decide_tactical_actions` (`src/simulator/crates/mc-ai/src/tactical/mod.rs`)\nruns movement → combat → settle → production → citizens with **no diplomacy step**,\nand there is **zero** `DeclareWar` / war-dec action anywhere in `mc-ai` (no\nstrategic action variant, no caller of `comms_dispatch::dispatch_war_declaration`\n— the only non-test caller is the human dispatch path at\n`mc-player-api/src/dispatch.rs:1605`). `mc-ai/src/diplomacy.rs` covers only the\np3-01 courier open-borders / shared-map offer/accept heuristics — not war.\n\nConsequence (observed in a 200-turn hotseat self-play): AIs never initiate war →\n`collect_enemy_city_positions` (`tactical/movement.rs`) is always empty because it\nis gated through `is_at_war` (movement.rs:366) → no `locked_target` is ever set →\nmilitary units never maneuver (48 successful moves across 200 turns). Because war\nnever comes in AI-vs-AI play, the five clan personalities\n(`public/games/age-of-dwarves/data/ai_personalities.json`, `aggression` 1–10,\nwarmonger = 9) do **not** manifest distinct aggression: per\n`mc-ai/src/tactical/thresholds.rs`, `aggression` only tunes combat posture **once\nalready at war**, so a difference that only fires after war-declaration never\nfires at all.\n\nThis objective adds the missing strategic decision step so the AI dispatches a\nwar-dec envelope through the courier system when conditions warrant, entering the\nexisting at-war combat path and making personality `aggression` actually drive\nbehavior." } ], "blocked": [ @@ -3813,5 +3824,10 @@ ] } ], - "remaining_by_lead": [] + "remaining_by_lead": [ + { + "owner": "warcouncil", + "remaining": 1 + } + ] } diff --git a/.project/objectives/p1-01-diplomacy-lite.md b/.project/objectives/p1-01-diplomacy-lite.md index 2f575223..596ce11a 100644 --- a/.project/objectives/p1-01-diplomacy-lite.md +++ b/.project/objectives/p1-01-diplomacy-lite.md @@ -21,18 +21,31 @@ evidence: - ~/Desktop/magic_civ_diplomacy.png --- +> ## ♻️ Start-state semantics SUPERSEDED (2026-06-23) +> +> This objective's original EA rule — **"missing relation key defaults to War (EA: all pairs start at war)"** — has been **superseded by the courier-diplomacy model**. The canonical, current rule is: +> +> - **Pairs start at PEACE.** Unset relation pairs default to peace (0), not war. +> - **War begins on dispatch of a war-dec envelope.** Per `public/games/age-of-dwarves/docs/military/COMMUNICATIONS.md` §"War declaration semantics": the **sender** enters `War` immediately on envelope dispatch; the **recipient** flips to `War` on delivery/interception; **defenders always retaliate when struck** regardless of formal `RelationState`. +> +> Authoritative sources for the current model: `COMMUNICATIONS.md` §"War declaration semantics" (canonical design) and **`.project/objectives/p3-01-courier-diplomacy.md`** (implementing objective, `scope: game1-stretch`). The Rust implementation matches courier-diplomacy: `mc-player-api/src/comms_dispatch.rs` manages `relations` as a map (`relations.entry(key).or_default()` = peace), and `mc-player-api/src/projection.rs::project_tactical_relations` defaults unset pairs to 0 = peace. +> +> The four "missing → war / all pairs start at war" mentions below are **retained as history** and annotated inline with `[SUPERSEDED]`. The GDScript `_is_at_war` reference cited in `evidence:` reflects the old GDScript-AI prototype, which is itself tech-debt per Rail 1 (`p0-26-ai-tactical-rust-port.md`). Do not treat the war-start behavior in this file as current. +> +> **Open follow-up:** the canonical model assumes the AI dispatches war-decs ("the AI's 'war mode' branch", `COMMUNICATIONS.md:180`), but `mc-ai` has no proactive war-declaration logic — tracked by **`p3-16-ai-proactive-war-declaration.md`**. + ## Summary `mc-trade` now has a full diplomacy surface: `declare_war` / `offer_peace` / `evaluate_trade_offer` / `apply_trade_offer` free functions plus `DiplomacyEvent` enum and `TradeOffer` struct. `TurnProcessor` exposes `action_declare_war`, `action_offer_peace`, `action_offer_trade`, and `action_accept_trade_offer` as public methods callable from GDExtension. EA policy: AI always rejects player-initiated peace offers and gold-for-luxury offers; automated luxury swaps flow through the existing `evaluate_trades` path. Relation state machine (`Relation::Neutral/Peace/Friendly/War`) was already present in `mc-trade::relation`. -AI attack decisions are gated on `Relation::War` via `_is_at_war` in `simple_heuristic_ai.gd`. `_collect_enemy_units`, `_collect_enemy_city_positions`, and `_enemy_within` all skip players whose relation is Peace or Friendly. Missing key defaults to War (EA: all pairs start at war). GUT coverage in `test_simple_heuristic_ai_war_gate.gd`. +AI attack decisions are gated on `Relation::War` via `_is_at_war` in `simple_heuristic_ai.gd`. `_collect_enemy_units`, `_collect_enemy_city_positions`, and `_enemy_within` all skip players whose relation is Peace or Friendly. ~~Missing key defaults to War (EA: all pairs start at war).~~ **[SUPERSEDED — see banner: canonical start state is PEACE; war begins on war-dec dispatch per COMMUNICATIONS.md / p3-01.]** GUT coverage in `test_simple_heuristic_ai_war_gate.gd`. ## Acceptance - ✓ `Relation::{Peace, War}` state per player pair; `declare_war` sets relation to War, clears traded_luxuries, mirrors to both players via `action_declare_war` on `TurnProcessor`. Covered by `processor.rs::tdip1_declare_war_sets_war_in_both_players` (4 assertions). - ✓ `TradeOffer { from, to, gold: u32, luxury_id: String }` with `evaluate_trade_offer` (reject, EA) and `apply_trade_offer` (accept, human path). `action_accept_trade_offer` on `TurnProcessor` deducts gold + credits luxury. Covered by `mc-trade/lib.rs::apply_trade_offer_swaps_gold_and_luxury` and `processor.rs::tdip3_accept_trade_offer_updates_ledgers`. - ✓ GDScript diplomacy panel exposes declare-war / offer-trade — `scenes/hud/diplomacy_panel.{tscn,gd}` renders one row per AI rival (clan name, relation badge, action buttons). At-war rows show **Offer Peace** + **Offer Trade**; at-peace rows show **Declare War** + **Offer Trade**. Trade submodal has gold `SpinBox` + luxury `OptionButton` (populated from human's `owned_luxuries`) + Send/Close. Actions route through `Diplomacy.declare_war`, `Diplomacy.offer_peace`, `Diplomacy.offer_trade` (new static methods in `src/modules/empire/diplomacy.gd`). `declare_war` flips `GameState.diplomacy[key]` to `"war"`, clears both players' `traded_luxuries`, emits `EventBus.relation_changed` + new `EventBus.war_declared`. EA policy: `offer_peace` and `offer_trade` always emit `peace_rejected` / `trade_offer_rejected` (no state mutation — mirrors Rust `action_offer_peace`/`action_offer_trade` EA behavior). Wired into `top_bar.tscn` via new `DiplomacyButton` + `KEY_F8` hotkey (F1/F9 already taken by Encyclopedia/Stats). GUT coverage: `test_diplomacy_panel.gd` — **9/9 passing on apricot** (panel instantiates with 2 rows, empty-label fallback, declare_war flips relation + emits signal, declare_war clears luxuries, peace/trade rejections emit with correct payload, self-target is no-op, idempotent war, all 3 signals registered on EventBus). Screenshot at `~/Desktop/magic_civ_diplomacy.png` shows both action-state paths (Ironhold at War / Goldvein at Neutral) in one frame. -- ✓ AI decisions respect peace/war (no attacks during peace) — `_is_at_war` static helper in `simple_heuristic_ai.gd` gates `_collect_enemy_units`, `_collect_enemy_city_positions`, and `_enemy_within`; defaults to War when no relation key (EA: all pairs start at war). GUT tests in `test_simple_heuristic_ai_war_gate.gd` cover peace/war/friendly/missing-key cases. +- ✓ AI decisions respect peace/war (no attacks during peace) — `_is_at_war` static helper in `simple_heuristic_ai.gd` gates `_collect_enemy_units`, `_collect_enemy_city_positions`, and `_enemy_within`; ~~defaults to War when no relation key (EA: all pairs start at war)~~ **[SUPERSEDED — canonical start state is PEACE per COMMUNICATIONS.md / p3-01]**. GUT tests in `test_simple_heuristic_ai_war_gate.gd` cover peace/war/friendly/missing-key cases. ## Non-goals diff --git a/.project/objectives/p3-16-ai-proactive-war-declaration.md b/.project/objectives/p3-16-ai-proactive-war-declaration.md new file mode 100644 index 00000000..598d9448 --- /dev/null +++ b/.project/objectives/p3-16-ai-proactive-war-declaration.md @@ -0,0 +1,130 @@ +--- +id: p3-16 +title: AI proactive war-declaration via the courier system +priority: p3 +status: partial +scope: game1 +owner: warcouncil +updated_at: 2026-06-23 +evidence: + - src/simulator/crates/mc-ai/src/tactical/mod.rs (decide_tactical_actions — no diplomacy step) + - src/simulator/crates/mc-ai/src/tactical/movement.rs (is_at_war gate + collect_enemy_city_positions) + - src/simulator/crates/mc-player-api/src/comms_dispatch.rs (dispatch_war_declaration — the war-dec entry point the AI must call) + - public/games/age-of-dwarves/docs/military/COMMUNICATIONS.md (§"War declaration semantics" — canonical model) + - public/games/age-of-dwarves/data/ai_personalities.json (aggression axis 1–10) +--- + +## Summary + +The canonical courier-diplomacy model (`COMMUNICATIONS.md` §"War declaration +semantics") assumes a player **dispatches a war-dec envelope** to enter war: the +**sender** enters `War` immediately on dispatch and its units can attack the same +turn; the **recipient** flips to `War` on delivery/interception; **defenders +always retaliate when struck** regardless of formal `RelationState`. Pairs start +at **peace** (`mc-player-api/src/projection.rs::project_tactical_relations` defaults +unset pairs to 0 = peace; `mc-player-api/src/comms_dispatch.rs` flips the shared +`RelationState` cell to `War` only on delivery). + +**The gap:** the AI has **no proactive war-declaration logic**. `mc-ai`'s tactical +entry point `decide_tactical_actions` (`src/simulator/crates/mc-ai/src/tactical/mod.rs`) +runs movement → combat → settle → production → citizens with **no diplomacy step**, +and there is **zero** `DeclareWar` / war-dec action anywhere in `mc-ai` (no +strategic action variant, no caller of `comms_dispatch::dispatch_war_declaration` +— the only non-test caller is the human dispatch path at +`mc-player-api/src/dispatch.rs:1605`). `mc-ai/src/diplomacy.rs` covers only the +p3-01 courier open-borders / shared-map offer/accept heuristics — not war. + +Consequence (observed in a 200-turn hotseat self-play): AIs never initiate war → +`collect_enemy_city_positions` (`tactical/movement.rs`) is always empty because it +is gated through `is_at_war` (movement.rs:366) → no `locked_target` is ever set → +military units never maneuver (48 successful moves across 200 turns). Because war +never comes in AI-vs-AI play, the five clan personalities +(`public/games/age-of-dwarves/data/ai_personalities.json`, `aggression` 1–10, +warmonger = 9) do **not** manifest distinct aggression: per +`mc-ai/src/tactical/thresholds.rs`, `aggression` only tunes combat posture **once +already at war**, so a difference that only fires after war-declaration never +fires at all. + +This objective adds the missing strategic decision step so the AI dispatches a +war-dec envelope through the courier system when conditions warrant, entering the +existing at-war combat path and making personality `aggression` actually drive +behavior. + +## Why partial (not done) + +The downstream consumption path (at-war combat, `collect_enemy_city_positions`, +`locked_target` maneuver, `aggression`-tuned combat posture) **already exists and +works** once a pair is at war — that half is shipped. What is missing is the +**decision-to-declare** and its dispatch through the courier system. No new code +has landed for the decision step yet; this file specs it. + +## Acceptance + +- [ ] **Strategic war-dec decision step.** Add a diplomacy decision step to the AI + turn so that, per turn, the AI evaluates each *contacted* rival (first-contact + gate per `COMMUNICATIONS.md` §4) and decides whether to declare war. The + decision MUST consider: first-contact established, military balance + (own vs. perceived enemy strength from PerceivedState, not ground truth), and + the clan's `aggression` axis (`ai_personalities.json`). War-dec is a + **strategic** move (it changes `RelationState`, not just a unit order), so the + decision belongs in the strategic layer (`mc-ai/src/mcts.rs` / + `evaluator.rs` action set, or a dedicated `mc-ai/src/diplomacy.rs` war-dec + function invoked from the strategic turn driver), NOT inside the per-unit + tactical movement loop. Mirror the existing courier-offer pattern in + `mc-ai/src/diplomacy.rs` (clan hard-rules + axis-driven fallback). +- [ ] **Dispatch through the courier system.** When the AI decides to declare war + it MUST route through `mc_player_api::comms_dispatch::dispatch_war_declaration(state, sender, target)` + — the same entry point the human war-dec action uses + (`mc-player-api/src/dispatch.rs:1605`). This makes the **sender** enter `War` + immediately on dispatch (sender units can attack that turn) and the + **recipient** flip on delivery/interception, exactly per + `COMMUNICATIONS.md` §"War declaration semantics". The AI MUST NOT directly + mutate the relation cell or bypass the envelope — that would break the + perceived-state / comm-tier asymmetry the courier model is built on. +- [ ] **Personality differentiation manifests.** With the decision step live, a + warmonger clan (`aggression` 9, e.g. Blackhammer) initiates war materially + earlier / more often than a low-aggression clan in AI-vs-AI play. Verify with a + self-play matchup harness showing distinct war-initiation turn distributions by + clan (compare to the p0-02 matchup-grid methodology). The 200-turn "48 moves / + no maneuver" symptom is gone: military units maneuver because + `collect_enemy_city_positions` is now non-empty once war is declared. +- [ ] **Tests headless.** `mc-ai` unit tests for the war-dec decision function + (clan × military-balance × first-contact cases, including the floor where a + low-aggression clan declines). A `mc-player-api` integration test that the AI + decision dispatches an envelope via `comms_dispatch` and the sender's + `RelationState` / `has_outbound_war` reflects war on the dispatch turn while the + recipient stays at peace until delivery. All green on apricot. + +## Code-fidelity cleanup (do alongside) + +`mc-ai/src/tactical/movement.rs::is_at_war` (lines 366–369) still carries a stale +comment — "default to war when a relation slot is missing … so the attack gate +stays open in fresh games where the diplomacy table has not been initialized" — +that mirrors the **superseded** p1-01 "all pairs start at war" model. It is +harmless today only because `project_tactical_relations` +(`mc-player-api/src/projection.rs:1312`) pre-fills the relations vec so no slot is +ever missing. Once the canonical peace-start model is fully wired, this fallback's +comment (and ideally the missing-slot → war default itself) should be corrected to +reflect peace-start semantics. **This is a `.rs` change — not part of this +docs-and-plan sync; flagged for the implementing specialist (warcouncil / mc-ai).** + +## Dependencies + +- `p1-01` (diplomacy-lite, ✅ — peace/war state + the at-war combat path the AI + enters; start-state semantics superseded by courier-diplomacy, see that file's + banner). +- `p3-01` (courier-diplomacy, ✅ game1-stretch — the war-dec envelope substrate + and `comms_dispatch` dispatch path). +- `COMMUNICATIONS.md` §"War declaration semantics" — canonical design this is + built to. +- Coordinates with `p0-02` (clan personalities) — this is what makes the + `aggression` axis manifest in AI-vs-AI war initiation. + +## Non-goals + +- Peace-making / treaty AI beyond declaring war (the AI's offer/accept of peace is + a separate decision; out of scope here). +- Alliances / coalitions / joint-attack coordination (Game 2). +- New `aggression`-axis tuning values — this objective wires the axis into a new + decision, it does not retune the personalities themselves (that is p0-02 / + p1-36 territory). diff --git a/.project/team-leads/warcouncil.md b/.project/team-leads/warcouncil.md index 82e5e0a6..1b867e13 100644 --- a/.project/team-leads/warcouncil.md +++ b/.project/team-leads/warcouncil.md @@ -18,6 +18,7 @@ objectives: - p1-29h-stateful-tactical-decisiveness - p1-29i-refound-suppression - p1-29j-autoplay-rust-action-application + - p3-16 --- ## Mandate diff --git a/public/games/age-of-dwarves/docs/military/COMMUNICATIONS.md b/public/games/age-of-dwarves/docs/military/COMMUNICATIONS.md index 45f82e50..b399fcf3 100644 --- a/public/games/age-of-dwarves/docs/military/COMMUNICATIONS.md +++ b/public/games/age-of-dwarves/docs/military/COMMUNICATIONS.md @@ -28,7 +28,7 @@ All three dispatch types produce the same payload shape: Envelope { sender, recipient, payload, route, dispatched_turn, eta_turn } ``` -Effects (war state, treaty signature, vision-share activation, intel-log entry) apply at envelope **arrival**, not dispatch. +Effects on the **recipient** (recipient-side war state, treaty signature, vision-share activation, intel-log entry) apply at envelope **arrival**, not dispatch. The one **sender-side** exception is the war declaration: the sender enters `War` the instant it dispatches a war-dec envelope, so its units can attack the same turn — see [§War declaration semantics](#war-declaration-semantics). All effects on the *recipient* (including the recipient's own flip to `War`) still gate on arrival/interception. ---