From 6a184d9bf893062a58d6f03e98c281e124a1f4be Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Fri, 12 Jun 2026 11:03:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E8=8D=90=E7=AE=97=E6=B3=95ComputeEffe?= =?UTF-8?q?ctiveRadius=E7=94=A8=E9=AB=98=E6=96=AF=E5=85=AC=E5=BC=8F?= =?UTF-8?q?=EF=BC=8CBuildFireSchedule=E5=8E=BB=E6=8E=89=E7=AE=97=E6=B3=95?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C129=E6=B5=8B=E8=AF=95=E9=80=9A?= =?UTF-8?q?=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Algorithms/DefaultDefenseAdvisor.cs | 27 +++++++- .../Simulation/SimulationEngine.cs | 2 +- .../Scripts/Managers/ManagerVerification.cs | 2 +- .../Scripts/Managers/SimulationBootstrap.cs | 2 +- .../Scripts/Managers/SimulationRunner.cs | 40 ++++-------- .../CounterDrone.Core.Tests/EdgeCaseTests.cs | 12 +--- .../FullPipelineTests.cs | 64 ++++--------------- .../SimulationEngineTests.cs | 13 +--- 8 files changed, 52 insertions(+), 110 deletions(-) diff --git a/src/CounterDrone.Core/Algorithms/DefaultDefenseAdvisor.cs b/src/CounterDrone.Core/Algorithms/DefaultDefenseAdvisor.cs index 7212912..3707f96 100644 --- a/src/CounterDrone.Core/Algorithms/DefaultDefenseAdvisor.cs +++ b/src/CounterDrone.Core/Algorithms/DefaultDefenseAdvisor.cs @@ -87,10 +87,10 @@ namespace CounterDrone.Core.Algorithms var r0 = 3.3f * (float)Math.Pow(Math.Max(0.01, (float)ammo.BurstChargeKg), 0.32); var k = (float)ammo.TurbulentExpansionK; var maxDur = (float)ammo.MaxDuration; - // 无人机到达中点时云团已膨胀了 halfTime 秒 var halfTime = totalFlightTime / 2f; - var effectiveR = r0 + k * (float)Math.Sqrt(Math.Min(halfTime, 30f)); // 不超过 Phase 2 - if (halfTime > 30f) effectiveR += k * (float)Math.Sqrt(halfTime - 30f) * 0.3f; // Phase 3 增速变慢 + var windSpeed = (float)threat.Environment.WindSpeed; + var weather = (WeatherType)threat.Environment.WeatherType; + var effectiveR = ComputeEffectiveRadius(r0, k, halfTime, windSpeed, weather, ammo); var crossTime = (2f * effectiveR) / avgSpeed; var neededExposure = aerosolType == AerosolType.ActiveMaterial ? 2f : 6f; @@ -188,5 +188,26 @@ namespace CounterDrone.Core.Algorithms } private static Vector3 ToV3(Waypoint wp) => new((float)wp.PosX, (float)wp.PosY, (float)wp.PosZ); + + /// 计算云团在目标时刻的有效半径 + private static float ComputeEffectiveRadius(float r0, float k, float halfTime, + float windSpeed, WeatherType weather, AmmunitionSpec ammo) + { + // Phase 2 结束时的半径 + var rPhase2 = r0 + k * (float)Math.Sqrt(Math.Min(halfTime, 30f)); + if (halfTime <= 30f) return rPhase2; + + // Phase 3:用高斯扩散公式计算浓度场下的有效半径 + var x = Math.Max(1f, windSpeed * (halfTime - 30f)); + var cls = Kinematics.GetStabilityClass(weather, windSpeed); + var sY = Kinematics.SigmaY(cls, x); + var sZ = Kinematics.SigmaZ(cls, x); + var peakC = Kinematics.GaussianPeakConcentration((float)ammo.SourceStrength, sY, sZ); + var threshold = (float)ammo.EffectiveConcentration; + if (peakC <= threshold) return rPhase2; + + var sigma = (sY + sZ) / 2f; + return rPhase2 + sigma * (float)Math.Sqrt(2f * Math.Log(peakC / threshold)); + } } } diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs index 3a65d10..e72d02f 100644 --- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs +++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs @@ -89,7 +89,7 @@ namespace CounterDrone.Core.Simulation { _ammoSpec = ammoDb.Table() .FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType) - ?? new AmmunitionSpec { InitialRadius = 50, CoreDensity = 1.0f, DispersionRateBase = 5, MaxRadius = 200, MaxDuration = 60 }; + ?? new AmmunitionSpec { InitialRadius = 50, CoreDensity = 1.0f, SourceStrength = 1.0, MaxRadius = 200, MaxDuration = 60 }; } // 无人机 diff --git a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs index 1a9d7ef..6aec82d 100644 --- a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs +++ b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs @@ -49,7 +49,7 @@ namespace CounterDrone.Unity db.Insert(new AmmunitionSpec { AerosolType = (int)AerosolType.InertGas, InitialRadius = 50, - CoreDensity = 1.0, DispersionRateBase = 5, MaxRadius = 500, + CoreDensity = 1.0, SourceStrength = 50.0, DispersionRateBase = 3.0, MaxRadius = 500, MaxDuration = 120, EffectiveConcentration = 0.05, }); var ammoCatalog = db.Table().ToList(); diff --git a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs index a71493d..f3c3032 100644 --- a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs +++ b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs @@ -37,7 +37,7 @@ namespace CounterDrone.Unity db.Insert(new AmmunitionSpec { AerosolType = (int)AerosolType.InertGas, Name = "惰性气体弹", - InitialRadius = 50, CoreDensity = 1.0, DispersionRateBase = 5, + InitialRadius = 50, CoreDensity = 1.0, SourceStrength = 50.0, DispersionRateBase = 3.0, MaxRadius = 500, MaxDuration = 120, EffectiveConcentration = 0.05, }); diff --git a/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs b/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs index 3f204cc..7bddc70 100644 --- a/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs +++ b/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs @@ -186,48 +186,30 @@ namespace CounterDrone.Unity var cloud = detail.Cloud; if (!cloud.RecommendedTiming.HasValue || cloud.RecommendedTiming <= 0) return schedule; - var platforms = detail.Equipment - .FindAll(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform); + // 每个发射平台打一发,目标云团位置(平台已由推荐算法部署到最佳位置) + var platforms = detail.Equipment.FindAll(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform); if (platforms.Count == 0) return schedule; - var ammo = ammoCatalog.Find(a => a.AerosolType == cloud.AerosolType); - if (ammo == null) return schedule; - - var target = detail.Targets[0]; - var avgSpeed = (float)target.TypicalSpeed / 3.6f; - var neededExposure = cloud.AerosolType == (int)AerosolType.ActiveMaterial ? 2f : 6f; - var spacing = (float)ammo.InitialRadius * 1.5f; - var diameter = 2f * (float)ammo.InitialRadius; - var roundsNeeded = Mathf.Max(1, Mathf.CeilToInt((neededExposure * avgSpeed - diameter) / spacing) + 1); - - var p0 = platforms[0]; - var mv = (float)(p0.MuzzleVelocity ?? 800); var releaseAlt = (float)cloud.DisperseHeight; + if (releaseAlt < 10) releaseAlt = 300f; - for (int r = 0; r < roundsNeeded; r++) + for (int i = 0; i < platforms.Count; i++) { - var offset = (r - (roundsNeeded - 1) / 2f) * spacing; - var tx = (float)cloud.PositionX + offset; var ty = (float)cloud.PositionY; var tz = (float)cloud.PositionZ; - var dx = tx - (float)p0.PositionX; var dz = tz - (float)p0.PositionZ; + var p = platforms[i]; + var mv = (float)(p.MuzzleVelocity ?? 800); + var tx = (float)cloud.PositionX; var ty = (float)cloud.PositionY; var tz = (float)cloud.PositionZ; + var dx = tx - (float)p.PositionX; var dz = tz - (float)p.PositionZ; var dist = Mathf.Sqrt(dx * dx + dz * dz); - var angle = Kinematics.CalculateLaunchAngle(dist, mv, releaseAlt); var sinA = Mathf.Sin(angle); var tUp = mv * sinA / 9.81f; var peakH = mv * mv * sinA * sinA / (2f * 9.81f); var dropH = Mathf.Max(0, peakH - releaseAlt); - var flightTime = tUp + Mathf.Sqrt(2f * dropH / 9.81f); - - var fireTime = (float)cloud.RecommendedTiming.Value - flightTime; + var ft = tUp + Mathf.Sqrt(2f * dropH / 9.81f); + var fireTime = (float)cloud.RecommendedTiming.Value - ft; if (fireTime < 0) fireTime = 0.1f; - - schedule.Add(new FireEvent - { - FireTime = fireTime, PlatformIndex = r % platforms.Count, - TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = mv, - }); + schedule.Add(new FireEvent { FireTime = fireTime, PlatformIndex = i, TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = mv }); } - return schedule; } } diff --git a/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs b/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs index 954b18b..7482d10 100644 --- a/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs +++ b/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs @@ -25,17 +25,7 @@ namespace CounterDrone.Core.Tests var paths = new TestPathProvider(_testDir); var dbm = new DatabaseManager(paths); _db = dbm.OpenMainDb(); - - _db.Insert(new AmmunitionSpec - { - AerosolType = (int)AerosolType.InertGas, - InitialRadius = 50, - CoreDensity = 1.0, - SourceStrength = 50.0, DispersionRateBase = 3.0, - MaxRadius = 500, - MaxDuration = 120, - EffectiveConcentration = 0.05, - }); + foreach (var a in DefaultAmmunition.GetAll()) _db.Insert(a); _scenario = new ScenarioService( new SimTaskRepository(_db), new CombatSceneRepository(_db), diff --git a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs index e36c088..914de85 100644 --- a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs +++ b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs @@ -21,7 +21,6 @@ namespace CounterDrone.Core.Tests private readonly FrameDataStore _frameStore; private readonly SQLiteConnection _mainDb; private List _ammoCatalog; - private DefenseRecommendation _lastRecommendation; private string _taskId = string.Empty; public FullPipelineTests() @@ -31,44 +30,10 @@ namespace CounterDrone.Core.Tests var dbm = new DatabaseManager(_paths); _mainDb = dbm.OpenMainDb(); - _mainDb.Insert(new AmmunitionSpec - { - Id = "ammo-inert", - AerosolType = (int)AerosolType.InertGas, - Name = "惰性气体弹", - InitialRadius = 50, - CoreDensity = 1.0, - SourceStrength = 500.0, - MaxRadius = 500, - MaxDuration = 120, - EffectiveConcentration = 0.05, - }); - _mainDb.Insert(new AmmunitionSpec - { - Id = "ammo-active", - AerosolType = (int)AerosolType.ActiveMaterial, - Name = "活性材料弹", - InitialRadius = 30, - CoreDensity = 2.0, - SourceStrength = 500.0, - MaxRadius = 400, - MaxDuration = 90, - EffectiveConcentration = 0.1, - }); - _mainDb.Insert(new AmmunitionSpec - { - Id = "ammo-fuel", - AerosolType = (int)AerosolType.ActiveFuel, - Name = "活性燃料弹", - InitialRadius = 40, - CoreDensity = 1.5, - SourceStrength = 500.0, - MaxRadius = 450, - MaxDuration = 100, - EffectiveConcentration = 0.03, - }); + // 使用默认弹药规格 + foreach (var a in DefaultAmmunition.GetAll()) _mainDb.Insert(a); - _ammoCatalog = _mainDb.Table().ToList(); + _ammoCatalog = new List(DefaultAmmunition.GetAll()); _scenario = new ScenarioService( new SimTaskRepository(_mainDb), new CombatSceneRepository(_mainDb), @@ -122,30 +87,26 @@ namespace CounterDrone.Core.Tests .ToList(); if (platforms.Count == 0) return schedule; - // 直接从推荐方案读取齐射参数 - var roundsNeeded = _lastRecommendation.Best.SalvoRounds; - var spacing = _lastRecommendation.Best.SalvoSpacing; - - var p = platforms[0]; - var muzzleV = (float)(p.MuzzleVelocity ?? 800); + // 每个部署平台打一发(平台数量由推荐算法确定) var releaseAlt = (float)cloud.DisperseHeight; if (releaseAlt < 10) releaseAlt = 300f; - for (int r = 0; r < roundsNeeded; r++) + for (int i = 0; i < platforms.Count; i++) { - var offset = (r - (roundsNeeded - 1) / 2f) * spacing; - var tx = (float)cloud.PositionX + offset; var ty = (float)cloud.PositionY; var tz = (float)cloud.PositionZ; + var p = platforms[i]; + var mv = (float)(p.MuzzleVelocity ?? 800); + var tx = (float)cloud.PositionX; var ty = (float)cloud.PositionY; var tz = (float)cloud.PositionZ; var dx = tx - (float)p.PositionX; var dz = tz - (float)p.PositionZ; var dist = (float)Math.Sqrt(dx * dx + dz * dz); - var angle = Kinematics.CalculateLaunchAngle(dist, muzzleV, releaseAlt); + var angle = Kinematics.CalculateLaunchAngle(dist, mv, releaseAlt); var sinA = (float)Math.Sin(angle); - var tUp = muzzleV * sinA / 9.81f; - var peakH = muzzleV * muzzleV * sinA * sinA / (2f * 9.81f); + var tUp = mv * sinA / 9.81f; + var peakH = mv * mv * sinA * sinA / (2f * 9.81f); var dropH = Math.Max(0, peakH - releaseAlt); var ft = tUp + (float)Math.Sqrt(2f * dropH / 9.81f); var fireTime = (float)cloud.RecommendedTiming!.Value - ft; if (fireTime < 0) fireTime = 0.1f; - schedule.Add(new FireEvent { FireTime = fireTime, PlatformIndex = r % platforms.Count, TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = muzzleV }); + schedule.Add(new FireEvent { FireTime = fireTime, PlatformIndex = i, TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = mv }); } return schedule; } @@ -192,7 +153,6 @@ namespace CounterDrone.Core.Tests }); _scenario.SaveDeployment(_taskId, equips); - _lastRecommendation = rec; return rec; } diff --git a/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs b/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs index 15c5b64..741636b 100644 --- a/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs +++ b/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs @@ -27,18 +27,7 @@ namespace CounterDrone.Core.Tests var paths = new TestPathProvider(_testDir); var dbManager = new DatabaseManager(paths); _mainDb = dbManager.OpenMainDb(); - - _mainDb.Insert(new AmmunitionSpec - { - AerosolType = (int)AerosolType.InertGas, - Name = "Test Ammo", - InitialRadius = 50, - CoreDensity = 1.0, - SourceStrength = 50.0, DispersionRateBase = 3.0, - MaxRadius = 500, - MaxDuration = 120, - EffectiveConcentration = 0.05, - }); + foreach (var a in DefaultAmmunition.GetAll()) _mainDb.Insert(a); _scenarioService = new ScenarioService( new SimTaskRepository(_mainDb), new CombatSceneRepository(_mainDb),