fix(@projects/@magic-civilization): 🐛 resolve phantom unlock in chronicle_keeping culture

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-28 01:44:26 -04:00
parent 902a644737
commit ad99dd9247
3 changed files with 125 additions and 3 deletions

View file

@ -0,0 +1,59 @@
---
id: p2-39
title: Resolve `chronicle_hall` phantom unlock in `chronicle_keeping` culture tech
priority: p2
status: open
scope: game1
updated_at: 2026-04-27
evidence:
- public/resources/culture/oral_tradition.json (line 96 — `chronicle_keeping.unlocks.buildings = ["chronicle_hall"]`)
- public/games/age-of-dwarves/data/buildings/mundane_wonders.json (line 102115 — `bardic_circle`, the actual `chronicle_keeping`-gated wonder)
- .project/objectives/p3-01-courier-diplomacy.md (cycle 3 audit that surfaced the phantom)
---
## Summary
The `chronicle_keeping` culture tech (era_4 oral_tradition pillar) declares
`unlocks.buildings = ["chronicle_hall"]`, but no `chronicle_hall` building file
exists anywhere in `public/resources/buildings/` or
`public/games/age-of-dwarves/data/buildings/`. Surfaced by the p3-01 cycle 3
audit when the era_4 Rune Scribe culture path tried to extend
`chronicle_hall.enables_units` and discovered it was a phantom.
The actual building gated on `culture_required: "chronicle_keeping"` is
`bardic_circle` (mundane_wonders.json:102 — tier 4, era 2 wonder). The phantom
is a stale unlock string left over from an earlier design.
This is a pre-existing data integrity bug, separate from p3-01's courier scope.
## Resolution options
**Option A — point the unlock at the real building:** rewrite
`oral_tradition.json:96` from `"chronicle_hall"` to `"bardic_circle"`. Smallest
diff. Truthful given current data.
**Option B — author a real `chronicle_hall` building:** an era_4 normal (non-wonder)
culture-tech-unlocked institution. Distinct from `bardic_circle` (wonder, tier 4).
Bigger diff. Adds a new strategic institution and unblocks the era_4 Rune Scribe
culture path that p3-01 abandoned.
**Default to Option A** unless explicitly requested otherwise. Option B becomes
viable only if the design wants culture-side courier production at era_4, which
is currently out of scope.
## Acceptance criteria
- [ ] Decide A vs B (default A).
- [ ] If A: edit `public/resources/culture/oral_tradition.json:96` `"chronicle_hall"``"bardic_circle"`. Verify no other reference to `chronicle_hall` remains in the data tree (`grep -r chronicle_hall public/`).
- [ ] If B: author `public/resources/buildings/chronicle_hall.json` conforming to `building.schema.json`. Add to `public/games/age-of-dwarves/data/buildings/manifest.json`.
- [ ] `python3 tools/validate-game-data.py` → 0 failed.
- [ ] Run dashboard regen.
## Non-goals
- Authoring or rebalancing the era_4 Rune Scribe culture path (separate scope inside p3-01 if pursued later).
- Audit of other phantom unlocks across the culture trees (out of scope; address one at a time as discovered).
## Dependencies
- None. Pure data integrity fix.

View file

