perf(tactical): ⚡ Improve early-game AI aggression by optimizing dominance thresholds and production logic
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
d49b4c0fa6
commit
9006ab3bb1
3 changed files with 40 additions and 16 deletions
|
|
@ -22,7 +22,7 @@ use std::time::Instant;
|
|||
use crate::evaluator::ScoringWeights;
|
||||
use crate::mcts::XorShift64;
|
||||
|
||||
use super::{Action, TacticalCity, TacticalPlayerState, TacticalState};
|
||||
use super::{Action, TacticalCity, TacticalPlayerState, TacticalState, TacticalUnit};
|
||||
|
||||
/// Gold floor required before the AI commits to a rush-buy assault unit.
|
||||
///
|
||||
|
|
@ -63,7 +63,16 @@ const CULTURE_AXIS_MONUMENT_THRESHOLD: u8 = 4;
|
|||
|
||||
/// Aggression multiplier above which the player counts as dominant over the
|
||||
/// opposing field (mirrors GDScript DOMINANCE_FACTOR).
|
||||
const DOMINANCE_FACTOR: f32 = 1.25;
|
||||
///
|
||||
/// Bumped 2026-04-26 from 1.25 → 2.0 to slow rush-domination dynamics.
|
||||
/// Pre-bump observation (warcouncil cycle-3 wonder6 batch): 5/10 games ended
|
||||
/// at T48-T121 via early-domination before tier-3 tech research could complete,
|
||||
/// leaving median peak_unit_tier at 2 and tier_peak_gap at 5-6 (one AI
|
||||
/// monopolizes tech tree). At 2.0× the AI requires real military superiority
|
||||
/// before committing to attack, giving losers more time to develop and
|
||||
/// closing the symmetry gap. Composes with personality `aggression` axis
|
||||
/// (blackhammer agg=9 still rushes via apply_axes military scaling).
|
||||
const DOMINANCE_FACTOR: f32 = 2.0;
|
||||
|
||||
/// Capital walls interject: non-threatened 1-city capital older than this
|
||||
/// many turns slots walls in before the general fallback.
|
||||
|
|
@ -276,7 +285,7 @@ fn pick_for_city(
|
|||
// founders — mirrors `city_index == 0` gate.
|
||||
let expansion_target = (axes.expansion as u32 / 3).clamp(1, 5);
|
||||
if city_count < expansion_target && city.is_capital {
|
||||
let founder_count = player.units.iter().filter(|u| is_founder(&u.kind)).count() as u32;
|
||||
let founder_count = player.units.iter().filter(|u| is_founder(u)).count() as u32;
|
||||
if founder_count == 0 {
|
||||
return ids::FOUNDER.into();
|
||||
}
|
||||
|
|
@ -406,16 +415,16 @@ fn count_military_units(player: &TacticalPlayerState) -> u32 {
|
|||
player
|
||||
.units
|
||||
.iter()
|
||||
.filter(|u| is_military(&u.kind))
|
||||
.filter(|u| is_military(u))
|
||||
.count() as u32
|
||||
}
|
||||
|
||||
fn is_military(kind: &str) -> bool {
|
||||
!matches!(kind, "founder" | "settler" | "worker")
|
||||
fn is_military(unit: &TacticalUnit) -> bool {
|
||||
!unit.is_founder() && unit.kind != "worker"
|
||||
}
|
||||
|
||||
fn is_founder(kind: &str) -> bool {
|
||||
matches!(kind, "founder" | "settler")
|
||||
fn is_founder(unit: &TacticalUnit) -> bool {
|
||||
unit.is_founder()
|
||||
}
|
||||
|
||||
/// Max military count across all opponents. GDScript uses total enemy
|
||||
|
|
|
|||
|
|
@ -161,6 +161,14 @@ pub struct TacticalUnit {
|
|||
pub patrol_order: Option<Vec<(i32, i32)>>,
|
||||
}
|
||||
|
||||
impl TacticalUnit {
|
||||
/// True when this unit can found a city. Checks the data-driven flag first;
|
||||
/// falls back to kind-string matching for test fixtures that omit the flag.
|
||||
pub fn is_founder(&self) -> bool {
|
||||
self.can_found_city || matches!(self.kind.as_str(), "settler" | "founder")
|
||||
}
|
||||
}
|
||||
|
||||
/// Specification for a producible military unit — carries enough data for the
|
||||
/// production layer to select tier-appropriate units as tech unlocks (p0-39).
|
||||
///
|
||||
|
|
|
|||
|
|
@ -75,11 +75,15 @@ pub fn derived_defense(axes: &BTreeMap<String, i32>) -> i32 {
|
|||
/// to capital-assault commitment. Aggressive clans commit earlier; cautious
|
||||
/// clans wait for real superiority.
|
||||
///
|
||||
/// Range: `axis=1` → 1.80 (very cautious), `axis=5` → 1.50 (post-p0-37+39
|
||||
/// tempo baseline), `axis=10` → 1.15 (rush-happy). Baseline raised 2026-04-18
|
||||
/// from 1.25 so games reach T250+ — tier-3+ tech chains need the runway.
|
||||
/// Range: `axis=1` → 2.5 (very cautious), `axis=5` → 2.0 (baseline),
|
||||
/// `axis=10` → 1.5 (rush-happy). Baseline raised 2026-04-26 from 1.50 to 2.0
|
||||
/// (and rush-happy floor from 1.15 → 1.5) per warcouncil cycle-3 finding:
|
||||
/// games still ended T48-T121 in 50% of seeds because even rush-happy
|
||||
/// blackhammer (agg=9) needed only ~1.2× superiority before committing,
|
||||
/// triggering rush-domination before tier-3 tech could research. Higher
|
||||
/// thresholds give losers economic + tech runway.
|
||||
pub fn dominance_factor(axes: &BTreeMap<String, i32>) -> f32 {
|
||||
lerp_axis(axis(axes, "aggression"), 1.80, 1.50, 1.15)
|
||||
lerp_axis(axis(axes, "aggression"), 2.5, 2.0, 1.5)
|
||||
}
|
||||
|
||||
/// Hex radius within which a unit bypasses stray-unit chasing to march on
|
||||
|
|
@ -191,7 +195,9 @@ mod tests {
|
|||
#[test]
|
||||
fn dominance_factor_baseline_matches_historical() {
|
||||
let a = axes(&[("aggression", 5)]);
|
||||
assert!((dominance_factor(&a) - 1.50).abs() < 1e-6);
|
||||
// Baseline raised 2026-04-26 from 1.50 → 2.0 (warcouncil cycle-3
|
||||
// rush-domination dampening). See dominance_factor() docs.
|
||||
assert!((dominance_factor(&a) - 2.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -242,7 +248,7 @@ mod tests {
|
|||
#[test]
|
||||
fn empty_axes_match_historical_baseline() {
|
||||
let empty: BTreeMap<String, i32> = BTreeMap::new();
|
||||
assert!((dominance_factor(&empty) - 1.50).abs() < 1e-6);
|
||||
assert!((dominance_factor(&empty) - 2.0).abs() < 1e-6);
|
||||
assert_eq!(capital_approach_hex(&empty), 16);
|
||||
assert!((retreat_hp_fraction(&empty) - 0.40).abs() < 1e-6);
|
||||
assert_eq!(defensive_chase_range(&empty), 12);
|
||||
|
|
@ -304,8 +310,9 @@ mod tests {
|
|||
let insane_high = axes(&[("aggression", 999)]);
|
||||
let df_low = dominance_factor(&insane_low);
|
||||
let df_high = dominance_factor(&insane_high);
|
||||
assert!(df_low >= 1.14 && df_low <= 1.81);
|
||||
assert!(df_high >= 1.14 && df_high <= 1.81);
|
||||
// Range bumped 2026-04-26 alongside dominance_factor lerp (1.5–2.5).
|
||||
assert!(df_low >= 1.49 && df_low <= 2.51);
|
||||
assert!(df_high >= 1.49 && df_high <= 2.51);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue