magicciv/.project/objectives/p2-56b-expertise-tier-progression.md
Natalie 3a2d589e6e feat(@projects/@magic-civilization): mark p2-11a and p2-56b as complete
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-04 03:07:14 -04:00

4.4 KiB

id title priority status scope owner updated_at evidence blocked_by
p2-56b Expertise tier progression — 5-tier specialist XP ladder p2 done game1 simulator-infra 2026-05-04
src/simulator/crates/mc-core/src/expertise.rs:30-39
src/simulator/crates/mc-city/src/expertise.rs:117-211
src/simulator/crates/mc-turn/src/processor.rs:864-905
public/games/age-of-dwarves/data/balance/expertise.json:1-11
src/simulator/crates/mc-city/src/expertise.rs:217-294

Context

Specialists in public/games/age-of-dwarves/docs/cities/SPECIALISTS.md progress through a 5-tier expertise ladder: Novice → Apprentice → Journeyman → Master → Grandmaster. Each tier scales the specialist's per-turn yield contribution and unlocks aura behaviour (handled in p2-56c). XP is earned per turn proportional to the assigned tile's yield in the specialist's WorkerCategory; XP decays each turn the specialist is idle (no slot assignment).

Acceptance

  • mc-core::ExpertiseTier enum (Novice, Apprentice, Journeyman, Master, Grandmaster) with Ord derived (tier comparison) — src/simulator/crates/mc-core/src/expertise.rs:30-39 + lib.rs:21,29 re-export. Stable ALL const + next()/previous()/xp_to_next()/is_capped() API; placed in its own module per design rather than crammed into worker.rs.
  • ✓ Per-tier XP thresholds + idle-decay constant loaded from public/games/age-of-dwarves/data/balance/expertise.json (single canonical balance file alongside biome_capacity.json/ecology_yields.json; no parallel data/specialists/ store). Loader: mc_city::expertise::ExpertiseConfig::from_json_strsrc/simulator/crates/mc-city/src/expertise.rs:79-99. Compile-time round-trip test test_config_loads_from_canonical_jsonsrc/simulator/crates/mc-city/src/expertise.rs:282-294.
  • mc-city::WorkerExpertise { tier, xp_in_tier } per-worker state with tick_xp_gain(yield_amount, cfg) -> ExpertiseTier and tick_idle_decay(cfg) -> ExpertiseTiersrc/simulator/crates/mc-city/src/expertise.rs:117-211. Cascading promotions and multi-tier demotions handled in a single tick. BTreeMap<WorkerCategory, WorkerExpertise> ledger added to bench CityStatesrc/simulator/crates/mc-city/src/lib.rs:106-115 (per-rails: BTreeMap, not HashMap).
  • ✓ Promotion + decay tests green: test_xp_promotes_at_threshold, test_xp_promotes_with_overflow_cascade, test_idle_decay_demotes_below_zero, test_idle_decay_clamps_at_novice_floor, test_grandmaster_capped, test_xp_round_trip_via_serde, test_full_ladder_run_to_capsrc/simulator/crates/mc-city/src/expertise.rs:217-294. cargo test -p mc-city --lib expertise → 8 passed. cargo test -p mc-core --lib expertise → 7 passed (ladder/serde/iteration).
  • ✓ Per-turn ledger update integrated into mc-turn::processor::process_city_production after yields are computed — src/simulator/crates/mc-turn/src/processor.rs:864-905. Sustenance ↔ food_yield, Construction ↔ prod_in; categories present in the ledger that didn't earn this turn are decayed via tick_idle_decay. No GDScript shadow accumulator — all logic in Rust SSoT. cargo test -p mc-turn --lib → 203 passed, 1 ignored. cargo check --workspace clean.

Source-of-truth rails

  • Rust crate: mc-city::expertise owns ledger + tier transitions. mc-turn calls it; GDScript only renders tier badges.
  • JSON path: public/games/age-of-dwarves/data/balance/expertise.json (thresholds + decay constant). No parallel data/specialists/.
  • mc-core wrapper: ExpertiseTier enum (snake_case, Ord) — no raw integers crossing module boundaries.

Out of scope

  • Master/Grandmaster aura emission — p2-56c.
  • Per-specialist unique abilities at Grandmaster — separate post-EA ticket.
  • Per-tier yield multipliers (60%/80%/100%/120%/140%) — applied at yield-fold time in a follow-up; this objective lands the ladder + tick, not the multiplier wiring.
  • UI animations for tier-up — downstream UI work.
  • Per-slot (SpecialistId, slot_id) keying — the bench CityState doesn't model per-citizen slot assignment yet; ledger keys by WorkerCategory (3-entry max) until per-slot lands.

References

  • public/games/age-of-dwarves/docs/cities/POPULATION.md (table updated to canonical tier names + decay constant)
  • public/games/age-of-dwarves/docs/cities/SPECIALISTS.md
  • Parent: p2-56a (WorkerCategory taxonomy)
  • Sibling: p2-56c (aura propagation)