@ -88,13 +88,13 @@ flavor stays Game 3 (Elves).
## Acceptance criteria
- [ ] **Data pack — units (8 remaining + 1 done)**: 9 new unit JSONs in `public/games/age-of-dwarves/data/units/` matching the Dwarven ladder above: `foot_runner` ✓ (cycle 1), `tunnel_runner`, `rune_scribe`, `hold_courier`, `beacon_bearer`, `steam_messenger`, `resonance_telegrapher`, `hold_network_warden`. (No era_10 unit — Adamantine Echo is wonder-only.) Each declares its era, prerequisite tech, prerequisite building, movement speed, intercept rules, and upgrade-from chain.
- [x] **Data pack — units** (cycles 13, **8 unit files** total — locked design has no era_10 unit since Adamantine Echo is wonder-only): all 8 authored in `public/games/age-of-dwarves/data/units/` matching the Dwarven ladder: `foot_runner`, `tunnel_runner`, `rune_scribe`, `hold_courier`, `beacon_bearer`, `steam_messenger`, `resonance_telegrapher`, `hold_network_warden`. Each declares era, `tech_required`, movement speed, `courier_tier.delay_class`, and `upgrades_to` chain. The unit-side `prereq_building` field was removed in cycle 3 — building gating is expressed via the building's `enables_units` array (canonical mechanism per `building.schema.json`).
- [ ] **Data pack — buildings (revised 2026-04-27, audited 2026-04-27)**: 6 new building files (the linear hub chain + era_10 wonder): `messenger_hut`, `hold_post`, `steam_forgery_annex`, `resonance_chamber`, `hold_network_citadel`, `adamantine_echo` — all authored cycle 1/2. Plus 1 existing non-wonder building extended with `enables_units` for the only legitimate culture path: `gathering_hall` (era_3 / Tunnel Runner). Era 4-9 culture paths abandoned after audit: every era_5+ culture-tech-unlocked building is `wonder_type: "world"`, and `chronicle_hall` (era_4) is a phantom (no file exists, only referenced in culture-tech unlocks).
- [ ] **Data pack — improvements (revised 2026-04-27)**: 5 improvement files in `public/resources/improvements/` (canonical store) — `tunnel` (era_3), `hold_road` (era_5, upgrade of `road`), `steam_track` (era_7, severable), `resonance_wire` (era_8, severable), and **`beacon_tower`** (era_6, killable hilltop structure built by engineer, provides LOS chain — relocated from buildings/). Cycle 2 wrote the first 4; beacon_tower needs c3 to relocate + restructure to improvement schema.
- [ ] **Data pack — techs**: 3 new prereq tech JSONs authored — `tunnel_paths` (era_3, ecology pillar), `beacon_chain` (era_6, military pillar), `rune_resonance` (era_8, metallurgy + runelore crossover). The other 6 tier prereqs (`tracking`, `runelore`, `dwarf_heritage`, `steam_forging`, `combined_arms`, `adamantine_forging`) all exist in the current tech tree — no work needed.
- [x] **Rust — `mc-trade` extension** (cycle 4): `DiplomaticAgreement` enum + `OpenBordersAgreement` + `SharedMapAgreement` types landed; `TradeLedger` migrated to `Vec<DiplomaticAgreement>` with `next_agreement_id` counter; existing luxury-swap call sites projected through the `LuxurySwap` variant.
- [ ] **Rust — courier route resolver** (cycle 4 partial): `step_shared_map_agreements` driver + `CourierMapView` trait scaffold + `CourierRoute` state struct + 3 lifecycle integration tests passing. Remaining: actual hex-graph pathfinding, per-tier movement-speed table, severable-improvement integration.
- [x] **Rust — events** (cycle 4): six payload-bearing event structs (`CourierDispatched`, `CourierIntercepted`, `MapDelivered`, `OpenBordersSigned`, `OpenBordersExpired`, `SharedMapExpired`), each carrying `agreement_id`. The originally-listed `TelegraphLinePillaged`/`SemaphoreTowerDestroyed`/`WirelessJammed` were Earth-flavored and superseded by the Dwarven ladder; severable-improvement events fold into the route resolver's intercept path instead.
- [ ] **Rust — courier route resolver scaffold** (cycle 4 partial — physics layer split into [p3-03](p3-03-courier-route-resolver.md)): `step_shared_map_agreements` driver + `CourierMapView` trait + `CourierRoute` state struct + 3 lifecycle integration tests using `MockMap` fixture all landed in p3-01 cycle 4. The remaining work — real hex pathfinding, per-tier movement-speed table, severable-improvement integration, Hold-Network reroute, Adamantine Echo instant sync — moved to **p3-03** so it can land independently. Bullet 6 flips to ✓ once p3-03 closes.
- [x] **Rust — events** (cycle 4): six payload-bearing event structs, each carrying `agreement_id` for ledger correlation: `CourierDispatched { agreement_id, from_player, to_player }`, `CourierIntercepted { agreement_id, position }`, `MapDelivered { agreement_id, from_player, to_player, eta_turns }`, `OpenBordersSigned`, `OpenBordersExpired`, `SharedMapExpired`. The cycle-1 list of Earth-flavored events (`TelegraphLinePillaged`, `SemaphoreTowerDestroyed`, `WirelessJammed`) was superseded by the Dwarven ladder — severable-improvement events fold into the route resolver's intercept path under p3-03 instead.
- [ ] **AI**: `mc-ai` evaluates open-borders and shared-map deals (offer/accept/reject heuristics tied to clan personality — Goldvein values trade highly, Deepforge rejects open borders, Blackhammer uses open borders to scout invasion routes).
- [ ] **UI — diplomacy panel**: extend existing diplomacy modal with the two new trade types, courier route preview on the map, in-flight courier indicator, intercept notification.
- [ ] **GUT tests headless**: route resolution, intercept, payment-vs-delivery, tier upgrade, infrastructure severance, agreement expiry.

View file

