feat(ai): ✨ adjust worker and military priorities for better city growth
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
653b24a7de
commit
7af041b97b
3 changed files with 35 additions and 24 deletions
|
|
@ -1075,6 +1075,13 @@ func _next_building(city: Variant, player: Variant, city_count: int, has_founder
|
|||
_score_add(scores, "worker", 7.0)
|
||||
if unimproved_food > 0 and own_workers < city_count and int(city.population) >= 2:
|
||||
_score_add(scores, "worker", 4.0)
|
||||
# First worker is a strong priority once pop can spare the food —
|
||||
# tile improvements are the primary long-term yield multiplier.
|
||||
if own_workers == 0 and int(city.population) >= 2:
|
||||
_score_add(scores, "worker", 10.0)
|
||||
# Keep worker supply replenished: one worker per city after first.
|
||||
if own_workers < city_count and int(city.population) >= 3:
|
||||
_score_add(scores, "worker", 3.0)
|
||||
if gold_now < 20 and gpt < 0:
|
||||
_score_add(scores, "marketplace", 7.0)
|
||||
if not city.has_building("forge"):
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ const FOUND_MIN_DIST_OWN: int = 4
|
|||
## deadlock founders that spawned near each other (observed in arena
|
||||
## smoke tests where start placement put both players on tile 0,0).
|
||||
const FOUND_MIN_DIST_ENEMY: int = 1
|
||||
const RETREAT_HP_FRACTION: float = 0.4
|
||||
const DEFENSIVE_CHASE_RANGE: int = 12
|
||||
const RETREAT_HP_FRACTION: float = 0.15
|
||||
const DEFENSIVE_CHASE_RANGE: int = 8
|
||||
const MILITARY_COMBAT_TYPES: Array[String] = [
|
||||
"melee", "ranged", "cavalry", "siege",
|
||||
]
|
||||
|
|
@ -574,8 +574,14 @@ static func _decide_production(
|
|||
):
|
||||
return _prod_unit(city_index, "founder")
|
||||
|
||||
# Priority 4: Military — maintain 2 warriors per city
|
||||
var want_military: bool = military_count < maxi(2, city_count * 2)
|
||||
# Priority 4: Military — maintain 2 warriors per city, scaling up to
|
||||
# match enemy's FULL army when they're closing on us so we don't lose
|
||||
# on parity once reserves arrive.
|
||||
var enemy_mil: int = enemy_total if threatened else 0
|
||||
var mil_target: int = maxi(4, city_count * 3)
|
||||
if enemy_mil > 0:
|
||||
mil_target = maxi(mil_target, enemy_mil + 1)
|
||||
var want_military: bool = military_count < mil_target
|
||||
if want_military:
|
||||
var unit_id: String = _pick_buildable_military_unit_id(city, player)
|
||||
if not unit_id.is_empty():
|
||||
|
|
|
|||
|
|
@ -99,11 +99,12 @@ impl TileYield {
|
|||
}
|
||||
}
|
||||
|
||||
/// Food consumed per citizen per turn. Lowered from 1.2 → 1.0 so late-game
|
||||
/// cities don't stall against the exponential threshold curve. At pop 20,
|
||||
/// consumption drops 24 → 20, freeing ~4 food/turn for growth. Target:
|
||||
/// median p0_pop_peak ≥ 30 at T300 (Civ5-like capital size).
|
||||
pub const FOOD_PER_POP: f64 = 1.0;
|
||||
/// Food consumed per citizen per turn. Tuned to 1.2 (well below Civ5's 2.0)
|
||||
/// so small cities can climb past the pop 2↔3 oscillation observed in iter10.
|
||||
/// With 4-food center: pop 3 needs 3.6, breakeven on center alone (+0.4
|
||||
/// surplus); pop 4 = 4.8, covered by one 1-food tile; pop 6 = 7.2, reachable
|
||||
/// with two decent food tiles. Target: median p0_pop_peak ≥ 7 at T150.
|
||||
pub const FOOD_PER_POP: f64 = 1.2;
|
||||
|
||||
/// Fraction of the previous growth threshold retained as stored food on
|
||||
/// growth (Civ5-style always-on granary effect). Each new pop starts with a
|
||||
|
|
@ -724,10 +725,10 @@ mod tests {
|
|||
fn food_surplus_calculation() {
|
||||
let mut city = City::found("Ironhold", (5, 5), true, 1);
|
||||
city.population = 1;
|
||||
// With no tile yields, city center gives 3 food.
|
||||
// Surplus = 3.0 - 2.0*1 = 1.0
|
||||
// With no tile yields, city center gives 4 food.
|
||||
// Surplus = 4.0 - 1.2*1 = 2.8
|
||||
let surplus = city.get_food_surplus(&[]);
|
||||
assert!((surplus - 1.0).abs() < f64::EPSILON);
|
||||
assert!((surplus - 2.8).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -740,27 +741,24 @@ mod tests {
|
|||
let ty = vec![
|
||||
TileYield { coord: (6, 5), food: 5.0, ..TileYield::default() },
|
||||
];
|
||||
// Surplus per turn: (3 + 5) - 2*1 = 6.0
|
||||
// Surplus per turn: (4 + 5) - 1.2*1 = 7.8
|
||||
// Threshold at pop 1: 15.0
|
||||
// Turn 1: food_stored = 6
|
||||
// Turn 1: food_stored = 7.8
|
||||
assert_eq!(city.process_growth(&ty), 0);
|
||||
assert_eq!(city.food_stored, 6.0);
|
||||
// Turn 2: food_stored = 12
|
||||
assert_eq!(city.process_growth(&ty), 0);
|
||||
assert_eq!(city.food_stored, 12.0);
|
||||
// Turn 3: food_stored = 18 >= 15 → grow, carry 3.0
|
||||
assert!((city.food_stored - 7.8).abs() < 1e-9);
|
||||
// Turn 2: food_stored = 15.6 >= 15 → grow, carry 0.6
|
||||
assert_eq!(city.process_growth(&ty), 1);
|
||||
assert_eq!(city.population, 2);
|
||||
assert_eq!(city.food_stored, 3.0); // 18 - 15 = 3
|
||||
assert!((city.food_stored - 0.6).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_growth_starvation() {
|
||||
let mut city = City::found("Ironhold", (5, 5), true, 1);
|
||||
city.population = 3;
|
||||
// No worked tiles beyond center → yields = 3 food
|
||||
// Consumption = 2*3 = 6. Surplus = 3 - 6 = -3
|
||||
// food_stored starts at 0, then 0 + (-3) = -3 < 0, pop > 1 → starve
|
||||
city.population = 5;
|
||||
// No worked tiles beyond center → yields = 4 food
|
||||
// Consumption = 1.2*5 = 6.0. Surplus = 4 - 6.0 = -2.0
|
||||
// food_stored: 0 + (-2.0) = -2.0 < 0, pop > 1 → starve
|
||||
let ty: Vec<TileYield> = vec![];
|
||||
assert_eq!(city.process_growth(&ty), -1);
|
||||
assert_eq!(city.population, 4);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue