统一平台物理模型:去除空基/地基分支
- BuildCandidate 合并 BuildAirBased/GroundBasedCandidate 为单一方法,统一用 MuzzleVelocity - TryGenerateFireEvents 统一用 InterceptCalculator.Compute,去除 ComputeHorizontal - 抛物线选解策略:两解逐一验证可行性(距离匹配 + fireTime>0),选最早拦截的解 - 空基 FireUnit 补充 MuzzleVelocity 字段(等于 CruiseSpeed) - 所有 238 个单元测试通过
This commit is contained in:
parent
92d1131dc1
commit
fb87fdfa22
@ -107,7 +107,7 @@
|
||||
"Id": "air-standard", "Name": "[Demo] 标准空基火力单元",
|
||||
"PlatformType": 0, "GunCount": 1, "ChannelsPerGun": 8, "ChannelInterval": 1.0,
|
||||
"Cooldown": 5.0, "AmmoChangeTime": 30.0,
|
||||
"CruiseSpeed": 80.0, "ReleaseAltitude": 1000.0,
|
||||
"MuzzleVelocity": 80.0, "CruiseSpeed": 80.0, "ReleaseAltitude": 1000.0,
|
||||
"AmmoTypes": [0, 1],
|
||||
"EORange": 4000.0, "IRRange": 3000.0,
|
||||
"MinElevation": -80.0, "MaxElevation": 30.0, "MinDetectAlt": 30.0, "MaxDetectAlt": 15000.0
|
||||
|
||||
@ -334,22 +334,14 @@ namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
if (!unit.AmmoTypes.Contains(neededAmmo)) continue;
|
||||
|
||||
if (unit.Type == PlatformType.AirBased)
|
||||
{
|
||||
var c = BuildAirBasedCandidate(threat, unit, neededAmmo, ammo, ammoEff, expansionTime, mid, midArc, env);
|
||||
if (c != null) candidates.Add(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
var c = BuildGroundBasedCandidate(threat, unit, neededAmmo, ammo, ammoEff, expansionTime, mid, midArc, env);
|
||||
if (c != null) candidates.Add(c);
|
||||
}
|
||||
var c = BuildCandidate(threat, unit, neededAmmo, ammo, ammoEff, expansionTime, mid, midArc, env);
|
||||
if (c != null) candidates.Add(c);
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private InterceptCandidate? BuildGroundBasedCandidate(DroneWave threat,
|
||||
private InterceptCandidate? BuildCandidate(DroneWave threat,
|
||||
FireUnit unit, AerosolType ammoType, AmmunitionSpec ammo,
|
||||
float effectiveR, float expansionTime,
|
||||
Vector3 mid, float midArc, CombatScene env)
|
||||
@ -362,55 +354,10 @@ namespace CounterDrone.Core.Algorithms
|
||||
float maxRange = unit.MuzzleVelocity * unit.MuzzleVelocity / 9.81f;
|
||||
if (dist > maxRange) return null;
|
||||
|
||||
float shellTime = dist / unit.MuzzleVelocity;
|
||||
if (!HasInterceptWindow(threat, midArc, expansionTime, shellTime)) return null;
|
||||
|
||||
float interceptTime = threat.ArrivalTime;
|
||||
|
||||
float avgSpeed = GetDroneSpeedMs(threat);
|
||||
float neededExposure = _damageModel.RequiredExposureSeconds((TargetType)threat.Target.TargetType, (PowerType)threat.Target.PowerType, ammoType);
|
||||
float actualExposure = effectiveR * 2f / avgSpeed;
|
||||
float prob = Math.Min(_config.MaxInterceptProbability, actualExposure / neededExposure);
|
||||
|
||||
return new InterceptCandidate
|
||||
{
|
||||
Unit = unit,
|
||||
AmmoType = ammoType,
|
||||
EarliestInterceptTime = interceptTime,
|
||||
KillProbability = prob,
|
||||
};
|
||||
}
|
||||
|
||||
private InterceptCandidate? BuildAirBasedCandidate(DroneWave threat,
|
||||
FireUnit unit, AerosolType ammoType, AmmunitionSpec ammo,
|
||||
float effectiveR, float expansionTime,
|
||||
Vector3 mid, float midArc, CombatScene env)
|
||||
{
|
||||
if (unit.ReleaseAltitude <= 0 || unit.CruiseSpeed <= 0) return null;
|
||||
float releaseAlt = unit.ReleaseAltitude;
|
||||
float flightDist = Kinematics.Distance3D(
|
||||
unit.Position.X, unit.Position.Y, unit.Position.Z,
|
||||
mid.X, releaseAlt, mid.Z);
|
||||
float flightTime = flightDist / unit.CruiseSpeed;
|
||||
float fallTime = Kinematics.AirDropFallTime(releaseAlt, (float)threat.Target.TypicalAltitude);
|
||||
float deliveryTime = flightTime + fallTime;
|
||||
|
||||
float interceptTime = threat.ArrivalTime;
|
||||
if (deliveryTime > interceptTime) return null;
|
||||
float deliveryTime = dist / unit.MuzzleVelocity;
|
||||
if (deliveryTime > threat.ArrivalTime) return null;
|
||||
if (!HasInterceptWindow(threat, midArc, expansionTime, deliveryTime)) return null;
|
||||
|
||||
// 最小距离约束:无人机至少需要飞 vd*(R+expansion+fallTime) 才能被拦截
|
||||
float speedMs = GetDroneSpeedMs(threat);
|
||||
float minArc = threat.DetectArc + speedMs * (_config.ReactionTime + expansionTime + fallTime);
|
||||
float totalArc = RouteGeometry.TotalLength(threat.Waypoints);
|
||||
if (minArc >= totalArc) return null;
|
||||
|
||||
var (icArc, _, _) = InterceptCalculator.ComputeHorizontal(
|
||||
threat.Waypoints, threat.DetectArc, GetDroneSpeedKph(threat),
|
||||
_config.ReactionTime + expansionTime, unit.Position, unit.CruiseSpeed,
|
||||
unit.ReleaseAltitude, (float)threat.Target.TypicalAltitude);
|
||||
if (icArc <= 0) return null;
|
||||
|
||||
float avgSpeed = GetDroneSpeedMs(threat);
|
||||
float neededExposure = _damageModel.RequiredExposureSeconds((TargetType)threat.Target.TargetType, (PowerType)threat.Target.PowerType, ammoType);
|
||||
float actualExposure = effectiveR * 2f / avgSpeed;
|
||||
@ -420,7 +367,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
Unit = unit,
|
||||
AmmoType = ammoType,
|
||||
EarliestInterceptTime = interceptTime,
|
||||
EarliestInterceptTime = threat.ArrivalTime,
|
||||
KillProbability = prob,
|
||||
};
|
||||
}
|
||||
@ -494,34 +441,22 @@ namespace CounterDrone.Core.Algorithms
|
||||
float typicalSpeed = GetDroneSpeedKph(threat);
|
||||
if (typicalSpeed <= 0) return (events, "目标速度无效");
|
||||
|
||||
// 用 InterceptCalculator 计算拦截弧长和发射角
|
||||
// ═══ 拦截弧长 ═══
|
||||
var mid = ThreatMidpoint(threat);
|
||||
float midArc = RouteGeometry.ArcLengthNearestTo(wps, mid.X, mid.Z);
|
||||
float crossArc;
|
||||
float launchAngle = 0;
|
||||
float interceptShellTime = 0;
|
||||
if (unit.Type == PlatformType.GroundBased)
|
||||
{
|
||||
var (ia, st, la) = InterceptCalculator.Compute(
|
||||
wps, threat.DetectArc, typicalSpeed,
|
||||
_config.ReactionTime + expansionTime, unit.Position, unit.MuzzleVelocity);
|
||||
crossArc = (ia > 0 ? ia : midArc) + targetOffset;
|
||||
launchAngle = la;
|
||||
interceptShellTime = st;
|
||||
}
|
||||
else
|
||||
{
|
||||
var (ia, _, la) = InterceptCalculator.ComputeHorizontal(
|
||||
wps, threat.DetectArc, typicalSpeed,
|
||||
_config.ReactionTime + expansionTime, unit.Position, unit.CruiseSpeed,
|
||||
unit.Position.Y, (float)threat.Target.TypicalAltitude);
|
||||
crossArc = ia + targetOffset;
|
||||
launchAngle = la;
|
||||
}
|
||||
var (ia, _, _) = InterceptCalculator.Compute(
|
||||
wps, threat.DetectArc, typicalSpeed,
|
||||
_config.ReactionTime + expansionTime, unit.Position, unit.MuzzleVelocity);
|
||||
float crossArc = (ia > 0 ? ia : midArc) + targetOffset;
|
||||
|
||||
// 编队横向偏移:按车道分布在航路法向上,与 DroneEntity 编队偏移一致(起点展开)。
|
||||
// DroneEntity: latOffset = formationIndex * lateralSpacing(0/50/100...)
|
||||
// 编队展开方向:读 RoutePlan.LateralAxis (0=X, 1=Y, 2=Z)
|
||||
// 无人机到达穿越点的时间:基于探测边界,而非航路起点
|
||||
float travelArc = crossArc - threat.DetectArc;
|
||||
if (travelArc < 0) return (events, $"探测边界在拦截点之后({threat.DetectArc:F0}m≥{crossArc:F0}m)");
|
||||
float txArrival = RouteGeometry.TravelTimeTo(wps, travelArc, typicalSpeed);
|
||||
float recommendedTiming = txArrival - expansionTime;
|
||||
if (recommendedTiming <= 0f) return (events, $"膨胀{expansionTime:F1}s≥到达{txArrival:F1}s");
|
||||
|
||||
// ═══ 编队横向偏移 ═══
|
||||
int axis = threat.Route?.LateralAxis ?? 2;
|
||||
float laneSpacing = yLanes > 1 ? formationWidth / (yLanes - 1) : 0f;
|
||||
float laneOffset = yLane * laneSpacing;
|
||||
@ -540,48 +475,38 @@ namespace CounterDrone.Core.Algorithms
|
||||
float dx = cloudGenX - unit.Position.X;
|
||||
float dz = cloudGenZ - unit.Position.Z;
|
||||
float targetDist = (float)Math.Sqrt(dx * dx + dz * dz);
|
||||
float mv = unit.Type == PlatformType.AirBased ? unit.CruiseSpeed : unit.MuzzleVelocity;
|
||||
float mv = unit.MuzzleVelocity;
|
||||
if (mv <= 0)
|
||||
throw new InvalidOperationException($"单元 {unit.Id}: 速度={mv} 必须>0");
|
||||
throw new InvalidOperationException($"单元 {unit.Id}: MuzzleVelocity={mv} 必须>0");
|
||||
float heightDiff = ty - unit.Position.Y;
|
||||
if (unit.Type == PlatformType.AirBased && heightDiff >= 0)
|
||||
return (events, $"空基单元 {unit.Id}: 平台高度({unit.Position.Y:F0})≤目标高度({ty:F0}),无法重力下落");
|
||||
|
||||
// 每发独立计算发射角,选离无人机更近的命中点(更早拦截)
|
||||
launchAngle = Kinematics.CalculateLaunchAngle(targetDist, mv, heightDiff);
|
||||
var motion = new ParabolicMotion(mv, launchAngle);
|
||||
var times = motion.GetFlightTimes(heightDiff);
|
||||
if (times.Length == 0)
|
||||
// ═══ 抛物线求解:两解逐一验证,选最早摧毁无人机的可行解 ═══
|
||||
var angles = ParabolicMotion.SolveAngles(targetDist, heightDiff, mv);
|
||||
if (!angles.HasValue)
|
||||
return (events, $"目标超出弹道射程: dist={targetDist:F0}m");
|
||||
// 两个解时:远点更靠近无人机(较早拦截),优先选远点
|
||||
float tof = times.Length == 2 ? times[1] : times[0];
|
||||
float range = mv * (float)Math.Cos(launchAngle) * tof;
|
||||
|
||||
// 无人机到达穿越点的时间:基于探测边界,而非航路起点。
|
||||
// planner 是参谋,只能基于探测信息规划。威胁从探测边界被发现,
|
||||
// 飞行时间 = (拦截弧长 - 探测弧长) / 速度。无探测时 DetectArc=0(回退到起点)。
|
||||
float travelArc = crossArc - threat.DetectArc;
|
||||
if (travelArc < 0) return (events, $"探测边界在拦截点之后({threat.DetectArc:F0}m≥{crossArc:F0}m)");
|
||||
float txArrival = RouteGeometry.TravelTimeTo(wps, travelArc, typicalSpeed);
|
||||
float recommendedTiming = txArrival - expansionTime;
|
||||
if (recommendedTiming <= 0f) return (events, $"膨胀{expansionTime:F1}s≥到达{txArrival:F1}s");
|
||||
|
||||
// cloudGenX/Z 已在上面计算(含风偏),直接使用
|
||||
|
||||
float deliveryTime;
|
||||
if (unit.Type == PlatformType.AirBased)
|
||||
deliveryTime = tof;
|
||||
else if (interceptShellTime <= 0)
|
||||
return (events, "拦截计算未得出有效飞行时间");
|
||||
else
|
||||
float? bestLaunchAngle = null, bestTof = null, bestFireTime = null;
|
||||
foreach (var a in new[] { angles.Value.Item1, angles.Value.Item2 })
|
||||
{
|
||||
deliveryTime = interceptShellTime;
|
||||
tof = interceptShellTime;
|
||||
var motion = new ParabolicMotion(mv, a);
|
||||
var ts = motion.GetFlightTimes(heightDiff);
|
||||
foreach (var t in ts)
|
||||
{
|
||||
float r = mv * (float)Math.Cos(a) * t;
|
||||
if (Math.Abs(r - targetDist) > 0.5f) continue;
|
||||
float ft = recommendedTiming - t;
|
||||
if (ft <= 0f) continue;
|
||||
// 选最早摧毁(fireTime 最小 → 发射最早 → 拦截最早)
|
||||
if (!bestFireTime.HasValue || ft < bestFireTime.Value)
|
||||
{
|
||||
bestLaunchAngle = a; bestTof = t; bestFireTime = ft;
|
||||
}
|
||||
}
|
||||
}
|
||||
float fireTime = recommendedTiming - deliveryTime;
|
||||
|
||||
if (fireTime <= 0f)
|
||||
return (events, $"发射时机{fireTime:F1}s≤0(到达{deliveryTime:F1}s>窗口{recommendedTiming:F1}s)");
|
||||
if (!bestLaunchAngle.HasValue)
|
||||
return (events, $"所有弹道解均不可行: dist={targetDist:F0}m");
|
||||
float launchAngle = bestLaunchAngle.Value;
|
||||
float tof = bestTof!.Value;
|
||||
float fireTime = bestFireTime!.Value;
|
||||
|
||||
events.Add(new FireEvent
|
||||
{
|
||||
|
||||
Binary file not shown.
@ -107,7 +107,7 @@
|
||||
"Id": "air-standard", "Name": "[Demo] 标准空基火力单元",
|
||||
"PlatformType": 0, "GunCount": 1, "ChannelsPerGun": 8, "ChannelInterval": 1.0,
|
||||
"Cooldown": 5.0, "AmmoChangeTime": 30.0,
|
||||
"CruiseSpeed": 80.0, "ReleaseAltitude": 1000.0,
|
||||
"MuzzleVelocity": 80.0, "CruiseSpeed": 80.0, "ReleaseAltitude": 1000.0,
|
||||
"AmmoTypes": [0, 1],
|
||||
"EORange": 4000.0, "IRRange": 3000.0,
|
||||
"MinElevation": -80.0, "MaxElevation": 30.0, "MinDetectAlt": 30.0, "MaxDetectAlt": 15000.0
|
||||
|
||||
@ -32,7 +32,7 @@ namespace CounterDrone.Core.Tests
|
||||
Id = id, Type = PlatformType.AirBased,
|
||||
Position = new Vector3(posX, 2500, -2000),
|
||||
GunCount = 1, ChannelsPerGun = 16,
|
||||
CruiseSpeed = 55, ReleaseAltitude = 1500,
|
||||
MuzzleVelocity = 55, CruiseSpeed = 55, ReleaseAltitude = 1500,
|
||||
TotalMunitions = 16, Cooldown = 5f,
|
||||
AmmoTypes = new() { AerosolType.InertGas },
|
||||
};
|
||||
@ -247,7 +247,7 @@ namespace CounterDrone.Core.Tests
|
||||
{
|
||||
Id = "u0", Type = PlatformType.AirBased,
|
||||
Position = new Vector3(100000, 2500, 100000), // 极远
|
||||
GunCount = 1, ChannelsPerGun = 4, CruiseSpeed = 10, ReleaseAltitude = 1500,
|
||||
GunCount = 1, ChannelsPerGun = 4, MuzzleVelocity = 10, CruiseSpeed = 10, ReleaseAltitude = 1500,
|
||||
TotalMunitions = 4, AmmoTypes = new() { AerosolType.InertGas },
|
||||
};
|
||||
var result = Plan(new() { unit }, MakeThreat());
|
||||
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user