From 29b047abafe2da1b48901d03f042585c551cd272 Mon Sep 17 00:00:00 2001 From: chexit Date: Mon, 2 Nov 2026 21:00:22 +0300 Subject: [PATCH] Remove seed-only defenders and reject unsupported player-launched strikes (#118) No compatible coordinate-strike loadout is implemented yet. Preserve flight and collision mechanics through explicit test fixtures without allowing generic stock or player position to create a production missile. Physical carriers, typed ammunition and logistics remain to implement. Co-Authored-By: Codex --- airdef-mod/src/sim/sites.rs | 4 +- airdef-mod/src/sim/strike.rs | 193 +++++++++++++++-------------------- airdef-mod/src/smoke.rs | 4 +- 3 files changed, 86 insertions(+), 115 deletions(-) diff --git a/airdef-mod/src/sim/sites.rs b/airdef-mod/src/sim/sites.rs index b7af4d8..2fcbcf2 100644 --- a/airdef-mod/src/sim/sites.rs +++ b/airdef-mod/src/sim/sites.rs @@ -1118,7 +1118,6 @@ mod tests { launches: 1, last: 0, state: super::super::strike::SiteState::Active, - cover: None, aimed: false, }); id @@ -1126,11 +1125,10 @@ mod tests { #[test] fn a_wave_keeps_the_site_a_strike_is_still_flying_to() { let mut sim = Sim::new(11); - sim.strikes.cover_override = Some(0); let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0)); let mut ground = Ground::default(); let mut out = Out::default(); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); // The next wave is planned while the missile is in the air: its housekeeping must not take the target away. let refs = sim.site_refs(); assert!(refs.contains(&id), "the flight is no reference to its site"); diff --git a/airdef-mod/src/sim/strike.rs b/airdef-mod/src/sim/strike.rs index a89e09d..102bc49 100644 --- a/airdef-mod/src/sim/strike.rs +++ b/airdef-mod/src/sim/strike.rs @@ -10,11 +10,10 @@ //! Directly observed launches provide independent point sightings, combined by weighted least squares (`solve`). //! A site without a sufficiently accurate fix remains a bearing and cannot be struck. //! -//! **Striking it.** `order_strike`: one of a few strike missiles (a stock that refills slowly), fired from where the -//! player stands at the *estimated* place. It flies `STRIKE_SPEED`; on arrival the enemy's cover may shoot it down; else -//! it lands with its own scatter. The true distance from the site decides: inside `HIT_R` the site is destroyed (stock -//! burnt, jobs cancelled, wreck and fire), inside `SUPPRESS_R` it is stunned for a while (its launches are delayed, not -//! cancelled), else it hit the ground beside it and nothing changed. So a badly known site is very likely a wasted missile. +//! **Striking it.** Production orders require a real carrier and an explicitly supported coordinate-strike +//! munition profile. No such loadout is currently implemented, so orders are rejected without spending ammunition. +//! The flight/impact mechanics remain available to test fixtures and existing flights; there is no virtual enemy +//! air defence. Only an actual simulated interceptor can justify an intercepted outcome. //! //! **Off the map** (`offmap`: no land was found or loaded, so no kit stands anywhere): the site is scouted like any other, //! as bearing and range, but there is nothing to see burn. A strike that would have hit just ends its launches @@ -56,9 +55,6 @@ pub struct Tune { pub suppress_r: f64, /// Ticks a stunned site launches nothing. pub suppress_ticks: u64, - /// The chance the enemy's cover shoots the missile down: none, light, heavy; and the shares of sites that have them. - pub cover_p: [f64; 3], - pub cover_share: [f64; 2], /// Ticks the wreck of a destroyed site smokes. pub burn: u64, /// A launched drone not taken into account after this many ticks is forgotten. @@ -84,8 +80,6 @@ pub const TUNE: Tune = Tune { hit_r: 35.0, suppress_r: 90.0, suppress_ticks: 600, - cover_p: [0.0, 0.3, 0.65], - cover_share: [0.4, 0.75], burn: 1800, scout_life: 1800, }; @@ -240,8 +234,6 @@ pub struct Known { pub launches: u32, pub last: u64, pub state: SiteState, - /// What a strike taught of its cover (0 none, 1 light, 2 heavy). - pub cover: Option, /// Where the strike on it is aimed, if one is on its way. pub aimed: bool, } @@ -265,8 +257,6 @@ pub struct Target { pub stock: u32, pub offmap: bool, pub alive: bool, - /// What its cover was (0 none, 1 light, 2 heavy), fixed at the launch. - pub cover: u8, } /// A strike missile in flight: the shot it was fired as, where it is, and where it is going. @@ -321,8 +311,6 @@ pub struct Strikes { pub stunned: u32, pub missed: u32, pub shot_down: u32, - /// The enemy's cover forced (tests); else it follows the seed. - pub cover_override: Option, } impl Strikes { @@ -391,33 +379,11 @@ impl Known { } } }; - let cover = match self.cover { - Some(0) => " · ПВО не прикрыта", - Some(_) => " · прикрыта ПВО", - None => "", - }; - format!("ПУ №{} ({}): {place}{state}{cover}; пусков замечено {}", self.site, class_name(self.class), self.launches) + format!("ПУ №{} ({}): {place}{state}; пусков замечено {}", self.site, class_name(self.class), self.launches) } } impl Sim { - /// Whether the enemy covers this site: 0 none, 1 light, 2 heavy; fixed by the seed and the site. - pub fn cover_of(&self, site: &LaunchSite) -> u8 { - if let Some(c) = self.strikes.cover_override { - return c; - } - let r = dice(self.seed, site.id as u64, site.side as u64, 101); - // A ballistic battery is a prize: guarded more often. - let r = if site.class == SiteClass::Battery { r * 0.7 } else { r }; - if r < TUNE.cover_share[0] { - 0 - } else if r < TUNE.cover_share[1] { - 1 - } else { - 2 - } - } - /// A drone just left site `site` (a launch the radars may have seen). pub(super) fn scout_launch(&mut self, drone: u32, site: u32, kind: DroneKind, at: DVec3) { let Some(s) = self.sites.get(site) else { return }; @@ -455,7 +421,7 @@ impl Sim { let i = match self.strikes.known.iter().position(|k| k.site == site) { Some(i) => i, None => { - self.strikes.known.push(Known { site, class, obs: Vec::new(), est: None, launches: 0, last: now, state: SiteState::Active, cover: None, aimed: false }); + self.strikes.known.push(Known { site, class, obs: Vec::new(), est: None, launches: 0, last: now, state: SiteState::Active, aimed: false }); self.strikes.known.len() - 1 } }; @@ -494,6 +460,7 @@ impl Sim { pub(super) fn step_strikes(&mut self, ground: &mut Ground, player: Option, out: &mut Out) { let now = self.tick; out.messages.append(&mut self.strikes.news); + #[cfg(any(test, feature = "test-fixtures"))] if self.strikes.stock < TUNE.stock_max { self.strikes.progress += 1; if self.strikes.progress >= TUNE.resupply { @@ -679,8 +646,22 @@ impl Sim { v } - /// Fires a strike missile at known site `id` from `from`. Returns the line for the player. - pub fn order_strike(&mut self, id: u32, from: DVec3, out: &mut Out) -> Result { + /// A manual order never substitutes the player or a generic stock for a physical launcher. + pub fn order_strike(&mut self, id: u32, _from: DVec3, _out: &mut Out) -> Result { + self.strikes.get(id).ok_or_else(|| format!("ПУ №{id} не найдена: /airdef sites покажет известные"))?; + Err(if self.sams.is_empty() { + "Удар недоступен: нет собственной пусковой с совместимым боеприпасом".into() + } else { + let reason = match super::catalog::unit(self.sams[0].kind).ground_strike() { + super::catalog::GroundStrike::Unavailable { reason } => reason, + }; + format!("Удар недоступен: {reason}; совместимый боеприпас не загружен") + }) + } + + /// Synthetic launcher used only by flight/impact regression fixtures; absent from production builds. + #[cfg(any(test, feature = "test-fixtures"))] + pub fn order_test_strike(&mut self, id: u32, from: DVec3, out: &mut Out) -> Result { let k = self.strikes.get(id).ok_or_else(|| format!("ПУ №{id} не найдена: /airdef sites покажет известные"))?; let est = k.est.filter(|e| e.fix).ok_or_else(|| { format!("ПУ №{id}: места не знаем, только азимут. Нужно больше пусков на виду или вторая РЛС в стороне от первой") @@ -700,7 +681,7 @@ impl Sim { } // The target as it is now: the missile is judged against this whatever becomes of the site meanwhile (#108). let site = self.sites.get(id).ok_or_else(|| format!("ПУ №{id} уже нет на карте"))?; - let snap = Target { pos: DVec2::new(site.pos.x, site.pos.z), stock: site.stock, offmap: site.offmap, alive: site.alive, cover: self.cover_of(site) }; + let snap = Target { pos: DVec2::new(site.pos.x, site.pos.z), stock: site.stock, offmap: site.offmap, alive: site.alive }; self.strikes.stock -= 1; self.strikes.fired += 1; self.strikes.next += 1; @@ -723,7 +704,6 @@ impl Sim { }); if let Some(k) = self.strikes.known.iter_mut().find(|k| k.site == id) { k.aimed = true; - k.cover = Some(snap.cover); } out.sound(DroneKind::Kalibr.launch_sound(), start, 1.0, 1.0); out.particles("minecraft:large_smoke", start, 0.6, DVec3::ZERO, 0.05, 30); @@ -742,7 +722,6 @@ impl Sim { let now = self.tick; if let Some(k) = self.strikes.known.iter_mut().find(|k| k.site == f.site) { k.aimed = false; - k.cover = Some(f.snap.cover); } // The site as it stands now; if it has been taken off the map since the launch (a new wave's housekeeping, // a clear), the shot was still fired at that place: it has its outcome by its coordinates (#108). @@ -750,16 +729,7 @@ impl Sim { Some(s) => (DVec2::new(s.pos.x, s.pos.z), s.offmap, s.alive, s.stock), None => (f.snap.pos, f.snap.offmap, f.snap.alive, f.snap.stock), }; - let cover = f.snap.cover; - // The enemy's cover. - if dice(self.seed, f.id as u64, f.site as u64, 201) < TUNE.cover_p[cover as usize] { - self.strikes.shot_down += 1; - let high = at + DVec3::Y * 40.0; - self.blast_at(high, 0, true, false, ground, player, out); - self.emit_combat_event(high, record::EventKind::Strike { site: f.site, result: record::StrikeResult::Intercepted }); - out.messages.push(format!("Удар по ПУ №{}: ракета сбита ПВО противника у площадки, цель цела", f.site)); - return; - } + // No seed-only defender: interceptions must come from physical simulated combat (#118). // Where it lands was fixed at the launch; how far from the site decides. let miss = (f.land - truth).length(); let result = if offmap && miss <= TUNE.hit_r { @@ -835,7 +805,7 @@ impl Sim { } /// A strike missile met the land or a building short of its aim: it goes off there, and the site is untouched - /// because nothing was aimed at. One result for the statistics and the record, before any cover (#106). + /// because nothing was aimed at. One result for the statistics and the record, before impact (#106). fn crash_strike(&mut self, f: Flight, at: DVec3, ground: &mut Ground, player: Option, out: &mut Out) { if let Some(k) = self.strikes.known.iter_mut().find(|k| k.site == f.site) { k.aimed = false; @@ -880,14 +850,7 @@ impl Sim { if v.is_empty() { v.push("Известных ПУ нет: их находят РЛС по пускам и трекам вылетевших целей".into()); } - v.push(format!( - "Ударных ракет: {}/{}{} · в полёте {} · дальность {}", - self.strikes.stock, - TUNE.stock_max, - if self.strikes.stock < TUNE.stock_max { format!(" (следующая через {} с)", self.strikes.seconds_to_next()) } else { String::new() }, - self.strikes.flights.len(), - super::distance(TUNE.range) - )); + v.push("Удар по координатам недоступен: требуется реализованный совместимый носитель и боеприпас".into()); v } } @@ -946,7 +909,7 @@ mod tests { /// (None: no radar). fn scene(seed: u64, n: u32, radar: Option) -> (Sim, Ground, Out, u32) { let mut sim = Sim::new(seed); - sim.strikes.cover_override = Some(0); + if let Some(at) = radar { let id = sim.id(); sim.sams.push(Sam::new(id, SamKind::Patriot, at, 0.0)); @@ -1066,10 +1029,10 @@ mod tests { let before = sim.launched; assert!(before >= 3 && before < 14, "{before}"); let player = DVec3::new(0.0, 64.0, 0.0); - let said = sim.order_strike(id, player, &mut out).unwrap(); + let said = sim.order_test_strike(id, player, &mut out).unwrap(); assert!(said.contains("ПУ"), "{said}"); assert_eq!(sim.strikes.stock, TUNE.stock - 1); - assert!(sim.order_strike(id, player, &mut out).is_err(), "a second missile at the same site while one flies"); + assert!(sim.order_test_strike(id, player, &mut out).is_err(), "a second missile at the same site while one flies"); out.messages.clear(); run(&mut sim, &mut ground, &mut out, 400); let site = sim.sites.get(id).unwrap(); @@ -1080,7 +1043,7 @@ mod tests { assert!(sim.strikes.wrecks.len() == 1 && sim.fires.list.len() == 1, "a wreck and a fire"); assert!(!sim.blasts.is_empty() || sim.tick > 0); assert!(matches!(sim.strikes.get(id).unwrap().state, SiteState::Destroyed(_))); - assert!(sim.order_strike(id, player, &mut out).is_err(), "destroyed already"); + assert!(sim.order_test_strike(id, player, &mut out).is_err(), "destroyed already"); // The result is on the record for replays and stats. assert_eq!((sim.strikes.fired, sim.strikes.destroyed), (1, 1)); } @@ -1099,7 +1062,7 @@ mod tests { k.est = Some(e); sim.strikes.known[0].obs.clear(); let queued = sim.queue.len(); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); out.messages.clear(); run(&mut sim, &mut ground, &mut out, 300); assert!(out.messages.iter().any(|m| m.contains("промах")), "{:?}", out.messages); @@ -1117,7 +1080,7 @@ mod tests { let mut e = k.est.unwrap(); e.pos = DVec2::new(0.0, 300.0 + 62.0); k.est = Some(e); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); let queued = sim.queue.len(); run(&mut sim, &mut ground, &mut out, 300); if sim.strikes.stunned == 1 { @@ -1143,14 +1106,13 @@ mod tests { launches: 1, last: 0, state: SiteState::Active, - cover: None, aimed: false, }); assert!(!sim.strikes.known[0].est.unwrap().fix); - let e = sim.order_strike(id, DVec3::ZERO, &mut out).unwrap_err(); + let e = sim.order_test_strike(id, DVec3::ZERO, &mut out).unwrap_err(); assert!(e.contains("азимут"), "{e}"); assert_eq!(sim.strikes.stock, TUNE.stock, "no missile spent"); - assert!(sim.order_strike(99, DVec3::ZERO, &mut out).is_err()); + assert!(sim.order_test_strike(99, DVec3::ZERO, &mut out).is_err()); } #[test] @@ -1158,9 +1120,9 @@ mod tests { let (mut sim, mut ground, mut out, id) = scene(11, 14, Some(DVec3::new(0.0, 64.0, 200.0))); run(&mut sim, &mut ground, &mut out, 120); // Too far. - assert!(sim.order_strike(id, DVec3::new(0.0, 64.0, 300.0 - TUNE.range - 100.0), &mut out).is_err()); + assert!(sim.order_test_strike(id, DVec3::new(0.0, 64.0, 300.0 - TUNE.range - 100.0), &mut out).is_err()); sim.strikes.stock = 0; - let e = sim.order_strike(id, DVec3::ZERO, &mut out).unwrap_err(); + let e = sim.order_test_strike(id, DVec3::ZERO, &mut out).unwrap_err(); assert!(e.contains("нет"), "{e}"); sim.strikes.progress = TUNE.resupply - 1; run(&mut sim, &mut ground, &mut out, 2); @@ -1168,26 +1130,38 @@ mod tests { } #[test] - fn enemy_cover_shoots_some_strikes_down_and_none_with_no_cover() { - let mut down = [0u32; 3]; - for cover in 0..3u8 { - for seed in 1..=30u64 { - let (mut sim, mut ground, mut out, id) = scene(seed, 14, Some(DVec3::new(0.0, 64.0, 200.0))); - sim.strikes.cover_override = Some(cover); - run(&mut sim, &mut ground, &mut out, 120); - sim.order_strike(id, DVec3::ZERO, &mut out).unwrap(); - run(&mut sim, &mut ground, &mut out, 200); - down[cover as usize] += sim.strikes.shot_down; - } + fn no_seed_only_enemy_defence_can_intercept_a_flight() { + for seed in 1..=30u64 { + let (mut sim, mut ground, mut out, id) = scene(seed, 14, Some(DVec3::new(0.0, 64.0, 200.0))); + run(&mut sim, &mut ground, &mut out, 120); + sim.order_test_strike(id, DVec3::ZERO, &mut out).unwrap(); + run(&mut sim, &mut ground, &mut out, 200); + assert_eq!(sim.strikes.shot_down, 0, "seed {seed}"); + assert!(!out.messages.iter().any(|m| m.contains("сбита ПВО противника"))); + assert!(sim.rec.iter().flat_map(|r| &r.events).chain(sim.replays.iter().flat_map(|r| &r.events)).all(|e| !matches!(e.kind, record::EventKind::Strike { result: record::StrikeResult::Intercepted, .. }))); } - assert_eq!(down[0], 0); - assert!(down[1] > 3 && down[1] < 20 && down[2] > down[1], "{down:?}"); + } + + #[test] + fn production_orders_never_launch_from_player_or_spend_generic_stock() { + let (mut sim, mut ground, mut out, id) = scene(11, 14, Some(DVec3::new(0.0, 64.0, 200.0))); + run(&mut sim, &mut ground, &mut out, 120); + let stock = sim.strikes.stock; + for player in [DVec3::ZERO, DVec3::new(4000.0, 64.0, 300.0)] { + let e = sim.order_strike(id, player, &mut out).unwrap_err(); + assert!(e.contains("боеприпас"), "{e}"); + assert!(sim.strikes.flights.is_empty()); + assert_eq!(sim.strikes.stock, stock); + assert_eq!(sim.strikes.fired, 0); + } + sim.sams.clear(); + assert!(sim.order_strike(id, DVec3::ZERO, &mut out).unwrap_err().contains("нет собственной")); } #[test] fn an_off_map_site_is_found_by_bearing_and_a_strike_there_only_ends_its_launches() { let mut sim = Sim::new(5); - sim.strikes.cover_override = Some(0); + // Two radars apart watch the start of an off-map site's drones. for x in [-150.0, 150.0] { let id = sim.id(); @@ -1224,7 +1198,7 @@ mod tests { // ... and the shot aims at what is known: force a good fix to test the outcome. let k = sim.strikes.known.iter_mut().find(|k| k.site == id).unwrap(); k.est = Some(Estimate { pos: DVec2::new(0.0, 700.0), radius: 20.0, fix: true, bearing: 3.14, bearing_err: 0.01, origin: DVec2::ZERO }); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); out.messages.clear(); let wrecks = sim.fires.list.len(); run(&mut sim, &mut ground, &mut out, 500); @@ -1239,7 +1213,7 @@ mod tests { let (mut sim, mut ground, mut out, id) = scene(11, 14, Some(DVec3::new(0.0, 64.0, 200.0))); run(&mut sim, &mut ground, &mut out, 100); // A recording is running (the launched drones started it): the strike lands in it. - sim.order_strike(id, DVec3::ZERO, &mut out).unwrap(); + sim.order_test_strike(id, DVec3::ZERO, &mut out).unwrap(); run(&mut sim, &mut ground, &mut out, 200); let count = |r: &record::Recording| r.events.iter().filter(|e| matches!(e.kind, record::EventKind::Strike { .. })).count(); let n = sim.rec.iter().chain(sim.replays.iter()).map(count).sum::(); @@ -1262,7 +1236,7 @@ mod tests { let land = Land(|_, _| 64); let (mut ground, mut out) = (Ground::default(), Out::default()); let mut sim = Sim::new(21); - sim.strikes.cover_override = Some(0); + let order = RaidOrder { kinds: vec![(DroneKind::Shahed, 30)], approach: Approach::Sector, dist: 300.0, aim: Aim::Objects }; sim.order_raid(&order, &[DVec3::new(0.0, 70.0, 0.0)], &mut out); let mut n = 0; @@ -1283,7 +1257,7 @@ mod tests { sim.step(&mut ground, None, &mut out); if !struck && sim.strikes.get(site.id).is_some_and(|k| k.est.is_some_and(|e| e.fix && e.radius < 40.0)) && sim.sites.pending(site.id) > 3 { left_at_strike = sim.sites.pending(site.id); - sim.order_strike(site.id, DVec3::new(0.0, 70.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(site.id, DVec3::new(0.0, 70.0, 0.0), &mut out).unwrap(); struck = true; } } @@ -1360,7 +1334,6 @@ mod tests { launches: 1, last: 0, state: SiteState::Active, - cover: None, aimed: false, }); for i in 0..stock { @@ -1378,17 +1351,17 @@ mod tests { } // #106: a wall between the shot and the site catches the missile: it goes off at the contact, the site is untouched - // and keeps its launches, the stock is spent once and the result is counted once, before any cover roll. + // and keeps its launches, the stock is spent once and the result is counted once, before impact handling. #[test] fn a_counter_strike_breaks_on_a_wall_between_it_and_its_site() { let mut sim = Sim::new(11); - // Heavy cover would shoot a missile down half the time: a broken one is never intercepted. - sim.strikes.cover_override = Some(2); + // A broken flight is counted once and never becomes a fictitious interception. + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 4); let (mut ground, mut out) = (Ground::default(), Out::default()); let wall = Wall { at: 140, hole: None }; warm(&mut ground, &wall, DVec3::new(0.0, 65.5, 0.0), DVec3::new(0.0, 105.0, 300.0)); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); out.messages.clear(); let mut crossed = false; for _ in 0..300 { @@ -1425,14 +1398,14 @@ mod tests { #[test] fn a_counter_strike_passes_an_opening_by_the_blocks_shape() { let mut sim = Sim::new(11); - sim.strikes.cover_override = Some(0); + // No launches: only the strike can make a recording here. let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 0); let (mut ground, mut out) = (Ground::default(), Out::default()); // A slit between 100 and 110 blocks high, where the missile flies at about 104. let wall = Wall { at: 140, hole: Some((100, 110)) }; warm(&mut ground, &wall, DVec3::new(0.0, 65.5, 0.0), DVec3::new(0.0, 105.0, 300.0)); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); let mut crossed = false; for _ in 0..400 { serve(&mut ground, &wall, sim.tick); @@ -1454,10 +1427,10 @@ mod tests { #[test] fn terminal_wall_is_resolved_before_landing() { let mut sim = Sim::new(11); - sim.strikes.cover_override = Some(0); + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 2); let (mut ground, mut out) = (Ground::default(), Out::default()); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); let f = sim.strikes.flights[0]; let wall = Wall { at: f.land.y.floor() as i32 - 1, hole: None }; for _ in 0..500 { @@ -1476,10 +1449,10 @@ mod tests { #[test] fn delayed_world_answers_do_not_accelerate_the_strike() { let mut sim = Sim::new(11); - sim.strikes.cover_override = Some(0); + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 1000.0), 0); let (mut ground, mut out) = (Ground::default(), Out::default()); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); for _ in 0..20 { sim.step(&mut ground, None, &mut out); } let f = sim.strikes.flights[0]; let open = Wall { at: 10_000, hole: None }; @@ -1501,11 +1474,11 @@ mod tests { #[test] fn a_counter_strike_on_an_open_way_still_reaches_its_site() { let mut sim = Sim::new(11); - sim.strikes.cover_override = Some(0); + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 2); let (mut ground, mut out) = (Ground::default(), Out::default()); let open = Wall { at: 10_000, hole: None }; - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); for _ in 0..400 { serve(&mut ground, &open, sim.tick); sim.step(&mut ground, None, &mut out); @@ -1524,10 +1497,10 @@ mod tests { #[test] fn a_strike_whose_site_is_taken_off_the_map_still_has_its_result() { let mut sim = Sim::new(11); - sim.strikes.cover_override = Some(0); + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 300.0), 0); let (mut ground, mut out) = (Ground::default(), Out::default()); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); // The world unloads (or the sky is cleared): no site to find on arrival. sim.clear(); assert!(sim.sites.get(id).is_none()); @@ -1551,11 +1524,11 @@ mod tests { #[test] fn a_late_counter_strike_keeps_the_recording_until_it_lands() { let mut sim = Sim::new(11); - sim.strikes.cover_override = Some(0); + let id = fixed_site(&mut sim, DVec3::new(0.0, 64.0, 1000.0), 0); let (mut ground, mut out) = (Ground::default(), Out::default()); let open = Wall { at: 10_000, hole: None }; - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut out).unwrap(); let drone = sim.make_drone(DroneKind::Shahed, DVec3::new(0.0, 65.0, 0.0), -DVec3::Z, DVec3::new(0.0, 64.0, 1000.0)); sim.drones.push(drone); serve(&mut ground, &open, sim.tick); diff --git a/airdef-mod/src/smoke.rs b/airdef-mod/src/smoke.rs index 552195c..1def21f 100644 --- a/airdef-mod/src/smoke.rs +++ b/airdef-mod/src/smoke.rs @@ -338,9 +338,9 @@ mod tests { site: id, class: SiteClass::Ramp, obs: Vec::new(), est: Some(Estimate { pos: DVec2::new(0.0, 300.0), radius: 5.0, fix: true, bearing: 0.0, bearing_err: 0.01, origin: DVec2::ZERO }), - launches: 1, last: 0, state: SiteState::Active, cover: None, aimed: false, + launches: 1, last: 0, state: SiteState::Active, aimed: false, }); - sim.order_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut crate::sim::Out::default()).unwrap(); + sim.order_test_strike(id, DVec3::new(0.0, 64.0, 0.0), &mut crate::sim::Out::default()).unwrap(); let mut smoke = Smoke::default(); sim.tick = 1; smoke.update(&sim);