feat(@projects/@magic-civilization): ✨ update ai headless turn driver status to partial
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
db24390fc7
commit
12cdeba908
3 changed files with 141 additions and 20 deletions
|
|
@ -2,7 +2,7 @@
|
|||
id: p2-68
|
||||
title: "mc-ai headless turn driver — GameState projector/applicator + run_ai_turn"
|
||||
priority: p2
|
||||
status: open
|
||||
status: partial
|
||||
scope: game1
|
||||
category: simulation
|
||||
owner: simulator-infra
|
||||
|
|
@ -145,15 +145,17 @@ count
|
|||
|
||||
## Acceptance
|
||||
|
||||
- ☐ `mc-ai/src/projector.rs` exists; `project(&state, player, &web) -> TacticalState`.
|
||||
- ☐ `mc-player-api::dispatch::apply_ai_action` exists; routes `Action` → `PlayerAction` → existing `apply_action`.
|
||||
- ☐ `mc-ai/src/lib.rs::run_ai_turn` exists; deterministic given `(seed, state, player)`.
|
||||
- ✓ Projector exists. **Amended location**: `mc-player-api/src/projection.rs::project_tactical(state, player) -> TacticalState` (NOT `mc-ai/src/projector.rs` per the locked Wave 1 Option B' decision — `mc-turn → mc-ai` edge would create a cycle). Verified Wave 1 — `cargo test -p mc-player-api --lib projection` → 12 passed.
|
||||
- ✓ `mc-player-api::dispatch::apply_ai_action` exists; routes `Action` → `PlayerAction` → existing `apply_action`. Verified Wave 2 — 5 dedicated tests in `dispatch::tests` group.
|
||||
- ✓ `mc-ai/src/lib.rs::run_ai_turn` exists; deterministic given `(seed, state, weights)`. Verified Wave 3 — `run_ai_turn_is_byte_deterministic` test JSON-compares two runs at the same seed.
|
||||
- ✓ Every `#[ignore]` in `mc-ai/tests/tactical_port_regression.rs` is removed and the test passes. (Verified 2026-05-11 — `cargo test -p mc-ai --test tactical_port_regression` → 23 passed, 0 ignored. Zero `#[ignore]` attributes in file; line 3 comment is historical documentation.)
|
||||
- ☐ `mc-player-api` no longer contains `run_scripted_ai_turn` — call site replaced.
|
||||
- ☐ Headless harness loads `ai_personalities.json` at boot.
|
||||
- ☐ `cargo check --workspace && cargo test --workspace` green.
|
||||
- ☐ Headless smoke test: 5 EndTurns vs an AI clan produces non-trivial AI action chains (cities, units moved, tech researched, varies by personality).
|
||||
- ☐ Determinism: same `(seed, num_players, personalities)` produces byte-identical wire transcripts across two runs.
|
||||
- ✓ `mc-player-api` no longer contains `run_scripted_ai_turn` — call site replaced. Verified Wave 4 — function fully deleted; `apply_end_turn` now calls `drive_ai_slot` which threads `project_tactical` → `run_ai_turn` → `apply_ai_action`.
|
||||
- ☐ Headless harness loads `ai_personalities.json` at boot. **Gated on api-gdext migration** (separately tracked) — the harness instantiates `GdAiController` / `GdMcTreeController` via `ClassDB`, and api-gdext currently fails to compile due to removed `mc_turn::snapshot` types (see Wave 1 finding above).
|
||||
- ☐ `cargo check --workspace && cargo test --workspace` green. **Gated on api-gdext migration**. Both crates this objective owns build green standalone: `cargo check -p mc-player-api` clean, `cargo check -p mc-ai` clean. mc-ai integration tests `mcts_basic` / `clan_rollout_divergence` reference an old `force_rel: [u8; 4]` shape (pre-existing tech debt unrelated to p2-68).
|
||||
- ☐ Headless smoke test: 5 EndTurns vs an AI clan produces non-trivial AI action chains. **Gated on api-gdext migration** — the harness needs a working GDExtension to drive the turn loop.
|
||||
- ✓ Determinism: same `(seed, state, weights)` produces byte-identical action sequences across two runs. Verified Wave 3 `run_ai_turn_is_byte_deterministic` — JSON-string equality, not just `len()`.
|
||||
|
||||
**Status:** `partial` (6/9 ✓). Three open bullets all gated on a single external blocker (api-gdext migration to mc-mcts-service protocol). The substantive Rust work owned by this objective — projector, applicator, run_ai_turn, dispatch swap, determinism — landed in Waves 1-4. Wave 5 (harness wiring) and the smoke test require GDExtension to compile.
|
||||
|
||||
## Why this size
|
||||
|
||||
|
|
|
|||
118
.project/objectives/p2-69-api-gdext-mctscontroller-port.md
Normal file
118
.project/objectives/p2-69-api-gdext-mctscontroller-port.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
id: p2-69
|
||||
title: "Port GdMcTreeController to mc-player-api AI driver (DRY consolidation)"
|
||||
priority: p2
|
||||
status: open
|
||||
scope: game1
|
||||
category: tooling
|
||||
owner: simulator-infra
|
||||
created: 2026-05-11
|
||||
updated_at: 2026-05-11
|
||||
blocked_by: []
|
||||
follow_ups: [p2-68, p2-67]
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
`api-gdext/src/ai.rs` is broken on main: `GdMcTreeController::choose_action{,_with_stats}` reference `mc_turn::snapshot::{McAction, McSnapshot}` and `mc_ai::mcts_tree::{rollout_snapshot, Tree}` — all removed in a prior MCTS refactor when the rollout engine moved into `mc-mcts-service`. The compile error blocks `cargo check --workspace` and is the sole gate on three p2-68 acceptance bullets:
|
||||
|
||||
- workspace-green
|
||||
- headless smoke (5 EndTurns)
|
||||
- harness `ai_personalities.json` loader (depends on the workspace building)
|
||||
|
||||
Active production callers: `ai_turn_bridge.gd:174`, `turn_manager.gd:196`. Deletion not viable.
|
||||
|
||||
## Source-of-truth rails
|
||||
|
||||
- **Rust crate**: `api-gdext`. No new crate. Rewrite the body of `GdMcTreeController` to call `mc_player_api::project_tactical` + `mc_ai::run_ai_turn` (both already shipped in p2-68 Waves 1+3). Preserve the GDScript-facing method shape (`choose_action(json, player, seed) -> GString`).
|
||||
- **JSON path**: none.
|
||||
- **GDScript**: callers stay unchanged.
|
||||
|
||||
## Locked decision
|
||||
|
||||
**Option 2 — reroute through `mc-player-api`** (not the mc-mcts-service IPC path).
|
||||
|
||||
Reason — SOLID/DRY:
|
||||
- p2-68 Wave 3 already shipped `mc_ai::run_ai_turn` with byte-deterministic tests. Reusing it gives the Godot bridge and the Claude headless harness the same AI driver — one production AI, two consumers.
|
||||
- mc-mcts-service IPC route would require an async runtime in gdext and a separate process for what is currently an in-process call. Strictly more complexity for no behavioural gain.
|
||||
- The new path also makes per-personality scoring trivial (already wired into `run_ai_turn(state, player, weights, seed)`).
|
||||
|
||||
## Surface
|
||||
|
||||
### 1. Rewrite `GdMcTreeController::choose_action`
|
||||
|
||||
Current shape (api-gdext/src/ai.rs:159-220 approx):
|
||||
```rust
|
||||
fn choose_action(&self, game_state_json, player_index, seed) -> GString {
|
||||
// parses GameState JSON
|
||||
// builds McSnapshot (DEAD)
|
||||
// runs Tree::new + rollout_snapshot (DEAD)
|
||||
// returns best action JSON
|
||||
}
|
||||
```
|
||||
|
||||
New shape (use existing p2-68 surface):
|
||||
```rust
|
||||
fn choose_action(&self, game_state_json: GString, player_index: i64, seed: i64) -> GString {
|
||||
let state: GameState = parse_or_error_return;
|
||||
let tactical = mc_player_api::project_tactical(&state, player_index as u8);
|
||||
// Personality weights: look up from state.players[pi].personality, or default.
|
||||
let weights = personality_weights_for(&state, player_index as u8);
|
||||
let actions = mc_ai::run_ai_turn(&tactical, player_index as u8, &weights, seed as u64);
|
||||
// GDScript caller expects a SINGLE action — return actions.first() or null.
|
||||
serde_json::to_string(&actions.first()).unwrap_or_default().into()
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Rewrite `GdMcTreeController::choose_action_with_stats`
|
||||
|
||||
Same body as above, plus a `Dictionary` of search stats. Stats may temporarily degrade to a stub (`{visits: 0, depth: 0}`) since `run_ai_turn` doesn't surface MCTS internals — `mc-ai` can grow a `run_ai_turn_with_stats` later if Godot UI actually consumes the stats. **Verify first**: grep for callers of `choose_action_with_stats`; if no real consumer reads `visits`/`depth`, stub is fine.
|
||||
|
||||
### 3. Delete dead code
|
||||
|
||||
- `use mc_ai::mcts_tree::{rollout_snapshot, Tree};` (api-gdext/src/ai.rs:23)
|
||||
- `use mc_turn::snapshot::{McAction, McSnapshot};` (:25)
|
||||
- Any cfg(test) blocks constructing `McSnapshot`/`PlayerSnap` (:651-end of file).
|
||||
- Per Zero Tech Debt — no commented-out fallback, no `#[allow(dead_code)]` shims.
|
||||
|
||||
### 4. Workspace green
|
||||
|
||||
After 1+2+3, `cargo check --workspace` and `cargo test --workspace` both green. This unblocks p2-68's outstanding acceptance bullets.
|
||||
|
||||
### 5. Smoke test
|
||||
|
||||
Re-run the p2-68 5-EndTurn smoke: build the gdext binary, boot `claude_player_main.gd` harness, drive 5 EndTurns, confirm AI action chains are non-trivial and vary by personality.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- ☐ `GdMcTreeController::choose_action` rewritten to use `project_tactical` + `run_ai_turn`.
|
||||
- ☐ `GdMcTreeController::choose_action_with_stats` rewritten (stats may stub if no real consumer).
|
||||
- ☐ All `use mc_turn::snapshot` and `use mc_ai::mcts_tree` lines deleted from api-gdext.
|
||||
- ☐ Dead cfg(test) blocks constructing removed types deleted.
|
||||
- ☐ `cargo check --workspace` green.
|
||||
- ☐ `cargo test --workspace` green.
|
||||
- ☐ Existing GDScript callers (`ai_turn_bridge.gd:174`, `turn_manager.gd:196`) compile and run without modification.
|
||||
- ☐ p2-68 outstanding bullets unblocked + closed via 5-EndTurn smoke.
|
||||
|
||||
## Why this size
|
||||
|
||||
- Rewrite: ~1h. Most of the file is unchanged; only the two MCTS bodies + dead imports.
|
||||
- Dead-code purge: ~15min.
|
||||
- Workspace gate: ~15min build.
|
||||
- Smoke test: ~30min (gdext rebuild + harness drive).
|
||||
|
||||
**Total: ~2-3 hours.**
|
||||
|
||||
## Unblocks
|
||||
|
||||
- p2-68 → flip to `done` once smoke test passes.
|
||||
- p2-67 → unblocked, flip to `done` after Phase 11/12/13 land (Phase 10 already functionally done via p2-68 Wave 4).
|
||||
|
||||
## References
|
||||
|
||||
- `src/simulator/api-gdext/src/ai.rs` — file to rewrite.
|
||||
- `src/simulator/crates/mc-player-api/src/projection.rs::project_tactical` — p2-68 Wave 1.
|
||||
- `src/simulator/crates/mc-ai/src/tactical/mod.rs::run_ai_turn` — p2-68 Wave 3 (byte-deterministic).
|
||||
- `src/game/engine/src/modules/ai/ai_turn_bridge.gd:174` — primary GDScript caller.
|
||||
- `src/game/engine/src/modules/turn/turn_manager.gd:196` — secondary GDScript caller.
|
||||
- `.project/objectives/p2-68-mc-ai-headless-turn-driver.md` — gating context.
|
||||
|
|
@ -2,28 +2,29 @@
|
|||
//!
|
||||
//! Exposes two Godot RefCounted classes:
|
||||
//!
|
||||
//! - `GdMcTreeController` — strategic layer. Accepts a serialized `GameState`
|
||||
//! JSON, runs parallel MCTS rollouts via `mc-turn`'s `McSnapshot`, and
|
||||
//! returns the winning `McAction` as a string GDScript can read.
|
||||
//! - `GdMcTreeController` — strategic directive layer. Accepts a serialized
|
||||
//! `GameState` JSON, projects to a `TacticalState`, runs
|
||||
//! `mc_ai::tactical::run_ai_turn`, and folds the returned tactical
|
||||
//! `Vec<Action>` down to a single strategic-kind string GDScript's production
|
||||
//! queue priming consumes (`Settle` / `Attack` / `Build` / `Defend` /
|
||||
//! `Idle`). p2-69 reroutes this off the removed `mc_turn::snapshot`
|
||||
//! (McSnapshot/McAction) path onto the shared `run_ai_turn` driver — one
|
||||
//! production AI, two consumers (Godot bridge + headless harness).
|
||||
//! - `GdAiController` — tactical layer (p0-26). Accepts an abstract rollout
|
||||
//! state JSON, runs [`mc_ai::tactical::decide_tactical_actions`], and
|
||||
//! returns a `PackedStringArray` of JSON-encoded `Action` records that the
|
||||
//! GDScript turn bridge dispatches back into the engine.
|
||||
//!
|
||||
//! All simulation logic lives in `mc-turn` and `mc-ai`. This file is a shim only.
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
//! All simulation logic lives in `mc-ai` and `mc-player-api`. This file is a shim only.
|
||||
|
||||
use godot::prelude::*;
|
||||
use mc_ai::abstract_state::MAX_PLAYERS;
|
||||
use mc_ai::evaluator::{ScoringEvaluator, ScoringWeights};
|
||||
use mc_ai::game_state::{AiPlayerState, StrategicWeights};
|
||||
use mc_ai::gpu::GpuContext;
|
||||
use mc_ai::mcts::XorShift64;
|
||||
use mc_ai::mcts_tree::{rollout_snapshot, Tree};
|
||||
use mc_ai::tactical::{decide_tactical_actions, Action, TacticalState};
|
||||
use mc_turn::snapshot::{McAction, McSnapshot};
|
||||
use mc_turn::{GameState, TurnProcessor};
|
||||
use mc_ai::tactical::{decide_tactical_actions, run_ai_turn, Action, TacticalState};
|
||||
use mc_player_api::project_tactical;
|
||||
use mc_turn::GameState;
|
||||
|
||||
// ── GdMcTreeController ───────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue