From ce76f20074f2bea838c7745c8b5d8ac91cc83000 Mon Sep 17 00:00:00 2001 From: chexit Date: Mon, 2 Nov 2026 23:58:35 +0300 Subject: [PATCH] Target SEAD strikes at observed radar contacts Co-authored-by: Codex --- airdef-mod/src/sim/ew.rs | 2 +- airdef-mod/src/sim/raid.rs | 33 ++++++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/airdef-mod/src/sim/ew.rs b/airdef-mod/src/sim/ew.rs index 05b9c1c..2e4e527 100644 --- a/airdef-mod/src/sim/ew.rs +++ b/airdef-mod/src/sim/ew.rs @@ -175,7 +175,7 @@ impl Sim { continue; } // Only those not yet sent at a unit. - if self.sams.iter().any(|s| s.pos.distance(d.target - d.err) < 6.0) { + if known.iter().any(|(_, pos)| pos.distance(d.target - d.err) < 6.0) { continue; } let to = if !ew.is_empty() { pick(n, &ew) } else { pick(n, &known) }; diff --git a/airdef-mod/src/sim/raid.rs b/airdef-mod/src/sim/raid.rs index 0495359..765f4a2 100644 --- a/airdef-mod/src/sim/raid.rs +++ b/airdef-mod/src/sim/raid.rs @@ -539,12 +539,14 @@ impl Sim { } } - /// The strikes a smart raid sends at the radars (they emit, so the enemy knows them whether or not they - /// fired): for the drones of `kinds` in order, the radar's place and id for those that go for one. Each radar + /// The strikes a smart raid sends at observed radars: for drones of `kinds` in order, + /// the last observed place and id for those that go for one. Each radar /// gets two or three, the fast strikers first, and all together no more than about a quarter of the raid. pub(super) fn radar_strikes(&self, kinds: &[(DroneKind, u32)]) -> Vec> { - let mut radars: Vec<&Sam> = self.sams.iter().filter(|s| s.alive() && s.kind == SamKind::Radar).collect(); - radars.sort_by_key(|s| s.id); + let mut radars: Vec<(u32, DVec3)> = self.known_units().into_iter() + .filter(|(id, _)| self.sams.iter().any(|s| s.id == *id && s.alive() && s.kind == SamKind::Radar)) + .collect(); + radars.sort_by_key(|(id, _)| *id); let flat: Vec = kinds.iter().flat_map(|&(k, n)| std::iter::repeat_n(k, n as usize)).collect(); let mut jobs = vec![None; flat.len()]; if radars.is_empty() { @@ -559,7 +561,7 @@ impl Sim { let cap = (flat.len() / 4).max(1); for (n, &(_, i)) in ranked.iter().take((radars.len() * want).min(cap)).enumerate() { let radar = radars[n % radars.len()]; - jobs[i] = Some((radar.pos, radar.id)); + jobs[i] = Some((radar.1, radar.0)); } jobs } @@ -969,6 +971,26 @@ mod tests { sim.drones.iter().chain(sim.queue.iter().map(|q| &q.1)).filter(|d| d.alive() && (d.target - d.err).distance(p) < 6.0).map(|d| d.kind).collect() } + #[test] + fn sead_uses_only_unexpired_observations_at_the_last_known_position() { + let mut sim = Sim::new(121); + let old = DVec3::new(100.0, 70.0, 200.0); + let mut radar = Sam::new(1, SamKind::Radar, old, 0.0); + radar.radar_on = false; + sim.sams.push(radar); + let kinds = [(DroneKind::Kh22, 8)]; + assert!(sim.radar_strikes(&kinds).iter().all(Option::is_none)); + sim.tick = 20; + sim.intel.revealed.push((1, old, sim.tick)); + sim.sams[0].pos = DVec3::new(900.0, 70.0, 900.0); + sim.tick = 40; + let jobs = sim.radar_strikes(&kinds); + assert_eq!(jobs.iter().flatten().count(), 2); + assert!(jobs.iter().flatten().all(|job| *job == (old, 1))); + sim.tick = 6020; + assert!(sim.radar_strikes(&kinds).iter().all(Option::is_none)); + } + #[test] fn a_smart_raid_goes_for_every_radar_and_the_enemys_adapting_does_not_turn_it_away() { let mut sim = Sim::new(21); @@ -978,6 +1000,7 @@ mod tests { let p = DVec3::new(at.0, 70.0, at.1); sim.sams.push(Sam::new(id, SamKind::Radar, p, i as f64)); radars.push((id, p)); + sim.intel.revealed.push((id, p, sim.tick)); } let ew = sim.id(); sim.sams.push(Sam::new(ew, SamKind::Ew, DVec3::new(0.0, 70.0, 0.0), 0.0));