fix: planner formation lane offset + add 3-drone integration test
Bug: planner used symmetric lane offset (Z=-50/0/+50) but DroneEntity uses start-anchored (Z=0/50/100). 3rd drone at Z=100 got no clouds. Fix: planner laneOffset = yLane * laneSpacing (matches DroneEntity formationIndex * lateralSpacing). Test: Scenario_3DronesFormation - 3 drones in Formation (Z=0/50/100), 3 fire units, all destroyed. 192 tests pass.
This commit is contained in:
parent
87e82727f2
commit
008df4a86c
@ -401,11 +401,13 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
float midArc = RouteGeometry.ArcLengthNearestTo(wps, mid.X, mid.Z);
|
float midArc = RouteGeometry.ArcLengthNearestTo(wps, mid.X, mid.Z);
|
||||||
float crossArc = midArc + targetOffset;
|
float crossArc = midArc + targetOffset;
|
||||||
|
|
||||||
// 编队横向偏移:按车道分布在航路法向上
|
// 编队横向偏移:按车道分布在航路法向上,与 DroneEntity 编队偏移一致(起点展开)。
|
||||||
|
// DroneEntity: latOffset = formationIndex * lateralSpacing(0/50/100...)
|
||||||
// 法向 = 切向旋转 90°,用 RouteGeometry.TangentAt 取穿越点处航路方向
|
// 法向 = 切向旋转 90°,用 RouteGeometry.TangentAt 取穿越点处航路方向
|
||||||
var (tanX, tanZ) = RouteGeometry.TangentAt(wps, crossArc);
|
var (tanX, tanZ) = RouteGeometry.TangentAt(wps, crossArc);
|
||||||
float normalX = -tanZ, normalZ = tanX; // 切向逆时针 90° = 左侧法向
|
float normalX = -tanZ, normalZ = tanX; // 切向逆时针 90° = 左侧法向
|
||||||
float laneOffset = yLanes > 1 ? (yLane - (yLanes - 1) / 2f) * (formationWidth / Math.Max(1, yLanes - 1)) : 0f;
|
float laneSpacing = yLanes > 1 ? formationWidth / Math.Max(1, yLanes - 1) : 0f;
|
||||||
|
float laneOffset = yLane * laneSpacing;
|
||||||
|
|
||||||
// 穿越点 = 航路上 crossArc 处 + 横向车道偏移
|
// 穿越点 = 航路上 crossArc 处 + 横向车道偏移
|
||||||
var (routeX, routeY, routeZ) = RouteGeometry.PositionAt(wps, crossArc);
|
var (routeX, routeY, routeZ) = RouteGeometry.PositionAt(wps, crossArc);
|
||||||
|
|||||||
@ -483,5 +483,60 @@ namespace CounterDrone.Core.Tests
|
|||||||
Assert.True(drone.Hp < 0.1f, msg);
|
Assert.True(drone.Hp < 0.1f, msg);
|
||||||
VerifyAndExportReport(eng, drone);
|
VerifyAndExportReport(eng, drone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
// 场景 6:3 架活塞编队(横排 Z=0/50/100)— 多无人机端到端
|
||||||
|
// 验证 planner 给每个车道分配云团、3 架都被击毁
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Scenario_3DronesFormation_AllDestroyed()
|
||||||
|
{
|
||||||
|
var task = _scenario.CreateTask("3架编队拦截测试", "");
|
||||||
|
_taskId = task.Id;
|
||||||
|
|
||||||
|
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||||
|
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||||
|
{
|
||||||
|
GroupId = "default", TargetType = (int)TargetType.Piston,
|
||||||
|
PowerType = (int)PowerType.Piston,
|
||||||
|
Quantity = 3,
|
||||||
|
TypicalSpeed = 200, TypicalAltitude = 500,
|
||||||
|
});
|
||||||
|
// 3 架横排编队:Formation 模式,横向间距 50m
|
||||||
|
// 引擎会生成 3 架无人机,Z 偏移 0/50/100(latIdx × LateralSpacing)
|
||||||
|
_scenario.SaveRoute(_taskId, "default", new RoutePlan
|
||||||
|
{
|
||||||
|
FormationMode = (int)FormationMode.Formation,
|
||||||
|
LateralSpacing = 50,
|
||||||
|
LateralCount = 3,
|
||||||
|
LongitudinalCount = 1,
|
||||||
|
},
|
||||||
|
new List<Waypoint>
|
||||||
|
{
|
||||||
|
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
|
new Waypoint { PosX = 10000, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
|
});
|
||||||
|
// 3 个火力单元部署在中点附近(X=4500,每个单元 i*50 偏移 → 4500/4550/4600)
|
||||||
|
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||||
|
{
|
||||||
|
MakeEquipment(DefaultFireUnits.GetById("ground-light"), AerosolType.InertGas, 3, 4500, 0, 50),
|
||||||
|
});
|
||||||
|
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||||
|
|
||||||
|
var eng = RunSimulation(8000);
|
||||||
|
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
||||||
|
var clouds = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
||||||
|
var destroyed = eng.Events.Count(e => e.Type == SimEventType.DroneDestroyed);
|
||||||
|
|
||||||
|
// 验证:3 架无人机都被击毁
|
||||||
|
var msg = $"3Drones: launched={launched} clouds={clouds} destroyed={destroyed} drones={eng.Drones.Count}";
|
||||||
|
foreach (var d in eng.Drones)
|
||||||
|
msg += $" [{d.Status} hp={d.Hp:F2} pos=({d.PosX:F0},{d.PosZ:F0})]";
|
||||||
|
VerifyAndExportReport(eng, eng.Drones[0]);
|
||||||
|
Assert.Equal(3, eng.Drones.Count);
|
||||||
|
Assert.True(launched > 0, msg);
|
||||||
|
Assert.True(eng.Drones.All(d => d.Status == DroneStatus.Destroyed), msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user