diff --git a/airdef-mod/src/sim/mod.rs b/airdef-mod/src/sim/mod.rs index fbdd3ac..fdc0708 100644 --- a/airdef-mod/src/sim/mod.rs +++ b/airdef-mod/src/sim/mod.rs @@ -2959,6 +2959,7 @@ impl Sim { let mut done: Vec<(u32, Option, bool, u32)> = Vec::new(); // (missile, target, kill, sam) let (mut missed, mut expired, mut orphaned) = (0u32, 0u32, 0u32); let (mut blocked, mut scrubbed) = (0u32, 0u32); + let mut impacts = HashMap::new(); let mut ignitions = Vec::new(); let mut burnouts = Vec::new(); #[cfg(test)] @@ -3091,6 +3092,9 @@ impl Sim { let ratio = (speed / t.vel.length().max(0.1)).min(1.0).powf(1.5); let fit = sams_fit.get(&m.sam).copied().unwrap_or(1.0) * launchers.get(&m.sam).map_or(1.0, |(at, max)| why::reach_fit(*max, at.distance(m.pos))); let kill = self.rng.chance(spec.pk * fit * ratio); + // The same swept closest approach determines the physical impact, not the + // target's end-of-tick location. Velocities are per simulation tick. + impacts.insert(m.id, (t.pos - t.vel + t.vel * k, (m.vel - t.vel).length())); missed += u32::from(!kill); done.push((m.id, Some(t.id), kill, m.sam)); continue; @@ -3159,7 +3163,7 @@ impl Sim { if let Some(t) = target.and_then(|t| self.drones.iter().position(|d| d.id == t)) { self.drones[t].engaged = self.drones[t].engaged.saturating_sub(1); if kill { - let (at, closing) = (self.drones[t].pos, (m.vel - self.drones[t].vel).length()); + let (at, closing) = impacts[&mid]; let _ = self.shoot_down_by(t, sam, Some(mid), Impact::missile(m.kind, at, closing), ground, out); } }