diff --git a/airdef-mod/src/sim/mod.rs b/airdef-mod/src/sim/mod.rs index 7cb7b3b..b2d005a 100644 --- a/airdef-mod/src/sim/mod.rs +++ b/airdef-mod/src/sim/mod.rs @@ -509,7 +509,13 @@ impl Ground { /// Explosions change the land: forget what was known round them. fn forget(&mut self, at: DVec3, radius: i32) { let (cx, cz) = (at.x.floor() as i32, at.z.floor() as i32); - self.known.retain(|&(x, z), _| (x - cx).abs() > radius || (z - cz).abs() > radius); + // A blast affects a small square. Removing those columns directly avoids scanning the + // entire height cache once per explosion (and again when its confirmation times out). + for x in cx.saturating_sub(radius)..=cx.saturating_add(radius) { + for z in cz.saturating_sub(radius)..=cz.saturating_add(radius) { + self.known.remove(&(x, z)); + } + } self.world.touched(at, f64::from(radius) + 2.0); } } diff --git a/airdef-mod/src/sim/regress.rs b/airdef-mod/src/sim/regress.rs index b8b4b11..3dc9c48 100644 --- a/airdef-mod/src/sim/regress.rs +++ b/airdef-mod/src/sim/regress.rs @@ -1989,3 +1989,20 @@ fn cold_ejection_power_and_coast_use_their_own_speed_rules() { assert!((fly(spec.cold + kind.burn_ticks(), 2.0) - 2.0 * 0.995).abs() < 1e-9, "coast drag"); assert!((fly(0, 0.2) - 0.4).abs() < 1e-9, "cold ejection minimum"); } + +#[test] +fn blast_forget_removes_only_columns_in_the_same_square() { + let mut ground = Ground::default(); + for x in -25..=25 { + for z in -25..=25 { + ground.known.insert((x, z), (x * 100 + z, 123)); + } + } + let before = ground.known.clone(); + let at = DVec3::new(-3.2, 70.0, 4.9); + ground.forget(at, 8); + let expected: std::collections::HashMap<_, _> = before.into_iter() + .filter(|&((x, z), _)| (x + 4_i32).abs() > 8 || (z - 4_i32).abs() > 8) + .collect(); + assert_eq!(ground.known, expected); +}