fix: 空基弹药从固定阵位水平发射,修正到达判定和插值

DefensePlanner 空基:
- 移除「飞到投放点再投弹」模型
- 改用固定阵位 + ComputeParabolicRange 前向计算
- ComputeHorizontal 使用 platform.PosY 替代 unit.ReleaseAltitude
- 云团位置 = 炮弹到达位置(飞行方向指向拦截点)

MunitionEntity:
- LaunchAngle/Azimuth/MuzzleVelocity/FlightDuration 改为 public
- 修正下落场景到达判定:发射点高于释放高度时等 Y 下降到位
- 修正插值除零回退 Math.Max(0.01f, ...) 导致负分母炸裂

SimulationEngine:
- 删除空基「到达投放点」死代码
- 删除 InterceptCalculatorAirTests 打印测试

测试: 241 通过 0 失败
This commit is contained in:
tian 2026-06-17 11:23:02 +08:00
parent e1f576fbe5
commit 7bdda062c0
3 changed files with 43 additions and 43 deletions

View File

@ -517,7 +517,7 @@ namespace CounterDrone.Core.Algorithms
var (ia, _, la) = InterceptCalculator.ComputeHorizontal(
wps, threat.DetectArc, typicalSpeed,
_config.ReactionTime + expansionTime, unit.Position, unit.CruiseSpeed,
unit.ReleaseAltitude, (float)threat.Target.TypicalAltitude);
unit.Position.Y, (float)threat.Target.TypicalAltitude);
crossArc = ia;
launchAngle = la;
}
@ -556,19 +556,28 @@ namespace CounterDrone.Core.Algorithms
float deliveryTime;
if (unit.Type == PlatformType.AirBased)
{
if (unit.ReleaseAltitude <= 0 || unit.CruiseSpeed <= 0)
throw new InvalidOperationException($"空基单元 {unit.Id}: ReleaseAltitude={unit.ReleaseAltitude}, CruiseSpeed={unit.CruiseSpeed} 必须>0");
float releaseAlt = unit.ReleaseAltitude;
float cruiseSpd = unit.CruiseSpeed;
float fallTime = Kinematics.AirDropFallTime(releaseAlt, (float)threat.Target.TypicalAltitude);
float driftDist = cruiseSpd * fallTime;
float distToCloud = Kinematics.Distance3D(
unit.Position.X, unit.Position.Y, unit.Position.Z,
cloudGenX, releaseAlt, cloudGenZ);
float flightDist = Math.Max(0f, distToCloud - driftDist);
float flightTime = flightDist / cruiseSpd;
deliveryTime = flightTime + fallTime;
fireTime = recommendedTiming - deliveryTime;
if (unit.CruiseSpeed <= 0)
throw new InvalidOperationException($"空基单元 {unit.Id}: CruiseSpeed={unit.CruiseSpeed} 必须>0");
float targetAlt = (float)threat.Target.TypicalAltitude;
float heightDiff = targetAlt - unit.Position.Y;
if (heightDiff >= 0)
return (events, $"空基单元 {unit.Id}: 平台高度({unit.Position.Y:F0})≤目标高度({targetAlt:F0}),无法重力下落");
// 弹药从固定阵位水平射出,重力下落到目标高度
var (horizontalRange, fallTime) = Kinematics.ComputeParabolicRange(unit.CruiseSpeed, 0, heightDiff);
deliveryTime = fallTime;
fireTime = recommendedTiming - fallTime;
// 云团初始位置 = 炮弹到达位置(飞行方向指向拦截点)
float dx = tx - unit.Position.X;
float dz = tz - unit.Position.Z;
float dist = (float)Math.Sqrt(dx * dx + dz * dz);
if (dist > 0.001f)
{
cloudGenX = unit.Position.X + dx / dist * horizontalRange;
cloudGenZ = unit.Position.Z + dz / dist * horizontalRange;
}
}
else
{

View File

@ -19,19 +19,21 @@ namespace CounterDrone.Core.Simulation
public bool HasArrived { get; private set; }
public float ArrivalTime { get; private set; }
private readonly float _muzzleVelocity;
private float _time;
private readonly float _launchAngle;
private readonly float _azimuth;
public float LaunchAngle { get; }
public float Azimuth { get; }
public float MuzzleVelocity { get; }
public float FlightDuration { get; private set; }
private readonly float _startX, _startY, _startZ;
private readonly float _launchTime;
private float _flightDuration;
// 空基投放:继承载机速度
private readonly float _carrierVelX, _carrierVelY, _carrierVelZ;
// 地基:标记是否已超过释放高度
private bool _hasExceededReleaseAltitude;
// 发射点高于释放高度时,炮弹下降到达
private bool _arrivesDescending;
public MunitionEntity(string id, PlatformType launchMode, AerosolType aerosolType,
float startX, float startY, float startZ,
@ -47,7 +49,7 @@ namespace CounterDrone.Core.Simulation
_startX = startX; _startY = startY; _startZ = startZ;
PosX = startX; PosY = startY; PosZ = startZ;
TargetX = targetX; TargetY = targetY; TargetZ = targetZ;
_muzzleVelocity = muzzleVelocity;
MuzzleVelocity = muzzleVelocity;
ReleaseAltitude = releaseAltitude;
_carrierVelX = carrierVelX;
_carrierVelY = carrierVelY;
@ -63,16 +65,18 @@ namespace CounterDrone.Core.Simulation
var dx = targetX - startX;
var dz = targetZ - startZ;
_azimuth = (float)System.Math.Atan2(dx, dz);
Azimuth = (float)System.Math.Atan2(dx, dz);
float heightDiff = releaseAltitude - startY;
_launchAngle = launchAngle.Value;
var (_, tof) = Kinematics.ComputeParabolicRange(muzzleVelocity, _launchAngle, heightDiff);
_flightDuration = tof;
LaunchAngle = launchAngle.Value;
var (_, tof) = Kinematics.ComputeParabolicRange(muzzleVelocity, LaunchAngle, heightDiff);
FlightDuration = tof;
// 发射点高于释放高度 → 下降到达;否则上升到达
_arrivesDescending = heightDiff < 0;
}
else
{
_flightDuration = Kinematics.AirDropFallTime(startY, releaseAltitude);
FlightDuration = Kinematics.AirDropFallTime(startY, releaseAltitude);
}
}
@ -84,22 +88,21 @@ namespace CounterDrone.Core.Simulation
if (LaunchMode == PlatformType.GroundBased)
{
(float x, float y, float z) = Kinematics.ParabolicPosition(
_startX, _startY, _startZ, _launchAngle, _azimuth, _muzzleVelocity, _time);
_startX, _startY, _startZ, LaunchAngle, Azimuth, MuzzleVelocity, _time);
PosX = x; PosY = y; PosZ = z;
if (PosY >= ReleaseAltitude && !_hasExceededReleaseAltitude)
if (((y >= ReleaseAltitude && !_arrivesDescending) || (y <= ReleaseAltitude && _arrivesDescending)) && !_hasExceededReleaseAltitude)
{
_hasExceededReleaseAltitude = true;
// 精确插值到 Y=ReleaseAltitude 时刻
float tPrev = _time - deltaTime;
var (px0, py0, pz0) = Kinematics.ParabolicPosition(
_startX, _startY, _startZ, _launchAngle, _azimuth, _muzzleVelocity, tPrev);
float frac = (ReleaseAltitude - py0) / System.Math.Max(0.01f, PosY - py0);
_startX, _startY, _startZ, LaunchAngle, Azimuth, MuzzleVelocity, tPrev);
float frac = (ReleaseAltitude - py0) / (PosY - py0);
PosX = px0 + (PosX - px0) * frac;
PosY = ReleaseAltitude;
PosZ = pz0 + (PosZ - pz0) * frac;
HasArrived = true;
ArrivalTime = _launchTime + _flightDuration;
ArrivalTime = _launchTime + FlightDuration;
}
}
else
@ -114,7 +117,7 @@ namespace CounterDrone.Core.Simulation
{
PosY = ReleaseAltitude;
HasArrived = true;
ArrivalTime = _launchTime + _flightDuration;
ArrivalTime = _launchTime + FlightDuration;
}
}
}

View File

@ -7,17 +7,5 @@ namespace CounterDrone.Core.Tests
{
public class InterceptCalculatorAirTests
{
[Fact]
public void Print_Result()
{
var route = new List<Waypoint> {
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
new() { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 },
};
var unit = new Vector3(6000, 1000, 0); // 平台实际位置
var (arc, ts, angle) = InterceptCalculator.ComputeHorizontal(
route, 0, 200, 32, unit, 200, 1500, 500);
Assert.True(false, $"arc={arc:F0}");
}
}
}