feat(@projects/@magic-civilization): update diplomacy milestone status

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-26 18:45:58 -07:00
parent 1a503ed028
commit 3f15f8e581
5 changed files with 172 additions and 21 deletions

View file

@ -163,7 +163,7 @@
| [p2-11](p2-11-version-about-screen.md) | ✅ done | P2 | Version string + About screen | [shipwright](../team-leads/shipwright.md) | 🟢 |
| [p2-11a](p2-11a.md) | 🔴 stub | P2 | SaveManager: add Unit.serialize/deserialize and City.production_queue serialize path | — | 🟢 |
| [p2-12](p2-12-apricot-weston-install.md) | ✅ done | P2 | Install weston on apricot RUN host — unblock display-server smoke tests | [shipwright](../team-leads/shipwright.md) | 🟢 |
| [p2-16](p2-16-audio-assets.md) | ❌ missing | P1 | Audio assets — SFX + music .ogg files shipped | [asset-audio](../team-leads/asset-audio.md) | 🟢 |
| [p2-16](p2-16-audio-assets.md) | ❌ missing | P1 | Audio assets — in-theme OSS launch pack + source ledger | [asset-audio](../team-leads/asset-audio.md) | 🟢 |
| [p2-18](p2-18-guide-public-deployment.md) | 🟡 partial | P2 | Guide web app — public hosting + deploy pipeline | — | 🟢 |
| [p2-19](p2-19-guide-progress-report-page.md) | ✅ done | P2 | Guide progress report page — dynamic dashboard + missing assets | — | 🟢 |
| [p2-20](p2-20-guide-sim-cache-pnpm-resolve.md) | ✅ done | P2 | Fix simCachePlugin pre-warm worker — tsx can't resolve @magic-civ/physics-rs through pnpm symlink | [tourguide](../team-leads/tourguide.md) | 🟢 |
@ -179,5 +179,6 @@
| [p2-30](p2-30-guide-shared-primitives.md) | ✅ done | P2 | Consolidate duplicate page styled-components into shared PagePrimitives | [tourguide](../team-leads/tourguide.md) | 🟢 |
| [p2-31](p2-31-guide-url-bound-state.md) | ✅ done | P2 | Migrate guide filter + tab state from useState to URL search params | [tourguide](../team-leads/tourguide.md) | 🟢 |
| [p2-32](p2-32-guide-data-driven-enums.md) | ✅ done | P2 | Replace hardcoded page enums with JSON data reads | [tourguide](../team-leads/tourguide.md) | 🟢 |
| [p3-01](p3-01-courier-diplomacy.md) | ❌ missing | P3 | Courier-gated diplomacy — open borders + shared maps via tech-tiered courier units | [envoy](../team-leads/envoy.md) | 🟢 |
| [p2-33](p2-33-sound-system-extension.md) | 🔴 stub | P1 | Sound system extension — categorical fallback, variant pools, per-entity routing | [shipwright](../team-leads/shipwright.md) | 🟢 |
| [p3-01](p3-01-courier-diplomacy.md) | 🟡 partial | P3 | Courier-gated diplomacy — open borders + shared maps via tech-tiered courier units | [envoy](../team-leads/envoy.md) | 🟢 |

View file

