fix: 空基投放点不被传送到终点——插值位置保留在投放点

- PlatformEntity 投放条件触发时不设置PosX=targetX,保留插值位置
- 空基FireEvent.TargetX=云位,platform飞向云位,距目标driftDist时投放
- planner恢复laneBaseTime+stagger覆盖,保证云位连续间距均匀
- CommandFlyTo接收disperseHeight计算driftDist
This commit is contained in:
tian 2026-06-13 16:57:14 +08:00
parent a5fef98858
commit 9e1fe869a6
3 changed files with 44 additions and 23 deletions

View File

@ -147,6 +147,8 @@ namespace CounterDrone.Core.Algorithms
// 逐单元分配:每个单元锁定一个 Y 车道
float spacing = 2f * turbulentRadius;
int[] laneNeeded = new int[yLanes];
float[] laneBaseTime = new float[yLanes];
bool[] laneBaseSet = new bool[yLanes];
for (int l = 0; l < yLanes; l++) laneNeeded[l] = singleNeeded;
int currentLane = 0;
int roundsCollected = 0;
@ -167,6 +169,14 @@ namespace CounterDrone.Core.Algorithms
if (toTake <= 0) continue;
int yLane = currentLane;
var mid = ThreatMidpoint(threat);
// 车道第一个单元设基准时间,后续单元以此为准保证间距均匀
if (!laneBaseSet[yLane])
{
var refEvt = GenerateFireEventsAt(threat, c.Unit, c.AmmoType, ammo, env, 0, yLane, yLanes, formationWidth);
laneBaseTime[yLane] = refEvt[0].FireTime;
laneBaseSet[yLane] = true;
}
float stagger = Kinematics.CloudCoverInterval(turbulentRadius * 2f, (float)threat.Target.TypicalSpeed, c.Unit.ChannelInterval);
var fevents = new List<FireEvent>();
@ -175,9 +185,11 @@ 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);
// 不覆盖 FireTimeGenerateFireEventsAt 已按偏移位置 + 单元飞行时间精确计算
int baseRoundInLane = singleNeeded - laneNeeded[currentLane];
foreach (var e in fe)
{
e.FireTime = laneBaseTime[yLane] + (baseRoundInLane + ch) * stagger;
e.TargetX = mid.X + offset;
e.PlatformIndex = unitIdx * c.Unit.TotalChannels + ch;
}
fevents.AddRange(fe);
@ -394,7 +406,7 @@ namespace CounterDrone.Core.Algorithms
{
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
var (effectiveR, _, _) = ComputeEffectiveRadius(threat, ammo, env);
float spacing = effectiveR * 1.5f;
float spacing = 2f * effectiveR;
float actualCoverage = spacing * (rounds - 1) + 2f * effectiveR;
float actualExposure = actualCoverage / avgSpeed;
@ -441,29 +453,21 @@ 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 fallTime = Kinematics.AirDropFallTime(releaseAlt, (float)threat.Target.TypicalAltitude);
// 投放点后移以抵消载具航速导致的漂移
float flightDistToCloud = Kinematics.Distance3D(
// 载具飞向云端方向,漂移距离 = 巡航速度 × 落体时间
float driftDist = cruiseSpd * fallTime;
float distToCloud = 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 flightDist = Math.Max(0f, distToCloud - driftDist);
float flightTime = flightDist / cruiseSpd;
fireTime = recommendedTiming - flightTime - fallTime;
tx = releaseX; tz = releaseZ;
}
else
{

View File

@ -29,6 +29,7 @@ namespace CounterDrone.Core.Simulation
private float _targetX, _targetY, _targetZ;
private float _flightDistance;
private float _flownDistance;
private float _driftDistance;
/// <summary>空基平台精确投放时刻(计算值)</summary>
public float ExactReleaseTime => _exactReleaseTime;
private float _exactReleaseTime;
@ -69,11 +70,13 @@ namespace CounterDrone.Core.Simulation
float step = CruiseSpeed * deltaTime;
_flownDistance += step;
if (_flownDistance >= _flightDistance || _flightDistance < 0.01f)
if (_flownDistance >= _flightDistance - _driftDistance - 0.01f || _flightDistance < 0.01f)
{
PosX = _targetX;
PosY = _targetY;
PosZ = _targetZ;
// 在投放点(非终点),保留插值位置
float rt = _flightDistance > 0.01f ? Math.Min(1f, _flownDistance / _flightDistance) : 1f;
PosX = PatrolX + (_targetX - PatrolX) * rt;
PosY = PatrolY + (_targetY - PatrolY) * rt;
PosZ = PatrolZ + (_targetZ - PatrolZ) * rt;
State = PlatformState.ReadyToRelease;
return;
}
@ -85,7 +88,7 @@ namespace CounterDrone.Core.Simulation
}
/// <summary>下令空基平台飞往投放点</summary>
public void CommandFlyTo(float targetX, float targetY, float targetZ, float fireTime)
public void CommandFlyTo(float targetX, float targetY, float targetZ, float fireTime, float disperseHeight)
{
if (PlatformType != Models.PlatformType.AirBased) return;
_targetX = targetX;
@ -97,7 +100,9 @@ namespace CounterDrone.Core.Simulation
_flightDistance = (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
_flownDistance = 0;
float flightTime = CruiseSpeed > 0 ? _flightDistance / CruiseSpeed : 0f;
_exactReleaseTime = fireTime + flightTime;
float fallTime = Kinematics.AirDropFallTime(PosY, disperseHeight);
_driftDistance = CruiseSpeed * fallTime;
_exactReleaseTime = fireTime + Math.Max(0f, _flightDistance - _driftDistance) / CruiseSpeed;
State = PlatformState.FlyingToTarget;
}

View File

@ -142,9 +142,19 @@ namespace CounterDrone.Core.Simulation
{
var result = _planner.Plan(fireUnits, threats, _scene);
SetFireSchedule(result.Best.MergedSchedule);
// 诊断:输出 Planner 云团预期位置
var lines = new List<string> { "idx,fireTime,tx,ty,tz,mv" };
for (int i = 0; i < _fireSchedule.Count; i++)
{
var f = _fireSchedule[i];
lines.Add($"{i},{f.FireTime:F3},{f.TargetX:F1},{f.TargetY:F1},{f.TargetZ:F1},{f.MuzzleVelocity:F0}");
}
System.IO.File.WriteAllLines(@"C:\Users\Tellme\Desktop\planner_targets.csv", lines);
}
}
// 初始化 cloud dump
System.IO.File.WriteAllText(@"C:\Users\Tellme\Desktop\cloud_actual.csv", "m.id,arrivalTime,px,py,pz,cloud.id,tx,ty,tz\n");
_frameDb = _frameStore.CreateOrOpen(_taskId);
State = SimulationState.Running;
}
@ -168,7 +178,7 @@ namespace CounterDrone.Core.Simulation
if (platform.PlatformType == Models.PlatformType.AirBased && platform.Ready)
{
platform.CommandFlyTo(fe.TargetX, platform.ReleaseAltitude, fe.TargetZ, fe.FireTime);
platform.CommandFlyTo(fe.TargetX, platform.ReleaseAltitude, fe.TargetZ, fe.FireTime, _disperseHeight);
}
else if (platform.Ready)
{
@ -217,6 +227,8 @@ namespace CounterDrone.Core.Simulation
_munitions.Remove(m);
OnCloudGenerated?.Invoke(cloud);
frameEvents.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = m.ArrivalTime, Description = "云团生成" });
System.IO.File.AppendAllText(@"C:\Users\Tellme\Desktop\cloud_actual.csv",
$"{m.Id},{m.ArrivalTime:F3},{m.PosX:F1},{m.PosY:F1},{m.PosZ:F1},{cloud.Id},{m.TargetX:F1},{m.TargetY:F1},{m.TargetZ:F1}\n");
}
}