fix: 统一选择远点命中 + FlightDuration 传递,喷气式通过

PlanUnitLane: ParabolicMotion.GetFlightTimes 选远点(离无人机近)
FireEvent: 新增 FlightDuration 字段
MunitionEntity: 接收 flightDuration → _useTimeArrival,按时间到达
地基: FlightDuration = interceptShellTime(保持 InterceptCalculator 一致性)
空基: FlightDuration = ParabolicMotion 选出的远点 tof

238 全部通过
This commit is contained in:
tian 2026-06-18 10:04:30 +08:00
parent b4f9a4055c
commit 92d1131dc1
5 changed files with 30 additions and 12 deletions

View File

@ -108,6 +108,7 @@ namespace CounterDrone.Core.Algorithms
public float TargetX, TargetY, TargetZ;
public float MuzzleVelocity;
public float LaunchAngle;
public float FlightDuration;
}
// ═══════════════════════════════════════════════

View File

@ -547,10 +547,15 @@ namespace CounterDrone.Core.Algorithms
if (unit.Type == PlatformType.AirBased && heightDiff >= 0)
return (events, $"空基单元 {unit.Id}: 平台高度({unit.Position.Y:F0})≤目标高度({ty:F0}),无法重力下落");
// 每发独立计算发射角(基于云团生成点,含风偏),验证弹道可达
// 每发独立计算发射角,选离无人机更近的命中点(更早拦截)
launchAngle = Kinematics.CalculateLaunchAngle(targetDist, mv, heightDiff);
try { Kinematics.ComputeParabolicRange(mv, launchAngle, heightDiff); }
catch (ArgumentException) { return (events, $"目标超出弹道射程: dist={targetDist:F0}m"); }
var motion = new ParabolicMotion(mv, launchAngle);
var times = motion.GetFlightTimes(heightDiff);
if (times.Length == 0)
return (events, $"目标超出弹道射程: dist={targetDist:F0}m");
// 两个解时:远点更靠近无人机(较早拦截),优先选远点
float tof = times.Length == 2 ? times[1] : times[0];
float range = mv * (float)Math.Cos(launchAngle) * tof;
// 无人机到达穿越点的时间:基于探测边界,而非航路起点。
// planner 是参谋,只能基于探测信息规划。威胁从探测边界被发现,
@ -565,11 +570,14 @@ namespace CounterDrone.Core.Algorithms
float deliveryTime;
if (unit.Type == PlatformType.AirBased)
deliveryTime = Kinematics.ComputeParabolicRange(mv, launchAngle, heightDiff).timeOfFlight;
deliveryTime = tof;
else if (interceptShellTime <= 0)
return (events, "拦截计算未得出有效飞行时间");
else
{
deliveryTime = interceptShellTime;
tof = interceptShellTime;
}
float fireTime = recommendedTiming - deliveryTime;
if (fireTime <= 0f)
@ -584,6 +592,7 @@ namespace CounterDrone.Core.Algorithms
TargetZ = cloudGenZ,
MuzzleVelocity = mv,
LaunchAngle = launchAngle,
FlightDuration = tof,
});
return (events, null);

View File

@ -54,6 +54,7 @@ namespace CounterDrone.Core.Simulation
private bool _hasExceededReleaseAltitude;
// 发射点高于释放高度时,炮弹下降到达
private bool _arrivesDescending;
private bool _useTimeArrival;
public MunitionEntity(string id, PlatformType launchMode, AerosolType aerosolType,
float startX, float startY, float startZ,
@ -61,6 +62,7 @@ namespace CounterDrone.Core.Simulation
float muzzleVelocity, float releaseAltitude,
float launchTime,
float? launchAngle = null,
float flightDuration = 0,
float carrierVelX = 0f, float carrierVelY = 0f, float carrierVelZ = 0f)
{
Id = id;
@ -89,10 +91,15 @@ namespace CounterDrone.Core.Simulation
float heightDiff = releaseAltitude - startY;
LaunchAngle = launchAngle.Value;
var (_, tof) = Kinematics.ComputeParabolicRange(muzzleVelocity, LaunchAngle, heightDiff);
FlightDuration = tof;
_arrivesDescending = heightDiff < 0;
// 发射点高于释放高度 → 下降到达;否则上升到达
if (flightDuration > 0)
{
FlightDuration = flightDuration;
_useTimeArrival = true; // planner 指定了时间,按时间到达
}
else
{
(_, FlightDuration) = Kinematics.ComputeParabolicRange(muzzleVelocity, LaunchAngle, heightDiff);
}
_arrivesDescending = heightDiff < 0;
}
else
@ -112,7 +119,8 @@ namespace CounterDrone.Core.Simulation
_startX, _startY, _startZ, LaunchAngle, Azimuth, MuzzleVelocity, _time);
PosX = x; PosY = y; PosZ = z;
if (((y >= ReleaseAltitude && !_arrivesDescending) || (y <= ReleaseAltitude && _arrivesDescending)) && !_hasExceededReleaseAltitude)
if (_useTimeArrival ? _time >= FlightDuration :
((y >= ReleaseAltitude && !_arrivesDescending) || (y <= ReleaseAltitude && _arrivesDescending)))
{
_hasExceededReleaseAltitude = true;
float tPrev = _time - deltaTime;

View File

@ -220,7 +220,7 @@ namespace CounterDrone.Core.Simulation
platform.AerosolType ?? Models.AerosolType.InertGas,
platform.PosX, platform.PosY, platform.PosZ,
fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight,
launchTime, fe.LaunchAngle);
launchTime, fe.LaunchAngle, fe.FlightDuration);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
_frameEventPool.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = launchTime, SourceId = platform.Id, Description = $"空基发射 {m.Id} @({platform.PosX:F0},{platform.PosY:F0},{platform.PosZ:F0})→({fe.TargetX:F0},{fe.TargetY:F0},{fe.TargetZ:F0})" });
@ -233,7 +233,7 @@ namespace CounterDrone.Core.Simulation
platform.AerosolType ?? Models.AerosolType.InertGas,
platform.PosX, platform.PosY, platform.PosZ,
fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight,
launchTime, fe.LaunchAngle);
launchTime, fe.LaunchAngle, fe.FlightDuration);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
_frameEventPool.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = launchTime, SourceId = platform.Id, Description = $"计划发射 {m.Id} @({platform.PosX:F0},{platform.PosY:F0},{platform.PosZ:F0})→({fe.TargetX:F0},{fe.TargetY:F0},{fe.TargetZ:F0})" });

View File

@ -131,7 +131,7 @@ namespace CounterDrone.Core.Tests
// 场景 4喷气发动机 → 活性材料拦截
// ═══════════════════════════════════════════════
[Fact(Skip = "高弹道-上升段触发问题")]
[Fact]
public void Scenario_Jet_ActiveMaterialIntercept()
{
var eng = RunPreset("喷气式拦截-活性材料");