460 lines
16 KiB
C#
460 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using CounterDrone.Core;
|
|
using CounterDrone.Core.Algorithms;
|
|
using CounterDrone.Core.Models;
|
|
using CounterDrone.Core.Repository;
|
|
using CounterDrone.Core.Services;
|
|
using CounterDrone.Core.Simulation;
|
|
using Xunit;
|
|
|
|
namespace CounterDrone.Core.Tests
|
|
{
|
|
public class SimulationEngineTests : IDisposable
|
|
{
|
|
private readonly string _testDir;
|
|
private readonly SimulationEngine _engine;
|
|
private readonly IScenarioService _scenarioService;
|
|
private readonly SQLite.SQLiteConnection _mainDb;
|
|
private string _taskId = string.Empty;
|
|
|
|
public SimulationEngineTests()
|
|
{
|
|
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
|
|
var paths = new TestPathProvider(_testDir);
|
|
var dbManager = new DatabaseManager(paths);
|
|
_mainDb = dbManager.OpenMainDb();
|
|
|
|
// 插入测试弹药
|
|
_mainDb.Insert(new AmmunitionSpec
|
|
{
|
|
AerosolType = (int)AerosolType.InertGas,
|
|
Name = "Test Ammo",
|
|
InitialRadius = 50,
|
|
CoreDensity = 1.0,
|
|
DispersionRateBase = 5,
|
|
MaxRadius = 500,
|
|
MaxDuration = 120,
|
|
EffectiveConcentration = 0.05,
|
|
});
|
|
|
|
_scenarioService = new ScenarioService(
|
|
new SimTaskRepository(_mainDb),
|
|
new CombatSceneRepository(_mainDb),
|
|
new ControlZoneRepository(_mainDb),
|
|
new TargetConfigRepository(_mainDb),
|
|
new EquipmentDeploymentRepository(_mainDb),
|
|
new CloudDispersalRepository(_mainDb),
|
|
new RoutePlanRepository(_mainDb),
|
|
new WaypointRepository(_mainDb));
|
|
|
|
var frameStore = new FrameDataStore(paths);
|
|
_engine = new SimulationEngine(_scenarioService, frameStore,
|
|
new DamageModelRouter(), paths);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_engine.Stop();
|
|
_mainDb?.Close();
|
|
if (Directory.Exists(_testDir))
|
|
Directory.Delete(_testDir, true);
|
|
}
|
|
|
|
private void SetupScenario(string name = "Test Scenario")
|
|
{
|
|
var task = _scenarioService.CreateTask(name, "");
|
|
_taskId = task.Id;
|
|
|
|
_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
|
|
{
|
|
TargetType = (int)TargetType.Piston,
|
|
Quantity = 1,
|
|
PowerType = (int)PowerType.Piston,
|
|
TypicalSpeed = 60,
|
|
TypicalAltitude = 300,
|
|
});
|
|
|
|
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>()); // 无防御
|
|
|
|
_scenarioService.SaveCloudDispersal(_taskId, new CloudDispersal
|
|
{
|
|
AerosolType = (int)AerosolType.InertGas,
|
|
DisperseHeight = 300,
|
|
Duration = 60,
|
|
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 },
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void Initialize_LoadsEntities()
|
|
{
|
|
SetupScenario();
|
|
_engine.Initialize(_taskId);
|
|
|
|
Assert.Single(_engine.Drones);
|
|
Assert.NotEmpty(_engine.Drones);
|
|
Assert.Equal(SimulationState.Running, _engine.State);
|
|
}
|
|
|
|
[Fact]
|
|
public void Tick_DroneMovesAlongRoute()
|
|
{
|
|
SetupScenario();
|
|
_engine.Initialize(_taskId);
|
|
|
|
// 无人机起点为(0,0,100)
|
|
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]
|
|
public void PauseAndResume()
|
|
{
|
|
SetupScenario();
|
|
_engine.Initialize(_taskId);
|
|
|
|
_engine.Pause();
|
|
Assert.Equal(SimulationState.Paused, _engine.State);
|
|
|
|
// Paused 时 tick 不改变状态
|
|
var result = _engine.Tick(1f);
|
|
Assert.Equal(SimulationState.Paused, result.State);
|
|
|
|
_engine.Resume();
|
|
Assert.Equal(SimulationState.Running, _engine.State);
|
|
}
|
|
|
|
[Fact]
|
|
public void Stop_ClosesGracefully()
|
|
{
|
|
SetupScenario();
|
|
_engine.Initialize(_taskId);
|
|
_engine.Tick(1f / 20f);
|
|
_engine.Stop();
|
|
|
|
Assert.Equal(SimulationState.Stopped, _engine.State);
|
|
}
|
|
|
|
[Fact]
|
|
public void FrameData_IsWritten()
|
|
{
|
|
SetupScenario();
|
|
_engine.Initialize(_taskId);
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
_engine.Tick(1f / 20f);
|
|
|
|
// 验证帧数据库文件存在
|
|
var framePath = Path.Combine(_testDir, "frames", $"{_taskId}.db");
|
|
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);
|
|
}
|
|
}
|
|
}
|