9.3 KiB
| 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 |
|
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_catalogempty → no legal unit-build choices.TacticalState.building_catalogempty → 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.rsand (if needed) thread catalog handles throughdispatch::apply_end_turn. Catalogs already exist inmc-units::UnitsCatalog(p2-67 Phase 9) and need a sibling inmc-buildings. - JSON path: none — projector reads existing
public/games/age-of-dwarves/data/{units,buildings}/*.jsonvia the loaders. - GDScript: harness wiring only — pass catalog handles into
GdPlayerApiat boot.
Surface
1. Catalog plumbing
UnitsCatalogalready loaded inclaude_player_main.gdforMapUnit::new(...). Pass it throughGdPlayerApi::new(...)soproject_tacticalcan read it.- Add
BuildingsCatalog(mirrorUnitsCatalogpattern). Load once at harness boot. - Optional:
TerrainCatalogfor per-tile yield lookups.
2. Projector enrichment
In project_tactical:
- Populate
tactical.unit_catalogfromUnitsCatalog— convert eachUnitDefto theTacticalUnitDefshapemc-aiexpects (cost, moves, attack, defense, prerequisites). - Populate
tactical.building_catalogfromBuildingsCatalog. - Per-tile yields: for each
tactical.tiles[i], setfood/production/gold/science/culturefrom theTerrainCatalog× current improvements/biome lookup. Mirror the formula used inmc-city::tile_yield::compute_yield. - Populate
strategic_axesfromScoringWeights(already set per-player via p2-67 Wave 1set_player_personality_json). - Populate
promotion_*_weight/difficulty_threshold_multfrom 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_tacticalpopulatesunit_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_tacticalpopulatesunit_catalog,building_catalog,strategic_axes, personality weights (clan_id,promotion_*_weight). [evidence:crates/mc-player-api/src/projection.rs:442-470; teststactical_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
TileStatecarries no food/prod/gold triple (only biome label + ecology fields). The closest formulamc_city::yield_fold::tile_yields_from_collectiblesreads 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. - ☑
BuildingsCatalogexists asVec<TacticalBuildingSpec>held onGameState::ai_building_catalog(mirror ofUnitsCatalogpattern, 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] - ☑
GdPlayerApiaccepts catalog handles via setters:set_units_catalog_json,set_buildings_catalog_json,set_difficulty_threshold_mult, plusunit_catalog_len/building_catalog_lendebug readers. [evidence:api-gdext/src/player_api.rs] - ⚠ 5-EndTurn smoke shows
actions_applied > 0on AI slots for turn 1 only; turns 2-5 emitactions_applied=0. [evidence:scripts/claude-smoke-5endturn.shrun on apricot 2026-05-11, both AI slots emit 1 action on turn 1 (Action::EnqueueBuildof a tier-1 unit), zero thereafter.]- Root cause:
mc_ai::tactical::production::pick_for_cityskips 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.
- Root cause:
- ☑ 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 --lib84/84 green;cargo test -p mc-ai --lib240/240 green; workspacecargo checkclean. - ⚠ 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 → TacticalState → pick_for_city → AI dispatch).
The remaining zero-emission gap on turns 2-5 is not a projector defect. It is the combined effect of:
- Single-slot per-city production queue blocks
EnqueueBuildonce filled. - Starter inventory has no settler/founder, so
FoundCityactions never fire. - Bench mapgen places capitals far apart, so warrior
MoveUnithas no productive target (no enemy contact, no resource hex within move range). Fortifyactions are not in the chain emitted bydecide_tactical_actionsfor 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_yieldsemantics into aTacticalTile::yieldslookup so city placement / citizen scoring has terrain signal. - p2-71b — Widen militarist starter inventory to include a settler/founder OR teach
decide_tactical_actionsto 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).