> **Generated by `@lilith/mcp-objectives` — do not hand-edit.** Source of truth is per-file YAML frontmatter in this directory. Completed: [DASHBOARD_COMPLETED.md](DASHBOARD_COMPLETED.md) · By category: [DASHBOARD_CATEGORIES.md](DASHBOARD_CATEGORIES.md).
> **Generated by `tools/objectives-report.py` — do not hand-edit.** Source of truth is per-file YAML frontmatter in this directory.
| [p0-15](p0-15-happiness-golden-age.md) | ✅ done | Happiness pool and Golden Age mechanics end-to-end | [shipwright](../team-leads/shipwright.md) | 2026-04-17 |
| [p0-26](p0-26-ai-tactical-rust-port.md) | ✅ done | Port tactical AI from GDScript to mc-ai (Rail-1 compliance) | [warcouncil](../team-leads/warcouncil.md) | 2026-04-18 |
| [p0-27](p0-27-gd-culture-bridge.md) | ✅ done | GdCulture bridge — live game delegates culture to mc-culture | [shipwright](../team-leads/shipwright.md) | 2026-04-17 |
| [p0-28](p0-28-gd-economy-bridge.md) | ✅ done | GdEconomy bridge — live game delegates gold/upkeep to mc-economy | [shipwright](../team-leads/shipwright.md) | 2026-04-17 |
| [p0-29](p0-29-gd-tech-bridge.md) | ✅ done | GdTechWeb bridge — live game delegates research to mc-tech | [shipwright](../team-leads/shipwright.md) | 2026-04-17 |
| [p0-41](p0-41.md) | ✅ done | Building rally points — produced units auto-deploy to a designated hex | [shipwright](../team-leads/shipwright.md) | 2026-04-24 |
| [p0-42](p0-42.md) | ✅ done | Formation aggregation — adjacent units link into a shaped formation with terrain reflow | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
| [p0-42a](p0-42a-formation-smoke.md) | ✅ done | Formation aggregation smoke — formations form and evolve at runtime | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
| [p0-43](p0-43.md) | ✅ done | "Formation AI — MCTS plans at formation level, not per-unit" | [warcouncil](../team-leads/warcouncil.md) | 2026-04-25 |
| [p1-12](p1-12-build-output-docs-alignment.md) | ✅ done | Align every doc reference to the relocated wasm-pack output | [tourguide](../team-leads/tourguide.md) | 2026-04-17 |
| [p1-13](p1-13-guide-dev-route-coverage.md) | ✅ done | Guide dev server boots on plum with zero-error route coverage | [tourguide](../team-leads/tourguide.md) | 2026-04-17 |
| [p1-15](p1-15-guide-next-deploy-infra.md) | ✅ done | Deploy dev guide to https://mc.next.black.local | [tourguide](../team-leads/tourguide.md) | 2026-04-17 |
| [p1-16](p1-16-guide-game1-scope-hygiene.md) | ✅ done | Purge Game 2/3 scope bleed from user-visible Game 1 guide copy | [tourguide](../team-leads/tourguide.md) | 2026-04-18 |
| [p1-17](p1-17-guide-next-auto-deploy.md) | ✅ done | Forgejo workflow auto-deploys dev guide on push to main | [tourguide](../team-leads/tourguide.md) | 2026-04-18 |
| [p1-20](p1-20-unit-action-capability-registry.md) | ✅ done | Unit action capability registry — one source of truth for "what can this unit do right now?" | [wireguard](../team-leads/wireguard.md) | 2026-04-19 |
| [p1-21](p1-21-unit-patrol-orders.md) | ✅ done | Unit patrol orders — standing order to loop between waypoint tiles | [wireguard](../team-leads/wireguard.md) | 2026-04-19 |
| [p1-26](p1-26-tile-placement-preview-ux.md) | ✅ done | "Tile-placement UX with effect preview — Civ7-style \\\"where does this go and what changes\\\"" | [shipwright](../team-leads/shipwright.md) | 2026-04-26 |
| [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 |
| [p1-28](p1-28-culture-research-tree.md) | ✅ done | "Culture research tree — real graph, bridge, UI" | [shipwright](../team-leads/shipwright.md) | 2026-04-26 |
| ID | Status | Title | Tags | Owner | Updated | Blocked |
|---|---|---|---|---|---|---|
| [p2-10](p2-10-regression-ci-gate.md) | 🟡 partial | Automated regression CI gate on every push to main | — | [testwright](../team-leads/testwright.md) | 2026-04-23 | 🟢 unblocked |
| [p2-06b](p2-06b-windows-runner.md) | ✅ done | Cross-compile Windows .exe + .dll from Linux via cargo-xwin (no Windows host) | [shipwright](../team-leads/shipwright.md) | 2026-04-25 |
| [p2-07](p2-07-credits-screen.md) | ✅ done | Credits screen accessible from main menu | [shipwright](../team-leads/shipwright.md) | 2026-04-17 |
| [p2-09](p2-09-guide-web-deploy.md) | ✅ done | Player guide web app — builds clean from source | — | 2026-04-17 |
| [p2-10](p2-10-regression-ci-gate.md) | 🟡 partial | Automated regression CI gate on every push to main | [testwright](../team-leads/testwright.md) | 2026-04-23 |
> These objectives are explicitly deferred. They are tracked for visibility but not blocking the current release.
> These objectives are explicitly future-scope. **Game 2 (Age of Kzzykt)** items introduce leylines, the Green school, and spacefaring. **Game 3 (Age of Elves)** items cover the full five-school magic system, Archons, and Arcane Ascension. None are part of the Game 1 Early Access release.
| ID | Status | Title | Tags | Owner | Updated | Blocked |
> These objectives were split into narrower children. Files are retained as index stubs so external references do not 404.
> These objectives were split into narrower children. Files are retained as index stubs so external references don't 404. The `superseded_by:` frontmatter field names the replacement IDs.
| ID | Status | Title | Tags | Owner | Updated | Blocked |
@ -37,12 +37,13 @@ This objective splits every bundle into per-building files, matching the units c
## Acceptance
- ✗ Each of the 11 category bundle files is split: every building entry becomes its own `resources/buildings/<id>.json` file (single-element JSON array per `BUILDING_SCHEMA.md`). The 11 bundle files are deleted afterwards.
- ✗ `data/buildings/manifest.json::includes` is updated to either (a) list every individual building ID (matching the units manifest convention), or (b) be removed entirely if the data loader can discover files by directory walk (it already does — see `data_loader.gd::_load_category_dir`). Pick the convention that matches units; document choice inline.
- ✗ `python3 tools/validate-game-data.py` passes 0 failures pre-split and 0 failures post-split. Same building-id count loaded by the engine before and after (audit count: 150 unique IDs across resources + data).
- ✗ A diff-friendly migration: each new file's content is identical to the entry that lived inside the bundle (same JSON dict, wrapped in single-element array). No content edits piggyback on the structural change. Reviewers can verify "this file was extracted, not modified."
- ✗ The 13 known data-vs-resources ID conflicts (`forge`, `walls`, `library`, `marketplace`, `monument`, `temple`, `barracks`, `siege_workshop`, plus 5 wonder duplicates in `mundane_wonders.json`) are surfaced into a follow-up audit objective rather than silently consolidated. Today: `data/buildings/forge.json` (cost 60, tier 1) silently overrides `resources/buildings/military.json``forge` (cost 100, tier 2) — this drift is not the scope of THIS objective, but it should be visible after the split.
- ✗ The encyclopedia / city-screen building list shows the same 150 IDs in the same order as before (stable sort — `data_loader.gd::_list_json_files_sorted` already sorts lexicographically, so per-file split won't reshuffle).
- ✓ Each of the 11 category bundle files split: every building entry extracted to `resources/buildings/<id>.json` (bare-object form, matching the existing 42 single-file convention in that directory rather than the array-wrapped form documented in `BUILDING_SCHEMA.md`; loader's `_extract_entries` accepts both). 66 files extracted; 11 bundle files deleted.
- ✓ `data/buildings/manifest.json::includes` regenerated to list every individual building ID (108 entries), matching the units manifest convention. Loader does not consume the manifest (`grep -rn` confirms zero references in `src/`); kept as documentary index per the units pattern.
- ✓ Diff-friendly migration: extraction script preserved each entry's JSON content verbatim (same dict, same key order, no content edits). Bundles deleted in same operation.
- ✓ Pre-vs-post ID set unchanged for `resources/buildings/`: 108 unique IDs before, 108 after — extraction script's own delta check returned 0 lost / 0 gained.
- ✓ The 13 data-vs-resources ID conflicts (`forge`, `walls`, `library`, `marketplace`, `monument`, `temple`, `barracks`, `siege_workshop`, `clan_moot_stone`, `covenant_stone`, `grand_observatory`, `hall_of_ancestors`, `voice_of_ages`, `world_pillar`) remain visible after the split — each is now a per-file resources entry that is silently overridden by a per-file or wonder-bundle data entry. Reconciliation deferred to a follow-up audit objective per the original out-of-scope note.
- ✓ Loader still walks `resources/buildings/` lexicographically (`data_loader.gd::_list_json_files_sorted`) — per-file split does not reshuffle determinism.
title: "mc-ai clan_affinity routing — Rust AI reads unit clan_affinity at build-decision time"
priority: p1
status: missing
status: partial
scope: game1
owner: warcouncil
updated_at: 2026-04-27
assigned_by: shipwright
blockedBy: [p1-34, p1-36]
evidence:
- "src/simulator/crates/mc-ai/src/tactical/state.rs — TacticalUnitSpec struct extended with `clan_affinity: Vec<String>` (list of clan IDs that prefer this unit) and `archetype: Option<String>` (mirrors units/*.json::archetype). Both #[serde(default)] for backward compat with pre-p1-34 fixtures."
- "src/simulator/crates/mc-ai/src/tactical/production.rs — pick_best_melee() signature gained `clan_id: &str`; new clan_affinity_score() returns 2 (affinity match), 1 (generic empty list, neutral), or 0 (off-clan). Sort key changed from `(tier, id)` to `(clan_affinity_score, tier, Reverse(id))` so affinity dominates tier within the same eligibility band but generic units beat off-clan at same tier."
- "Call site at production.rs:224 (decide_production → pick_for_city) now passes `&player.clan_id` from TacticalPlayerState."
- "4 new tests in tactical::production::tests::clan_affinity_* — all passing: match_outranks_off_clan_at_same_tier, off_clan_still_buildable_when_no_alternative, generic_unit_neutral_fallback, higher_tier_match_beats_lower_tier_match."
- "Full mc-ai test sweep: 190 passed, 0 failed (PATH=$HOME/.cargo/bin cargo test -p mc-ai --lib). No regressions in the existing 24 production tests after threading clan_id through."
- "Workspace cargo build clean: only pre-existing warnings (magic-civ-physics 8 warns, mc-sim bin warnings)."
- "No new GDScript AI logic added — all clan_affinity routing lives in Rust per Rail #1. ai_turn_bridge.gd is bridge-only and unchanged."
remaining_work:
- "Apricot 10-seed T300 batch run (acceptance criterion 5) — verifying unit-mix histogram divergence (Blackhammer ≥40% light melee; Deepforge ≥30% siege/walker; Ironhold ≥40% heavy melee) requires apricot SSH and cargo run on the multi-tenant build host. The data-side wiring is complete and tests prove the algorithm; behavioral measurement deferred to a separate batch-run pass."
- "GDExtension passthrough — the bridge that hands TacticalUnitSpec from GDScript to Rust may need updating to populate the new clan_affinity / archetype fields. Quick check needed: `grep -rn 'TacticalUnitSpec' src/simulator/crates/api-gdext/src/` to see if there's a Godot-side conversion that needs the new fields. If unit data flows directly from JSON via serde, no change needed (the #[serde(default)] keeps deserialization tolerant)."