magicciv/.project/objectives/p3-05e-civic-modifier-propagation.md
2026-05-15 00:27:10 -07:00

3.9 KiB

id title priority status scope category owner created updated_at blocked_by follow_ups
p3-05e Civic modifier propagation — apply civic effects to per-city yields p3 partial game1 civics unassigned 2026-05-03 2026-05-14
p3-05b
p3-05c
p3-05d

Context

With the three axis catalogs (p3-05b/c/d) and CivicState (p3-05a) in place, this objective wires civic modifiers into the per-city yield computation in mc-economy and mc-city. Modifier semantics are documented in public/games/age-of-dwarves/docs/civics/CIVICS.md — additive vs multiplicative, per-yield vs global, application order.

Acceptance

  • mc-civics::resolve_modifiers(state: &CivicState, &CivicCatalog) -> ResolvedModifiers returns a typed modifier bundle merging the three active civics. Anarchy axes contribute zero modifiers. Evidence: src/simulator/crates/mc-civics/src/lib.rs (new crate added to workspace Cargo.toml); ResolvedModifiers is a typed struct (18 fields covering every modifier key in the 15 catalog JSONs); axis_choice_id returns None for the anarchy sentinel.
  • mc-economy::city_yield::compute consumes ResolvedModifiers (passed by reference, not global mutable state); applies in documented order: base → additive → multiplicative. Evidence: src/simulator/crates/mc-economy/src/city_yield.rs::compute(base: CityYield, mods: &ResolvedModifiers) -> CityYield; module docstring documents the three-step order; test_application_order_additive_before_multiplicative exercises it.
  • mc-city::specialist::contribution consumes ResolvedModifiers.specialist_xp_rate (from Labor axis) when crediting XP per p2-56b. Deferredspecialist_xp_rate field is present on ResolvedModifiers and is multiplied correctly across axes (test test_partial_anarchy_keeps_other_axes covers inequality_amplifier with the same code path); wiring it into the existing mc-city::specialist site is a small follow-up that drags p2-56b semantics into this ticket and was out of scope for the time budget.
  • cargo test -p mc-civics test_modifier_resolution_anarchy_zero and cargo test -p mc-economy test_civic_modifier_changes_yield green. Evidence: cargo test -p mc-civics test_modifier_resolution_anarchy_zero1 passed; cargo test -p mc-economy test_civic_modifier_changes_yield1 passed (this session).
  • ✓ Headless parity test: switching from mercantile_markets to planned_economy measurably shifts per-city gold yield in the documented direction. Evidence: mc-economy/src/city_yield.rs::test_civic_modifier_changes_yield builds both CivicStates, resolves modifiers, and asserts mercantile gold (120) > planned gold (95) from base 100 (mercantile +20%, planned -5%). Test runs under cargo test — no display server required.

Status notes

  • The catalog reference doc public/games/age-of-dwarves/docs/civics/CIVICS.md named in the original context does not exist; CIVIC_OVERVIEW.md exists but does not document additive-vs-multiplicative semantics. Convention is now documented in the mc-civics lib docstring (key-suffix table). Authoring the design doc is a follow-up, not a Rust-side blocker.
  • Bullet 3 (mc-city::specialist) is the only open item. K = 4 / N = 5 → status partial.

Source-of-truth rails

  • Rust crate: mc-civics owns resolution; mc-economy/mc-city consume typed bundle. No GDScript shadow modifier table.
  • JSON path: civic JSONs from p3-05b/c/d are the only source of modifier values; no hardcoded constants.
  • mc-core wrapper: ResolvedModifiers typed struct (per-yield fields), not a stringly-typed HashMap<String, f32>.

Out of scope

  • Anarchy duration / switch UX — p3-06.
  • Inequality cascade — p3-07a/b.
  • AI civic-choice scoring — separate AI ticket.

References

  • public/games/age-of-dwarves/docs/civics/CIVICS.md
  • Parents: p3-05a, p3-05b, p3-05c, p3-05d
  • Sibling: p3-06