refactor: 移除隐藏 Math.Max 回退,改为显式验证或数值 epsilon

GaussianPuffDispersion:
- BurstChargeKg ≤ 0 → 抛异常,不再用 Math.Max(0.01, ...) 掩护
- Phase3 扩散距离 x ≤ 0 → 跳过,不再用 Math.Max(1f, ...)
- Opacity/SizeMultiplier → 除零改为显式判 0
- 缓存 _initialRadius 避免重复 Pow 计算

CloudExpansionModel:
- BurstChargeKg ≤ 0 → 抛异常
- Phase3 x ≤ 0 → 直接返回 Phase2 末半径
- RoundsNeeded: s ≤ 0 → 返回 1

DefensePlanner:
- yLanes>1 判定已在上一行,去掉冗余 Math.Max(1, ...)

InterceptCalculator:
- 保留 +0.001f epsilon(确保 ts>0),但不作为隐藏回退
- 改用显式加偏移替代 Math.Max(0.001f, ...)

DetectionCalculator.SpreadRadius: accuracy<0 → 抛异常
RouteGeometry: arcLength<0 → 钳位为 0(显式 if)

保留 10 处合法 Math.Max(取两值中较大者或几何钳位)
This commit is contained in:
tian 2026-06-17 11:30:56 +08:00
parent 7bdda062c0
commit da4e8d5c63
6 changed files with 39 additions and 29 deletions

View File

@ -8,15 +8,21 @@ namespace CounterDrone.Core.Algorithms
{
private readonly AmmunitionSpec _ammo;
private readonly CombatScene _env;
private readonly float _initialRadius;
public CloudExpansionModel(AmmunitionSpec ammo, CombatScene env)
{
_ammo = ammo;
_env = env;
var w = (float)ammo.BurstChargeKg;
if (w <= 0)
throw new ArgumentException($"BurstChargeKg 必须 > 0实际: {w}", nameof(ammo));
_initialRadius = 3.3f * (float)Math.Pow(w, 0.32);
}
/// <summary>Phase 1 爆轰初始半径 (m)</summary>
public float InitialRadius => 3.3f * (float)Math.Pow(Math.Max(0.01, (float)_ammo.BurstChargeKg), 0.32f);
public float InitialRadius => _initialRadius;
/// <summary>Phase 2 结束时的有效半径</summary>
public float TurbulentRadius => RadiusAt(Phase2Duration);
@ -29,21 +35,22 @@ namespace CounterDrone.Core.Algorithms
/// <summary>指定时刻湍流膨胀半径 R(t) = R₀ + k√t</summary>
public float RadiusAt(float elapsedSeconds)
{
if (elapsedSeconds <= 0) return InitialRadius;
if (elapsedSeconds <= 0) return _initialRadius;
float p2 = Phase2Duration;
bool inPhase3 = elapsedSeconds > p2;
if (!inPhase3)
return InitialRadius + TurbulentExpansionK * (float)Math.Sqrt(elapsedSeconds);
return _initialRadius + TurbulentExpansionK * (float)Math.Sqrt(elapsedSeconds);
// Phase 3: 高斯扩散
float windSpeed = (float)_env.WindSpeed;
float x = Math.Max(1f, windSpeed * (elapsedSeconds - p2));
float x = windSpeed * (elapsedSeconds - p2);
if (x <= 0) return _initialRadius + TurbulentExpansionK * (float)Math.Sqrt(p2);
var cls = Kinematics.GetStabilityClass((WeatherType)_env.WeatherType, windSpeed);
float sY = Kinematics.SigmaY(cls, x);
float sZ = Kinematics.SigmaZ(cls, x);
float peakC = Kinematics.GaussianPeakConcentration((float)_ammo.SourceStrength, sY, sZ);
float effConc = (float)_ammo.EffectiveConcentration;
float rPhase2 = InitialRadius + TurbulentExpansionK * (float)Math.Sqrt(p2);
float rPhase2 = _initialRadius + TurbulentExpansionK * (float)Math.Sqrt(p2);
if (peakC <= effConc) return rPhase2;
float sigma = (sY + sZ) / 2f;
return rPhase2 + sigma * (float)Math.Sqrt(2f * Math.Log(peakC / effConc));
@ -54,26 +61,25 @@ namespace CounterDrone.Core.Algorithms
{
float r = RadiusAt(elapsedSeconds);
float volume = (4f / 3f) * (float)Math.PI * r * r * r;
return volume > 0.001f ? (float)_ammo.SourceStrength / volume : (float)_ammo.CoreDensity;
if (volume <= 0.001f) return (float)_ammo.CoreDensity;
return (float)_ammo.SourceStrength / volume;
}
/// <summary>覆盖指定距离需要的云团数量</summary>
/// <param name="requiredCoverage">需要覆盖的距离 (m)</param>
/// <param name="spacing">相邻云团中心间距 (m),默认=2R(相切)。重叠时由调用方传入更小值。</param>
public int RoundsNeeded(float requiredCoverage, float? spacing = null)
{
float s = spacing ?? 2f * TurbulentRadius;
return Math.Max(1, (int)Math.Ceiling(requiredCoverage / s));
if (s <= 0) return 1;
int n = (int)Math.Ceiling(requiredCoverage / s);
return n < 1 ? 1 : n;
}
/// <summary>达到指定半径所需时间 (s)</summary>
public float TimeToReach(float radius)
{
float r0 = InitialRadius;
if (radius <= r0) return 0;
if (radius <= _initialRadius) return 0;
float k = TurbulentExpansionK;
return (radius - r0) * (radius - r0) / (k * k);
return (radius - _initialRadius) * (radius - _initialRadius) / (k * k);
}
}
}

