feat(@projects/@magic-civilization): expose drive_ai_slot for benchmarking

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-06-08 01:20:42 -07:00
parent 9e7566a628
commit db30279c60
3 changed files with 29 additions and 2 deletions

View file

@ -976,7 +976,12 @@ fn translate_processor_events(events: &[mc_replay::TurnEvent]) -> Vec<Event> {
/// counted but DO NOT abort the rest of the turn — a single
/// unknown unit or illegal move must not collapse the entire AI
/// side (matches the GDScript dispatch behaviour).
fn drive_ai_slot(state: &mut GameState, ai_slot: u8) -> u32 {
/// Drive one AI slot for a turn: project vision, run the slot's controller
/// (`run_ai_turn` by default), and apply the chosen actions. Public so headless
/// multi-clan benches (`dominion_bench`) can drive the *real* AI through the
/// same path the interactive game uses (`apply_end_turn`), instead of relying
/// on `mc-turn`'s inline fallback movement. Returns the action count.
pub fn drive_ai_slot(state: &mut GameState, ai_slot: u8) -> u32 {
let pi: usize = ai_slot as usize;
if pi >= state.players.len() {
return 0;

View file

@ -23,6 +23,7 @@ mc-city = { path = "../mc-city" }
mc-culture = { path = "../mc-culture" }
mc-economy = { path = "../mc-economy" }
mc-ai = { path = "../mc-ai" }
mc-player-api = { path = "../mc-player-api" }
serde.workspace = true
serde_json.workspace = true
rayon = "1"

View file

@ -355,7 +355,20 @@ fn run_scenario_with_profiles(num_players: usize, all_profiles: &[ProfileJson])
..Default::default() }],
unit_upkeep: vec![],
strategic_axes: make_axes(profile),
scoring_weights: ScoringWeights::default(),
// Real per-personality scoring weights (military/expansion/etc.),
// the same source the live game uses (`ScoringWeights::from_personality`).
// Without these the AI scores every action ~0 and `drive_ai_slot`
// produces no moves — the inert `default()` was why the bench showed
// zero PvP. Falls back to default if the personality file is absent.
scoring_weights: ScoringWeights::from_personality(
&profile.id,
&Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.nth(2)
.expect("workspace root")
.join("public/resources/ai/personalities"),
)
.unwrap_or_default(),
expansion_points: 0,
city_buildings: vec![vec![]],
city_improvements: Default::default(),
@ -413,6 +426,14 @@ fn run_scenario_with_profiles(num_players: usize, all_profiles: &[ProfileJson])
let mut victory_turn: Option<u32> = None;
for turn_num in 1..=TOTAL_TURNS {
// Drive every slot's REAL AI (`run_ai_turn` via the controller registry)
// before the turn resolves — the same `drive_ai_slot` the interactive
// game runs inside `apply_end_turn`. Without this the bench relied on
// `mc-turn`'s inline `nearest_lair` fallback (PvE only → zero PvP). All
// slots are AI here (no human), so we drive all of them.
for slot in 0..state.players.len() as u8 {
mc_player_api::dispatch::drive_ai_slot(&mut state, slot);
}
let result = processor.step(&mut state);
// Track unit production per player.