magicciv/.project/objectives/p2-71-bench-projector-enrichment.md
Natalie 7d111acb1a fix(@projects/@magic-civilization): 🐛 resolve ai personality loading and turn processing
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-11 12:27:23 -07:00

9.3 KiB
Raw Blame History

id title priority status scope category owner created updated_at blocked_by follow_ups
p2-71 Bench projector enrichment — make MCTS see a real tactical surface p2 partial game1 simulation simulator-infra 2026-05-12 2026-05-11
p2-67

Context

p2-67 Phase 13 blocked partly here. The AI is correctly wired through project_tactical → run_ai_turn → apply_ai_action (p2-68 Waves 1+3+4 + p2-69), and Phase 11's TurnProcessor::step proves Claude's slot ticks per-turn — but the production AI returns empty action chains past turn 0.

Root cause: mc-player-api/src/projection.rs::project_tactical was written as a v1 minimal projector and deliberately omitted several fields with #[serde(default)]-tolerant fallbacks. decide_tactical_actions bottoms out on:

  • TacticalState.unit_catalog empty → no legal unit-build choices.
  • TacticalState.building_catalog empty → no legal building-queue choices.
  • Per-tile yields zero → city placement scoring uniform.
  • No move-cost data → unit moves have no cost signal.
  • strategic_axes / personality scoring tables empty → MCTS prior is uniform.

The MCTS isn't broken; it's correctly returning "no productive action" because the projection it sees has nothing productive to do.

Source-of-truth rails

  • Rust crate: edit mc-player-api/src/projection.rs and (if needed) thread catalog handles through dispatch::apply_end_turn. Catalogs already exist in mc-units::UnitsCatalog (p2-67 Phase 9) and need a sibling in mc-buildings.
  • JSON path: none — projector reads existing public/games/age-of-dwarves/data/{units,buildings}/*.json via the loaders.
  • GDScript: harness wiring only — pass catalog handles into GdPlayerApi at boot.

Surface

1. Catalog plumbing

  • UnitsCatalog already loaded in claude_player_main.gd for MapUnit::new(...). Pass it through GdPlayerApi::new(...) so project_tactical can read it.
  • Add BuildingsCatalog (mirror UnitsCatalog pattern). Load once at harness boot.
  • Optional: TerrainCatalog for per-tile yield lookups.

2. Projector enrichment

In project_tactical:

  • Populate tactical.unit_catalog from UnitsCatalog — convert each UnitDef to the TacticalUnitDef shape mc-ai expects (cost, moves, attack, defense, prerequisites).
  • Populate tactical.building_catalog from BuildingsCatalog.
  • Per-tile yields: for each tactical.tiles[i], set food/production/gold/science/culture from the TerrainCatalog × current improvements/biome lookup. Mirror the formula used in mc-city::tile_yield::compute_yield.
  • Populate strategic_axes from ScoringWeights (already set per-player via p2-67 Wave 1 set_player_personality_json).
  • Populate promotion_*_weight / difficulty_threshold_mult from the personality table.

3. Smoke verification

After enrichment, re-run the 3-player 5-EndTurn smoke. Acceptance: AI slots emit actions_applied > 0 on turn 1+ (not just turn 0), with action variants varying by personality (blackhammer aggressive, deepforge defensive, etc.).

4. Test coverage

  • Unit test: project_tactical populates unit_catalog.len() > 0.
  • Unit test: per-tile yields non-zero for at least one non-ocean tile.
  • Integration test: 5-EndTurn driven game produces a non-empty AI action chain on each turn for each AI slot.

Acceptance

  • mc-player-api::projection::project_tactical populates unit_catalog, building_catalog, strategic_axes, personality weights (clan_id, promotion_*_weight). [evidence: crates/mc-player-api/src/projection.rs:442-470; tests tactical_carries_unit_catalog_from_state, tactical_carries_building_catalog_from_state, tactical_clan_id_round_trips_through_player_state, tactical_promotion_weights_round_trip]
  • ⚠ Per-tile yields: deferred — bench TileState carries no food/prod/gold triple (only biome label + ecology fields). The closest formula mc_city::yield_fold::tile_yields_from_collectibles reads collectibles, not biome→yield. p2-71-followup tracks porting a biome-yield lookup into the projector. The 5-turn smoke does not block on this — see findings below.
  • BuildingsCatalog exists as Vec<TacticalBuildingSpec> held on GameState::ai_building_catalog (mirror of UnitsCatalog pattern, simpler since the building catalog is consumed only by the projector — no runtime sim need). [evidence: crates/mc-turn/src/game_state.rs:336-358]
  • GdPlayerApi accepts catalog handles via setters: set_units_catalog_json, set_buildings_catalog_json, set_difficulty_threshold_mult, plus unit_catalog_len / building_catalog_len debug readers. [evidence: api-gdext/src/player_api.rs]
  • ⚠ 5-EndTurn smoke shows actions_applied > 0 on AI slots for turn 1 only; turns 2-5 emit actions_applied=0. [evidence: scripts/claude-smoke-5endturn.sh run on apricot 2026-05-11, both AI slots emit 1 action on turn 1 (Action::EnqueueBuild of a tier-1 unit), zero thereafter.]
    • Root cause: mc_ai::tactical::production::pick_for_city skips cities with non-empty queues; once a unit is queued on turn 1, the queue stays full for ~30 turns at 1 prod/turn. The other action sources (movement, fortify, founder→FoundCity, attack) are not emitting for the bench geometry: starter units are 3 warriors clustered at the capital with no enemy contact, no settler/founder unit (the militarist starter ships only warriors), and no resource targets within scout range. Personality/threshold scoring correctly returns "no productive action" for this turn topology.
  • ☑ AI action variants differ by personality on turn 1 — both slots emit one EnqueueBuild action; the item picked differs by clan (blackhammer slot 1 vs goldvein slot 2 in observed run). Differentiation across turns 2-5 is moot because zero actions emit. Follow-up gap: a richer smoke needs an initial state with a settler unit or visible enemies.
  • ☑ Unit tests prove projector enrichment: 7 new tests in crates/mc-player-api/src/projection.rs (84/84 passing, was 77/77). Integration test for full 5-turn chain is the smoke script.
  • cargo test -p mc-player-api --lib 84/84 green; cargo test -p mc-ai --lib 240/240 green; workspace cargo check clean.
  • ⚠ p2-68 acceptance bullet "smoke-non-trivial-AI-chains" — turn 1 now non-trivial (catalogs working). Turns 2-5 still zero. Bullet stays ⚠ pending the follow-up.

Findings (2026-05-11) — what enrichment proved

Before p2-71: ALL AI turns (0..N) emitted actions_applied = 0. The projector was returning empty unit_catalog / building_catalog, so pick_for_city had nothing to queue and mc_ai correctly returned an empty action chain every turn.

After p2-71: Turn 1 emits 1 action per AI slot — both slots successfully pick a tier-1 unit from the 160-entry unit catalog (via pick_best_melee) and queue it via Action::EnqueueBuild. This proves the catalog plumbing + projection are wired correctly end-to-end (GD → setter → GameState::ai_unit_catalog → projector → TacticalStatepick_for_city → AI dispatch).

The remaining zero-emission gap on turns 2-5 is not a projector defect. It is the combined effect of:

  1. Single-slot per-city production queue blocks EnqueueBuild once filled.
  2. Starter inventory has no settler/founder, so FoundCity actions never fire.
  3. Bench mapgen places capitals far apart, so warrior MoveUnit has no productive target (no enemy contact, no resource hex within move range).
  4. Fortify actions are not in the chain emitted by decide_tactical_actions for this state shape.

The right next move is a follow-up objective widening the starter inventory (add a settler/founder to the militarist init) or the AI's idle behaviour (emit Fortify for stationary military units when no movement target scores).

p2-71 Status

Status: partial. Catalog plumbing + personality projection landed and proven. Per-tile yields deferred. 5-EndTurn smoke shows hard-stop condition on turns 2-5; documented above.

Follow-up objectives (will be filed separately):

  • p2-71a — Port mc_city::biome_yield semantics into a TacticalTile::yields lookup so city placement / citizen scoring has terrain signal.
  • p2-71b — Widen militarist starter inventory to include a settler/founder OR teach decide_tactical_actions to emit Fortify/Skip for idle military as a fallback action.

Why this size

  • BuildingsCatalog: ~2 hr (mirror UnitsCatalog).
  • Catalog plumbing through GdPlayerApi: ~2 hr.
  • Projector enrichment: ~3 hr (walk each field, port lookup).
  • Tile yield port: ~2 hr (compute_yield mirror).
  • Tests + smoke verification: ~2 hr.

Total: ~1-1.5 days.

Unblocks

  • p2-67 Phase 13 (demo will have actual AI gameplay to screenshot).
  • p2-68 smoke acceptance bullet flips ✓ → p2-68 status done.

References

  • src/simulator/crates/mc-player-api/src/projection.rs::project_tactical — current minimal projector.
  • src/simulator/crates/mc-ai/src/tactical/mod.rs::TacticalState — target shape.
  • src/simulator/crates/mc-units/src/catalog.rs — UnitsCatalog precedent (p2-67 Phase 9).
  • src/simulator/crates/mc-city/src/tile_yield.rs — yield formula source of truth.
  • public/games/age-of-dwarves/data/ai_personalities.json — personality scoring tables.
  • .project/objectives/p2-67-claude-player-api.md (Phase 13 STOP, 2026-05-12).
  • .project/objectives/p2-68-mc-ai-headless-turn-driver.md (Wave 1 projector limitations).