feat(city): Add new city generation rules and behavior methods to the City struct

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-04-15 23:07:55 -07:00
parent 7ecefdd470
commit dff8bf8552

View file

@ -99,12 +99,12 @@ impl TileYield {
}
}
/// Food consumed per citizen per turn. Tuned to 1.5 (below Civ5's 2.0) so
/// small cities (pop 2-3) in plains/hills-heavy biomes can still climb past
/// pop 3 on center yield + one mediocre food tile. With 4-food center:
/// pop 2 breakeven → +1.0 surplus; pop 3 deficit of 0.5 covered by any
/// 1-food tile; pop 4 needs two food tiles (still achievable).
pub const FOOD_PER_POP: f64 = 1.5;
/// 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;
/// Base city HP before population scaling.
pub const BASE_CITY_HP: u32 = 200;
@ -677,9 +677,9 @@ mod tests {
let mut city = City::found("Ironhold", (5, 5), true, 1);
city.population = 1;
// With no tile yields, city center gives 4 food.
// Surplus = 4.0 - 1.5*1 = 2.5
// Surplus = 4.0 - 1.2*1 = 2.8
let surplus = city.get_food_surplus(&[]);
assert!((surplus - 2.5).abs() < f64::EPSILON);
assert!((surplus - 2.8).abs() < 1e-9);
}
#[test]
@ -692,27 +692,27 @@ mod tests {
let ty = vec![
TileYield { coord: (6, 5), food: 5.0, ..TileYield::default() },
];
// Surplus per turn: (4 + 5) - 1.5*1 = 7.5
// Surplus per turn: (4 + 5) - 1.2*1 = 7.8
// Threshold at pop 1: 15.0
// Turn 1: food_stored = 7.5
// Turn 1: food_stored = 7.8
assert_eq!(city.process_growth(&ty), 0);
assert_eq!(city.food_stored, 7.5);
// Turn 2: food_stored = 15.0 >= 15 → grow, carry 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, 0.0); // 15 - 15 = 0
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;
city.population = 5;
// No worked tiles beyond center → yields = 4 food
// Consumption = 1.5*3 = 4.5. Surplus = 4 - 4.5 = -0.5
// food_stored: 0 + (-0.5) = -0.5 < 0, pop > 1 → starve
// 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, 2);
assert_eq!(city.population, 4);
assert_eq!(city.food_stored, 0.0);
}