Use catalog sound identifiers throughout runtime
build / mod (push) Failing after 19s

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
2026-11-04 15:59:12 +03:00
parent 1553976484
commit daa9ed7cbc
6 changed files with 83 additions and 60 deletions
+14 -12
View File
@@ -2,15 +2,17 @@
//! tail; from afar the muffled report and a late boom), and the camera's reaction (a brief
//! exposure and bloom punch). Everything is a function of the blast, its distance and its age, so
//! a replay hears and sees the same as the live raid did, from the replay's camera.
use crate::sim::catalog::sound;
/// The power (0 air burst .. 4 ballistic) of a blast sound event, if it is one.
pub fn blast_power(event: &str) -> Option<usize> {
Some(match event {
"airdef:blast.air" => 0,
"airdef:blast.medium" => 1,
"airdef:blast.big" => 2,
"airdef:blast.heavy" => 3,
"airdef:blast.huge" => 4,
sound::BLAST_AIR => 0,
sound::BLAST_MEDIUM => 1,
sound::BLAST_BIG => 2,
sound::BLAST_HEAVY => 3,
sound::BLAST_HUGE => 4,
_ => return None,
})
}
@@ -34,7 +36,7 @@ pub const FULL: usize = 3;
/// nearest of its tick.
pub fn layers(power: usize, dist: f64, far: f64, gain: f32, pitch: f32, rank: usize) -> Vec<Layer> {
let g = gain.clamp(0.3, 2.0);
let own = ["airdef:blast.air", "airdef:blast.medium", "airdef:blast.big", "airdef:blast.heavy", "airdef:blast.huge"][power.min(4)];
let own = [sound::BLAST_AIR, sound::BLAST_MEDIUM, sound::BLAST_BIG, sound::BLAST_HEAVY, sound::BLAST_HUGE][power.min(4)];
let layer = |name: &str, volume: f32, pitch: f32, delay: u64| Layer { name: name.to_string(), volume, pitch, delay };
let far_away = dist > far;
let suffix = if far_away { "_far" } else { "" };
@@ -45,22 +47,22 @@ pub fn layers(power: usize, dist: f64, far: f64, gain: f32, pitch: f32, rank: us
// The muffled report, then a deep boom that arrives late.
return vec![
layer(&format!("{own}_far"), g, pitch, 0),
layer("airdef:blast.big_far", 0.7 * g, pitch * 0.6, 10 + 2 * power as u64),
layer(sound::BLAST_BIG_FAR, 0.7 * g, pitch * 0.6, 10 + 2 * power as u64),
];
}
let tail = (0.45 + 0.08 * power as f32) * g;
if power == 0 {
// An air burst has no body of its own: the crack, the thump, the roll.
vec![
layer("airdef:blast.air", g, pitch * 1.12, 0),
layer("airdef:blast.big", 0.85 * g, pitch * 0.66, 2),
layer("airdef:blast.huge", tail, pitch * 0.5, 7),
layer(sound::BLAST_AIR, g, pitch * 1.12, 0),
layer(sound::BLAST_BIG, 0.85 * g, pitch * 0.66, 2),
layer(sound::BLAST_HUGE, tail, pitch * 0.5, 7),
]
} else {
vec![
layer(own, g, pitch, 0),
layer("airdef:blast.air", 0.7 * g, pitch * 1.15, 0),
layer("airdef:blast.huge", tail, pitch * 0.5, 7),
layer(sound::BLAST_AIR, 0.7 * g, pitch * 1.15, 0),
layer(sound::BLAST_HUGE, tail, pitch * 0.5, 7),
]
}
}
+8 -6
View File
@@ -9,6 +9,8 @@
//! for a tick or two (a step the simulation skipped) does not stop the sound; a *cut* (a replay
//! seek, a new shot) stops every engine and starts them again under new ids, so nothing glides
//! across the jump.
use crate::sim::catalog::sound;
use glam::DVec3;
use std::collections::{BTreeMap, BTreeSet};
@@ -52,12 +54,12 @@ pub enum Op {
/// engine_sounds_has_no_hole` finds them (after the fade-in, before the sound begins to die).
pub fn steady_part(event: &str) -> (f32, f32) {
match event {
"airdef:drone.shahed" => (0.2, 2.2),
"airdef:drone.geran3" => (0.2, 2.0),
"airdef:drone.lancet" => (0.2, 1.4),
"airdef:drone.cruise" => (0.2, 2.6),
"airdef:drone.rocket" => (0.2, 2.6),
"airdef:missile.flight" => (0.2, 2.2),
sound::DRONE_SHAHED => (0.2, 2.2),
sound::DRONE_GERAN3 => (0.2, 2.0),
sound::DRONE_LANCET => (0.2, 1.4),
sound::DRONE_CRUISE => (0.2, 2.6),
sound::DRONE_ROCKET => (0.2, 2.6),
sound::MISSILE_FLIGHT => (0.2, 2.2),
_ => (0.0, 0.0),
}
}
+8 -6
View File
@@ -4,6 +4,8 @@
//! world and a director's camera (a full-screen camera view) follows them —
//! behind the drone, then behind the interceptor or from the side — slowing
//! time round the moment itself.
use crate::sim::catalog::sound;
use crate::film::{self, Film};
use crate::panel::distance;
use crate::sim::Ground;
@@ -526,11 +528,11 @@ impl Player {
for e in rec.events.iter().filter(|e| (self.heard..self.t).contains(&(e.at as f64))) {
let event = match e.kind {
EventKind::Kill { kind, outcome, .. } => crate::sim::outcome::look(kind, outcome).3,
EventKind::Hit { power, .. } | EventKind::Crash { power, .. } => ["airdef:blast.air", "airdef:blast.medium", "airdef:blast.big", "airdef:blast.heavy", "airdef:blast.huge"][(power as usize).min(4)],
EventKind::SamLost { .. } => "airdef:blast.big",
EventKind::Split { .. } => "airdef:boom.sonic",
EventKind::Hit { power, .. } | EventKind::Crash { power, .. } => [sound::BLAST_AIR, sound::BLAST_MEDIUM, sound::BLAST_BIG, sound::BLAST_HEAVY, sound::BLAST_HUGE][(power as usize).min(4)],
EventKind::SamLost { .. } => sound::BLAST_BIG,
EventKind::Split { .. } => sound::BOOM_SONIC,
EventKind::Strike { result: StrikeResult::Halted, .. } => continue,
EventKind::Strike { result, .. } => ["airdef:blast.air", "airdef:blast.medium", "airdef:blast.big", "airdef:blast.heavy", "airdef:blast.huge"][match result {
EventKind::Strike { result, .. } => [sound::BLAST_AIR, sound::BLAST_MEDIUM, sound::BLAST_BIG, sound::BLAST_HEAVY, sound::BLAST_HUGE][match result {
StrikeResult::Destroyed => 4,
StrikeResult::Intercepted => 0,
_ => 3,
@@ -539,7 +541,7 @@ impl Player {
let gain = if let EventKind::Kill { kind, outcome, .. } = e.kind { crate::sim::outcome::look(kind, outcome).2 } else { 1.0 };
out.sound(event, heard(e.pos), gain, 1.0);
if let EventKind::Kill { outcome: crate::sim::outcome::Outcome::AirDetonation, .. } = e.kind {
out.sound("airdef:boom.sonic", heard(e.pos), 0.8, 1.2);
out.sound(sound::BOOM_SONIC, heard(e.pos), 0.8, 1.2);
}
}
// New recordings carry the exact launch and ignition sounds and their actual positions.
@@ -570,7 +572,7 @@ impl Player {
for tr in rec.tracks.iter().filter(|tr| matches!(tr.what, What::StrikeCarrier(_))) {
let at = tr.first as f64 + crate::sim::strike::EJECT_TICKS as f64;
if (self.heard..self.t).contains(&at) && let Some(p) = tr.at(rec.origin, at) {
out.sound("airdef:launch.s400", heard(p), 1.0, 1.0);
out.sound(sound::LAUNCH_S400, heard(p), 1.0, 1.0);
}
}
self.heard = self.t;
+14
View File
@@ -7,16 +7,30 @@ pub const DRONE_ROCKET: &str = "airdef:drone.rocket";
pub const MISSILE_FLIGHT: &str = "airdef:missile.flight";
pub const DRONE_BALLISTIC: &str = "airdef:drone.ballistic";
pub const BOOM_SONIC: &str = "airdef:boom.sonic";
pub const BOOM_SONIC_FAR: &str = "airdef:boom.sonic_far";
pub const LAUNCH_PATRIOT: &str = "airdef:launch.patriot";
pub const LAUNCH_PATRIOT_FAR: &str = "airdef:launch.patriot_far";
pub const LAUNCH_S400_EJECT: &str = "airdef:launch.s400_eject";
pub const LAUNCH_S400_EJECT_FAR: &str = "airdef:launch.s400_eject_far";
pub const LAUNCH_S400: &str = "airdef:launch.s400";
pub const LAUNCH_S400_FAR: &str = "airdef:launch.s400_far";
pub const LAUNCH_S500_EJECT: &str = "airdef:launch.s500_eject";
pub const LAUNCH_S500_EJECT_FAR: &str = "airdef:launch.s500_eject_far";
pub const LAUNCH_S500: &str = "airdef:launch.s500";
pub const LAUNCH_S500_FAR: &str = "airdef:launch.s500_far";
pub const LAUNCH_PANTSIR: &str = "airdef:launch.pantsir";
pub const LAUNCH_PANTSIR_FAR: &str = "airdef:launch.pantsir_far";
pub const GUN_GEPARD: &str = "airdef:gun.gepard";
pub const GUN_GEPARD_FAR: &str = "airdef:gun.gepard_far";
pub const GUN_PANTSIR: &str = "airdef:gun.pantsir";
pub const GUN_PANTSIR_FAR: &str = "airdef:gun.pantsir_far";
pub const BLAST_BIG: &str = "airdef:blast.big";
pub const BLAST_BIG_FAR: &str = "airdef:blast.big_far";
pub const BLAST_HEAVY: &str = "airdef:blast.heavy";
pub const BLAST_HEAVY_FAR: &str = "airdef:blast.heavy_far";
pub const BLAST_HUGE: &str = "airdef:blast.huge";
pub const BLAST_HUGE_FAR: &str = "airdef:blast.huge_far";
pub const BLAST_MEDIUM: &str = "airdef:blast.medium";
pub const BLAST_MEDIUM_FAR: &str = "airdef:blast.medium_far";
pub const BLAST_AIR: &str = "airdef:blast.air";
pub const BLAST_AIR_FAR: &str = "airdef:blast.air_far";
+37 -36
View File
@@ -12,6 +12,7 @@ use std::collections::HashMap;
pub mod battery;
pub mod launch;
pub mod catalog;
use catalog::sound;
mod generated_bounds;
mod damage;
pub mod doctrine;
@@ -680,11 +681,11 @@ impl DroneKind {
/// The sound of a launch from the ground: a light catapult shot, a rocket booster, a heavy ballistic launch.
pub fn launch_sound(self) -> &'static str {
match self {
DroneKind::Shahed | DroneKind::Geran3 | DroneKind::Lancet | DroneKind::Gerbera => "airdef:launch.s400_eject",
DroneKind::Kh101 | DroneKind::Kalibr | DroneKind::Kh22 => "airdef:launch.patriot",
DroneKind::Zircon => "airdef:launch.s400",
DroneKind::Shahed | DroneKind::Geran3 | DroneKind::Lancet | DroneKind::Gerbera => sound::LAUNCH_S400_EJECT,
DroneKind::Kh101 | DroneKind::Kalibr | DroneKind::Kh22 => sound::LAUNCH_PATRIOT,
DroneKind::Zircon => sound::LAUNCH_S400,
DroneKind::Iskander | DroneKind::Kinzhal | DroneKind::Oreshnik | DroneKind::OreshnikBlock | DroneKind::OreshnikSub => {
"airdef:launch.s500"
sound::LAUNCH_S500
}
}
}
@@ -704,7 +705,7 @@ impl DroneKind {
engine: Engine::Prop(1.34, 1.0),
decoy: false,
stealth: 1.0,
sound: ("airdef:drone.shahed", 1.0, 48),
sound: (sound::DRONE_SHAHED, 1.0, 48),
class: Class::Drone,
ballistic: None,
},
@@ -722,7 +723,7 @@ impl DroneKind {
engine: Engine::Jet(0.34, 1.55, 1.0),
decoy: false,
stealth: 1.1,
sound: ("airdef:drone.geran3", 1.0, 40),
sound: (sound::DRONE_GERAN3, 1.0, 40),
class: Class::Jet,
ballistic: None,
},
@@ -740,7 +741,7 @@ impl DroneKind {
engine: Engine::Prop(0.93, 0.55),
decoy: false,
stealth: 1.4,
sound: ("airdef:drone.lancet", 1.0, 32),
sound: (sound::DRONE_LANCET, 1.0, 32),
class: Class::Drone,
ballistic: None,
},
@@ -758,7 +759,7 @@ impl DroneKind {
engine: Engine::Prop(1.03, 0.9),
decoy: true,
stealth: 0.9,
sound: ("airdef:drone.shahed", 1.3, 40),
sound: (sound::DRONE_SHAHED, 1.3, 40),
class: Class::Drone,
ballistic: None,
},
@@ -776,7 +777,7 @@ impl DroneKind {
engine: Engine::Jet(-0.27, 1.4, 0.6),
decoy: false,
stealth: 1.5,
sound: ("airdef:drone.cruise", 1.0, 50),
sound: (sound::DRONE_CRUISE, 1.0, 50),
class: Class::Cruise,
ballistic: None,
},
@@ -794,7 +795,7 @@ impl DroneKind {
engine: Engine::Jet(-0.2, 1.5, 0.6),
decoy: false,
stealth: 1.3,
sound: ("airdef:drone.cruise", 1.2, 50),
sound: (sound::DRONE_CRUISE, 1.2, 50),
class: Class::Cruise,
ballistic: None,
},
@@ -812,7 +813,7 @@ impl DroneKind {
engine: Engine::Rocket(1.5, 1.3),
decoy: false,
stealth: 1.4,
sound: ("airdef:drone.rocket", 0.8, 46),
sound: (sound::DRONE_ROCKET, 0.8, 46),
class: Class::Supersonic,
ballistic: None,
},
@@ -830,7 +831,7 @@ impl DroneKind {
engine: Engine::Rocket(1.5, 1.1),
decoy: false,
stealth: 2.0,
sound: ("airdef:drone.rocket", 1.35, 46),
sound: (sound::DRONE_ROCKET, 1.35, 46),
class: Class::Supersonic,
ballistic: None,
},
@@ -848,7 +849,7 @@ impl DroneKind {
engine: Engine::Rocket(1.5, 1.6),
decoy: false,
stealth: 1.6,
sound: ("airdef:drone.rocket", 0.7, 46),
sound: (sound::DRONE_ROCKET, 0.7, 46),
class: Class::Ballistic,
ballistic: Some(1000.0),
},
@@ -866,7 +867,7 @@ impl DroneKind {
engine: Engine::Rocket(1.5, 1.5),
decoy: false,
stealth: 2.2,
sound: ("airdef:drone.rocket", 0.6, 46),
sound: (sound::DRONE_ROCKET, 0.6, 46),
class: Class::Ballistic,
ballistic: Some(1500.0),
},
@@ -884,7 +885,7 @@ impl DroneKind {
engine: Engine::Rocket(1.5, 2.4),
decoy: false,
stealth: 3.0,
sound: ("airdef:drone.rocket", 0.5, 46),
sound: (sound::DRONE_ROCKET, 0.5, 46),
class: Class::Ballistic,
ballistic: Some(2600.0),
},
@@ -902,7 +903,7 @@ impl DroneKind {
engine: Engine::Rocket(1.0, 1.0),
decoy: false,
stealth: 3.5,
sound: ("airdef:drone.ballistic", 0.8, 9999),
sound: (sound::DRONE_BALLISTIC, 0.8, 9999),
class: Class::Ballistic,
ballistic: None,
},
@@ -920,7 +921,7 @@ impl DroneKind {
engine: Engine::Rocket(1.0, 1.0),
decoy: false,
stealth: 4.0,
sound: ("airdef:drone.ballistic", 1.0, 9999),
sound: (sound::DRONE_BALLISTIC, 1.0, 9999),
class: Class::Ballistic,
ballistic: None,
},
@@ -1106,13 +1107,13 @@ impl MissileKind {
/// The sound of its launch.
pub fn launch_sound(self) -> &'static str {
match self {
MissileKind::Pac3 => "airdef:launch.patriot",
MissileKind::M57e6 => "airdef:launch.pantsir",
MissileKind::M48n6 => "airdef:launch.s400_eject",
MissileKind::M77n6 => "airdef:launch.s500_eject",
MissileKind::IrisT | MissileKind::M9m338 => "airdef:launch.s400_eject",
MissileKind::Amraam | MissileKind::M9m317 => "airdef:launch.patriot",
MissileKind::P1sun => "airdef:drone.lancet",
MissileKind::Pac3 => sound::LAUNCH_PATRIOT,
MissileKind::M57e6 => sound::LAUNCH_PANTSIR,
MissileKind::M48n6 => sound::LAUNCH_S400_EJECT,
MissileKind::M77n6 => sound::LAUNCH_S500_EJECT,
MissileKind::IrisT | MissileKind::M9m338 => sound::LAUNCH_S400_EJECT,
MissileKind::Amraam | MissileKind::M9m317 => sound::LAUNCH_PATRIOT,
MissileKind::P1sun => sound::DRONE_LANCET,
}
}
fn spec(self) -> &'static MissileSpec {
@@ -2075,13 +2076,13 @@ impl Sim {
let v = (aim - d.pos).normalize_or(DVec3::NEG_Y) * 3.0;
released.push((piece, d.pos + v.normalize_or_zero() * (i as f64 * 0.3), v, aim, d.sector));
}
out.sound("airdef:boom.sonic", d.pos, 1.0, 0.7);
out.sound(sound::BOOM_SONIC, d.pos, 1.0, 0.7);
splits.push((d.id, d.pos));
// Its pieces count as launched instead of it.
self.launched += 5;
out.particles("minecraft:firework", d.pos, 1.5, DVec3::ZERO, 0.3, 40);
if d.kind == DroneKind::OreshnikBlock && self.rng.chance(0.5) {
out.sound("airdef:drone.ballistic", d.pos, 1.0, 1.1);
out.sound(sound::DRONE_BALLISTIC, d.pos, 1.0, 1.1);
}
removed.push(d.id);
continue;
@@ -2106,8 +2107,8 @@ impl Sim {
if d.vel.y < -1.0 && d.spin == 0.0 && d.pos.distance(d.target) < d.vel.length() * 80.0 {
d.spin = 1.0;
let p = if d.kind == DroneKind::Kinzhal { 0.85 } else { 1.0 };
out.sound("airdef:drone.ballistic", d.pos, 1.0, p);
out.sound("airdef:boom.sonic", d.pos, 1.0, p);
out.sound(sound::DRONE_BALLISTIC, d.pos, 1.0, p);
out.sound(sound::BOOM_SONIC, d.pos, 1.0, p);
}
continue;
}
@@ -2401,7 +2402,7 @@ impl Sim {
out.particles("minecraft:lava", pos, 1.0, DVec3::ZERO, 0.0, 16);
out.particles("minecraft:campfire_signal_smoke", pos, 1.2, DVec3::new(0.0, 0.04, 0.0), 0.01, if big { 30 } else { 10 });
let pitch = 0.9 + self.vis.f() as f32 * 0.2;
let event = ["airdef:blast.air", "airdef:blast.medium", "airdef:blast.big", "airdef:blast.heavy", "airdef:blast.huge"][power];
let event = [sound::BLAST_AIR, sound::BLAST_MEDIUM, sound::BLAST_BIG, sound::BLAST_HEAVY, sound::BLAST_HUGE][power];
out.sound(event, pos, 1.0, if small { pitch * 1.15 } else { pitch });
if power >= 3 {
// A heavy warhead throws a column of dirt and a ring of dust.
@@ -2518,7 +2519,7 @@ impl Sim {
out.particles("minecraft:large_smoke", pos, size * 0.3, DVec3::new(0.0, 0.05, 0.0), 0.07, if self.shader { 20 } else { 40 });
out.particles("minecraft:firework", pos, 0.6, DVec3::ZERO, 0.5, 50);
// The shock arrives after the flash (`boom` sounds travel); the warhead's blast hurts what is near.
out.sound("airdef:boom.sonic", pos, 0.8, 1.2);
out.sound(sound::BOOM_SONIC, pos, 0.8, 1.2);
self.blast_damage(pos, 0, ground, out);
}
}
@@ -2828,7 +2829,7 @@ impl Sim {
}
// A gun sound holds a few shots: one every other burst.
if sam.bursts % 2 == 0 {
let event = if sam.kind == SamKind::Pantsir { "airdef:gun.pantsir" } else { "airdef:gun.gepard" };
let event = if sam.kind == SamKind::Pantsir { sound::GUN_PANTSIR } else { sound::GUN_GEPARD };
out.sound(event, mount, 1.0, 0.95 + self.vis.f() as f32 * 0.1);
}
// A fresh working own-radar fire-control track does not depend on seeing the target.
@@ -2937,7 +2938,7 @@ impl Sim {
if spec.cold > 0 {
// Thrown out by gas: a puff, a thump; the motor lights later.
out.particles("minecraft:cloud", muzzle, 0.6, dir * 0.05, 0.05, 30);
let event = if kind == MissileKind::M77n6 { "airdef:launch.s500_eject" } else { "airdef:launch.s400_eject" };
let event = if kind == MissileKind::M77n6 { sound::LAUNCH_S500_EJECT } else { sound::LAUNCH_S400_EJECT };
out.sound(event, muzzle, 1.0, 1.0);
self.scene_event(muzzle, record::SceneKind::MissileLaunch { missile: id, sam, target, kind, sound: event, pitch: 1.0 });
} else {
@@ -2946,7 +2947,7 @@ impl Sim {
out.particles("minecraft:cloud", muzzle - dir * 2.0, 1.4, DVec3::new(0.0, 0.02, 0.0), 0.06, 70);
out.particles("minecraft:campfire_cosy_smoke", muzzle - dir * 3.0, 1.2, DVec3::ZERO, 0.02, 20);
}
let event = if kind == MissileKind::M57e6 { "airdef:launch.pantsir" } else { "airdef:launch.patriot" };
let event = if kind == MissileKind::M57e6 { sound::LAUNCH_PANTSIR } else { sound::LAUNCH_PATRIOT };
let pitch = 0.95 + self.vis.f() as f32 * 0.1;
out.sound(event, muzzle, 1.0, pitch);
self.scene_event(muzzle, record::SceneKind::MissileLaunch { missile: id, sam, target, kind, sound: event, pitch });
@@ -3142,7 +3143,7 @@ impl Sim {
continue;
}
if lit && m.age == spec.cold + 1 {
let sound = (spec.cold > 0 && spec.flame > 0.0).then_some(if m.kind == MissileKind::M77n6 { "airdef:launch.s500" } else { "airdef:launch.s400" });
let sound = (spec.cold > 0 && spec.flame > 0.0).then_some(if m.kind == MissileKind::M77n6 { sound::LAUNCH_S500 } else { sound::LAUNCH_S400 });
ignitions.push((m.pos, m.id, m.kind, sound));
}
if lit {
@@ -3157,7 +3158,7 @@ impl Sim {
out.particles("minecraft:cloud", back - new * 0.5, 0.12 * spec.scale as f32, DVec3::ZERO, 0.005, if m.age < 30 { 3 } else { 1 });
}
if m.age == spec.cold + 1 && spec.cold > 0 {
let event = if m.kind == MissileKind::M77n6 { "airdef:launch.s500" } else { "airdef:launch.s400" };
let event = if m.kind == MissileKind::M77n6 { sound::LAUNCH_S500 } else { sound::LAUNCH_S400 };
out.sound(event, m.pos, 1.0, 1.0);
out.particles("minecraft:cloud", m.pos, 1.0, DVec3::ZERO, 0.08, 40);
}
@@ -3187,7 +3188,7 @@ impl Sim {
let m = self.missiles.swap_remove(i);
self.fireball(m.pos, 2.5 * m.kind.spec().scale, 7);
out.particles("minecraft:explosion", m.pos, 0.4, DVec3::ZERO, 0.0, 3);
out.sound("airdef:blast.air", m.pos, 1.0, 1.25);
out.sound(sound::BLAST_AIR, m.pos, 1.0, 1.25);
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 {
+2
View File
@@ -35,6 +35,8 @@ def generate_sound_ids(entries):
event = entry["event"]
name = event.replace(".", "_").upper()
lines.append(f'pub const {name}: &str = "airdef:{event}";')
if entry["far"]:
lines.append(f'pub const {name}_FAR: &str = "airdef:{event}_far";')
return "\n".join(lines + [""])