Bound counter-strike steps and check terminal collisions (#106 #117)

This commit is contained in:
2026-11-02 00:45:14 +03:00
parent cd3c4ed76d
commit 5860c5009f
+69 -9
View File
@@ -587,12 +587,11 @@ impl Sim {
let below = ground.at(DVec3::new(f.land.x, 0.0, f.land.y), now);
let end = DVec3::new(f.land.x, below.unwrap_or(f.from.y), f.land.y);
let t = ((now - f.start) as f64 / (f.arrive - f.start).max(1) as f64).clamp(0.0, 1.0);
let want = f.from.lerp(end, t) + DVec3::Y * (TUNE.height * (std::f64::consts::PI * t).sin().max(0.0).sqrt());
let planned = f.from.lerp(end, t) + DVec3::Y * (TUNE.height * (std::f64::consts::PI * t).sin().max(0.0).sqrt());
let from = f.pos;
if now >= f.arrive && from.distance(end) <= TUNE.speed * 1.5 + 2.0 {
self.land_strike(f, from, ground, player, out);
continue;
}
// Time spent waiting for world data must not accumulate into a faster step.
let delta = planned - from;
let want = from + delta.clamp_length_max(TUNE.speed);
// A missile that never reaches its point (the world never tells of the way) ends somewhere: it does
// not stay in the air for ever.
if now >= f.arrive + 600 {
@@ -605,9 +604,11 @@ impl Sim {
}
let sweep = ground.trace(f.key(), from, want, false, now);
let mut pos = want;
let mut permitted = true;
let contact = match sweep {
world::Sweep::Hit { point, .. } => Some(point),
world::Sweep::Wait { t } => {
permitted = false;
pos = from.lerp(want, (t - 1e-6).max(0.0));
None
}
@@ -616,19 +617,32 @@ impl Sim {
// the game doesn't have (not one still loading) is passed, high over the land.
let low = ground.at(from, now).is_some_and(|g| from.y < g + 12.0);
if let Some(t) = ground.unread(&sweep, &mut f.hold, low) {
permitted = false;
pos = from.lerp(want, t);
}
None
}
world::Sweep::Clear => None,
world::Sweep::Clear => {
ground.unread(&sweep, &mut f.hold, false);
None
},
};
if let Some(point) = contact {
self.crash_strike(f, point, ground, player, out);
// A surface exactly at the intended endpoint is the landing, not an intervening wall.
if point.distance(end) < 1e-6 {
self.land_strike(f, point, ground, player, out);
} else {
self.crash_strike(f, point, ground, player, out);
}
continue;
}
if permitted && pos.distance(end) < 1e-6 {
self.land_strike(f, pos, ground, player, out);
continue;
}
// The land's height says it met the ground where the blocks were not read: the same contact.
let here = ground.at(pos, now);
if ground.ground_hit(here, pos, 0.2, &sweep, now) {
if permitted && ground.ground_hit(here, pos, 0.2, &sweep, now) {
self.crash_strike(f, pos, ground, player, out);
continue;
}
@@ -641,7 +655,7 @@ impl Sim {
out.model(id + 1, "airdef:geran3_flame", pos + rot * DVec3::new(0.0, up * spec.scale, back * spec.scale), rot, spec.scale * size * flicker, true);
}
if let Some((event, pitch)) = DroneKind::Kalibr.engine_voice() {
out.engine(id, event, pos, dir * TUNE.speed, 0.8, pitch, false);
out.engine(id, event, pos, pos - from, 0.8, pitch, false);
}
f.pos = pos;
rest.push(f);
@@ -1413,6 +1427,52 @@ mod tests {
assert_eq!(sim.strikes.fired, 1);
}
#[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();
let f = sim.strikes.flights[0];
let wall = Wall { at: f.land.y.floor() as i32 - 1, hole: None };
for _ in 0..500 {
serve(&mut ground, &wall, sim.tick);
sim.step(&mut ground, None, &mut out);
if sim.strikes.flights.is_empty() { break; }
}
assert!(sim.strikes.flights.is_empty());
assert_eq!((sim.strikes.destroyed, sim.strikes.missed), (0, 1));
assert!(sim.sites.get(id).unwrap().alive);
assert_eq!(sim.sites.pending(id), 2);
assert!(matches!(strike_events(&sim).as_slice(), [record::EventKind::Strike { result: record::StrikeResult::Blocked, .. }]));
assert!((sim.blasts[0].at.z - f64::from(wall.at)).abs() < 0.01);
}
#[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();
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 };
warm(&mut ground, &open, f.from, DVec3::new(f.land.x, 110.0, f.land.y));
let mut previous = f.pos;
for _ in 0..500 {
serve(&mut ground, &open, sim.tick);
sim.step(&mut ground, None, &mut out);
if let Some(f) = sim.strikes.flights.first() {
assert!(previous.distance(f.pos) <= TUNE.speed + 1e-8, "jump at tick {}", sim.tick);
previous = f.pos;
} else { break; }
}
assert!(sim.strikes.flights.is_empty(), "strike failed to arrive");
assert_eq!(sim.strikes.destroyed, 1, "{:?}", out.messages);
}
// #106: an open way works as before: the missile reaches its point, the site dies, one result, one stock spent.
#[test]
fn a_counter_strike_on_an_open_way_still_reaches_its_site() {