完善模型属性:加 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:
parent
ca0ce8aa52
commit
ad0eaf47da
@ -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 — 算法类型重命名
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
// === 任务 ===
|
||||
public enum TaskStatus
|
||||
public enum ScenarioStatus
|
||||
{
|
||||
Draft = 0,
|
||||
Configuring = 1,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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()))
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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>
|
||||
{
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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=1000,D2 边界 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++)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user