Keep radar-controlled gun accuracy independent of darkness

This commit is contained in:
2026-11-02 00:45:14 +03:00
parent 5860c5009f
commit 733dc07863
4 changed files with 42 additions and 5 deletions
+2 -1
View File
@@ -97,7 +97,8 @@ CI: `.gitea/workflows/build.yml` на каждый пуш собирает то
перенацеливает ещё не взлетевшие Ланцеты и Шахеды на них; если цель-комплекс уничтожен, дроны идут к
следующему известному. Дроны под обстрелом прижимаются к земле.
**Ночь.** Ночью пушки попадают втрое хуже, если цель не освещена. Освещают **прожекторы** (луч ведёт ближайшую
**Ночь.** Исправные пушки с собственным свежим радарным сопровождением не теряют точность из-за темноты.
Цели освещают **прожекторы** (луч ведёт ближайшую
цель из сети, приоритет — цели пушек) и **осветительные ракеты**: пушечные комплексы пускают их сами (раз в
25 с на цель в темноте), игрок — **осветительным пистолетом** или `/airdef flare`. Ракета висит ~30 с и
освещает всё вокруг на ~60–90 м.
+7 -2
View File
@@ -1,5 +1,5 @@
//! Night fighting: guns aim by sight, and at night they hit far less unless
//! the target is lit. Searchlights follow the network's tracks with their
//! Night fighting: darkness penalizes aiming without fresh radar fire control,
//! unless the target is lit. Searchlights follow the network's tracks with their
//! beams; illumination flares (fired by the guns on their own, or by the
//! player) hang over the sky for half a minute and light everything round.
use super::*;
@@ -24,6 +24,11 @@ pub const DARK_HITS: f64 = 0.4;
/// Ticks between a gun's own flares.
const FLARE_EVERY: u32 = 500;
/// Night only reduces a gun's hit chance when it has to aim by sight.
pub(super) fn hit_factor(night: bool, lit: bool, radar_fire_control: bool) -> f64 {
if night && !lit && !radar_fire_control { DARK_HITS } else { 1.0 }
}
pub struct Flare {
id: u32,
pub pos: DVec3,
+5 -2
View File
@@ -2728,8 +2728,11 @@ impl Sim {
let event = if sam.kind == SamKind::Pantsir { "airdef:gun.pantsir" } else { "airdef:gun.gepard" };
out.sound(event, mount, 1.0, 0.95 + self.vis.f() as f32 * 0.1);
}
// At night a gun sees what is lit (a searchlight, a flare) and little else.
let dark = if self.night && !self.lit.contains(&t.id) { lights::DARK_HITS } else { 1.0 };
// A fresh working own-radar fire-control track does not depend on seeing the target.
// Guns without that radar picture still take the darkness penalty unless illuminated.
let radar_fire_control = sam.radar_up()
&& sam.track(gt).is_some_and(|tr| tr.own_within(now, sight::GUN_AGE));
let dark = lights::hit_factor(self.night, self.lit.contains(&t.id), radar_fire_control);
// The aim point is the track's forecast: the farther it is from the truth, the more shells miss.
let off = est.distance(t.pos);
let p = gun.p * sam.gun_fit() * dark / (t.kind.spec().stealth * (1.0 + (off / 8.0).powi(2))) * (1.0 - dist / (gun.range * 1.3)).max(0.35);
+28
View File
@@ -181,6 +181,34 @@ fn a_close_blast_can_leave_a_firing_but_immobile_system() {
assert!(found, "no seed left it firing but immobile");
}
#[test]
fn a_fresh_own_radar_track_keeps_gun_hits_equal_day_and_night() {
fn firing(seed: u64, night: bool) -> (u32, u32) {
let mut sim = Sim::new(seed);
let (mut ground, mut out) = flat();
let gun = radar_at(&mut sim, SamKind::Gepard, 0.0);
sim.night = night;
sim.add_drone(
DroneKind::Shahed,
DVec3::new(0.0, 95.0, 35.0),
DVec3::ZERO,
DVec3::new(5000.0, 95.0, 35.0),
);
let starting_bursts = sim.sams[gun].bursts;
for _ in 0..240 {
sim.step(&mut ground, None, &mut out);
}
(starting_bursts - sim.sams[gun].bursts, sim.sams[gun].kills)
}
for seed in 0..32 {
let day = firing(seed, false);
let night = firing(seed, true);
assert!(day.0 > 0, "seed {seed} did not fire");
assert_eq!(night, day, "seed {seed}: radar gun changed its fire or kills at night");
}
}
#[test]
fn failed_parts_block_what_they_serve_and_repair_restores_them() {
let mut sim = Sim::new(2);