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 SQLite; using Xunit; namespace CounterDrone.Core.Tests { public class FullPipelineTests : IDisposable { private readonly string _testDir; private readonly IPathProvider _paths; private readonly IScenarioService _scenario; private readonly FrameDataStore _frameStore; private readonly SQLiteConnection _mainDb; private List _ammoCatalog; private string _taskId = string.Empty; public FullPipelineTests() { _testDir = Path.Combine(Path.GetTempPath(), $"cd_full_{Guid.NewGuid():N}"); _paths = new TestPathProvider(_testDir); var dbm = new DatabaseManager(_paths); _mainDb = dbm.OpenMainDb(); foreach (var a in DefaultAmmunition.GetAll()) _mainDb.Insert(a); _ammoCatalog = new List(DefaultAmmunition.GetAll()); _scenario = 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)); _frameStore = new FrameDataStore(_paths); } public void Dispose() { _mainDb?.Close(); if (Directory.Exists(_testDir)) Directory.Delete(_testDir, true); } private SimulationEngine RunSimulation(int maxTicks, float tickDt = 1f / 20f, float timeScale = 8f) { var engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefaultDefensePlanner(_ammoCatalog)); engine.Initialize(_taskId); engine.TimeScale = timeScale; // 8倍速加速 for (int i = 0; i < maxTicks; i++) { var r = engine.Tick(tickDt); if (r.State == SimulationState.Completed) break; } engine.Stop(); return engine; } private static readonly string ReportOutputDir = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "..", "..", "reports"); private static EquipmentDeployment MakeEquipment(FireUnitTemplate template, AerosolType ammoType, int quantity, double posX, double posY, double posZ) { return new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.LaunchPlatform, PlatformType = template.PlatformType, Quantity = quantity, PositionX = posX, PositionY = posY, PositionZ = posZ, AerosolType = (int)ammoType, MunitionCount = template.GunCount * template.ChannelsPerGun, GunCount = template.GunCount, ChannelsPerGun = template.ChannelsPerGun, ChannelInterval = template.ChannelInterval, Cooldown = template.Cooldown, MuzzleVelocity = template.MuzzleVelocity > 0 ? template.MuzzleVelocity : null, CruiseSpeed = template.CruiseSpeed > 0 ? template.CruiseSpeed : null, ReleaseAltitude = template.ReleaseAltitude > 0 ? template.ReleaseAltitude : null, }; } private void VerifyAndExportReport(SimulationEngine eng, DroneEntity drone) { var detail = _scenario.GetTaskDetail(_taskId)!; var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime); Assert.Contains("对抗结果", report); Assert.Contains(detail.Task.Name, report); var svc = new ReportService(_mainDb, _paths); var r = svc.Generate(_taskId, detail, eng.Events.ToList(), drone.Status.ToString(), eng.SimulationTime); svc.ExportToFile(r.Id, ReportOutputDir); // 持久化目录 } // ═══════════════════════════════════════════════ // 场景 1:无防御 — 无人机到达目标 // ═══════════════════════════════════════════════ [Fact] public void Scenario_NoDefense_DroneReachesTarget() { var task = _scenario.CreateTask("无防御测试", ""); _taskId = task.Id; _scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 }); _scenario.SaveTarget(_taskId, new TargetConfig { GroupId = "default", TargetType = (int)TargetType.FixedWing, PowerType = (int)PowerType.Jet, Quantity = 1, TypicalSpeed = 600, TypicalAltitude = 400, }); _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 400, PosZ = 0, Speed = 600 }, new Waypoint { PosX = 3000, PosY = 400, PosZ = 0, Speed = 600 }, }); _scenario.SaveCloudDispersal(_taskId, new CloudDispersal()); _scenario.SaveDeployment(_taskId, new List()); var eng = RunSimulation(400); Assert.Equal(DroneStatus.ReachedTarget, eng.Drones[0].Status); } // ═══════════════════════════════════════════════ // 场景 2:管控区域侵入 // ═══════════════════════════════════════════════ [Fact] public void Scenario_ZoneIntrusion_DroneMarked() { var task = _scenario.CreateTask("管控区侵入测试", ""); _taskId = task.Id; _scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 }); _scenario.SaveTarget(_taskId, new TargetConfig { GroupId = "default", TargetType = (int)TargetType.Electric, PowerType = (int)PowerType.Electric, Quantity = 1, TypicalSpeed = 300, TypicalAltitude = 300, }); _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Speed = 300 }, new Waypoint { PosX = 3000, PosY = 300, PosZ = 0, Speed = 300 }, }); _scenario.SaveCloudDispersal(_taskId, new CloudDispersal()); _scenario.SaveDeployment(_taskId, new List()); _scenario.SaveControlZones(_taskId, new List { new Models.ControlZone { Name = "禁飞区", VerticesJson = "[{\"X\":1200,\"Y\":0,\"Z\":-200},{\"X\":1800,\"Y\":0,\"Z\":-200},{\"X\":1800,\"Y\":0,\"Z\":200},{\"X\":1200,\"Y\":0,\"Z\":200}]", MinAltitude = 0, MaxAltitude = 1000, }, }); var eng = RunSimulation(400); Assert.Equal(DroneStatus.ZoneIntruded, eng.Drones[0].Status); } // ═══════════════════════════════════════════════ // 场景 3:活塞式 200km/h,惰性气体拦截 // ═══════════════════════════════════════════════ [Fact] public void Scenario_Piston_InertGasIntercept() { var task = _scenario.CreateTask("拦截活塞式无人机", ""); _taskId = task.Id; _scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 3, WindDirection = (int)WindDirection.W }); _scenario.SaveTarget(_taskId, new TargetConfig { GroupId = "default", TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston, Quantity = 5, // 5 架 TypicalSpeed = 200, TypicalAltitude = 500, }); _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Formation, LateralSpacing = 50, LateralCount = 5, LongitudinalCount = 1, }, new List { new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 }, new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 }, }); _scenario.SaveDeployment(_taskId, new List { MakeEquipment(DefaultFireUnits.GetById("ground-light"), AerosolType.InertGas, 5, 5000, 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 times = eng.Events.Where(e => e.Type == SimEventType.MunitionLaunched).Select(e => e.OccurredAt).OrderBy(t => t).ToList(); var timeInfo = $"times=[{times.First():F2}..{times.Last():F2}] span={times.Last()-times.First():F2}s"; var msg = $"Piston: launched={launched} clouds={clouds} drones={eng.Drones.Count} {timeInfo}"; foreach (var d in eng.Drones) msg += $" [{d.Status} hp={d.Hp:F2}]"; VerifyAndExportReport(eng, eng.Drones[0]); System.IO.File.WriteAllText(@"C:\Users\Tellme\Desktop\test_piston.txt", msg); Assert.True(launched > 0, msg); Assert.True(eng.Drones.All(d => d.Status == DroneStatus.Destroyed), msg); } // ═══════════════════════════════════════════════ // 场景 4:喷气发动机 → 算法推荐活性材料 // ═══════════════════════════════════════════════ [Fact] public void Scenario_Jet_ActiveMaterialIntercept() { var task = _scenario.CreateTask("拦截喷气式无人机", ""); _taskId = task.Id; _scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 }); _scenario.SaveTarget(_taskId, new TargetConfig { GroupId = "default", TargetType = (int)TargetType.HighSpeed, PowerType = (int)PowerType.Jet, Quantity = 1, TypicalSpeed = 500, TypicalAltitude = 800, }); _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 800, PosZ = 0, Speed = 500 }, new Waypoint { PosX = 30000, PosY = 800, PosZ = 0, Speed = 500 }, }); _scenario.SaveDeployment(_taskId, new List { MakeEquipment(DefaultFireUnits.GetById("ground-standard"), AerosolType.ActiveMaterial, 1, 8000, 0, 50), }); _scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.ActiveMaterial, DisperseHeight = 800 }); var eng = RunSimulation(8000); 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={clouds} status={drone.Status} hp={drone.Hp:F2}"; VerifyAndExportReport(eng, drone); Assert.True(launched > 0, msg); Assert.Equal(DroneStatus.Destroyed, drone.Status); } // ═══════════════════════════════════════════════ // 场景 5:空基平台 — 基于推荐方案,平台改为空基 // ═══════════════════════════════════════════════ [Fact] public void Scenario_AirBased_PlatformFliesAndDrops() { var task = _scenario.CreateTask("空基平台拦截测试", ""); _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 = 1, TypicalSpeed = 300, TypicalAltitude = 500, }); _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 300 }, new Waypoint { PosX = 15000, PosY = 500, PosZ = 0, Speed = 300 }, }); // 部署空基平台单元 _scenario.SaveDeployment(_taskId, new List { MakeEquipment(DefaultFireUnits.GetById("air-standard"), AerosolType.InertGas, 4, 6500, 2500, 0), }); _scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 }); // 验证:空基 PlatformType 正确持久化 var savedAir = _scenario.GetTaskDetail(_taskId)!; Assert.All(savedAir.Equipment, eq => Assert.Equal((int?)0, eq.PlatformType)); var eng = RunSimulation(8000); // 诊断:空基发射事件应有"空基"描述 Assert.All(eng.Events.Where(e => e.Type == SimEventType.MunitionLaunched), e => Assert.Contains("空基", e.Description)); // 验证引擎内 PlatformType Assert.NotEmpty(eng.Events); Assert.All(eng.Events.Where(e => e.Type == SimEventType.MunitionLaunched), e => Assert.Contains("空基", e.Description)); 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={clouds} status={drone.Status} hp={drone.Hp:F2}"; System.IO.File.WriteAllText(@"C:\Users\Tellme\Desktop\test_air.txt", msg); VerifyAndExportReport(eng, drone); Assert.True(launched > 0, msg); Assert.Equal(DroneStatus.Destroyed, drone.Status); } } }