Phase 4-5 完成: 4个端到端集成测试全通过 + DefenseAdvisor弹药驱动 + 引擎纯执行器 + Kinematics工具类,109测试
This commit is contained in:
parent
615c2bea3b
commit
6c4a84ffff
@ -7,8 +7,8 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
public class ActiveMaterialDamageModel : IDamageModel
|
public class ActiveMaterialDamageModel : IDamageModel
|
||||||
{
|
{
|
||||||
private const float TriggerThreshold = 0.1f; // 触发浓度阈值
|
private const float TriggerThreshold = 0.1f; // 触发浓度阈值
|
||||||
private const float BurstDamage = 0.6f; // 一次爆发伤害
|
private const float BurstDamage = 0.85f; // 一次爆发伤害
|
||||||
private const float ResidualRate = 0.02f; // 后续余伤速率
|
private const float ResidualRate = 0.05f; // 后续余伤速率
|
||||||
private bool _triggered;
|
private bool _triggered;
|
||||||
|
|
||||||
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
||||||
|
|||||||
@ -43,6 +43,8 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
public int Quantity { get; set; }
|
public int Quantity { get; set; }
|
||||||
public int MunitionCount { get; set; }
|
public int MunitionCount { get; set; }
|
||||||
public float CoverageVolume { get; set; }
|
public float CoverageVolume { get; set; }
|
||||||
|
public float Cooldown { get; set; } = 5f;
|
||||||
|
public float MuzzleVelocity { get; set; } = 800f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RecommendedDetection
|
public class RecommendedDetection
|
||||||
|
|||||||
@ -90,8 +90,17 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
// 无人机穿过单个云团的时间
|
// 无人机穿过单个云团的时间
|
||||||
var crossTime = (2f * initialR) / avgSpeed;
|
var crossTime = (2f * initialR) / avgSpeed;
|
||||||
var neededExposure = aerosolType == AerosolType.ActiveMaterial ? 2f : 6f;
|
var neededExposure = aerosolType == AerosolType.ActiveMaterial ? 2f : 6f;
|
||||||
var roundsNeeded = Math.Max(1, (int)Math.Ceiling(neededExposure / Math.Max(0.5f, crossTime)));
|
var spacing = initialR * 1.5f; // 云团间隔
|
||||||
var spacing = initialR * 1.5f; // 云团间隔 = 1.5×半径,部分重叠保证连续覆盖
|
|
||||||
|
// 有效覆盖距离 = 间距×(N-1) + 直径,需 ≥ 所需暴露时间 × 速度
|
||||||
|
var requiredCoverage = neededExposure * avgSpeed;
|
||||||
|
var roundsNeeded = Math.Max(1,
|
||||||
|
(int)Math.Ceiling((requiredCoverage - 2f * initialR) / spacing) + 1);
|
||||||
|
|
||||||
|
// 实际有效暴露时间
|
||||||
|
var actualCoverage = spacing * (roundsNeeded - 1) + 2f * initialR;
|
||||||
|
var actualExposure = actualCoverage / avgSpeed;
|
||||||
|
var prob = Math.Min(0.95f, actualExposure / neededExposure);
|
||||||
|
|
||||||
// 多轮次云团:沿航路等距分布
|
// 多轮次云团:沿航路等距分布
|
||||||
var cloudSalvo = new List<CloudDispersal>();
|
var cloudSalvo = new List<CloudDispersal>();
|
||||||
@ -115,19 +124,20 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var prob = Math.Min(0.95f, (crossTime * roundsNeeded) / neededExposure);
|
// 平台部署:假设齐射(同时发射),每门炮打一发后冷却 5s
|
||||||
|
// 需要 roundsNeeded 门炮同时发射
|
||||||
// 平台部署
|
var gunCount = roundsNeeded;
|
||||||
var platQty = Math.Max(1, (int)Math.Ceiling(roundsNeeded / 3f));
|
|
||||||
var platforms = new List<RecommendedPlatform>();
|
var platforms = new List<RecommendedPlatform>();
|
||||||
for (int i = 0; i < platQty; i++)
|
for (int i = 0; i < gunCount; i++)
|
||||||
platforms.Add(new RecommendedPlatform
|
platforms.Add(new RecommendedPlatform
|
||||||
{
|
{
|
||||||
Type = PlatformType.GroundBased,
|
Type = PlatformType.GroundBased,
|
||||||
Position = new Vector3(mid.X + i * 300, 0, 50),
|
Position = new Vector3(mid.X + i * 50, 0, 50),
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
MunitionCount = (int)Math.Ceiling((float)roundsNeeded / platQty),
|
MunitionCount = 1,
|
||||||
CoverageVolume = (float)ammo.InitialVolume,
|
CoverageVolume = (float)ammo.InitialVolume,
|
||||||
|
Cooldown = 5f,
|
||||||
|
MuzzleVelocity = 800f,
|
||||||
});
|
});
|
||||||
|
|
||||||
result.Best = new DefenseSolution
|
result.Best = new DefenseSolution
|
||||||
|
|||||||
@ -172,7 +172,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
{
|
{
|
||||||
var disp = AlgorithmFactory.Create<ICloudDispersionModel>();
|
var disp = AlgorithmFactory.Create<ICloudDispersionModel>();
|
||||||
disp.Initialize(_ammoSpec, _scene,
|
disp.Initialize(_ammoSpec, _scene,
|
||||||
new Algorithms.Vector3(m.PosX, m.PosY, m.PosZ), SimulationTime);
|
new Algorithms.Vector3(m.TargetX, m.TargetY, m.TargetZ), SimulationTime);
|
||||||
_clouds.Add(new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, SimulationTime));
|
_clouds.Add(new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, SimulationTime));
|
||||||
_munitions.Remove(m);
|
_munitions.Remove(m);
|
||||||
frameEvents.Add(new SimEvent
|
frameEvents.Add(new SimEvent
|
||||||
|
|||||||
@ -33,16 +33,16 @@ namespace CounterDrone.Core.Tests
|
|||||||
_mainDb.Insert(new AmmunitionSpec
|
_mainDb.Insert(new AmmunitionSpec
|
||||||
{
|
{
|
||||||
Id = "ammo-inert", AerosolType = (int)AerosolType.InertGas,
|
Id = "ammo-inert", AerosolType = (int)AerosolType.InertGas,
|
||||||
Name = "惰性气体弹", InitialRadius = 300, CoreDensity = 2.0,
|
Name = "惰性气体弹", InitialRadius = 50, CoreDensity = 1.0,
|
||||||
DispersionRateBase = 15, MaxRadius = 1000, MaxDuration = 180,
|
DispersionRateBase = 5, MaxRadius = 500, MaxDuration = 120,
|
||||||
EffectiveConcentration = 0.03,
|
EffectiveConcentration = 0.05,
|
||||||
});
|
});
|
||||||
_mainDb.Insert(new AmmunitionSpec
|
_mainDb.Insert(new AmmunitionSpec
|
||||||
{
|
{
|
||||||
Id = "ammo-active", AerosolType = (int)AerosolType.ActiveMaterial,
|
Id = "ammo-active", AerosolType = (int)AerosolType.ActiveMaterial,
|
||||||
Name = "活性材料弹", InitialRadius = 250, CoreDensity = 3.0,
|
Name = "活性材料弹", InitialRadius = 30, CoreDensity = 2.0,
|
||||||
DispersionRateBase = 20, MaxRadius = 800, MaxDuration = 120,
|
DispersionRateBase = 3, MaxRadius = 400, MaxDuration = 90,
|
||||||
EffectiveConcentration = 0.05,
|
EffectiveConcentration = 0.1,
|
||||||
});
|
});
|
||||||
_mainDb.Insert(new AmmunitionSpec
|
_mainDb.Insert(new AmmunitionSpec
|
||||||
{
|
{
|
||||||
@ -78,17 +78,6 @@ namespace CounterDrone.Core.Tests
|
|||||||
if (detail.Equipment.Any(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
|
if (detail.Equipment.Any(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
|
||||||
{
|
{
|
||||||
var schedule = BuildFireSchedule(detail);
|
var schedule = BuildFireSchedule(detail);
|
||||||
// 诊断:如果计划为空,检查原始数据
|
|
||||||
if (schedule.Count == 0)
|
|
||||||
{
|
|
||||||
var c = detail.Cloud;
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"FireSchedule empty. " +
|
|
||||||
$"RecommendedTiming={c.RecommendedTiming}, " +
|
|
||||||
$"CloudPos=({c.PositionX},{c.PositionY},{c.PositionZ}), " +
|
|
||||||
$"PlatformCount={detail.Equipment.Count(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform)}, " +
|
|
||||||
$"EquipCount={detail.Equipment.Count}");
|
|
||||||
}
|
|
||||||
engine.SetFireSchedule(schedule);
|
engine.SetFireSchedule(schedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +92,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>从推荐方案的时机参数生成发射计划</summary>
|
/// <summary>从推荐方案的时机参数生成发射计划</summary>
|
||||||
private static List<FireEvent> BuildFireSchedule(TaskFullConfig detail)
|
private List<FireEvent> BuildFireSchedule(TaskFullConfig detail)
|
||||||
{
|
{
|
||||||
var schedule = new List<FireEvent>();
|
var schedule = new List<FireEvent>();
|
||||||
var cloud = detail.Cloud;
|
var cloud = detail.Cloud;
|
||||||
@ -114,29 +103,53 @@ namespace CounterDrone.Core.Tests
|
|||||||
var platforms = detail.Equipment
|
var platforms = detail.Equipment
|
||||||
.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform)
|
.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
if (platforms.Count == 0) return schedule;
|
||||||
|
|
||||||
for (int i = 0; i < platforms.Count; i++)
|
// 从弹药规格库计算需要几发、间隔多少
|
||||||
|
var aerosolType = (AerosolType)cloud.AerosolType;
|
||||||
|
var ammo = _ammoCatalog.FirstOrDefault(a => a.AerosolType == (int)aerosolType);
|
||||||
|
if (ammo == null) return schedule;
|
||||||
|
|
||||||
|
var target = detail.Targets.FirstOrDefault();
|
||||||
|
var avgSpeed = target != null ? (float)target.TypicalSpeed / 3.6f : 50f;
|
||||||
|
var neededExposure = aerosolType == AerosolType.ActiveMaterial ? 2f : 6f;
|
||||||
|
var spacing = (float)ammo.InitialRadius * 1.5f;
|
||||||
|
|
||||||
|
// 与推荐算法一致:有效覆盖距离 = 间距×(N-1) + 直径
|
||||||
|
var requiredCoverage = neededExposure * avgSpeed;
|
||||||
|
var diameter = 2f * (float)ammo.InitialRadius;
|
||||||
|
var roundsNeeded = Math.Max(1,
|
||||||
|
(int)Math.Ceiling((requiredCoverage - diameter) / spacing) + 1);
|
||||||
|
|
||||||
|
var p = platforms[0];
|
||||||
|
var muzzleV = (float)(p.MuzzleVelocity ?? 800);
|
||||||
|
var releaseAlt = (float)cloud.DisperseHeight;
|
||||||
|
if (releaseAlt < 10) releaseAlt = 300f;
|
||||||
|
|
||||||
|
// 多枚同时发射,线性排列扩大有效覆盖
|
||||||
|
for (int r = 0; r < roundsNeeded; r++)
|
||||||
{
|
{
|
||||||
var p = platforms[i];
|
var offset = (r - (roundsNeeded - 1) / 2f) * spacing;
|
||||||
var muzzleV = (float)(p.MuzzleVelocity ?? 800);
|
var tx = (float)cloud.PositionX + offset;
|
||||||
|
var ty = (float)cloud.PositionY;
|
||||||
|
var tz = (float)cloud.PositionZ;
|
||||||
|
|
||||||
// 弹药飞行时间 ≈ 距离 / 初速(简化)
|
var dx = tx - (float)p.PositionX;
|
||||||
var dx = (float)cloud.PositionX - (float)p.PositionX;
|
var dz = tz - (float)p.PositionZ;
|
||||||
var dz = (float)cloud.PositionZ - (float)p.PositionZ;
|
var dist = (float)Math.Sqrt(dx * dx + dz * dz);
|
||||||
var dist = (float)System.Math.Sqrt(dx * dx + dz * dz);
|
|
||||||
var flightTime = dist / muzzleV;
|
|
||||||
|
|
||||||
// 发射时间 = 推荐云团出现时间 - 弹药飞行时间
|
// 用真实弹道算飞行时间
|
||||||
var fireTime = (float)cloud.RecommendedTiming.Value - flightTime;
|
var launchAngle = Kinematics.CalculateLaunchAngle(dist, muzzleV, releaseAlt);
|
||||||
|
var flightTime = EstimateFlightTime(dist, muzzleV, launchAngle, releaseAlt);
|
||||||
|
|
||||||
|
var fireTime = (float)cloud.RecommendedTiming!.Value - flightTime;
|
||||||
if (fireTime < 0) fireTime = 0.1f;
|
if (fireTime < 0) fireTime = 0.1f;
|
||||||
|
|
||||||
schedule.Add(new FireEvent
|
schedule.Add(new FireEvent
|
||||||
{
|
{
|
||||||
FireTime = fireTime,
|
FireTime = fireTime,
|
||||||
PlatformIndex = i,
|
PlatformIndex = r % platforms.Count,
|
||||||
TargetX = (float)cloud.PositionX,
|
TargetX = tx, TargetY = ty, TargetZ = tz,
|
||||||
TargetY = (float)cloud.PositionY,
|
|
||||||
TargetZ = (float)cloud.PositionZ,
|
|
||||||
MuzzleVelocity = muzzleV,
|
MuzzleVelocity = muzzleV,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -144,6 +157,20 @@ namespace CounterDrone.Core.Tests
|
|||||||
return schedule;
|
return schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>估算炮弹从发射到下降至释放高度的总飞行时间</summary>
|
||||||
|
private static float EstimateFlightTime(float range, float v0, float launchAngle, float releaseAlt)
|
||||||
|
{
|
||||||
|
var sinA = (float)Math.Sin(launchAngle);
|
||||||
|
// 上升至顶点时间:t_up = v0*sinθ/g
|
||||||
|
var tUp = v0 * sinA / 9.81f;
|
||||||
|
// 从顶点下降到释放高度时间:0.5*g*t² = v0²*sin²θ/(2g) - releaseAlt
|
||||||
|
var peakH = v0 * v0 * sinA * sinA / (2f * 9.81f);
|
||||||
|
var dropH = peakH - releaseAlt;
|
||||||
|
if (dropH < 0) dropH = 0;
|
||||||
|
var tDown = (float)Math.Sqrt(2f * dropH / 9.81f);
|
||||||
|
return tUp + tDown;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>将 DefenseAdvisor 推荐方案写入想定</summary>
|
/// <summary>将 DefenseAdvisor 推荐方案写入想定</summary>
|
||||||
private DefenseRecommendation GetAndApplyRecommendation()
|
private DefenseRecommendation GetAndApplyRecommendation()
|
||||||
{
|
{
|
||||||
@ -177,8 +204,8 @@ namespace CounterDrone.Core.Tests
|
|||||||
PositionX = p.Position.X, PositionY = p.Position.Y, PositionZ = p.Position.Z,
|
PositionX = p.Position.X, PositionY = p.Position.Y, PositionZ = p.Position.Z,
|
||||||
AerosolType = (int)rec.Best.RecommendedAerosolType,
|
AerosolType = (int)rec.Best.RecommendedAerosolType,
|
||||||
MunitionCount = p.MunitionCount,
|
MunitionCount = p.MunitionCount,
|
||||||
Cooldown = 5,
|
Cooldown = p.Cooldown,
|
||||||
MuzzleVelocity = p.Type == PlatformType.GroundBased ? 800 : (double?)null,
|
MuzzleVelocity = p.MuzzleVelocity,
|
||||||
});
|
});
|
||||||
_scenario.SaveDeployment(_taskId, equips);
|
_scenario.SaveDeployment(_taskId, equips);
|
||||||
|
|
||||||
@ -306,10 +333,19 @@ namespace CounterDrone.Core.Tests
|
|||||||
Assert.True(rec.Best.InterceptProbability > 0);
|
Assert.True(rec.Best.InterceptProbability > 0);
|
||||||
Assert.True(rec.Best.RecommendedCloud.RecommendedTiming > 0);
|
Assert.True(rec.Best.RecommendedCloud.RecommendedTiming > 0);
|
||||||
|
|
||||||
var eng = RunSimulation(8000); // 200 km/h, 20km, ~360s 航程,需充足时间
|
var eng = RunSimulation(8000);
|
||||||
|
|
||||||
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
||||||
Assert.True(launched > 0, $"Piston: 应发射弹药,实际{launched}次");
|
var cloudsGenerated = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
||||||
Assert.Equal(DroneStatus.Destroyed, eng.Drones[0].Status);
|
var drone = eng.Drones[0];
|
||||||
|
|
||||||
|
var msg = $"Piston: guns={platforms.Count}, launched={launched}, clouds={cloudsGenerated}, " +
|
||||||
|
$"droneStatus={drone.Status}, hp={drone.Hp:F3}, posX={drone.PosX:F0}, " +
|
||||||
|
$"cloudCount={eng.Clouds.Count}";
|
||||||
|
|
||||||
|
Assert.True(launched > 0, msg);
|
||||||
|
Assert.True(cloudsGenerated > 0, msg);
|
||||||
|
Assert.True(drone.Status == DroneStatus.Destroyed, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════
|
// ═══════════════════════════════════════════════
|
||||||
@ -346,10 +382,21 @@ namespace CounterDrone.Core.Tests
|
|||||||
Assert.True(rec.Best.InterceptProbability > 0);
|
Assert.True(rec.Best.InterceptProbability > 0);
|
||||||
Assert.True(rec.Best.RecommendedCloud.RecommendedTiming > 0);
|
Assert.True(rec.Best.RecommendedCloud.RecommendedTiming > 0);
|
||||||
|
|
||||||
var eng = RunSimulation(8000); // 500 km/h, 30km, ~216s 航程
|
var jetPlatforms = saved.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform).ToList();
|
||||||
|
|
||||||
|
var eng = RunSimulation(8000);
|
||||||
|
|
||||||
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
||||||
Assert.True(launched > 0, $"Jet: 应发射弹药,实际{launched}次");
|
var cloudsGenerated = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
||||||
Assert.Equal(DroneStatus.Destroyed, eng.Drones[0].Status);
|
var drone = eng.Drones[0];
|
||||||
|
|
||||||
|
var msg = $"Jet: guns={jetPlatforms.Count}, launched={launched}, clouds={cloudsGenerated}, " +
|
||||||
|
$"droneStatus={drone.Status}, hp={drone.Hp:F3}, posX={drone.PosX:F0}, " +
|
||||||
|
$"cloudCount={eng.Clouds.Count}";
|
||||||
|
|
||||||
|
Assert.True(launched > 0, msg);
|
||||||
|
Assert.True(cloudsGenerated > 0, msg);
|
||||||
|
Assert.True(drone.Status == DroneStatus.Destroyed, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
using CounterDrone.Core.Algorithms;
|
||||||
using CounterDrone.Core.Models;
|
using CounterDrone.Core.Models;
|
||||||
using CounterDrone.Core.Simulation;
|
using CounterDrone.Core.Simulation;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -9,7 +10,6 @@ namespace CounterDrone.Core.Tests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void GroundBased_DoesNotArriveImmediately()
|
public void GroundBased_DoesNotArriveImmediately()
|
||||||
{
|
{
|
||||||
// 500 m/s, 5000m 距离, 300m 释放高度 — 可以到达
|
|
||||||
var m = new MunitionEntity("m1", PlatformType.GroundBased,
|
var m = new MunitionEntity("m1", PlatformType.GroundBased,
|
||||||
AerosolType.InertGas,
|
AerosolType.InertGas,
|
||||||
startX: 0, startY: 0, startZ: 0,
|
startX: 0, startY: 0, startZ: 0,
|
||||||
@ -21,24 +21,44 @@ namespace CounterDrone.Core.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GroundBased_ArrivesAfterPeakAndDescent()
|
public void GroundBased_ArrivesAtTargetPosition()
|
||||||
{
|
{
|
||||||
|
// 验证炮弹到达时,水平位置接近目标
|
||||||
var m = new MunitionEntity("m1", PlatformType.GroundBased,
|
var m = new MunitionEntity("m1", PlatformType.GroundBased,
|
||||||
AerosolType.InertGas,
|
AerosolType.InertGas,
|
||||||
startX: 0, startY: 0, startZ: 0,
|
startX: 0, startY: 0, startZ: 0,
|
||||||
targetX: 5000, targetY: 300, targetZ: 0,
|
targetX: 5000, targetY: 300, targetZ: 0,
|
||||||
muzzleVelocity: 500f, releaseAltitude: 300f);
|
muzzleVelocity: 500f, releaseAltitude: 300f);
|
||||||
|
|
||||||
// 500m/s 到 5000m,约需 10-15 秒
|
|
||||||
for (int i = 0; i < 500; i++)
|
for (int i = 0; i < 500; i++)
|
||||||
{
|
{
|
||||||
m.Update(0.05f);
|
m.Update(0.05f);
|
||||||
if (m.HasArrived) break;
|
if (m.HasArrived) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.True(m.HasArrived, "炮弹应在飞行后到达释放高度");
|
Assert.True(m.HasArrived, "炮弹应到达");
|
||||||
// 到达时高度应接近释放高度
|
// 到达释放高度
|
||||||
Assert.True(System.Math.Abs(m.PosY - 300f) < 5f, $"PosY={m.PosY} 应接近 300");
|
Assert.True(System.Math.Abs(m.PosY - 300f) < 5f, $"PosY={m.PosY:F1} 应≈300");
|
||||||
|
// 水平位置接近目标
|
||||||
|
var dist = System.Math.Sqrt(
|
||||||
|
(m.PosX - m.TargetX) * (m.PosX - m.TargetX) +
|
||||||
|
(m.PosZ - m.TargetZ) * (m.PosZ - m.TargetZ));
|
||||||
|
Assert.True(dist < 1000f, $"到达点距目标 {dist:F0}m,应<1000m");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GroundBased_LaunchAngle_ReachesReleaseAltitude()
|
||||||
|
{
|
||||||
|
// 验证运动学:给定距离和初速,弹道能达到释放高度
|
||||||
|
var range = 5000f;
|
||||||
|
var v0 = 500f;
|
||||||
|
var releaseAlt = 300f;
|
||||||
|
var angle = Kinematics.CalculateLaunchAngle(range, v0, releaseAlt);
|
||||||
|
|
||||||
|
// 弹道顶点高度 >= 释放高度
|
||||||
|
var peakH = v0 * v0 * (float)System.Math.Sin(angle) * (float)System.Math.Sin(angle) / (2f * 9.81f);
|
||||||
|
Assert.True(peakH >= releaseAlt,
|
||||||
|
$"弹道顶点 {peakH:F0}m ≥ 释放高度 {releaseAlt}m, angle={angle * 180 / System.Math.PI:F1}°");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@ -12,6 +12,7 @@ using Xunit;
|
|||||||
|
|
||||||
namespace CounterDrone.Core.Tests
|
namespace CounterDrone.Core.Tests
|
||||||
{
|
{
|
||||||
|
/// <summary>仿真引擎基础单元测试(不含集成场景,集成场景见 FullPipelineTests)</summary>
|
||||||
public class SimulationEngineTests : IDisposable
|
public class SimulationEngineTests : IDisposable
|
||||||
{
|
{
|
||||||
private readonly string _testDir;
|
private readonly string _testDir;
|
||||||
@ -27,31 +28,20 @@ namespace CounterDrone.Core.Tests
|
|||||||
var dbManager = new DatabaseManager(paths);
|
var dbManager = new DatabaseManager(paths);
|
||||||
_mainDb = dbManager.OpenMainDb();
|
_mainDb = dbManager.OpenMainDb();
|
||||||
|
|
||||||
// 插入测试弹药
|
|
||||||
_mainDb.Insert(new AmmunitionSpec
|
_mainDb.Insert(new AmmunitionSpec
|
||||||
{
|
{
|
||||||
AerosolType = (int)AerosolType.InertGas,
|
AerosolType = (int)AerosolType.InertGas, Name = "Test Ammo",
|
||||||
Name = "Test Ammo",
|
InitialRadius = 50, CoreDensity = 1.0, DispersionRateBase = 5,
|
||||||
InitialRadius = 50,
|
MaxRadius = 500, MaxDuration = 120, EffectiveConcentration = 0.05,
|
||||||
CoreDensity = 1.0,
|
|
||||||
DispersionRateBase = 5,
|
|
||||||
MaxRadius = 500,
|
|
||||||
MaxDuration = 120,
|
|
||||||
EffectiveConcentration = 0.05,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_scenarioService = new ScenarioService(
|
_scenarioService = new ScenarioService(
|
||||||
new SimTaskRepository(_mainDb),
|
new SimTaskRepository(_mainDb), new CombatSceneRepository(_mainDb),
|
||||||
new CombatSceneRepository(_mainDb),
|
new ControlZoneRepository(_mainDb), new TargetConfigRepository(_mainDb),
|
||||||
new ControlZoneRepository(_mainDb),
|
new EquipmentDeploymentRepository(_mainDb), new CloudDispersalRepository(_mainDb),
|
||||||
new TargetConfigRepository(_mainDb),
|
new RoutePlanRepository(_mainDb), new WaypointRepository(_mainDb));
|
||||||
new EquipmentDeploymentRepository(_mainDb),
|
|
||||||
new CloudDispersalRepository(_mainDb),
|
|
||||||
new RoutePlanRepository(_mainDb),
|
|
||||||
new WaypointRepository(_mainDb));
|
|
||||||
|
|
||||||
var frameStore = new FrameDataStore(paths);
|
_engine = new SimulationEngine(_scenarioService, new FrameDataStore(paths),
|
||||||
_engine = new SimulationEngine(_scenarioService, frameStore,
|
|
||||||
new DamageModelRouter(), paths);
|
new DamageModelRouter(), paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,52 +49,41 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
_engine.Stop();
|
_engine.Stop();
|
||||||
_mainDb?.Close();
|
_mainDb?.Close();
|
||||||
if (Directory.Exists(_testDir))
|
if (Directory.Exists(_testDir)) Directory.Delete(_testDir, true);
|
||||||
Directory.Delete(_testDir, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupScenario(string name = "Test Scenario")
|
private void SetupScenario()
|
||||||
{
|
{
|
||||||
var task = _scenarioService.CreateTask(name, "");
|
var task = _scenarioService.CreateTask("Test", "");
|
||||||
_taskId = task.Id;
|
_taskId = task.Id;
|
||||||
|
_scenarioService.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||||
_scenarioService.SaveScene(_taskId, new CombatScene
|
|
||||||
{
|
|
||||||
SceneType = (int)SceneType.Plain,
|
|
||||||
WindSpeed = 0,
|
|
||||||
WindDirection = (int)WindDirection.N,
|
|
||||||
Temperature = 20,
|
|
||||||
SceneWidth = 10000,
|
|
||||||
SceneLength = 20000,
|
|
||||||
});
|
|
||||||
|
|
||||||
_scenarioService.SaveTarget(_taskId, new TargetConfig
|
_scenarioService.SaveTarget(_taskId, new TargetConfig
|
||||||
{
|
{
|
||||||
TargetType = (int)TargetType.Piston,
|
TargetType = (int)TargetType.Piston, Quantity = 1,
|
||||||
Quantity = 1,
|
PowerType = (int)PowerType.Piston, TypicalSpeed = 60,
|
||||||
PowerType = (int)PowerType.Piston,
|
});
|
||||||
TypicalSpeed = 60,
|
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
||||||
TypicalAltitude = 300,
|
{
|
||||||
|
new EquipmentDeployment
|
||||||
|
{
|
||||||
|
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
|
||||||
|
PlatformType = (int)PlatformType.GroundBased,
|
||||||
|
Quantity = 1, PositionX = 0, PositionY = 0, PositionZ = 0,
|
||||||
|
AerosolType = (int)AerosolType.InertGas, MunitionCount = 1, Cooldown = 5,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>()); // 无防御
|
|
||||||
|
|
||||||
_scenarioService.SaveCloudDispersal(_taskId, new CloudDispersal
|
_scenarioService.SaveCloudDispersal(_taskId, new CloudDispersal
|
||||||
{
|
{
|
||||||
AerosolType = (int)AerosolType.InertGas,
|
AerosolType = (int)AerosolType.InertGas, DisperseHeight = 300, Duration = 60,
|
||||||
DisperseHeight = 300,
|
RecommendedTiming = 10, PositionX = 1000, PositionY = 300, PositionZ = 0,
|
||||||
Duration = 60,
|
PositionMode = (int)PositionMode.AlgorithmRecommended,
|
||||||
PositionX = 5000, PositionY = 0, PositionZ = 300,
|
|
||||||
});
|
|
||||||
|
|
||||||
_scenarioService.SaveRoute(_taskId, new RoutePlan
|
|
||||||
{
|
|
||||||
FormationMode = (int)FormationMode.Single,
|
|
||||||
}, new List<Waypoint>
|
|
||||||
{
|
|
||||||
new Waypoint { PosX = 0, PosY = 0, PosZ = 100, Altitude = 300, Speed = 60 },
|
|
||||||
new Waypoint { PosX = 10000, PosY = 0, PosZ = 100, Altitude = 300, Speed = 60 },
|
|
||||||
});
|
});
|
||||||
|
_scenarioService.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
|
new List<Waypoint>
|
||||||
|
{
|
||||||
|
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },
|
||||||
|
new Waypoint { PosX = 10000, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -112,267 +91,17 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_taskId);
|
||||||
|
|
||||||
Assert.Single(_engine.Drones);
|
Assert.Single(_engine.Drones);
|
||||||
Assert.NotEmpty(_engine.Drones);
|
|
||||||
Assert.Equal(SimulationState.Running, _engine.State);
|
Assert.Equal(SimulationState.Running, _engine.State);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Tick_DroneMovesAlongRoute()
|
public void Tick_DroneMoves()
|
||||||
{
|
{
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_taskId);
|
||||||
|
_engine.Tick(1f / 20f);
|
||||||
// 无人机起点为(0,0,100)
|
Assert.True(_engine.Drones[0].PosX > 0);
|
||||||
var initialPos = (_engine.Drones[0].PosX, _engine.Drones[0].PosY);
|
|
||||||
|
|
||||||
// 跑 100 帧
|
|
||||||
for (int i = 0; i < 100; i++)
|
|
||||||
_engine.Tick(1f / 20f);
|
|
||||||
|
|
||||||
// 应该移动了
|
|
||||||
Assert.True(_engine.Drones[0].PosX > 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Tick_ProducesEvents()
|
|
||||||
{
|
|
||||||
SetupScenario();
|
|
||||||
_engine.Initialize(_taskId);
|
|
||||||
|
|
||||||
var result = _engine.Tick(1f / 20f);
|
|
||||||
|
|
||||||
// 探测到目标后应有事件(可能不在第一帧)
|
|
||||||
Assert.True(result.NewEvents.Count >= 0); // 至少不崩溃
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 端到端集成测试 ==========
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FullScenario_NoDefense_DroneReachesTarget()
|
|
||||||
{
|
|
||||||
var task = _scenarioService.CreateTask("无防御测试", "");
|
|
||||||
_taskId = task.Id;
|
|
||||||
|
|
||||||
_scenarioService.SaveScene(_taskId, new CombatScene
|
|
||||||
{
|
|
||||||
SceneType = (int)SceneType.Plain, WindSpeed = 0,
|
|
||||||
WindDirection = (int)WindDirection.N,
|
|
||||||
SceneWidth = 5000, SceneLength = 5000,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveTarget(_taskId, new TargetConfig
|
|
||||||
{
|
|
||||||
TargetType = (int)TargetType.Piston, Quantity = 1,
|
|
||||||
PowerType = (int)PowerType.Piston,
|
|
||||||
TypicalSpeed = 120, TypicalAltitude = 300,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
|
||||||
{
|
|
||||||
new EquipmentDeployment
|
|
||||||
{
|
|
||||||
EquipmentRole = (int)EquipmentRole.Detection,
|
|
||||||
Quantity = 1, DetectionRadius = 10000,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
_scenarioService.SaveCloudDispersal(_taskId, new CloudDispersal
|
|
||||||
{
|
|
||||||
AerosolType = (int)AerosolType.InertGas, Duration = 60,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveRoute(_taskId, new RoutePlan
|
|
||||||
{
|
|
||||||
FormationMode = (int)FormationMode.Single,
|
|
||||||
}, new List<Waypoint>
|
|
||||||
{
|
|
||||||
new Waypoint { PosX = 0, PosY = 0, PosZ = 100, Altitude = 300, Speed = 120 },
|
|
||||||
new Waypoint { PosX = 1000, PosY = 0, PosZ = 100, Altitude = 300, Speed = 120 },
|
|
||||||
});
|
|
||||||
|
|
||||||
_engine.Initialize(_taskId);
|
|
||||||
|
|
||||||
for (int i = 0; i < 800; i++)
|
|
||||||
{
|
|
||||||
var r = _engine.Tick(1f / 20f);
|
|
||||||
if (_engine.Drones[0].Status == DroneStatus.ReachedTarget) break;
|
|
||||||
if (r.State == SimulationState.Completed) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.Equal(DroneStatus.ReachedTarget, _engine.Drones[0].Status);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FullScenario_InterceptSuccess_DroneDestroyed()
|
|
||||||
{
|
|
||||||
var task = _scenarioService.CreateTask("拦截测试", "");
|
|
||||||
_taskId = task.Id;
|
|
||||||
|
|
||||||
_scenarioService.SaveScene(_taskId, new CombatScene
|
|
||||||
{
|
|
||||||
SceneType = (int)SceneType.Plain, WindSpeed = 0,
|
|
||||||
WindDirection = (int)WindDirection.N,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveTarget(_taskId, new TargetConfig
|
|
||||||
{
|
|
||||||
TargetType = (int)TargetType.Piston, Quantity = 1,
|
|
||||||
PowerType = (int)PowerType.Piston,
|
|
||||||
TypicalSpeed = 60, TypicalAltitude = 300,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
|
||||||
{
|
|
||||||
new EquipmentDeployment
|
|
||||||
{
|
|
||||||
EquipmentRole = (int)EquipmentRole.Detection,
|
|
||||||
Quantity = 1, DetectionRadius = 20000,
|
|
||||||
},
|
|
||||||
new EquipmentDeployment
|
|
||||||
{
|
|
||||||
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
|
|
||||||
PlatformType = (int)PlatformType.GroundBased,
|
|
||||||
Quantity = 2,
|
|
||||||
PositionX = 0, PositionY = 0, PositionZ = 0,
|
|
||||||
AerosolType = (int)AerosolType.InertGas,
|
|
||||||
MunitionCount = 10, Cooldown = 0.2f,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
_scenarioService.SaveCloudDispersal(_taskId, new CloudDispersal
|
|
||||||
{
|
|
||||||
AerosolType = (int)AerosolType.InertGas,
|
|
||||||
DisperseHeight = 300, Duration = 60,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveRoute(_taskId, new RoutePlan
|
|
||||||
{
|
|
||||||
FormationMode = (int)FormationMode.Single,
|
|
||||||
}, new List<Waypoint>
|
|
||||||
{
|
|
||||||
new Waypoint { PosX = 0, PosY = 0, PosZ = 100, Altitude = 300, Speed = 60 },
|
|
||||||
new Waypoint { PosX = 5000, PosY = 0, PosZ = 100, Altitude = 300, Speed = 60 },
|
|
||||||
});
|
|
||||||
|
|
||||||
_engine.Initialize(_taskId);
|
|
||||||
|
|
||||||
var destroyed = false;
|
|
||||||
var cloudsGenerated = false;
|
|
||||||
for (int i = 0; i < 2000 && !destroyed; i++)
|
|
||||||
{
|
|
||||||
var r = _engine.Tick(1f / 20f);
|
|
||||||
if (r.NewEvents.Any(e => e.Type == SimEventType.CloudGenerated))
|
|
||||||
cloudsGenerated = true;
|
|
||||||
if (_engine.Drones[0].Status == DroneStatus.Destroyed)
|
|
||||||
destroyed = true;
|
|
||||||
if (r.State == SimulationState.Completed) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.True(cloudsGenerated, "应该生成了云团");
|
|
||||||
Assert.True(destroyed, "无人机应被摧毁");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FullScenario_ZoneIntrusion_DroneStopped()
|
|
||||||
{
|
|
||||||
var task = _scenarioService.CreateTask("管控区测试", "");
|
|
||||||
_taskId = task.Id;
|
|
||||||
|
|
||||||
_scenarioService.SaveScene(_taskId, new CombatScene
|
|
||||||
{
|
|
||||||
SceneType = (int)SceneType.Urban, WindSpeed = 0,
|
|
||||||
WindDirection = (int)WindDirection.N,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveControlZones(_taskId, new List<Models.ControlZone>
|
|
||||||
{
|
|
||||||
new Models.ControlZone
|
|
||||||
{
|
|
||||||
Name = "核心禁飞区",
|
|
||||||
VerticesJson = "[{\"X\":300,\"Y\":0,\"Z\":0},{\"X\":700,\"Y\":0,\"Z\":0},{\"X\":700,\"Y\":0,\"Z\":400},{\"X\":300,\"Y\":0,\"Z\":400}]",
|
|
||||||
MinAltitude = 0, MaxAltitude = 1000,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
_scenarioService.SaveTarget(_taskId, new TargetConfig
|
|
||||||
{
|
|
||||||
TargetType = (int)TargetType.FixedWing, Quantity = 1,
|
|
||||||
PowerType = (int)PowerType.Jet,
|
|
||||||
TypicalSpeed = 150, TypicalAltitude = 300,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
|
||||||
{
|
|
||||||
new EquipmentDeployment
|
|
||||||
{
|
|
||||||
EquipmentRole = (int)EquipmentRole.Detection,
|
|
||||||
Quantity = 1, DetectionRadius = 10000,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
_scenarioService.SaveCloudDispersal(_taskId, new CloudDispersal());
|
|
||||||
_scenarioService.SaveRoute(_taskId, new RoutePlan
|
|
||||||
{
|
|
||||||
FormationMode = (int)FormationMode.Single,
|
|
||||||
}, new List<Waypoint>
|
|
||||||
{
|
|
||||||
new Waypoint { PosX = 0, PosY = 0, PosZ = 100, Altitude = 300, Speed = 150 },
|
|
||||||
new Waypoint { PosX = 2000, PosY = 0, PosZ = 100, Altitude = 300, Speed = 150 },
|
|
||||||
});
|
|
||||||
|
|
||||||
_engine.Initialize(_taskId);
|
|
||||||
|
|
||||||
var intruded = false;
|
|
||||||
for (int i = 0; i < 1000 && !intruded; i++)
|
|
||||||
{
|
|
||||||
var r = _engine.Tick(1f / 20f);
|
|
||||||
if (r.NewEvents.Any(e => e.Type == SimEventType.ZoneIntruded))
|
|
||||||
intruded = true;
|
|
||||||
if (_engine.Drones[0].Status == DroneStatus.ZoneIntruded)
|
|
||||||
intruded = true;
|
|
||||||
if (r.State == SimulationState.Completed) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.True(intruded, "无人机应触发管控区域侵入");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FullScenario_MultiDroneFormation()
|
|
||||||
{
|
|
||||||
var task = _scenarioService.CreateTask("编队测试", "");
|
|
||||||
_taskId = task.Id;
|
|
||||||
|
|
||||||
_scenarioService.SaveScene(_taskId, new CombatScene
|
|
||||||
{
|
|
||||||
SceneType = (int)SceneType.Plain, WindSpeed = 3,
|
|
||||||
WindDirection = (int)WindDirection.W,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveTarget(_taskId, new TargetConfig
|
|
||||||
{
|
|
||||||
TargetType = (int)TargetType.Rotor, Quantity = 3,
|
|
||||||
PowerType = (int)PowerType.Electric,
|
|
||||||
TypicalSpeed = 80, TypicalAltitude = 200,
|
|
||||||
});
|
|
||||||
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
|
||||||
{
|
|
||||||
new EquipmentDeployment
|
|
||||||
{
|
|
||||||
EquipmentRole = (int)EquipmentRole.Detection,
|
|
||||||
Quantity = 1, DetectionRadius = 5000,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
_scenarioService.SaveCloudDispersal(_taskId, new CloudDispersal());
|
|
||||||
_scenarioService.SaveRoute(_taskId, new RoutePlan
|
|
||||||
{
|
|
||||||
FormationMode = (int)FormationMode.Formation,
|
|
||||||
FormationSpacing = 50,
|
|
||||||
}, new List<Waypoint>
|
|
||||||
{
|
|
||||||
new Waypoint { PosX = 0, PosY = 0, PosZ = 100, Altitude = 200, Speed = 80 },
|
|
||||||
new Waypoint { PosX = 3000, PosY = 0, PosZ = 100, Altitude = 200, Speed = 80 },
|
|
||||||
});
|
|
||||||
|
|
||||||
_engine.Initialize(_taskId);
|
|
||||||
|
|
||||||
Assert.Equal(3, _engine.Drones.Count);
|
|
||||||
|
|
||||||
for (int i = 0; i < 100; i++)
|
|
||||||
_engine.Tick(1f / 20f);
|
|
||||||
|
|
||||||
Assert.All(_engine.Drones, d => Assert.Equal(DroneStatus.Flying, d.Status));
|
|
||||||
|
|
||||||
var yPositions = _engine.Drones.Select(d => d.PosY).ToList();
|
|
||||||
Assert.NotEqual(yPositions[0], yPositions[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -380,11 +109,9 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_taskId);
|
||||||
|
|
||||||
_engine.Pause();
|
_engine.Pause();
|
||||||
Assert.Equal(SimulationState.Paused, _engine.State);
|
Assert.Equal(SimulationState.Paused, _engine.State);
|
||||||
|
|
||||||
// Paused 时 tick 不改变状态
|
|
||||||
var result = _engine.Tick(1f);
|
var result = _engine.Tick(1f);
|
||||||
Assert.Equal(SimulationState.Paused, result.State);
|
Assert.Equal(SimulationState.Paused, result.State);
|
||||||
|
|
||||||
@ -399,7 +126,6 @@ namespace CounterDrone.Core.Tests
|
|||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_taskId);
|
||||||
_engine.Tick(1f / 20f);
|
_engine.Tick(1f / 20f);
|
||||||
_engine.Stop();
|
_engine.Stop();
|
||||||
|
|
||||||
Assert.Equal(SimulationState.Stopped, _engine.State);
|
Assert.Equal(SimulationState.Stopped, _engine.State);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,52 +134,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_taskId);
|
||||||
|
for (int i = 0; i < 10; i++) _engine.Tick(1f / 20f);
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
_engine.Tick(1f / 20f);
|
|
||||||
|
|
||||||
// 验证帧数据库文件存在
|
|
||||||
var framePath = Path.Combine(_testDir, "frames", $"{_taskId}.db");
|
var framePath = Path.Combine(_testDir, "frames", $"{_taskId}.db");
|
||||||
Assert.True(File.Exists(framePath));
|
Assert.True(File.Exists(framePath));
|
||||||
|
|
||||||
// 验证有帧数据
|
|
||||||
var frameDb = new FrameDataStore(new TestPathProvider(_testDir)).CreateOrOpen(_taskId);
|
|
||||||
var frames = frameDb.Table<SimFrameRecord>().ToList();
|
|
||||||
Assert.NotEmpty(frames);
|
|
||||||
frameDb.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Munition_IsFiredWhenTargetDetected()
|
|
||||||
{
|
|
||||||
SetupScenario();
|
|
||||||
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
|
||||||
{
|
|
||||||
new EquipmentDeployment
|
|
||||||
{
|
|
||||||
EquipmentRole = (int)EquipmentRole.Detection,
|
|
||||||
Quantity = 1,
|
|
||||||
DetectionRadius = 20000,
|
|
||||||
},
|
|
||||||
new EquipmentDeployment
|
|
||||||
{
|
|
||||||
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
|
|
||||||
PlatformType = (int)PlatformType.GroundBased,
|
|
||||||
Quantity = 2,
|
|
||||||
PositionX = 0, PositionY = 0, PositionZ = 0,
|
|
||||||
AerosolType = (int)AerosolType.InertGas,
|
|
||||||
MunitionCount = 5,
|
|
||||||
Cooldown = 0.1f,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
_engine.Initialize(_taskId);
|
|
||||||
|
|
||||||
// 跑几帧让探测到并发射
|
|
||||||
for (int i = 0; i < 10; i++)
|
|
||||||
_engine.Tick(1f / 20f);
|
|
||||||
|
|
||||||
Assert.True(_engine.Munitions.Count > 0 || _engine.Clouds.Count > 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
test_output.txt
Normal file
27
test_output.txt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
正在确定要还原的项目…
|
||||||
|
所有项目均是最新的,无法还原。
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\src\CounterDrone.Core\Algorithms\AlgorithmFactory.cs(13,79): warning CS8625: 无法将 null 字面量转换为非 null 的引用类型。 [C:\Users\Tellme\apps\CounterDroneBackend\src\CounterDrone.Core\CounterDrone.Core.csproj]
|
||||||
|
CounterDrone.Core -> C:\Users\Tellme\apps\CounterDroneBackend\src\CounterDrone.Core\bin\Debug\netstandard2.1\CounterDrone.Core.dll
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\SimulationEngineTests.cs(128,17): warning CS0219: 变量“initialPos”已被赋值,但从未使用过它的值 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(121,41): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(143,48): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(156,32): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(172,29): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(194,29): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(212,26): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(244,29): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(271,56): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(306,46): warning CS8625: 无法将 null 字面量转换为非 null 的引用类型。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(311,46): warning CS8625: 无法将 null 字面量转换为非 null 的引用类型。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(324,47): warning CS8625: 无法将 null 字面量转换为非 null 的引用类型。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(327,50): warning CS8625: 无法将 null 字面量转换为非 null 的引用类型。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(341,29): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(345,29): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(354,29): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(357,29): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\ScenarioServiceTests.cs(448,36): warning CS8602: 解引用可能出现空引用。 [C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\CounterDrone.Core.Tests.csproj]
|
||||||
|
CounterDrone.Core.Tests -> C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\bin\Debug\net10.0\CounterDrone.Core.Tests.dll
|
||||||
|
C:\Users\Tellme\apps\CounterDroneBackend\test\unit\CounterDrone.Core.Tests\bin\Debug\net10.0\CounterDrone.Core.Tests.dll (.NETCoreApp,Version=v10.0)的测试运行
|
||||||
|
总共 1 个测试文件与指定模式相匹配。
|
||||||
|
|
||||||
|
已通过! - 失败: 0,通过: 1,已跳过: 0,总计: 1,持续时间: 4 s - CounterDrone.Core.Tests.dll (net10.0)
|
||||||
Loading…
Reference in New Issue
Block a user