fix: 每单元固定Y车道 + CloudCoverInterval物理间隔 + 16通道
- Solve: 每单元分配一个Y车道,所有弹打同一高度 - stagger=CloudCoverInterval 替代硬编码 ChannelInterval - Kinematics.CloudCoverInterval: 云团直径/目标速度,不小于硬件间隔 - DefaultFireUnits: ground-light 改为 16通道 - 报告 F1→F2 精度 - MultiLane_3Drones_3Lanes_ExactOutput 单元测试
This commit is contained in:
parent
4f509e5a2a
commit
6a6c03f0fa
@ -173,6 +173,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);
|
||||
|
||||
var fevents = new List<FireEvent>();
|
||||
for (int ch = 0; ch < toTake; ch++)
|
||||
@ -180,7 +181,7 @@ 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);
|
||||
foreach (var e in fe)
|
||||
e.FireTime = unitBaseTime + ch * c.Unit.ChannelInterval;
|
||||
e.FireTime = unitBaseTime + ch * stagger;
|
||||
fevents.AddRange(fe);
|
||||
eventIdx++;
|
||||
}
|
||||
|
||||
@ -113,6 +113,17 @@ namespace CounterDrone.Core.Algorithms
|
||||
return (dx * s, dy * s, dz * s);
|
||||
}
|
||||
|
||||
/// <summary>云团覆盖间隔:无人机穿越单个云团的时间 = 云团直径 / 无人机速度</summary>
|
||||
/// <param name="cloudDiameter">云团有效直径 m</param>
|
||||
/// <param name="targetSpeedKmh">目标速度 km/h</param>
|
||||
/// <param name="minInterval">硬件最小间隔 s</param>
|
||||
public static float CloudCoverInterval(float cloudDiameter, float targetSpeedKmh, float minInterval = 1f)
|
||||
{
|
||||
float speedMs = targetSpeedKmh / 3.6f;
|
||||
if (speedMs <= 0.1f) return minInterval;
|
||||
return Math.Max(minInterval, cloudDiameter / speedMs);
|
||||
}
|
||||
|
||||
/// <summary>点到矩形距离(简化判定)</summary>
|
||||
public static float Distance2D(float x1, float z1, float x2, float z2)
|
||||
{
|
||||
|
||||
@ -16,7 +16,7 @@ namespace CounterDrone.Core
|
||||
Id = "ground-light",
|
||||
Name = "轻型地基火力单元",
|
||||
PlatformType = 1,
|
||||
GunCount = 2,
|
||||
GunCount = 4,
|
||||
ChannelsPerGun = 4,
|
||||
ChannelInterval = 1.0,
|
||||
Cooldown = 5.0,
|
||||
|
||||
@ -137,7 +137,7 @@ namespace CounterDrone.Core.Services
|
||||
SimEventType.SimulationEnd => "仿真结束",
|
||||
_ => e.Description,
|
||||
};
|
||||
sb.AppendLine($"| {e.OccurredAt:F1} | {TranslateEvent(e.Type)} | {desc} | {unitCell} |");
|
||||
sb.AppendLine($"| {e.OccurredAt:F2} | {TranslateEvent(e.Type)} | {desc} | {unitCell} |");
|
||||
}
|
||||
sb.AppendLine();
|
||||
|
||||
|
||||
@ -111,32 +111,28 @@ namespace CounterDrone.Core.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiUnit_Parallel_ShorterThanSingle()
|
||||
public void MultiLane_3Drones_3Lanes_ExactOutput()
|
||||
{
|
||||
// 3 架无人机横向编队,应生成 3 车道各 11 发
|
||||
var threat = MakeThreat(speed: 200);
|
||||
threat.Target.Quantity = 3;
|
||||
threat.Route = new RoutePlan { FormationMode = (int)Models.FormationMode.Formation, LateralSpacing = 50 };
|
||||
threat.Waypoints.Clear();
|
||||
threat.Waypoints.Add(new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 });
|
||||
threat.Waypoints.Add(new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 });
|
||||
|
||||
// 每个单元只有 6 通道,单机需求 11 发 → 一个不够,必须两个并射
|
||||
var unitSmall = new FireUnit { Id = "u0", Type = PlatformType.GroundBased,
|
||||
Position = new Vector3(5000, 0, 50), GunCount = 1, ChannelsPerGun = 6,
|
||||
MuzzleVelocity = 800, TotalMunitions = 11, Cooldown = 5f,
|
||||
AmmoTypes = new() { AerosolType.InertGas } };
|
||||
var unitSmall2 = new FireUnit { Id = "u1", Type = PlatformType.GroundBased,
|
||||
Position = new Vector3(5100, 0, 50), GunCount = 1, ChannelsPerGun = 6,
|
||||
MuzzleVelocity = 800, TotalMunitions = 11, Cooldown = 5f,
|
||||
AmmoTypes = new() { AerosolType.InertGas } };
|
||||
var result = Plan(new() { MakeGroundUnit("u0", 5000), MakeGroundUnit("u1", 5000), MakeGroundUnit("u2", 5000) }, threat);
|
||||
|
||||
var r1 = Plan(new() { unitSmall }, threat);
|
||||
var r2 = Plan(new() { unitSmall, unitSmall2 }, threat);
|
||||
var events = result.Best.MergedSchedule.OrderBy(e => e.FireTime).ToList();
|
||||
|
||||
float s1 = r1.Best.MergedSchedule.Max(e => e.FireTime) - r1.Best.MergedSchedule.Min(e => e.FireTime);
|
||||
float s2 = r2.Best.MergedSchedule.Max(e => e.FireTime) - r2.Best.MergedSchedule.Min(e => e.FireTime);
|
||||
// 打印所有事件
|
||||
var lines = events.Select(e => $"{e.FireTime:F2}s Y={e.TargetY:F0} u={e.PlatformIndex}").ToList();
|
||||
var dump = string.Join("\n", lines.Take(15)) + $"\n... ({events.Count} total)";
|
||||
|
||||
// 2 单元并射应短于 1 单元
|
||||
var msg = $"2单元={s2:F1}s({r2.Best.MergedSchedule.Count}发) 1单元={s1:F1}s({r1.Best.MergedSchedule.Count}发)";
|
||||
Assert.True(s2 < s1, msg);
|
||||
Assert.True(events.Count >= 30, $"count={events.Count}\n{dump}");
|
||||
// 验证时间不等(有错开)
|
||||
float first = events[0].FireTime, last = events[^1].FireTime;
|
||||
Assert.True(last - first > 2f, $"spread={last-first:F1}s, 应有错开\n{dump}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -179,12 +175,12 @@ namespace CounterDrone.Core.Tests
|
||||
var events = result.Best.MergedSchedule.OrderBy(e => e.FireTime).ToList();
|
||||
Assert.True(events.Count >= 3);
|
||||
|
||||
// 间隔应稳定 ≈ ChannelInterval(1s),不应因目标位置变化而波动
|
||||
float expectedGap = Kinematics.CloudCoverInterval(40f, 120f, 1f);
|
||||
for (int i = 1; i < events.Count; i++)
|
||||
{
|
||||
float gap = events[i].FireTime - events[i - 1].FireTime;
|
||||
Assert.True(Math.Abs(gap - 1.0f) < 0.2f,
|
||||
$"gap[{i}]={gap:F3}, expected 1.0±0.2");
|
||||
Assert.True(Math.Abs(gap - expectedGap) < 0.3f,
|
||||
$"gap[{i}]={gap:F3}, expected {expectedGap:F2}±0.3");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user