@ -0,0 +1,63 @@
---
id: p3-03
title: Courier route resolver — real hex pathfinding, per-tier movement, severable infrastructure
priority: p3
status: open
scope: game1-stretch
owner: envoy
updated_at: 2026-04-27
evidence:
- .project/objectives/p3-01-courier-diplomacy.md (parent — bullet 6 split out into this objective)
- src/simulator/crates/mc-trade/src/lib.rs (`CourierMapView` trait + `CourierRoute` struct + `step_shared_map_agreements`, scaffold landed in p3-01 cycle 4)
- src/simulator/crates/mc-trade/tests/courier_lifecycle.rs (3 lifecycle integration tests using `MockMap`)
- public/games/age-of-dwarves/data/units/{foot_runner,…,hold_network_warden}.json (8 courier units with `courier_tier` metadata)
- public/resources/improvements/{tunnel,hold_road,steam_track,resonance_wire,beacon_tower}.json (route infrastructure, `severable` flagged on steam_track + resonance_wire)
---
## Summary
p3-01 cycle 4 landed the **types** for courier-gated diplomacy
(`DiplomaticAgreement` enum, `OpenBordersAgreement`, `SharedMapAgreement`,
`CourierRoute`, `CourierMapView` trait, `step_shared_map_agreements` driver, six
event payloads). It also landed three lifecycle integration tests against a
`MockMap` fixture that hard-codes intercept probability.
This objective owns the **physics layer** that lets those types resolve against
the actual game world: hex pathfinding from sender capital to receiver capital,
per-tier movement-speed table feeding ETA calculations, and integration with
severable improvements (Steam Track, Resonance Wire, Beacon Tower) so that a
mid-route pillage actually intercepts the courier.
Splitting this out of p3-01 lets the parent objective close at "data + types +
lifecycle" once AI/UI/tests/proof scenes land, while the route-resolver work
stays its own bounded chunk of mc-trade ↔ mc-map glue.
## Acceptance criteria
- [ ] **Real `CourierMapView` impl in mc-turn (or new `mc-courier` crate)**: implements the trait against the real `mc-map` hex grid + improvements layer. No fixture intercept probabilities — intercept must be deterministic from world state.
- [ ] **Hex pathfinding sender_capital → receiver_capital**: A* or Dijkstra over the courier's allowed terrain (Foot Runner avoids mountains, Tunnel Runner prefers tunnels, Steam Messenger requires Steam Track, Resonance Telegrapher requires Resonance Wire, etc.). Path stored in `CourierRoute` for replay + UI overlay.
- [ ] **Per-tier movement-speed table**: lookup keyed by `courier_tier.delay_class` from each unit JSON. Step function consumes movement points per turn against path length to produce ETA + per-turn position update.
- [ ] **Severable-improvement integration**: pillaging a Steam Track / Resonance Wire / Beacon Tower hex on a courier's planned route emits `CourierIntercepted` (route severed) on the next step. Integration test: courier in transit, pillage event fires, intercept resolves, payment retained.
- [ ] **Hold-Network mesh re-route (era_9+)**: when sender + receiver both control a Hold-Network Citadel, severed links auto re-route through the nearest alternate path within X hexes. Failing that, the intercept resolves normally.
- [ ] **Adamantine Echo instant sync (era_10)**: when both sender + receiver have built the Adamantine Echo wonder, shared-map deals deliver next turn regardless of physical route. Encodes the "wonder collapses delay to zero" mechanic from the locked design table.
- [ ] **Per-turn `CourierDispatched`/`MapDelivered` events emitted with real positions**: not the fixture stub. Position field reflects the hex the courier currently occupies.
- [ ] **Tests**:
- Real-map pathfinding: foot_runner from one capital to another, ETA matches movement table.
- Severance: pillage Steam Track mid-route → intercept on next step.
- Hold-Network reroute: severed Steam Track with two Hold-Network Citadels → reroute, no intercept.
- Adamantine Echo: agreement delivered next turn even with no intervening infrastructure.
- Tier upgrade: re-running the same agreement after tier upgrade shrinks ETA.
- [ ] **Updated p3-01 bullet 6**: link this objective and flip 6 to ✓ once these criteria all close.
## Non-goals
- AI heuristics for courier dispatch (p3-01 bullet 8, separate cycle).
- UI courier overlay on the diplomacy map (p3-01 bullet 9).
- Proof scenes (p3-01 bullet 11).
- Per-clan personality tuning of intercept aggression — stays in mc-ai.
## Dependencies
- **Inputs:** p3-01 cycle 4 types + driver (already landed). `mc-map` hex grid + improvements layer (existing). Per-tier movement values from the 8 courier unit JSONs (already authored).
- **Blocks on:** none — all prerequisites are landed.
- **Enables:** p3-01 bullet 6 closure; p3-01 bullets 8/9/10/11 can land in parallel against this objective's API.