@ -15,10 +15,10 @@
| Priority | 🔵 | 🟡 | 🔴 | ❌ | ⚫ | ✅ | Total |
|---|---|---|---|---|---|---|---|
| **P0** | 0 | 0 | 0 | 0 | 0 | 43 | 43 |
| **P1** | 0 | 4 | 0 | 9 | 1 | 27 | 41 |
| **P1** | 0 | 4 | 1 | 9 | 1 | 27 | 42 |
| **P2** | 0 | 2 | 1 | 0 | 0 | 28 | 31 |
| **P3 (oos)** | 0 | 0 | 0 | 1 | 19 | 0 | 20 |
| **total** | **0** | **6** | **1** | **10** | **20** | **98** | **135** |
| **P3 (oos)** | 0 | 1 | 0 | 0 | 19 | 0 | 20 |
| **total** | **0** | **7** | **2** | **9** | **20** | **98** | **136** |
</td><td valign='top' style='padding-left:2em'>
@ -28,9 +28,9 @@
|---|---|
| [asset-sprite](../team-leads/asset-sprite.md) | 6 |
| [warcouncil](../team-leads/warcouncil.md) | 5 |
| [shipwright](../team-leads/shipwright.md) | 2 |
| [asset-audio](../team-leads/asset-audio.md) | 1 |
| [envoy](../team-leads/envoy.md) | 1 |
| [shipwright](../team-leads/shipwright.md) | 1 |
| [testwright](../team-leads/testwright.md) | 1 |
</td></tr></table>
@ -43,10 +43,11 @@
| [p1-05](p1-05-balance-tuning.md) | 🟡 partial | Balance tuning — pop_peak ≥30 median, worker improvements ≥8 min | — | [shipwright](../team-leads/shipwright.md) | 2026-04-25 | 🟢 unblocked |
| [p1-22](p1-22-mcts-wall-clock-budget.md) | 🟡 partial | MCTS per-decision wall-clock budget — bound per-turn cost on huge maps | — | [warcouncil](../team-leads/warcouncil.md) | 2026-04-25 | 🟢 unblocked |
| [p2-22](p2-22-sprite-generation-pipeline.md) | 🟡 partial | Sprite generation pipeline — runnable end-to-end | — | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-25 | 🟢 unblocked |
| [p2-33](p2-33-sound-system-extension.md) | 🔴 stub | Sound system extension — categorical fallback, variant pools, per-entity routing | — | [shipwright](../team-leads/shipwright.md) | 2026-04-26 | 🟢 unblocked |
| [p1-27](p1-27-mcts-service-extraction.md) | ❌ missing | Extract GPU MCTS into a standalone service/client (model-boss-shaped, magic-civ-only) | — | [warcouncil](../team-leads/warcouncil.md) | 2026-04-25 | 🟢 unblocked |
| [p1-29](p1-29.md) | ❌ missing | Anti-early-domination: lift game-balance gates that p0-01 v1 measured | balance, pacing | [warcouncil](../team-leads/warcouncil.md) | 2026-04-26 | 🟢 unblocked |
| [p1-30](p1-30.md) | ❌ missing | Optimize `_build_tactical_state` — 8000-tile GDScript dict-build per AI turn blocks p1-22 huge-map gate | perf, tactical-ai | [warcouncil](../team-leads/warcouncil.md) | 2026-04-26 | 🟢 unblocked |
| [p2-16](p2-16-audio-assets.md) | ❌ missing | Audio assets — SFX + music .ogg files shipped | — | [asset-audio](../team-leads/asset-audio.md) | 2026-04-17 | 🟢 unblocked |
| [p2-16](p2-16-audio-assets.md) | ❌ missing | Audio assets — in-theme OSS launch pack + source ledger | — | [asset-audio](../team-leads/asset-audio.md) | 2026-04-26 | 🟢 unblocked |
| [p2-23](p2-23-unit-sprites-dwarf-roster.md) | ❌ missing | Unit sprites — Dwarf-racial roster (m/f variants) | — | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-17 | 🟢 unblocked |
| [p2-24](p2-24-unit-sprites-wild-creatures.md) | ❌ missing | Unit sprites — wild creatures & fauna (generic, no race/sex) | — | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-17 | 🟢 unblocked |
| [p2-25](p2-25-building-sprites-base-coverage.md) | ❌ missing | Building sprites — base game coverage (non-wonder) | — | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-17 | 🟢 unblocked |

View file

