diff --git a/airdef-core/tests/scenarios.rs b/airdef-core/tests/scenarios.rs index 33ddcb0..e7b1936 100644 --- a/airdef-core/tests/scenarios.rs +++ b/airdef-core/tests/scenarios.rs @@ -35,3 +35,47 @@ fn same_seed_same_result() { let b = harness::run(&harness::logistics()); assert_eq!((a.ticks_run, a.launched, a.kills, a.hits, a.fired, a.ground_queries), (b.ticks_run, b.launched, b.kills, b.hits, b.fired, b.ground_queries)); } + +#[test] +fn overlapping_mass_strikes_on_flat_world() { + use airdef::sim::{Aim, Approach, DroneKind as D, Ground, Out, RaidOrder, Sam, Sim}; + use glam::DVec3; + let sc = harness::big_smart(); + let terrain = harness::Terrain::Flat(70); + let mut sim = Sim::new(sc.seed); + sim.damage = false; + for unit in &sc.units { + let id = sim.id(); + sim.sams.push(Sam::new(id, unit.kind, DVec3::new(unit.pos.x, 70.0, unit.pos.z), unit.heading)); + } + sim.targets = vec![DVec3::new(0.0, 70.0, 0.0)]; + sim.target_health = vec![100.0]; + // Exact composition of the menu's "Массированный удар" preset. + let order = RaidOrder { kinds: vec![(D::Gerbera, 8), (D::Shahed, 12), (D::Geran3, 4), (D::Kh101, 4), (D::Kalibr, 4), (D::Kh22, 2), (D::Iskander, 2)], approach: Approach::AllSides, aim: Aim::Smart, dist: 4000.0 }; + let mut ground = Ground::default(); + let mut pending = Vec::new(); + let mut out = Out::default(); + let targets = sim.targets.clone(); + assert_eq!(sim.order_raid(&order, &targets, &mut out), 36); + let mut second = false; + let mut peak = 0; + for now in 1..=12000 { + if !second && !sim.planning() { + assert_eq!(sim.order_raid(&order, &targets, &mut out), 36); + second = true; + } + ground.answer(std::mem::take(&mut pending), now); + out = Out::default(); + sim.step(&mut ground, None, &mut out); + let wanted = ground.take_wanted(); + pending = wanted.into_iter().map(|(x, z)| (x, z, 70)).collect(); + let sections = ground.world.take_wanted(now); + ground.world.serve(&terrain, sections, now); + peak = peak.max(sim.drones.iter().filter(|d| d.alive()).count()); + assert!(sim.drones.iter().all(|d| d.pos.is_finite() && d.vel.is_finite())); + if sim.launched >= 72 && peak > 36 { break; } + } + eprintln!("overlapping mass strikes: launched {}, peak airborne {peak}", sim.launched); + assert!(second && sim.launched >= 72, "second raid failed to launch; launched {}", sim.launched); + assert!(peak > 36, "raids never overlapped; peak {peak}"); +}