diff --git a/.project/objectives/DASHBOARD_CATEGORIES.md b/.project/objectives/DASHBOARD_CATEGORIES.md
index 979f2d36..5c490a15 100644
--- a/.project/objectives/DASHBOARD_CATEGORIES.md
+++ b/.project/objectives/DASHBOARD_CATEGORIES.md
@@ -114,7 +114,7 @@
| [p1-25](p1-25-export-script-error-cleanup.md) | β
done | P1 | Eliminate parse-error spam in export logs (Unit dup decl + SaveManager stray) | [shipwright](../team-leads/shipwright.md) | π’ |
| [p1-26](p1-26-tile-placement-preview-ux.md) | π‘ partial | P1 | Tile-placement UX with effect preview β Civ7-style \"where does this go and what changes\" | [shipwright](../team-leads/shipwright.md) | π’ |
| [p1-27](p1-27-mcts-service-extraction.md) | β missing | P1 | Extract GPU MCTS into a standalone service/client (model-boss-shaped, magic-civ-only) | [warcouncil](../team-leads/warcouncil.md) | π’ |
-| [p1-28](p1-28-culture-research-tree.md) | π‘ partial | P1 | Culture research tree β real graph, bridge, UI | [shipwright](../team-leads/shipwright.md) | π’ |
+| [p1-28](p1-28-culture-research-tree.md) | β
done | P1 | Culture research tree β real graph, bridge, UI | [shipwright](../team-leads/shipwright.md) | π’ |
| [p2-01](p2-01-minimap-improvements.md) | β
done | P2 | Minimap β fog reflection and unit markers | [shipwright](../team-leads/shipwright.md) | π’ |
| [p2-02](p2-02-hud-tooltips.md) | β
done | P2 | Tooltips on all HUD elements | [shipwright](../team-leads/shipwright.md) | π’ |
| [p2-03](p2-03-hotkey-cheat-sheet.md) | β
done | P2 | Hotkey cheat sheet (F1 / ?) | [shipwright](../team-leads/shipwright.md) | π’ |
diff --git a/.project/objectives/DASHBOARD_COMPLETED.md b/.project/objectives/DASHBOARD_COMPLETED.md
index 37f60973..5a481d60 100644
--- a/.project/objectives/DASHBOARD_COMPLETED.md
+++ b/.project/objectives/DASHBOARD_COMPLETED.md
@@ -76,6 +76,7 @@
| [p1-23](p1-23-stats-tracker-restore.md) | Restore StatsTracker β demographics overview broken in shipped builds | β | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
| [p1-24](p1-24-windows-path-separator.md) | ai_personalities.json fails to load from packed builds (all platforms) β pass JSON contents not path | β | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
| [p1-25](p1-25-export-script-error-cleanup.md) | Eliminate parse-error spam in export logs (Unit dup decl + SaveManager stray) | β | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
+| [p1-28](p1-28-culture-research-tree.md) | Culture research tree β real graph, bridge, UI | β | [shipwright](../team-leads/shipwright.md) | 2026-04-26 |
| [p2-06](p2-06-export-pipeline.md) | Export pipeline for Windows / macOS / Linux | β | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
| [p2-28](p2-28-sprite-provenance-ledger.md) | Sprite provenance ledger β LICENSES.md per-file attribution | β | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-25 |
diff --git a/.project/objectives/README.md b/.project/objectives/README.md
index 4fe67b63..9a71a971 100644
--- a/.project/objectives/README.md
+++ b/.project/objectives/README.md
@@ -15,10 +15,10 @@
| Priority | π΅ | π‘ | π΄ | β | β« | β
| Total |
|---|---|---|---|---|---|---|---|
| **P0** | 0 | 1 | 0 | 0 | 0 | 42 | 43 |
-| **P1** | 0 | 6 | 0 | 7 | 1 | 25 | 39 |
+| **P1** | 0 | 5 | 0 | 7 | 1 | 26 | 39 |
| **P2** | 0 | 2 | 1 | 0 | 0 | 28 | 31 |
| **P3 (oos)** | 0 | 0 | 0 | 1 | 19 | 0 | 20 |
-| **total** | **0** | **9** | **1** | **8** | **20** | **95** | **133** |
+| **total** | **0** | **8** | **1** | **8** | **20** | **96** | **133** |
@@ -28,7 +28,7 @@
|---|---|
| [asset-sprite](../team-leads/asset-sprite.md) | 6 |
| [warcouncil](../team-leads/warcouncil.md) | 4 |
-| [shipwright](../team-leads/shipwright.md) | 3 |
+| [shipwright](../team-leads/shipwright.md) | 2 |
| [asset-audio](../team-leads/asset-audio.md) | 1 |
| [envoy](../team-leads/envoy.md) | 1 |
| [testwright](../team-leads/testwright.md) | 1 |
@@ -49,7 +49,6 @@
| [p1-05](p1-05-balance-tuning.md) | π‘ partial | Balance tuning β pop_peak β₯30 median, worker improvements β₯8 min | β | [shipwright](../team-leads/shipwright.md) | 2026-04-25 | π’ unblocked |
| [p1-22](p1-22-mcts-wall-clock-budget.md) | π‘ partial | MCTS per-decision wall-clock budget β bound per-turn cost on huge maps | β | [warcouncil](../team-leads/warcouncil.md) | 2026-04-25 | π’ unblocked |
| [p1-26](p1-26-tile-placement-preview-ux.md) | π‘ partial | Tile-placement UX with effect preview β Civ7-style \"where does this go and what changes\" | β | [shipwright](../team-leads/shipwright.md) | 2026-04-26 | π’ unblocked |
-| [p1-28](p1-28-culture-research-tree.md) | π‘ partial | Culture research tree β real graph, bridge, UI | β | [shipwright](../team-leads/shipwright.md) | 2026-04-26 | π’ unblocked |
| [p2-22](p2-22-sprite-generation-pipeline.md) | π‘ partial | Sprite generation pipeline β runnable end-to-end | β | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-25 | π’ unblocked |
| [p1-27](p1-27-mcts-service-extraction.md) | β missing | Extract GPU MCTS into a standalone service/client (model-boss-shaped, magic-civ-only) | β | [warcouncil](../team-leads/warcouncil.md) | 2026-04-25 | π’ unblocked |
| [p2-16](p2-16-audio-assets.md) | β missing | Audio assets β SFX + music .ogg files shipped | β | [asset-audio](../team-leads/asset-audio.md) | 2026-04-17 | π’ unblocked |
diff --git a/.project/objectives/objectives.json b/.project/objectives/objectives.json
index a4e1fba8..e3151357 100644
--- a/.project/objectives/objectives.json
+++ b/.project/objectives/objectives.json
@@ -1,9 +1,9 @@
{
- "generated_at": "2026-04-26T12:02:46Z",
+ "generated_at": "2026-04-26T20:52:19Z",
"totals": {
- "done": 95,
+ "done": 96,
"in_progress": 0,
- "partial": 9,
+ "partial": 8,
"stub": 1,
"missing": 8,
"oos": 20,
@@ -796,7 +796,7 @@
"id": "p1-28",
"title": "Culture research tree β real graph, bridge, UI",
"priority": "p1",
- "status": "partial",
+ "status": "done",
"scope": "game1",
"owner": "shipwright",
"updated_at": "2026-04-26",
@@ -1458,7 +1458,7 @@
},
{
"owner": "shipwright",
- "remaining": 3
+ "remaining": 2
},
{
"owner": "asset-audio",
diff --git a/.project/objectives/p1-28-culture-research-tree.md b/.project/objectives/p1-28-culture-research-tree.md
index 3434da23..e32c8eb1 100644
--- a/.project/objectives/p1-28-culture-research-tree.md
+++ b/.project/objectives/p1-28-culture-research-tree.md
@@ -7,11 +7,9 @@ scope: game1
owner: shipwright
updated_at: 2026-04-26
evidence:
- - GdCultureWeb registered in libmagic_civ_physics.x86_64.so (built 2026-04-26 on apricot from HEAD)
- - "culture_tree_proof.tscn renders correctly: title 'Culture Tree', 6 pillars, 30 traditions, available T1 cards yellow with Research buttons, indicator badges (B/E/W), detail-panel hint text"
- - "Phase-gate screenshot at .project/screenshots/culture_tree_proof_2026-04-26.png (1920Γ1080, 116 KB)"
- - "DataLoader BLOCKER fixed: proof scene now calls DataLoader.load_theme + ThemeVocabulary.load_vocabulary if not populated; capture_screenshot.gd's culture_tree_proof route ensures the theme is loaded before scene change"
- - "5 dangling culture prereqs fixed in artisanship.json/legacy.json/oral_tradition.json/philosophy.json/statecraft.json (civil_engineering, world_theory, scholarship, governanceΓ2, clan_law, high_lore removed)"
+ - "culture_tree_proof screenshot (apricot, 2026-04-26_08-13-47): 1920x1080, 6 pillars (Ancestor Worship, Artisanship, Legacy, Oral Tradition, Philosophy, Statecraft), 30 tradition cards rendered with tier/cost/unlock badges, 2 highlighted researched traditions, detail panel visible. Proof scene ran via capture_screenshot.gd routing with DataLoader.load_theme('age-of-dwarves') pre-load fix. GdCultureWeb confirmed: 30 traditions across 6 pillars at runtime."
+ - GdCultureWeb in libmagic_civ_physics.x86_64.so on apricot (built 2026-04-26)
+ - "7 dangling culture prereqs removed: civil_engineering, world_theory, scholarship, governanceΓ2, clan_law, high_lore"
---
## Summary
diff --git a/public/games/age-of-dwarves/guide/src/data/game.ts b/public/games/age-of-dwarves/guide/src/data/game.ts
index 8c1db305..0774366b 100644
--- a/public/games/age-of-dwarves/guide/src/data/game.ts
+++ b/public/games/age-of-dwarves/guide/src/data/game.ts
@@ -93,8 +93,12 @@ const allowedBuildingSources = new Set(buildingsManifest.includes as readonly st
const allBuildingsFlat: Building[] = Object.entries(buildingMods).flatMap(([pathStr, mod]) => {
const stem = (pathStr.split('/').pop() ?? '').replace(/\.json$/, '')
- if (!allowedBuildingSources.has(stem)) return []
- return Array.isArray(mod) ? mod : [mod]
+ const items: Building[] = Array.isArray(mod) ? mod : [mod]
+ // Wonders (wonder_type != null) bypass the manifest allowlist β they live as
+ // individual files and don't need to be enumerated there. building_categories.json
+ // is correctly excluded: it has no wonder_type field, so wonder_type is undefined,
+ // and undefined != null is false in JS.
+ return items.filter(item => allowedBuildingSources.has(stem) || item.wonder_type != null)
})
export const allBuildings: Building[] = allBuildingsFlat.filter((b) => b.wonder_type == null)
diff --git a/src/simulator/crates/mc-ai/src/tactical/movement.rs b/src/simulator/crates/mc-ai/src/tactical/movement.rs
index d8254516..5ad92580 100644
--- a/src/simulator/crates/mc-ai/src/tactical/movement.rs
+++ b/src/simulator/crates/mc-ai/src/tactical/movement.rs
@@ -998,7 +998,8 @@ mod tests {
#[test]
fn dominance_factor_gate_marches_on_city() {
// 2 own military vs 1 enemy β own/enemy = 2.0 >= DOMINANCE_FACTOR
- // (1.25). City closer than the stray enemy (dist 5 vs dist 9).
+ // (2.0 β bumped 2026-04-26 from 1.25 to slow rush-domination).
+ // City closer than the stray enemy (dist 5 vs dist 9).
// Expect a move TOWARD the enemy city rather than the stray.
let me = player(
0,
diff --git a/src/simulator/crates/mc-ai/src/tactical/production.rs b/src/simulator/crates/mc-ai/src/tactical/production.rs
index d13a8978..699d389a 100644
--- a/src/simulator/crates/mc-ai/src/tactical/production.rs
+++ b/src/simulator/crates/mc-ai/src/tactical/production.rs
@@ -63,7 +63,16 @@ const CULTURE_AXIS_MONUMENT_THRESHOLD: u8 = 4;
/// Aggression multiplier above which the player counts as dominant over the
/// opposing field (mirrors GDScript DOMINANCE_FACTOR).
-const DOMINANCE_FACTOR: f32 = 1.25;
+///
+/// Bumped 2026-04-26 from 1.25 β 2.0 to slow rush-domination dynamics.
+/// Pre-bump observation (warcouncil cycle-3 wonder6 batch): 5/10 games ended
+/// at T48-T121 via early-domination before tier-3 tech research could complete,
+/// leaving median peak_unit_tier at 2 and tier_peak_gap at 5-6 (one AI
+/// monopolizes tech tree). At 2.0Γ the AI requires real military superiority
+/// before committing to attack, giving losers more time to develop and
+/// closing the symmetry gap. Composes with personality `aggression` axis
+/// (blackhammer agg=9 still rushes via apply_axes military scaling).
+const DOMINANCE_FACTOR: f32 = 2.0;
/// Capital walls interject: non-threatened 1-city capital older than this
/// many turns slots walls in before the general fallback.
|