| | | 1 | | using System; |
| | | 2 | | using System.Collections.Generic; |
| | | 3 | | using System.Linq; |
| | | 4 | | using CounterDrone.Core.Models; |
| | | 5 | | |
| | | 6 | | namespace CounterDrone.Core.Algorithms |
| | | 7 | | { |
| | | 8 | | public class DefaultDefenseAdvisor : IDefenseAdvisor |
| | | 9 | | { |
| | | 10 | | private readonly List<AmmunitionSpec> _ammoCatalog; |
| | | 11 | | |
| | 16 | 12 | | public DefaultDefenseAdvisor(List<AmmunitionSpec> ammoCatalog) |
| | 16 | 13 | | { |
| | 16 | 14 | | _ammoCatalog = ammoCatalog ?? new List<AmmunitionSpec>(); |
| | 16 | 15 | | } |
| | | 16 | | |
| | 1 | 17 | | private static readonly Dictionary<PowerType, AerosolType> MatchTable = new() |
| | 1 | 18 | | { |
| | 1 | 19 | | { PowerType.Electric, AerosolType.InertGas }, |
| | 1 | 20 | | { PowerType.Piston, AerosolType.InertGas }, |
| | 1 | 21 | | { PowerType.Jet, AerosolType.ActiveMaterial }, |
| | 1 | 22 | | }; |
| | | 23 | | |
| | | 24 | | public DefenseRecommendation Recommend(ThreatProfile threat) |
| | 15 | 25 | | { |
| | 15 | 26 | | var result = new DefenseRecommendation |
| | 15 | 27 | | { |
| | 15 | 28 | | Best = new DefenseSolution(), |
| | 15 | 29 | | Critical = new DefenseSolution(), |
| | 15 | 30 | | }; |
| | | 31 | | |
| | | 32 | | // ===== 输入校验 ===== |
| | 15 | 33 | | if (threat.Targets.Count == 0) |
| | 1 | 34 | | { |
| | 1 | 35 | | result.Best.SummaryRationale = "失败:未配置威胁目标"; |
| | 1 | 36 | | return result; |
| | | 37 | | } |
| | 14 | 38 | | if (threat.Waypoints.Count < 2) |
| | 1 | 39 | | { |
| | 1 | 40 | | result.Best.SummaryRationale = "失败:需要至少两个航路点"; |
| | 1 | 41 | | return result; |
| | | 42 | | } |
| | | 43 | | |
| | 13 | 44 | | var target = threat.Targets[0]; |
| | 13 | 45 | | if (target.TypicalSpeed <= 0) |
| | 0 | 46 | | { |
| | 0 | 47 | | result.Best.SummaryRationale = "失败:目标速度为 0"; |
| | 0 | 48 | | return result; |
| | | 49 | | } |
| | | 50 | | |
| | | 51 | | // ===== Step A:气溶胶选型 ===== |
| | 13 | 52 | | var powerType = (PowerType)target.PowerType; |
| | 13 | 53 | | var aerosolType = MatchTable.TryGetValue(powerType, out var match) |
| | 13 | 54 | | ? match : AerosolType.InertGas; |
| | | 55 | | |
| | 27 | 56 | | var ammo = _ammoCatalog.FirstOrDefault(a => a.AerosolType == (int)aerosolType); |
| | 13 | 57 | | if (ammo == null) |
| | 1 | 58 | | { |
| | 1 | 59 | | result.Best.SummaryRationale = $"失败:弹药库中未找到 {aerosolType} 类型的弹药规格"; |
| | 1 | 60 | | return result; |
| | | 61 | | } |
| | | 62 | | |
| | 12 | 63 | | var rationale = powerType switch |
| | 12 | 64 | | { |
| | 12 | 65 | | PowerType.Electric or PowerType.Piston => |
| | 10 | 66 | | $"{powerType}发动机依赖氧气,推荐惰性气体窒息方案", |
| | 12 | 67 | | PowerType.Jet => |
| | 2 | 68 | | $"{powerType}发动机高温表面可触发活性材料爆燃反应", |
| | 0 | 69 | | _ => "默认推荐惰性气体方案", |
| | 12 | 70 | | }; |
| | | 71 | | |
| | | 72 | | // ===== Step B:时空交汇优化 ===== |
| | 12 | 73 | | var start = ToV3(threat.Waypoints[0]); |
| | 12 | 74 | | var end = ToV3(threat.Waypoints[^1]); |
| | 12 | 75 | | var mid = new Vector3((start.X + end.X) / 2f, (start.Y + end.Y) / 2f, (start.Z + end.Z) / 2f); |
| | 12 | 76 | | var routeLength = start.DistanceTo(end); |
| | 12 | 77 | | var avgSpeed = (float)target.TypicalSpeed / 3.6f; |
| | 12 | 78 | | var totalFlightTime = routeLength / avgSpeed; |
| | | 79 | | |
| | 12 | 80 | | if (routeLength < 100) |
| | 0 | 81 | | { |
| | 0 | 82 | | result.Best.SummaryRationale = "失败:航路太短(<100m)"; |
| | 0 | 83 | | return result; |
| | | 84 | | } |
| | | 85 | | |
| | | 86 | | // 弹药参数 |
| | 12 | 87 | | var initialR = (float)ammo.InitialRadius; |
| | 12 | 88 | | var maxDur = (float)ammo.MaxDuration; |
| | | 89 | | |
| | | 90 | | // 无人机穿过单个云团的时间 |
| | 12 | 91 | | var crossTime = (2f * initialR) / avgSpeed; |
| | 12 | 92 | | var neededExposure = aerosolType == AerosolType.ActiveMaterial ? 2f : 6f; |
| | 12 | 93 | | var spacing = initialR * 1.5f; // 云团间隔 |
| | | 94 | | |
| | | 95 | | // 有效覆盖距离 = 间距×(N-1) + 直径,需 ≥ 所需暴露时间 × 速度 |
| | 12 | 96 | | var requiredCoverage = neededExposure * avgSpeed; |
| | 12 | 97 | | var roundsNeeded = Math.Max(1, |
| | 12 | 98 | | (int)Math.Ceiling((requiredCoverage - 2f * initialR) / spacing) + 1); |
| | | 99 | | |
| | | 100 | | // 实际有效暴露时间 |
| | 12 | 101 | | var actualCoverage = spacing * (roundsNeeded - 1) + 2f * initialR; |
| | 12 | 102 | | var actualExposure = actualCoverage / avgSpeed; |
| | 12 | 103 | | var prob = Math.Min(0.95f, actualExposure / neededExposure); |
| | | 104 | | |
| | | 105 | | // 多轮次云团:沿航路等距分布 |
| | 12 | 106 | | var cloudSalvo = new List<CloudDispersal>(); |
| | 112 | 107 | | for (int i = 0; i < roundsNeeded; i++) |
| | 44 | 108 | | { |
| | 44 | 109 | | var offset = (i - (roundsNeeded - 1) / 2f) * spacing; |
| | 44 | 110 | | cloudSalvo.Add(new CloudDispersal |
| | 44 | 111 | | { |
| | 44 | 112 | | AerosolType = (int)aerosolType, |
| | 44 | 113 | | PositionX = mid.X + offset, |
| | 44 | 114 | | PositionY = mid.Y, |
| | 44 | 115 | | PositionZ = mid.Z, |
| | 44 | 116 | | DisperseHeight = (float)target.TypicalAltitude, |
| | 44 | 117 | | TriggerMode = (int)TriggerMode.Time, |
| | 44 | 118 | | Duration = (int)maxDur, |
| | 44 | 119 | | InitialScale = ammo.InitialVolume, |
| | 44 | 120 | | ReleaseMode = (int)ReleaseMode.Single, |
| | 44 | 121 | | Source = "Algorithm", |
| | 44 | 122 | | PositionMode = (int)PositionMode.AlgorithmRecommended, |
| | 44 | 123 | | RecommendedTiming = totalFlightTime / 2f, |
| | 44 | 124 | | }); |
| | 44 | 125 | | } |
| | | 126 | | |
| | | 127 | | // 平台部署:假设齐射(同时发射),每门炮打一发后冷却 5s |
| | | 128 | | // 需要 roundsNeeded 门炮同时发射 |
| | 12 | 129 | | var gunCount = roundsNeeded; |
| | 12 | 130 | | var platforms = new List<RecommendedPlatform>(); |
| | 112 | 131 | | for (int i = 0; i < gunCount; i++) |
| | 44 | 132 | | platforms.Add(new RecommendedPlatform |
| | 44 | 133 | | { |
| | 44 | 134 | | Type = PlatformType.GroundBased, |
| | 44 | 135 | | Position = new Vector3(mid.X + i * 50, 0, 50), |
| | 44 | 136 | | Quantity = 1, |
| | 44 | 137 | | MunitionCount = 1, |
| | 44 | 138 | | CoverageVolume = (float)ammo.InitialVolume, |
| | 44 | 139 | | Cooldown = 5f, |
| | 44 | 140 | | MuzzleVelocity = 800f, |
| | 44 | 141 | | }); |
| | | 142 | | |
| | 12 | 143 | | result.Best = new DefenseSolution |
| | 12 | 144 | | { |
| | 12 | 145 | | RecommendedAerosolType = aerosolType, |
| | 12 | 146 | | AerosolRationale = rationale, |
| | 12 | 147 | | RecommendedCloud = cloudSalvo[0], |
| | 12 | 148 | | CloudSalvo = cloudSalvo, |
| | 12 | 149 | | Platforms = platforms, |
| | 12 | 150 | | Detections = new List<RecommendedDetection> |
| | 12 | 151 | | { |
| | 12 | 152 | | new RecommendedDetection |
| | 12 | 153 | | { |
| | 12 | 154 | | Position = new Vector3(mid.X, mid.Y, 0), |
| | 12 | 155 | | DetectionRadius = Math.Max(3000f, routeLength * 0.4f), |
| | 12 | 156 | | Quantity = 1, |
| | 12 | 157 | | } |
| | 12 | 158 | | }, |
| | 12 | 159 | | InterceptProbability = prob, |
| | 12 | 160 | | SummaryRationale = $"{aerosolType}方案,{roundsNeeded}发,预计拦截概率 {prob:P0}", |
| | 12 | 161 | | }; |
| | | 162 | | |
| | 12 | 163 | | result.Critical = new DefenseSolution |
| | 12 | 164 | | { |
| | 12 | 165 | | RecommendedAerosolType = aerosolType, |
| | 12 | 166 | | RecommendedCloud = new CloudDispersal |
| | 12 | 167 | | { |
| | 12 | 168 | | AerosolType = (int)aerosolType, |
| | 12 | 169 | | PositionX = start.X + (end.X - start.X) * 0.25f, |
| | 12 | 170 | | PositionY = start.Y, PositionZ = start.Z, |
| | 12 | 171 | | DisperseHeight = (float)target.TypicalAltitude, |
| | 12 | 172 | | Duration = (int)maxDur, |
| | 12 | 173 | | Source = "Algorithm", |
| | 12 | 174 | | PositionMode = (int)PositionMode.AlgorithmRecommended, |
| | 12 | 175 | | RecommendedTiming = totalFlightTime * 0.25f, |
| | 12 | 176 | | }, |
| | 12 | 177 | | Platforms = platforms, |
| | 12 | 178 | | InterceptProbability = prob * 0.3f, |
| | 12 | 179 | | SummaryRationale = $"临界方案:拦截概率仅 {prob * 0.3f:P0}", |
| | 12 | 180 | | }; |
| | | 181 | | |
| | 12 | 182 | | return result; |
| | 15 | 183 | | } |
| | | 184 | | |
| | 24 | 185 | | private static Vector3 ToV3(Waypoint wp) => new((float)wp.PosX, (float)wp.PosY, (float)wp.PosZ); |
| | | 186 | | } |
| | | 187 | | } |