From a5fef98858ee9da4522757eac485cbbe490fea2e Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sat, 13 Jun 2026 16:40:27 +0800 Subject: [PATCH] =?UTF-8?q?wip:=20=E7=A9=BA=E5=9F=BAPlanner=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E2=80=94=E2=80=94=E6=8A=95=E6=94=BE=E7=82=B9=E5=90=8E?= =?UTF-8?q?=E7=A7=BB=E6=8A=B5=E6=B6=88=E8=BD=BD=E5=85=B7=E6=BC=82=E7=A7=BB?= =?UTF-8?q?=EF=BC=9B=E4=BA=91=E5=9B=A2/=E5=8F=91=E5=B0=84=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E7=94=A8=E7=B2=BE=E7=A1=AE=E6=97=B6=E5=88=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 空基 GenerateFireEventsAt 投放点后移 driftX = cruiseSpeed * fallTime - MunitionEntity 记录 launchTime + flightDuration → ArrivalTime 精确计算 - 云团 Initialization 用 munition.ArrivalTime 非 SimulationTime - CloudGenerated 事件 OccurredAt 用精确到达时刻 - PlatformEntity 存储 ExactReleaseTime - 修复缺失 else 导致的空基走地基分支 --- .../Algorithms/DefaultDefensePlanner.cs | 23 +++++++++++++------ .../Algorithms/Kinematics.cs | 8 +++++++ .../Simulation/MunitionEntity.cs | 12 ++++++++++ .../Simulation/PlatformEntity.cs | 7 +++++- .../Simulation/SimulationEngine.cs | 14 ++++++----- .../FullPipelineTests.cs | 2 ++ .../MunitionEntityTests.cs | 8 +++---- 7 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs b/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs index 6d53832..cfd3c9b 100644 --- a/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs +++ b/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs @@ -167,8 +167,6 @@ namespace CounterDrone.Core.Algorithms if (toTake <= 0) continue; 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(turbulentRadius * 2f, (float)threat.Target.TypicalSpeed, c.Unit.ChannelInterval); var fevents = new List(); @@ -177,10 +175,9 @@ namespace CounterDrone.Core.Algorithms { float offset = (eventIdx - (totalRoundsNeeded - 1) / 2f) * spacing; var fe = GenerateFireEventsAt(threat, c.Unit, c.AmmoType, ammo, env, offset, yLane, yLanes, formationWidth); - int baseRoundInLane = singleNeeded - laneNeeded[currentLane]; + // 不覆盖 FireTime:GenerateFireEventsAt 已按偏移位置 + 单元飞行时间精确计算 foreach (var e in fe) { - e.FireTime = unitBaseTime + (baseRoundInLane + ch) * stagger; e.PlatformIndex = unitIdx * c.Unit.TotalChannels + ch; } fevents.AddRange(fe); @@ -444,17 +441,29 @@ namespace CounterDrone.Core.Algorithms float fireTime; if (unit.Type == PlatformType.AirBased) - { if (unit.ReleaseAltitude <= 0 || unit.CruiseSpeed <= 0) throw new InvalidOperationException($"空基单元 {unit.Id}: ReleaseAltitude={unit.ReleaseAltitude}, CruiseSpeed={unit.CruiseSpeed} 必须>0"); float releaseAlt = unit.ReleaseAltitude; float cruiseSpd = unit.CruiseSpeed; - float flightDist = Kinematics.Distance3D( + float fallTime = Kinematics.AirDropFallTime(releaseAlt, (float)threat.Target.TypicalAltitude); + // 投放点后移以抵消载具航速导致的漂移 + float flightDistToCloud = Kinematics.Distance3D( unit.Position.X, unit.Position.Y, unit.Position.Z, tx, releaseAlt, tz); + float flightTimeToCloud = flightDistToCloud / cruiseSpd; + // 载具飞行方向单位向量 + float dirX = (tx - unit.Position.X) / Math.Max(1f, flightDistToCloud); + float dirZ = (tz - unit.Position.Z) / Math.Max(1f, flightDistToCloud); + float driftX = cruiseSpd * fallTime * dirX; + float driftZ = cruiseSpd * fallTime * dirZ; + float releaseX = tx - driftX; + float releaseZ = tz - driftZ; + float flightDist = Kinematics.Distance3D( + unit.Position.X, unit.Position.Y, unit.Position.Z, + releaseX, releaseAlt, releaseZ); float flightTime = flightDist / cruiseSpd; - float fallTime = Kinematics.AirDropFallTime(releaseAlt, (float)threat.Target.TypicalAltitude); fireTime = recommendedTiming - flightTime - fallTime; + tx = releaseX; tz = releaseZ; } else { diff --git a/src/CounterDrone.Core/Algorithms/Kinematics.cs b/src/CounterDrone.Core/Algorithms/Kinematics.cs index a479490..e5d46d1 100644 --- a/src/CounterDrone.Core/Algorithms/Kinematics.cs +++ b/src/CounterDrone.Core/Algorithms/Kinematics.cs @@ -52,6 +52,14 @@ namespace CounterDrone.Core.Algorithms return Math.Max(angleRange, angleHeight); } + /// 弹道飞行时间(秒) + public static float ParabolicTimeOfFlight(float range, float launchAngle, float muzzleVelocity) + { + float cosAngle = (float)Math.Cos(launchAngle); + if (cosAngle < 0.001f) return range / Math.Max(1f, muzzleVelocity * 0.7f); + return range / (muzzleVelocity * cosAngle); + } + /// 根据发射参数计算抛物线位置 public static (float X, float Y, float Z) ParabolicPosition( float startX, float startY, float startZ, diff --git a/src/CounterDrone.Core/Simulation/MunitionEntity.cs b/src/CounterDrone.Core/Simulation/MunitionEntity.cs index 627fd01..d02f1b1 100644 --- a/src/CounterDrone.Core/Simulation/MunitionEntity.cs +++ b/src/CounterDrone.Core/Simulation/MunitionEntity.cs @@ -16,12 +16,15 @@ namespace CounterDrone.Core.Simulation public float TargetZ { get; } public float ReleaseAltitude { get; } public bool HasArrived { get; private set; } + public float ArrivalTime { get; private set; } private readonly float _muzzleVelocity; private float _time; private readonly float _launchAngle; private readonly float _azimuth; private readonly float _startX, _startY, _startZ; + private readonly float _launchTime; + private float _flightDuration; // 空基投放:继承载机速度 private readonly float _carrierVelX, _carrierVelY, _carrierVelZ; @@ -32,6 +35,7 @@ namespace CounterDrone.Core.Simulation float startX, float startY, float startZ, float targetX, float targetY, float targetZ, float muzzleVelocity, float releaseAltitude, + float launchTime, float carrierVelX = 0f, float carrierVelY = 0f, float carrierVelZ = 0f) { Id = id; @@ -45,6 +49,7 @@ namespace CounterDrone.Core.Simulation _carrierVelX = carrierVelX; _carrierVelY = carrierVelY; _carrierVelZ = carrierVelZ; + _launchTime = launchTime; if (launchMode == PlatformType.GroundBased) { @@ -53,6 +58,11 @@ namespace CounterDrone.Core.Simulation var horizontalDist = (float)System.Math.Sqrt(dx * dx + dz * dz); _azimuth = horizontalDist > 0.001f ? (float)System.Math.Atan2(dx, dz) : 0; _launchAngle = Kinematics.CalculateLaunchAngle(horizontalDist, muzzleVelocity, releaseAltitude); + _flightDuration = Kinematics.ParabolicTimeOfFlight(horizontalDist, _launchAngle, muzzleVelocity); + } + else + { + _flightDuration = Kinematics.AirDropFallTime(startY, releaseAltitude); } } @@ -74,6 +84,7 @@ namespace CounterDrone.Core.Simulation { PosY = ReleaseAltitude; HasArrived = true; + ArrivalTime = _launchTime + _flightDuration; } } else @@ -88,6 +99,7 @@ namespace CounterDrone.Core.Simulation { PosY = ReleaseAltitude; HasArrived = true; + ArrivalTime = _launchTime + _flightDuration; } } } diff --git a/src/CounterDrone.Core/Simulation/PlatformEntity.cs b/src/CounterDrone.Core/Simulation/PlatformEntity.cs index 4902c27..78d07ed 100644 --- a/src/CounterDrone.Core/Simulation/PlatformEntity.cs +++ b/src/CounterDrone.Core/Simulation/PlatformEntity.cs @@ -29,6 +29,9 @@ namespace CounterDrone.Core.Simulation private float _targetX, _targetY, _targetZ; private float _flightDistance; private float _flownDistance; + /// 空基平台精确投放时刻(计算值) + public float ExactReleaseTime => _exactReleaseTime; + private float _exactReleaseTime; // 地基平台 public float MuzzleVelocity { get; } @@ -82,7 +85,7 @@ namespace CounterDrone.Core.Simulation } /// 下令空基平台飞往投放点 - public void CommandFlyTo(float targetX, float targetY, float targetZ) + public void CommandFlyTo(float targetX, float targetY, float targetZ, float fireTime) { if (PlatformType != Models.PlatformType.AirBased) return; _targetX = targetX; @@ -93,6 +96,8 @@ namespace CounterDrone.Core.Simulation float dz = targetZ - PosZ; _flightDistance = (float)Math.Sqrt(dx * dx + dy * dy + dz * dz); _flownDistance = 0; + float flightTime = CruiseSpeed > 0 ? _flightDistance / CruiseSpeed : 0f; + _exactReleaseTime = fireTime + flightTime; State = PlatformState.FlyingToTarget; } diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs index 4d72e74..fba8fe3 100644 --- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs +++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs @@ -168,7 +168,7 @@ namespace CounterDrone.Core.Simulation if (platform.PlatformType == Models.PlatformType.AirBased && platform.Ready) { - platform.CommandFlyTo(fe.TargetX, platform.ReleaseAltitude, fe.TargetZ); + platform.CommandFlyTo(fe.TargetX, platform.ReleaseAltitude, fe.TargetZ, fe.FireTime); } else if (platform.Ready) { @@ -178,7 +178,8 @@ namespace CounterDrone.Core.Simulation platform.PlatformType ?? Models.PlatformType.GroundBased, platform.AerosolType ?? Models.AerosolType.InertGas, platform.PosX, platform.PosY, platform.PosZ, - fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight); + fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight, + fe.FireTime); _munitions.Add(m); OnMunitionLaunched?.Invoke(m); frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = fe.FireTime, SourceId = platform.Id, Description = $"计划发射 {m.Id}" }); @@ -196,10 +197,11 @@ namespace CounterDrone.Core.Simulation platform.PosX, platform.PosY, platform.PosZ, platform.PosX, _disperseHeight, platform.PosZ, 0f, _disperseHeight, + platform.ExactReleaseTime, cvx, cvy, cvz); _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 = platform.ExactReleaseTime, SourceId = platform.Id, Description = $"空基投放 {m.Id}" }); } // 2. 弹药飞行 → 云团生成 @@ -209,12 +211,12 @@ namespace CounterDrone.Core.Simulation if (m.HasArrived) { var disp = AlgorithmFactory.Create(); - disp.Initialize(_ammoSpec, _scene, new Algorithms.Vector3(m.TargetX, m.TargetY, m.TargetZ), SimulationTime); - var cloud = new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, SimulationTime); + disp.Initialize(_ammoSpec, _scene, new Algorithms.Vector3(m.PosX, m.PosY, m.PosZ), m.ArrivalTime); + var cloud = new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, m.ArrivalTime); _clouds.Add(cloud); _munitions.Remove(m); OnCloudGenerated?.Invoke(cloud); - frameEvents.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = SimulationTime, Description = "云团生成" }); + frameEvents.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = m.ArrivalTime, Description = "云团生成" }); } } diff --git a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs index c07d39d..3bce83a 100644 --- a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs +++ b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs @@ -219,6 +219,7 @@ namespace CounterDrone.Core.Tests foreach (var d in eng.Drones) msg += $" [{d.Status} hp={d.Hp:F2}]"; VerifyAndExportReport(eng, eng.Drones[0]); + System.IO.File.WriteAllText(@"C:\Users\Tellme\Desktop\test_piston.txt", msg); Assert.True(launched > 0, msg); Assert.True(eng.Drones.All(d => d.Status == DroneStatus.Destroyed), msg); } @@ -319,6 +320,7 @@ namespace CounterDrone.Core.Tests var clouds = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated); var msg = $"AirBased: launched={launched} clouds={clouds} status={drone.Status} hp={drone.Hp:F2}"; + System.IO.File.WriteAllText(@"C:\Users\Tellme\Desktop\test_air.txt", msg); VerifyAndExportReport(eng, drone); Assert.True(launched > 0, msg); Assert.Equal(DroneStatus.Destroyed, drone.Status); diff --git a/test/unit/CounterDrone.Core.Tests/MunitionEntityTests.cs b/test/unit/CounterDrone.Core.Tests/MunitionEntityTests.cs index 2cbfaf0..eaad6ae 100644 --- a/test/unit/CounterDrone.Core.Tests/MunitionEntityTests.cs +++ b/test/unit/CounterDrone.Core.Tests/MunitionEntityTests.cs @@ -14,7 +14,7 @@ namespace CounterDrone.Core.Tests AerosolType.InertGas, startX: 0, startY: 0, startZ: 0, targetX: 5000, targetY: 300, targetZ: 0, - muzzleVelocity: 500f, releaseAltitude: 300f); + muzzleVelocity: 500f, releaseAltitude: 300f, launchTime: 0f); m.Update(0.05f); Assert.False(m.HasArrived, "炮弹不能第一帧就到达"); @@ -28,7 +28,7 @@ namespace CounterDrone.Core.Tests AerosolType.InertGas, startX: 0, startY: 0, startZ: 0, targetX: 5000, targetY: 300, targetZ: 0, - muzzleVelocity: 500f, releaseAltitude: 300f); + muzzleVelocity: 500f, releaseAltitude: 300f, launchTime: 0f); for (int i = 0; i < 500; i++) { @@ -68,7 +68,7 @@ namespace CounterDrone.Core.Tests AerosolType.InertGas, startX: 0, startY: 0, startZ: 0, targetX: 1000, targetY: 300, targetZ: 0, - muzzleVelocity: 500f, releaseAltitude: 300f); + muzzleVelocity: 500f, releaseAltitude: 300f, launchTime: 0f); m.Update(1f); var dist = System.Math.Sqrt(m.PosX * m.PosX + m.PosZ * m.PosZ); @@ -82,7 +82,7 @@ namespace CounterDrone.Core.Tests AerosolType.InertGas, startX: 0, startY: 500, startZ: 0, targetX: 1000, targetY: 300, targetZ: 0, - muzzleVelocity: 0, releaseAltitude: 300f); + muzzleVelocity: 0, releaseAltitude: 300f, launchTime: 0f); // 空基投放:自由落体 500m → 300m = 200m 下落 // h = 0.5*g*t² → t = sqrt(2*200/9.81) ≈ 6.4s