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 <noreply@openai.com>
This commit is contained in:
2026-11-02 21:00:22 +03:00
parent 814f8c7e83
commit 29b047abaf
3 changed files with 86 additions and 115 deletions
+1 -3
View File
@@ -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");
+83 -110
View File
@@ -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<u8>,
/// 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<u8>,
}
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<DVec3>, 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<String, String> {
/// 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<String, String> {
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<String, String> {
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<DVec3>, 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<DVec3>) -> (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::<usize>();
@@ -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);
+2 -2
View File
@@ -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);