diff --git a/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs b/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs index 00112d0..6d53832 100644 --- a/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs +++ b/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs @@ -131,7 +131,8 @@ namespace CounterDrone.Core.Algorithms var ammo = _ammoCatalog.First(a => a.AerosolType == (int)neededAmmo); // 单机需求 - int singleNeeded = CalcRoundsNeeded(threat, ammo, env, false); + var (effectiveRadius, expansionTime, turbulentRadius) = ComputeEffectiveRadius(threat, ammo, env); + int singleNeeded = CalcRoundsNeeded(threat, ammo, env, false, turbulentRadius); // 横向编队:每种 Width = Quantity 个独立车道 int yLanes = 1; @@ -144,8 +145,7 @@ namespace CounterDrone.Core.Algorithms int totalRoundsNeeded = singleNeeded * yLanes; // 逐单元分配:每个单元锁定一个 Y 车道 - var (effR2, _) = ComputeEffectiveRadius(threat, ammo, env); - float spacing = effR2 * 1.5f; + float spacing = 2f * turbulentRadius; int[] laneNeeded = new int[yLanes]; for (int l = 0; l < yLanes; l++) laneNeeded[l] = singleNeeded; int currentLane = 0; @@ -169,7 +169,7 @@ namespace CounterDrone.Core.Algorithms int yLane = currentLane; var refEvt = GenerateFireEventsAt(threat, c.Unit, c.AmmoType, ammo, env, 0, yLane, yLanes, formationWidth); float unitBaseTime = refEvt[0].FireTime; - float stagger = Kinematics.CloudCoverInterval(effR2 * 2f, (float)threat.Target.TypicalSpeed, c.Unit.ChannelInterval); + float stagger = Kinematics.CloudCoverInterval(turbulentRadius * 2f, (float)threat.Target.TypicalSpeed, c.Unit.ChannelInterval); var fevents = new List(); int unitIdx = fireUnits.IndexOf(c.Unit); @@ -245,7 +245,7 @@ namespace CounterDrone.Core.Algorithms var neededAmmo = MatchAmmo((PowerType)threat.Target.PowerType); var ammo = _ammoCatalog.First(a => a.AerosolType == (int)neededAmmo); - var ammoEff = ComputeEffectiveRadius(threat, ammo, env); + var (ammoEff, _, _) = ComputeEffectiveRadius(threat, ammo, env); foreach (var unit in availableUnits) { @@ -268,7 +268,7 @@ namespace CounterDrone.Core.Algorithms private InterceptCandidate? BuildGroundBasedCandidate(DroneGroup threat, FireUnit unit, AerosolType ammoType, AmmunitionSpec ammo, - (float radius, float expTime) ammoEff, CombatScene env) + float effectiveR, CombatScene env) { var mid = ThreatMidpoint(threat); float dx = mid.X - unit.Position.X; @@ -284,7 +284,7 @@ namespace CounterDrone.Core.Algorithms float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f; float neededExposure = ammoType == AerosolType.ActiveMaterial ? 2f : 6f; - float actualExposure = ammoEff.radius * 2f / avgSpeed; + float actualExposure = effectiveR * 2f / avgSpeed; float prob = Math.Min(0.95f, actualExposure / neededExposure); return new InterceptCandidate @@ -292,7 +292,7 @@ namespace CounterDrone.Core.Algorithms Unit = unit, AmmoType = ammoType, EarliestInterceptTime = interceptTime, - CoverageDuration = ammoEff.radius * 2f, + CoverageDuration = effectiveR * 2f, KillProbability = prob, FlightTime = shellTime, ShellFlightTime = shellTime, @@ -301,7 +301,7 @@ namespace CounterDrone.Core.Algorithms private InterceptCandidate? BuildAirBasedCandidate(DroneGroup threat, FireUnit unit, AerosolType ammoType, AmmunitionSpec ammo, - (float radius, float expTime) ammoEff, CombatScene env) + float effectiveR, CombatScene env) { if (unit.ReleaseAltitude <= 0 || unit.CruiseSpeed <= 0) return null; var mid = ThreatMidpoint(threat); @@ -318,7 +318,7 @@ namespace CounterDrone.Core.Algorithms float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f; float neededExposure = ammoType == AerosolType.ActiveMaterial ? 2f : 6f; - float actualExposure = ammoEff.radius * 2f / avgSpeed; + float actualExposure = effectiveR * 2f / avgSpeed; float prob = Math.Min(0.95f, actualExposure / neededExposure); return new InterceptCandidate @@ -326,7 +326,7 @@ namespace CounterDrone.Core.Algorithms Unit = unit, AmmoType = ammoType, EarliestInterceptTime = interceptTime, - CoverageDuration = ammoEff.radius * 2f, + CoverageDuration = effectiveR * 2f, KillProbability = prob, FlightTime = totalTime, ShellFlightTime = fallTime, @@ -337,7 +337,7 @@ namespace CounterDrone.Core.Algorithms // 弹药计算(使用 AmmunitionSpec + 环境参数) // ═══════════════════════════════════════════════ - private (float effectiveRadius, float expansionTime) ComputeEffectiveRadius( + private (float effectiveRadius, float expansionTime, float rPhase2) ComputeEffectiveRadius( DroneGroup threat, AmmunitionSpec ammo, CombatScene env) { float totalFlightTime = threat.ArrivalTime * 2f; @@ -353,10 +353,10 @@ namespace CounterDrone.Core.Algorithms // Phase 2 湍流膨胀后的半径 float phase2Time = Math.Min(halfTime, 30f); - float rPhase2 = r0 + k * (float)Math.Sqrt(Math.Max(0, phase2Time)); + float turbulentRadius = r0 + k * (float)Math.Sqrt(Math.Max(0, phase2Time)); - float expansionTime = (float)Math.Pow((rPhase2 - r0) / Math.Max(k, 0.01f), 2); - float effectiveR = rPhase2; + float expansionTime = (float)Math.Pow((turbulentRadius - r0) / Math.Max(k, 0.01f), 2); + float effectiveR = turbulentRadius; // Phase 3 高斯扩散 if (halfTime > 30f) @@ -370,19 +370,18 @@ namespace CounterDrone.Core.Algorithms if (peakC > threshold) { float sigma = (sY + sZ) / 2f; - effectiveR = rPhase2 + sigma * (float)Math.Sqrt(2f * Math.Log(peakC / threshold)); + effectiveR = turbulentRadius + sigma * (float)Math.Sqrt(2f * Math.Log(peakC / threshold)); } } - return (effectiveR, expansionTime); + return (effectiveR, expansionTime, turbulentRadius); } private int CalcRoundsNeeded(DroneGroup threat, AmmunitionSpec ammo, - CombatScene env, bool isAirBased) + CombatScene env, bool isAirBased, float turbulentRadius) { float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f; - var (effectiveR, _) = ComputeEffectiveRadius(threat, ammo, env); - float spacing = effectiveR * 1.5f; + float spacing = 2f * turbulentRadius; var aerosolType = (AerosolType)ammo.AerosolType; float neededExposure = aerosolType == AerosolType.ActiveMaterial ? 2f : 6f; @@ -390,14 +389,14 @@ namespace CounterDrone.Core.Algorithms float requiredCoverage = singleCoverage; return Math.Max(1, - (int)Math.Ceiling((requiredCoverage - 2f * effectiveR) / spacing) + 1); + (int)Math.Ceiling((requiredCoverage - 2f * turbulentRadius) / spacing) + 1); } private float ComputeInterceptProbability(DroneGroup threat, AmmunitionSpec ammo, int rounds, CombatScene env) { float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f; - var (effectiveR, _) = ComputeEffectiveRadius(threat, ammo, env); + var (effectiveR, _, _) = ComputeEffectiveRadius(threat, ammo, env); float spacing = effectiveR * 1.5f; float actualCoverage = spacing * (rounds - 1) + 2f * effectiveR; float actualExposure = actualCoverage / avgSpeed; @@ -419,7 +418,7 @@ namespace CounterDrone.Core.Algorithms var events = new List(); var mid = ThreatMidpoint(threat); - var (effectiveR, expansionTime) = ComputeEffectiveRadius(threat, ammo, env); + var (effectiveR, expansionTime, _) = ComputeEffectiveRadius(threat, ammo, env); // 编队 Y 偏移:按车道分布 float laneSpacingZ = yLanes > 1 ? formationWidth / (yLanes - 1) : 0f; diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs index deeaaa7..4d72e74 100644 --- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs +++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs @@ -181,7 +181,7 @@ namespace CounterDrone.Core.Simulation fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight); _munitions.Add(m); OnMunitionLaunched?.Invoke(m); - frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = SimulationTime, SourceId = platform.Id, Description = $"计划发射 {m.Id}" }); + frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = fe.FireTime, SourceId = platform.Id, Description = $"计划发射 {m.Id}" }); } } diff --git a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs index e549f38..c07d39d 100644 --- a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs +++ b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs @@ -187,14 +187,14 @@ namespace CounterDrone.Core.Tests { GroupId = "default", TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston, - Quantity = 10, // 10 架 + Quantity = 5, // 5 架 TypicalSpeed = 200, TypicalAltitude = 500, }); _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Formation, LateralSpacing = 50, - LateralCount = 10, + LateralCount = 5, LongitudinalCount = 1, }, new List @@ -204,7 +204,7 @@ namespace CounterDrone.Core.Tests }); _scenario.SaveDeployment(_taskId, new List { - MakeEquipment(DefaultFireUnits.GetById("ground-light"), AerosolType.InertGas, 10, 5000, 0, 50), + MakeEquipment(DefaultFireUnits.GetById("ground-light"), AerosolType.InertGas, 5, 5000, 0, 50), }); _scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });