Match counter-strike launch audio and live smoke to replay (#109)
build / mod (push) Successful in 8m46s

This commit is contained in:
2026-11-02 00:45:14 +03:00
parent 733dc07863
commit afe320786d
2 changed files with 70 additions and 2 deletions
+28 -2
View File
@@ -535,10 +535,15 @@ impl Player {
}
}
for tr in rec.tracks.iter().filter(|tr| (self.heard..self.t).contains(&(tr.first as f64))) {
if let What::Missile(kind, _) = tr.what
let event = match tr.what {
What::Missile(kind, _) => Some(kind.launch_sound()),
What::Strike => Some(DroneKind::Kalibr.launch_sound()),
What::Drone(_) => None,
};
if let Some(event) = event
&& let Some(p) = tr.at(rec.origin, tr.first as f64)
{
out.sound(kind.launch_sound(), heard(p), 1.0, 1.0);
out.sound(event, heard(p), 1.0, 1.0);
}
}
self.heard = self.t;
@@ -1102,6 +1107,27 @@ mod engine_tests {
let _ = rec;
p
}
#[test]
fn counter_strike_launch_is_heard_once_and_can_be_replayed_after_seek() {
let mut rec = Recording::blank(100);
rec.straight(1, What::Strike, 10, 70, DVec3::ZERO, DVec3::new(300.0, 70.0, 0.0));
let mut p = player(&rec);
p.heard = 9.0;
p.t = 11.0;
let mut out = Out::default();
p.sounds(&rec, DVec3::ZERO, &mut out);
assert_eq!(out.sounds.len(), 1);
assert_eq!(out.sounds[0].0, DroneKind::Kalibr.launch_sound());
out.sounds.clear();
p.t = 12.0;
p.sounds(&rec, DVec3::ZERO, &mut out);
assert!(out.sounds.is_empty(), "the loop engine must not repeat the launch");
p.heard = 9.0; // seek/cut resets the one-shot cursor
p.t = 11.0;
p.sounds(&rec, DVec3::ZERO, &mut out);
assert_eq!(out.sounds.len(), 1);
}
/// A host that remembers which sources run.
#[derive(Default)]
struct Host(std::collections::BTreeMap<u32, [f64; 3]>);
+42
View File
@@ -247,6 +247,9 @@ impl Smoke {
self.trail((1, m.id), m.pos, tick, look);
}
}
for f in &sim.strikes.flights {
self.trail((2, f.id), f.pos, tick, drone_look(Class::Cruise, crate::sim::DroneKind::Kalibr.spec().trail_scale()));
}
// Forget what is gone.
if tick % 100 == 0 {
let alive: std::collections::HashSet<(u8, u32)> = sim
@@ -254,6 +257,7 @@ impl Smoke {
.iter()
.map(|d| (0, d.id))
.chain(sim.missiles.iter().map(|m| (1, m.id)))
.chain(sim.strikes.flights.iter().map(|f| (2, f.id)))
.collect();
self.last.retain(|k, _| alive.contains(k));
}
@@ -286,6 +290,44 @@ mod tests {
use super::*;
use crate::sim::{DroneKind, MissileKind};
#[test]
fn live_counter_strike_leaves_a_trail_and_releases_its_source() {
use crate::sim::sites::{LaunchSite, SiteClass};
use crate::sim::strike::{Estimate, Known, SiteState};
use glam::DVec2;
let mut sim = Sim::new(11);
let id = 1;
sim.sites.list.push(LaunchSite {
id, side: 0, sector: 0, bearing: 0.0, class: SiteClass::Ramp,
pos: DVec3::new(0.0, 64.0, 300.0), range: 300.0, water: false,
stock: 0, planned: 0, alive: true, next_free: 0, offmap: false,
});
sim.strikes.known.push(Known {
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,
});
sim.order_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);
let from = sim.strikes.flights[0].pos;
sim.tick = 2;
sim.strikes.flights[0].pos += DVec3::Z * 8.0;
smoke.update(&sim);
let segments = smoke.segments(&sim);
assert_eq!(segments.len(), 1, "a live strike must emit the cruise trail also seen in replay");
assert_eq!(segments[0].az, from.z);
assert_eq!(segments[0].bz, from.z + 8.0);
let expected = drone_look(Class::Cruise, DroneKind::Kalibr.spec().trail_scale());
assert_eq!(segments[0].density, expected.density);
sim.strikes.flights.clear();
sim.tick = 100;
smoke.update(&sim);
assert!(smoke.last.is_empty(), "finished strikes must not leave source bookkeeping behind");
}
#[test]
fn trails_follow_one_rule() {
assert!(missile_look(MissileKind::P1sun).is_none());