38 KiB
| id | title | priority | status | scope | owner | updated_at | evidence | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| p3-01 | Courier-gated diplomacy — open borders + shared maps via tech-tiered courier units | p3 | done | game1-stretch | envoy | 2026-04-29 |
|
Summary
Game 1 ships diplomacy-lite: peace/war toggle plus a single bilateral luxury↔gold
trade action (mc-trade). This objective expands the diplomatic surface with two
trade options gated on physical infrastructure rather than instant agreement, so
information itself becomes a strategic resource that decays with distance and tech:
-
Open borders — pay luxury or gold for the right to move units through another civ's territory for N turns. Instant effect; pure trade.
-
Shared map — pay luxury or gold for the other civ's explored map for N turns. Not instant: the deal is gated on a courier link between capitals. Knowledge propagates at the courier's movement speed; the courier is killable mid-route (intercept = no map delivered, payment already made). The Courier unit family has tech-gated upgrade tiers, one per era from era_2 onward; later tiers shrink the delay window and shift the intercept surface from killing-the-unit to severing-the-infrastructure.
This is scope: game1-stretch — Game 1's stated scope is "diplomacy-lite", so this objective is post-Early-Access content unless explicitly pulled forward.
Courier tier ladder (Dwarven flavor — one per era, era_2 → era_10)
Locked 2026-04-26 after Dwarven-tech-tree audit. Names + prereq mappings below respect the Dwarven thematic (subterranean, Norse, runes, holds, steam-forging — no horses, no carrier birds, no Earth-styled industrial telecom).
Locked design (revised 2026-04-27 after schema audit)
Mechanism facts grounded in the actual project:
unit.schema.jsonhas noculture_requiredfield — units gate ontech_requiredonly.building.schema.jsonhas bothtech_requiredandculture_required. Existing wonders inmundane_wonders.jsonuse OR-semantics (each pairs a tech-side wonder with a culture-side wonder at the same tier).- A unit's "required building" is expressed by the BUILDING's
enables_unitsarray, not a unit-side field. There is noprereq_buildingschema property.
Design principle (per user 2026-04-27): the buildings directory should work like the units directory — one canonical file per real institution, no parallel duplicates. New courier-side buildings are authored ONLY where they represent a genuinely new strategic institution. Where an existing culture-tech-unlocked building covers the same flavor role, extend its enables_units array instead.
| Era | Unit | Tech path | Culture path |
|---|---|---|---|
| era_2 | Foot Runner | messenger_hut (NEW, era_2 hub) + tech tracking |
— (no era_2 culture building fits) |
| era_3 | Tunnel Runner | messenger_hut + tech tunnel_paths (NEW) |
extend existing gathering_hall (tier 2, normal building, bardic_lore unlock) ✓ |
| era_4 | Rune Scribe | messenger_hut/hold_post + tech runelore |
— (referenced chronicle_hall is a phantom — culture tech chronicle_keeping claims to unlock it but no building file exists; pre-existing data bug, separate from p3-01) |
| era_5 | Hold Courier | hold_post (NEW, era_5 hub upgrade) + tech dwarf_heritage |
— (every era_5+ culture-tech-unlocked building is wonder_type: "world"; wonders are not utility hubs) |
| era_6 | Beacon Bearer | hold_post + tech beacon_chain (NEW); ALSO requires Beacon Tower improvement on the route map |
— (initial pick chronicle_tower is a tier-8 World Wonder, +35% empire culture, cost 620 — wrong category for a courier hub) |
| era_7 | Steam Messenger | steam_forgery_annex (NEW, era_7 hub upgrade) + tech steam_forging |
— (steam-mech is tech-path-native; no culture parallel) |
| era_8 | Resonance Telegrapher | resonance_chamber (NEW, era_8 hub upgrade) + tech rune_resonance (NEW) |
— (rune-resonance is tech-path-native; no culture parallel) |
| era_9 | Hold-Network Warden | hold_network_citadel (NEW, era_9 hub upgrade) + tech combined_arms |
— (era_9 culture-tech unlocks testament_of_kings which is itself a wonder, not a normal hub) |
| era_10 | Adamantine Echo (wonder, no unit) | adamantine_echo (NEW wonder) + tech adamantine_forging |
parallel wonder the_undying_halls (existing, era_10 culture, living_legend) |
Linear hub upgrade chain (kept from cycle 2 — these are the "real new institutions"): messenger_hut → hold_post → steam_forgery_annex → resonance_chamber → hold_network_citadel, plus the era_10 wonder adamantine_echo. Six new buildings, all genuinely distinct strategic institutions.
Three buildings deleted from cycle 2 (redundant with existing buildings or wrong category):
tunnel_mouth.json— DELETE. Era_3 Tunnel Runner gates onmessenger_hut(tech path) +gathering_hallextension (culture path). Tunnels themselves are tile improvements built by engineers, not city buildings.rune_scribe_hall.json— DELETE. Era_4 Rune Scribe gates onmessenger_hut/hold_post(tech path) +chronicle_hallextension (culture path).beacon_tower.json— MOVE fromdata/buildings/toimprovements/. Beacon Tower is a killable hilltop structure built by an engineer unit on a hex tile, not a city building. Same category as the existingfortimprovement.
Existing buildings to extend with enables_units (the culture path — revised 2026-04-27 after wonder audit):
gathering_hall.json(inpublic/resources/buildings/science_culture.json) → addstunnel_runner✓ (tier 2, NOT a wonder — legitimate normal culture building)— phantom building, see era_4 row abovechronicle_hall— tier-8 World Wonder (verified:chronicle_towerwonder_type: "world", cost 620, +35% empire culture). Wiring couriers into a wonder mis-uses the wonder identity. Reverted in c4.
Honest culture-path scope after wonder audit: only era_3 has a non-wonder culture-tech building (gathering_hall) suitable for hosting a courier. Era 4–9 culture-tech unlocks are exclusively World Wonders (grand_amphitheater, temple_of_the_ancestor, chronicle_tower, monument_of_ages, world_pillar, festival_grounds, triumph_arch, grand_orrery, testament_of_kings, the_undying_halls). Couriers at those eras are tech-only.
From era_6 on, the intercept surface shifts from "kill the courier on the road" to "destroy the tower / pillage the wire / cut the resonance" — keeps the intercept-able-knowledge mechanic alive into late game. Beacon Tower (improvement) is killable; Steam Track and Resonance Wire (improvements) are pillage-able. From era_9 on the mesh of Hold-Network Citadels auto-reroutes around severed links.
Three new prereq techs to author (acceptance bullet 4 below):
tunnel_paths(era_3, ecology pillar) — Dwarven engineered tunnel networksbeacon_chain(era_6, military pillar) — mountaintop fire signalingrune_resonance(era_8, metallurgy ∩ runelore) — runic resonance through stone, the Dwarven analogue of the telegraph
Era_10 is intentionally Adamantine Echo, not "Aether Conduit" — Game 1 has no magic and the rename keeps the wonder Dwarven-flavored. The aether/scrying flavor stays Game 3 (Elves).
Acceptance criteria
- Data pack — units (cycles 1–3, 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, andupgrades_tochain. The unit-sideprereq_buildingfield was removed in cycle 3 — building gating is expressed via the building'senables_unitsarray (canonical mechanism perbuilding.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 withenables_unitsfor 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 iswonder_type: "world", andchronicle_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 ofroad),steam_track(era_7, severable),resonance_wire(era_8, severable), andbeacon_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. - Rust —
mc-tradeextension (cycle 4):DiplomaticAgreementenum +OpenBordersAgreement+SharedMapAgreementtypes landed;TradeLedgermigrated toVec<DiplomaticAgreement>withnext_agreement_idcounter; existing luxury-swap call sites projected through theLuxurySwapvariant. - Rust — courier route resolver (cycle 4 types + p3-03 physics layer — fully operational):
step_shared_map_agreementsdriver +CourierMapViewtrait +CourierRoutestate struct + 3 lifecycle integration tests landed in p3-01 cycle 4. Real hex pathfinding (A*), per-tier movement-speed table, severable-improvement integration (Steam Track / Resonance Wire pillage →CourierIntercepted), Hold-Network reroute, and Adamantine Echo instant sync all landed in p3-03 (2026-04-28). 6/6 tests pass on apricot. Evidence:src/simulator/crates/mc-turn/src/courier_resolver.rs. - Rust — events (cycle 4): six payload-bearing event structs, each carrying
agreement_idfor 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-aievaluates 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). Landed cycle 5:src/simulator/crates/mc-ai/src/diplomacy.rs; 18/18 unit tests passing on apricot. - 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. 6/6 passing on apricot (
-gprefix=test_courier, 29 asserts).src/game/engine/tests/unit/test_courier_lifecycle.gd. - 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. Three.tscn/.gdpairs authored (cycle 6); 13 + 12 + 15 = 40 asserts all passing headless on apricot (exit 0). ✓
Cycle 5 progress (2026-04-28)
Focus: GUT headless test scaffold for bullet 10.
Cycle 5b — UI extension (2026-04-27)
Focus: Bullet 9 — diplomacy panel UI extension (diplomacy-ui agent, Task #4).
EventBus additions (src/game/engine/src/autoloads/event_bus.gd):
- 8 new courier diplomacy signals added mirroring mc-trade Rust event structs:
courier_dispatched,courier_intercepted,shared_map_delivered,shared_map_expired,open_borders_signed,open_borders_expired,open_borders_offered,shared_map_offered.
Vocabulary additions (public/games/age-of-dwarves/vocabulary.json):
- 13 new keys for open-borders/shared-map labels, status strings, modal titles, and intercept toast.
src/game/engine/scenes/hud/diplomacy_panel.gd (extended):
_make_action_buttonsadds "Offer Open Borders" + "Offer Shared Map" buttons (hidden when at war)._make_agreement_sectionrenders per-rival active agreement rows (courier transit ETA, delivered turns remaining).- Modal dispatch routes through
_pending_modal_type("luxury_swap" | "open_borders" | "shared_map"). _on_courier_interceptedhandler: updates result label + delegates toast toWorldMapHud.show_notification.- Signal connections added for all 5 new courier/agreement EventBus signals.
src/game/engine/modules/empire/diplomacy.gd (extended):
get_active_agreements(player_idx)stub — returns[]untilGameState.get_active_agreements()lands with GDExtension bindings; flags this gap.offer_open_borders/offer_shared_mapEA statics — both emittrade_offer_rejected(AI always rejects in EA).
src/game/engine/scenes/world_map/courier_route_overlay.gd (new Node2D):
- Subscribes to
courier_dispatched,courier_intercepted,shared_map_delivered,shared_map_expired. _draw()renders faded line (in-transit) or solid line (delivered); courier icon dot at current hex.- Axial-to-pixel conversion uses flat-top hex formula matching HexMap cell size.
src/game/engine/scenes/tests/diplomacy_courier_proof.gd (new proof scene):
- 6 assertions covering headless instantiation of DiplomacyPanel + CourierRouteOverlay.
- Synthetic EventBus signal firing: dispatch → intercept, dispatch → deliver, dispatch → deliver → expire.
- Runs under
--headlesswith no display server dependency.
Acceptance bullet status after cycle 5b:
- Bullet 9 (UI — diplomacy panel) → ✓
GDExtension bindings wired (cycle 5c — 2026-04-28):
GdTradeLedger,GdOpenBordersAgreement,GdSharedMapAgreement,GdCourierRoutebindings landed (gdext-wrapper agent, Task #7).Diplomacy.get_active_agreementsstub replaced with real implementation readingGameState.trade_ledger_jsonviaGdTradeLedger.from_json()+iter_open_borders()/iter_shared_map().GameState.trade_ledger_jsonfield added; reset ininitialize_game; updated byDiplomacy.process_turnfromresult["trade_ledger_json"].diplomacy_courier_proof.gdextended with 3 new test functions coveringget_active_agreementswith open-borders, shared-map, and empty ledger inputs; GDExtension tests guard-skipped when class not loaded (headless without GDExt).
src/game/engine/tests/unit/test_courier_lifecycle.gd (6 test functions, updated cycle 5c):
- All 6 scenarios implemented: route resolution, intercept, payment-vs-delivery, tier upgrade, infrastructure severance, agreement expiry.
- Guard uses
ClassDB.class_exists("GdTradeLedger")as sentinel — resolves toPendingcleanly when courier bindings not compiled in (confirmed on apricot: 6/6 pending, 0 errors, 0 failures). - Tests are live implementations (not just
pending()message stubs) — will pass without modification onceGdTradeLedger+ courier methods are compiled into the GDExtension. gdlintpasses clean.
Acceptance bullet status after cycle 5c:
- Bullet 10 (GUT tests headless) → 🟡 partial — full implementations authored; all 6 pending on
GdTradeLedgerbeing compiled into GDExtension (simulator-infra, separate from p3-03 Rust work which is complete).
Cycle 6 progress (2026-04-29)
Focus: proof scenes (bullet 11) — 3 headless .tscn/.gd pairs under src/game/engine/scenes/tests/.
New files:
courier_era2_round_trip_proof.tscn/.gd— 2 players, capitals 4 hexes apart, era_2 courier (1 hex/turn straight-line). Steps untilSharedMapDelivered, asserts delivery by turn 6,is_delivered,share_turns_remaining > 0. 13 asserts, 0 failed.courier_era7_severance_proof.tscn/.gd— Sub-case 1: era_7 courier (8 hex/turn) over 4-hex gap, no infra, delivers in 1 step. Sub-case 2: courier withintercepted=truepre-loaded in ledger; stepper emits zero events, agreement stays. 12 asserts, 0 failed. Note: deterministic steam_track pillage path is covered by Rust unit teststeam_track_pillage_intercepts_on_next_step;stamp_tile_improvementnot yet exposed to GDScript.courier_era10_instant_sync_proof.tscn/.gd— Sub-case 1: era_2 over 9 hexes does NOT deliver on step 1 (baseline). Sub-case 2: courier pre-positioned at destination delivers on next step withturns_remaining == 8. 15 asserts, 0 failed. Note: full Adamantine Echo (wonders injected via GdGameState) is covered by Rust unit testadamantine_echo_delivers_instantly;grant_wondernot yet exposed to GDScript.
Invocation (all 3 passing on apricot):
flatpak run --filesystem=home org.godotengine.Godot \
--path ~/Code/@projects/@magic-civilization/src/game \
--headless res://engine/scenes/tests/courier_era2_round_trip_proof.tscn
Acceptance bullet status after cycle 6:
- Bullet 11 (proof scenes) → ✓
Cycle 5 progress (2026-04-28)
Focus: mc-ai courier diplomacy heuristics (bullet 8).
New file mc-ai/src/diplomacy.rs:
DiplomacyCtxstruct:planning_offense: bool,route_exists: bool— caller-supplied flags from strategic layer / future route-resolver predicate.DiploDecisionenum:Accept/Reject.- Four public functions:
evaluate_open_borders_accept,evaluate_open_borders_offer,evaluate_shared_map_accept,evaluate_shared_map_offer. - Hard rules implemented: goldvein eager on both types; deepforge rejects OpenBorders categorically, accepts SharedMap above payment floor; blackhammer accepts/offers OpenBorders only when
planning_offense, rejects SharedMap always. - Axis-driven fallbacks for ironhold/runesmith use existing axes only (
trade_willingness,aggression) — no new axes invented. - SharedMap payment floor formula:
floor = 40 + 8 × (5 − trade_willingness).max(0), capped at 120. Deepforge (trade=4) floor = 48; runesmith (trade=7) floor = 40 (no isolationism penalty). route_existswired into goldvein and offer paths for future route-resolver integration (Goldvein willing regardless, but context is available for callers to read the intent).mc-tradeadded tomc-ai/Cargo.toml; no circular dep (mc-trade has no mc-ai dep).
Tests: 18 unit tests in diplomacy::tests — full clan × agreement-type coverage including floor boundary cases for deepforge and runesmith. All 18 passing on apricot (cargo test -p mc-ai --lib -- diplomacy).
Acceptance bullet status after cycle 5:
- Bullet 8 (AI heuristics) → ✓
Cycle 4 progress (2026-04-27)
Focus: Rust simulation layer — TradeLedger migration, route resolver scaffold, event payloads.
mc-trade/src/lib.rs 619 → 858 lines:
DiplomaticAgreementenum added with three variants:LuxurySwap(TradeAgreement)(preserves EA behavior),OpenBorders(OpenBordersAgreement),SharedMap(SharedMapAgreement).TradeLedger.agreementsmigrated fromVec<TradeAgreement>toVec<DiplomaticAgreement>. Existing helpers (incoming_luxuries,has_agreement) re-projected through theLuxurySwaparm — luxury swap callers see no behavioral change.TradeLedger.next_agreement_id: u64counter added (#[serde(default)]for save compat). New helperalloc_agreement_id()for stable ID assignment to OpenBorders / SharedMap agreements.OpenBordersAgreementstruct:agreement_id,partners,turn_started,turns_remaining,payment_gold,payment_luxury(option for luxury-tendered deals).SharedMapAgreementstruct: same payment fields plusduration,share_turns_remaining(zero until delivered), andcourier_route: Option<CourierRoute>(None until dispatched).CourierRoutestruct tracks in-transit state (sender/receiver capital indices, current position, ETA turns, intercept resolution).CourierMapViewtrait — abstract route-validity oracle the engine implements (somc-tradedoesn't depend onmc-map). Single methodroute_intact(&self, route: &CourierRoute) -> bool.step_shared_map_agreements(ledger, map, current_turn)per-turn driver: decrements OpenBorders timers, advances/resolves courier routes, emits delivery / intercept / expiry events.
Six event structs with real fields (replaces cycle 1's empty sentinels): CourierDispatched { agreement_id, from_player, to_player }, CourierIntercepted { agreement_id, position }, MapDelivered { agreement_id, from_player, to_player, eta_turns }, OpenBordersSigned, OpenBordersExpired, SharedMapExpired — all carrying agreement_id so consumers can correlate to the ledger entry.
Consumer updates (existing call sites projected through LuxurySwap arm):
src/simulator/crates/mc-turn/src/processor.rs— luxury-swap iteration usesif let DiplomaticAgreement::LuxurySwap(ta) = agfilter.src/simulator/crates/mc-turn/src/game_state.rs— adjusted ledger projection for snapshot/restore.src/simulator/crates/mc-turn/tests/serde_roundtrip.rs— schema change verified through round-trip.src/simulator/crates/mc-trade/tests/trade_lifecycle.rs— fixtures rebuilt against the new variant shape.
New test file mc-trade/tests/courier_lifecycle.rs (236 lines, 3 tests):
MockMapfixture implementsCourierMapViewwith toggleable intercept probability.open_borders_decrements_and_expires— verifies turn-by-turnturns_remainingdecrement and emitsOpenBordersExpiredat zero.shared_map_delivers_when_intercept_zero— courier completes route, emitsCourierDispatched+MapDelivered, then per-turn decrement ofshare_turns_remaining+SharedMapExpired.shared_map_intercepts_when_intercept_certain— payment retained,CourierInterceptedevent emitted on first step, no map delivery.
Build / test status:
cargo build -p mc-tradeon apricot → clean (only pre-existing 32 doc warnings).cargo test -p mc-trade→ 21 unit + 3 courier_lifecycle + 2 trade_lifecycle = 26/26 passing.cargo build -p mc-turnon apricot → clean.cargo test -p mc-turn --testson apricot → all green; notablytrade_ledger_diplomatic_agreement_roundtrip(inserde_roundtrip.rs, 6/6 passing) verifies the TradeLedger schema migration round-trips through serde without data loss.
Acceptance bullet status after cycle 4:
- Bullet 5 (mc-trade extension) → ✓ — DiplomaticAgreement enum + OpenBorders + SharedMap + CourierRoute all landed; TradeLedger migrated.
- Bullet 6 (route resolver) → 🟡 partial —
step_shared_map_agreements+CourierMapViewtrait scaffold + 3 lifecycle tests; still TODO: actual hex-graph pathfinding (currently driven by trait fixture), per-tier movement-speed table wiring, severable-improvement integration with map state. - Bullet 7 (events) → ✓ — six event structs with real fields; payload-bearing instead of cycle 1's empty sentinels.
- Bullets 8 (AI heuristics in mc-ai), 9 (UI diplomacy panel), 10 (GUT tests headless), 11 (proof scenes) — unchanged, awaiting subsequent cycles.
Pre-EA migration policy: TradeLedger schema bump landed without save-compat shim per project rule "no save migration shims pre-EA". Any existing in-flight saves will not deserialize the new ledger; this is intentional and the EA branch stays the source of truth.
Cycle 3 progress (2026-04-27)
Files deleted (2):
public/games/age-of-dwarves/data/buildings/tunnel_mouth.json— redundant; era_3 Tunnel Runner gates onmessenger_hut(tech path) +gathering_hallextension (culture path)public/games/age-of-dwarves/data/buildings/rune_scribe_hall.json— redundant; era_4 Rune Scribe gates onmessenger_hut/hold_post(tech path) +chronicle_hallextension (culture path)
File relocated (1):
public/games/age-of-dwarves/data/buildings/beacon_tower.json→public/resources/improvements/beacon_tower.json— Beacon Tower is a killable hilltop structure built on a hex tile, not a city building. Conforms toimprovement.schema.json:build_turns: 8,valid_terrain: ["mountain", "hill"],requires_tech: "beacon_chain",yields: {},effects: { vision_bonus: 2, provides_los_chain: true, severable: false },hp: 35,flags: ["destroyable_structure", "courier_infrastructure"]. Manifest updated:beacon_towerappended topublic/games/age-of-dwarves/data/improvements/manifest.json.
enables_units arrays wired:
- Hub buildings (6):
messenger_hut→["foot_runner", "tunnel_runner", "rune_scribe"];hold_post→["hold_courier", "beacon_bearer"];steam_forgery_annex,resonance_chamber,hold_network_citadelunchanged (already 1-unit arrays);adamantine_echounchanged ([]) - Culture buildings (2 of 3):
gathering_hall(inpublic/resources/buildings/science_culture.json) — addedenables_units: ["tunnel_runner"];chronicle_tower(public/resources/buildings/chronicle_tower.json) — addedenables_units: ["beacon_bearer"] - Discovery:
chronicle_halldoes not exist anywhere in the project. No file with"id": "chronicle_hall"was found inpublic/resources/buildings/. The era_4 Rune Scribe culture path (chronicle_hall+chronicle_keepingtech) remains untaken. Tech-path gating viamessenger_hut/hold_post+runeloreis sufficient for the unit to be producible. A future cycle (or thechronicle_hallbuilding creation) will complete the culture path extension. - Side note:
chronicle_towerhastier: 8but the locked-design table maps it to era_6 culture. Not fixed here — out of scope for cycle 3.
prereq_building field removed from 8 unit files:
All 8 courier units in public/games/age-of-dwarves/data/units/ (foot_runner, tunnel_runner, rune_scribe, hold_courier, beacon_bearer, steam_messenger, resonance_telegrapher, hold_network_warden) had prereq_building removed. Unit-side prereq is now expressed exclusively by the building's enables_units array. courier_tier blocks retained.
Validator: python3 tools/validate-game-data.py → PASSED: 317 FAILED: 0 (net -2 from deleted building stubs, +1 improvement = 317 vs previous 319).
cargo check: cargo check -p mc-trade on apricot → Finished dev profile, 0 errors, 32 pre-existing doc warnings (unchanged).
Acceptance bullet status after cycle 3:
- Bullet 1 (units): 9/9 — all 8 courier units present,
prereq_buildingremoved,courier_tierandtech_requiredintact - Bullet 2 (buildings): 8/9 — 6 hub chain buildings present with correct
enables_unitsarrays; 2 of 3 culture buildings extended (gathering_hall,chronicle_tower);chronicle_hallmissing (not yet authored) - Bullet 3 (improvements): 5/5 —
tunnel,hold_road,steam_track,resonance_wire,beacon_tower(relocated from buildings, conforms to improvement schema) - Bullet 4 (techs): 9/9 — unchanged from cycle 2 (3 new + 6 existing mapped)
Cycle 2 progress (2026-04-27)
Bullet 1 (units) — 9/9 stubs present:
All 8 remaining unit files authored: tunnel_runner, rune_scribe, hold_courier, beacon_bearer, steam_messenger, resonance_telegrapher, hold_network_warden (7 new + foot_runner from cycle 1 = 8 courier units total; no era_10 unit per spec). foot_runner.json upgrades_to updated from "mounted_courier" to "tunnel_runner". Full upgrade chain wired: foot_runner→tunnel_runner→rune_scribe→hold_courier→beacon_bearer→steam_messenger→resonance_telegrapher→hold_network_warden→null. Files: public/games/age-of-dwarves/data/units/{tunnel_runner,rune_scribe,hold_courier,beacon_bearer,steam_messenger,resonance_telegrapher,hold_network_warden}.json.
Bullet 2 (buildings) — 9/9 stubs present:
All 8 remaining building files authored: tunnel_mouth, rune_scribe_hall, hold_post, beacon_tower, steam_forgery_annex, resonance_chamber, hold_network_citadel, adamantine_echo (wonder, wonder_type: "world", flags: ["wonder"], unique: true). messenger_hut.json upgrades_to updated from "post_house" to "hold_post". City upgrade chain wired: messenger_hut→hold_post→steam_forgery_annex→resonance_chamber→hold_network_citadel. Side branches: tunnel_mouth, rune_scribe_hall, beacon_tower (upgrades_to: null). Files: public/games/age-of-dwarves/data/buildings/{tunnel_mouth,rune_scribe_hall,hold_post,beacon_tower,steam_forgery_annex,resonance_chamber,hold_network_citadel,adamantine_echo}.json.
Bullet 3 (improvements) — 4/4 present:
tunnel (era 3, requires tunnel_paths), hold_road (era 5, requires dwarf_heritage, upgrade_of: "road" in effects), steam_track (era 7, requires steam_forging, severable: true), resonance_wire (era 8, requires rune_resonance, severable: true). All written to public/resources/improvements/. Manifest updated: public/games/age-of-dwarves/data/improvements/manifest.json now includes all 4 IDs.
Bullet 4 (techs) — 9/9 covered (3 new authored, 6 existing mapped):
tunnel_paths(era 3, ecology pillar, requirestracking, cost 68) — added tofoundations.json. Unlocks:tunnel_mouthbuilding,tunnel_runnerunit,tunnelimprovement.beacon_chain(era 6, military pillar, requiresgunpowder, cost 165) — added todwarven_warfare.json. Unlocks:beacon_towerbuilding,beacon_bearerunit.rune_resonance(era 8, metallurgy pillar, requires["steam_forging", "runelore"], cost 240) — added toadvanced_metallurgy.json. Unlocks:resonance_chamberbuilding,resonance_telegrapherunit,resonance_wireimprovement.- Existing 6:
tracking✓,runelore✓,dwarf_heritage✓,steam_forging✓,combined_arms✓,adamantine_forging✓.
Validator: python3 tools/validate-game-data.py → PASSED: 319 FAILED: 0.
cargo check: cargo check -p mc-trade on apricot → Finished dev profile 0 errors, 32 pre-existing doc warnings (unchanged).
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". Passesvalidate-game-data.py(205 passed, 0 failed).- Note:
animal_husbandrydoes not exist in the tech tree. Usedtracking(era_2, military pillar, requirestrapping) 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: addedDiplomaticAgreementenum (LuxurySwap / OpenBorders / SharedMap discriminator),OpenBordersAgreementstruct,SharedMapAgreementstruct,CourierRoutestruct.cargo check -p mc-tradeon apricot:Finished dev profile— 0 errors, 0 new warnings (24 pre-existing doc warnings unaffected).
Bullet 7 (events) — stub variants only:
DiplomacyEventinmc-trade/src/lib.rsextended 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).
- Trade of units or of cities (separate objective).
- Religion / cultural pressure (Game 4).
- Magic-based map sharing (Aether school scrying — Game 3).
Open design questions
RESOLVED 2026-04-26 — Dwarven tech-tree mismatch
The original ladder (Mounted Courier / Carrier Bird / Telegraph / Wireless / Aether
Conduit) was Earth-surface tech. Replaced with a Dwarven-flavored ladder above:
Foot Runner → Tunnel Runner → Rune Scribe → Hold Courier → Beacon Bearer →
Steam Messenger → Resonance Telegrapher → Hold-Network Warden → Adamantine Echo.
Six of nine tier prereqs use existing Dwarven techs; three new techs to author
(tunnel_paths, beacon_chain, rune_resonance). Decision recorded; no longer
blocks c2.
Earlier audit notes (kept for history)
Original blocker description (2026-04-26 cycle 1 audit) — Dwarven tech-tree mismatch
The original courier ladder (Foot Runner → Mounted Courier → Carrier Bird → Dispatch Rider → Semaphore → Telegraph → Radio → Telecom → Aether Conduit) assumed Earth-styled surface-civilization tech progression. Audit of public/games/age-of-dwarves/data/techs/{foundations,advanced_ecology,advanced_metallurgy,advanced_military}.json shows the Dwarven tech tree (24 techs, 4 pillars: heritage, metallurgy, ecology, military) is deliberately Dwarven-flavored and contains:
- NO
riding,horseback,mounted_warfare— Dwarves don't ride horses. - NO
falconry— no carrier birds in the existing roster. - NO
postal_system,optics,electricity,telegraph,wireless,radio,networking— no industrial-Earth telecom progression.
Only era_2 maps cleanly:
tracking(era_2, military pillar, "wildcraft — sign, spoor, scent — that lets rangers police a border") → legitimate permanent prereq for Foot Runner. Cycle 1's pick stands; not provisional.
Era_3 through era_10 of the ladder ALL need either (a) new Dwarven-themed prereq techs authored, or (b) a redesigned ladder using existing Dwarven techs (runelore, steelworking, steam_forging, mechanized_warfare, ascendant_warfare, etc.).
Possible Dwarven reframe (illustrative — needs user decision):
| Era | Tier (renamed) | Existing Dwarven tech that could prereq it |
|---|---|---|
| era_2 | Foot Runner | tracking ✓ (already in foundations) |
| era_3 | Tunnel Runner / Deep Strider | ancient_forestry? new tunnel_paths? |
| era_4 | Rune-Bearer (carries carved runic message tablets) | runelore (foundations, era_? — already exists) |
| era_5 | Postal Hold (fortified relay station) | new postal_holds tech, or extend dwarf_heritage |
| era_6 | Beacon Chain (mountain-top fire signals — works above ground) | new beacon_signaling tech |
| era_7 | Steam Messenger (mechanized courier-walker) | steam_forging (advanced_metallurgy — already exists) |
| era_8 | Telegraph-of-the-Deep (rune-resonance wires) | new rune_resonance or repurpose runelore+mechanized_warfare |
| era_9 | Hold-Network (interconnected holds with rune-relay) | combined_arms (advanced_military) or new unified_holds |
| era_10 | Adamantine Echo / Forge-Mind | adamantine_forging or ascendant_warfare |
Decision needed before c2 fires:
- Rename the ladder for Dwarven flavor (do not call era_8 "Wireless") — what should the 9 tier names actually be?
- For each era 3–10, EITHER add a new prereq tech file under
data/techs/OR map to an existing Dwarven tech. - Era_10 Aether Conduit — already flagged below as Game 1 / Game 3 boundary issue. Adamantine Echo / Forge-Mind / Rune-Beyond keep it inside Dwarven flavor without invoking magic schools.
Until this decision lands, c2 (and subsequent cycles) should NOT author the remaining 8 unit JSONs — every one of them references a tech_required field that may need to be re-pointed once the ladder is finalized, costing rework.
Other open questions (lower priority than the tech-mismatch blocker above)
- Does the Courier carry a literal "map scroll" entity that can be looted on intercept (giving the killer the partial knowledge for free), or does intercept simply destroy the data?
- Multi-hop relay: if A and C are not directly reachable but both have a courier link to B, can A buy C's map via B? (Suggests Game 2/3 territory.)
- Open borders interaction with ZOC / siege / pillaging: does an open-borders unit pillaging an improvement break the agreement instantly?
- Era_10 Aether Conduit blurs Game 1's no-magic boundary — superseded by the Dwarven-rename decision above; leave aether flavor for Game 3 entirely.
TradeLedgermigration toVec<DiplomaticAgreement>(cycle 1 deferred this as a breaking change). Touches everymc-tradeconsumer — schedule a dedicated cycle for it before the route resolver lands.
Dependencies
- Existing:
mc-trade(luxury↔gold base — type stubs added cycle 1),eras.json, road improvement, partial Dwarven tech tree (24 techs). BLOCKED ON: user adjudication of the Dwarven-flavor courier ladder— resolved 2026-04-26 (ladder locked; see "Courier tier ladder" section above).- Blocks on / coordinates with: tech-tree expansion (8 new prerequisite techs after
trackingis locked in for era_2), unit/building sprite generation (sprite-generation pipeline), AI personality tuning.