-
- {summariseUnlocks(p.tech.unlocks)}
-
-
-
- ))}
-
-
-
+
);
}
-
-function summariseUnlocks(u: TechUnlocks): string {
- const parts: string[] = [];
- const fmt = (id: string) => id.replace(/_/g, " ");
- if (u.buildings?.length) parts.push(...u.buildings.map(fmt));
- if (u.units?.length) parts.push(...u.units.map(fmt));
- if (u.improvements?.length) parts.push(...u.improvements.map(fmt));
- if (u.lenses?.length) parts.push(...u.lenses.map(id => `lens: ${fmt(id)}`));
- return parts.length ? parts.join(" · ") : "—";
-}
diff --git a/.project/designs/app/tsconfig.tsbuildinfo b/.project/designs/app/tsconfig.tsbuildinfo
index c7b49dc0..2f5c6d4f 100644
--- a/.project/designs/app/tsconfig.tsbuildinfo
+++ b/.project/designs/app/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/app.tsx","./src/audiosynth.ts","./src/main.tsx","./src/theme.ts","./src/components/combat/combatantcard.tsx","./src/components/combat/damagematrix.tsx","./src/components/combat/hpafterbar.tsx","./src/components/combat/hpslider.tsx","./src/components/combat/kwbanner.tsx","./src/components/combat/modifierlist.tsx","./src/components/combat/probabilitybar.tsx","./src/components/combat/unitbrowser.tsx","./src/components/combat/unitrow.tsx","./src/components/ui/button.tsx","./src/components/ui/panel.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tag.tsx","./src/data/allbuildings.ts","./src/data/allunits.ts","./src/data/audiopacks.ts","./src/data/scenarios.ts","./src/data/units.ts","./src/pages/audiopackdetail.tsx","./src/pages/audiopacks.tsx","./src/pages/audiosystem.tsx","./src/pages/buildingtrees.tsx","./src/pages/cityscreen.tsx","./src/pages/civilopedia.tsx","./src/pages/clanpersonality.tsx","./src/pages/combatcalculator.tsx","./src/pages/combatpreview.tsx","./src/pages/credits.tsx","./src/pages/designgallery.tsx","./src/pages/diplomacy.tsx","./src/pages/empiredashboard.tsx","./src/pages/endgamesummary.tsx","./src/pages/eraprogression.tsx","./src/pages/gamesetup.tsx","./src/pages/gdrustbridge.tsx","./src/pages/gdrustmap.tsx","./src/pages/hexformation.tsx","./src/pages/hud.tsx","./src/pages/index.tsx","./src/pages/mainmenu.tsx","./src/pages/notifications.tsx","./src/pages/pastgames.tsx","./src/pages/permutations.tsx","./src/pages/promotionpicker.tsx","./src/pages/replay.tsx","./src/pages/settings.tsx","./src/pages/statistics.tsx","./src/pages/techtree.tsx","./src/pages/turnsummary.tsx","./src/pages/unitactions.tsx","./src/pages/worldgen.tsx","./src/pages/hud/minimap.tsx","./src/pages/hud/chrome.ts","./src/pages/hud/positions.ts","./src/pages/worldgen/biometransitions.tsx","./src/pages/worldgen/climate.tsx","./src/pages/worldgen/ecology.tsx","./src/pages/worldgen/hydrology.tsx","./src/pages/worldgen/lab.tsx","./src/pages/worldgen/mappanel.tsx","./src/pages/worldgen/noiseanatomy.tsx","./src/pages/worldgen/pipelinepanel.tsx","./src/pages/worldgen/presets.tsx","./src/pages/worldgen/rng.tsx","./src/pages/worldgen/substrate.tsx","./src/pages/worldgen/tectonics.tsx","./src/pages/worldgen/terraincatalog.tsx","./src/pages/worldgen/whittakerplot.tsx","./src/pages/worldgen/_layerpage.tsx","./src/pages/worldgen/lab/mapcanvas.tsx","./src/pages/worldgen/lab/overlaytoggles.tsx","./src/pages/worldgen/lab/presetbar.tsx","./src/pages/worldgen/lab/tileinspector.tsx","./src/pages/worldgen/lab/constants.ts","./src/pages/worldgen/lab/mapinteractions.ts","./src/pages/worldgen/lab/observations.ts","./src/pages/worldgen/lab/render.ts","./src/pages/worldgen/lab/types.ts","./src/utils/combatcalc.ts","./src/utils/wasm/smoke.ts","./src/utils/wasm/usewasmgrid.ts","./src/utils/worldgen/hexcanvas.test.ts","./src/utils/worldgen/hexcanvas.ts","./src/utils/worldgen/indicatordecorations.ts","./src/utils/worldgen/noise.ts","./src/utils/worldgen/poisson.ts","./src/utils/worldgen/terrain.ts","../../reports/gd-rust-relationships.json","../../../public/resources/audio/library.json","../../../public/games/age-of-dwarves/data/audio/manifest.json","../../../public/games/age-of-dwarves/data/audio/pools.json"],"version":"5.9.3"}
\ No newline at end of file
+{"root":["./src/app.tsx","./src/audiosynth.ts","./src/main.tsx","./src/theme.ts","./src/components/combat/combatantcard.tsx","./src/components/combat/damagematrix.tsx","./src/components/combat/hpafterbar.tsx","./src/components/combat/hpslider.tsx","./src/components/combat/kwbanner.tsx","./src/components/combat/modifierlist.tsx","./src/components/combat/probabilitybar.tsx","./src/components/combat/unitbrowser.tsx","./src/components/combat/unitrow.tsx","./src/components/tree/treeview.tsx","./src/components/tree/types.ts","./src/components/ui/button.tsx","./src/components/ui/panel.tsx","./src/components/ui/tabs.tsx","./src/components/ui/tag.tsx","./src/data/allbuildings.ts","./src/data/allunits.ts","./src/data/audiopacks.ts","./src/data/scenarios.ts","./src/data/units.ts","./src/pages/audiopackdetail.tsx","./src/pages/audiopacks.tsx","./src/pages/audiosystem.tsx","./src/pages/buildingtrees.tsx","./src/pages/cityscreen.tsx","./src/pages/civilopedia.tsx","./src/pages/clanpersonality.tsx","./src/pages/combatcalculator.tsx","./src/pages/combatpreview.tsx","./src/pages/credits.tsx","./src/pages/culturetree.tsx","./src/pages/designgallery.tsx","./src/pages/diplomacy.tsx","./src/pages/empiredashboard.tsx","./src/pages/endgamesummary.tsx","./src/pages/eraprogression.tsx","./src/pages/gamesetup.tsx","./src/pages/gdrustbridge.tsx","./src/pages/gdrustmap.tsx","./src/pages/hexformation.tsx","./src/pages/hud.tsx","./src/pages/index.tsx","./src/pages/mainmenu.tsx","./src/pages/notifications.tsx","./src/pages/pastgames.tsx","./src/pages/permutations.tsx","./src/pages/promotionpicker.tsx","./src/pages/replay.tsx","./src/pages/settings.tsx","./src/pages/statistics.tsx","./src/pages/techtree.tsx","./src/pages/turnsummary.tsx","./src/pages/unitactions.tsx","./src/pages/worldgen.tsx","./src/pages/hud/minimap.tsx","./src/pages/hud/chrome.ts","./src/pages/hud/positions.ts","./src/pages/worldgen/biometransitions.tsx","./src/pages/worldgen/climate.tsx","./src/pages/worldgen/ecology.tsx","./src/pages/worldgen/hydrology.tsx","./src/pages/worldgen/lab.tsx","./src/pages/worldgen/mappanel.tsx","./src/pages/worldgen/noiseanatomy.tsx","./src/pages/worldgen/pipelinepanel.tsx","./src/pages/worldgen/presets.tsx","./src/pages/worldgen/rng.tsx","./src/pages/worldgen/substrate.tsx","./src/pages/worldgen/tectonics.tsx","./src/pages/worldgen/terraincatalog.tsx","./src/pages/worldgen/whittakerplot.tsx","./src/pages/worldgen/_layerpage.tsx","./src/pages/worldgen/lab/mapcanvas.tsx","./src/pages/worldgen/lab/overlaytoggles.tsx","./src/pages/worldgen/lab/presetbar.tsx","./src/pages/worldgen/lab/tileinspector.tsx","./src/pages/worldgen/lab/constants.ts","./src/pages/worldgen/lab/mapinteractions.ts","./src/pages/worldgen/lab/observations.ts","./src/pages/worldgen/lab/render.ts","./src/pages/worldgen/lab/types.ts","./src/utils/combatcalc.ts","./src/utils/wasm/smoke.ts","./src/utils/wasm/usewasmgrid.ts","./src/utils/worldgen/hexcanvas.test.ts","./src/utils/worldgen/hexcanvas.ts","./src/utils/worldgen/indicatordecorations.ts","./src/utils/worldgen/noise.ts","./src/utils/worldgen/poisson.ts","./src/utils/worldgen/terrain.ts","../../reports/gd-rust-relationships.json","../../../public/resources/audio/library.json","../../../public/games/age-of-dwarves/data/audio/manifest.json","../../../public/games/age-of-dwarves/data/audio/pools.json"],"version":"5.9.3"}
\ No newline at end of file
diff --git a/.project/objectives/p1-36-ai-personalities-t1-t10-coverage.md b/.project/objectives/p1-36-ai-personalities-t1-t10-coverage.md
index 38846642..1ca051eb 100644
--- a/.project/objectives/p1-36-ai-personalities-t1-t10-coverage.md
+++ b/.project/objectives/p1-36-ai-personalities-t1-t10-coverage.md
@@ -98,3 +98,24 @@ User-requested R2 fix: "bump the clan_affinity scoring constant in `tactical/pro
**Real failure: `pick_best_melee` only considers `unit_type == "melee"`.** `production.rs:373` filters to melee units only. Deepforge's signature units (`forge_titan`, `steam_walker`, `iron_strider`, `rail_cannon`) are `unit_type: "siege"` or `"walker"` — they will NEVER be picked by `pick_best_melee`, regardless of affinity weight. There is currently no `pick_best_siege` or `pick_best_walker` selector. Deepforge's AI defaulting to warrior is **structurally correct given the function only considers melee**.
**Real R2 fix (deferred):** add parallel selectors `pick_best_siege`, `pick_best_walker`, `pick_best_ranged` (mirror of `pick_best_melee` with different `unit_type` filter) and a strategic-axis-driven dispatcher that picks among them based on the AI's clan personality (Deepforge prefers siege/walker; Goldvein prefers ranged; Ironhold prefers heavy melee; Blackhammer prefers light melee/cavalry; Runesmith mixes). This is a `game-ai` `mc-ai` enhancement, ~3-5 hours of work. Bumping the existing constant would not move the metric.
+
+## Cycle 3 — production selector expansion (LANDED 2026-05-03)
+
+`src/simulator/crates/mc-ai/src/tactical/production.rs` refactored: `pick_best_melee` extracted into generic `pick_best_unit_of_type(unit_type, …)` parameterized by unit_type string. New `pick_best_unit_for_clan(clan_id, …)` dispatcher implements the per-clan archetype ladder:
+
+| Clan | Preferred ladder |
+|---|---|
+| deepforge | siege → ranged → melee |
+| goldvein | ranged → melee → siege |
+| blackhammer | melee → siege → ranged |
+| ironhold | melee → siege → ranged |
+| runesmith | ranged → melee → siege |
+| (default) | melee → siege → ranged |
+
+Algorithm: for each preferred type, pick the highest-tier buildable unit; only return when the result is a CLAN-AFFINITY match (score=2). Generic units fall through so the next type can fire. If no archetype match exists in any preferred type, fall back to the legacy `pick_best_melee` (returns generic warrior/etc.). `pick_for_city` callsite at line 224 swapped to call `pick_best_unit_for_clan`. The local `melee_id` variable name retained for diff stability — semantic remains "the AI's chosen military unit for this city".
+
+7 new tests cover: siege selector returns siege only, dispatcher routes deepforge → siege, goldvein → ranged, blackhammer → melee affinity (not generic), fallback to melee when no archetype buildable, affinity-over-generic preference within same type, empty catalog → None, unknown clan → default ladder. **mc-ai 222/222 lib tests pass** (was 215, +7 new). Cargo workspace check clean.
+
+Verification batch chained with p1-30 cycle 3 retune (`~/Code/project-buildspace/magic-civilization/.local/iter/huge-map-5clan-20260503_103147` on apricot, 10 seeds × T500 × 5 AI personalities). The autoplay-batch.sh p1-45 prebuild step rebuilds the GDExt with the dispatcher in effect; results will populate the table below once batch completes.
+
+**Status remains `partial` until the batch confirms 4/5 clans show distinct top-3 production histograms.**