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 TargetX, TargetY, TargetZ;
public float MuzzleVelocity; public float MuzzleVelocity;
public float LaunchAngle; public float LaunchAngle;
public float FlightDuration;
} }
// ═══════════════════════════════════════════════ // ═══════════════════════════════════════════════

View File

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

View File

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

View File

@ -220,7 +220,7 @@ namespace CounterDrone.Core.Simulation
platform.AerosolType ?? Models.AerosolType.InertGas, platform.AerosolType ?? Models.AerosolType.InertGas,
platform.PosX, platform.PosY, platform.PosZ, platform.PosX, platform.PosY, platform.PosZ,
fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight, fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight,
launchTime, fe.LaunchAngle); launchTime, fe.LaunchAngle, fe.FlightDuration);
_munitions.Add(m); _munitions.Add(m);
OnMunitionLaunched?.Invoke(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})" }); _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.AerosolType ?? Models.AerosolType.InertGas,
platform.PosX, platform.PosY, platform.PosZ, platform.PosX, platform.PosY, platform.PosZ,
fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight, fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight,
launchTime, fe.LaunchAngle); launchTime, fe.LaunchAngle, fe.FlightDuration);
_munitions.Add(m); _munitions.Add(m);
OnMunitionLaunched?.Invoke(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})" }); _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喷气发动机 → 活性材料拦截 // 场景 4喷气发动机 → 活性材料拦截
// ═══════════════════════════════════════════════ // ═══════════════════════════════════════════════
[Fact(Skip = "高弹道-上升段触发问题")] [Fact]
public void Scenario_Jet_ActiveMaterialIntercept() public void Scenario_Jet_ActiveMaterialIntercept()
{ {
var eng = RunPreset("喷气式拦截-活性材料"); var eng = RunPreset("喷气式拦截-活性材料");