diff --git a/airdef-mod/src/lib.rs b/airdef-mod/src/lib.rs index a131c8d..02727c2 100644 --- a/airdef-mod/src/lib.rs +++ b/airdef-mod/src/lib.rs @@ -2540,6 +2540,82 @@ mod tests { use super::*; + fn test_mod() -> AirDef { + AirDef { + sim: Sim::new(1), + ground: Ground::default(), + dirty: false, + next_save: 0, + hud_shown: false, + monitor: None, + monitor_shown: false, + grid: false, + grid_ids: Vec::new(), + wave: wave::WaveConfig::default(), + presentation: presentation::Session::default(), + block_log: None, + time_view: false, + film_prev: None, + smoke: Default::default(), + far: true, + acoustics: None, + moving: None, + hd: false, + world: None, + health_mark: 0, + menu: None, + listener: DVec3::ZERO, + punch: 0.0, + pending: Vec::new(), + engines: engines::Engines::default(), + engine_gen: u32::MAX, + engines_replay: false, + model_ids: std::collections::HashMap::new(), + model_generation: u32::MAX, + by_id: Vec::new(), + map: map::MapUi::default(), + } + } + + #[test] + fn delivery_request_and_cargo_survive_full_state_round_trip() { + struct Flat; + impl sim::world::WorldSource for Flat { + fn solid(&self, _: i32, y: i32, _: i32) -> Option { Some(y < 64) } + } + let mut before = test_mod(); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 30.0)] { + let id = before.sim.id(); + before.sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + before.sim.sams[1].ground_request = true; + before.sim.sams[1].ground_rounds = 1; + before.sim.sams[1].missiles = 3; + before.sim.sams[2].ground_stock = 1; + before.sim.order_move(2, DVec3::new(100.0, 64.0, 0.0)); + let text = before.state_text(false, false); + let mut after = test_mod(); + after.apply_state(&text).unwrap(); + assert_eq!((after.sim.sams[0].ground_stock, after.sim.sams[1].ground_rounds, after.sim.sams[1].missiles), (0, 1, 3)); + assert!(after.sim.sams[1].ground_request); + assert_eq!(after.sim.sams[2].ground_stock, 1); + assert!(after.sim.sams[2].moving()); + let mut out = Out::default(); + for _ in 0..6000 { + let now = after.sim.tick; + let cols = after.ground.take_wanted().into_iter().map(|(x, z)| (x, z, 64)).collect(); + after.ground.answer(cols, now); + let sections = after.ground.world.take_wanted(now); + after.ground.world.serve(&Flat, sections, now); + after.sim.step(&mut after.ground, None, &mut out); + if after.sim.sams[0].air_48n6_stock == 1 { break; } + out = Out::default(); + } + assert_eq!((after.sim.sams[1].ground_rounds, after.sim.sams[1].missiles), (2, 2)); + assert!(!after.sim.sams[1].ground_request); + assert_eq!((after.sim.sams[0].air_48n6_stock, after.sim.sams[2].ground_stock, after.sim.sams[2].air_48n6_stock), (1, 0, 0)); + } + /// #83: while an export records no other replay or film replaces it; after it, they may. #[test] fn typed_ammunition_changes_mark_the_state_dirty() { diff --git a/airdef-mod/src/sim/logistics.rs b/airdef-mod/src/sim/logistics.rs index beab072..afa20f8 100644 --- a/airdef-mod/src/sim/logistics.rs +++ b/airdef-mod/src/sim/logistics.rs @@ -261,4 +261,28 @@ mod tests { assert_eq!((sim.sams[0].air_48n6_stock, sim.sams[2].air_48n6_stock), (1, 0)); assert!(sim.load_ground_round(1).is_err()); } + #[test] + fn pending_delivery_waits_for_compatible_type_and_cell_after_unload() { + let mut sim = Sim::new(2); + for kind in [SamKind::Depot, SamKind::S400, SamKind::Tzm] { + let id = sim.id(); sim.sams.push(Sam::new(id, kind, DVec3::ZERO, 0.0)); + } + sim.sams[1].ground_request = true; + sim.sams[2].ground_stock = 1; + sim.sams[1].kind = SamKind::Patriot; + sim.tick = 2; + for _ in 0..4 { sim.step_supply(&mut Out::default()); } + assert_eq!(sim.sams[2].ground_stock, 1); + assert_eq!(sim.sams[1].ground_rounds, 0); + sim.sams[1].kind = SamKind::S400; + sim.sams[1].missiles = 0; + sim.sams[1].ground_rounds = 4; + for _ in 0..4 { sim.step_supply(&mut Out::default()); } + assert_eq!(sim.sams[2].ground_stock, 1); + sim.unload_ground_round(1).unwrap(); + for _ in 0..4 { sim.step_supply(&mut Out::default()); } + assert_eq!((sim.sams[1].ground_rounds, sim.sams[2].ground_stock, sim.sams[0].ground_stock), (4, 0, 1)); + assert!(!sim.sams[1].ground_request); + } + } diff --git a/airdef-mod/src/sim/regress.rs b/airdef-mod/src/sim/regress.rs index 276be28..a22bb6a 100644 --- a/airdef-mod/src/sim/regress.rs +++ b/airdef-mod/src/sim/regress.rs @@ -1767,3 +1767,88 @@ fn a_blast_in_the_blocks_waits_for_the_games_word() { let s = ground.world.stats; assert_eq!((s.blasts_asked, s.blasts_confirmed, s.blasts_unchanged, s.blasts_unloaded), (3, 1, 0, 2)); } + +#[test] +fn strike_round_truck_drives_from_depot_to_carrier_and_returns_air_round() { + let mut sim = Sim::new(5); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] { + let id = sim.id(); + sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + sim.sams[0].ground_stock = 1; + sim.load_ground_round(1).unwrap(); + let (mut ground, land) = (Ground::default(), sites::testland::Land(|_, _| 64)); + let mut out = Out::default(); + let mut seen_loaded = false; + let mut furthest: f64 = 0.0; + for _ in 0..6000 { + sites::testland::step(&mut sim, &mut ground, &land, &mut out); + seen_loaded |= sim.sams[2].ground_stock == 1; + furthest = furthest.max(sim.sams[2].pos.x); + if sim.sams[0].air_48n6_stock == 1 { break; } + out = Out::default(); + } + assert!(seen_loaded, "truck never loaded RM48U"); + assert!(furthest > 140.0, "truck did not drive to carrier: {furthest}"); + assert_eq!((sim.sams[0].ground_stock, sim.sams[0].air_48n6_stock), (0, 1)); + assert_eq!((sim.sams[1].missiles, sim.sams[1].ground_rounds), (3, 1)); + assert_eq!((sim.sams[2].ground_stock, sim.sams[2].air_48n6_stock), (0, 0)); + assert!(!sim.sams[1].ground_request); + assert!(sim.load_ground_round(1).is_err()); +} + +#[test] +fn strike_delivery_waits_while_carrier_moves_and_resumes_after_repair() { + let mut sim = Sim::new(8); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 0.0)] { + let id = sim.id(); + sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + sim.sams[0].ground_stock = 1; + sim.load_ground_round(1).unwrap(); + let (mut ground, land) = (Ground::default(), sites::testland::Land(|_, _| 64)); + let mut out = Out::default(); + for _ in 0..300 { + sites::testland::step(&mut sim, &mut ground, &land, &mut out); + if sim.sams[2].ground_stock == 1 { break; } + out = Out::default(); + } + assert_eq!(sim.sams[2].ground_stock, 1); + assert!(sim.order_move(1, DVec3::new(220.0, 64.0, 0.0))); + // Repair cancels both marches but preserves the outstanding request and truck's load. + sim.repair(); + assert!(sim.sams[1].ground_request); + assert_eq!(sim.sams[2].ground_stock, 1); + assert!(sim.order_move(1, DVec3::new(220.0, 64.0, 0.0))); + for _ in 0..100 { + sites::testland::step(&mut sim, &mut ground, &land, &mut out); + out = Out::default(); + } + assert_eq!(sim.sams[1].ground_rounds, 0); + for _ in 0..6000 { + sites::testland::step(&mut sim, &mut ground, &land, &mut out); + if sim.sams[1].ground_rounds == 1 { break; } + out = Out::default(); + } + assert_eq!((sim.sams[1].ground_rounds, sim.sams[1].missiles), (1, 3)); + assert!(!sim.sams[1].ground_request); +} + +#[test] +fn destroying_a_pending_carrier_and_loaded_truck_does_not_restore_stock_on_repair() { + let mut sim = Sim::new(8); + for (kind, x) in [(SamKind::Depot, 0.0), (SamKind::S400, 160.0), (SamKind::Tzm, 80.0)] { + let id = sim.id(); + sim.sams.push(Sam::new(id, kind, DVec3::new(x, 64.0, 0.0), 0.0)); + } + sim.sams[1].ground_request = true; + sim.sams[2].ground_stock = 1; + let mut out = Out::default(); + sim.direct_hit(sim.sams[1].pos + DVec3::Y, DVec3::ZERO, 1, &mut out); + sim.direct_hit(sim.sams[2].pos + DVec3::Y, DVec3::ZERO, 1, &mut out); + assert!(!sim.sams[1].alive()); + assert!(!sim.sams[2].alive()); + sim.repair(); + assert!(!sim.sams[1].ground_request); + assert_eq!((sim.sams[0].ground_stock, sim.sams[2].ground_stock), (0, 0)); +}