diff --git a/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs b/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs index a7aa608..4ca6853 100644 --- a/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs +++ b/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs @@ -438,7 +438,7 @@ namespace CounterDrone.Core.Algorithms TargetX = tx, TargetY = mid.Y, TargetZ = tz, - MuzzleVelocity = unit.MuzzleVelocity, + MuzzleVelocity = unit.Type == PlatformType.AirBased ? 0f : unit.MuzzleVelocity, }); return events; diff --git a/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs b/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs index 19b7659..26ee9c2 100644 --- a/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs +++ b/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs @@ -234,7 +234,7 @@ namespace CounterDrone.Core.Tests } // ═══════════════════════════════════════ - // 空基 + // 空基弹道 // ═══════════════════════════════════════ [Fact] @@ -251,6 +251,68 @@ namespace CounterDrone.Core.Tests Assert.True(result.Best.ThreatsEngaged > 0); } + [Fact] + public void AirBased_FireTime_EarlierThanArrival() + { + // 空基需要飞行+下落时间,发射应远早于到达时间 + var units = new List + { + new FireUnit { Id = "u0", Type = PlatformType.AirBased, + Position = new Vector3(3000, 2500, -2000), GunCount = 4, ChannelsPerGun = 4, + CruiseSpeed = 55, ReleaseAltitude = 1500, TotalMunitions = 16, + AmmoTypes = new() { AerosolType.InertGas } }, + }; + var threat = MakeThreat(PowerType.Piston, 120); + var result = new DefaultDefensePlanner(TestAmmo).Plan(units, new() { threat }, new CombatScene()); + float arrivalTime = threat.GetArrivalTime(); + // 空基至少需要 (飞行距离/55 + 下落时间) 的提前量 + float minAdvance = 20f; // 至少提前 20 秒 + Assert.All(result.Best.MergedSchedule, fe => + Assert.True(fe.FireTime < arrivalTime - minAdvance, + $"空基FireTime={fe.FireTime:F1}, arrival={arrivalTime:F1}, 应至少提前{minAdvance}s")); + } + + [Fact] + public void AirBased_FireTime_EarlierThanGroundBased() + { + // 同样威胁,空基应比地基更早发射(需要飞行时间) + var threat = MakeThreat(PowerType.Piston, 120); + var airUnits = new List + { + new FireUnit { Id = "u0", Type = PlatformType.AirBased, + Position = new Vector3(3000, 2500, -2000), GunCount = 4, ChannelsPerGun = 4, + CruiseSpeed = 55, ReleaseAltitude = 1500, TotalMunitions = 16, + AmmoTypes = new() { AerosolType.InertGas } }, + }; + var groundUnits = MakeGroundUnits(1); + + var airResult = new DefaultDefensePlanner(TestAmmo).Plan(airUnits, new() { threat }, new CombatScene()); + var groundResult = new DefaultDefensePlanner(TestAmmo).Plan(groundUnits, new() { threat }, new CombatScene()); + + float airFirst = airResult.Best.MergedSchedule.Min(e => e.FireTime); + float groundFirst = groundResult.Best.MergedSchedule.Min(e => e.FireTime); + Assert.True(airFirst < groundFirst, + $"空基首发射={airFirst:F1}s, 地基首发射={groundFirst:F1}s, 空基应更早"); + } + + [Fact] + public void AirBased_NoMuzzleVelocityInFireEvent() + { + // 空基 FireEvent 的 MuzzleVelocity 应为 0(不适用) + var units = new List + { + new FireUnit { Id = "u0", Type = PlatformType.AirBased, + Position = new Vector3(3000, 2500, -2000), GunCount = 4, ChannelsPerGun = 4, + CruiseSpeed = 55, ReleaseAltitude = 1500, TotalMunitions = 16, + AmmoTypes = new() { AerosolType.InertGas } }, + }; + var threat = MakeThreat(PowerType.Piston, 120); + var result = new DefaultDefensePlanner(TestAmmo).Plan(units, new() { threat }, new CombatScene()); + Assert.All(result.Best.MergedSchedule, fe => + Assert.True(fe.MuzzleVelocity < 10f, + $"空基 MuzzleVelocity={fe.MuzzleVelocity}, 应≈0")); + } + // ═══════════════════════════════════════ // 集成验证 // ═══════════════════════════════════════