View File

@ -527,7 +527,7 @@ namespace CounterDrone.Core.Algorithms
// 法向 = 切向旋转 90°用 RouteGeometry.TangentAt 取穿越点处航路方向
var (tanX, tanZ) = RouteGeometry.TangentAt(wps, crossArc);
float normalX = -tanZ, normalZ = tanX; // 切向逆时针 90° = 左侧法向
float laneSpacing = yLanes > 1 ? formationWidth / Math.Max(1, yLanes - 1) : 0f;
float laneSpacing = yLanes > 1 ? formationWidth / (yLanes - 1) : 0f;
float laneOffset = yLane * laneSpacing;
// 穿越点 = 航路上 crossArc 处 + 横向车道偏移

View File

@ -137,7 +137,9 @@ namespace CounterDrone.Core.Algorithms
/// 当前模型:散布半径 = 精度值(如精度 100m → 散布 ±100m。</summary>
public static float SpreadRadius(float accuracy)
{
return Math.Max(0f, accuracy);
if (accuracy < 0)
throw new ArgumentException($"accuracy 必须 ≥ 0实际: {accuracy}", nameof(accuracy));
return accuracy;
}
}

View File

@ -19,11 +19,11 @@ namespace CounterDrone.Core.Algorithms
private Vector3 _center;
private Vector3 _windVelocity;
private bool _inPhase3;
private float _initialRadius;
public Vector3 Center => _center;
public float Radius => _currentRadius;
public float CoreDensity => _currentDensity;
/// <summary>云团中心峰值浓度(无人机穿过位置)</summary>
public float PeakDensity { get; private set; }
public float EffectiveRadius => _currentRadius;
public ParticleParams Particles { get; } = new();
@ -38,9 +38,11 @@ namespace CounterDrone.Core.Algorithms
_elapsed = 0f;
_inPhase3 = false;
// Phase 1: 爆轰膨胀 → 初始半径
var w = (float)ammo.BurstChargeKg;
_currentRadius = 3.3f * (float)Math.Pow(Math.Max(0.01, w), 0.32);
if (w <= 0)
throw new ArgumentException($"BurstChargeKg 必须 > 0实际: {w}", nameof(ammo));
_initialRadius = 3.3f * (float)Math.Pow(w, 0.32);
_currentRadius = _initialRadius;
_currentDensity = (float)ammo.CoreDensity;
PeakDensity = (float)ammo.CoreDensity;
_center = releasePos;
@ -59,7 +61,6 @@ namespace CounterDrone.Core.Algorithms
var (vx, vy, vz) = Kinematics.WindToVector(windDir, windSpeed);
_windVelocity = new Vector3(vx, vy, vz);
// 判断阶段切换
if (!_inPhase3 && _elapsed >= 30f)
_inPhase3 = true;
@ -67,8 +68,7 @@ namespace CounterDrone.Core.Algorithms
{
// Phase 2: 湍流扩散 R(t) = R₀ + k × √t
var k = (float)_ammo.TurbulentExpansionK;
_currentRadius = 3.3f * (float)Math.Pow(Math.Max(0.01, (float)_ammo.BurstChargeKg), 0.32)
+ k * (float)Math.Sqrt(_elapsed);
_currentRadius = _initialRadius + k * (float)Math.Sqrt(_elapsed);
// 密度 = 源强 / 体积
var volume = (4f / 3f) * (float)Math.PI * _currentRadius * _currentRadius * _currentRadius;
_currentDensity = volume > 0.001f ? (float)_ammo.SourceStrength / volume : 0f;
@ -76,12 +76,12 @@ namespace CounterDrone.Core.Algorithms
else
{
// Phase 3: 高斯扩散
var x = Math.Max(1f, windSpeed * (_elapsed - 30f));
float x = windSpeed * (_elapsed - 30f);
if (x <= 0) return; // 尚未进入有效扩散距离
var cls = Kinematics.GetStabilityClass((WeatherType)_env.WeatherType, windSpeed);
var sY = Kinematics.SigmaY(cls, x);
var sZ = Kinematics.SigmaZ(cls, x);
_currentDensity = Kinematics.GaussianPeakConcentration((float)_ammo.SourceStrength, sY, sZ);
// 有效半径从浓度反推
var effConc = (float)_ammo.EffectiveConcentration;
if (_currentDensity > effConc)
{
@ -95,11 +95,11 @@ namespace CounterDrone.Core.Algorithms
_center.Y += _windVelocity.Y * deltaTime;
_center.Z += _windVelocity.Z * deltaTime;
// 粒子参数
Particles.Opacity = Math.Max(0.1f, _currentDensity / (float)_ammo.CoreDensity);
Particles.SizeMultiplier = _currentRadius / Math.Max(0.5f, 3.3f * (float)Math.Pow(Math.Max(0.01, (float)_ammo.BurstChargeKg), 0.32));
// 粒子参数(仅用于可视化,不参与物理计算)
float coreDensity = (float)_ammo.CoreDensity;
Particles.Opacity = coreDensity > 0 ? _currentDensity / coreDensity : 0f;
Particles.SizeMultiplier = _initialRadius > 0 ? _currentRadius / _initialRadius : 0f;
// 消散
if (_elapsed >= (float)_ammo.MaxDuration || _currentRadius >= (float)_ammo.MaxRadius)
{
IsDissipated = true;

View File

@ -75,7 +75,8 @@ namespace CounterDrone.Core.Algorithms
return MathF.Atan2(sinA, cosA);
}
float lo = detectArc + Math.Max(0.001f, vd * reactionTime + 0.001f);
// +0.001f 确保 ts > 0否则 F() 返回 MaxValue 导致搜索失败
float lo = detectArc + vd * reactionTime + 0.001f;
if (lo >= totalArc) return (0, 0, 0);
float fLo = F(lo);

View File

@ -108,7 +108,8 @@ namespace CounterDrone.Core.Algorithms
{
if (wps == null || wps.Count < 2) return (1f, 0f);
float remaining = Math.Max(0f, arcLength);
if (arcLength < 0) arcLength = 0;
float remaining = arcLength;
for (int i = 0; i < wps.Count - 1; i++)
{
float dx = (float)wps[i + 1].PosX - (float)wps[i].PosX;