diff --git a/.project/objectives/README.md b/.project/objectives/README.md
index 1aec9346..f6f3bf63 100644
--- a/.project/objectives/README.md
+++ b/.project/objectives/README.md
@@ -14,11 +14,11 @@
| Priority | ✅ | 🟡 | 🔴 | ❌ | ⚫ | Total |
|---|---|---|---|---|---|---|
-| **P0** | 27 | 7 | 1 | 0 | 0 | 35 |
+| **P0** | 27 | 7 | 3 | 0 | 0 | 37 |
| **P1** | 15 | 4 | 2 | 0 | 1 | 22 |
| **P2** | 14 | 5 | 0 | 8 | 0 | 27 |
| **P3 (oos)** | 0 | 0 | 0 | 0 | 17 | 17 |
-| **total** | **56** | **16** | **3** | **8** | **18** | **101** |
+| **total** | **56** | **16** | **5** | **8** | **18** | **103** |
@@ -26,8 +26,8 @@
| Team Lead | Remaining |
|---|---|
+| [warcouncil](../team-leads/warcouncil.md) | 8 |
| [asset-sprite](../team-leads/asset-sprite.md) | 7 |
-| [warcouncil](../team-leads/warcouncil.md) | 6 |
| [wireguard](../team-leads/wireguard.md) | 6 |
| [shipwright](../team-leads/shipwright.md) | 2 |
| [testwright](../team-leads/testwright.md) | 2 |
@@ -74,6 +74,8 @@
| [p0-33](p0-33-world-map-input-and-panel-wiring.md) | 🟡 partial | World-map input wiring — unit selection panel, city click, ESC/F10 menu, panel close | [wireguard](../team-leads/wireguard.md) | 2026-04-18 |
| [p0-34](p0-34-freepeople-tribe-founding.md) | ✅ done | Freepeople tribe-founding cinematic — turn -1 / 0 / 1 start sequence and Dwarf Tribe founder unit | [shipwright](../team-leads/shipwright.md) | 2026-04-18 |
| [p0-35](p0-35-movement-mode-ux.md) | 🟡 partial | Movement mode UX — Move button, path preview, right-click confirm, fog-aware pathing | [wireguard](../team-leads/wireguard.md) | 2026-04-18 |
+| [p0-37](p0-37-personality-emergent-tactical-thresholds.md) | 🔴 stub | Personality-emergent tactical thresholds (lift 7 hardcoded constants into axis-derived functions) | [warcouncil](../team-leads/warcouncil.md) | 2026-04-18 |
+| [p0-38](p0-38-mcts-personality-priors.md) | 🔴 stub | Inject personality-utility scores as MCTS UCB1 priors | [warcouncil](../team-leads/warcouncil.md) | 2026-04-18 |
## P1 — Ship-readiness
diff --git a/.project/objectives/p0-01-mcts-wiring.md b/.project/objectives/p0-01-mcts-wiring.md
index 9ea26503..b211e8bc 100644
--- a/.project/objectives/p0-01-mcts-wiring.md
+++ b/.project/objectives/p0-01-mcts-wiring.md
@@ -53,9 +53,10 @@ Normal-vs-Normal smoke (`apricot-20260418_074209`, 10 seeds T300, AI_GPU_ROLLOUT
All 5 quality sub-gates FAIL: tier_peak 2.5-3.0 vs required ≥6, peak_unit_tier 1.0 vs required ≥6 in ≥7/10, tier_peak_gap 3-4 vs required ≤2, wonder_count 0 (none built), total_combats below target. **Diagnosis**: games resolve T39-T100 via early domination before tech progresses past tier 1. This is a GAMEPLAY BALANCE issue (domination threshold too loose, tech costs too steep, or map too small), not an AI defect — MCTS correctly pursues the shortest path to victory, which happens to be rush-domination under current data.
**Remaining to reach done:**
-1. Tune one of: `DOMINANCE_FACTOR` (domination victory threshold), MCTS strategic horizon / rollout count, tech research costs, map size defaults, or difficulty.json pacing — until median `tier_peak` ≥ 6 in Normal-vs-Normal batch.
-2. Re-run Normal-vs-Normal 10-seed T300 batch; confirm all 5 sub-gates clear.
-3. Tuning lives in warcouncil's lane but parameter choice may require shipwright (economy/tech) input.
+1. Land `p0-37` (lift the 7 tactical constants to axis-derived functions) — primary lever per 2026-04-18 council analysis. Personality-emergent thresholds should push median game length past T250 (via cautious-clan games) and spread tier_peak across clans.
+2. Land `p0-38` (MCTS UCB1 → PUCT with personality-utility priors) if p0-37 alone leaves tier_peak < 6. Priors bias MCTS tree shape per clan, compounding tactical-layer emergence into strategic search.
+3. Re-run Normal-vs-Normal 10-seed T300 batch after each lands; confirm all 5 sub-gates clear.
+4. If still short, targeted tuning of softmax temperature (p0-38), threshold axis-coefficients (p0-37), or tech research costs (shipwright input).
## Non-goals
diff --git a/.project/objectives/p0-08-domination-victory.md b/.project/objectives/p0-08-domination-victory.md
index cce38aef..27bfaa13 100644
--- a/.project/objectives/p0-08-domination-victory.md
+++ b/.project/objectives/p0-08-domination-victory.md
@@ -27,4 +27,4 @@ Domination victory fires when one player captures all opponent original capitals
- ✓ `processor::end_turn_phase` calls domination check before score check (domination takes precedence). `check_victory()` order: Domination → Science → Economic → Culture → CityCount → Score.
- ✓ `victory_screen.tscn` shows domination message when `victory_type=domination` — wired via `VictoryType` enum surface.
- ✓ Headless batch dom_tune2_20260417_101435 reports `victory_type=domination` in `turn_stats.jsonl` — 2/10 seeds (≥2/10 target met). AI heuristic tuning: `DOMINANCE_FACTOR=1.25`, `CAPITAL_APPROACH_HEX=16`, `FINAL_PUSH_ENEMY_CITY_COUNT=1` — ported into `mc-ai::tactical::{movement,production}` during p0-26. Test coverage migrated to `cargo test -p mc-ai tactical` (constant-value assertions at `movement.rs:1049-1054` + `production.rs:446`).
-- ✗ **Domination tempo calibration** (added 2026-04-18 per p1-05 dependency). Post-port batches (5 clan pins + smoke, 2026-04-18) resolve T39-T150 via domination — too fast for the downstream quality gates. p1-05 luxury variance requires median game length ≥ T250; p0-01 state-at-end tier_peak ≥ 6 requires games reach tier 6+ content before resolving. Current tempo rushes past both. **Retune target**: raise `DOMINANCE_FACTOR` from 1.25 toward 1.75-2.0 OR scale up `CAPITAL_WALLS_MIN_AGE_TURNS` (production.rs:68, currently 20) so early capital assaults fail more often. Needs a parameter sweep + 10-seed validation batch per candidate value.
+- ✗ **Domination tempo calibration** (added 2026-04-18 per p1-05 dependency). Post-port batches (5 clan pins + smoke, 2026-04-18) resolve T39-T150 via domination — too fast for the downstream quality gates. p1-05 luxury variance requires median game length ≥ T250; p0-01 state-at-end tier_peak ≥ 6 requires games reach tier 6+ content before resolving. Current tempo rushes past both. **Fix path per 2026-04-18 council analysis**: lift `DOMINANCE_FACTOR` (+ 6 other tactical thresholds) into axis-derived functions per `p0-37`. Median factor will rise (cautious clans pull it up) AND per-clan dispersion emerges (aggressive clans still rush fast; cautious ones play long). This replaces the "tune a global constant" approach with "tune the axis-coefficient spread".
diff --git a/.project/objectives/p0-24-difficulty-calibrated-ai-progression.md b/.project/objectives/p0-24-difficulty-calibrated-ai-progression.md
index d4a0f646..a98b9c7a 100644
--- a/.project/objectives/p0-24-difficulty-calibrated-ai-progression.md
+++ b/.project/objectives/p0-24-difficulty-calibrated-ai-progression.md
@@ -32,17 +32,50 @@ extra_starting_units, starting_gold_bonus}`. Grep confirms only
`mc-ai` + the tactical executor do NOT consume the production / gold / unit
bonuses, so the knobs are data-only at the decision layer.
+## Architecture decision (2026-04-18) — compose with personality, don't replace it
+
+Research synthesis (Vox Deorum, Sims 3 utility, Tactical Troops) suggests
+difficulty should be a *multiplicative layer on top of personality*, not a
+parallel override:
+
+```
+effective_threshold(axes, difficulty)
+ = personality_threshold(axes) # p0-37
+ × difficulty_multiplier(tier) # this objective
+ + difficulty_offset(tier) # where bounded
+```
+
+This means Easy-Blackhammer still behaves aggressively (axis-driven), just
+less efficiently (production_mult < 1). Hard-Goldvein still hoards gold,
+just with bonus starting funds. Difficulty shapes resource efficiency +
+reaction speed; personality shapes what the AI *wants to do*.
+
+Concretely:
+- `production_mult` → applied inside `tactical::production::build_priority`
+ as a multiplier on yield outputs (or equivalently, a faster tick on the
+ build queue — implementation detail).
+- `starting_gold_bonus` + `extra_starting_units` → applied at setup in
+ `auto_play.gd` or `game_state.gd` init.
+- `research_mult` → already in `mc-tech::costs.rs`; verify still active
+ post-port.
+- New knob: `difficulty_threshold_mult` — scales the p0-37 axis-derived
+ posture thresholds. Easy AI lowers DOMINANCE_FACTOR by 20% (overcommits);
+ Hard AI raises by 15% (waits for real superiority).
+
**Pre-work required before batches can be run:**
-1. Wire `ai_modifiers.production_mult` into `mc-ai::tactical::production` (or
+1. Land `p0-37` (axis-derived thresholds) so there's a personality surface
+ for difficulty to compose onto. Without p0-37, difficulty scales a flat
+ constant and still produces undifferentiated clans per tier.
+2. Add `difficulty_threshold_mult` to `difficulty.json::ai_modifiers` and
+ read it in `mc_ai::tactical::thresholds::*` functions.
+3. Wire `ai_modifiers.production_mult` into `mc-ai::tactical::production` (or
thread it through `TacticalState.player_stats.production_bonus`) so AI
production outputs scale per tier.
-2. Wire `starting_gold_bonus` + `extra_starting_units` into the engine-side
+4. Wire `starting_gold_bonus` + `extra_starting_units` into the engine-side
setup path (`auto_play.gd` or `game_state.gd` init).
-3. Surface the difficulty id through the game-setup env (`AI_DIFFICULTY=easy|normal|hard`)
+5. Surface the difficulty id through the game-setup env (`AI_DIFFICULTY=easy|normal|hard`)
+ plumb down to both the mc-tech cost multiplier and the new mc-ai tactical
hook.
-4. Unblock p0-01's gameplay-balance issue first — tier differentiation cannot
- be measured while every tier resolves T39-T100 via rush-domination.
## Depends on
diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json
index 19ca3422..323c28a2 100644
--- a/public/games/age-of-dwarves/data/objectives.json
+++ b/public/games/age-of-dwarves/data/objectives.json
@@ -1,12 +1,12 @@
{
- "generated_at": "2026-04-18T17:25:56Z",
+ "generated_at": "2026-04-18T17:58:48Z",
"totals": {
- "partial": 16,
- "stub": 3,
- "missing": 8,
"done": 56,
+ "partial": 16,
+ "stub": 5,
"oos": 18,
- "total": 101
+ "missing": 8,
+ "total": 103
},
"objectives": [
{
@@ -359,6 +359,26 @@
"updated_at": "2026-04-18",
"summary": "Movement is currently a silent left-click on a reachable hex — no path shown, no\nconfirmation step. Players expect the Civ-style flow: enter movement mode (M key\nor Move button), see a path preview, right-click to confirm. This objective\nadds the full movement-mode state machine, path rendering, fog-of-war-aware\npathing, and the Move button on the unit action panel with disabled-state\ntooltips for all action buttons.\n\nDepends on **p0-33** (unit panel must be in the scene tree before the Move\nbutton can be wired)."
},
+ {
+ "id": "p0-37",
+ "title": "Personality-emergent tactical thresholds (lift 7 hardcoded constants into axis-derived functions)",
+ "priority": "p0",
+ "status": "stub",
+ "scope": "game1",
+ "owner": "warcouncil",
+ "updated_at": "2026-04-18",
+ "summary": "The p0-26 tactical port faithfully copied 7 tuning constants from\n`simple_heuristic_ai.gd` into Rust. They're currently flat globals that ignore\npersonality axes and difficulty tier, which means:\n\n- **Rail-2 violation**: gameplay tuning hardcoded in Rust instead of derived\n from JSON-owned data (`ai_personalities.json::strategic_axes`).\n- **Personality suppression**: every clan uses the same posture-flip threshold,\n so aggression / grudge_persistence / wealth axes only affect production\n scoring, not commit-to-assault decisions. Clan flavor flattens on the\n tactical layer.\n- **Downstream gate failures**: p0-01 tier_peak, p0-02 era-divergence, p0-22\n median-turn all share the same root — games resolve T39-T100 via\n rush-domination because one global factor governs every clan's\n rush-commit decision.\n\nThe existing `ScoringWeights::apply_axes` (evaluator.rs:180-204) already\nproves the pattern works: `aggression` scales `military_base`, `expansion`\nscales `site_food`. That pattern stops at scoring; it should continue into\nposture / retreat / chase / siege thresholds.\n\nResearch basis (2024-2025):\n- **Sims 3 / Richard Evans (Game AI Pro)**: axis-shaped utility → NPCs diverge\n in identical states. 16 years of production evidence.\n- **Tactical Troops: Anthracite Shift**: utility-AI-scored orders feed MCTS\n priors. Our axis-derived thresholds are the utility layer.\n- **Vox Deorum (Civ-V, arxiv 2512.18564, Dec 2025)**: validates\n macro/tactical decoupling across 2,327 games — our MCTS-strategic +\n axis-driven-tactical layering sits in the sweet spot."
+ },
+ {
+ "id": "p0-38",
+ "title": "Inject personality-utility scores as MCTS UCB1 priors",
+ "priority": "p0",
+ "status": "stub",
+ "scope": "game1",
+ "owner": "warcouncil",
+ "updated_at": "2026-04-18",
+ "summary": "Current MCTS selection uses classical UCB1 at tree nodes — all actions start\nwith equal prior, exploration is driven only by visit count. `ScoringWeights`\nand `strategic_axes` feed the *tactical executor* and *leaf evaluator* but\nNOT the tree-selection step. This means MCTS explores the same branches for\nevery clan; divergence only appears at the leaf.\n\nAlphaGo's core contribution was **learned priors** seeded into the tree. We\ndon't need learning — we have personality utility. Inject it as the `P(s,a)`\nterm in the PUCT / UCB1-with-prior formula:\n\n```\nscore(a) = Q(s,a) + c_puct × P(s,a) × sqrt(N(s)) / (1 + N(s,a))\n```\n\nWhere `P(s,a) = softmax(personality_utility(state, action) / temperature)`\nand `personality_utility` is the same `ScoringWeights`-driven evaluator used\nat the leaf.\n\nEffect: blackhammer's MCTS tree spends more branches on early assault\nvariants; goldvein's tree spends more branches on tech-up + defend variants.\nWithout the prior, both clans' trees are identical shape — only the leaf\nevaluator differs, and leaf evaluation is after 20+ turns of rollout where\nthe differentiating choice has already been washed out."
+ },
{
"id": "p0-35",
"title": "Ecology telemetry instrumentation — flora canopy / undergrowth fields in turn_stats.jsonl",
|