feat(@projects/@magic-civilization): add city food growth test via turn processor

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-11 09:46:49 -07:00
parent 8ccdea62fd
commit 560f99484b

View file

@ -1286,6 +1286,105 @@ mod tests {
);
}
/// p2-67 Phase 11. `apply_end_turn` now runs `TurnProcessor::step`
/// between the AI driver loop and the closing `TurnStarted` emit.
/// Step accumulates food into `CityState.food_stored`; once the
/// threshold is crossed, `population` grows. This test seeds a city
/// with food_yield = 12 (well above the pop-1 maintenance of 2) so
/// the food_stored crosses the threshold in 2 turns and population
/// rises from 1 → 2.
#[test]
fn end_turn_ticks_city_food_growth_via_turn_processor() {
use mc_city::CityState;
let mut state = GameState::default();
let mut ps = PlayerState::default();
ps.player_index = 0;
let mut city = CityState::starter();
// Starter has population=1, food_stored=0, food_yield=4 (net +2 / turn).
// Threshold at pop=1 is `15 + 6*0 + 0 = 15`. With a juiced food_yield
// of 16 (net +14 / turn) we cross in 2 turns deterministically.
city.food_yield = 16;
ps.cities.push(city);
ps.city_positions.push((0, 0));
ps.city_buildings.push(Vec::new());
ps.city_improvements.push(Vec::new());
ps.city_ecology.push(Default::default());
state.players.push(ps);
// Turn 0 → state.turn becomes 1, food_stored = 14, population still 1.
let _ = apply_action(&mut state, 0, &PlayerAction::EndTurn).unwrap();
assert_eq!(state.turn, 1);
assert_eq!(state.players[0].cities[0].population, 1);
assert_eq!(state.players[0].cities[0].food_stored, 14);
// Turn 1 → food_stored crosses 15, pop grows to 2.
let _ = apply_action(&mut state, 0, &PlayerAction::EndTurn).unwrap();
assert_eq!(state.turn, 2);
assert_eq!(
state.players[0].cities[0].population, 2,
"city should have grown to population 2"
);
// food_stored after growth = 14 (pre-grow) + 14 (this turn's net) - 15 (threshold) = 13.
// (The threshold check happens AFTER this turn's food is added but
// BEFORE the maintenance is rebilled for the new pop — `process_city_production`
// accumulates pre-grow.)
assert_eq!(state.players[0].cities[0].food_stored, 13);
}
/// p2-67 Phase 11. With a queue carrying a `Queueable::Unit` and
/// enough `production_stored`, `try_spawn_unit` in `TurnProcessor::step`
/// spawns a real unit at the city's hex. Asserts via state inspection
/// (no `CityUnitCompleted` wire event exists today — the production
/// path mutates `player.units` silently).
#[test]
fn end_turn_completes_queued_unit_via_turn_processor() {
use mc_city::{CityState, Queueable};
let mut state = GameState::default();
let mut ps = PlayerState::default();
ps.player_index = 0;
let mut city = CityState::starter();
// Pre-stuff production_stored above the spawn cost (4 by default
// — see `LairCombatConfig::default`'s `unit_spawn_cost`). Anything
// ≥ 4 lets `try_spawn_unit` trigger.
city.production_stored = 100;
city.queue = Some(Queueable::Unit {
unit_id: "dwarf_warrior".into(),
});
city.queue_cost = Some(8);
ps.cities.push(city);
ps.city_positions.push((5, 5));
ps.city_buildings.push(Vec::new());
ps.city_improvements.push(Vec::new());
ps.city_ecology.push(Default::default());
state.players.push(ps);
let unit_count_before = state.players[0].units.len();
let _ = apply_action(&mut state, 0, &PlayerAction::EndTurn).unwrap();
assert_eq!(state.turn, 1);
assert!(
state.players[0].units.len() > unit_count_before,
"queued unit should have spawned (units before={}, after={})",
unit_count_before,
state.players[0].units.len()
);
}
/// p2-67 Phase 11. Unit movement_remaining refresh is now owned by
/// `TurnProcessor::step` (DRY rule — the dispatch-level `refresh_units`
/// call was deleted in this same patch). Asserts a fortified unit that
/// exhausted its movement budget gets a fresh budget after EndTurn.
#[test]
fn end_turn_refreshes_unit_movement_via_turn_processor() {
let mut state = make_state_with_units(vec![(0, 1, 3, 3)]);
// make_state_with_units gives every unit `with_moves(32)` —
// simulate a consumed budget.
state.players[0].units[0].movement_remaining = 0;
assert_eq!(state.players[0].units[0].base_moves, 32);
let _ = apply_action(&mut state, 0, &PlayerAction::EndTurn).unwrap();
assert_eq!(state.turn, 1);
assert_eq!(
state.players[0].units[0].movement_remaining, 32,
"movement should refresh to base_moves after step"
);
}
#[test]
fn noop_makes_no_state_change_and_no_events() {
let mut state = GameState::default();