完善模型属性:加 Model/Description 字段,TaskNumber→ScenarioNumber,Task→Info,Task→Scenario 方法名

- DroneProfile/DroneSpec: 加 Model、Description
- Scenario: 加 Description,TaskNumber→ScenarioNumber
- EquipmentDeployment/FireUnitSpec: 加 Description
- ScenarioConfig: Task→Info
- ScenarioService: CreateTask→CreateScenario 等方法名统一
- 238 单元测试通过
This commit is contained in:
tian 2026-06-18 15:15:41 +08:00
parent ca0ce8aa52
commit ad0eaf47da
34 changed files with 306 additions and 292 deletions

View File

@ -103,7 +103,7 @@
- **`GroupId``WaveId`**DroneProfile 四表的编组外键重命名为批次外键
- **RoutePlan 索引重命名**`(ScenarioId, GroupId)` → `(ScenarioId, WaveId)`
- **RoutePlanRepository / WaypointRepository**`GetByTaskAndGroup` → `GetByTaskAndWave`
- **RoutePlanRepository / WaypointRepository**`GetByScenarioAndGroup` → `GetByScenarioAndWave`
- **IScenarioService.SaveRoute**`groupId` 参数 → `waveId`
### Changed — 算法类型重命名

View File

@ -177,7 +177,7 @@ public class SimulationRunner : MonoBehaviour
```csharp
// === 任务 ===
enum TaskStatus { Draft = 0, Configuring = 1, Completed = 2 }
enum ScenarioStatus { Draft = 0, Configuring = 1, Completed = 2 }
// === 场景 ===
enum SceneType { Urban = 0, Plain = 1, Mountain = 2, Coast = 3 }
@ -307,7 +307,7 @@ CounterDroneBackend_Data/
|------|------|------|------|
| Id | TEXT | PK | GUID |
| Name | TEXT | NOT NULL | 任务名称 |
| TaskNumber | TEXT | UNIQUE | SIM-yyyyMMdd-xxx |
| ScenarioNumber | TEXT | UNIQUE | SIM-yyyyMMdd-xxx |
| Status | INTEGER | DEFAULT 0 | 0=草稿 1=配置中 2=已完成 |
| CurrentStep | INTEGER | DEFAULT 1 | 当前配置步骤 1-5 |
| TickRate | INTEGER | DEFAULT 20 | 仿真 Tick 频率Hz |
@ -1385,10 +1385,10 @@ interface IModelService
// === 想定管理 ===
interface IScenarioService
{
Scenario CreateTask(string name, string taskNumber);
void DeleteTask(string id);
PagedResult<Scenario> SearchTasks(string kw, DateTime? from, DateTime? to, int page, int size);
ScenarioConfig GetTaskDetail(string id);
Scenario CreateScenario(string name, string scenarioNumber);
void DeleteScenario(string id);
PagedResult<Scenario> SearchScenarios(string kw, DateTime? from, DateTime? to, int page, int size);
ScenarioConfig GetScenarioDetail(string id);
// 步骤保存
void SaveScene(string scenarioId, CombatScene scene);
@ -1425,7 +1425,7 @@ class FrameDataStore
void Flush(); // 批量落库
void Discard();
List<SimFrameRecord> ReadFrames(string scenarioId, int fromFrame, int toFrame);
void DeleteTaskDb(string scenarioId);
void DeleteScenarioDb(string scenarioId);
int CleanupExpired(); // TTL 清理RetentionDays默认 180
}

View File

@ -70,18 +70,18 @@ var demos = scenario.Search("Demo", null, null, 1, 100);
var scenarioId = demos.Items[0].Id;
// 方式 B自定义
var task = scenario.CreateTask("我的想定", "");
scenario.SaveScene(task.Id, new CombatScene { WindSpeed = 5, WindDirection = (int)WindDirection.E });
scenario.SaveTarget(task.Id, new DroneProfile { WaveId = "w1", Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500 });
scenario.SaveRoute(task.Id, "w1", new RoutePlan(), new List<Waypoint> {
var scenario = scenario.CreateScenario("我的想定", "");
scenario.SaveScene(scenario.Id, new CombatScene { WindSpeed = 5, WindDirection = (int)WindDirection.E });
scenario.SaveTarget(scenario.Id, new DroneProfile { WaveId = "w1", Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500 });
scenario.SaveRoute(scenario.Id, "w1", new RoutePlan(), new List<Waypoint> {
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
new() { PosX = 10000, PosY = 500, PosZ = 0, Speed = 200 }
});
scenario.SaveDeployment(task.Id, new List<EquipmentDeployment> {
scenario.SaveDeployment(scenario.Id, new List<EquipmentDeployment> {
new() { EquipmentRole = 1, PlatformType = 1, Quantity = 1, PositionX = 5000, AerosolType = 0, MunitionCount = 8 }
});
scenario.SaveCloud(task.Id, new CloudDispersal { AerosolType = 0, DisperseHeight = 500 });
scenario.UpdateStep(task.Id, 5);
scenario.SaveCloud(scenario.Id, new CloudDispersal { AerosolType = 0, DisperseHeight = 500 });
scenario.UpdateStep(scenario.Id, 5);
```
### 3.3 步骤 3运行仿真
@ -142,7 +142,7 @@ mgr.DataService.GetAllFireUnits() / GetAllDrones() / GetAllSensors()
mgr.DataService.GetAllEnvironments() / GetAllFormations() / GetAllRoutes()
// 想定 CRUD
mgr.CreateTask(name, "") / mgr.DeleteTask(id) / mgr.Search(kw, from, to, page, size)
mgr.CreateScenario(name, "") / mgr.DeleteScenario(id) / mgr.Search(kw, from, to, page, size)
mgr.SaveScene / SaveTarget / SaveDeployment / SaveCloud / SaveRoute / UpdateStep
mgr.GetDetail(id) → ScenarioConfig
```

View File

@ -117,7 +117,7 @@ namespace CounterDrone.Core
db.CreateTable<FormationTemplate>();
db.CreateTable<RouteTemplate>();
db.CreateIndex("Scenario", "TaskNumber");
db.CreateIndex("Scenario", "ScenarioNumber");
db.CreateIndex("ControlZone", "ScenarioId");
db.CreateIndex("DroneProfile", "ScenarioId");
db.CreateIndex("EquipmentDeployment", "ScenarioId");

View File

@ -164,6 +164,8 @@ namespace CounterDrone.Core
public string Id { get; set; } = "";
public string Name { get; set; } = "";
public int DroneType { get; set; }
public string Model { get; set; } = "";
public string Description { get; set; } = "";
public int PowerType { get; set; }
public double Wingspan { get; set; }
public double TypicalSpeed { get; set; }
@ -175,6 +177,8 @@ namespace CounterDrone.Core
{
WaveId = waveId,
DroneType = DroneType,
Model = Model,
Description = Description,
PowerType = PowerType,
Quantity = 1,
Wingspan = Wingspan,

View File

@ -14,12 +14,12 @@ namespace CounterDrone.Core
public static void Seed(IScenarioService scenario, DefaultData d)
{
// 检测旧格式任务(名称含 [Demo],但 SQL LIKE 中 [] 是通配符,搜索 "Demo"
var existing = scenario.SearchTasks("Demo", null, null, 1, 100);
var existing = scenario.SearchScenarios("Demo", null, null, 1, 100);
if (existing.TotalCount > 0)
{
// 版本升级:删除旧 Demo 任务,重新创建
foreach (var t in existing.Items)
scenario.DeleteTask(t.Id);
scenario.DeleteScenario(t.Id);
}
SeedNoDefense(scenario, d);
@ -35,7 +35,7 @@ namespace CounterDrone.Core
private static void SeedNoDefense(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 无防御-无人机抵达目标", "");
var t = s.CreateScenario("[Demo] 无防御-无人机抵达目标", "");
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "shahed").ToDroneProfile());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(), R("3km-h400", 600, d));
@ -46,7 +46,7 @@ namespace CounterDrone.Core
private static void SeedZoneIntrusion(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 管控区域侵入", "");
var t = s.CreateScenario("[Demo] 管控区域侵入", "");
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "electric-scout").ToDroneProfile());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(), R("3km-h300", 300, d));
@ -66,7 +66,7 @@ namespace CounterDrone.Core
private static void SeedPistonWindy(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 活塞拦截-西风5ms", "");
var t = s.CreateScenario("[Demo] 活塞拦截-西风5ms", "");
var scene = d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene();
scene.WindSpeed = 5;
scene.WindDirection = (int)WindDirection.W;
@ -87,7 +87,7 @@ namespace CounterDrone.Core
private static void SeedJetActiveMaterial(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 喷气式拦截-活性材料", "");
var t = s.CreateScenario("[Demo] 喷气式拦截-活性材料", "");
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "cruise-missile").ToDroneProfile());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(),
@ -105,7 +105,7 @@ namespace CounterDrone.Core
private static void SeedAirBasedWindy(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 空基拦截-东风5ms", "");
var t = s.CreateScenario("[Demo] 空基拦截-东风5ms", "");
var scene = d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene();
scene.WindSpeed = 5;
scene.WindDirection = (int)WindDirection.E;
@ -126,7 +126,7 @@ namespace CounterDrone.Core
private static void Seed3DronesAirBased(IScenarioService s, DefaultData d)
{
var t = s.CreateTask("[Demo] 3架空基编队拦截", "");
var t = s.CreateScenario("[Demo] 3架空基编队拦截", "");
var scene = d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene();
scene.WindSpeed = 5;
scene.WindDirection = (int)WindDirection.E;

View File

@ -17,6 +17,12 @@ namespace CounterDrone.Core.Models
public int DroneType { get; set; } = (int)Models.DroneType.Rotor;
/// <summary>型号</summary>
public string Model { get; set; } = string.Empty;
/// <summary>描述/用途</summary>
public string Description { get; set; } = string.Empty;
public int Quantity { get; set; } = 1;
public int PowerType { get; set; } = (int)Models.PowerType.Electric;

View File

@ -1,7 +1,7 @@
namespace CounterDrone.Core.Models
{
// === 任务 ===
public enum TaskStatus
public enum ScenarioStatus
{
Draft = 0,
Configuring = 1,

View File

@ -13,6 +13,8 @@ namespace CounterDrone.Core.Models
[Indexed]
public string ScenarioId { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int EquipmentRole { get; set; } = (int)Models.EquipmentRole.LaunchPlatform;
public int Quantity { get; set; } = 1;

View File

@ -14,9 +14,11 @@ namespace CounterDrone.Core.Models
public string Name { get; set; } = string.Empty;
[Unique]
public string TaskNumber { get; set; } = string.Empty;
public string ScenarioNumber { get; set; } = string.Empty;
public int Status { get; set; } = (int)TaskStatus.Draft;
public string Description { get; set; } = string.Empty;
public int Status { get; set; } = (int)ScenarioStatus.Draft;
public int CurrentStep { get; set; } = 1;

View File

@ -5,7 +5,7 @@ namespace CounterDrone.Core.Models
/// <summary>想定完整配置(聚合 5 步配置)</summary>
public class ScenarioConfig
{
public Scenario Task { get; set; } = new();
public Scenario Info { get; set; } = new();
public CombatScene Scene { get; set; } = new();
public List<ControlZone> ControlZones { get; set; } = new();
public List<DroneProfile> Targets { get; set; } = new();

View File

@ -13,9 +13,9 @@ namespace CounterDrone.Core.Models
[Indexed]
public string ScenarioId { get; set; } = string.Empty;
public string TaskName { get; set; } = string.Empty;
public string ScenarioName { get; set; } = string.Empty;
public string TaskNumber { get; set; } = string.Empty;
public string ScenarioNumber { get; set; } = string.Empty;
public string CompletedAt { get; set; } = string.Empty;

View File

@ -14,7 +14,7 @@ namespace CounterDrone.Core.Repository
return Db.Table<RoutePlan>().Where(r => r.ScenarioId == scenarioId).ToList();
}
public RoutePlan GetByTaskAndWave(string scenarioId, string waveId)
public RoutePlan GetByScenarioAndWave(string scenarioId, string waveId)
{
return Db.Table<RoutePlan>()
.FirstOrDefault(r => r.ScenarioId == scenarioId && r.WaveId == waveId);

View File

@ -9,9 +9,9 @@ namespace CounterDrone.Core.Repository
{
public ScenarioRepository(SQLiteConnection db) : base(db) { }
public Scenario GetByTaskNumber(string taskNumber)
public Scenario GetByScenarioNumber(string ScenarioNumber)
{
return Db.Table<Scenario>().FirstOrDefault(t => t.TaskNumber == taskNumber);
return Db.Table<Scenario>().FirstOrDefault(t => t.ScenarioNumber == ScenarioNumber);
}
public List<Scenario> Search(string keyword, string? dateFrom, string? dateTo, int offset, int limit, out int totalCount)
@ -19,7 +19,7 @@ namespace CounterDrone.Core.Repository
var query = Db.Table<Scenario>().AsQueryable();
if (!string.IsNullOrWhiteSpace(keyword))
query = query.Where(t => t.Name.Contains(keyword) || t.TaskNumber.Contains(keyword));
query = query.Where(t => t.Name.Contains(keyword) || t.ScenarioNumber.Contains(keyword));
if (!string.IsNullOrWhiteSpace(dateFrom))
query = query.Where(t => t.CreatedAt.CompareTo(dateFrom) >= 0);

View File

@ -17,7 +17,7 @@ namespace CounterDrone.Core.Repository
.ToList();
}
public List<Waypoint> GetByTaskAndWave(string scenarioId, string waveId)
public List<Waypoint> GetByScenarioAndWave(string scenarioId, string waveId)
{
return Db.Table<Waypoint>()
.Where(w => w.ScenarioId == scenarioId && w.WaveId == waveId)

View File

@ -6,10 +6,10 @@ namespace CounterDrone.Core.Services
/// <summary>想定管理服务</summary>
public interface IScenarioService
{
Scenario CreateTask(string name, string taskNumber);
void DeleteTask(string id);
PagedResult<Scenario> SearchTasks(string keyword, string? dateFrom, string? dateTo, int page, int pageSize);
ScenarioConfig? GetTaskDetail(string id);
Scenario CreateScenario(string name, string scenarioNumber);
void DeleteScenario(string id);
PagedResult<Scenario> SearchScenarios(string keyword, string? dateFrom, string? dateTo, int page, int pageSize);
ScenarioConfig? GetScenarioDetail(string id);
void SaveScene(string scenarioId, CombatScene scene);
void SaveControlZones(string scenarioId, List<ControlZone> zones);

View File

@ -26,8 +26,8 @@ namespace CounterDrone.Core.Services
sb.AppendLine();
sb.AppendLine($"| 项目 | 值 |");
sb.AppendLine($"|------|-----|");
sb.AppendLine($"| 任务名称 | {config.Task.Name} |");
sb.AppendLine($"| 任务编号 | {config.Task.TaskNumber} |");
sb.AppendLine($"| 任务名称 | {config.Info.Name} |");
sb.AppendLine($"| 任务编号 | {config.Info.ScenarioNumber} |");
sb.AppendLine($"| 仿真耗时 | {simulationDuration:F1} 秒 |");
sb.AppendLine($"| 气溶胶类型 | {TranslateAerosol((AerosolType)config.Cloud.AerosolType)} |");
sb.AppendLine();

View File

@ -32,8 +32,8 @@ namespace CounterDrone.Core.Services
var report = new SimulationReport
{
ScenarioId = scenarioId,
TaskName = config.Task.Name,
TaskNumber = config.Task.TaskNumber,
ScenarioName = config.Info.Name,
ScenarioNumber = config.Info.ScenarioNumber,
CompletedAt = DateTime.UtcNow.ToString("o"),
TargetCount = total,
EquipmentCount = config.Equipment.Count,
@ -63,7 +63,7 @@ namespace CounterDrone.Core.Services
var query = _db.Table<SimulationReport>().AsQueryable();
if (!string.IsNullOrWhiteSpace(keyword))
query = query.Where(r => r.TaskName.Contains(keyword) || r.TaskNumber.Contains(keyword));
query = query.Where(r => r.ScenarioName.Contains(keyword) || r.ScenarioNumber.Contains(keyword));
if (!string.IsNullOrWhiteSpace(dateFrom))
query = query.Where(r => r.CompletedAt.CompareTo(dateFrom) >= 0);
if (!string.IsNullOrWhiteSpace(dateTo))
@ -88,7 +88,7 @@ namespace CounterDrone.Core.Services
Directory.CreateDirectory(outputDir);
var safeTime = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss");
var fileName = $"{report.TaskName}_{safeTime}.md";
var fileName = $"{report.ScenarioName}_{safeTime}.md";
var filePath = Path.Combine(outputDir, fileName);
File.WriteAllText(filePath, report.Content);
return filePath;

View File

@ -9,7 +9,7 @@ namespace CounterDrone.Core.Services
{
public class ScenarioService : IScenarioService
{
private readonly ScenarioRepository _taskRepo;
private readonly ScenarioRepository _scenarioRepo;
private readonly CombatSceneRepository _sceneRepo;
private readonly ControlZoneRepository _zoneRepo;
private readonly DroneProfileRepository _targetRepo;
@ -19,7 +19,7 @@ namespace CounterDrone.Core.Services
private readonly WaypointRepository _waypointRepo;
public ScenarioService(
ScenarioRepository taskRepo,
ScenarioRepository scenarioRepo,
CombatSceneRepository sceneRepo,
ControlZoneRepository zoneRepo,
DroneProfileRepository targetRepo,
@ -28,7 +28,7 @@ namespace CounterDrone.Core.Services
RoutePlanRepository routeRepo,
WaypointRepository waypointRepo)
{
_taskRepo = taskRepo;
_scenarioRepo = scenarioRepo;
_sceneRepo = sceneRepo;
_zoneRepo = zoneRepo;
_targetRepo = targetRepo;
@ -38,44 +38,44 @@ namespace CounterDrone.Core.Services
_waypointRepo = waypointRepo;
}
// ========== Task CRUD ==========
// ========== scenario CRUD ==========
public Scenario CreateTask(string name, string taskNumber)
public Scenario CreateScenario(string name, string ScenarioNumber)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("任务名称不能为空");
if (string.IsNullOrWhiteSpace(taskNumber))
taskNumber = GenerateTaskNumber();
if (string.IsNullOrWhiteSpace(ScenarioNumber))
ScenarioNumber = GenerateScenarioNumber();
var existing = _taskRepo.GetByTaskNumber(taskNumber);
var existing = _scenarioRepo.GetByScenarioNumber(ScenarioNumber);
if (existing != null)
throw new ArgumentException($"任务编号 {taskNumber} 已存在");
throw new ArgumentException($"任务编号 {ScenarioNumber} 已存在");
var task = new Scenario
var scenario = new Scenario
{
Name = name,
TaskNumber = taskNumber,
Status = (int)TaskStatus.Configuring,
ScenarioNumber = ScenarioNumber,
Status = (int)ScenarioStatus.Configuring,
CurrentStep = 1,
};
_taskRepo.Insert(task);
return task;
_scenarioRepo.Insert(scenario);
return scenario;
}
public void DeleteTask(string id)
public void DeleteScenario(string id)
{
_taskRepo.Delete(id);
_scenarioRepo.Delete(id);
}
public PagedResult<Scenario> SearchTasks(string keyword, string? dateFrom, string? dateTo, int page, int pageSize)
public PagedResult<Scenario> SearchScenarios(string keyword, string? dateFrom, string? dateTo, int page, int pageSize)
{
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 10;
var offset = (page - 1) * pageSize;
var items = _taskRepo.Search(keyword, dateFrom, dateTo, offset, pageSize, out var total);
var items = _scenarioRepo.Search(keyword, dateFrom, dateTo, offset, pageSize, out var total);
return new PagedResult<Scenario>
{
@ -86,14 +86,14 @@ namespace CounterDrone.Core.Services
};
}
public ScenarioConfig? GetTaskDetail(string id)
public ScenarioConfig? GetScenarioDetail(string id)
{
var task = _taskRepo.GetById(id);
if (task == null) return null;
var scenario = _scenarioRepo.GetById(id);
if (scenario == null) return null;
return new ScenarioConfig
{
Task = task,
Info = scenario,
Scene = _sceneRepo.GetById(id) ?? new CombatScene { ScenarioId = id },
ControlZones = _zoneRepo.GetByScenarioId(id),
Targets = _targetRepo.GetByScenarioId(id),
@ -116,7 +116,7 @@ namespace CounterDrone.Core.Services
_sceneRepo.Update(scene);
else
_sceneRepo.Insert(scene);
TouchTask(scenarioId);
TouchScenario(scenarioId);
}
public void SaveControlZones(string scenarioId, List<ControlZone> zones)
@ -128,7 +128,7 @@ namespace CounterDrone.Core.Services
zones[i].OrderIndex = i;
}
_zoneRepo.InsertAll(zones);
TouchTask(scenarioId);
TouchScenario(scenarioId);
}
public void SaveTarget(string scenarioId, DroneProfile target)
@ -142,7 +142,7 @@ namespace CounterDrone.Core.Services
_targetRepo.Update(target);
else
_targetRepo.Insert(target);
TouchTask(scenarioId);
TouchScenario(scenarioId);
}
public void SaveDeployment(string scenarioId, List<EquipmentDeployment> equips)
@ -155,7 +155,7 @@ namespace CounterDrone.Core.Services
e.Id = Guid.NewGuid().ToString();
}
_equipRepo.InsertAll(equips);
TouchTask(scenarioId);
TouchScenario(scenarioId);
}
public void AddDetection(string scenarioId, EquipmentDeployment detection)
@ -165,7 +165,7 @@ namespace CounterDrone.Core.Services
if (string.IsNullOrEmpty(detection.Id))
detection.Id = Guid.NewGuid().ToString();
_equipRepo.Insert(detection);
TouchTask(scenarioId);
TouchScenario(scenarioId);
}
public void DeleteDetection(string detectionId)
@ -186,14 +186,14 @@ namespace CounterDrone.Core.Services
_cloudRepo.Update(cloud);
else
_cloudRepo.Insert(cloud);
TouchTask(scenarioId);
TouchScenario(scenarioId);
}
public void SaveRoute(string scenarioId, string waveId, RoutePlan route, List<Waypoint> waypoints)
{
route.ScenarioId = scenarioId;
route.WaveId = waveId;
var existing = _routeRepo.GetByTaskAndWave(scenarioId, waveId);
var existing = _routeRepo.GetByScenarioAndWave(scenarioId, waveId);
if (existing != null)
{
route.Id = existing.Id;
@ -203,7 +203,7 @@ namespace CounterDrone.Core.Services
_routeRepo.Insert(route);
// 删除该批次的旧航路点,插入新的
var oldWps = _waypointRepo.GetByTaskAndWave(scenarioId, waveId);
var oldWps = _waypointRepo.GetByScenarioAndWave(scenarioId, waveId);
foreach (var w in oldWps) _waypointRepo.Delete(w.Id);
for (int i = 0; i < waypoints.Count; i++)
{
@ -214,36 +214,36 @@ namespace CounterDrone.Core.Services
waypoints[i].Id = Guid.NewGuid().ToString();
}
_waypointRepo.InsertAll(waypoints);
TouchTask(scenarioId);
TouchScenario(scenarioId);
}
public void UpdateStep(string scenarioId, int step)
{
var task = _taskRepo.GetById(scenarioId);
if (task == null) return;
var scenario = _scenarioRepo.GetById(scenarioId);
if (scenario == null) return;
if (step < 1) step = 1;
if (step > 5) step = 5;
task.CurrentStep = step;
_taskRepo.Update(task);
scenario.CurrentStep = step;
_scenarioRepo.Update(scenario);
}
// ========== Helpers ==========
private void TouchTask(string scenarioId)
private void TouchScenario(string scenarioId)
{
var task = _taskRepo.GetById(scenarioId);
if (task != null)
var scenario = _scenarioRepo.GetById(scenarioId);
if (scenario != null)
{
task.UpdatedAt = DateTime.UtcNow.ToString("o");
_taskRepo.Update(task);
scenario.UpdatedAt = DateTime.UtcNow.ToString("o");
_scenarioRepo.Update(scenario);
}
}
private static int _counter;
public static string GenerateTaskNumber()
public static string GenerateScenarioNumber()
{
var now = DateTime.Now;
var datePart = now.ToString("yyyyMMdd");

View File

@ -85,12 +85,12 @@ namespace CounterDrone.Core.Simulation
public void Initialize(string scenarioId)
{
_scenarioId = scenarioId;
var config = _scenarioService.GetTaskDetail(scenarioId);
if (config == null) throw new InvalidOperationException("Task not found");
var config = _scenarioService.GetScenarioDetail(scenarioId);
if (config == null) throw new InvalidOperationException("scenario not found");
_scene = config.Scene;
_disperseHeight = (float)config.Cloud.DisperseHeight;
_tickRate = config.Task.TickRate;
_tickRate = config.Info.TickRate;
_tickInterval = 1.0f / _tickRate;
using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath()))

View File

@ -82,7 +82,7 @@
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.5",
"System.Threading.Tasks.Extensions": "4.5.4"
"System.Threading.Scenarios.Extensions": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/System.IO.Pipelines.dll": {
@ -141,7 +141,7 @@
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "9.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
"System.Threading.Scenarios.Extensions": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/System.Text.Json.dll": {
@ -150,12 +150,12 @@
}
}
},
"System.Threading.Tasks.Extensions/4.5.4": {
"System.Threading.Scenarios.Extensions/4.5.4": {
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Threading.Tasks.Extensions.dll": {
"lib/netstandard2.0/System.Threading.Scenarios.Extensions.dll": {
"assemblyVersion": "4.2.0.1",
"fileVersion": "4.6.28619.1"
}
@ -260,12 +260,12 @@
"path": "system.text.json/9.0.0",
"hashPath": "system.text.json.9.0.0.nupkg.sha512"
},
"System.Threading.Tasks.Extensions/4.5.4": {
"System.Threading.Scenarios.Extensions/4.5.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"path": "system.threading.tasks.extensions/4.5.4",
"hashPath": "system.threading.tasks.extensions.4.5.4.nupkg.sha512"
"path": "system.threading.scenarios.extensions/4.5.4",
"hashPath": "system.threading.scenarios.extensions.4.5.4.nupkg.sha512"
}
}
}

View File

@ -28,9 +28,9 @@ namespace CounterDrone.Unity
var scenarioMgr = gameObject.AddComponent<ScenarioManager>();
scenarioMgr.Awake();
var task = scenarioMgr.CreateTask("Verification", "");
var scenarioId = task.Id;
Debug.Log($"2. ScenarioManager OK. Task: {scenarioId}");
var scenario = scenarioMgr.CreateScenario("Verification", "");
var scenarioId = scenario.Id;
Debug.Log($"2. ScenarioManager OK. scenario: {scenarioId}");
scenarioMgr.SaveScene(scenarioId, new CombatScene { WindSpeed = 0 });
scenarioMgr.SaveTarget(scenarioId, new DroneProfile

View File

@ -45,11 +45,11 @@ namespace CounterDrone.Unity
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
public Scenario CreateTask(string name, string number) => _service.CreateTask(name, number);
public void DeleteTask(string id) => _service.DeleteTask(id);
public ScenarioConfig GetDetail(string id) => _service.GetTaskDetail(id);
public Scenario CreateScenario(string name, string number) => _service.CreateScenario(name, number);
public void DeleteScenario(string id) => _service.DeleteScenario(id);
public ScenarioConfig GetDetail(string id) => _service.GetScenarioDetail(id);
public PagedResult<Scenario> Search(string kw, string from, string to, int page, int size)
=> _service.SearchTasks(kw, from, to, page, size);
=> _service.SearchScenarios(kw, from, to, page, size);
public void SaveScene(string scenarioId, CombatScene s) => _service.SaveScene(scenarioId, s);
public void SaveTarget(string scenarioId, DroneProfile t) => _service.SaveTarget(scenarioId, t);
@ -62,8 +62,8 @@ namespace CounterDrone.Unity
void Verify()
{
Awake();
var task = CreateTask("验证任务", "");
Debug.Log($"ScenarioManager OK. Created: {task.Name} ({task.TaskNumber})");
var scenario = CreateScenario("验证任务", "");
Debug.Log($"ScenarioManager OK. Created: {scenario.Name} ({scenario.ScenarioNumber})");
}
}
}

View File

@ -52,7 +52,7 @@ namespace CounterDrone.Unity
if (_usePreset)
{
// 方式1直接使用预设想定最简单
var demos = scenario.SearchTasks("Demo", null, null, 1, 1);
var demos = scenario.SearchScenarios("Demo", null, null, 1, 1);
if (demos.TotalCount == 0)
{
Debug.LogError("无预设想定,请确认 defaults.json 已放入 StreamingAssets");
@ -66,8 +66,8 @@ namespace CounterDrone.Unity
// 方式2用 DefaultData 预设组装自定义想定
var defaults = DefaultData.Load(paths);
var task = scenario.CreateTask("Unity Demo", "");
scenarioId = task.Id;
var scenario = scenario.CreateScenario("Unity Demo", "");
scenarioId = scenario.Id;
scenario.SaveScene(scenarioId, defaults.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
scenario.SaveTarget(scenarioId, defaults.Drones.First(p => p.Id == "shahed").ToDroneProfile());
@ -77,7 +77,7 @@ namespace CounterDrone.Unity
.ToWaypoints(_droneSpeed));
// 从火力单元预设构建 FireUnit → Planner 自动生成方案 → 引擎执行
var detail = scenario.GetTaskDetail(scenarioId);
var detail = scenario.GetScenarioDetail(scenarioId);
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
var planner = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths));
var threats = new List<DroneWave>

View File

@ -55,8 +55,8 @@ namespace CounterDrone.Unity
public void LoadAndStart(string scenarioId)
{
_scenarioId = scenarioId;
var detail = _scenario.GetTaskDetail(scenarioId);
if (detail == null) { Debug.LogError("Task not found"); return; }
var detail = _scenario.GetScenarioDetail(scenarioId);
if (detail == null) { Debug.LogError("scenario not found"); return; }
_engine.Initialize(scenarioId);

View File

@ -53,7 +53,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void OpenFrameDb_CreatesFrameRecordTable()
{
var db = _dbManager.OpenFrameDb("task_001");
var db = _dbManager.OpenFrameDb("scenario_001");
var count = db.ExecuteScalar<int>(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='SimFrameRecord'");
@ -65,7 +65,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void DeleteFrameDb_RemovesFile()
{
var scenarioId = "task_del";
var scenarioId = "scenario_del";
var db = _dbManager.OpenFrameDb(scenarioId);
db.Close();

View File

@ -42,8 +42,8 @@ namespace CounterDrone.Core.Tests
[Fact]
public void EmptyDeployment_EngineRunsWithoutCrash()
{
var task = _scenario.CreateTask("空部署", "");
_scenarioId = task.Id;
var scenario = _scenario.CreateScenario("空部署", "");
_scenarioId = scenario.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
_scenario.SaveTarget(_scenarioId, new DroneProfile
@ -81,8 +81,8 @@ namespace CounterDrone.Core.Tests
[Fact]
public void ExtremeWind_DroneStillFlies()
{
var task = _scenario.CreateTask("飓风", "");
_scenarioId = task.Id;
var scenario = _scenario.CreateScenario("飓风", "");
_scenarioId = scenario.Id;
_scenario.SaveScene(_scenarioId, new CombatScene
{
@ -124,8 +124,8 @@ namespace CounterDrone.Core.Tests
[Fact]
public void LongRoute_CompletesWithoutError()
{
var task = _scenario.CreateTask("超长航路", "");
_scenarioId = task.Id;
var scenario = _scenario.CreateScenario("超长航路", "");
_scenarioId = scenario.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
_scenario.SaveTarget(_scenarioId, new DroneProfile

View File

@ -36,22 +36,22 @@ namespace CounterDrone.Core.Tests
public void CleanupExpired_DeletesOldFiles()
{
_store.RetentionDays = 0;
CreateFakeDb("task_old_1");
CreateFakeDb("task_old_2");
CreateFakeDb("scenario_old_1");
CreateFakeDb("scenario_old_2");
var deleted = _store.CleanupExpired();
Assert.Equal(2, deleted);
Assert.False(File.Exists(Path.Combine(_testDir, "frames", "task_old_1.db")));
Assert.False(File.Exists(Path.Combine(_testDir, "frames", "task_old_2.db")));
Assert.False(File.Exists(Path.Combine(_testDir, "frames", "scenario_old_1.db")));
Assert.False(File.Exists(Path.Combine(_testDir, "frames", "scenario_old_2.db")));
}
[Fact]
public void CleanupExpired_KeepsRecentFiles()
{
_store.RetentionDays = 365;
CreateFakeDb("task_new");
CreateFakeDb("scenario_new");
var filePath = Path.Combine(_testDir, "frames", "task_new.db");
var filePath = Path.Combine(_testDir, "frames", "scenario_new.db");
File.SetCreationTime(filePath, DateTime.Now);
var deleted = _store.CleanupExpired();
@ -68,7 +68,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void RecordAndFlush_ThenReadFrames()
{
_store.BeginRecording("task_rw");
_store.BeginRecording("scenario_rw");
var snapshots = new List<EntitySnapshot>
{
new EntitySnapshot { EntityId = "d1", EntityType = Models.EntityType.Drone, PosX = 100 },
@ -78,7 +78,7 @@ namespace CounterDrone.Core.Tests
_store.RecordFrame(2, 0.10f, snapshots);
_store.Flush();
var frames = _store.ReadFrames("task_rw", 1, 2);
var frames = _store.ReadFrames("scenario_rw", 1, 2);
Assert.Equal(2, frames.Count);
Assert.Equal(1, frames[0].FrameIndex);
Assert.Equal(2, frames[1].FrameIndex);
@ -87,15 +87,15 @@ namespace CounterDrone.Core.Tests
[Fact]
public void Flush_EmptyBuffer_DoesNothing()
{
_store.BeginRecording("task_empty");
_store.BeginRecording("scenario_empty");
_store.Flush(); // no crash
Assert.False(File.Exists(Path.Combine(_testDir, "frames", "task_empty.db")));
Assert.False(File.Exists(Path.Combine(_testDir, "frames", "scenario_empty.db")));
}
[Fact]
public void Discard_ClearsBuffer()
{
_store.BeginRecording("task_discard");
_store.BeginRecording("scenario_discard");
_store.RecordFrame(0, 0f, new List<EntitySnapshot> { new EntitySnapshot { EntityId = "d1" } });
_store.Discard();
Assert.Equal(0, _store.BufferedFrameCount);

View File

@ -66,10 +66,10 @@ namespace CounterDrone.Core.Tests
private void VerifyAndExportReport(SimulationEngine eng, DroneEntity drone)
{
var detail = _scenario.GetTaskDetail(_scenarioId)!;
var detail = _scenario.GetScenarioDetail(_scenarioId)!;
var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime);
Assert.Contains("对抗结果", report);
Assert.Contains(detail.Task.Name, report);
Assert.Contains(detail.Info.Name, report);
var svc = new ReportService(_mainDb, _paths);
var r = svc.Generate(_scenarioId, detail, eng.Events.ToList(), drone.Status.ToString(), eng.SimulationTime);
@ -78,10 +78,10 @@ namespace CounterDrone.Core.Tests
private ScenarioConfig LoadPreset(string name)
{
var results = _scenario.SearchTasks(name, null, null, 1, 1);
var results = _scenario.SearchScenarios(name, null, null, 1, 1);
Assert.True(results.TotalCount > 0, $"预设想定未找到: {name}");
_scenarioId = results.Items[0].Id;
return _scenario.GetTaskDetail(_scenarioId)!;
return _scenario.GetScenarioDetail(_scenarioId)!;
}
private SimulationEngine RunPreset(string name, int maxFrames = 8000)
@ -152,7 +152,7 @@ namespace CounterDrone.Core.Tests
Assert.All(eng.Events.Where(e => e.Type == SimEventType.MunitionLaunched),
e => Assert.Contains("空基", e.Description));
var drone = eng.Drones[0];
var detail = _scenario.GetTaskDetail(_scenarioId)!;
var detail = _scenario.GetScenarioDetail(_scenarioId)!;
var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime);
Assert.True(drone.Hp < 0.1f, $"Hp={drone.Hp:F3} pos=({drone.PosX:F0},{drone.PosY:F0}) launched={eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched)}\n{report}");
VerifyAndExportReport(eng, drone);
@ -182,8 +182,8 @@ namespace CounterDrone.Core.Tests
public void Scenario_DetectionDriven_PlanningDelayedByDetectionBoundary()
{
// 场景A无探测设备上帝视角从航路起点算
var taskA = _scenario.CreateTask("无探测远航路", "");
_scenarioId = taskA.Id;
var scenarioA = _scenario.CreateScenario("无探测远航路", "");
_scenarioId = scenarioA.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenario.SaveTarget(_scenarioId, new DroneProfile
{
@ -207,8 +207,8 @@ namespace CounterDrone.Core.Tests
// 场景B有独立探测设备雷达 6000m部署在 X=10000 航路中点附近)
// 探测圆边界 X=4000 和 X=16000无人机从 X=0 飞向 20000在 X=4000 进入探测
// planner 基于探测边界 X=4000 算到达时间(而非起点 X=0
var taskB = _scenario.CreateTask("有探测远航路", "");
_scenarioId = taskB.Id;
var scenarioB = _scenario.CreateScenario("有探测远航路", "");
_scenarioId = scenarioB.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenario.SaveTarget(_scenarioId, new DroneProfile
{

View File

@ -35,7 +35,7 @@ namespace CounterDrone.Core.Tests
var gen = new ReportGenerator();
var config = new ScenarioConfig
{
Task = new Scenario { Name = "测试任务", TaskNumber = "SIM-001" },
Info = new Scenario { Name = "测试任务", ScenarioNumber = "SIM-001" },
Targets = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>
{

View File

@ -36,17 +36,17 @@ namespace CounterDrone.Core.Tests
{
var config = new ScenarioConfig
{
Task = new Scenario { Name = "Test", TaskNumber = "SIM-001" },
Info = new Scenario { Name = "Test", ScenarioNumber = "SIM-001" },
Targets = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>(),
Cloud = new CloudDispersal(),
};
var report = _service.Generate("task1", config, new List<SimEvent>(), "Destroyed", 45f);
var report = _service.Generate("scenario1", config, new List<SimEvent>(), "Destroyed", 45f);
Assert.NotNull(report);
Assert.NotEmpty(report.Id);
Assert.Equal("Test", report.TaskName);
Assert.Equal("Test", report.ScenarioName);
Assert.Contains("仿真评估报告", report.Content);
var fetched = _service.GetReport(report.Id);
@ -58,13 +58,13 @@ namespace CounterDrone.Core.Tests
{
var config = new ScenarioConfig
{
Task = new Scenario { Name = "Del", TaskNumber = "SIM-DEL" },
Info = new Scenario { Name = "Del", ScenarioNumber = "SIM-DEL" },
Targets = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>(),
Cloud = new CloudDispersal(),
};
var report = _service.Generate("task1", config, new List<SimEvent>(), "Destroyed", 10f);
var report = _service.Generate("scenario1", config, new List<SimEvent>(), "Destroyed", 10f);
_service.DeleteReport(report.Id);
Assert.Null(_service.GetReport(report.Id));
}
@ -74,15 +74,15 @@ namespace CounterDrone.Core.Tests
{
var cfg = new ScenarioConfig
{
Task = new Scenario { Name = "城市防御", TaskNumber = "SIM-A" },
Info = new Scenario { Name = "城市防御", ScenarioNumber = "SIM-A" },
Targets = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>(),
Cloud = new CloudDispersal(),
};
_service.Generate("t1", cfg, new List<SimEvent>(), "Destroyed", 10f);
cfg.Task.Name = "平原拦截";
cfg.Task.TaskNumber = "SIM-B";
cfg.Info.Name = "平原拦截";
cfg.Info.ScenarioNumber = "SIM-B";
_service.Generate("t2", cfg, new List<SimEvent>(), "Destroyed", 20f);
var result = _service.SearchReports("城市", null, null, 1, 10);

View File

@ -25,14 +25,14 @@ namespace CounterDrone.Core.Tests
public void DroneProfile_DeleteByScenarioId_RemovesAll()
{
var repo = new DroneProfileRepository(_db);
repo.Insert(new DroneProfile { Id = "t1", ScenarioId = "taskA", Quantity = 1 });
repo.Insert(new DroneProfile { Id = "t2", ScenarioId = "taskA", Quantity = 2 });
repo.Insert(new DroneProfile { Id = "t3", ScenarioId = "taskB", Quantity = 1 });
repo.Insert(new DroneProfile { Id = "t1", ScenarioId = "scenarioA", Quantity = 1 });
repo.Insert(new DroneProfile { Id = "t2", ScenarioId = "scenarioA", Quantity = 2 });
repo.Insert(new DroneProfile { Id = "t3", ScenarioId = "scenarioB", Quantity = 1 });
repo.DeleteByScenarioId("taskA");
repo.DeleteByScenarioId("scenarioA");
Assert.Empty(repo.GetByScenarioId("taskA"));
Assert.Single(repo.GetByScenarioId("taskB"));
Assert.Empty(repo.GetByScenarioId("scenarioA"));
Assert.Single(repo.GetByScenarioId("scenarioB"));
}
[Fact]

View File

@ -7,7 +7,7 @@ using CounterDrone.Core.Repository;
using CounterDrone.Core.Services;
using SQLite;
using Xunit;
using TaskStatus = CounterDrone.Core.Models.TaskStatus;
using ScenarioStatus = CounterDrone.Core.Models.ScenarioStatus;
namespace CounterDrone.Core.Tests
{
@ -41,86 +41,86 @@ namespace CounterDrone.Core.Tests
Directory.Delete(_testDir, true);
}
// ========== Task CRUD ==========
// ========== scenario CRUD ==========
[Fact]
public void CreateTask_Valid_ReturnsTask()
public void CreateScenario_Valid_ReturnsScenario()
{
var task = _service.CreateTask("测试任务", "SIM-20260611-001");
var scenario = _service.CreateScenario("测试任务", "SIM-20260611-001");
Assert.NotNull(task);
Assert.Equal("测试任务", task.Name);
Assert.Equal("SIM-20260611-001", task.TaskNumber);
Assert.Equal((int)TaskStatus.Configuring, task.Status);
Assert.Equal(1, task.CurrentStep);
Assert.NotEmpty(task.Id);
Assert.NotNull(scenario);
Assert.Equal("测试任务", scenario.Name);
Assert.Equal("SIM-20260611-001", scenario.ScenarioNumber);
Assert.Equal((int)ScenarioStatus.Configuring, scenario.Status);
Assert.Equal(1, scenario.CurrentStep);
Assert.NotEmpty(scenario.Id);
}
[Fact]
public void CreateTask_AutoGeneratesTaskNumber()
public void CreateScenario_AutoGeneratesScenarioNumber()
{
var task = _service.CreateTask("自动编号", "");
Assert.StartsWith("SIM-", task.TaskNumber);
var scenario = _service.CreateScenario("自动编号", "");
Assert.StartsWith("SIM-", scenario.ScenarioNumber);
}
[Fact]
public void CreateTask_EmptyName_Throws()
public void CreateScenario_EmptyName_Throws()
{
Assert.Throws<ArgumentException>(() =>
_service.CreateTask("", "SIM-001"));
_service.CreateScenario("", "SIM-001"));
}
[Fact]
public void CreateTask_DuplicateNumber_Throws()
public void CreateScenario_DuplicateNumber_Throws()
{
_service.CreateTask("Task A", "SIM-001");
_service.CreateScenario("scenario A", "SIM-001");
Assert.Throws<ArgumentException>(() =>
_service.CreateTask("Task B", "SIM-001"));
_service.CreateScenario("scenario B", "SIM-001"));
}
[Fact]
public void DeleteTask_RemovesTask()
public void DeleteScenario_RemovesScenario()
{
var task = _service.CreateTask("ToDelete", "");
_service.DeleteTask(task.Id);
var scenario = _service.CreateScenario("ToDelete", "");
_service.DeleteScenario(scenario.Id);
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Null(detail);
}
[Fact]
public void GetTaskDetail_ReturnsFullConfig()
public void GetScenarioDetail_ReturnsFullConfig()
{
var task = _service.CreateTask("Full Config", "");
var scenario = _service.CreateScenario("Full Config", "");
// 保存各步骤配置
_service.SaveScene(task.Id, new CombatScene
_service.SaveScene(scenario.Id, new CombatScene
{
SceneType = (int)SceneType.Mountain,
WindSpeed = 10.0,
Temperature = 25.0,
});
_service.SaveTarget(task.Id, new DroneProfile
_service.SaveTarget(scenario.Id, new DroneProfile
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
Quantity = 3,
});
_service.SaveCloudDispersal(task.Id, new CloudDispersal
_service.SaveCloudDispersal(scenario.Id, new CloudDispersal
{
AerosolType = (int)AerosolType.ActiveMaterial,
Duration = 45.0,
});
_service.UpdateStep(task.Id, 4);
_service.UpdateStep(scenario.Id, 4);
// 读取完整配置
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal("Full Config", detail.Task.Name);
Assert.Equal(4, detail.Task.CurrentStep);
Assert.Equal("Full Config", detail.Info.Name);
Assert.Equal(4, detail.Info.CurrentStep);
Assert.Equal((int)SceneType.Mountain, detail.Scene.SceneType);
Assert.Equal(10.0, detail.Scene.WindSpeed);
Assert.Equal(3, detail.Targets[0].Quantity);
@ -132,15 +132,15 @@ namespace CounterDrone.Core.Tests
[Fact]
public void SaveScene_FirstTime_Inserts()
{
var task = _service.CreateTask("Scene Test", "");
_service.SaveScene(task.Id, new CombatScene
var scenario = _service.CreateScenario("Scene Test", "");
_service.SaveScene(scenario.Id, new CombatScene
{
WeatherType = (int)WeatherType.Fog,
Visibility = 2000.0,
TimeOfDay = "03:00",
});
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal((int)WeatherType.Fog, detail.Scene.WeatherType);
Assert.Equal(2000.0, detail.Scene.Visibility);
Assert.Equal("03:00", detail.Scene.TimeOfDay);
@ -149,27 +149,27 @@ namespace CounterDrone.Core.Tests
[Fact]
public void SaveScene_SecondTime_Updates()
{
var task = _service.CreateTask("Update Scene", "");
_service.SaveScene(task.Id, new CombatScene { WindSpeed = 5.0 });
_service.SaveScene(task.Id, new CombatScene { WindSpeed = 15.0 });
var scenario = _service.CreateScenario("Update Scene", "");
_service.SaveScene(scenario.Id, new CombatScene { WindSpeed = 5.0 });
_service.SaveScene(scenario.Id, new CombatScene { WindSpeed = 15.0 });
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(15.0, detail.Scene.WindSpeed);
}
[Fact]
public void SaveControlZones_SavesAndRetrieves()
{
var task = _service.CreateTask("Zone Test", "");
var scenario = _service.CreateScenario("Zone Test", "");
var zones = new List<ControlZone>
{
new ControlZone { Name = "禁区A", VerticesJson = "[{\"x\":0,\"y\":0,\"z\":0},{\"x\":100,\"y\":0,\"z\":0},{\"x\":100,\"y\":100,\"z\":0}]", MinAltitude = 0, MaxAltitude = 500 },
new ControlZone { Name = "禁区B", VerticesJson = "[{\"x\":200,\"y\":200,\"z\":0}]", MinAltitude = 100, MaxAltitude = 300 },
};
_service.SaveControlZones(task.Id, zones);
_service.SaveControlZones(scenario.Id, zones);
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(2, detail.ControlZones.Count);
Assert.Equal("禁区A", detail.ControlZones[0].Name);
Assert.Equal(0, detail.ControlZones[0].OrderIndex);
@ -180,26 +180,26 @@ namespace CounterDrone.Core.Tests
[Fact]
public void SaveControlZones_ReplaceExisting()
{
var task = _service.CreateTask("Zone Replace", "");
_service.SaveControlZones(task.Id, new List<ControlZone>
var scenario = _service.CreateScenario("Zone Replace", "");
_service.SaveControlZones(scenario.Id, new List<ControlZone>
{
new ControlZone { Name = "Old" }
});
_service.SaveControlZones(task.Id, new List<ControlZone>
_service.SaveControlZones(scenario.Id, new List<ControlZone>
{
new ControlZone { Name = "New1" },
new ControlZone { Name = "New2" },
});
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(2, detail.ControlZones.Count);
}
[Fact]
public void SaveTarget_SavesCorrectly()
{
var task = _service.CreateTask("Target Test", "");
_service.SaveTarget(task.Id, new DroneProfile
var scenario = _service.CreateScenario("Target Test", "");
_service.SaveTarget(scenario.Id, new DroneProfile
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
@ -210,7 +210,7 @@ namespace CounterDrone.Core.Tests
TypicalAltitude = 800.0,
});
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
var target = detail.Targets[0];
Assert.Equal((int)DroneType.FixedWing, target.DroneType);
Assert.Equal(5, target.Quantity);
@ -220,7 +220,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void SaveDeployment_SavesMultiple()
{
var task = _service.CreateTask("Equip Test", "");
var scenario = _service.CreateScenario("Equip Test", "");
var equips = new List<EquipmentDeployment>
{
new EquipmentDeployment
@ -240,9 +240,9 @@ namespace CounterDrone.Core.Tests
},
};
_service.SaveDeployment(task.Id, equips);
_service.SaveDeployment(scenario.Id, equips);
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(2, detail.Equipment.Count);
var platform = detail.Equipment.Find(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
@ -254,7 +254,7 @@ namespace CounterDrone.Core.Tests
[Fact]
public void SaveRoute_SavesWaypoints()
{
var task = _service.CreateTask("Route Test", "");
var scenario = _service.CreateScenario("Route Test", "");
var route = new RoutePlan
{
FormationMode = (int)FormationMode.Formation,
@ -267,9 +267,9 @@ namespace CounterDrone.Core.Tests
new Waypoint { PosX = 10000, PosY = 0, PosZ = 100, Altitude = 300, Speed = 80 },
};
_service.SaveRoute(task.Id, "default", route, waypoints);
_service.SaveRoute(scenario.Id, "default", route, waypoints);
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal((int)FormationMode.Formation, detail.Routes[0].FormationMode);
Assert.Equal(3, detail.WaypointGroups["default"].Count);
Assert.Equal(0, detail.WaypointGroups["default"][0].OrderIndex);
@ -280,53 +280,53 @@ namespace CounterDrone.Core.Tests
// ========== Search & Pagination ==========
[Fact]
public void SearchTasks_KeywordFilter()
public void SearchScenarios_KeywordFilter()
{
_service.CreateTask("城市防御演习", "");
_service.CreateTask("平原拦截测试", "");
_service.CreateTask("海岸边防演练", "");
_service.CreateScenario("城市防御演习", "");
_service.CreateScenario("平原拦截测试", "");
_service.CreateScenario("海岸边防演练", "");
var result = _service.SearchTasks("城市", null, null, 1, 10);
var result = _service.SearchScenarios("城市", null, null, 1, 10);
Assert.Equal(1, result.TotalCount);
Assert.Equal("城市防御演习", result.Items[0].Name);
}
[Fact]
public void SearchTasks_NoResults()
public void SearchScenarios_NoResults()
{
_service.CreateTask("Task A", "");
var result = _service.SearchTasks("不存在的任务", null, null, 1, 10);
_service.CreateScenario("scenario A", "");
var result = _service.SearchScenarios("不存在的任务", null, null, 1, 10);
Assert.Equal(0, result.TotalCount);
}
[Fact]
public void SearchTasks_Pagination()
public void SearchScenarios_Pagination()
{
for (int i = 0; i < 15; i++)
_service.CreateTask($"Task {i:D2}", "");
_service.CreateScenario($"scenario {i:D2}", "");
var page1 = _service.SearchTasks(null, null, null, 1, 5);
var page1 = _service.SearchScenarios(null, null, null, 1, 5);
Assert.Equal(21, page1.TotalCount); // 15 + 6 预设想定
Assert.Equal(5, page1.Items.Count);
Assert.Equal(5, page1.TotalPages); // 21 ÷ 5 = 5 页
var page3 = _service.SearchTasks(null, null, null, 3, 5);
var page3 = _service.SearchScenarios(null, null, null, 3, 5);
Assert.Equal(5, page3.Items.Count);
}
[Fact]
public void SearchTasks_DateRangeFilter()
public void SearchScenarios_DateRangeFilter()
{
// All tasks created today, so date filter should include them
_service.CreateTask("Recent Task", "");
// All scenarios created today, so date filter should include them
_service.CreateScenario("Recent scenario", "");
var yesterday = DateTime.UtcNow.AddDays(-1).ToString("yyyy-MM-dd");
var tomorrow = DateTime.UtcNow.AddDays(1).ToString("yyyy-MM-dd");
var result = _service.SearchTasks(null, yesterday, tomorrow, 1, 10);
var result = _service.SearchScenarios(null, yesterday, tomorrow, 1, 10);
Assert.Equal(7, result.TotalCount); // 1 + 6 预设想定
var oldResult = _service.SearchTasks(null, "2020-01-01", "2020-12-31", 1, 10);
var oldResult = _service.SearchScenarios(null, "2020-01-01", "2020-12-31", 1, 10);
Assert.Equal(0, oldResult.TotalCount);
}
@ -335,28 +335,28 @@ namespace CounterDrone.Core.Tests
[Fact]
public void UpdateStep_ValidSteps()
{
var task = _service.CreateTask("Step Test", "");
Assert.Equal(1, task.CurrentStep);
var scenario = _service.CreateScenario("Step Test", "");
Assert.Equal(1, scenario.CurrentStep);
_service.UpdateStep(task.Id, 3);
var detail = _service.GetTaskDetail(task.Id);
Assert.Equal(3, detail.Task.CurrentStep);
_service.UpdateStep(scenario.Id, 3);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(3, detail.Info.CurrentStep);
_service.UpdateStep(task.Id, 5);
detail = _service.GetTaskDetail(task.Id);
Assert.Equal(5, detail.Task.CurrentStep);
_service.UpdateStep(scenario.Id, 5);
detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(5, detail.Info.CurrentStep);
}
[Fact]
public void UpdateStep_ClampedToRange()
{
var task = _service.CreateTask("Clamp Test", "");
var scenario = _service.CreateScenario("Clamp Test", "");
_service.UpdateStep(task.Id, 0);
Assert.Equal(1, _service.GetTaskDetail(task.Id).Task.CurrentStep);
_service.UpdateStep(scenario.Id, 0);
Assert.Equal(1, _service.GetScenarioDetail(scenario.Id).Info.CurrentStep);
_service.UpdateStep(task.Id, 10);
Assert.Equal(5, _service.GetTaskDetail(task.Id).Task.CurrentStep);
_service.UpdateStep(scenario.Id, 10);
Assert.Equal(5, _service.GetScenarioDetail(scenario.Id).Info.CurrentStep);
}
// ========== Full Workflow ==========
@ -365,11 +365,11 @@ namespace CounterDrone.Core.Tests
public void FullWorkflow_CreateConfigureSearch()
{
// 1. 创建任务
var task = _service.CreateTask("完整流程测试", "");
Assert.NotNull(task);
var scenario = _service.CreateScenario("完整流程测试", "");
Assert.NotNull(scenario);
// 2. 步骤1作战场景
_service.SaveScene(task.Id, new CombatScene
_service.SaveScene(scenario.Id, new CombatScene
{
SceneType = (int)SceneType.Urban,
WeatherType = (int)WeatherType.Rain,
@ -378,10 +378,10 @@ namespace CounterDrone.Core.Tests
Temperature = 18.0,
TimeOfDay = "14:00",
});
_service.UpdateStep(task.Id, 1);
_service.UpdateStep(scenario.Id, 1);
// 3. 步骤1扩展管控区域
_service.SaveControlZones(task.Id, new List<ControlZone>
_service.SaveControlZones(scenario.Id, new List<ControlZone>
{
new ControlZone
{
@ -392,7 +392,7 @@ namespace CounterDrone.Core.Tests
});
// 4. 步骤2目标配置
_service.SaveTarget(task.Id, new DroneProfile
_service.SaveTarget(scenario.Id, new DroneProfile
{
WaveId = "default",
DroneType = (int)DroneType.HighSpeed,
@ -400,10 +400,10 @@ namespace CounterDrone.Core.Tests
PowerType = (int)PowerType.Jet,
TypicalSpeed = 300.0,
});
_service.UpdateStep(task.Id, 2);
_service.UpdateStep(scenario.Id, 2);
// 5. 步骤3装备部署
_service.SaveDeployment(task.Id, new List<EquipmentDeployment>
_service.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
{
new EquipmentDeployment
{
@ -421,10 +421,10 @@ namespace CounterDrone.Core.Tests
MunitionCount = 2,
},
});
_service.UpdateStep(task.Id, 3);
_service.UpdateStep(scenario.Id, 3);
// 6. 步骤4云团抛撒
_service.SaveCloudDispersal(task.Id, new CloudDispersal
_service.SaveCloudDispersal(scenario.Id, new CloudDispersal
{
AerosolType = (int)AerosolType.ActiveMaterial,
TriggerMode = (int)TriggerMode.Area,
@ -433,10 +433,10 @@ namespace CounterDrone.Core.Tests
PositionY = 0,
PositionZ = 300,
});
_service.UpdateStep(task.Id, 4);
_service.UpdateStep(scenario.Id, 4);
// 7. 步骤5航路规划
_service.SaveRoute(task.Id, "default", new RoutePlan
_service.SaveRoute(scenario.Id, "default", new RoutePlan
{
FormationMode = (int)FormationMode.Swarm,
LateralSpacing = 30.0,
@ -445,14 +445,14 @@ namespace CounterDrone.Core.Tests
new Waypoint { PosX = 0, PosY = 100, PosZ = 200, Altitude = 600, Speed = 250 },
new Waypoint { PosX = 10000, PosY = 100, PosZ = 200, Altitude = 600, Speed = 250 },
});
_service.UpdateStep(task.Id, 5);
_service.UpdateStep(scenario.Id, 5);
// 8. 验证完整配置
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal("完整流程测试", detail.Task.Name);
Assert.Equal(5, detail.Task.CurrentStep);
Assert.Equal((int)TaskStatus.Configuring, detail.Task.Status);
Assert.Equal("完整流程测试", detail.Info.Name);
Assert.Equal(5, detail.Info.CurrentStep);
Assert.Equal((int)ScenarioStatus.Configuring, detail.Info.Status);
// 场景
Assert.Equal((int)SceneType.Urban, detail.Scene.SceneType);
@ -481,7 +481,7 @@ namespace CounterDrone.Core.Tests
Assert.Equal(250.0, detail.WaypointGroups["default"][0].Speed);
// 9. 搜索验证
var searchResult = _service.SearchTasks("完整流程", null, null, 1, 10);
var searchResult = _service.SearchScenarios("完整流程", null, null, 1, 10);
Assert.Equal(1, searchResult.TotalCount);
}
@ -492,8 +492,8 @@ namespace CounterDrone.Core.Tests
[Fact]
public void AddDetection_PersistsAndQueryable()
{
var task = _service.CreateTask("探测设备测试", "");
_service.AddDetection(task.Id, new EquipmentDeployment
var scenario = _service.CreateScenario("探测设备测试", "");
_service.AddDetection(scenario.Id, new EquipmentDeployment
{
Quantity = 1,
PositionX = 5000, PositionY = 0, PositionZ = 0,
@ -501,7 +501,7 @@ namespace CounterDrone.Core.Tests
DetectionAccuracy = 50,
});
var detections = _service.GetDetections(task.Id);
var detections = _service.GetDetections(scenario.Id);
Assert.Single(detections);
Assert.Equal((int)EquipmentRole.Detection, detections[0].EquipmentRole);
Assert.Equal(8000.0, detections[0].RadarRange);
@ -511,9 +511,9 @@ namespace CounterDrone.Core.Tests
[Fact]
public void GetDetections_FiltersOutLaunchPlatforms()
{
var task = _service.CreateTask("探测过滤测试", "");
var scenario = _service.CreateScenario("探测过滤测试", "");
// 一个火力单元
_service.SaveDeployment(task.Id, new List<EquipmentDeployment>
_service.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
{
new EquipmentDeployment
{
@ -522,39 +522,39 @@ namespace CounterDrone.Core.Tests
},
});
// 两个探测设备(独立添加)
_service.AddDetection(task.Id, new EquipmentDeployment { RadarRange = 5000 });
_service.AddDetection(task.Id, new EquipmentDeployment { EORange = 3000 });
_service.AddDetection(scenario.Id, new EquipmentDeployment { RadarRange = 5000 });
_service.AddDetection(scenario.Id, new EquipmentDeployment { EORange = 3000 });
var detections = _service.GetDetections(task.Id);
var detections = _service.GetDetections(scenario.Id);
Assert.Equal(2, detections.Count); // 只有探测设备,不含火力单元
}
[Fact]
public void DeleteDetection_RemovesOnlyOne()
{
var task = _service.CreateTask("探测删除测试", "");
_service.AddDetection(task.Id, new EquipmentDeployment { RadarRange = 5000 });
var scenario = _service.CreateScenario("探测删除测试", "");
_service.AddDetection(scenario.Id, new EquipmentDeployment { RadarRange = 5000 });
var det2 = new EquipmentDeployment { EORange = 3000 };
_service.AddDetection(task.Id, det2);
_service.AddDetection(scenario.Id, det2);
_service.DeleteDetection(det2.Id);
var detections = _service.GetDetections(task.Id);
var detections = _service.GetDetections(scenario.Id);
Assert.Single(detections);
Assert.Equal(5000.0, detections[0].RadarRange);
}
[Fact]
public void GetTaskDetail_IncludesDetections()
public void GetScenarioDetail_IncludesDetections()
{
var task = _service.CreateTask("想定含探测", "");
_service.SaveDeployment(task.Id, new List<EquipmentDeployment>
var scenario = _service.CreateScenario("想定含探测", "");
_service.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
{
new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 1 },
});
_service.AddDetection(task.Id, new EquipmentDeployment { RadarRange = 10000 });
_service.AddDetection(scenario.Id, new EquipmentDeployment { RadarRange = 10000 });
var detail = _service.GetTaskDetail(task.Id);
var detail = _service.GetScenarioDetail(scenario.Id);
// Equipment 包含火力单元 + 探测设备
Assert.Equal(2, detail.Equipment.Count);
Assert.Contains(detail.Equipment, e => e.EquipmentRole == (int)EquipmentRole.Detection);

View File

@ -47,8 +47,8 @@ namespace CounterDrone.Core.Tests
private void SetupScenario()
{
var task = _scenarioService.CreateTask("Test", "");
_scenarioId = task.Id;
var scenario = _scenarioService.CreateScenario("Test", "");
_scenarioId = scenario.Id;
_scenarioService.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
_scenarioService.SaveTarget(_scenarioId, new DroneProfile
{
@ -148,8 +148,8 @@ namespace CounterDrone.Core.Tests
private void SetupScenarioWithDetection()
{
var task = _scenarioService.CreateTask("Detect", "");
_scenarioId = task.Id;
var scenario = _scenarioService.CreateScenario("Detect", "");
_scenarioId = scenario.Id;
_scenarioService.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveTarget(_scenarioId, new DroneProfile
{
@ -222,24 +222,24 @@ namespace CounterDrone.Core.Tests
public void TargetDetected_FiresAgainAfterLeaveAndReenter()
{
// 探测设备在 X=5000 半径 1000 → 入口 X=4000出口 X=6000
var task = _scenarioService.CreateTask("Reenter", "");
_scenarioService.SaveScene(task.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveTarget(task.Id, new DroneProfile
var scenario = _scenarioService.CreateScenario("Reenter", "");
_scenarioService.SaveScene(scenario.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveTarget(scenario.Id, new DroneProfile
{
WaveId = "w1", Quantity = 1, TypicalSpeed = 120, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
});
_scenarioService.SaveDeployment(task.Id, new List<EquipmentDeployment>
_scenarioService.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
{
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 5000, PositionY = 0, PositionZ = 0, RadarRange = 1000 },
new() { EquipmentRole = (int)EquipmentRole.LaunchPlatform, PlatformType = (int)PlatformType.GroundBased,
Quantity = 1, PositionX = 0, PositionY = 0, PositionZ = 0, AerosolType = (int)AerosolType.InertGas, MunitionCount = 1 },
});
_scenarioService.SaveCloudDispersal(task.Id, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 300, Duration = 60 });
_scenarioService.SaveRoute(task.Id, "w1", new RoutePlan(),
_scenarioService.SaveCloudDispersal(scenario.Id, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 300, Duration = 60 });
_scenarioService.SaveRoute(scenario.Id, "w1", new RoutePlan(),
new() { new() { PosX = 0, PosY = 300, PosZ = 0, Speed = 120 }, new() { PosX = 10000, PosY = 300, PosZ = 0, Speed = 120 } });
_engine.SetFireSchedule(new List<FireEvent>());
_engine.Initialize(task.Id);
_engine.Initialize(scenario.Id);
int detectCount = 0;
for (int i = 0; i < 3000; i++)
@ -273,25 +273,25 @@ namespace CounterDrone.Core.Tests
{
// 两个探测设备D1在 X=3000 半径 2000、D2在 X=7000 半径 4000
// D1 边界 X=1000D2 边界 X=3000 → D1 更早发现
var task = _scenarioService.CreateTask("Fuse", "");
_scenarioService.SaveScene(task.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveTarget(task.Id, new DroneProfile
var scenario = _scenarioService.CreateScenario("Fuse", "");
_scenarioService.SaveScene(scenario.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveTarget(scenario.Id, new DroneProfile
{
WaveId = "w1", Quantity = 1, TypicalSpeed = 60, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
});
_scenarioService.SaveDeployment(task.Id, new List<EquipmentDeployment>
_scenarioService.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
{
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 3000, PositionY = 0, PositionZ = 0, RadarRange = 2000, DetectionAccuracy = 50 },
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 7000, PositionY = 0, PositionZ = 0, RadarRange = 4000, DetectionAccuracy = 30 },
new() { EquipmentRole = (int)EquipmentRole.LaunchPlatform, PlatformType = (int)PlatformType.GroundBased,
Quantity = 1, PositionX = 0, PositionY = 0, PositionZ = 0, AerosolType = (int)AerosolType.InertGas, MunitionCount = 1 },
});
_scenarioService.SaveCloudDispersal(task.Id, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 300, Duration = 60 });
_scenarioService.SaveRoute(task.Id, "w1", new RoutePlan(),
_scenarioService.SaveCloudDispersal(scenario.Id, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 300, Duration = 60 });
_scenarioService.SaveRoute(scenario.Id, "w1", new RoutePlan(),
new() { new() { PosX = 0, PosY = 300, PosZ = 0, Speed = 60 }, new() { PosX = 10000, PosY = 300, PosZ = 0, Speed = 60 } });
_engine.SetFireSchedule(new List<FireEvent>());
_engine.Initialize(task.Id);
_engine.Initialize(scenario.Id);
SimEvent? detected = null;
for (int i = 0; i < 6000 && detected == null; i++)