Phase 3: 算法层 — 高斯烟团扩散模型、3种毁伤模型、DefenseAdvisor、AlgorithmFactory,73个测试全部通过
This commit is contained in:
parent
b33a6a87e1
commit
dc4ebc3f09
37
src/CounterDrone.Core/Algorithms/ActiveFuelDamageModel.cs
Normal file
37
src/CounterDrone.Core/Algorithms/ActiveFuelDamageModel.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
/// <summary>吸入式爆炸 — 指数累积型:暴露时间越长伤害越高</summary>
|
||||
public class ActiveFuelDamageModel : IDamageModel
|
||||
{
|
||||
private const float EffectiveThreshold = 0.03f; // 有效浓度阈值
|
||||
private const float BaseRate = 0.02f; // 基础速率
|
||||
private const float ExpFactor = 0.3f; // 指数增长因子
|
||||
|
||||
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
||||
{
|
||||
if (aerosolType != AerosolType.ActiveFuel) return 0f;
|
||||
if (cloudDensity < EffectiveThreshold) return 0f;
|
||||
|
||||
// 伤害 = 基础速率 × e^(暴露时间 × 因子) × deltaTime
|
||||
// 在云团中待得越久伤害指数增长
|
||||
var exponential = (float)System.Math.Exp(exposureTime * ExpFactor);
|
||||
var damage = BaseRate * exponential * deltaTime;
|
||||
|
||||
// 高速目标更脆弱
|
||||
var sensitivity = droneType == TargetType.HighSpeed ? 1.5f : 1.0f;
|
||||
return damage * sensitivity;
|
||||
}
|
||||
|
||||
public DamageStage GetDamageStage(float accumulatedDamage)
|
||||
{
|
||||
if (accumulatedDamage >= 1.0f) return DamageStage.Destroyed;
|
||||
if (accumulatedDamage >= 0.6f) return DamageStage.AttitudeLoss;
|
||||
if (accumulatedDamage >= 0.3f) return DamageStage.EngineAnomaly;
|
||||
return DamageStage.Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
/// <summary>爆燃式 — 触发型:双条件满足后瞬间高伤害</summary>
|
||||
public class ActiveMaterialDamageModel : IDamageModel
|
||||
{
|
||||
private const float TriggerThreshold = 0.1f; // 触发浓度阈值
|
||||
private const float BurstDamage = 0.6f; // 一次爆发伤害
|
||||
private const float ResidualRate = 0.02f; // 后续余伤速率
|
||||
private bool _triggered;
|
||||
|
||||
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
||||
{
|
||||
if (aerosolType != AerosolType.ActiveMaterial) return 0f;
|
||||
if (cloudDensity < TriggerThreshold) return 0f;
|
||||
|
||||
// 喷气发动机高温触发爆燃,更敏感
|
||||
if (!_triggered)
|
||||
{
|
||||
_triggered = true;
|
||||
var sensitivity = powerType == PowerType.Jet ? 1.3f : 1.0f;
|
||||
return BurstDamage * sensitivity;
|
||||
}
|
||||
|
||||
return ResidualRate * deltaTime;
|
||||
}
|
||||
|
||||
public DamageStage GetDamageStage(float accumulatedDamage)
|
||||
{
|
||||
if (accumulatedDamage >= 1.0f) return DamageStage.Destroyed;
|
||||
if (accumulatedDamage >= 0.5f) return DamageStage.AttitudeLoss; // 爆燃后更快进入失控
|
||||
if (accumulatedDamage >= 0.2f) return DamageStage.EngineAnomaly;
|
||||
return DamageStage.Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/CounterDrone.Core/Algorithms/AlgorithmFactory.cs
Normal file
32
src/CounterDrone.Core/Algorithms/AlgorithmFactory.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
/// <summary>算法工厂 — 统一管理算法组件的创建与替换</summary>
|
||||
public static class AlgorithmFactory
|
||||
{
|
||||
private static readonly Dictionary<Type, Type> _registry = new()
|
||||
{
|
||||
{ typeof(ICloudDispersionModel), typeof(GaussianPuffDispersion) },
|
||||
{ typeof(IDamageModel), typeof(DamageModelRouter) },
|
||||
{ typeof(IDefenseAdvisor), typeof(DefaultDefenseAdvisor) },
|
||||
};
|
||||
|
||||
/// <summary>替换某个算法实现(第三方接入点)</summary>
|
||||
public static void Register<TInterface, TImpl>() where TImpl : TInterface, new()
|
||||
{
|
||||
_registry[typeof(TInterface)] = typeof(TImpl);
|
||||
}
|
||||
|
||||
/// <summary>创建算法实例</summary>
|
||||
public static T Create<T>() where T : class
|
||||
{
|
||||
if (_registry.TryGetValue(typeof(T), out var implType))
|
||||
return (T)Activator.CreateInstance(implType)!;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"No implementation registered for {typeof(T).Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs
Normal file
74
src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
/// <summary>威胁画像 — 防御推荐算法的输入</summary>
|
||||
public class ThreatProfile
|
||||
{
|
||||
public CombatScene Environment { get; set; } = new();
|
||||
public List<TargetConfig> Targets { get; set; } = new();
|
||||
public RoutePlan Route { get; set; } = new();
|
||||
public List<Waypoint> Waypoints { get; set; } = new();
|
||||
public List<ControlZone> ControlZones { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>防御推荐方案</summary>
|
||||
public class DefenseRecommendation
|
||||
{
|
||||
public DefenseSolution Best { get; set; } = new();
|
||||
public DefenseSolution Critical { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>单套防御方案</summary>
|
||||
public class DefenseSolution
|
||||
{
|
||||
public AerosolType RecommendedAerosolType { get; set; }
|
||||
public string AerosolRationale { get; set; } = string.Empty;
|
||||
public CloudDispersal RecommendedCloud { get; set; } = new();
|
||||
|
||||
public List<RecommendedPlatform> Platforms { get; set; } = new();
|
||||
public List<RecommendedDetection> Detections { get; set; } = new();
|
||||
|
||||
public float InterceptProbability { get; set; }
|
||||
public string SummaryRationale { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class RecommendedPlatform
|
||||
{
|
||||
public PlatformType Type { get; set; }
|
||||
public Vector3 Position { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
public int MunitionCount { get; set; }
|
||||
public float CoverageVolume { get; set; }
|
||||
}
|
||||
|
||||
public class RecommendedDetection
|
||||
{
|
||||
public Vector3 Position { get; set; }
|
||||
public float DetectionRadius { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>三维向量(纯 C#,不依赖 UnityEngine)</summary>
|
||||
public struct Vector3
|
||||
{
|
||||
public float X, Y, Z;
|
||||
public Vector3(float x, float y, float z) { X = x; Y = y; Z = z; }
|
||||
|
||||
public static Vector3 operator +(Vector3 a, Vector3 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
|
||||
public static Vector3 operator -(Vector3 a, Vector3 b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
|
||||
public static Vector3 operator *(Vector3 a, float s) => new(a.X * s, a.Y * s, a.Z * s);
|
||||
public float Length => (float)System.Math.Sqrt(X * X + Y * Y + Z * Z);
|
||||
public float DistanceTo(Vector3 other) => (this - other).Length;
|
||||
}
|
||||
|
||||
/// <summary>粒子渲染参数</summary>
|
||||
public class ParticleParams
|
||||
{
|
||||
public float EmitRate { get; set; } = 100;
|
||||
public float Opacity { get; set; } = 1.0f;
|
||||
public string ColorHex { get; set; } = "#FFFFFF";
|
||||
public float SizeMultiplier { get; set; } = 1.0f;
|
||||
}
|
||||
}
|
||||
32
src/CounterDrone.Core/Algorithms/DamageModelRouter.cs
Normal file
32
src/CounterDrone.Core/Algorithms/DamageModelRouter.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
/// <summary>毁伤模型路由器 — 根据气溶胶类型分派</summary>
|
||||
public class DamageModelRouter : IDamageModel
|
||||
{
|
||||
private readonly InertGasDamageModel _inertGas = new();
|
||||
private readonly ActiveMaterialDamageModel _activeMaterial = new();
|
||||
private readonly ActiveFuelDamageModel _activeFuel = new();
|
||||
|
||||
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
||||
{
|
||||
return aerosolType switch
|
||||
{
|
||||
AerosolType.InertGas => _inertGas.CalculateDamage(droneType, powerType, aerosolType, cloudDensity, exposureTime, deltaTime),
|
||||
AerosolType.ActiveMaterial => _activeMaterial.CalculateDamage(droneType, powerType, aerosolType, cloudDensity, exposureTime, deltaTime),
|
||||
AerosolType.ActiveFuel => _activeFuel.CalculateDamage(droneType, powerType, aerosolType, cloudDensity, exposureTime, deltaTime),
|
||||
_ => 0f,
|
||||
};
|
||||
}
|
||||
|
||||
public DamageStage GetDamageStage(float accumulatedDamage)
|
||||
{
|
||||
if (accumulatedDamage >= 1.0f) return DamageStage.Destroyed;
|
||||
if (accumulatedDamage >= 0.6f) return DamageStage.AttitudeLoss;
|
||||
if (accumulatedDamage >= 0.25f) return DamageStage.EngineAnomaly;
|
||||
return DamageStage.Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
176
src/CounterDrone.Core/Algorithms/DefaultDefenseAdvisor.cs
Normal file
176
src/CounterDrone.Core/Algorithms/DefaultDefenseAdvisor.cs
Normal file
@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
public class DefaultDefenseAdvisor : IDefenseAdvisor
|
||||
{
|
||||
private static readonly Dictionary<PowerType, AerosolType> MatchTable = new()
|
||||
{
|
||||
{ PowerType.Electric, AerosolType.InertGas },
|
||||
{ PowerType.Piston, AerosolType.InertGas },
|
||||
{ PowerType.Jet, AerosolType.ActiveMaterial },
|
||||
};
|
||||
|
||||
public DefenseRecommendation Recommend(ThreatProfile threat)
|
||||
{
|
||||
if (threat.Targets.Count == 0 || threat.Waypoints.Count < 2)
|
||||
return new DefenseRecommendation();
|
||||
|
||||
var target = threat.Targets[0];
|
||||
var route = threat.Waypoints;
|
||||
|
||||
// Step A:气溶胶选型
|
||||
var powerType = (PowerType)target.PowerType;
|
||||
var aerosolType = MatchTable.TryGetValue(powerType, out var match)
|
||||
? match : AerosolType.InertGas;
|
||||
|
||||
var rationale = powerType switch
|
||||
{
|
||||
PowerType.Electric or PowerType.Piston =>
|
||||
$"{powerType}发动机依赖氧气,推荐惰性气体窒息方案(吸入式灭火)",
|
||||
PowerType.Jet =>
|
||||
$"{powerType}发动机高温表面可触发活性材料爆燃反应",
|
||||
_ => "默认推荐惰性气体方案",
|
||||
};
|
||||
|
||||
// Step B:时空交汇优化
|
||||
var start = ToV3(route[0]);
|
||||
var end = ToV3(route[^1]);
|
||||
var mid = new Vector3((start.X + end.X) / 2f, (start.Y + end.Y) / 2f, (start.Z + end.Z) / 2f);
|
||||
var routeLength = start.DistanceTo(end);
|
||||
var avgSpeed = (float)target.TypicalSpeed / 3.6f; // km/h → m/s
|
||||
var flightTime = routeLength / avgSpeed;
|
||||
|
||||
// 最佳值:航路中点
|
||||
var bestCloud = new CloudDispersal
|
||||
{
|
||||
AerosolType = (int)aerosolType,
|
||||
PositionX = mid.X,
|
||||
PositionY = mid.Y,
|
||||
PositionZ = mid.Z,
|
||||
DisperseHeight = (float)target.TypicalAltitude,
|
||||
TriggerMode = (int)TriggerMode.Time,
|
||||
Duration = 60,
|
||||
Source = "Algorithm",
|
||||
PositionMode = (int)PositionMode.AlgorithmRecommended,
|
||||
RecommendedTiming = flightTime / 2f,
|
||||
};
|
||||
|
||||
var bestProb = EstimateInterceptProbability(route, avgSpeed, mid, 50f, 60f);
|
||||
|
||||
// 最危险值:航路 1/4 处
|
||||
var criticalPos = new Vector3(
|
||||
start.X + (end.X - start.X) * 0.25f,
|
||||
start.Y + (end.Y - start.Y) * 0.25f,
|
||||
start.Z + (end.Z - start.Z) * 0.25f);
|
||||
|
||||
var criticalCloud = new CloudDispersal
|
||||
{
|
||||
AerosolType = (int)aerosolType,
|
||||
PositionX = criticalPos.X,
|
||||
PositionY = criticalPos.Y,
|
||||
PositionZ = criticalPos.Z,
|
||||
DisperseHeight = (float)target.TypicalAltitude,
|
||||
TriggerMode = (int)TriggerMode.Time,
|
||||
Duration = 60,
|
||||
Source = "Algorithm",
|
||||
PositionMode = (int)PositionMode.AlgorithmRecommended,
|
||||
RecommendedTiming = flightTime / 4f,
|
||||
};
|
||||
|
||||
var criticalProb = EstimateInterceptProbability(route, avgSpeed, criticalPos, 50f, 60f);
|
||||
|
||||
// Step C:反推平台部署
|
||||
var bestPlatforms = RecommendPlatforms(bestCloud, target);
|
||||
|
||||
return new DefenseRecommendation
|
||||
{
|
||||
Best = new DefenseSolution
|
||||
{
|
||||
RecommendedAerosolType = aerosolType,
|
||||
AerosolRationale = rationale,
|
||||
RecommendedCloud = bestCloud,
|
||||
Platforms = bestPlatforms,
|
||||
Detections = new List<RecommendedDetection>
|
||||
{
|
||||
new RecommendedDetection
|
||||
{
|
||||
Position = new Vector3(mid.X, mid.Y, 0),
|
||||
DetectionRadius = System.Math.Max(3000f, routeLength * 0.4f),
|
||||
Quantity = 1,
|
||||
}
|
||||
},
|
||||
InterceptProbability = bestProb,
|
||||
SummaryRationale = $"最佳方案:{aerosolType}方案,预计拦截概率 {bestProb:P0}",
|
||||
},
|
||||
Critical = new DefenseSolution
|
||||
{
|
||||
RecommendedAerosolType = aerosolType,
|
||||
AerosolRationale = "临界方案",
|
||||
RecommendedCloud = criticalCloud,
|
||||
Platforms = RecommendPlatforms(criticalCloud, target),
|
||||
Detections = new List<RecommendedDetection>(),
|
||||
InterceptProbability = criticalProb,
|
||||
SummaryRationale = $"最危险方案:拦截概率仅 {criticalProb:P0}",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private List<RecommendedPlatform> RecommendPlatforms(CloudDispersal cloud, TargetConfig target)
|
||||
{
|
||||
// 简化:默认地基火炮
|
||||
var platforms = new List<RecommendedPlatform>();
|
||||
var targetCount = target.Quantity;
|
||||
var platformsNeeded = Math.Max(1, (int)Math.Ceiling(targetCount / 2.0));
|
||||
|
||||
for (int i = 0; i < platformsNeeded; i++)
|
||||
{
|
||||
platforms.Add(new RecommendedPlatform
|
||||
{
|
||||
Type = PlatformType.GroundBased,
|
||||
Position = new Vector3((float)(cloud.PositionX + i * 200), 0, 50),
|
||||
Quantity = 1,
|
||||
MunitionCount = 3,
|
||||
CoverageVolume = 500000f,
|
||||
});
|
||||
}
|
||||
|
||||
return platforms;
|
||||
}
|
||||
|
||||
private float EstimateInterceptProbability(List<Waypoint> route, float avgSpeed,
|
||||
Vector3 cloudPos, float cloudRadius, float cloudDuration)
|
||||
{
|
||||
// 简化估算:计算目标在云团中的穿越时间比例
|
||||
float totalDist = 0;
|
||||
float inCloudDist = 0;
|
||||
|
||||
for (int i = 0; i < route.Count - 1; i++)
|
||||
{
|
||||
var segStart = ToV3(route[i]);
|
||||
var segEnd = ToV3(route[i + 1]);
|
||||
var segLen = segStart.DistanceTo(segEnd);
|
||||
totalDist += segLen;
|
||||
|
||||
// 简化:检查线段中点是否在云团内
|
||||
var segMid = new Vector3(
|
||||
(segStart.X + segEnd.X) / 2f,
|
||||
(segStart.Y + segEnd.Y) / 2f,
|
||||
(segStart.Z + segEnd.Z) / 2f);
|
||||
|
||||
if (segMid.DistanceTo(cloudPos) <= cloudRadius)
|
||||
inCloudDist += segLen;
|
||||
}
|
||||
|
||||
return totalDist > 0 ? System.Math.Min(0.95f, inCloudDist / totalDist) : 0f;
|
||||
}
|
||||
|
||||
private static Vector3 ToV3(Waypoint wp)
|
||||
{
|
||||
return new Vector3((float)wp.PosX, (float)wp.PosY, (float)wp.PosZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/CounterDrone.Core/Algorithms/GaussianPuffDispersion.cs
Normal file
91
src/CounterDrone.Core/Algorithms/GaussianPuffDispersion.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
/// <summary>高斯烟团扩散模型 — 简化实现</summary>
|
||||
public class GaussianPuffDispersion : ICloudDispersionModel
|
||||
{
|
||||
private AmmunitionSpec _ammo = null!;
|
||||
private float _elapsed;
|
||||
private float _currentRadius;
|
||||
private Vector3 _center;
|
||||
private Vector3 _windVelocity;
|
||||
|
||||
public Vector3 Center => _center;
|
||||
public float Radius => _currentRadius;
|
||||
public float CoreDensity { get; private set; }
|
||||
public float EffectiveRadius => _currentRadius; // 简化:有效半径 = 物理半径
|
||||
public ParticleParams Particles { get; } = new();
|
||||
public bool IsDissipated { get; private set; }
|
||||
|
||||
public void Initialize(AmmunitionSpec ammo, CombatScene env, Vector3 releasePos, float releaseTime)
|
||||
{
|
||||
_ammo = ammo;
|
||||
_elapsed = 0f;
|
||||
_currentRadius = (float)ammo.InitialRadius;
|
||||
_center = releasePos;
|
||||
CoreDensity = (float)ammo.CoreDensity;
|
||||
IsDissipated = false;
|
||||
|
||||
// 风速 → 速度矢量
|
||||
var dir = WindToVector((WindDirection)env.WindDirection);
|
||||
_windVelocity = dir * (float)env.WindSpeed;
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime, float windSpeed, WindDirection windDir)
|
||||
{
|
||||
if (IsDissipated) return;
|
||||
|
||||
_elapsed += deltaTime;
|
||||
|
||||
// 更新风速(允许动态变化)
|
||||
var dir = WindToVector(windDir);
|
||||
_windVelocity = dir * windSpeed;
|
||||
|
||||
// 半径膨胀:初始半径 + 扩散速率 × 时间
|
||||
// 加入大气稳定度因子(简化:1.0)
|
||||
var dispersionRate = (float)(_ammo.DispersionRateBase * 1.0f);
|
||||
_currentRadius = (float)_ammo.InitialRadius + dispersionRate * _elapsed;
|
||||
|
||||
// 中心随风漂移
|
||||
_center += _windVelocity * deltaTime;
|
||||
|
||||
// 密度衰减:核心密度 × (初始半径 / 当前半径)³
|
||||
if (_currentRadius > 0.001f)
|
||||
{
|
||||
var volumeRatio = (float)System.Math.Pow((float)_ammo.InitialRadius / _currentRadius, 3);
|
||||
CoreDensity = (float)_ammo.CoreDensity * volumeRatio;
|
||||
}
|
||||
|
||||
// 更新粒子参数
|
||||
Particles.Opacity = System.Math.Max(0.1f, CoreDensity / (float)_ammo.CoreDensity);
|
||||
Particles.SizeMultiplier = _currentRadius / (float)_ammo.InitialRadius;
|
||||
|
||||
// 消散条件
|
||||
if (_elapsed >= (float)_ammo.MaxDuration || _currentRadius >= (float)_ammo.MaxRadius)
|
||||
{
|
||||
IsDissipated = true;
|
||||
Particles.Opacity = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector3 WindToVector(WindDirection dir)
|
||||
{
|
||||
var angle = dir switch
|
||||
{
|
||||
WindDirection.N => 0f,
|
||||
WindDirection.NE => 45f,
|
||||
WindDirection.E => 90f,
|
||||
WindDirection.SE => 135f,
|
||||
WindDirection.S => 180f,
|
||||
WindDirection.SW => 225f,
|
||||
WindDirection.W => 270f,
|
||||
WindDirection.NW => 315f,
|
||||
_ => 0f,
|
||||
};
|
||||
var rad = angle * (float)System.Math.PI / 180f;
|
||||
return new Vector3((float)System.Math.Sin(rad), 0, (float)System.Math.Cos(rad));
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/CounterDrone.Core/Algorithms/ICloudDispersionModel.cs
Normal file
16
src/CounterDrone.Core/Algorithms/ICloudDispersionModel.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
public interface ICloudDispersionModel
|
||||
{
|
||||
void Initialize(AmmunitionSpec ammo, CombatScene env, Vector3 releasePos, float releaseTime);
|
||||
void Tick(float deltaTime, float windSpeed, WindDirection windDir);
|
||||
Vector3 Center { get; }
|
||||
float Radius { get; }
|
||||
float CoreDensity { get; }
|
||||
float EffectiveRadius { get; }
|
||||
ParticleParams Particles { get; }
|
||||
bool IsDissipated { get; }
|
||||
}
|
||||
}
|
||||
12
src/CounterDrone.Core/Algorithms/IDamageModel.cs
Normal file
12
src/CounterDrone.Core/Algorithms/IDamageModel.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
public interface IDamageModel
|
||||
{
|
||||
float CalculateDamage(TargetType droneType, PowerType powerType,
|
||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime);
|
||||
|
||||
DamageStage GetDamageStage(float accumulatedDamage);
|
||||
}
|
||||
}
|
||||
9
src/CounterDrone.Core/Algorithms/IDefenseAdvisor.cs
Normal file
9
src/CounterDrone.Core/Algorithms/IDefenseAdvisor.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
public interface IDefenseAdvisor
|
||||
{
|
||||
DefenseRecommendation Recommend(ThreatProfile threat);
|
||||
}
|
||||
}
|
||||
31
src/CounterDrone.Core/Algorithms/InertGasDamageModel.cs
Normal file
31
src/CounterDrone.Core/Algorithms/InertGasDamageModel.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
/// <summary>吸入式灭火 — 阈值型:密度达标后线性累积</summary>
|
||||
public class InertGasDamageModel : IDamageModel
|
||||
{
|
||||
private const float EffectiveThreshold = 0.05f; // 有效浓度阈值
|
||||
private const float DamageRate = 0.15f; // 每秒毁伤率
|
||||
|
||||
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
||||
{
|
||||
if (aerosolType != AerosolType.InertGas) return 0f;
|
||||
if (cloudDensity < EffectiveThreshold) return 0f;
|
||||
|
||||
// 活塞发动机对惰性气体最敏感
|
||||
var sensitivity = powerType == PowerType.Piston ? 1.5f : 1.0f;
|
||||
return DamageRate * sensitivity * deltaTime;
|
||||
}
|
||||
|
||||
public DamageStage GetDamageStage(float accumulatedDamage)
|
||||
{
|
||||
if (accumulatedDamage >= 1.0f) return DamageStage.Destroyed;
|
||||
if (accumulatedDamage >= 0.6f) return DamageStage.AttitudeLoss;
|
||||
if (accumulatedDamage >= 0.25f) return DamageStage.EngineAnomaly;
|
||||
return DamageStage.Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
|
||||
@ -210,12 +211,13 @@ namespace CounterDrone.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
private static int _counter;
|
||||
|
||||
public static string GenerateTaskNumber()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var datePart = now.ToString("yyyyMMdd");
|
||||
var random = new Random();
|
||||
var seq = random.Next(1, 1000);
|
||||
var seq = Interlocked.Increment(ref _counter);
|
||||
return $"SIM-{datePart}-{seq:D3}";
|
||||
}
|
||||
}
|
||||
|
||||
49
test/unit/CounterDrone.Core.Tests/AlgorithmFactoryTests.cs
Normal file
49
test/unit/CounterDrone.Core.Tests/AlgorithmFactoryTests.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using CounterDrone.Core.Algorithms;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class AlgorithmFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
public void Create_ReturnsDefaultImplementations()
|
||||
{
|
||||
var dispersion = AlgorithmFactory.Create<ICloudDispersionModel>();
|
||||
Assert.IsType<GaussianPuffDispersion>(dispersion);
|
||||
|
||||
var damage = AlgorithmFactory.Create<IDamageModel>();
|
||||
Assert.IsType<DamageModelRouter>(damage);
|
||||
|
||||
var advisor = AlgorithmFactory.Create<IDefenseAdvisor>();
|
||||
Assert.IsType<DefaultDefenseAdvisor>(advisor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Register_ReplacesImplementation()
|
||||
{
|
||||
// 替换为自定义实现
|
||||
AlgorithmFactory.Register<ICloudDispersionModel, MockDispersion>();
|
||||
|
||||
var instance = AlgorithmFactory.Create<ICloudDispersionModel>();
|
||||
Assert.IsType<MockDispersion>(instance);
|
||||
|
||||
// 还原
|
||||
AlgorithmFactory.Register<ICloudDispersionModel, GaussianPuffDispersion>();
|
||||
}
|
||||
}
|
||||
|
||||
public class MockDispersion : ICloudDispersionModel
|
||||
{
|
||||
public Vector3 Center => new();
|
||||
public float Radius => 100f;
|
||||
public float CoreDensity => 1f;
|
||||
public float EffectiveRadius => 100f;
|
||||
public ParticleParams Particles => new();
|
||||
public bool IsDissipated => false;
|
||||
|
||||
public void Initialize(CounterDrone.Core.Models.AmmunitionSpec ammo,
|
||||
CounterDrone.Core.Models.CombatScene env, Vector3 releasePos, float releaseTime) { }
|
||||
public void Tick(float deltaTime, float windSpeed,
|
||||
CounterDrone.Core.Models.WindDirection windDir) { }
|
||||
}
|
||||
}
|
||||
129
test/unit/CounterDrone.Core.Tests/DamageModelTests.cs
Normal file
129
test/unit/CounterDrone.Core.Tests/DamageModelTests.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using CounterDrone.Core.Algorithms;
|
||||
using CounterDrone.Core.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class DamageModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void InertGas_PistonEngine_TakesExtraDamage()
|
||||
{
|
||||
var model = new InertGasDamageModel();
|
||||
|
||||
// 活塞发动机暴露于惰性气体
|
||||
var dmg = model.CalculateDamage(TargetType.Piston, PowerType.Piston,
|
||||
AerosolType.InertGas, cloudDensity: 0.1f, exposureTime: 2f, deltaTime: 1f);
|
||||
|
||||
Assert.True(dmg > 0.1f); // 高于基础速率(0.15 × 1.5 = 0.225)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InertGas_BelowThreshold_NoDamage()
|
||||
{
|
||||
var model = new InertGasDamageModel();
|
||||
|
||||
var dmg = model.CalculateDamage(TargetType.Piston, PowerType.Piston,
|
||||
AerosolType.InertGas, cloudDensity: 0.01f, exposureTime: 2f, deltaTime: 1f);
|
||||
|
||||
Assert.Equal(0f, dmg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InertGas_WrongAerosolType_NoDamage()
|
||||
{
|
||||
var model = new InertGasDamageModel();
|
||||
|
||||
var dmg = model.CalculateDamage(TargetType.Piston, PowerType.Piston,
|
||||
AerosolType.ActiveMaterial, cloudDensity: 0.1f, exposureTime: 2f, deltaTime: 1f);
|
||||
|
||||
Assert.Equal(0f, dmg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveMaterial_TriggersBurst()
|
||||
{
|
||||
var model = new ActiveMaterialDamageModel();
|
||||
|
||||
var dmg1 = model.CalculateDamage(TargetType.FixedWing, PowerType.Jet,
|
||||
AerosolType.ActiveMaterial, cloudDensity: 0.2f, exposureTime: 1f, deltaTime: 1f);
|
||||
|
||||
Assert.True(dmg1 >= 0.5f); // 爆发伤害
|
||||
|
||||
var dmg2 = model.CalculateDamage(TargetType.FixedWing, PowerType.Jet,
|
||||
AerosolType.ActiveMaterial, cloudDensity: 0.2f, exposureTime: 2f, deltaTime: 1f);
|
||||
|
||||
Assert.True(dmg2 < 0.1f); // 后续余伤很小
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveMaterial_JetEngine_ExtraSensitivity()
|
||||
{
|
||||
var model = new ActiveMaterialDamageModel();
|
||||
|
||||
var dmg = model.CalculateDamage(TargetType.FixedWing, PowerType.Jet,
|
||||
AerosolType.ActiveMaterial, cloudDensity: 0.2f, exposureTime: 0f, deltaTime: 0.1f);
|
||||
|
||||
// 喷气发动机:0.6 × 1.3 = 0.78
|
||||
Assert.True(dmg > 0.7f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveFuel_ExponentialGrowth()
|
||||
{
|
||||
var model = new ActiveFuelDamageModel();
|
||||
|
||||
var dmg1 = model.CalculateDamage(TargetType.HighSpeed, PowerType.Jet,
|
||||
AerosolType.ActiveFuel, cloudDensity: 0.1f, exposureTime: 1f, deltaTime: 1f);
|
||||
|
||||
var dmg2 = model.CalculateDamage(TargetType.HighSpeed, PowerType.Jet,
|
||||
AerosolType.ActiveFuel, cloudDensity: 0.1f, exposureTime: 5f, deltaTime: 1f);
|
||||
|
||||
// 5 秒后的伤害应显著大于 1 秒后
|
||||
Assert.True(dmg2 > dmg1 * 2f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveFuel_HighSpeedTarget_ExtraSensitive()
|
||||
{
|
||||
var model = new ActiveFuelDamageModel();
|
||||
|
||||
var dmg = model.CalculateDamage(TargetType.HighSpeed, PowerType.Jet,
|
||||
AerosolType.ActiveFuel, cloudDensity: 0.1f, exposureTime: 1f, deltaTime: 1f);
|
||||
|
||||
Assert.True(dmg > 0.02f); // 基础速率 × 灵敏度
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DamageStage_Progression()
|
||||
{
|
||||
var model = new InertGasDamageModel();
|
||||
|
||||
Assert.Equal(DamageStage.Normal, model.GetDamageStage(0.1f));
|
||||
Assert.Equal(DamageStage.EngineAnomaly, model.GetDamageStage(0.3f));
|
||||
Assert.Equal(DamageStage.AttitudeLoss, model.GetDamageStage(0.7f));
|
||||
Assert.Equal(DamageStage.Destroyed, model.GetDamageStage(1.0f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Router_RoutesToCorrectModel()
|
||||
{
|
||||
var router = new DamageModelRouter();
|
||||
|
||||
// 惰性气体
|
||||
var dmg1 = router.CalculateDamage(TargetType.Piston, PowerType.Piston,
|
||||
AerosolType.InertGas, 0.1f, 1f, 1f);
|
||||
Assert.True(dmg1 > 0f);
|
||||
|
||||
// 活性材料
|
||||
var dmg2 = router.CalculateDamage(TargetType.FixedWing, PowerType.Jet,
|
||||
AerosolType.ActiveMaterial, 0.2f, 0f, 1f);
|
||||
Assert.True(dmg2 > 0f);
|
||||
|
||||
// 活性燃料
|
||||
var dmg3 = router.CalculateDamage(TargetType.HighSpeed, PowerType.Jet,
|
||||
AerosolType.ActiveFuel, 0.1f, 1f, 1f);
|
||||
Assert.True(dmg3 > 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
125
test/unit/CounterDrone.Core.Tests/DefenseAdvisorTests.cs
Normal file
125
test/unit/CounterDrone.Core.Tests/DefenseAdvisorTests.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Algorithms;
|
||||
using CounterDrone.Core.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class DefenseAdvisorTests
|
||||
{
|
||||
private ThreatProfile CreateSimpleThreat(PowerType powerType = PowerType.Piston)
|
||||
{
|
||||
return new ThreatProfile
|
||||
{
|
||||
Environment = new CombatScene
|
||||
{
|
||||
WindSpeed = 5.0,
|
||||
WindDirection = (int)WindDirection.E,
|
||||
},
|
||||
Targets = new List<TargetConfig>
|
||||
{
|
||||
new TargetConfig
|
||||
{
|
||||
TargetType = (int)TargetType.Piston,
|
||||
PowerType = (int)powerType,
|
||||
Quantity = 3,
|
||||
TypicalSpeed = 120.0,
|
||||
TypicalAltitude = 500.0,
|
||||
},
|
||||
},
|
||||
Route = new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||
Waypoints = new List<Waypoint>
|
||||
{
|
||||
new Waypoint { PosX = 0, PosY = 0, PosZ = 100, Altitude = 500, Speed = 120 },
|
||||
new Waypoint { PosX = 10000, PosY = 0, PosZ = 100, Altitude = 500, Speed = 120 },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Recommend_PistonEngine_RecommendsInertGas()
|
||||
{
|
||||
var advisor = new DefaultDefenseAdvisor();
|
||||
var result = advisor.Recommend(CreateSimpleThreat(PowerType.Piston));
|
||||
|
||||
Assert.Equal(AerosolType.InertGas, result.Best.RecommendedAerosolType);
|
||||
Assert.Contains("惰性气体", result.Best.AerosolRationale);
|
||||
Assert.True(result.Best.InterceptProbability > 0f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Recommend_JetEngine_RecommendsActiveMaterial()
|
||||
{
|
||||
var advisor = new DefaultDefenseAdvisor();
|
||||
var result = advisor.Recommend(CreateSimpleThreat(PowerType.Jet));
|
||||
|
||||
Assert.Equal(AerosolType.ActiveMaterial, result.Best.RecommendedAerosolType);
|
||||
Assert.Contains("活性材料", result.Best.AerosolRationale);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Recommend_ElectricEngine_RecommendsInertGas()
|
||||
{
|
||||
var advisor = new DefaultDefenseAdvisor();
|
||||
var result = advisor.Recommend(CreateSimpleThreat(PowerType.Electric));
|
||||
|
||||
Assert.Equal(AerosolType.InertGas, result.Best.RecommendedAerosolType);
|
||||
Assert.Contains("惰性气体", result.Best.AerosolRationale);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Recommend_ProducesBestAndCritical()
|
||||
{
|
||||
var advisor = new DefaultDefenseAdvisor();
|
||||
var result = advisor.Recommend(CreateSimpleThreat());
|
||||
|
||||
Assert.NotNull(result.Best);
|
||||
Assert.NotNull(result.Critical);
|
||||
Assert.True(result.Best.InterceptProbability >= result.Critical.InterceptProbability,
|
||||
"最佳值拦截概率应 ≥ 最危险值");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Recommend_CloudPosition_OnRoute()
|
||||
{
|
||||
var advisor = new DefaultDefenseAdvisor();
|
||||
var result = advisor.Recommend(CreateSimpleThreat());
|
||||
|
||||
// 最佳位置应在航路中点附近 (5000, 0, 100)
|
||||
var pos = result.Best.RecommendedCloud;
|
||||
Assert.True(pos.PositionX > 1000 && pos.PositionX < 9000,
|
||||
$"PositionX={pos.PositionX} 应在航路范围内");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Recommend_ReturnsPlatforms()
|
||||
{
|
||||
var advisor = new DefaultDefenseAdvisor();
|
||||
var result = advisor.Recommend(CreateSimpleThreat());
|
||||
|
||||
Assert.NotEmpty(result.Best.Platforms);
|
||||
Assert.Equal(PlatformType.GroundBased, result.Best.Platforms[0].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Recommend_ReturnsDetection()
|
||||
{
|
||||
var advisor = new DefaultDefenseAdvisor();
|
||||
var result = advisor.Recommend(CreateSimpleThreat());
|
||||
|
||||
Assert.NotEmpty(result.Best.Detections);
|
||||
Assert.True(result.Best.Detections[0].DetectionRadius > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Recommend_MarksSourceAsAlgorithm()
|
||||
{
|
||||
var advisor = new DefaultDefenseAdvisor();
|
||||
var result = advisor.Recommend(CreateSimpleThreat());
|
||||
|
||||
Assert.Equal("Algorithm", result.Best.RecommendedCloud.Source);
|
||||
Assert.Equal((int)PositionMode.AlgorithmRecommended,
|
||||
result.Best.RecommendedCloud.PositionMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs
Normal file
129
test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CounterDrone.Core.Algorithms;
|
||||
using CounterDrone.Core.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class DispersionModelTests
|
||||
{
|
||||
private AmmunitionSpec CreateTestAmmo()
|
||||
{
|
||||
return new AmmunitionSpec
|
||||
{
|
||||
InitialRadius = 50.0,
|
||||
InitialVolume = 500000.0,
|
||||
CoreDensity = 1.2,
|
||||
EdgeDensity = 0.1,
|
||||
DispersionRateBase = 5.0,
|
||||
MaxRadius = 200.0,
|
||||
MaxDuration = 60.0,
|
||||
EffectiveConcentration = 0.05,
|
||||
};
|
||||
}
|
||||
|
||||
private CombatScene CreateTestEnv(float windSpeed = 5.0f, WindDirection dir = WindDirection.E)
|
||||
{
|
||||
return new CombatScene
|
||||
{
|
||||
WindSpeed = windSpeed,
|
||||
WindDirection = (int)dir,
|
||||
Temperature = 20,
|
||||
Humidity = 60,
|
||||
Pressure = 1013,
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_SetsProperties()
|
||||
{
|
||||
var model = new GaussianPuffDispersion();
|
||||
var ammo = CreateTestAmmo();
|
||||
var env = CreateTestEnv();
|
||||
|
||||
model.Initialize(ammo, env, new Vector3(1000, 0, 300), 0f);
|
||||
|
||||
Assert.Equal(50f, model.Radius);
|
||||
Assert.Equal(1.2f, model.CoreDensity);
|
||||
Assert.False(model.IsDissipated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_ExpandsRadius()
|
||||
{
|
||||
var model = new GaussianPuffDispersion();
|
||||
model.Initialize(CreateTestAmmo(), CreateTestEnv(), new Vector3(0, 0, 0), 0f);
|
||||
|
||||
// 5 秒后
|
||||
model.Tick(5f, 5f, WindDirection.E);
|
||||
// 初始 50 + 扩散速率 5 × 5s = 75
|
||||
Assert.True(model.Radius > 55f && model.Radius < 80f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_WindDrift_MovesCenter()
|
||||
{
|
||||
var model = new GaussianPuffDispersion();
|
||||
model.Initialize(CreateTestAmmo(), CreateTestEnv(10f, WindDirection.E), new Vector3(0, 0, 0), 0f);
|
||||
|
||||
model.Tick(10f, 10f, WindDirection.E);
|
||||
|
||||
// 东风 10 m/s × 10s = 100m,东是 +X
|
||||
Assert.True(model.Center.X > 80f);
|
||||
Assert.True(model.Center.Z < 20f); // 纯东向,Z 不应有大偏移
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_DensityDecays()
|
||||
{
|
||||
var model = new GaussianPuffDispersion();
|
||||
model.Initialize(CreateTestAmmo(), CreateTestEnv(0f, WindDirection.N), new Vector3(0, 0, 0), 0f);
|
||||
|
||||
model.Tick(20f, 0f, WindDirection.N);
|
||||
|
||||
// 半径膨胀后密度应下降
|
||||
Assert.True(model.CoreDensity < 1.2f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_DissipatesAfterMaxDuration()
|
||||
{
|
||||
var model = new GaussianPuffDispersion();
|
||||
var ammo = CreateTestAmmo();
|
||||
ammo.MaxDuration = 5.0; // 5秒消散
|
||||
model.Initialize(ammo, CreateTestEnv(), new Vector3(0, 0, 0), 0f);
|
||||
|
||||
model.Tick(6f, 0f, WindDirection.N);
|
||||
|
||||
Assert.True(model.IsDissipated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_DissipatesAtMaxRadius()
|
||||
{
|
||||
var model = new GaussianPuffDispersion();
|
||||
var ammo = CreateTestAmmo();
|
||||
ammo.MaxRadius = 60.0;
|
||||
ammo.DispersionRateBase = 100.0; // 快速膨胀
|
||||
model.Initialize(ammo, CreateTestEnv(), new Vector3(0, 0, 0), 0f);
|
||||
|
||||
model.Tick(1f, 0f, WindDirection.N);
|
||||
|
||||
Assert.True(model.IsDissipated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParticleOpacity_DecaysWithDensity()
|
||||
{
|
||||
var model = new GaussianPuffDispersion();
|
||||
model.Initialize(CreateTestAmmo(), CreateTestEnv(0f, WindDirection.N), new Vector3(0, 0, 0), 0f);
|
||||
|
||||
Assert.Equal(1.0f, model.Particles.Opacity, 0.01f);
|
||||
|
||||
model.Tick(30f, 0f, WindDirection.N);
|
||||
|
||||
Assert.True(model.Particles.Opacity < 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user