wip: 空基Planner修正——投放点后移抵消载具漂移;云团/发射事件用精确时刻

- 空基 GenerateFireEventsAt 投放点后移 driftX = cruiseSpeed * fallTime
- MunitionEntity 记录 launchTime + flightDuration → ArrivalTime 精确计算
- 云团 Initialization 用 munition.ArrivalTime 非 SimulationTime
- CloudGenerated 事件 OccurredAt 用精确到达时刻
- PlatformEntity 存储 ExactReleaseTime
- 修复缺失 else 导致的空基走地基分支
This commit is contained in:
tian 2026-06-13 16:40:27 +08:00
parent 09f51939d8
commit a5fef98858
7 changed files with 56 additions and 18 deletions

View File

@ -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<FireEvent>();
@ -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];
// 不覆盖 FireTimeGenerateFireEventsAt 已按偏移位置 + 单元飞行时间精确计算
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
{

View File

@ -52,6 +52,14 @@ namespace CounterDrone.Core.Algorithms
return Math.Max(angleRange, angleHeight);
}
/// <summary>弹道飞行时间(秒)</summary>
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);
}
/// <summary>根据发射参数计算抛物线位置</summary>
public static (float X, float Y, float Z) ParabolicPosition(
float startX, float startY, float startZ,

View File

@ -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;
}
}
}

View File

@ -29,6 +29,9 @@ namespace CounterDrone.Core.Simulation
private float _targetX, _targetY, _targetZ;
private float _flightDistance;
private float _flownDistance;
/// <summary>空基平台精确投放时刻(计算值)</summary>
public float ExactReleaseTime => _exactReleaseTime;
private float _exactReleaseTime;
// 地基平台
public float MuzzleVelocity { get; }
@ -82,7 +85,7 @@ namespace CounterDrone.Core.Simulation
}
/// <summary>下令空基平台飞往投放点</summary>
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;
}

View File

@ -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<ICloudDispersionModel>();
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 = "云团生成" });
}
}

View File

@ -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);

View File

@ -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