test: 收紧集成测试断言 — 击毁才通过,暴露 Planner 真实场景不足
- 删除 ApplyDefensePlan helper(引擎自治,不再需要外部调Planner) - Piston 200km/h: 断言 DroneStatus==Destroyed(当前 FAIL) - Jet 500km/h: 断言 DroneStatus==Destroyed - AirBased 300km/h: 断言 DroneStatus==Destroyed(当前 FAIL) - 测试真实暴露 Planner 算法缺陷
This commit is contained in:
parent
50ec14bb24
commit
9bd1d5a7ee
@ -65,90 +65,6 @@ namespace CounterDrone.Core.Tests
|
|||||||
private static readonly string ReportOutputDir = Path.Combine(
|
private static readonly string ReportOutputDir = Path.Combine(
|
||||||
AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "..", "..", "reports");
|
AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "..", "..", "reports");
|
||||||
|
|
||||||
private DefensePlan ApplyDefensePlan(PlatformType? preferredPlatform = null)
|
|
||||||
{
|
|
||||||
var detail = _scenario.GetTaskDetail(_taskId)!;
|
|
||||||
|
|
||||||
// 从已保存的 EquipmentDeployment 构建 FireUnit 列表
|
|
||||||
var fireUnits = new List<FireUnit>();
|
|
||||||
var platType = preferredPlatform ?? PlatformType.GroundBased;
|
|
||||||
var idx = 0;
|
|
||||||
foreach (var eq in detail.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
|
|
||||||
{
|
|
||||||
int qty = Math.Max(1, eq.Quantity);
|
|
||||||
for (int i = 0; i < qty; i++)
|
|
||||||
{
|
|
||||||
fireUnits.Add(new FireUnit
|
|
||||||
{
|
|
||||||
Id = $"u{idx++}",
|
|
||||||
Type = platType,
|
|
||||||
Position = new Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ),
|
|
||||||
CruiseSpeed = (float)(eq.CruiseSpeed ?? 55f),
|
|
||||||
ReleaseAltitude = (float)(eq.ReleaseAltitude ?? 0f),
|
|
||||||
MuzzleVelocity = (float)(eq.MuzzleVelocity ?? 800f),
|
|
||||||
TotalMunitions = eq.MunitionCount ?? 1,
|
|
||||||
AmmoTypes = new List<AerosolType> { (AerosolType)(eq.AerosolType ?? 0) },
|
|
||||||
Cooldown = (float)eq.Cooldown,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没装备,生成默认的地面单元
|
|
||||||
if (fireUnits.Count == 0)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 5; i++)
|
|
||||||
fireUnits.Add(new FireUnit
|
|
||||||
{
|
|
||||||
Id = $"u{i}",
|
|
||||||
Type = platType,
|
|
||||||
Position = new Vector3(5000 + i * 50, 0, 50),
|
|
||||||
CruiseSpeed = 55f,
|
|
||||||
ReleaseAltitude = 1000f,
|
|
||||||
MuzzleVelocity = 800f,
|
|
||||||
TotalMunitions = 1,
|
|
||||||
AmmoTypes = new List<AerosolType> { AerosolType.InertGas, AerosolType.ActiveMaterial, AerosolType.ActiveFuel },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建 DroneGroup 列表
|
|
||||||
var threats = new List<DroneGroup>();
|
|
||||||
foreach (var t in detail.Targets)
|
|
||||||
{
|
|
||||||
var route = detail.Routes.FirstOrDefault(r => r.GroupId == t.GroupId);
|
|
||||||
var wps = detail.WaypointGroups.GetValueOrDefault(t.GroupId, new List<Waypoint>());
|
|
||||||
threats.Add(new DroneGroup
|
|
||||||
{
|
|
||||||
GroupId = t.GroupId,
|
|
||||||
Target = t,
|
|
||||||
Route = route ?? new RoutePlan(),
|
|
||||||
Waypoints = wps,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var planner = new DefaultDefensePlanner(_ammoCatalog);
|
|
||||||
var result = planner.Plan(fireUnits, threats, detail.Scene);
|
|
||||||
|
|
||||||
// 写入云团参数
|
|
||||||
if (threats.Count > 0)
|
|
||||||
{
|
|
||||||
var t = threats[0];
|
|
||||||
var mid = new CounterDrone.Core.Algorithms.Vector3(
|
|
||||||
(float)(t.Waypoints[0].PosX + t.Waypoints[^1].PosX) / 2f,
|
|
||||||
(float)(t.Waypoints[0].PosY + t.Waypoints[^1].PosY) / 2f,
|
|
||||||
(float)(t.Waypoints[0].PosZ + t.Waypoints[^1].PosZ) / 2f);
|
|
||||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal
|
|
||||||
{
|
|
||||||
AerosolType = platType == PlatformType.AirBased ? (int)AerosolType.InertGas : (int)AerosolType.InertGas,
|
|
||||||
PositionX = mid.X, PositionY = mid.Y, PositionZ = mid.Z,
|
|
||||||
DisperseHeight = (float)t.Target.TypicalAltitude,
|
|
||||||
Source = "Algorithm",
|
|
||||||
PositionMode = (int)PositionMode.AlgorithmRecommended,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Best;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyAndExportReport(SimulationEngine eng, DroneEntity drone)
|
private void VerifyAndExportReport(SimulationEngine eng, DroneEntity drone)
|
||||||
{
|
{
|
||||||
var detail = _scenario.GetTaskDetail(_taskId)!;
|
var detail = _scenario.GetTaskDetail(_taskId)!;
|
||||||
@ -237,7 +153,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════
|
// ═══════════════════════════════════════════════
|
||||||
// 场景 3:活塞发动机 → 算法推荐惰性气体 → 拦截成功
|
// 场景 3:活塞式 200km/h,惰性气体拦截
|
||||||
// ═══════════════════════════════════════════════
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -246,19 +162,12 @@ namespace CounterDrone.Core.Tests
|
|||||||
var task = _scenario.CreateTask("拦截活塞式无人机", "");
|
var task = _scenario.CreateTask("拦截活塞式无人机", "");
|
||||||
_taskId = task.Id;
|
_taskId = task.Id;
|
||||||
|
|
||||||
_scenario.SaveScene(_taskId, new CombatScene
|
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 3, WindDirection = (int)WindDirection.W });
|
||||||
{
|
|
||||||
WindSpeed = 3,
|
|
||||||
WindDirection = (int)WindDirection.W,
|
|
||||||
});
|
|
||||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||||
{
|
{
|
||||||
GroupId = "default",
|
GroupId = "default", TargetType = (int)TargetType.Piston,
|
||||||
TargetType = (int)TargetType.Piston,
|
PowerType = (int)PowerType.Piston, Quantity = 1,
|
||||||
PowerType = (int)PowerType.Piston,
|
TypicalSpeed = 200, TypicalAltitude = 500,
|
||||||
Quantity = 1,
|
|
||||||
TypicalSpeed = 200,
|
|
||||||
TypicalAltitude = 500,
|
|
||||||
});
|
});
|
||||||
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
new List<Waypoint>
|
new List<Waypoint>
|
||||||
@ -266,8 +175,6 @@ namespace CounterDrone.Core.Tests
|
|||||||
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 },
|
new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
});
|
});
|
||||||
|
|
||||||
// 部署地基单元
|
|
||||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||||
{
|
{
|
||||||
new EquipmentDeployment
|
new EquipmentDeployment
|
||||||
@ -279,25 +186,16 @@ namespace CounterDrone.Core.Tests
|
|||||||
MuzzleVelocity = 800, Cooldown = 5,
|
MuzzleVelocity = 800, Cooldown = 5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||||
var plan = ApplyDefensePlan();
|
|
||||||
Assert.True(plan.ThreatsEngaged > 0);
|
|
||||||
Assert.NotEmpty(plan.MergedSchedule);
|
|
||||||
Assert.True(plan.MergedSchedule.Count >= 8,
|
|
||||||
$"Piston Planner 只生成了 {plan.MergedSchedule.Count} 发,预期 >= 8");
|
|
||||||
|
|
||||||
var eng = RunSimulation(8000);
|
var eng = RunSimulation(8000);
|
||||||
|
|
||||||
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
|
||||||
var cloudsGenerated = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
|
||||||
var drone = eng.Drones[0];
|
var drone = eng.Drones[0];
|
||||||
|
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
||||||
|
var clouds = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
||||||
|
|
||||||
var msg = $"Piston: launched={launched}, clouds={cloudsGenerated}, " +
|
var msg = $"Piston: launched={launched} clouds={clouds} status={drone.Status} hp={drone.Hp:F2}";
|
||||||
$"droneStatus={drone.Status}, hp={drone.Hp:F3}, posX={drone.PosX:F0}, " +
|
|
||||||
$"cloudCount={eng.Clouds.Count}";
|
|
||||||
|
|
||||||
Assert.True(launched > 0, msg);
|
Assert.True(launched > 0, msg);
|
||||||
Assert.True(cloudsGenerated > 0, msg);
|
Assert.Equal(DroneStatus.Destroyed, drone.Status);
|
||||||
VerifyAndExportReport(eng, drone);
|
VerifyAndExportReport(eng, drone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,23 +237,16 @@ namespace CounterDrone.Core.Tests
|
|||||||
MuzzleVelocity = 800, Cooldown = 5,
|
MuzzleVelocity = 800, Cooldown = 5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.ActiveMaterial, DisperseHeight = 800 });
|
||||||
var plan = ApplyDefensePlan();
|
|
||||||
Assert.True(plan.ThreatsEngaged > 0);
|
|
||||||
Assert.NotEmpty(plan.MergedSchedule);
|
|
||||||
|
|
||||||
var eng = RunSimulation(8000);
|
var eng = RunSimulation(8000);
|
||||||
|
|
||||||
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
|
||||||
var cloudsGenerated = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
|
||||||
var drone = eng.Drones[0];
|
var drone = eng.Drones[0];
|
||||||
|
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
||||||
|
var clouds = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
||||||
|
|
||||||
var msg = $"Jet: launched={launched}, clouds={cloudsGenerated}, " +
|
var msg = $"Jet: launched={launched} clouds={clouds} status={drone.Status} hp={drone.Hp:F2}";
|
||||||
$"droneStatus={drone.Status}, hp={drone.Hp:F3}, posX={drone.PosX:F0}, " +
|
|
||||||
$"cloudCount={eng.Clouds.Count}";
|
|
||||||
|
|
||||||
Assert.True(launched > 0, msg);
|
Assert.True(launched > 0, msg);
|
||||||
Assert.True(cloudsGenerated > 0, msg);
|
Assert.Equal(DroneStatus.Destroyed, drone.Status);
|
||||||
VerifyAndExportReport(eng, drone);
|
VerifyAndExportReport(eng, drone);
|
||||||
}
|
}
|
||||||
// ═══════════════════════════════════════════════
|
// ═══════════════════════════════════════════════
|
||||||
@ -397,29 +288,16 @@ namespace CounterDrone.Core.Tests
|
|||||||
CruiseSpeed = 55, ReleaseAltitude = 1500, Cooldown = 5,
|
CruiseSpeed = 55, ReleaseAltitude = 1500, Cooldown = 5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||||
var plan = ApplyDefensePlan(PlatformType.AirBased);
|
|
||||||
Assert.True(plan.ThreatsEngaged > 0);
|
|
||||||
Assert.NotEmpty(plan.MergedSchedule);
|
|
||||||
|
|
||||||
var eng = RunSimulation(8000);
|
var eng = RunSimulation(8000);
|
||||||
|
|
||||||
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
|
||||||
var cloudsGenerated = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
|
||||||
var drone = eng.Drones[0];
|
var drone = eng.Drones[0];
|
||||||
|
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
||||||
|
var clouds = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
||||||
|
|
||||||
var msg = $"AirBased: launched={launched}, clouds={cloudsGenerated}, " +
|
var msg = $"AirBased: launched={launched} clouds={clouds} status={drone.Status} hp={drone.Hp:F2}";
|
||||||
$"droneStatus={drone.Status}, hp={drone.Hp:F3}, simTime={eng.SimulationTime:F1}s";
|
|
||||||
|
|
||||||
Assert.True(launched > 0, msg);
|
Assert.True(launched > 0, msg);
|
||||||
Assert.True(cloudsGenerated > 0, msg);
|
Assert.Equal(DroneStatus.Destroyed, drone.Status);
|
||||||
|
|
||||||
// 验证平台确实飞了(位置偏离了巡逻点)
|
|
||||||
var platSnapshots = eng.Events
|
|
||||||
.Where(e => e.Type == SimEventType.MunitionLaunched && e.Description.Contains("空基投放"))
|
|
||||||
.ToList();
|
|
||||||
Assert.True(platSnapshots.Count > 0, "应有空基投放事件");
|
|
||||||
|
|
||||||
VerifyAndExportReport(eng, drone);
|
VerifyAndExportReport(eng, drone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user