Release interceptor engine source at burnout
Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
@@ -82,6 +82,12 @@ impl Engines {
|
||||
self.live.is_empty()
|
||||
}
|
||||
|
||||
/// An explicitly unpowered entity releases its source without waiting for the missing-frame grace.
|
||||
pub fn stop(&mut self, key: u32) -> Option<Op> {
|
||||
self.dead.remove(&key);
|
||||
self.live.remove(&key).map(|live| Op::Stop(live.id))
|
||||
}
|
||||
|
||||
/// The game released every source: forget them (nothing to stop).
|
||||
pub fn reset(&mut self) {
|
||||
self.live.clear();
|
||||
@@ -197,6 +203,15 @@ mod tests {
|
||||
damaged: false,
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn explicit_burnout_releases_the_source_immediately() {
|
||||
let mut engines = Engines::default();
|
||||
let ops = engines.step(vec![d(42, 0.0)], false, DVec3::ZERO);
|
||||
assert!(matches!(ops.as_slice(), [Op::Start(_, _)]));
|
||||
assert!(matches!(engines.stop(42), Some(Op::Stop(_))));
|
||||
assert!(engines.is_empty());
|
||||
assert!(engines.stop(42).is_none());
|
||||
}
|
||||
fn starts(ops: &[Op]) -> usize {
|
||||
ops.iter().filter(|o| matches!(o, Op::Start(..))).count()
|
||||
}
|
||||
|
||||
@@ -715,13 +715,14 @@ impl AirDef {
|
||||
self.engines.reset();
|
||||
self.engine_gen = generation;
|
||||
}
|
||||
if self.engines.is_empty() && out.engines.is_empty() && !cut {
|
||||
if self.engines.is_empty() && out.engines.is_empty() && out.engine_stops.is_empty() && !cut {
|
||||
return;
|
||||
}
|
||||
let ops: Vec<render::SourceOp> = self
|
||||
.engines
|
||||
.step(std::mem::take(&mut out.engines), cut, ctx.listener)
|
||||
.into_iter()
|
||||
let mut changes = self.engines.step(std::mem::take(&mut out.engines), cut, ctx.listener);
|
||||
for key in std::mem::take(&mut out.engine_stops) {
|
||||
if let Some(op) = self.engines.stop(key) { changes.push(op); }
|
||||
}
|
||||
let ops: Vec<render::SourceOp> = changes.into_iter()
|
||||
.map(|op| match op {
|
||||
engines::Op::Start(id, v) => source_op(render::SourceOpKind::Start, id, &v),
|
||||
engines::Op::Update(id, v) => source_op(render::SourceOpKind::Update, id, &v),
|
||||
@@ -1949,6 +1950,7 @@ impl Mod for AirDef {
|
||||
out.particles.clear();
|
||||
out.sounds.clear();
|
||||
out.engines.clear();
|
||||
out.engine_stops.clear();
|
||||
out.shake = None;
|
||||
}
|
||||
playback.tick(&self.sim, &mut self.ground, tick_context.listener, &mut out);
|
||||
@@ -1968,6 +1970,7 @@ impl Mod for AirDef {
|
||||
out.particles.clear();
|
||||
out.sounds.clear();
|
||||
out.engines.clear();
|
||||
out.engine_stops.clear();
|
||||
out.shake = None;
|
||||
}
|
||||
playback.tick(&self.sim, &mut self.ground, tick_context.listener, &mut out);
|
||||
|
||||
@@ -500,6 +500,8 @@ impl Player {
|
||||
if rec.missile_is_lit(tr, self.t, kind.lit_after()) {
|
||||
let (event, gain, pitch) = kind.engine_voice();
|
||||
out.engine(key, event, at, vel, gain, pitch, false);
|
||||
} else {
|
||||
out.engine_stops.push(key);
|
||||
}
|
||||
}
|
||||
What::StrikeCarrier(_) => {
|
||||
@@ -1146,6 +1148,11 @@ mod engine_tests {
|
||||
assert!(!rec.missile_is_lit(&rec.tracks[0], 50.0, 5));
|
||||
assert!(rec.missile_is_lit(&rec.tracks[0], 49.0, 5), "seek back restores the recorded state");
|
||||
let mut p = player(&rec);
|
||||
p.rate = 1.0;
|
||||
p.t = 50.0;
|
||||
let mut burnout = Out::default();
|
||||
p.engines(&rec, DVec3::ZERO, false, &mut burnout);
|
||||
assert!(burnout.engine_stops.contains(&0x8000_0000));
|
||||
let mut out = Out::default();
|
||||
p.heard = 18.0;
|
||||
p.t = 21.0;
|
||||
|
||||
@@ -113,6 +113,8 @@ pub struct Out {
|
||||
/// The engines sounding this tick (persistent sources, `crate::engines`), and whether the
|
||||
/// picture cut (a replay seek or a new shot): every engine then starts afresh.
|
||||
pub engines: Vec<EngineDecl>,
|
||||
/// Sources explicitly stopped this tick (for example, motor burnout).
|
||||
pub engine_stops: Vec<u32>,
|
||||
pub engine_cut: bool,
|
||||
pub commands: Vec<String>,
|
||||
/// Camera jolt (strength, seconds), the strongest this tick.
|
||||
@@ -2983,6 +2985,7 @@ impl Sim {
|
||||
let spec = m.kind.spec();
|
||||
let target = self.drones.iter().find(|d| d.id == m.target && d.alive());
|
||||
let lit = m.engine_lit();
|
||||
if !lit { out.engine_stops.push(0x4000_0000 | m.id); }
|
||||
let coast = m.age > spec.cold + m.kind.burn_ticks();
|
||||
if m.age == spec.cold + m.kind.burn_ticks() + 1 {
|
||||
burnouts.push((m.pos, m.id, m.kind));
|
||||
|
||||
@@ -1877,6 +1877,7 @@ fn motor_burnout_reduces_speed_and_stops_powered_effects() {
|
||||
assert!(powered <= last && sim.missiles[0].vel.length() < last);
|
||||
assert!(!sim.missiles[0].engine_lit());
|
||||
assert!(!out.engines.iter().any(|e| e.key == 0x4000_0000 | id));
|
||||
assert!(out.engine_stops.contains(&(0x4000_0000 | id)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user