diff --git a/.project/objectives/DASHBOARD_CATEGORIES.md b/.project/objectives/DASHBOARD_CATEGORIES.md index 28b098dc..d598664b 100644 --- a/.project/objectives/DASHBOARD_CATEGORIES.md +++ b/.project/objectives/DASHBOARD_CATEGORIES.md @@ -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) | 🟢 | diff --git a/.project/objectives/README.md b/.project/objectives/README.md index 15f45070..01b71149 100644 --- a/.project/objectives/README.md +++ b/.project/objectives/README.md @@ -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** | @@ -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 | @@ -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 | diff --git a/.project/objectives/objectives.json b/.project/objectives/objectives.json index b0d6c20b..f7fcabd3 100644 --- a/.project/objectives/objectives.json +++ b/.project/objectives/objectives.json @@ -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 `.` → `.` →\n ``, 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 diff --git a/.project/objectives/p3-01-courier-diplomacy.md b/.project/objectives/p3-01-courier-diplomacy.md index 0795b389..e51f3867 100644 --- a/.project/objectives/p3-01-courier-diplomacy.md +++ b/.project/objectives/p3-01-courier-diplomacy.md @@ -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). diff --git a/src/simulator/crates/mc-trade/src/lib.rs b/src/simulator/crates/mc-trade/src/lib.rs index d536410a..86fcacb7 100644 --- a/src/simulator/crates/mc-trade/src/lib.rs +++ b/src/simulator/crates/mc-trade/src/lib.rs @@ -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, +} + +/// 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, + /// Route tracking for the courier carrying the map scroll. + /// None until a courier is dispatched; resolver populates this each turn. + pub courier_route: Option, +} + +/// 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, + /// 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.