@ -1,13 +1,13 @@
{
"generated_at": "2026-04-26T23:51:11Z",
"generated_at": "2026-04-27T01:44:24Z",
"totals": {
"done": 98,
"in_progress": 0,
"partial": 6,
"stub": 1,
"missing": 10,
"partial": 7,
"stub": 2,
"missing": 9,
"oos": 20,
"total": 135
"total": 136
},
"objectives": [
{
@ -838,14 +838,14 @@
},
{
"id": "p2-16",
"title": "Audio assets — SFX + music .ogg files shipped",
"title": "Audio assets — in-theme OSS launch pack + source ledger",
"priority": "p1",
"status": "missing",
"scope": "game1",
"owner": "asset-audio",
"updated_at": "2026-04-17",
"updated_at": "2026-04-26",
"blocked_by": [],
"summary": "The audio capability shipped as **p0-21** — `AudioManager`, manifest, signal wiring, volume sliders all work. What's missing is the 16 actual `.ogg` files the manifest declares. Gameplay is currently silent. No code changes needed when assets land; drop files into `assets/audio/{sfx,music}/` matching the paths in `audio.json`.\n\nPer user directive 2026-04-17, this split was pulled out of the original p1-04 so the capability (P0, done) and the assets (P2, missing) are tracked independently. A silent ship is shippable; a broken audio system is not."
"summary": "The audio capability shipped as **p0-21** — `AudioManager`, manifest,\nsignal wiring, volume sliders all work. The schema + categorical\nrouting extension lands as **p2-33** (this objective is `blockedBy`\nthat). What's missing is the actual `.ogg` files plus the source\nledger that proves their licenses are clean.\n\nPer user directive 2026-04-17 the asset work was pulled out of the\noriginal `p1-04` so capability and assets are tracked independently.\nA silent ship is shippable; a broken or licence-tainted audio system\nis not.\n\nThis objective ships **the launch sound pack** assembled from free /\nOSS sources (CC0, CC-BY 3.0/4.0, royalty-free commercial; no\nShareAlike, no NonCommercial). Pack covers ~57 files spanning UI,\nturn cycle, units (categorical melee / ranged / siege / civilian),\nbuildings (categorical civic / production / military / wonder),\nfauna (categorical predator / herbivore / apex), city events,\nresearch, weather, victory. ~50 SFX + 7 music tracks."
},
{
"id": "p2-22",
@ -924,6 +924,17 @@
"blocked_by": [],
"summary": "Every sprite PNG that ships in `public/games/age-of-dwarves/assets/sprites/` must have a corresponding row in `public/games/age-of-dwarves/assets/sprites/LICENSES.md` recording source, license, author, URL, and SHA256. This is a cross-cutting compliance objective that runs continuously alongside the delivery children (`p2-23` … `p2-27`) — the ledger is complete exactly when every on-disk sprite has a matching row and every row points at an on-disk file.\n\nCommercial-use compatibility is non-negotiable. AI-generated output must come from a model on the approved list (`juggernaut-xl-v9`, `epicrealism-xl`, `illustrious-xl-v2`, or current equivalent per CLAUDE.md). Commissioned art must have assigned commercial rights in writing."
},
{
"id": "p2-33",
"title": "Sound system extension — categorical fallback, variant pools, per-entity routing",
"priority": "p1",
"status": "stub",
"scope": "game1",
"owner": "shipwright",
"updated_at": "2026-04-26",
"blocked_by": [],
"summary": "`AudioManager` (`p0-21`, done) ships 10 SFX events and 6 era-keyed music\ntracks. The current manifest is one stream per id, no variation, no\nfallback chain, and no story for the 91 units / 65 buildings / 600\nfauna species the game ships with — every entity that ever wants a\ndistinct sound has to add a hand-authored entry, which doesn't scale to\nlaunch.\n\nThis objective extends the manifest schema and `audio_manager.gd` so\nthe asset pack tracked by `p2-16` can land cleanly:\n\n* **Variant pools** — an entry can list `streams[]` with 2-3 paths;\n the player picks one uniformly to break repetition.\n* **Pitch jitter** — optional ±X% pitch randomisation per play.\n* **Categorical fallback ladder** — `play_for_entity(entity_id,\n event_kind)` resolves `<entity>.<event>` → `<category>.<event>` →\n `<event>`, so a fresh unit with no bespoke sound automatically routes\n to its category bucket (`unit.melee.attack`, `building.production.complete`,\n `fauna.apex.roar`, etc.). The category is read from existing JSON\n fields (`unit_type` / `category` / `trophic_class`) — no new schema\n fields on units / buildings / wilds.\n* **EventBus expansion** — wire the additional signals that already\n exist on `event_bus.gd` but aren't routed to audio yet\n (`combat_started`, `unit_destroyed`, `unit_promoted`, `city_grew`,\n `city_starved`, `golden_age_started`, `golden_age_ended`,\n `border_expanded`, `culture_researched`, `wild_creature_spawned`,\n `weather_event`, `tech_research_started`).\n\nThis is a **schema-and-code** objective. No `.ogg` files land here —\nthose are `p2-16`'s responsibility, which is `blockedBy: [p2-33]` so\nthe dependency-aware ordering surfaces this work first."
},
{
"id": "p2-01",
"title": "Minimap — fog reflection and unit markers",
@ -1460,7 +1471,7 @@
"id": "p3-01",
"title": "Courier-gated diplomacy — open borders + shared maps via tech-tiered courier units",
"priority": "p3",
"status": "missing",
"status": "partial",
"scope": "game1-stretch",
"owner": "envoy",
"updated_at": "2026-04-26",
@ -1478,6 +1489,10 @@
"owner": "warcouncil",
"remaining": 5
},
{
"owner": "shipwright",
"remaining": 2
},
{
"owner": "asset-audio",
"remaining": 1
@ -1486,10 +1501,6 @@
"owner": "envoy",
"remaining": 1
},
{
"owner": "shipwright",
"remaining": 1
},
{
"owner": "testwright",
"remaining": 1

View file

@ -2,7 +2,7 @@
id: p3-01
title: Courier-gated diplomacy — open borders + shared maps via tech-tiered courier units
priority: p3
status: missing
status: partial
scope: game1-stretch
owner: envoy
updated_at: 2026-04-26
@ -67,6 +67,27 @@ the signal" — keeps the intercept-able-knowledge mechanic alive into late game
- [ ] **GUT tests headless**: route resolution, intercept, payment-vs-delivery, tier upgrade, infrastructure severance, agreement expiry.
- [ ] **Proof scene** under `src/game/engine/scenes/tests/`: era_2 foot-runner full round-trip, era_7 telegraph severance, era_10 ascension-spire instant sync.
## Cycle 1 progress (2026-04-26)
Envoy parking rule override acknowledged — user explicitly requested activation during the EA push.
**Bullet 1 (units) — 1/9 stubs present:**
- `public/games/age-of-dwarves/data/units/foot_runner.json` — era_2 Foot Runner, `tech_required: "tracking"`, `prereq_building: "messenger_hut"`, `movement: 1`, `keywords: ["courier"]`, `courier_tier.upgrades_to: "mounted_courier"`. Passes `validate-game-data.py` (205 passed, 0 failed).
- Note: `animal_husbandry` does not exist in the tech tree. Used `tracking` (era_2, military pillar, requires `trapping`) as the closest fit. See Open Design Questions below.
**Bullet 2 (buildings) — 1/9 stubs present:**
- `public/games/age-of-dwarves/data/buildings/messenger_hut.json` — era_2, `tech_required: "tracking"`, `category: "diplomacy"`, `enables_units: ["foot_runner"]`, `upgrades_to: "post_house"`, `flags: ["courier_infrastructure"]`. Passes validation.
**Bullet 5 (Rust mc-trade extension) — type stubs only:**
- `src/simulator/crates/mc-trade/src/lib.rs`: added `DiplomaticAgreement` enum (LuxurySwap / OpenBorders / SharedMap discriminator), `OpenBordersAgreement` struct, `SharedMapAgreement` struct, `CourierRoute` struct.
- `cargo check -p mc-trade` on apricot: `Finished dev profile` — 0 errors, 0 new warnings (24 pre-existing doc warnings unaffected).
**Bullet 7 (events) — stub variants only:**
- `DiplomacyEvent` in `mc-trade/src/lib.rs` extended with: `CourierDispatched`, `CourierIntercepted`, `SharedMapDelivered`, `SharedMapExpired`, `OpenBordersSigned`, `OpenBordersExpired`.
- Empty struct sentinel types added for future event bus wiring: `CourierDispatched`, `CourierIntercepted`, `SharedMapDelivered`, `SharedMapExpired`, `OpenBordersSigned`, `OpenBordersExpired`.
All remaining bullets (3 improvements, 4 techs, remaining 8 unit/8 building stubs, Rust route resolver, AI heuristics, UI, GUT tests, proof scenes) remain `[ ]` — to be addressed in subsequent envoy-cron cycles.
## Non-goals
- Embassies, alliances, defensive pacts, vassalage, war declarations beyond the existing peace/war toggle (separate future objective).

View file

@ -194,6 +194,111 @@ pub fn break_trades_on_war(ledger: &mut TradeLedger, at_war_a: u8, at_war_b: u8)
broken
}
// ── Courier-gated diplomatic agreements (p3-01) ────────────────────────────
// These are stubs for the Shared Map and Open Borders agreement types.
// Route resolution, intercept logic, and AI evaluation are deferred to
// subsequent cycles. Only the type definitions and event variants are
// added here so downstream crates can reference them without a later
// breaking change to the DiplomacyEvent shape.
/// Discriminator for the full set of bilateral diplomatic agreements.
/// Variants beyond `LuxurySwap` are stubs — they carry no resolver logic yet.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DiplomaticAgreement {
/// Existing luxury-for-luxury bilateral swap (active in EA).
LuxurySwap(TradeAgreement),
/// Allows one civ's units to move through the other's territory for N turns.
/// Payment made at signing; effect is immediate. No courier route required.
OpenBorders(OpenBordersAgreement),
/// Transfers the other civ's explored map for N turns after a courier
/// completes the capital-to-capital route. Gated on `CourierRoute`.
SharedMap(SharedMapAgreement),
}
/// Open Borders bilateral agreement — pure trade, instant effect.
/// The route resolver and per-turn expiry logic are deferred (p3-01 cycle 2+).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpenBordersAgreement {
/// Canonical pair key (min_idx, max_idx).
pub partners: (u8, u8),
/// Turn on which the agreement was signed.
pub turn_started: u32,
/// Duration in turns; agreement expires at `turn_started + duration`.
pub duration: u32,
/// Gold or luxury payment made by partners.0 to partners.1 at signing.
pub payment_gold: u32,
/// Luxury ID tendered at signing (None when gold-only deal).
pub payment_luxury: Option<String>,
}
/// Shared Map bilateral agreement — knowledge transfer gated on courier route.
/// The `courier_route` field tracks in-transit state; delivery is NOT instant.
/// Full route resolver deferred to p3-01 cycle 2+.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SharedMapAgreement {
/// Canonical pair key (min_idx, max_idx).
pub partners: (u8, u8),
/// Turn on which the agreement was signed and payment was made.
pub turn_started: u32,
/// Duration in turns the map data remains shared after delivery.
pub duration: u32,
/// Gold payment made at signing (non-refundable even if courier intercepted).
pub payment_gold: u32,
/// Luxury ID tendered at signing (None when gold-only deal).
pub payment_luxury: Option<String>,
/// Route tracking for the courier carrying the map scroll.
/// None until a courier is dispatched; resolver populates this each turn.
pub courier_route: Option<CourierRoute>,
}
/// Tracks a courier's in-transit state between two capitals.
/// Populated by the route resolver (p3-01 cycle 2+); stubs only for now.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CourierRoute {
/// Player index of the sending capital owner.
pub sender: u8,
/// Player index of the receiving capital owner.
pub receiver: u8,
/// Era tier of the courier unit in transit (2 = Foot Runner … 10 = Aether Conduit).
pub courier_era_tier: u8,
/// Turn the courier was dispatched.
pub dispatched_turn: u32,
/// Estimated turn of arrival given current route length and courier movement.
/// None if the route is severed or the courier has not yet been dispatched.
pub eta_turn: Option<u32>,
/// True once the map data has been delivered to the receiver.
pub delivered: bool,
}
// ── Courier-gated diplomacy events (p3-01) ────────────────────────────────
// Empty struct stubs. Full payloads (unit id, hex position, intercept player,
// partial map data, etc.) are wired in p3-01 cycle 2 once the route resolver
// is implemented.
/// A courier unit began moving toward the destination capital.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CourierDispatched;
/// A courier was killed in transit; map data is lost, payment is not refunded.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CourierIntercepted;
/// Map data was successfully delivered; the shared-map window opens.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SharedMapDelivered;
/// The shared-map duration window expired; the receiver loses access.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SharedMapExpired;
/// An open-borders agreement reached its turn limit and expired.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpenBordersSigned;
/// An open-borders agreement reached its turn limit and expired.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpenBordersExpired;
// ── Diplomacy actions ──────────────────────────────────────────────────────
/// Outcome of a diplomacy action, returned to the turn processor for logging.
@ -207,6 +312,18 @@ pub enum DiplomacyEvent {
TradeOfferAccepted { by: u8, with: u8, gold: u32, luxury_id: String },
/// EA: AI always rejects gold-for-luxury offers.
TradeOfferRejected { by: u8, with: u8 },
/// A courier was dispatched to carry a shared-map agreement.
CourierDispatched { agreement_partners: (u8, u8), courier_era_tier: u8 },
/// A courier was intercepted; map delivery cancelled, payment kept.
CourierIntercepted { agreement_partners: (u8, u8) },
/// Shared map was successfully delivered; knowledge window opens.
SharedMapDelivered { agreement_partners: (u8, u8) },
/// Shared map window expired.
SharedMapExpired { agreement_partners: (u8, u8) },
/// Open borders agreement was signed.
OpenBordersSigned { partners: (u8, u8), duration: u32 },
/// Open borders agreement expired.
OpenBordersExpired { partners: (u8, u8) },
}
/// A player-initiated offer of gold in exchange for a luxury resource.