重命名核心模型:TargetConfig→DroneProfile, SimTask→Scenario, TaskFullConfig→ScenarioConfig, TaskId→ScenarioId
- DroneType 枚举清理:移除 Electric/Piston,重编号 HighSpeed=2 - planner_config.json / defaults.json 同步迁移 - 238 单元测试全部通过
This commit is contained in:
parent
def3996f4f
commit
ca0ce8aa52
@ -37,7 +37,7 @@
|
|||||||
### Added — FrameDataStore LiveFrames 内存回放
|
### Added — FrameDataStore LiveFrames 内存回放
|
||||||
|
|
||||||
- **Flush 后保留 LiveFrames 副本**:仿真刚结束可内存回放,零磁盘 IO
|
- **Flush 后保留 LiveFrames 副本**:仿真刚结束可内存回放,零磁盘 IO
|
||||||
- **ReplayController 双路径**:`LoadReplay(taskId, frameStore)` 优先内存,回退 SQLite
|
- **ReplayController 双路径**:`LoadReplay(scenarioId, frameStore)` 优先内存,回退 SQLite
|
||||||
- **LiveFrames 生命周期**:BeginRecording/Discard 清除
|
- **LiveFrames 生命周期**:BeginRecording/Discard 清除
|
||||||
|
|
||||||
### Changed — 3机空基编队测试启用
|
### Changed — 3机空基编队测试启用
|
||||||
@ -73,7 +73,7 @@
|
|||||||
|
|
||||||
### Changed — 消除硬编码
|
### Changed — 消除硬编码
|
||||||
|
|
||||||
- **速度从 waypoint.Speed 读取**:`DroneEntity.CruiseSpeed` / `GetDroneSpeedKph` 不再用 `TargetConfig.TypicalSpeed`
|
- **速度从 waypoint.Speed 读取**:`DroneEntity.CruiseSpeed` / `GetDroneSpeedKph` 不再用 `DroneProfile.TypicalSpeed`
|
||||||
- **`AmmunitionSpec` +`Phase2Duration`**(30s):`CloudExpansionModel` 不再写死 `30f`
|
- **`AmmunitionSpec` +`Phase2Duration`**(30s):`CloudExpansionModel` 不再写死 `30f`
|
||||||
- **`GetArrivalTime` 基于 DetectArc**:从探测点到中点算到达时间(而非航路起点)
|
- **`GetArrivalTime` 基于 DetectArc**:从探测点到中点算到达时间(而非航路起点)
|
||||||
- **TestData 改为从 seeded 数据库读取**(不再内嵌 JSON 重复 defaults.json)
|
- **TestData 改为从 seeded 数据库读取**(不再内嵌 JSON 重复 defaults.json)
|
||||||
@ -101,8 +101,8 @@
|
|||||||
|
|
||||||
### Changed — 数据模型重命名
|
### Changed — 数据模型重命名
|
||||||
|
|
||||||
- **`GroupId` → `WaveId`**:TargetConfig、EquipmentDeployment、RoutePlan、Waypoint 四表的编组外键重命名为批次外键
|
- **`GroupId` → `WaveId`**DroneProfile 四表的编组外键重命名为批次外键
|
||||||
- **RoutePlan 索引重命名**:`(TaskId, GroupId)` → `(TaskId, WaveId)`
|
- **RoutePlan 索引重命名**:`(ScenarioId, GroupId)` → `(ScenarioId, WaveId)`
|
||||||
- **RoutePlanRepository / WaypointRepository**:`GetByTaskAndGroup` → `GetByTaskAndWave`
|
- **RoutePlanRepository / WaypointRepository**:`GetByTaskAndGroup` → `GetByTaskAndWave`
|
||||||
- **IScenarioService.SaveRoute**:`groupId` 参数 → `waveId`
|
- **IScenarioService.SaveRoute**:`groupId` 参数 → `waveId`
|
||||||
|
|
||||||
|
|||||||
@ -116,17 +116,17 @@
|
|||||||
|
|
||||||
"targets": [
|
"targets": [
|
||||||
{ "Id": "quadcopter", "Name": "[Demo] 小型四旋翼(DJI类)",
|
{ "Id": "quadcopter", "Name": "[Demo] 小型四旋翼(DJI类)",
|
||||||
"TargetType": 0, "PowerType": 0, "Wingspan": 1.2, "TypicalSpeed": 60.0, "TypicalAltitude": 300.0 },
|
"DroneType": 0, "PowerType": 0, "Wingspan": 1.2, "TypicalSpeed": 60.0, "TypicalAltitude": 300.0 },
|
||||||
{ "Id": "electric-scout", "Name": "[Demo] 电推侦察无人机",
|
{ "Id": "electric-scout", "Name": "[Demo] 电推侦察无人机",
|
||||||
"TargetType": 2, "PowerType": 0, "Wingspan": 1.8, "TypicalSpeed": 100.0, "TypicalAltitude": 500.0 },
|
"DroneType": 1, "PowerType": 0, "Wingspan": 1.8, "TypicalSpeed": 100.0, "TypicalAltitude": 500.0 },
|
||||||
{ "Id": "fixed-piston", "Name": "[Demo] 固定翼活塞(Orlan类)",
|
{ "Id": "fixed-piston", "Name": "[Demo] 固定翼活塞(Orlan类)",
|
||||||
"TargetType": 1, "PowerType": 1, "Wingspan": 3.5, "TypicalSpeed": 150.0, "TypicalAltitude": 1000.0 },
|
"DroneType": 1, "PowerType": 1, "Wingspan": 3.5, "TypicalSpeed": 150.0, "TypicalAltitude": 1000.0 },
|
||||||
{ "Id": "shahed", "Name": "[Demo] 活塞巡飞弹(Shahed类)",
|
{ "Id": "shahed", "Name": "[Demo] 活塞巡飞弹(Shahed类)",
|
||||||
"TargetType": 3, "PowerType": 1, "Wingspan": 2.5, "TypicalSpeed": 200.0, "TypicalAltitude": 500.0 },
|
"DroneType": 1, "PowerType": 1, "Wingspan": 2.5, "TypicalSpeed": 200.0, "TypicalAltitude": 500.0 },
|
||||||
{ "Id": "tb2", "Name": "[Demo] 中空长航时(TB2类)",
|
{ "Id": "tb2", "Name": "[Demo] 中空长航时(TB2类)",
|
||||||
"TargetType": 1, "PowerType": 1, "Wingspan": 12.0, "TypicalSpeed": 220.0, "TypicalAltitude": 5500.0 },
|
"DroneType": 1, "PowerType": 1, "Wingspan": 12.0, "TypicalSpeed": 220.0, "TypicalAltitude": 5500.0 },
|
||||||
{ "Id": "cruise-missile", "Name": "[Demo] 巡航导弹(喷气式)",
|
{ "Id": "cruise-missile", "Name": "[Demo] 巡航导弹(喷气式)",
|
||||||
"TargetType": 4, "PowerType": 2, "Wingspan": 1.5, "TypicalSpeed": 200.0, "TypicalAltitude": 2000.0 }
|
"DroneType": 2, "PowerType": 2, "Wingspan": 1.5, "TypicalSpeed": 200.0, "TypicalAltitude": 2000.0 }
|
||||||
],
|
],
|
||||||
|
|
||||||
"detectionEquipment": [
|
"detectionEquipment": [
|
||||||
|
|||||||
@ -5,9 +5,7 @@
|
|||||||
"TypeCoefficient": {
|
"TypeCoefficient": {
|
||||||
"HighSpeed": 4.0,
|
"HighSpeed": 4.0,
|
||||||
"FixedWing": 2.0,
|
"FixedWing": 2.0,
|
||||||
"Piston": 2.0,
|
"Rotor": 1.0
|
||||||
"Rotor": 1.0,
|
|
||||||
"Electric": 1.0
|
|
||||||
},
|
},
|
||||||
"AmmoMatch": {
|
"AmmoMatch": {
|
||||||
"Electric": "InertGas",
|
"Electric": "InertGas",
|
||||||
|
|||||||
@ -134,7 +134,7 @@ public class SimulationRunner : MonoBehaviour
|
|||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
// 通过 DI 或工厂获取实例(Core 创建)
|
// 通过 DI 或工厂获取实例(Core 创建)
|
||||||
_engine = SimulationEngineFactory.Create(taskId);
|
_engine = SimulationEngineFactory.Create(scenarioId);
|
||||||
_recorder = ServiceFactory.CreateRecordService();
|
_recorder = ServiceFactory.CreateRecordService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ enum WeatherType { Sunny = 0, Overcast = 1, Fog = 2, Rain = 3, Night = 4 }
|
|||||||
enum WindDirection { N = 0, NE = 1, E = 2, SE = 3, S = 4, SW = 5, W = 6, NW = 7 }
|
enum WindDirection { N = 0, NE = 1, E = 2, SE = 3, S = 4, SW = 5, W = 6, NW = 7 }
|
||||||
|
|
||||||
// === 目标 ===
|
// === 目标 ===
|
||||||
enum TargetType { Rotor = 0, FixedWing = 1, Electric = 2, Piston = 3, HighSpeed = 4 }
|
enum DroneType { Rotor = 0, FixedWing = 1, Electric = 2, Piston = 3, HighSpeed = 4 }
|
||||||
enum PowerType { Electric = 0, Piston = 1, Jet = 2 }
|
enum PowerType { Electric = 0, Piston = 1, Jet = 2 }
|
||||||
|
|
||||||
// === 装备 & 搭载平台 ===
|
// === 装备 & 搭载平台 ===
|
||||||
@ -213,7 +213,7 @@ enum FormationMode { Single = 0, Formation = 1, Swarm = 2 }
|
|||||||
// 两者都往统一信息网络送探测信息,planner 基于融合后的探测边界规划。
|
// 两者都往统一信息网络送探测信息,planner 基于融合后的探测边界规划。
|
||||||
// 无人机批次(DroneWave):有共同航路的一组无人机(攻击方)。
|
// 无人机批次(DroneWave):有共同航路的一组无人机(攻击方)。
|
||||||
// 批次关联航路但不独占(多个批次可共享同一条航路)。
|
// 批次关联航路但不独占(多个批次可共享同一条航路)。
|
||||||
// 配置层 = TargetConfig(含 WaveId) + RoutePlan(TaskId, WaveId) + Waypoints。
|
// 配置层 = DroneProfile(含 WaveId) + RoutePlan(ScenarioId, WaveId) + Waypoints。
|
||||||
|
|
||||||
// === 运行时 ===
|
// === 运行时 ===
|
||||||
enum EntityType { Drone = 0, Platform = 1, DetectionEquip = 2, Cloud = 3, Munition = 4 }
|
enum EntityType { Drone = 0, Platform = 1, DetectionEquip = 2, Cloud = 3, Munition = 4 }
|
||||||
@ -249,8 +249,8 @@ CounterDroneBackend_Data/
|
|||||||
│ └── SimulationReport
|
│ └── SimulationReport
|
||||||
│
|
│
|
||||||
├── frames/ ← 帧数据分库(6 个月 TTL)
|
├── frames/ ← 帧数据分库(6 个月 TTL)
|
||||||
│ ├── {taskId_001}.db ← 单任务 ~161 MB(30min 仿真)
|
│ ├── {scenarioId_001}.db ← 单任务 ~161 MB(30min 仿真)
|
||||||
│ └── {taskId_002}.db
|
│ └── {scenarioId_002}.db
|
||||||
│
|
│
|
||||||
└── models/ ← 模型文件 + 预览图
|
└── models/ ← 模型文件 + 预览图
|
||||||
```
|
```
|
||||||
@ -263,25 +263,25 @@ CounterDroneBackend_Data/
|
|||||||
主库(main.db)— 永久保留:
|
主库(main.db)— 永久保留:
|
||||||
ModelInfo — 模型元数据
|
ModelInfo — 模型元数据
|
||||||
AmmunitionSpec — 弹药基础参数(第三方数据,NEW)
|
AmmunitionSpec — 弹药基础参数(第三方数据,NEW)
|
||||||
SimTask — 仿真任务主表
|
Scenario — 仿真任务主表
|
||||||
CombatScene — 步骤1:作战场景(含24h时间、天气)
|
CombatScene — 步骤1:作战场景(含24h时间、天气)
|
||||||
ControlZone — 步骤1+:管控区域/电子围栏(NEW)
|
ControlZone — 步骤1+:管控区域/电子围栏(NEW)
|
||||||
TargetConfig — 步骤2:目标配置(含 WaveId 关联批次)
|
DroneProfile — 步骤2:目标配置(含 WaveId 关联批次)
|
||||||
EquipmentDeployment — 步骤3:装备部署(火力单元含探测+打击,或独立探测节点)
|
EquipmentDeployment — 步骤3:装备部署(火力单元含探测+打击,或独立探测节点)
|
||||||
CloudDispersal — 步骤4:云团抛撒配置
|
CloudDispersal — 步骤4:云团抛撒配置
|
||||||
RoutePlan — 步骤5:航路规划(1:N SimTask,多批次;批次关联但不独占航路,FK 关联 TargetConfig.WaveId)
|
RoutePlan — 步骤5:航路规划(1:N Scenario,多批次;批次关联但不独占航路,FK 关联 DroneProfile.WaveId)
|
||||||
Waypoint — 航路点
|
Waypoint — 航路点
|
||||||
SimulationReport — 仿真报告
|
SimulationReport — 仿真报告
|
||||||
SimEvent — 仿真事件(时序图数据源)
|
SimEvent — 仿真事件(时序图数据源)
|
||||||
|
|
||||||
任务分库(frames/{taskId}.db)— 6 个月 TTL:
|
任务分库(frames/{scenarioId}.db)— 6 个月 TTL:
|
||||||
SimFrameRecord — 逐帧位置记录(回放用)
|
SimFrameRecord — 逐帧位置记录(回放用)
|
||||||
```
|
```
|
||||||
|
|
||||||
> **核心作战概念**:
|
> **核心作战概念**:
|
||||||
> - **火力单元(FireUnit)**:能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。包含探测(雷达/光电/红外)+ 打击(发射架/火炮)的完整闭环。配置层 = `EquipmentDeployment(LaunchPlatform)`。
|
> - **火力单元(FireUnit)**:能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。包含探测(雷达/光电/红外)+ 打击(发射架/火炮)的完整闭环。配置层 = `EquipmentDeployment(LaunchPlatform)`。
|
||||||
> - **独立探测节点(DetectionNode)**:只探测不打击的侦查节点(如前沿警戒雷达)。配置层 = `EquipmentDeployment(Detection)`。
|
> - **独立探测节点(DetectionNode)**:只探测不打击的侦查节点(如前沿警戒雷达)。配置层 = `EquipmentDeployment(Detection)`。
|
||||||
> - **无人机批次(DroneWave)**:有共同航路的一组无人机(攻击方)。批次关联航路但不独占(多个批次可共享同一条航路)。配置层 = `TargetConfig(WaveId)` + `RoutePlan(TaskId, WaveId)` + `Waypoints`。
|
> - **无人机批次(DroneWave)**:有共同航路的一组无人机(攻击方)。批次关联航路但不独占(多个批次可共享同一条航路)。配置层 = `DroneProfile(WaveId)` + `RoutePlan(ScenarioId, WaveId)` + `Waypoints`。
|
||||||
> - 原来的 `Group`(编组)表已移除:无人机编队概念升级为"批次",装备编组概念不再需要(火力单元是独立作战单位)。
|
> - 原来的 `Group`(编组)表已移除:无人机编队概念升级为"批次",装备编组概念不再需要(火力单元是独立作战单位)。
|
||||||
|
|
||||||
### 4.3 业务表详细设计
|
### 4.3 业务表详细设计
|
||||||
@ -301,7 +301,7 @@ CounterDroneBackend_Data/
|
|||||||
|
|
||||||
> 支持格式:**.fbx / .obj / .stl / .glb / .gltf**,单文件 ≤ 500MB,支持 2K 及以上分辨率贴图。
|
> 支持格式:**.fbx / .obj / .stl / .glb / .gltf**,单文件 ≤ 500MB,支持 2K 及以上分辨率贴图。
|
||||||
|
|
||||||
#### SimTask — 仿真任务主表
|
#### Scenario — 仿真任务主表
|
||||||
|
|
||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
@ -318,7 +318,7 @@ CounterDroneBackend_Data/
|
|||||||
|
|
||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| TaskId | TEXT | PK, FK | 1:1 SimTask |
|
| ScenarioId | TEXT | PK, FK | 1:1 Scenario |
|
||||||
| SceneType | INTEGER | | 0城市 1平原 2山区 3海岸 |
|
| SceneType | INTEGER | | 0城市 1平原 2山区 3海岸 |
|
||||||
| TimeOfDay | TEXT | DEFAULT "12:00" | **NEW** 24h 制时间,如 "14:30" |
|
| TimeOfDay | TEXT | DEFAULT "12:00" | **NEW** 24h 制时间,如 "14:30" |
|
||||||
| WeatherType | INTEGER | | 0晴 1阴 2雾 3雨 4夜 |
|
| WeatherType | INTEGER | | 0晴 1阴 2雾 3雨 4夜 |
|
||||||
@ -339,7 +339,7 @@ CounterDroneBackend_Data/
|
|||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| Id | TEXT | PK | GUID |
|
| Id | TEXT | PK | GUID |
|
||||||
| TaskId | TEXT | FK | |
|
| ScenarioId | TEXT | FK | |
|
||||||
| Name | TEXT | | 区域名称 |
|
| Name | TEXT | | 区域名称 |
|
||||||
| VerticesJson | TEXT | NOT NULL | 多边形顶点 JSON:`[{"x":0,"y":0,"z":0},...]` |
|
| VerticesJson | TEXT | NOT NULL | 多边形顶点 JSON:`[{"x":0,"y":0,"z":0},...]` |
|
||||||
| MinAltitude | REAL | | 区域底高 m |
|
| MinAltitude | REAL | | 区域底高 m |
|
||||||
@ -348,14 +348,14 @@ CounterDroneBackend_Data/
|
|||||||
|
|
||||||
> 管控区域在场景设置(地形+天气)之后配置。无人机进入管控区域 → 触发 ZoneIntruded 事件 → 该无人机任务失败。
|
> 管控区域在场景设置(地形+天气)之后配置。无人机进入管控区域 → 触发 ZoneIntruded 事件 → 该无人机任务失败。
|
||||||
|
|
||||||
#### TargetConfig — 步骤2:目标配置
|
#### DroneProfile — 步骤2:目标配置
|
||||||
|
|
||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| Id | TEXT | PK | GUID |
|
| Id | TEXT | PK | GUID |
|
||||||
| TaskId | TEXT | FK | |
|
| ScenarioId | TEXT | FK | |
|
||||||
| WaveId | TEXT | FK | 关联无人机批次(同一 WaveId 的无人机共享航路) |
|
| WaveId | TEXT | FK | 关联无人机批次(同一 WaveId 的无人机共享航路) |
|
||||||
| TargetType | INTEGER | | 0旋翼 1固定翼 2电推 3活塞 4高速 |
|
| DroneType | INTEGER | | 0旋翼 1固定翼 2电推 3活塞 4高速 |
|
||||||
| Quantity | INTEGER | DEFAULT 1 | 目标数量 |
|
| Quantity | INTEGER | DEFAULT 1 | 目标数量 |
|
||||||
| PowerType | INTEGER | | 0电推 1活塞 2喷吸气 |
|
| PowerType | INTEGER | | 0电推 1活塞 2喷吸气 |
|
||||||
| Wingspan | REAL | | m |
|
| Wingspan | REAL | | m |
|
||||||
@ -377,7 +377,7 @@ CounterDroneBackend_Data/
|
|||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| Id | TEXT | PK | GUID |
|
| Id | TEXT | PK | GUID |
|
||||||
| TaskId | TEXT | FK | |
|
| ScenarioId | TEXT | FK | |
|
||||||
| EquipmentRole | INTEGER | | 0=独立探测节点 1=火力单元(发射平台) |
|
| EquipmentRole | INTEGER | | 0=独立探测节点 1=火力单元(发射平台) |
|
||||||
| Quantity | INTEGER | DEFAULT 1 | 部署数量 |
|
| Quantity | INTEGER | DEFAULT 1 | 部署数量 |
|
||||||
| WaveId | TEXT | NULLABLE | 可选关联批次(用于 UI 分组,火力单元是独立作战单位,不强依赖批次) |
|
| WaveId | TEXT | NULLABLE | 可选关联批次(用于 UI 分组,火力单元是独立作战单位,不强依赖批次) |
|
||||||
@ -414,7 +414,7 @@ CounterDroneBackend_Data/
|
|||||||
|
|
||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| TaskId | TEXT | PK, FK | 1:1 SimTask |
|
| ScenarioId | TEXT | PK, FK | 1:1 Scenario |
|
||||||
| AerosolType | INTEGER | | 0吸入式灭火 1爆燃式 2吸入式爆炸 |
|
| AerosolType | INTEGER | | 0吸入式灭火 1爆燃式 2吸入式爆炸 |
|
||||||
| DisperseHeight | REAL | DEFAULT 300 | m |
|
| DisperseHeight | REAL | DEFAULT 300 | m |
|
||||||
| TriggerMode | INTEGER | | 0时间 1区域 2手动 |
|
| TriggerMode | INTEGER | | 0时间 1区域 2手动 |
|
||||||
@ -440,11 +440,11 @@ CounterDroneBackend_Data/
|
|||||||
|
|
||||||
#### RoutePlan — 步骤5:航路规划
|
#### RoutePlan — 步骤5:航路规划
|
||||||
|
|
||||||
> **支持多批次**:通过 `TaskId + WaveId` 唯一标识一条航路,一个任务可有多个批次各自独立航路。
|
> **支持多批次**:通过 `ScenarioId + WaveId` 唯一标识一条航路,一个任务可有多个批次各自独立航路。
|
||||||
|
|
||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| TaskId | TEXT | PK(复合) | |
|
| ScenarioId | TEXT | PK(复合) | |
|
||||||
| WaveId | TEXT | PK(复合) | 关联批次:同一任务不同批次可有独立航路(多个批次可共享同一条航路) |
|
| WaveId | TEXT | PK(复合) | 关联批次:同一任务不同批次可有独立航路(多个批次可共享同一条航路) |
|
||||||
| FormationMode | INTEGER | | 0单机 1编队 2蜂群 |
|
| FormationMode | INTEGER | | 0单机 1编队 2蜂群 |
|
||||||
| FormationSpacing | REAL | DEFAULT 50 | m |
|
| FormationSpacing | REAL | DEFAULT 50 | m |
|
||||||
@ -455,7 +455,7 @@ CounterDroneBackend_Data/
|
|||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| Id | TEXT | PK | GUID |
|
| Id | TEXT | PK | GUID |
|
||||||
| TaskId | TEXT | FK | |
|
| ScenarioId | TEXT | FK | |
|
||||||
| OrderIndex | INTEGER | NOT NULL | 航路顺序 |
|
| OrderIndex | INTEGER | NOT NULL | 航路顺序 |
|
||||||
| PosX | REAL | | 坐标(支持三维点选) |
|
| PosX | REAL | | 坐标(支持三维点选) |
|
||||||
| PosY | REAL | | |
|
| PosY | REAL | | |
|
||||||
@ -466,21 +466,21 @@ CounterDroneBackend_Data/
|
|||||||
#### ~~Group~~ — 编组(已移除)
|
#### ~~Group~~ — 编组(已移除)
|
||||||
|
|
||||||
> **概念升级**:原 Group 表的两种类型已分别升级:
|
> **概念升级**:原 Group 表的两种类型已分别升级:
|
||||||
> - `DroneFleet`(无人机编队)→ **无人机批次(DroneWave)**:由 `TargetConfig(WaveId)` + `RoutePlan(TaskId, WaveId)` + `Waypoints` 隐式表达。批次关联航路但不独占(多个批次可共享同一条航路)。
|
> - `DroneFleet`(无人机编队)→ **无人机批次(DroneWave)**:由 `DroneProfile(WaveId)` + `RoutePlan(ScenarioId, WaveId)` + `Waypoints` 隐式表达。批次关联航路但不独占(多个批次可共享同一条航路)。
|
||||||
> - `EquipmentGroup`(装备编组)→ **不再需要**。火力单元是独立作战的最小单位,不需要上层编组。
|
> - `EquipmentGroup`(装备编组)→ **不再需要**。火力单元是独立作战的最小单位,不需要上层编组。
|
||||||
>
|
>
|
||||||
> 数据层:`Group` 表保留但不再主动使用(向后兼容)。`TargetConfig.WaveId` 语义为"批次 ID"。
|
> 数据层:`Group` 表保留但不再主动使用(向后兼容)。`DroneProfile.WaveId` 语义为"批次 ID"。
|
||||||
|
|
||||||
### 4.4 运行时表
|
### 4.4 运行时表
|
||||||
|
|
||||||
> **注意**:SimEvent 和 SimulationReport 存储在主库 main.db;SimFrameRecord 存储在各任务的独立分库 frames/{taskId}.db 中。
|
> **注意**:SimEvent 和 SimulationReport 存储在主库 main.db;SimFrameRecord 存储在各任务的独立分库 frames/{scenarioId}.db 中。
|
||||||
|
|
||||||
#### SimFrameRecord — 逐帧记录
|
#### SimFrameRecord — 逐帧记录
|
||||||
|
|
||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| Id | INTEGER | PK AUTOINCREMENT | |
|
| Id | INTEGER | PK AUTOINCREMENT | |
|
||||||
| TaskId | TEXT | FK | |
|
| ScenarioId | TEXT | FK | |
|
||||||
| FrameIndex | INTEGER | | 帧序号 |
|
| FrameIndex | INTEGER | | 帧序号 |
|
||||||
| Timestamp | REAL | | 仿真时间(秒) |
|
| Timestamp | REAL | | 仿真时间(秒) |
|
||||||
| EntityId | TEXT | | |
|
| EntityId | TEXT | | |
|
||||||
@ -511,7 +511,7 @@ CounterDroneBackend_Data/
|
|||||||
| 字段 | 类型 | 约束 | 说明 |
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| Id | TEXT | PK | GUID |
|
| Id | TEXT | PK | GUID |
|
||||||
| TaskId | TEXT | FK | |
|
| ScenarioId | TEXT | FK | |
|
||||||
| OccurredAt | REAL | | 仿真时间(秒) |
|
| OccurredAt | REAL | | 仿真时间(秒) |
|
||||||
| EventType | INTEGER | | 事件类型枚举 |
|
| EventType | INTEGER | | 事件类型枚举 |
|
||||||
| SourceId | TEXT | | 源实体 ID |
|
| SourceId | TEXT | | 源实体 ID |
|
||||||
@ -691,7 +691,7 @@ Unity 渲染毁伤效果(尾焰异常、姿态失控、坠落)
|
|||||||
- 当前位置、速度、朝向
|
- 当前位置、速度、朝向
|
||||||
- HP(0~1 归一化)
|
- HP(0~1 归一化)
|
||||||
- DamageStage(Normal → EngineAnomaly → AttitudeLoss → Destroyed)
|
- DamageStage(Normal → EngineAnomaly → AttitudeLoss → Destroyed)
|
||||||
- 目标类型 TargetType、动力类型 PowerType
|
- 目标类型 DroneType、动力类型 PowerType
|
||||||
- 存活状态(飞行中/抵达目标/侵入管控区/被摧毁)
|
- 存活状态(飞行中/抵达目标/侵入管控区/被摧毁)
|
||||||
|
|
||||||
运动模型(弧长驱动,统一在 RouteGeometry):
|
运动模型(弧长驱动,统一在 RouteGeometry):
|
||||||
@ -964,12 +964,12 @@ public interface IDamageModel
|
|||||||
/// <param name="exposureTime">累计暴露时间(s)</param>
|
/// <param name="exposureTime">累计暴露时间(s)</param>
|
||||||
/// <param name="deltaTime">本次增量暴露时长(s)</param>
|
/// <param name="deltaTime">本次增量暴露时长(s)</param>
|
||||||
/// <returns>本次毁伤值(0~1 归一化)</returns>
|
/// <returns>本次毁伤值(0~1 归一化)</returns>
|
||||||
float CalculateDamage(TargetType droneType, PowerType powerType,
|
float CalculateDamage(DroneType droneType, PowerType powerType,
|
||||||
AerosolType aerosolType, float cloudDensity,
|
AerosolType aerosolType, float cloudDensity,
|
||||||
float exposureTime, float deltaTime);
|
float exposureTime, float deltaTime);
|
||||||
|
|
||||||
/// <summary>达到 100% 毁伤所需的连续暴露时间 (s),供 planner 估算拦截可行性</summary>
|
/// <summary>达到 100% 毁伤所需的连续暴露时间 (s),供 planner 估算拦截可行性</summary>
|
||||||
float RequiredExposureSeconds(TargetType droneType, PowerType powerType, AerosolType aerosolType);
|
float RequiredExposureSeconds(DroneType droneType, PowerType powerType, AerosolType aerosolType);
|
||||||
|
|
||||||
DamageStage GetDamageStage(float accumulatedDamage);
|
DamageStage GetDamageStage(float accumulatedDamage);
|
||||||
}
|
}
|
||||||
@ -1257,7 +1257,7 @@ public class FrameDataStore
|
|||||||
// 内存回放:Flush 后保留副本,供回放直接读取(零磁盘 IO)
|
// 内存回放:Flush 后保留副本,供回放直接读取(零磁盘 IO)
|
||||||
public List<SimFrameRecord> LiveFrames { get; private set; }
|
public List<SimFrameRecord> LiveFrames { get; private set; }
|
||||||
|
|
||||||
void BeginRecording(string taskId); // 清空 buffer + LiveFrames
|
void BeginRecording(string scenarioId); // 清空 buffer + LiveFrames
|
||||||
void RecordFrame(...); // 仅写内存 List<FrameRecord>
|
void RecordFrame(...); // 仅写内存 List<FrameRecord>
|
||||||
void Flush(); // 序列化 → LiveFrames 副本 + SQLite 入库
|
void Flush(); // 序列化 → LiveFrames 副本 + SQLite 入库
|
||||||
void Discard(); // 清空 buffer + LiveFrames
|
void Discard(); // 清空 buffer + LiveFrames
|
||||||
@ -1286,9 +1286,9 @@ public class FrameDataStore
|
|||||||
|
|
||||||
### 7.3 回放
|
### 7.3 回放
|
||||||
|
|
||||||
- `ReplayController.LoadReplay(taskId, frameStore)` 双路径:
|
- `ReplayController.LoadReplay(scenarioId, frameStore)` 双路径:
|
||||||
- 传入 `frameStore` 且 `LiveFrames` 非空 → 直接使用内存数据(仿真刚结束,零延迟)
|
- 传入 `frameStore` 且 `LiveFrames` 非空 → 直接使用内存数据(仿真刚结束,零延迟)
|
||||||
- 否则 → 从 `frames/{taskId}.db` 读取(历史仿真)
|
- 否则 → 从 `frames/{scenarioId}.db` 读取(历史仿真)
|
||||||
- WHERE FrameIndex BETWEEN a AND b 命中索引,毫秒级
|
- WHERE FrameIndex BETWEEN a AND b 命中索引,毫秒级
|
||||||
- 回放帧率由前端控制(可与录制不同)
|
- 回放帧率由前端控制(可与录制不同)
|
||||||
- 支持暂停 / 快进 / 后退 / 跳转
|
- 支持暂停 / 快进 / 后退 / 跳转
|
||||||
@ -1316,12 +1316,12 @@ public class FrameDataStore
|
|||||||
### 7.5 TTL 清理策略
|
### 7.5 TTL 清理策略
|
||||||
|
|
||||||
```
|
```
|
||||||
仿真完成 → 生成 frames/{taskId}.db
|
仿真完成 → 生成 frames/{scenarioId}.db
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
CleanupExpired() 每天执行一次:
|
CleanupExpired() 每天执行一次:
|
||||||
├─ 扫描 frames/ 目录
|
├─ 扫描 frames/ 目录
|
||||||
├─ 检查 {taskId}.db 的创建时间
|
├─ 检查 {scenarioId}.db 的创建时间
|
||||||
└─ 超过 6 个月 → 删除文件
|
└─ 超过 6 个月 → 删除文件
|
||||||
|
|
||||||
可选扩展:
|
可选扩展:
|
||||||
@ -1350,7 +1350,7 @@ public class FrameDataStore
|
|||||||
|
|
||||||
| 阶段 | 内容 | 数据来源 |
|
| 阶段 | 内容 | 数据来源 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| **仿真前** | 我方配置(装备类型/数量、火力单元部署、弹药类型/数量、部署位置、管控区域) | SimTask + 5 步配置 |
|
| **仿真前** | 我方配置(装备类型/数量、火力单元部署、弹药类型/数量、部署位置、管控区域) | Scenario + 5 步配置 |
|
||||||
| **仿真中** | 关键事件时序表、各实体轨迹概要、云团演化数据 | SimEvent + SimFrameRecord |
|
| **仿真中** | 关键事件时序表、各实体轨迹概要、云团演化数据 | SimEvent + SimFrameRecord |
|
||||||
| **仿真后** | 拦截成功率、各阶段耗时、毁伤效果评估、对抗结果判定 | SimEvent 汇总统计 |
|
| **仿真后** | 拦截成功率、各阶段耗时、毁伤效果评估、对抗结果判定 | SimEvent 汇总统计 |
|
||||||
|
|
||||||
@ -1385,28 +1385,28 @@ interface IModelService
|
|||||||
// === 想定管理 ===
|
// === 想定管理 ===
|
||||||
interface IScenarioService
|
interface IScenarioService
|
||||||
{
|
{
|
||||||
SimTask CreateTask(string name, string taskNumber);
|
Scenario CreateTask(string name, string taskNumber);
|
||||||
void DeleteTask(string id);
|
void DeleteTask(string id);
|
||||||
PagedResult<SimTask> SearchTasks(string kw, DateTime? from, DateTime? to, int page, int size);
|
PagedResult<Scenario> SearchTasks(string kw, DateTime? from, DateTime? to, int page, int size);
|
||||||
TaskFullConfig GetTaskDetail(string id);
|
ScenarioConfig GetTaskDetail(string id);
|
||||||
|
|
||||||
// 步骤保存
|
// 步骤保存
|
||||||
void SaveScene(string taskId, CombatScene scene);
|
void SaveScene(string scenarioId, CombatScene scene);
|
||||||
void SaveControlZones(string taskId, List<ControlZone> zones); // NEW
|
void SaveControlZones(string scenarioId, List<ControlZone> zones); // NEW
|
||||||
void SaveTarget(string taskId, TargetConfig target);
|
void SaveTarget(string scenarioId, DroneProfile target);
|
||||||
void SaveDeployment(string taskId, List<EquipmentDeployment> equips); // 重构
|
void SaveDeployment(string scenarioId, List<EquipmentDeployment> equips); // 重构
|
||||||
void SaveCloudDispersal(string taskId, CloudDispersal cloud);
|
void SaveCloudDispersal(string scenarioId, CloudDispersal cloud);
|
||||||
void SaveRoute(string taskId, RoutePlan route, List<Waypoint> wps);
|
void SaveRoute(string scenarioId, RoutePlan route, List<Waypoint> wps);
|
||||||
void UpdateStep(string taskId, int step);
|
void UpdateStep(string scenarioId, int step);
|
||||||
|
|
||||||
// 综合防御推荐(NEW — 多编队版)
|
// 综合防御推荐(NEW — 多编队版)
|
||||||
DefenseRecommendation GetDefenseRecommendation(string taskId);
|
DefenseRecommendation GetDefenseRecommendation(string scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// === 仿真引擎 ===
|
// === 仿真引擎 ===
|
||||||
interface ISimulationEngine
|
interface ISimulationEngine
|
||||||
{
|
{
|
||||||
void Initialize(string taskId);
|
void Initialize(string scenarioId);
|
||||||
SimulationFrameResult Tick(float deltaTime); // 返回本帧快照+新事件(复用对象池)
|
SimulationFrameResult Tick(float deltaTime); // 返回本帧快照+新事件(复用对象池)
|
||||||
void Pause();
|
void Pause();
|
||||||
void Resume();
|
void Resume();
|
||||||
@ -1420,19 +1420,19 @@ interface ISimulationEngine
|
|||||||
// Stop/完成时调 Flush 批量落库。事件 SimEvent 由引擎直接写入主库。
|
// Stop/完成时调 Flush 批量落库。事件 SimEvent 由引擎直接写入主库。
|
||||||
class FrameDataStore
|
class FrameDataStore
|
||||||
{
|
{
|
||||||
void BeginRecording(string taskId);
|
void BeginRecording(string scenarioId);
|
||||||
void RecordFrame(int frameIdx, float timestamp, List<EntitySnapshot> entities);
|
void RecordFrame(int frameIdx, float timestamp, List<EntitySnapshot> entities);
|
||||||
void Flush(); // 批量落库
|
void Flush(); // 批量落库
|
||||||
void Discard();
|
void Discard();
|
||||||
List<SimFrameRecord> ReadFrames(string taskId, int fromFrame, int toFrame);
|
List<SimFrameRecord> ReadFrames(string scenarioId, int fromFrame, int toFrame);
|
||||||
void DeleteTaskDb(string taskId);
|
void DeleteTaskDb(string scenarioId);
|
||||||
int CleanupExpired(); // TTL 清理(RetentionDays,默认 180)
|
int CleanupExpired(); // TTL 清理(RetentionDays,默认 180)
|
||||||
}
|
}
|
||||||
|
|
||||||
// === 仿真报告 ===
|
// === 仿真报告 ===
|
||||||
interface IReportService
|
interface IReportService
|
||||||
{
|
{
|
||||||
SimulationReport Generate(string taskId);
|
SimulationReport Generate(string scenarioId);
|
||||||
SimulationReport GetReport(string id);
|
SimulationReport GetReport(string id);
|
||||||
void DeleteReport(string id);
|
void DeleteReport(string id);
|
||||||
byte[] ExportReport(string id, string format); // "pdf" | "word"
|
byte[] ExportReport(string id, string format); // "pdf" | "word"
|
||||||
@ -1449,28 +1449,28 @@ interface IReportService
|
|||||||
一个仿真任务可包含**多种类型、多批次**的无人机,每批次有独立的航路:
|
一个仿真任务可包含**多种类型、多批次**的无人机,每批次有独立的航路:
|
||||||
|
|
||||||
```
|
```
|
||||||
SimTask "城市防御演习"
|
Scenario "城市防御演习"
|
||||||
├── CombatScene(共享)
|
├── CombatScene(共享)
|
||||||
├── ControlZone[](共享)
|
├── ControlZone[](共享)
|
||||||
├── CloudDispersal(共享:云团参数,具体拦截方案由算法确定)
|
├── CloudDispersal(共享:云团参数,具体拦截方案由算法确定)
|
||||||
│
|
│
|
||||||
├── 批次 A(活塞×3) ← WaveId
|
├── 批次 A(活塞×3) ← WaveId
|
||||||
│ ├── TargetConfig(WaveId=A,类型=活塞,数量=3)
|
│ ├── DroneProfile(WaveId=A,类型=活塞,数量=3)
|
||||||
│ ├── RoutePlan(TaskId + WaveId=A,航路 A:北→南)
|
│ ├── RoutePlan(ScenarioId + WaveId=A,航路 A:北→南)
|
||||||
│ └── Waypoint[]
|
│ └── Waypoint[]
|
||||||
│
|
│
|
||||||
├── 批次 B(喷气×2) ← WaveId
|
├── 批次 B(喷气×2) ← WaveId
|
||||||
│ ├── TargetConfig(WaveId=B,类型=高速,数量=2)
|
│ ├── DroneProfile(WaveId=B,类型=高速,数量=2)
|
||||||
│ ├── RoutePlan(TaskId + WaveId=B,航路 B:西→东)
|
│ ├── RoutePlan(ScenarioId + WaveId=B,航路 B:西→东)
|
||||||
│ └── Waypoint[]
|
│ └── Waypoint[]
|
||||||
│
|
│
|
||||||
└── 批次 C(旋翼×5) ← WaveId
|
└── 批次 C(旋翼×5) ← WaveId
|
||||||
├── TargetConfig(WaveId=C,类型=旋翼,数量=5)
|
├── DroneProfile(WaveId=C,类型=旋翼,数量=5)
|
||||||
├── RoutePlan(TaskId + WaveId=C,航路 C)
|
├── RoutePlan(ScenarioId + WaveId=C,航路 C)
|
||||||
└── Waypoint[]
|
└── Waypoint[]
|
||||||
```
|
```
|
||||||
|
|
||||||
**关键变更**:`RoutePlan` 主键为 `(TaskId, WaveId)` 复合键。`Waypoint` 通过 `(TaskId, WaveId)` 关联航路。
|
**关键变更**:`RoutePlan` 主键为 `(ScenarioId, WaveId)` 复合键。`Waypoint` 通过 `(ScenarioId, WaveId)` 关联航路。
|
||||||
|
|
||||||
### 推荐算法
|
### 推荐算法
|
||||||
|
|
||||||
@ -1515,13 +1515,13 @@ engine.SetFireSchedule(merged);
|
|||||||
|
|
||||||
| 表 | 变更 |
|
| 表 | 变更 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `RoutePlan` | PK 为 (TaskId, WaveId) 复合键 |
|
| `RoutePlan` | PK 为 (ScenarioId, WaveId) 复合键 |
|
||||||
| `TargetConfig` | 不变(已有 WaveId) |
|
| `DroneProfile` | 不变(已有 WaveId) |
|
||||||
| `Waypoint` | 关联 `(TaskId, WaveId)` 定位航路 |
|
| `Waypoint` | 关联 `(ScenarioId, WaveId)` 定位航路 |
|
||||||
|
|
||||||
`IScenarioService` 的 `SaveRoute` 签名:
|
`IScenarioService` 的 `SaveRoute` 签名:
|
||||||
```csharp
|
```csharp
|
||||||
void SaveRoute(string taskId, string waveId, RoutePlan route, List<Waypoint> wps); // waveId 参数
|
void SaveRoute(string scenarioId, string waveId, RoutePlan route, List<Waypoint> wps); // waveId 参数
|
||||||
```
|
```
|
||||||
|
|
||||||
### 报告
|
### 报告
|
||||||
|
|||||||
@ -59,7 +59,7 @@ class FireUnit {
|
|||||||
```csharp
|
```csharp
|
||||||
class DroneWave {
|
class DroneWave {
|
||||||
string WaveId; // 批次 ID
|
string WaveId; // 批次 ID
|
||||||
TargetConfig Target; // 类型、数量、动力、翼展、速度、高度
|
DroneProfile Target; // 类型、数量、动力、翼展、速度、高度
|
||||||
List<Waypoint> Waypoints; // 航路点
|
List<Waypoint> Waypoints; // 航路点
|
||||||
float ArrivalTime; // 预计算:到达防御区域中点的时间 s
|
float ArrivalTime; // 预计算:到达防御区域中点的时间 s
|
||||||
}
|
}
|
||||||
@ -292,7 +292,7 @@ public class DefaultDefensePlanner : IDefensePlanner
|
|||||||
| 旧接口 | 新接口 | 状态 |
|
| 旧接口 | 新接口 | 状态 |
|
||||||
|--------|--------|------|
|
|--------|--------|------|
|
||||||
| `IDefenseAdvisor.Recommend(ThreatProfile)` | `IDefensePlanner.Plan(fireUnits, threats, env)` | ✅ 已替换 |
|
| `IDefenseAdvisor.Recommend(ThreatProfile)` | `IDefensePlanner.Plan(fireUnits, threats, env)` | ✅ 已替换 |
|
||||||
| `IDefenseAdvisor.GetDefenseRecommendation(taskId)` | 不再需要 | ✅ 已删除 |
|
| `IDefenseAdvisor.GetDefenseRecommendation(scenarioId)` | 不再需要 | ✅ 已删除 |
|
||||||
| `DefaultDefenseAdvisor.RecommendMultiGroup()` | Planner 原生支持多批次 | ✅ 已删除 |
|
| `DefaultDefenseAdvisor.RecommendMultiGroup()` | Planner 原生支持多批次 | ✅ 已删除 |
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -300,7 +300,7 @@ public class DefaultDefensePlanner : IDefensePlanner
|
|||||||
## 7. 数据流改造(已实现)
|
## 7. 数据流改造(已实现)
|
||||||
|
|
||||||
```
|
```
|
||||||
SimulationEngine.Initialize(taskId)
|
SimulationEngine.Initialize(scenarioId)
|
||||||
│
|
│
|
||||||
│ // 引擎内部组装
|
│ // 引擎内部组装
|
||||||
├── BuildFireUnits(config) → List<FireUnit>
|
├── BuildFireUnits(config) → List<FireUnit>
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
## 数据流
|
## 数据流
|
||||||
|
|
||||||
```
|
```
|
||||||
想定配置(EquipmentDeployment + TargetConfig + RoutePlan + CombatScene)
|
想定配置(EquipmentDeployment + DroneProfile + RoutePlan + CombatScene)
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
SimulationEngine.Initialize()
|
SimulationEngine.Initialize()
|
||||||
|
|||||||
@ -181,7 +181,7 @@ Phase 12 ✅ 空基/地基统一规划 + 编队轴 + LaneDivider
|
|||||||
| 8.1.1 | 探测设备搜索逻辑 | ✅ 事前规划:`DetectionCalculator` 算统一信息网络最早探测点,planner 基于探测边界算到达时间;天气衰减光电;精度影响散布。**实时探测设计完成**(总体架构设计第十四章),待 Phase 10 开发 |
|
| 8.1.1 | 探测设备搜索逻辑 | ✅ 事前规划:`DetectionCalculator` 算统一信息网络最早探测点,planner 基于探测边界算到达时间;天气衰减光电;精度影响散布。**实时探测设计完成**(总体架构设计第十四章),待 Phase 10 开发 |
|
||||||
| 8.1.2 | 蜂群运动模型 | ⬜ `FormationMode.Swarm` 枚举已定义,需差异化行为(随机扰动、个体差异) |
|
| 8.1.2 | 蜂群运动模型 | ⬜ `FormationMode.Swarm` 枚举已定义,需差异化行为(随机扰动、个体差异) |
|
||||||
| 8.1.3 | 空基平台 + DefensePlanner | ✅ 五步规划引擎,通道模型,物理间隔错发,路径积分毁伤判定 |
|
| 8.1.3 | 空基平台 + DefensePlanner | ✅ 五步规划引擎,通道模型,物理间隔错发,路径积分毁伤判定 |
|
||||||
| 8.1.4 | 预置典型目标库 | ⬜ 具体无人机型号 JSON 配置(如 DJI Mavic 3、Shahed-136 等),导入 `TargetConfig` 默认值 |
|
| 8.1.4 | 预置典型目标库 | ⬜ 具体无人机型号 JSON 配置(如 DJI Mavic 3、Shahed-136 等),导入 `DroneProfile` 默认值 |
|
||||||
| 8.1.5 | 毁伤曲线参数校准 | ✅ RequiredExposureSeconds 替代硬编码,密度阈值统一在引擎检查 |
|
| 8.1.5 | 毁伤曲线参数校准 | ✅ RequiredExposureSeconds 替代硬编码,密度阈值统一在引擎检查 |
|
||||||
| 8.1.6 | Fallback/default 清理 | ✅ 删除所有硬编码默认值和静默 fallback,参数缺失即报错 |
|
| 8.1.6 | Fallback/default 清理 | ✅ 删除所有硬编码默认值和静默 fallback,参数缺失即报错 |
|
||||||
|
|
||||||
@ -339,7 +339,7 @@ T1(数据层)→ T2(算法层 IsInCoverage)
|
|||||||
| # | 任务 | 说明 |
|
| # | 任务 | 说明 |
|
||||||
|---|------|------|
|
|---|------|------|
|
||||||
| 11.5.1 | FrameDataStore.LiveFrames | Flush 后保留副本;BeginRecording/Discard 清除 |
|
| 11.5.1 | FrameDataStore.LiveFrames | Flush 后保留副本;BeginRecording/Discard 清除 |
|
||||||
| 11.5.2 | ReplayController 双路径 | LoadReplay(taskId, frameStore) 优先内存,回退 SQLite |
|
| 11.5.2 | ReplayController 双路径 | LoadReplay(scenarioId, frameStore) 优先内存,回退 SQLite |
|
||||||
|
|
||||||
### 11.6 测试启用
|
### 11.6 测试启用
|
||||||
|
|
||||||
|
|||||||
@ -67,12 +67,12 @@ var scenario = GetComponent<ScenarioManager>();
|
|||||||
|
|
||||||
// 方式 A:预设想定(最快)
|
// 方式 A:预设想定(最快)
|
||||||
var demos = scenario.Search("Demo", null, null, 1, 100);
|
var demos = scenario.Search("Demo", null, null, 1, 100);
|
||||||
var taskId = demos.Items[0].Id;
|
var scenarioId = demos.Items[0].Id;
|
||||||
|
|
||||||
// 方式 B:自定义
|
// 方式 B:自定义
|
||||||
var task = scenario.CreateTask("我的想定", "");
|
var task = scenario.CreateTask("我的想定", "");
|
||||||
scenario.SaveScene(task.Id, new CombatScene { WindSpeed = 5, WindDirection = (int)WindDirection.E });
|
scenario.SaveScene(task.Id, new CombatScene { WindSpeed = 5, WindDirection = (int)WindDirection.E });
|
||||||
scenario.SaveTarget(task.Id, new TargetConfig { WaveId = "w1", Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500 });
|
scenario.SaveTarget(task.Id, new DroneProfile { WaveId = "w1", Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500 });
|
||||||
scenario.SaveRoute(task.Id, "w1", new RoutePlan(), new List<Waypoint> {
|
scenario.SaveRoute(task.Id, "w1", new RoutePlan(), new List<Waypoint> {
|
||||||
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
new() { PosX = 10000, PosY = 500, PosZ = 0, Speed = 200 }
|
new() { PosX = 10000, PosY = 500, PosZ = 0, Speed = 200 }
|
||||||
@ -88,7 +88,7 @@ scenario.UpdateStep(task.Id, 5);
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var runner = GetComponent<SimulationRunner>();
|
var runner = GetComponent<SimulationRunner>();
|
||||||
runner.LoadAndStart(taskId);
|
runner.LoadAndStart(scenarioId);
|
||||||
runner.Engine.TimeScale = 4f;
|
runner.Engine.TimeScale = 4f;
|
||||||
|
|
||||||
// 事件
|
// 事件
|
||||||
@ -110,8 +110,8 @@ void Update() {
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var rm = GetComponent<ReportManager>();
|
var rm = GetComponent<ReportManager>();
|
||||||
var detail = scenario.GetDetail(taskId);
|
var detail = scenario.GetDetail(scenarioId);
|
||||||
var report = rm.Generate(taskId, detail, runner.Engine.Events.ToList(),
|
var report = rm.Generate(scenarioId, detail, runner.Engine.Events.ToList(),
|
||||||
runner.Engine.Drones[0].Status.ToString(), runner.Engine.SimulationTime);
|
runner.Engine.Drones[0].Status.ToString(), runner.Engine.SimulationTime);
|
||||||
string path = rm.Export(report.Id);
|
string path = rm.Export(report.Id);
|
||||||
```
|
```
|
||||||
@ -120,7 +120,7 @@ string path = rm.Export(report.Id);
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var replay = GetComponent<ReplayController>();
|
var replay = GetComponent<ReplayController>();
|
||||||
replay.LoadReplay(taskId);
|
replay.LoadReplay(scenarioId);
|
||||||
for (int i = 0; i < replay.TotalFrames; i++) {
|
for (int i = 0; i < replay.TotalFrames; i++) {
|
||||||
var frame = replay.GetFrame(i);
|
var frame = replay.GetFrame(i);
|
||||||
// frame.SimulationTime, frame.EntitySnapshots...
|
// frame.SimulationTime, frame.EntitySnapshots...
|
||||||
@ -144,14 +144,14 @@ mgr.DataService.GetAllEnvironments() / GetAllFormations() / GetAllRoutes()
|
|||||||
// 想定 CRUD
|
// 想定 CRUD
|
||||||
mgr.CreateTask(name, "") / mgr.DeleteTask(id) / mgr.Search(kw, from, to, page, size)
|
mgr.CreateTask(name, "") / mgr.DeleteTask(id) / mgr.Search(kw, from, to, page, size)
|
||||||
mgr.SaveScene / SaveTarget / SaveDeployment / SaveCloud / SaveRoute / UpdateStep
|
mgr.SaveScene / SaveTarget / SaveDeployment / SaveCloud / SaveRoute / UpdateStep
|
||||||
mgr.GetDetail(id) → TaskFullConfig
|
mgr.GetDetail(id) → ScenarioConfig
|
||||||
```
|
```
|
||||||
|
|
||||||
### SimulationRunner — 仿真执行
|
### SimulationRunner — 仿真执行
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var runner = GetComponent<SimulationRunner>();
|
var runner = GetComponent<SimulationRunner>();
|
||||||
runner.LoadAndStart(taskId);
|
runner.LoadAndStart(scenarioId);
|
||||||
runner.Engine.TimeScale = 4f;
|
runner.Engine.TimeScale = 4f;
|
||||||
runner.Engine.State / Drones / Clouds / Munitions / Platforms / DetectionEntities / Events
|
runner.Engine.State / Drones / Clouds / Munitions / Platforms / DetectionEntities / Events
|
||||||
runner.Engine.Tick(dt) → SimulationFrameResult { EntitySnapshots, NewEvents }
|
runner.Engine.Tick(dt) → SimulationFrameResult { EntitySnapshots, NewEvents }
|
||||||
@ -163,7 +163,7 @@ runner.Stop();
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var rm = GetComponent<ReportManager>();
|
var rm = GetComponent<ReportManager>();
|
||||||
rm.Generate(taskId, detail, events, status, duration) → SimulationReport
|
rm.Generate(scenarioId, detail, events, status, duration) → SimulationReport
|
||||||
rm.Export(reportId) → 文件路径
|
rm.Export(reportId) → 文件路径
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ rm.Export(reportId) → 文件路径
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var replay = GetComponent<ReplayController>();
|
var replay = GetComponent<ReplayController>();
|
||||||
replay.LoadReplay(taskId);
|
replay.LoadReplay(scenarioId);
|
||||||
replay.TotalFrames / replay.GetFrame(index)
|
replay.TotalFrames / replay.GetFrame(index)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -9,20 +9,20 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
private const float BaseRate = 0.02f;
|
private const float BaseRate = 0.02f;
|
||||||
private const float ExpFactor = 0.3f;
|
private const float ExpFactor = 0.3f;
|
||||||
|
|
||||||
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
public float CalculateDamage(DroneType droneType, PowerType powerType,
|
||||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
||||||
{
|
{
|
||||||
if (aerosolType != AerosolType.ActiveFuel) return 0f;
|
if (aerosolType != AerosolType.ActiveFuel) return 0f;
|
||||||
var exponential = (float)System.Math.Exp(exposureTime * ExpFactor);
|
var exponential = (float)System.Math.Exp(exposureTime * ExpFactor);
|
||||||
var damage = BaseRate * exponential * deltaTime;
|
var damage = BaseRate * exponential * deltaTime;
|
||||||
var sensitivity = droneType == TargetType.HighSpeed ? 1.5f : 1.0f;
|
var sensitivity = droneType == DroneType.HighSpeed ? 1.5f : 1.0f;
|
||||||
return damage * sensitivity;
|
return damage * sensitivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float RequiredExposureSeconds(TargetType droneType, PowerType powerType, AerosolType aerosolType)
|
public float RequiredExposureSeconds(DroneType droneType, PowerType powerType, AerosolType aerosolType)
|
||||||
{
|
{
|
||||||
if (aerosolType != AerosolType.ActiveFuel) return float.MaxValue;
|
if (aerosolType != AerosolType.ActiveFuel) return float.MaxValue;
|
||||||
var sensitivity = droneType == TargetType.HighSpeed ? 1.5f : 1.0f;
|
var sensitivity = droneType == DroneType.HighSpeed ? 1.5f : 1.0f;
|
||||||
// 数值求解:BaseRate * e^(t*ExpFactor) * sensitivity 的积分 = 1
|
// 数值求解:BaseRate * e^(t*ExpFactor) * sensitivity 的积分 = 1
|
||||||
// ∫[0,T] BaseRate * s * e^(k*t) dt = BaseRate * s * (e^(k*T) - 1) / k = 1
|
// ∫[0,T] BaseRate * s * e^(k*t) dt = BaseRate * s * (e^(k*T) - 1) / k = 1
|
||||||
// e^(k*T) = 1 + k / (BaseRate * s)
|
// e^(k*T) = 1 + k / (BaseRate * s)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
private const float ResidualRate = 0.05f;
|
private const float ResidualRate = 0.05f;
|
||||||
private bool _triggered;
|
private bool _triggered;
|
||||||
|
|
||||||
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
public float CalculateDamage(DroneType droneType, PowerType powerType,
|
||||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
||||||
{
|
{
|
||||||
if (aerosolType != AerosolType.ActiveMaterial) return 0f;
|
if (aerosolType != AerosolType.ActiveMaterial) return 0f;
|
||||||
@ -25,7 +25,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
return ResidualRate * deltaTime;
|
return ResidualRate * deltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float RequiredExposureSeconds(TargetType droneType, PowerType powerType, AerosolType aerosolType)
|
public float RequiredExposureSeconds(DroneType droneType, PowerType powerType, AerosolType aerosolType)
|
||||||
{
|
{
|
||||||
if (aerosolType != AerosolType.ActiveMaterial) return float.MaxValue;
|
if (aerosolType != AerosolType.ActiveMaterial) return float.MaxValue;
|
||||||
var sensitivity = powerType == PowerType.Jet ? 1.3f : 1.0f;
|
var sensitivity = powerType == PowerType.Jet ? 1.3f : 1.0f;
|
||||||
|
|||||||
@ -51,7 +51,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
public class DroneWave
|
public class DroneWave
|
||||||
{
|
{
|
||||||
public string WaveId { get; set; } = string.Empty;
|
public string WaveId { get; set; } = string.Empty;
|
||||||
public TargetConfig Target { get; set; } = new();
|
public DroneProfile Target { get; set; } = new();
|
||||||
public RoutePlan Route { get; set; } = new();
|
public RoutePlan Route { get; set; } = new();
|
||||||
public List<Waypoint> Waypoints { get; set; } = new();
|
public List<Waypoint> Waypoints { get; set; } = new();
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
private readonly ActiveMaterialDamageModel _activeMaterial = new();
|
private readonly ActiveMaterialDamageModel _activeMaterial = new();
|
||||||
private readonly ActiveFuelDamageModel _activeFuel = new();
|
private readonly ActiveFuelDamageModel _activeFuel = new();
|
||||||
|
|
||||||
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
public float CalculateDamage(DroneType droneType, PowerType powerType,
|
||||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
||||||
{
|
{
|
||||||
return aerosolType switch
|
return aerosolType switch
|
||||||
@ -21,7 +21,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public float RequiredExposureSeconds(TargetType droneType, PowerType powerType, AerosolType aerosolType)
|
public float RequiredExposureSeconds(DroneType droneType, PowerType powerType, AerosolType aerosolType)
|
||||||
{
|
{
|
||||||
return aerosolType switch
|
return aerosolType switch
|
||||||
{
|
{
|
||||||
|
|||||||
@ -90,9 +90,9 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
// Step 1: 威胁指数
|
// Step 1: 威胁指数
|
||||||
// ═══════════════════════════════════════════════
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
private static float CalcThreatIndex(PlannerConfig config, TargetConfig target)
|
private static float CalcThreatIndex(PlannerConfig config, DroneProfile target)
|
||||||
{
|
{
|
||||||
float typeCoef = config.TypeCoefficient.GetValueOrDefault((TargetType)target.TargetType, 1f);
|
float typeCoef = config.TypeCoefficient.GetValueOrDefault((DroneType)target.DroneType, 1f);
|
||||||
float speedCoef = (float)target.TypicalSpeed / 60f;
|
float speedCoef = (float)target.TypicalSpeed / 60f;
|
||||||
return typeCoef * speedCoef;
|
return typeCoef * speedCoef;
|
||||||
}
|
}
|
||||||
@ -359,7 +359,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
if (!HasInterceptWindow(threat, midArc, expansionTime, deliveryTime)) return null;
|
if (!HasInterceptWindow(threat, midArc, expansionTime, deliveryTime)) return null;
|
||||||
|
|
||||||
float avgSpeed = GetDroneSpeedMs(threat);
|
float avgSpeed = GetDroneSpeedMs(threat);
|
||||||
float neededExposure = _damageModel.RequiredExposureSeconds((TargetType)threat.Target.TargetType, (PowerType)threat.Target.PowerType, ammoType);
|
float neededExposure = _damageModel.RequiredExposureSeconds((DroneType)threat.Target.DroneType, (PowerType)threat.Target.PowerType, ammoType);
|
||||||
float actualExposure = effectiveR * 2f / avgSpeed;
|
float actualExposure = effectiveR * 2f / avgSpeed;
|
||||||
float prob = Math.Min(_config.MaxInterceptProbability, actualExposure / neededExposure);
|
float prob = Math.Min(_config.MaxInterceptProbability, actualExposure / neededExposure);
|
||||||
|
|
||||||
@ -394,7 +394,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
var cloudModel = new CloudExpansionModel(ammo, env);
|
var cloudModel = new CloudExpansionModel(ammo, env);
|
||||||
float avgSpeed = GetDroneSpeedMs(threat);
|
float avgSpeed = GetDroneSpeedMs(threat);
|
||||||
float neededExposure = _damageModel.RequiredExposureSeconds(
|
float neededExposure = _damageModel.RequiredExposureSeconds(
|
||||||
(TargetType)threat.Target.TargetType, (PowerType)threat.Target.PowerType, (AerosolType)ammo.AerosolType);
|
(DroneType)threat.Target.DroneType, (PowerType)threat.Target.PowerType, (AerosolType)ammo.AerosolType);
|
||||||
float requiredCoverage = neededExposure * avgSpeed;
|
float requiredCoverage = neededExposure * avgSpeed;
|
||||||
// 间距由配置的重叠比例驱动,公式在 CloudExpansionModel(共享)
|
// 间距由配置的重叠比例驱动,公式在 CloudExpansionModel(共享)
|
||||||
float spacing = 2f * turbulentRadius * (1f - _config.CloudOverlapRatio);
|
float spacing = 2f * turbulentRadius * (1f - _config.CloudOverlapRatio);
|
||||||
@ -411,7 +411,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
float actualExposure = actualCoverage / avgSpeed;
|
float actualExposure = actualCoverage / avgSpeed;
|
||||||
|
|
||||||
var aerosolType = (AerosolType)ammo.AerosolType;
|
var aerosolType = (AerosolType)ammo.AerosolType;
|
||||||
float neededExposure = _damageModel.RequiredExposureSeconds((TargetType)threat.Target.TargetType, (PowerType)threat.Target.PowerType, aerosolType);
|
float neededExposure = _damageModel.RequiredExposureSeconds((DroneType)threat.Target.DroneType, (PowerType)threat.Target.PowerType, aerosolType);
|
||||||
|
|
||||||
return Math.Min(_config.MaxInterceptProbability, actualExposure / neededExposure);
|
return Math.Min(_config.MaxInterceptProbability, actualExposure / neededExposure);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,11 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
/// <summary>毁伤模型接口</summary>
|
/// <summary>毁伤模型接口</summary>
|
||||||
public interface IDamageModel
|
public interface IDamageModel
|
||||||
{
|
{
|
||||||
float CalculateDamage(TargetType droneType, PowerType powerType,
|
float CalculateDamage(DroneType droneType, PowerType powerType,
|
||||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime);
|
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime);
|
||||||
|
|
||||||
/// <summary>达到 100% 毁伤所需的连续暴露时间 (s)</summary>
|
/// <summary>达到 100% 毁伤所需的连续暴露时间 (s)</summary>
|
||||||
float RequiredExposureSeconds(TargetType droneType, PowerType powerType, AerosolType aerosolType);
|
float RequiredExposureSeconds(DroneType droneType, PowerType powerType, AerosolType aerosolType);
|
||||||
|
|
||||||
DamageStage GetDamageStage(float accumulatedDamage);
|
DamageStage GetDamageStage(float accumulatedDamage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
{
|
{
|
||||||
private const float DamageRate = 0.15f; // 每秒毁伤率
|
private const float DamageRate = 0.15f; // 每秒毁伤率
|
||||||
|
|
||||||
public float CalculateDamage(TargetType droneType, PowerType powerType,
|
public float CalculateDamage(DroneType droneType, PowerType powerType,
|
||||||
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime)
|
||||||
{
|
{
|
||||||
if (aerosolType != AerosolType.InertGas) return 0f;
|
if (aerosolType != AerosolType.InertGas) return 0f;
|
||||||
@ -16,7 +16,7 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
return DamageRate * sensitivity * deltaTime;
|
return DamageRate * sensitivity * deltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float RequiredExposureSeconds(TargetType droneType, PowerType powerType, AerosolType aerosolType)
|
public float RequiredExposureSeconds(DroneType droneType, PowerType powerType, AerosolType aerosolType)
|
||||||
{
|
{
|
||||||
if (aerosolType != AerosolType.InertGas) return float.MaxValue;
|
if (aerosolType != AerosolType.InertGas) return float.MaxValue;
|
||||||
var sensitivity = powerType == PowerType.Piston ? 1.5f : 1.0f;
|
var sensitivity = powerType == PowerType.Piston ? 1.5f : 1.0f;
|
||||||
|
|||||||
@ -20,8 +20,8 @@ namespace CounterDrone.Core.Algorithms
|
|||||||
/// <summary>拦截概率上限(封顶值)</summary>
|
/// <summary>拦截概率上限(封顶值)</summary>
|
||||||
public float MaxInterceptProbability { get; set; }
|
public float MaxInterceptProbability { get; set; }
|
||||||
|
|
||||||
/// <summary>威胁类型系数表(TargetType → 系数)</summary>
|
/// <summary>威胁类型系数表(DroneType → 系数)</summary>
|
||||||
public Dictionary<TargetType, float> TypeCoefficient { get; set; } = new();
|
public Dictionary<DroneType, float> TypeCoefficient { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>弹药匹配表(PowerType → AerosolType)</summary>
|
/// <summary>弹药匹配表(PowerType → AerosolType)</summary>
|
||||||
public Dictionary<PowerType, AerosolType> AmmoMatch { get; set; } = new();
|
public Dictionary<PowerType, AerosolType> AmmoMatch { get; set; } = new();
|
||||||
|
|||||||
@ -31,19 +31,19 @@ namespace CounterDrone.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>创建/打开任务帧数据库</summary>
|
/// <summary>创建/打开任务帧数据库</summary>
|
||||||
public SQLiteConnection OpenFrameDb(string taskId)
|
public SQLiteConnection OpenFrameDb(string scenarioId)
|
||||||
{
|
{
|
||||||
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
|
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{scenarioId}.db");
|
||||||
var db = new SQLiteConnection(dbPath);
|
var db = new SQLiteConnection(dbPath);
|
||||||
db.CreateTable<SimFrameRecord>();
|
db.CreateTable<SimFrameRecord>();
|
||||||
db.CreateIndex("SimFrameRecord", new[] { "TaskId", "FrameIndex" });
|
db.CreateIndex("SimFrameRecord", new[] { "ScenarioId", "FrameIndex" });
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>删除任务帧数据库文件</summary>
|
/// <summary>删除任务帧数据库文件</summary>
|
||||||
public void DeleteFrameDb(string taskId)
|
public void DeleteFrameDb(string scenarioId)
|
||||||
{
|
{
|
||||||
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
|
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{scenarioId}.db");
|
||||||
if (File.Exists(dbPath))
|
if (File.Exists(dbPath))
|
||||||
File.Delete(dbPath);
|
File.Delete(dbPath);
|
||||||
}
|
}
|
||||||
@ -86,8 +86,8 @@ namespace CounterDrone.Core
|
|||||||
|
|
||||||
var defaults = DefaultData.Load(_paths);
|
var defaults = DefaultData.Load(_paths);
|
||||||
var scenario = new ScenarioService(
|
var scenario = new ScenarioService(
|
||||||
new SimTaskRepository(db), new CombatSceneRepository(db),
|
new ScenarioRepository(db), new CombatSceneRepository(db),
|
||||||
new ControlZoneRepository(db), new TargetConfigRepository(db),
|
new ControlZoneRepository(db), new DroneProfileRepository(db),
|
||||||
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
|
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
|
||||||
new RoutePlanRepository(db), new WaypointRepository(db));
|
new RoutePlanRepository(db), new WaypointRepository(db));
|
||||||
|
|
||||||
@ -100,14 +100,14 @@ namespace CounterDrone.Core
|
|||||||
{
|
{
|
||||||
db.CreateTable<ModelInfo>();
|
db.CreateTable<ModelInfo>();
|
||||||
db.CreateTable<AmmunitionSpec>();
|
db.CreateTable<AmmunitionSpec>();
|
||||||
db.CreateTable<SimTask>();
|
db.CreateTable<Scenario>();
|
||||||
db.CreateTable<CombatScene>();
|
db.CreateTable<CombatScene>();
|
||||||
db.CreateTable<ControlZone>();
|
db.CreateTable<ControlZone>();
|
||||||
db.CreateTable<TargetConfig>();
|
db.CreateTable<DroneProfile>();
|
||||||
db.CreateTable<EquipmentDeployment>();
|
db.CreateTable<EquipmentDeployment>();
|
||||||
db.CreateTable<CloudDispersal>();
|
db.CreateTable<CloudDispersal>();
|
||||||
db.CreateTable<RoutePlan>();
|
db.CreateTable<RoutePlan>();
|
||||||
db.CreateIndex("RoutePlan", new[] { "TaskId", "WaveId" }, true);
|
db.CreateIndex("RoutePlan", new[] { "ScenarioId", "WaveId" }, true);
|
||||||
db.CreateTable<Waypoint>();
|
db.CreateTable<Waypoint>();
|
||||||
db.CreateTable<SimulationReport>();
|
db.CreateTable<SimulationReport>();
|
||||||
db.CreateTable<FireUnitSpec>();
|
db.CreateTable<FireUnitSpec>();
|
||||||
@ -117,12 +117,12 @@ namespace CounterDrone.Core
|
|||||||
db.CreateTable<FormationTemplate>();
|
db.CreateTable<FormationTemplate>();
|
||||||
db.CreateTable<RouteTemplate>();
|
db.CreateTable<RouteTemplate>();
|
||||||
|
|
||||||
db.CreateIndex("SimTask", "TaskNumber");
|
db.CreateIndex("Scenario", "TaskNumber");
|
||||||
db.CreateIndex("ControlZone", "TaskId");
|
db.CreateIndex("ControlZone", "ScenarioId");
|
||||||
db.CreateIndex("TargetConfig", "TaskId");
|
db.CreateIndex("DroneProfile", "ScenarioId");
|
||||||
db.CreateIndex("EquipmentDeployment", "TaskId");
|
db.CreateIndex("EquipmentDeployment", "ScenarioId");
|
||||||
db.CreateIndex("Waypoint", "TaskId");
|
db.CreateIndex("Waypoint", "ScenarioId");
|
||||||
db.CreateIndex("SimulationReport", "TaskId");
|
db.CreateIndex("SimulationReport", "ScenarioId");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -163,18 +163,18 @@ namespace CounterDrone.Core
|
|||||||
[SQLite.PrimaryKey]
|
[SQLite.PrimaryKey]
|
||||||
public string Id { get; set; } = "";
|
public string Id { get; set; } = "";
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
public int TargetType { get; set; }
|
public int DroneType { get; set; }
|
||||||
public int PowerType { get; set; }
|
public int PowerType { get; set; }
|
||||||
public double Wingspan { get; set; }
|
public double Wingspan { get; set; }
|
||||||
public double TypicalSpeed { get; set; }
|
public double TypicalSpeed { get; set; }
|
||||||
public double TypicalAltitude { get; set; }
|
public double TypicalAltitude { get; set; }
|
||||||
|
|
||||||
public TargetConfig ToTargetConfig(string waveId = "default")
|
public DroneProfile ToDroneProfile(string waveId = "default")
|
||||||
{
|
{
|
||||||
return new TargetConfig
|
return new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = waveId,
|
WaveId = waveId,
|
||||||
TargetType = TargetType,
|
DroneType = DroneType,
|
||||||
PowerType = PowerType,
|
PowerType = PowerType,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
Wingspan = Wingspan,
|
Wingspan = Wingspan,
|
||||||
|
|||||||
@ -37,7 +37,7 @@ namespace CounterDrone.Core
|
|||||||
{
|
{
|
||||||
var t = s.CreateTask("[Demo] 无防御-无人机抵达目标", "");
|
var t = s.CreateTask("[Demo] 无防御-无人机抵达目标", "");
|
||||||
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
|
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
|
||||||
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "shahed").ToTargetConfig());
|
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));
|
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(), R("3km-h400", 600, d));
|
||||||
s.SaveCloudDispersal(t.Id, new CloudDispersal());
|
s.SaveCloudDispersal(t.Id, new CloudDispersal());
|
||||||
s.SaveDeployment(t.Id, new List<EquipmentDeployment>());
|
s.SaveDeployment(t.Id, new List<EquipmentDeployment>());
|
||||||
@ -48,7 +48,7 @@ namespace CounterDrone.Core
|
|||||||
{
|
{
|
||||||
var t = s.CreateTask("[Demo] 管控区域侵入", "");
|
var t = s.CreateTask("[Demo] 管控区域侵入", "");
|
||||||
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
|
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").ToTargetConfig());
|
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));
|
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(), R("3km-h300", 300, d));
|
||||||
s.SaveCloudDispersal(t.Id, new CloudDispersal());
|
s.SaveCloudDispersal(t.Id, new CloudDispersal());
|
||||||
s.SaveDeployment(t.Id, new List<EquipmentDeployment>());
|
s.SaveDeployment(t.Id, new List<EquipmentDeployment>());
|
||||||
@ -71,7 +71,7 @@ namespace CounterDrone.Core
|
|||||||
scene.WindSpeed = 5;
|
scene.WindSpeed = 5;
|
||||||
scene.WindDirection = (int)WindDirection.W;
|
scene.WindDirection = (int)WindDirection.W;
|
||||||
s.SaveScene(t.Id, scene);
|
s.SaveScene(t.Id, scene);
|
||||||
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "shahed").ToTargetConfig());
|
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(),
|
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(),
|
||||||
new List<Waypoint> {
|
new List<Waypoint> {
|
||||||
new() { PosX = 6600, PosY = 500, PosZ = 0, Speed = 200 },
|
new() { PosX = 6600, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
@ -89,7 +89,7 @@ namespace CounterDrone.Core
|
|||||||
{
|
{
|
||||||
var t = s.CreateTask("[Demo] 喷气式拦截-活性材料", "");
|
var t = s.CreateTask("[Demo] 喷气式拦截-活性材料", "");
|
||||||
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
|
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").ToTargetConfig());
|
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(),
|
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(),
|
||||||
new List<Waypoint> {
|
new List<Waypoint> {
|
||||||
new() { PosX = 18000, PosY = 500, PosZ = 0, Speed = 200 },
|
new() { PosX = 18000, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
@ -110,7 +110,7 @@ namespace CounterDrone.Core
|
|||||||
scene.WindSpeed = 5;
|
scene.WindSpeed = 5;
|
||||||
scene.WindDirection = (int)WindDirection.E;
|
scene.WindDirection = (int)WindDirection.E;
|
||||||
s.SaveScene(t.Id, scene);
|
s.SaveScene(t.Id, scene);
|
||||||
s.SaveTarget(t.Id, d.Drones.First(p => p.Id == "shahed").ToTargetConfig());
|
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(),
|
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(),
|
||||||
new List<Waypoint> {
|
new List<Waypoint> {
|
||||||
new() { PosX = 9500, PosY = 500, PosZ = 0, Speed = 200 },
|
new() { PosX = 9500, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
@ -131,7 +131,7 @@ namespace CounterDrone.Core
|
|||||||
scene.WindSpeed = 5;
|
scene.WindSpeed = 5;
|
||||||
scene.WindDirection = (int)WindDirection.E;
|
scene.WindDirection = (int)WindDirection.E;
|
||||||
s.SaveScene(t.Id, scene);
|
s.SaveScene(t.Id, scene);
|
||||||
var target = d.Drones.First(p => p.Id == "shahed").ToTargetConfig();
|
var target = d.Drones.First(p => p.Id == "shahed").ToDroneProfile();
|
||||||
target.Quantity = 3;
|
target.Quantity = 3;
|
||||||
s.SaveTarget(t.Id, target);
|
s.SaveTarget(t.Id, target);
|
||||||
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "line-3").ToRoutePlan(),
|
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "line-3").ToRoutePlan(),
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace CounterDrone.Core.Models
|
|||||||
public class CloudDispersal
|
public class CloudDispersal
|
||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public string TaskId { get; set; } = string.Empty;
|
public string ScenarioId { get; set; } = string.Empty;
|
||||||
|
|
||||||
public int AerosolType { get; set; } = (int)Models.AerosolType.InertGas;
|
public int AerosolType { get; set; } = (int)Models.AerosolType.InertGas;
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace CounterDrone.Core.Models
|
|||||||
public class CombatScene
|
public class CombatScene
|
||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public string TaskId { get; set; } = string.Empty;
|
public string ScenarioId { get; set; } = string.Empty;
|
||||||
|
|
||||||
public int SceneType { get; set; } = (int)Models.SceneType.Plain;
|
public int SceneType { get; set; } = (int)Models.SceneType.Plain;
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace CounterDrone.Core.Models
|
|||||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
[Indexed]
|
[Indexed]
|
||||||
public string TaskId { get; set; } = string.Empty;
|
public string ScenarioId { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|||||||
@ -4,18 +4,18 @@ using SQLite;
|
|||||||
namespace CounterDrone.Core.Models
|
namespace CounterDrone.Core.Models
|
||||||
{
|
{
|
||||||
/// <summary>步骤2:目标配置</summary>
|
/// <summary>步骤2:目标配置</summary>
|
||||||
[Table("TargetConfig")]
|
[Table("DroneProfile")]
|
||||||
public class TargetConfig
|
public class DroneProfile
|
||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
[Indexed]
|
[Indexed]
|
||||||
public string TaskId { get; set; } = string.Empty;
|
public string ScenarioId { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string WaveId { get; set; } = string.Empty;
|
public string WaveId { get; set; } = string.Empty;
|
||||||
|
|
||||||
public int TargetType { get; set; } = (int)Models.TargetType.Rotor;
|
public int DroneType { get; set; } = (int)Models.DroneType.Rotor;
|
||||||
|
|
||||||
public int Quantity { get; set; } = 1;
|
public int Quantity { get; set; } = 1;
|
||||||
|
|
||||||
@ -33,13 +33,11 @@ namespace CounterDrone.Core.Models
|
|||||||
}
|
}
|
||||||
|
|
||||||
// === 目标 ===
|
// === 目标 ===
|
||||||
public enum TargetType
|
public enum DroneType
|
||||||
{
|
{
|
||||||
Rotor = 0,
|
Rotor = 0,
|
||||||
FixedWing = 1,
|
FixedWing = 1,
|
||||||
Electric = 2,
|
HighSpeed = 2
|
||||||
Piston = 3,
|
|
||||||
HighSpeed = 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PowerType
|
public enum PowerType
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace CounterDrone.Core.Models
|
|||||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
[Indexed]
|
[Indexed]
|
||||||
public string TaskId { get; set; } = string.Empty;
|
public string ScenarioId { get; set; } = string.Empty;
|
||||||
|
|
||||||
public int EquipmentRole { get; set; } = (int)Models.EquipmentRole.LaunchPlatform;
|
public int EquipmentRole { get; set; } = (int)Models.EquipmentRole.LaunchPlatform;
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace CounterDrone.Core.Models
|
|||||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
[Indexed]
|
[Indexed]
|
||||||
public string TaskId { get; set; } = string.Empty;
|
public string ScenarioId { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Indexed]
|
[Indexed]
|
||||||
public string WaveId { get; set; } = string.Empty;
|
public string WaveId { get; set; } = string.Empty;
|
||||||
|
|||||||
@ -4,8 +4,8 @@ using SQLite;
|
|||||||
namespace CounterDrone.Core.Models
|
namespace CounterDrone.Core.Models
|
||||||
{
|
{
|
||||||
/// <summary>仿真任务主表</summary>
|
/// <summary>仿真任务主表</summary>
|
||||||
[Table("SimTask")]
|
[Table("Scenario")]
|
||||||
public class SimTask
|
public class Scenario
|
||||||
{
|
{
|
||||||
[PrimaryKey]
|
[PrimaryKey]
|
||||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
@ -3,12 +3,12 @@ using System.Collections.Generic;
|
|||||||
namespace CounterDrone.Core.Models
|
namespace CounterDrone.Core.Models
|
||||||
{
|
{
|
||||||
/// <summary>想定完整配置(聚合 5 步配置)</summary>
|
/// <summary>想定完整配置(聚合 5 步配置)</summary>
|
||||||
public class TaskFullConfig
|
public class ScenarioConfig
|
||||||
{
|
{
|
||||||
public SimTask Task { get; set; } = new();
|
public Scenario Task { get; set; } = new();
|
||||||
public CombatScene Scene { get; set; } = new();
|
public CombatScene Scene { get; set; } = new();
|
||||||
public List<ControlZone> ControlZones { get; set; } = new();
|
public List<ControlZone> ControlZones { get; set; } = new();
|
||||||
public List<TargetConfig> Targets { get; set; } = new();
|
public List<DroneProfile> Targets { get; set; } = new();
|
||||||
public List<EquipmentDeployment> Equipment { get; set; } = new();
|
public List<EquipmentDeployment> Equipment { get; set; } = new();
|
||||||
public CloudDispersal Cloud { get; set; } = new();
|
public CloudDispersal Cloud { get; set; } = new();
|
||||||
/// <summary>多批次航路(多批次支持)</summary>
|
/// <summary>多批次航路(多批次支持)</summary>
|
||||||
@ -10,7 +10,7 @@ namespace CounterDrone.Core.Models
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
[Indexed]
|
[Indexed]
|
||||||
public string TaskId { get; set; } = string.Empty;
|
public string ScenarioId { get; set; } = string.Empty;
|
||||||
|
|
||||||
public int FrameIndex { get; set; }
|
public int FrameIndex { get; set; }
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace CounterDrone.Core.Models
|
|||||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
[Indexed]
|
[Indexed]
|
||||||
public string TaskId { get; set; } = string.Empty;
|
public string ScenarioId { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string TaskName { get; set; } = string.Empty;
|
public string TaskName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace CounterDrone.Core.Models
|
|||||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
[Indexed]
|
[Indexed]
|
||||||
public string TaskId { get; set; } = string.Empty;
|
public string ScenarioId { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>关联的批次 ID(多批次支持)</summary>
|
/// <summary>关联的批次 ID(多批次支持)</summary>
|
||||||
public string WaveId { get; set; } = string.Empty;
|
public string WaveId { get; set; } = string.Empty;
|
||||||
|
|||||||
@ -9,17 +9,17 @@ namespace CounterDrone.Core.Repository
|
|||||||
{
|
{
|
||||||
public ControlZoneRepository(SQLiteConnection db) : base(db) { }
|
public ControlZoneRepository(SQLiteConnection db) : base(db) { }
|
||||||
|
|
||||||
public List<ControlZone> GetByTaskId(string taskId)
|
public List<ControlZone> GetByScenarioId(string scenarioId)
|
||||||
{
|
{
|
||||||
return Db.Table<ControlZone>()
|
return Db.Table<ControlZone>()
|
||||||
.Where(z => z.TaskId == taskId)
|
.Where(z => z.ScenarioId == scenarioId)
|
||||||
.OrderBy(z => z.OrderIndex)
|
.OrderBy(z => z.OrderIndex)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteByTaskId(string taskId)
|
public void DeleteByScenarioId(string scenarioId)
|
||||||
{
|
{
|
||||||
var zones = GetByTaskId(taskId);
|
var zones = GetByScenarioId(scenarioId);
|
||||||
foreach (var z in zones)
|
foreach (var z in zones)
|
||||||
Db.Delete(z);
|
Db.Delete(z);
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/CounterDrone.Core/Repository/DroneProfileRepository.cs
Normal file
24
src/CounterDrone.Core/Repository/DroneProfileRepository.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using CounterDrone.Core.Models;
|
||||||
|
using SQLite;
|
||||||
|
|
||||||
|
namespace CounterDrone.Core.Repository
|
||||||
|
{
|
||||||
|
public class DroneProfileRepository : BaseRepository<DroneProfile>
|
||||||
|
{
|
||||||
|
public DroneProfileRepository(SQLiteConnection db) : base(db) { }
|
||||||
|
|
||||||
|
public List<DroneProfile> GetByScenarioId(string scenarioId)
|
||||||
|
{
|
||||||
|
return Db.Table<DroneProfile>().Where(t => t.ScenarioId == scenarioId).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteByScenarioId(string scenarioId)
|
||||||
|
{
|
||||||
|
var targets = GetByScenarioId(scenarioId);
|
||||||
|
foreach (var t in targets)
|
||||||
|
Db.Delete(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,22 +9,22 @@ namespace CounterDrone.Core.Repository
|
|||||||
{
|
{
|
||||||
public EquipmentDeploymentRepository(SQLiteConnection db) : base(db) { }
|
public EquipmentDeploymentRepository(SQLiteConnection db) : base(db) { }
|
||||||
|
|
||||||
public List<EquipmentDeployment> GetByTaskId(string taskId)
|
public List<EquipmentDeployment> GetByScenarioId(string scenarioId)
|
||||||
{
|
{
|
||||||
return Db.Table<EquipmentDeployment>().Where(e => e.TaskId == taskId).ToList();
|
return Db.Table<EquipmentDeployment>().Where(e => e.ScenarioId == scenarioId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>按任务和角色查询装备(EquipmentRole: 0=Detection, 1=LaunchPlatform)</summary>
|
/// <summary>按任务和角色查询装备(EquipmentRole: 0=Detection, 1=LaunchPlatform)</summary>
|
||||||
public List<EquipmentDeployment> GetByTaskIdAndRole(string taskId, int equipmentRole)
|
public List<EquipmentDeployment> GetByScenarioIdAndRole(string scenarioId, int equipmentRole)
|
||||||
{
|
{
|
||||||
return Db.Table<EquipmentDeployment>()
|
return Db.Table<EquipmentDeployment>()
|
||||||
.Where(e => e.TaskId == taskId && e.EquipmentRole == equipmentRole)
|
.Where(e => e.ScenarioId == scenarioId && e.EquipmentRole == equipmentRole)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteByTaskId(string taskId)
|
public void DeleteByScenarioId(string scenarioId)
|
||||||
{
|
{
|
||||||
var equips = GetByTaskId(taskId);
|
var equips = GetByScenarioId(scenarioId);
|
||||||
foreach (var e in equips)
|
foreach (var e in equips)
|
||||||
Db.Delete(e);
|
Db.Delete(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,15 +9,15 @@ namespace CounterDrone.Core.Repository
|
|||||||
{
|
{
|
||||||
public RoutePlanRepository(SQLiteConnection db) : base(db) { }
|
public RoutePlanRepository(SQLiteConnection db) : base(db) { }
|
||||||
|
|
||||||
public List<RoutePlan> GetByTaskId(string taskId)
|
public List<RoutePlan> GetByScenarioId(string scenarioId)
|
||||||
{
|
{
|
||||||
return Db.Table<RoutePlan>().Where(r => r.TaskId == taskId).ToList();
|
return Db.Table<RoutePlan>().Where(r => r.ScenarioId == scenarioId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoutePlan GetByTaskAndWave(string taskId, string waveId)
|
public RoutePlan GetByTaskAndWave(string scenarioId, string waveId)
|
||||||
{
|
{
|
||||||
return Db.Table<RoutePlan>()
|
return Db.Table<RoutePlan>()
|
||||||
.FirstOrDefault(r => r.TaskId == taskId && r.WaveId == waveId);
|
.FirstOrDefault(r => r.ScenarioId == scenarioId && r.WaveId == waveId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,18 +5,18 @@ using SQLite;
|
|||||||
|
|
||||||
namespace CounterDrone.Core.Repository
|
namespace CounterDrone.Core.Repository
|
||||||
{
|
{
|
||||||
public class SimTaskRepository : BaseRepository<SimTask>
|
public class ScenarioRepository : BaseRepository<Scenario>
|
||||||
{
|
{
|
||||||
public SimTaskRepository(SQLiteConnection db) : base(db) { }
|
public ScenarioRepository(SQLiteConnection db) : base(db) { }
|
||||||
|
|
||||||
public SimTask GetByTaskNumber(string taskNumber)
|
public Scenario GetByTaskNumber(string taskNumber)
|
||||||
{
|
{
|
||||||
return Db.Table<SimTask>().FirstOrDefault(t => t.TaskNumber == taskNumber);
|
return Db.Table<Scenario>().FirstOrDefault(t => t.TaskNumber == taskNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SimTask> Search(string keyword, string? dateFrom, string? dateTo, int offset, int limit, out int totalCount)
|
public List<Scenario> Search(string keyword, string? dateFrom, string? dateTo, int offset, int limit, out int totalCount)
|
||||||
{
|
{
|
||||||
var query = Db.Table<SimTask>().AsQueryable();
|
var query = Db.Table<Scenario>().AsQueryable();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(keyword))
|
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.TaskNumber.Contains(keyword));
|
||||||
@ -1,24 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using CounterDrone.Core.Models;
|
|
||||||
using SQLite;
|
|
||||||
|
|
||||||
namespace CounterDrone.Core.Repository
|
|
||||||
{
|
|
||||||
public class TargetConfigRepository : BaseRepository<TargetConfig>
|
|
||||||
{
|
|
||||||
public TargetConfigRepository(SQLiteConnection db) : base(db) { }
|
|
||||||
|
|
||||||
public List<TargetConfig> GetByTaskId(string taskId)
|
|
||||||
{
|
|
||||||
return Db.Table<TargetConfig>().Where(t => t.TaskId == taskId).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteByTaskId(string taskId)
|
|
||||||
{
|
|
||||||
var targets = GetByTaskId(taskId);
|
|
||||||
foreach (var t in targets)
|
|
||||||
Db.Delete(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -9,25 +9,25 @@ namespace CounterDrone.Core.Repository
|
|||||||
{
|
{
|
||||||
public WaypointRepository(SQLiteConnection db) : base(db) { }
|
public WaypointRepository(SQLiteConnection db) : base(db) { }
|
||||||
|
|
||||||
public List<Waypoint> GetByTaskId(string taskId)
|
public List<Waypoint> GetByScenarioId(string scenarioId)
|
||||||
{
|
{
|
||||||
return Db.Table<Waypoint>()
|
return Db.Table<Waypoint>()
|
||||||
.Where(w => w.TaskId == taskId)
|
.Where(w => w.ScenarioId == scenarioId)
|
||||||
.OrderBy(w => w.OrderIndex)
|
.OrderBy(w => w.OrderIndex)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Waypoint> GetByTaskAndWave(string taskId, string waveId)
|
public List<Waypoint> GetByTaskAndWave(string scenarioId, string waveId)
|
||||||
{
|
{
|
||||||
return Db.Table<Waypoint>()
|
return Db.Table<Waypoint>()
|
||||||
.Where(w => w.TaskId == taskId && w.WaveId == waveId)
|
.Where(w => w.ScenarioId == scenarioId && w.WaveId == waveId)
|
||||||
.OrderBy(w => w.OrderIndex)
|
.OrderBy(w => w.OrderIndex)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteByTaskId(string taskId)
|
public void DeleteByScenarioId(string scenarioId)
|
||||||
{
|
{
|
||||||
var wps = GetByTaskId(taskId);
|
var wps = GetByScenarioId(scenarioId);
|
||||||
foreach (var w in wps)
|
foreach (var w in wps)
|
||||||
Db.Delete(w);
|
Db.Delete(w);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace CounterDrone.Core.Services
|
|||||||
/// <summary>报告服务</summary>
|
/// <summary>报告服务</summary>
|
||||||
public interface IReportService
|
public interface IReportService
|
||||||
{
|
{
|
||||||
SimulationReport Generate(string taskId, TaskFullConfig config,
|
SimulationReport Generate(string scenarioId, ScenarioConfig config,
|
||||||
List<SimEvent> events, string finalDroneStatus, float duration);
|
List<SimEvent> events, string finalDroneStatus, float duration);
|
||||||
SimulationReport GetReport(string id);
|
SimulationReport GetReport(string id);
|
||||||
void DeleteReport(string id);
|
void DeleteReport(string id);
|
||||||
|
|||||||
@ -6,25 +6,25 @@ namespace CounterDrone.Core.Services
|
|||||||
/// <summary>想定管理服务</summary>
|
/// <summary>想定管理服务</summary>
|
||||||
public interface IScenarioService
|
public interface IScenarioService
|
||||||
{
|
{
|
||||||
SimTask CreateTask(string name, string taskNumber);
|
Scenario CreateTask(string name, string taskNumber);
|
||||||
void DeleteTask(string id);
|
void DeleteTask(string id);
|
||||||
PagedResult<SimTask> SearchTasks(string keyword, string? dateFrom, string? dateTo, int page, int pageSize);
|
PagedResult<Scenario> SearchTasks(string keyword, string? dateFrom, string? dateTo, int page, int pageSize);
|
||||||
TaskFullConfig? GetTaskDetail(string id);
|
ScenarioConfig? GetTaskDetail(string id);
|
||||||
|
|
||||||
void SaveScene(string taskId, CombatScene scene);
|
void SaveScene(string scenarioId, CombatScene scene);
|
||||||
void SaveControlZones(string taskId, List<ControlZone> zones);
|
void SaveControlZones(string scenarioId, List<ControlZone> zones);
|
||||||
void SaveTarget(string taskId, TargetConfig target);
|
void SaveTarget(string scenarioId, DroneProfile target);
|
||||||
void SaveDeployment(string taskId, List<EquipmentDeployment> equips);
|
void SaveDeployment(string scenarioId, List<EquipmentDeployment> equips);
|
||||||
|
|
||||||
/// <summary>添加单个探测设备(独立于 SaveDeployment,不覆盖火力单元)</summary>
|
/// <summary>添加单个探测设备(独立于 SaveDeployment,不覆盖火力单元)</summary>
|
||||||
void AddDetection(string taskId, EquipmentDeployment detection);
|
void AddDetection(string scenarioId, EquipmentDeployment detection);
|
||||||
/// <summary>删除探测设备</summary>
|
/// <summary>删除探测设备</summary>
|
||||||
void DeleteDetection(string detectionId);
|
void DeleteDetection(string detectionId);
|
||||||
/// <summary>获取任务的所有探测设备(EquipmentRole.Detection)</summary>
|
/// <summary>获取任务的所有探测设备(EquipmentRole.Detection)</summary>
|
||||||
List<EquipmentDeployment> GetDetections(string taskId);
|
List<EquipmentDeployment> GetDetections(string scenarioId);
|
||||||
|
|
||||||
void SaveCloudDispersal(string taskId, CloudDispersal cloud);
|
void SaveCloudDispersal(string scenarioId, CloudDispersal cloud);
|
||||||
void SaveRoute(string taskId, string waveId, RoutePlan route, List<Waypoint> waypoints);
|
void SaveRoute(string scenarioId, string waveId, RoutePlan route, List<Waypoint> waypoints);
|
||||||
void UpdateStep(string taskId, int step);
|
void UpdateStep(string scenarioId, int step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ namespace CounterDrone.Core.Services
|
|||||||
{
|
{
|
||||||
public class ReportGenerator
|
public class ReportGenerator
|
||||||
{
|
{
|
||||||
public string Generate(TaskFullConfig config, List<SimEvent> events,
|
public string Generate(ScenarioConfig config, List<SimEvent> events,
|
||||||
Simulation.DroneStatus finalDroneStatus, float simulationDuration)
|
Simulation.DroneStatus finalDroneStatus, float simulationDuration)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
@ -70,7 +70,7 @@ namespace CounterDrone.Core.Services
|
|||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine($"| 参数 | 值 |");
|
sb.AppendLine($"| 参数 | 值 |");
|
||||||
sb.AppendLine($"|------|-----|");
|
sb.AppendLine($"|------|-----|");
|
||||||
sb.AppendLine($"| 类型 | {TranslateTarget((TargetType)(target?.TargetType ?? 0))} |");
|
sb.AppendLine($"| 类型 | {TranslateTarget((DroneType)(target?.DroneType ?? 0))} |");
|
||||||
sb.AppendLine($"| 动力 | {TranslatePower((PowerType)(target?.PowerType ?? 0))} |");
|
sb.AppendLine($"| 动力 | {TranslatePower((PowerType)(target?.PowerType ?? 0))} |");
|
||||||
sb.AppendLine($"| 数量 | {totalDrones} 架 |");
|
sb.AppendLine($"| 数量 | {totalDrones} 架 |");
|
||||||
sb.AppendLine($"| 翼展 | {target?.Wingspan ?? 0:F1} m |");
|
sb.AppendLine($"| 翼展 | {target?.Wingspan ?? 0:F1} m |");
|
||||||
@ -282,11 +282,10 @@ namespace CounterDrone.Core.Services
|
|||||||
TriggerMode.Manual => "手动触发", _ => t.ToString(),
|
TriggerMode.Manual => "手动触发", _ => t.ToString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string TranslateTarget(TargetType t) => t switch
|
private static string TranslateTarget(DroneType t) => t switch
|
||||||
{
|
{
|
||||||
TargetType.Rotor => "旋翼", TargetType.FixedWing => "固定翼",
|
DroneType.Rotor => "旋翼", DroneType.FixedWing => "固定翼",
|
||||||
TargetType.Electric => "电推", TargetType.Piston => "活塞",
|
DroneType.HighSpeed => "高速目标", _ => t.ToString(),
|
||||||
TargetType.HighSpeed => "高速目标", _ => t.ToString(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string TranslatePower(PowerType p) => p switch
|
private static string TranslatePower(PowerType p) => p switch
|
||||||
|
|||||||
@ -20,7 +20,7 @@ namespace CounterDrone.Core.Services
|
|||||||
_paths = paths;
|
_paths = paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimulationReport Generate(string taskId, TaskFullConfig config,
|
public SimulationReport Generate(string scenarioId, ScenarioConfig config,
|
||||||
List<SimEvent> events, string finalDroneStatus, float duration)
|
List<SimEvent> events, string finalDroneStatus, float duration)
|
||||||
{
|
{
|
||||||
var destroyed = events.Count(e => e.Type == SimEventType.DroneDestroyed);
|
var destroyed = events.Count(e => e.Type == SimEventType.DroneDestroyed);
|
||||||
@ -31,7 +31,7 @@ namespace CounterDrone.Core.Services
|
|||||||
|
|
||||||
var report = new SimulationReport
|
var report = new SimulationReport
|
||||||
{
|
{
|
||||||
TaskId = taskId,
|
ScenarioId = scenarioId,
|
||||||
TaskName = config.Task.Name,
|
TaskName = config.Task.Name,
|
||||||
TaskNumber = config.Task.TaskNumber,
|
TaskNumber = config.Task.TaskNumber,
|
||||||
CompletedAt = DateTime.UtcNow.ToString("o"),
|
CompletedAt = DateTime.UtcNow.ToString("o"),
|
||||||
|
|||||||
@ -9,20 +9,20 @@ namespace CounterDrone.Core.Services
|
|||||||
{
|
{
|
||||||
public class ScenarioService : IScenarioService
|
public class ScenarioService : IScenarioService
|
||||||
{
|
{
|
||||||
private readonly SimTaskRepository _taskRepo;
|
private readonly ScenarioRepository _taskRepo;
|
||||||
private readonly CombatSceneRepository _sceneRepo;
|
private readonly CombatSceneRepository _sceneRepo;
|
||||||
private readonly ControlZoneRepository _zoneRepo;
|
private readonly ControlZoneRepository _zoneRepo;
|
||||||
private readonly TargetConfigRepository _targetRepo;
|
private readonly DroneProfileRepository _targetRepo;
|
||||||
private readonly EquipmentDeploymentRepository _equipRepo;
|
private readonly EquipmentDeploymentRepository _equipRepo;
|
||||||
private readonly CloudDispersalRepository _cloudRepo;
|
private readonly CloudDispersalRepository _cloudRepo;
|
||||||
private readonly RoutePlanRepository _routeRepo;
|
private readonly RoutePlanRepository _routeRepo;
|
||||||
private readonly WaypointRepository _waypointRepo;
|
private readonly WaypointRepository _waypointRepo;
|
||||||
|
|
||||||
public ScenarioService(
|
public ScenarioService(
|
||||||
SimTaskRepository taskRepo,
|
ScenarioRepository taskRepo,
|
||||||
CombatSceneRepository sceneRepo,
|
CombatSceneRepository sceneRepo,
|
||||||
ControlZoneRepository zoneRepo,
|
ControlZoneRepository zoneRepo,
|
||||||
TargetConfigRepository targetRepo,
|
DroneProfileRepository targetRepo,
|
||||||
EquipmentDeploymentRepository equipRepo,
|
EquipmentDeploymentRepository equipRepo,
|
||||||
CloudDispersalRepository cloudRepo,
|
CloudDispersalRepository cloudRepo,
|
||||||
RoutePlanRepository routeRepo,
|
RoutePlanRepository routeRepo,
|
||||||
@ -40,7 +40,7 @@ namespace CounterDrone.Core.Services
|
|||||||
|
|
||||||
// ========== Task CRUD ==========
|
// ========== Task CRUD ==========
|
||||||
|
|
||||||
public SimTask CreateTask(string name, string taskNumber)
|
public Scenario CreateTask(string name, string taskNumber)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
throw new ArgumentException("任务名称不能为空");
|
throw new ArgumentException("任务名称不能为空");
|
||||||
@ -52,7 +52,7 @@ namespace CounterDrone.Core.Services
|
|||||||
if (existing != null)
|
if (existing != null)
|
||||||
throw new ArgumentException($"任务编号 {taskNumber} 已存在");
|
throw new ArgumentException($"任务编号 {taskNumber} 已存在");
|
||||||
|
|
||||||
var task = new SimTask
|
var task = new Scenario
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
TaskNumber = taskNumber,
|
TaskNumber = taskNumber,
|
||||||
@ -69,7 +69,7 @@ namespace CounterDrone.Core.Services
|
|||||||
_taskRepo.Delete(id);
|
_taskRepo.Delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PagedResult<SimTask> SearchTasks(string keyword, string? dateFrom, string? dateTo, int page, int pageSize)
|
public PagedResult<Scenario> SearchTasks(string keyword, string? dateFrom, string? dateTo, int page, int pageSize)
|
||||||
{
|
{
|
||||||
if (page < 1) page = 1;
|
if (page < 1) page = 1;
|
||||||
if (pageSize < 1) pageSize = 10;
|
if (pageSize < 1) pageSize = 10;
|
||||||
@ -77,7 +77,7 @@ namespace CounterDrone.Core.Services
|
|||||||
|
|
||||||
var items = _taskRepo.Search(keyword, dateFrom, dateTo, offset, pageSize, out var total);
|
var items = _taskRepo.Search(keyword, dateFrom, dateTo, offset, pageSize, out var total);
|
||||||
|
|
||||||
return new PagedResult<SimTask>
|
return new PagedResult<Scenario>
|
||||||
{
|
{
|
||||||
Items = items,
|
Items = items,
|
||||||
TotalCount = total,
|
TotalCount = total,
|
||||||
@ -86,21 +86,21 @@ namespace CounterDrone.Core.Services
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public TaskFullConfig? GetTaskDetail(string id)
|
public ScenarioConfig? GetTaskDetail(string id)
|
||||||
{
|
{
|
||||||
var task = _taskRepo.GetById(id);
|
var task = _taskRepo.GetById(id);
|
||||||
if (task == null) return null;
|
if (task == null) return null;
|
||||||
|
|
||||||
return new TaskFullConfig
|
return new ScenarioConfig
|
||||||
{
|
{
|
||||||
Task = task,
|
Task = task,
|
||||||
Scene = _sceneRepo.GetById(id) ?? new CombatScene { TaskId = id },
|
Scene = _sceneRepo.GetById(id) ?? new CombatScene { ScenarioId = id },
|
||||||
ControlZones = _zoneRepo.GetByTaskId(id),
|
ControlZones = _zoneRepo.GetByScenarioId(id),
|
||||||
Targets = _targetRepo.GetByTaskId(id),
|
Targets = _targetRepo.GetByScenarioId(id),
|
||||||
Equipment = _equipRepo.GetByTaskId(id),
|
Equipment = _equipRepo.GetByScenarioId(id),
|
||||||
Cloud = _cloudRepo.GetById(id) ?? new CloudDispersal { TaskId = id },
|
Cloud = _cloudRepo.GetById(id) ?? new CloudDispersal { ScenarioId = id },
|
||||||
Routes = _routeRepo.GetByTaskId(id),
|
Routes = _routeRepo.GetByScenarioId(id),
|
||||||
WaypointGroups = _waypointRepo.GetByTaskId(id)
|
WaypointGroups = _waypointRepo.GetByScenarioId(id)
|
||||||
.GroupBy(w => w.WaveId)
|
.GroupBy(w => w.WaveId)
|
||||||
.ToDictionary(g => g.Key, g => g.OrderBy(w => w.OrderIndex).ToList()),
|
.ToDictionary(g => g.Key, g => g.OrderBy(w => w.OrderIndex).ToList()),
|
||||||
};
|
};
|
||||||
@ -108,32 +108,32 @@ namespace CounterDrone.Core.Services
|
|||||||
|
|
||||||
// ========== Step Save ==========
|
// ========== Step Save ==========
|
||||||
|
|
||||||
public void SaveScene(string taskId, CombatScene scene)
|
public void SaveScene(string scenarioId, CombatScene scene)
|
||||||
{
|
{
|
||||||
scene.TaskId = taskId;
|
scene.ScenarioId = scenarioId;
|
||||||
var existing = _sceneRepo.GetById(taskId);
|
var existing = _sceneRepo.GetById(scenarioId);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
_sceneRepo.Update(scene);
|
_sceneRepo.Update(scene);
|
||||||
else
|
else
|
||||||
_sceneRepo.Insert(scene);
|
_sceneRepo.Insert(scene);
|
||||||
TouchTask(taskId);
|
TouchTask(scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveControlZones(string taskId, List<ControlZone> zones)
|
public void SaveControlZones(string scenarioId, List<ControlZone> zones)
|
||||||
{
|
{
|
||||||
_zoneRepo.DeleteByTaskId(taskId);
|
_zoneRepo.DeleteByScenarioId(scenarioId);
|
||||||
for (int i = 0; i < zones.Count; i++)
|
for (int i = 0; i < zones.Count; i++)
|
||||||
{
|
{
|
||||||
zones[i].TaskId = taskId;
|
zones[i].ScenarioId = scenarioId;
|
||||||
zones[i].OrderIndex = i;
|
zones[i].OrderIndex = i;
|
||||||
}
|
}
|
||||||
_zoneRepo.InsertAll(zones);
|
_zoneRepo.InsertAll(zones);
|
||||||
TouchTask(taskId);
|
TouchTask(scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveTarget(string taskId, TargetConfig target)
|
public void SaveTarget(string scenarioId, DroneProfile target)
|
||||||
{
|
{
|
||||||
target.TaskId = taskId;
|
target.ScenarioId = scenarioId;
|
||||||
if (string.IsNullOrEmpty(target.Id))
|
if (string.IsNullOrEmpty(target.Id))
|
||||||
target.Id = Guid.NewGuid().ToString();
|
target.Id = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
@ -142,30 +142,30 @@ namespace CounterDrone.Core.Services
|
|||||||
_targetRepo.Update(target);
|
_targetRepo.Update(target);
|
||||||
else
|
else
|
||||||
_targetRepo.Insert(target);
|
_targetRepo.Insert(target);
|
||||||
TouchTask(taskId);
|
TouchTask(scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveDeployment(string taskId, List<EquipmentDeployment> equips)
|
public void SaveDeployment(string scenarioId, List<EquipmentDeployment> equips)
|
||||||
{
|
{
|
||||||
_equipRepo.DeleteByTaskId(taskId);
|
_equipRepo.DeleteByScenarioId(scenarioId);
|
||||||
foreach (var e in equips)
|
foreach (var e in equips)
|
||||||
{
|
{
|
||||||
e.TaskId = taskId;
|
e.ScenarioId = scenarioId;
|
||||||
if (string.IsNullOrEmpty(e.Id))
|
if (string.IsNullOrEmpty(e.Id))
|
||||||
e.Id = Guid.NewGuid().ToString();
|
e.Id = Guid.NewGuid().ToString();
|
||||||
}
|
}
|
||||||
_equipRepo.InsertAll(equips);
|
_equipRepo.InsertAll(equips);
|
||||||
TouchTask(taskId);
|
TouchTask(scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddDetection(string taskId, EquipmentDeployment detection)
|
public void AddDetection(string scenarioId, EquipmentDeployment detection)
|
||||||
{
|
{
|
||||||
detection.TaskId = taskId;
|
detection.ScenarioId = scenarioId;
|
||||||
detection.EquipmentRole = (int)EquipmentRole.Detection;
|
detection.EquipmentRole = (int)EquipmentRole.Detection;
|
||||||
if (string.IsNullOrEmpty(detection.Id))
|
if (string.IsNullOrEmpty(detection.Id))
|
||||||
detection.Id = Guid.NewGuid().ToString();
|
detection.Id = Guid.NewGuid().ToString();
|
||||||
_equipRepo.Insert(detection);
|
_equipRepo.Insert(detection);
|
||||||
TouchTask(taskId);
|
TouchTask(scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteDetection(string detectionId)
|
public void DeleteDetection(string detectionId)
|
||||||
@ -173,27 +173,27 @@ namespace CounterDrone.Core.Services
|
|||||||
_equipRepo.Delete(detectionId);
|
_equipRepo.Delete(detectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<EquipmentDeployment> GetDetections(string taskId)
|
public List<EquipmentDeployment> GetDetections(string scenarioId)
|
||||||
{
|
{
|
||||||
return _equipRepo.GetByTaskIdAndRole(taskId, (int)EquipmentRole.Detection);
|
return _equipRepo.GetByScenarioIdAndRole(scenarioId, (int)EquipmentRole.Detection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveCloudDispersal(string taskId, CloudDispersal cloud)
|
public void SaveCloudDispersal(string scenarioId, CloudDispersal cloud)
|
||||||
{
|
{
|
||||||
cloud.TaskId = taskId;
|
cloud.ScenarioId = scenarioId;
|
||||||
var existing = _cloudRepo.GetById(taskId);
|
var existing = _cloudRepo.GetById(scenarioId);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
_cloudRepo.Update(cloud);
|
_cloudRepo.Update(cloud);
|
||||||
else
|
else
|
||||||
_cloudRepo.Insert(cloud);
|
_cloudRepo.Insert(cloud);
|
||||||
TouchTask(taskId);
|
TouchTask(scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveRoute(string taskId, string waveId, RoutePlan route, List<Waypoint> waypoints)
|
public void SaveRoute(string scenarioId, string waveId, RoutePlan route, List<Waypoint> waypoints)
|
||||||
{
|
{
|
||||||
route.TaskId = taskId;
|
route.ScenarioId = scenarioId;
|
||||||
route.WaveId = waveId;
|
route.WaveId = waveId;
|
||||||
var existing = _routeRepo.GetByTaskAndWave(taskId, waveId);
|
var existing = _routeRepo.GetByTaskAndWave(scenarioId, waveId);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
route.Id = existing.Id;
|
route.Id = existing.Id;
|
||||||
@ -203,23 +203,23 @@ namespace CounterDrone.Core.Services
|
|||||||
_routeRepo.Insert(route);
|
_routeRepo.Insert(route);
|
||||||
|
|
||||||
// 删除该批次的旧航路点,插入新的
|
// 删除该批次的旧航路点,插入新的
|
||||||
var oldWps = _waypointRepo.GetByTaskAndWave(taskId, waveId);
|
var oldWps = _waypointRepo.GetByTaskAndWave(scenarioId, waveId);
|
||||||
foreach (var w in oldWps) _waypointRepo.Delete(w.Id);
|
foreach (var w in oldWps) _waypointRepo.Delete(w.Id);
|
||||||
for (int i = 0; i < waypoints.Count; i++)
|
for (int i = 0; i < waypoints.Count; i++)
|
||||||
{
|
{
|
||||||
waypoints[i].TaskId = taskId;
|
waypoints[i].ScenarioId = scenarioId;
|
||||||
waypoints[i].WaveId = waveId;
|
waypoints[i].WaveId = waveId;
|
||||||
waypoints[i].OrderIndex = i;
|
waypoints[i].OrderIndex = i;
|
||||||
if (string.IsNullOrEmpty(waypoints[i].Id))
|
if (string.IsNullOrEmpty(waypoints[i].Id))
|
||||||
waypoints[i].Id = Guid.NewGuid().ToString();
|
waypoints[i].Id = Guid.NewGuid().ToString();
|
||||||
}
|
}
|
||||||
_waypointRepo.InsertAll(waypoints);
|
_waypointRepo.InsertAll(waypoints);
|
||||||
TouchTask(taskId);
|
TouchTask(scenarioId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStep(string taskId, int step)
|
public void UpdateStep(string scenarioId, int step)
|
||||||
{
|
{
|
||||||
var task = _taskRepo.GetById(taskId);
|
var task = _taskRepo.GetById(scenarioId);
|
||||||
if (task == null) return;
|
if (task == null) return;
|
||||||
|
|
||||||
if (step < 1) step = 1;
|
if (step < 1) step = 1;
|
||||||
@ -231,9 +231,9 @@ namespace CounterDrone.Core.Services
|
|||||||
|
|
||||||
// ========== Helpers ==========
|
// ========== Helpers ==========
|
||||||
|
|
||||||
private void TouchTask(string taskId)
|
private void TouchTask(string scenarioId)
|
||||||
{
|
{
|
||||||
var task = _taskRepo.GetById(taskId);
|
var task = _taskRepo.GetById(scenarioId);
|
||||||
if (task != null)
|
if (task != null)
|
||||||
{
|
{
|
||||||
task.UpdatedAt = DateTime.UtcNow.ToString("o");
|
task.UpdatedAt = DateTime.UtcNow.ToString("o");
|
||||||
|
|||||||
@ -9,7 +9,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
{
|
{
|
||||||
public string Id { get; }
|
public string Id { get; }
|
||||||
public string WaveId { get; }
|
public string WaveId { get; }
|
||||||
public TargetType TargetType { get; }
|
public DroneType DroneType { get; }
|
||||||
public PowerType PowerType { get; }
|
public PowerType PowerType { get; }
|
||||||
public float Wingspan { get; }
|
public float Wingspan { get; }
|
||||||
public float CruiseSpeed { get; }
|
public float CruiseSpeed { get; }
|
||||||
@ -38,13 +38,13 @@ namespace CounterDrone.Core.Simulation
|
|||||||
/// 查找弧长 s 所在段:最大的 i 使 _cumArc[i] >= s。</summary>
|
/// 查找弧长 s 所在段:最大的 i 使 _cumArc[i] >= s。</summary>
|
||||||
private readonly float[] _cumArc;
|
private readonly float[] _cumArc;
|
||||||
|
|
||||||
public DroneEntity(string id, string waveId, TargetConfig config, List<Waypoint> route,
|
public DroneEntity(string id, string waveId, DroneProfile config, List<Waypoint> route,
|
||||||
int formationIndex, float lateralSpacing, int longitudinalIndex, float longitudinalSpacing,
|
int formationIndex, float lateralSpacing, int longitudinalIndex, float longitudinalSpacing,
|
||||||
FormationMode mode, int lateralAxis = 2, int longitudinalAxis = 0)
|
FormationMode mode, int lateralAxis = 2, int longitudinalAxis = 0)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
WaveId = waveId;
|
WaveId = waveId;
|
||||||
TargetType = (TargetType)config.TargetType;
|
DroneType = (DroneType)config.DroneType;
|
||||||
PowerType = (PowerType)config.PowerType;
|
PowerType = (PowerType)config.PowerType;
|
||||||
Wingspan = (float)config.Wingspan;
|
Wingspan = (float)config.Wingspan;
|
||||||
float spd = (float)route[0].Speed;
|
float spd = (float)route[0].Speed;
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
{
|
{
|
||||||
private readonly IPathProvider _paths;
|
private readonly IPathProvider _paths;
|
||||||
private readonly List<FrameRecord> _buffer = new();
|
private readonly List<FrameRecord> _buffer = new();
|
||||||
private string _taskId = string.Empty;
|
private string _scenarioId = string.Empty;
|
||||||
public int RetentionDays { get; set; } = 180;
|
public int RetentionDays { get; set; } = 180;
|
||||||
public int BufferedFrameCount { get; private set; }
|
public int BufferedFrameCount { get; private set; }
|
||||||
/// <summary>仿真刚结束时保留内存快照,支持快速回放。Flush 后自动清空。</summary>
|
/// <summary>仿真刚结束时保留内存快照,支持快速回放。Flush 后自动清空。</summary>
|
||||||
@ -23,9 +23,9 @@ namespace CounterDrone.Core.Simulation
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>开始录制。清空缓冲区,准备接收帧数据。</summary>
|
/// <summary>开始录制。清空缓冲区,准备接收帧数据。</summary>
|
||||||
public void BeginRecording(string taskId)
|
public void BeginRecording(string scenarioId)
|
||||||
{
|
{
|
||||||
_taskId = taskId;
|
_scenarioId = scenarioId;
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
LiveFrames.Clear();
|
LiveFrames.Clear();
|
||||||
BufferedFrameCount = 0;
|
BufferedFrameCount = 0;
|
||||||
@ -65,7 +65,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
var s = fr.Snapshot;
|
var s = fr.Snapshot;
|
||||||
var record = new SimFrameRecord
|
var record = new SimFrameRecord
|
||||||
{
|
{
|
||||||
TaskId = _taskId, FrameIndex = fr.FrameIndex, Timestamp = fr.Timestamp,
|
ScenarioId = _scenarioId, FrameIndex = fr.FrameIndex, Timestamp = fr.Timestamp,
|
||||||
EntityId = s.EntityId, EntityType = (int)s.EntityType,
|
EntityId = s.EntityId, EntityType = (int)s.EntityType,
|
||||||
PosX = s.PosX, PosY = s.PosY, PosZ = s.PosZ,
|
PosX = s.PosX, PosY = s.PosY, PosZ = s.PosZ,
|
||||||
StateData = s.EntityType switch
|
StateData = s.EntityType switch
|
||||||
@ -84,30 +84,30 @@ namespace CounterDrone.Core.Simulation
|
|||||||
|
|
||||||
_buffer.Clear();
|
_buffer.Clear();
|
||||||
|
|
||||||
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{_taskId}.db");
|
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{_scenarioId}.db");
|
||||||
Directory.CreateDirectory(_paths.GetFramesDir());
|
Directory.CreateDirectory(_paths.GetFramesDir());
|
||||||
using var db = new SQLiteConnection(dbPath);
|
using var db = new SQLiteConnection(dbPath);
|
||||||
db.CreateTable<SimFrameRecord>();
|
db.CreateTable<SimFrameRecord>();
|
||||||
db.CreateIndex("SimFrameRecord", new[] { "TaskId", "FrameIndex" });
|
db.CreateIndex("SimFrameRecord", new[] { "ScenarioId", "FrameIndex" });
|
||||||
db.InsertAll(records);
|
db.InsertAll(records);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Discard() { _buffer.Clear(); LiveFrames.Clear(); BufferedFrameCount = 0; }
|
public void Discard() { _buffer.Clear(); LiveFrames.Clear(); BufferedFrameCount = 0; }
|
||||||
|
|
||||||
public List<SimFrameRecord> ReadFrames(string taskId, int fromFrame, int toFrame)
|
public List<SimFrameRecord> ReadFrames(string scenarioId, int fromFrame, int toFrame)
|
||||||
{
|
{
|
||||||
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
|
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{scenarioId}.db");
|
||||||
if (!File.Exists(dbPath)) return new List<SimFrameRecord>();
|
if (!File.Exists(dbPath)) return new List<SimFrameRecord>();
|
||||||
using var db = new SQLiteConnection(dbPath);
|
using var db = new SQLiteConnection(dbPath);
|
||||||
return db.Table<SimFrameRecord>()
|
return db.Table<SimFrameRecord>()
|
||||||
.Where(f => f.TaskId == taskId && f.FrameIndex >= fromFrame && f.FrameIndex <= toFrame)
|
.Where(f => f.ScenarioId == scenarioId && f.FrameIndex >= fromFrame && f.FrameIndex <= toFrame)
|
||||||
.OrderBy(f => f.FrameIndex)
|
.OrderBy(f => f.FrameIndex)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteFrameDb(string taskId)
|
public void DeleteFrameDb(string scenarioId)
|
||||||
{
|
{
|
||||||
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
|
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{scenarioId}.db");
|
||||||
if (File.Exists(dbPath)) File.Delete(dbPath);
|
if (File.Exists(dbPath)) File.Delete(dbPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
private bool _anyThreatDetected;
|
private bool _anyThreatDetected;
|
||||||
private float _detectionTime;
|
private float _detectionTime;
|
||||||
private readonly List<SimEvent> _allEvents = new();
|
private readonly List<SimEvent> _allEvents = new();
|
||||||
private string _taskId = string.Empty;
|
private string _scenarioId = string.Empty;
|
||||||
private readonly IDefensePlanner _planner;
|
private readonly IDefensePlanner _planner;
|
||||||
private int _entityCounter;
|
private int _entityCounter;
|
||||||
|
|
||||||
@ -82,10 +82,10 @@ namespace CounterDrone.Core.Simulation
|
|||||||
_nextFireIndex = 0;
|
_nextFireIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(string taskId)
|
public void Initialize(string scenarioId)
|
||||||
{
|
{
|
||||||
_taskId = taskId;
|
_scenarioId = scenarioId;
|
||||||
var config = _scenarioService.GetTaskDetail(taskId);
|
var config = _scenarioService.GetTaskDetail(scenarioId);
|
||||||
if (config == null) throw new InvalidOperationException("Task not found");
|
if (config == null) throw new InvalidOperationException("Task not found");
|
||||||
|
|
||||||
_scene = config.Scene;
|
_scene = config.Scene;
|
||||||
@ -179,7 +179,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (RecordFrames)
|
if (RecordFrames)
|
||||||
_frameStore.BeginRecording(_taskId);
|
_frameStore.BeginRecording(_scenarioId);
|
||||||
_anyThreatDetected = false;
|
_anyThreatDetected = false;
|
||||||
State = SimulationState.Running;
|
State = SimulationState.Running;
|
||||||
}
|
}
|
||||||
@ -363,7 +363,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
{
|
{
|
||||||
float exposureIncrement = inCloudDistance / (drone.CruiseSpeed / 3.6f);
|
float exposureIncrement = inCloudDistance / (drone.CruiseSpeed / 3.6f);
|
||||||
drone.ExposureTime += exposureIncrement;
|
drone.ExposureTime += exposureIncrement;
|
||||||
var dmg = _damageModel.CalculateDamage(drone.TargetType, drone.PowerType, cloud.AerosolType, cloud.Dispersion.CoreDensity, drone.ExposureTime, exposureIncrement);
|
var dmg = _damageModel.CalculateDamage(drone.DroneType, drone.PowerType, cloud.AerosolType, cloud.Dispersion.CoreDensity, drone.ExposureTime, exposureIncrement);
|
||||||
drone.ApplyDamage(dmg);
|
drone.ApplyDamage(dmg);
|
||||||
if (drone.Status == DroneStatus.Destroyed)
|
if (drone.Status == DroneStatus.Destroyed)
|
||||||
{
|
{
|
||||||
@ -427,7 +427,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
public void Resume() { if (State == SimulationState.Paused) State = SimulationState.Running; }
|
public void Resume() { if (State == SimulationState.Paused) State = SimulationState.Running; }
|
||||||
public void Stop() { State = SimulationState.Stopped; _frameStore.Flush(); }
|
public void Stop() { State = SimulationState.Stopped; _frameStore.Flush(); }
|
||||||
|
|
||||||
private static List<FireUnit> BuildFireUnits(TaskFullConfig config)
|
private static List<FireUnit> BuildFireUnits(ScenarioConfig config)
|
||||||
{
|
{
|
||||||
var units = new List<FireUnit>();
|
var units = new List<FireUnit>();
|
||||||
foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
|
foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
|
||||||
@ -464,7 +464,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
|
|
||||||
/// <summary>构建统一信息网络的探测源列表。
|
/// <summary>构建统一信息网络的探测源列表。
|
||||||
/// 独立探测设备(EquipmentRole.Detection)+ 火力单元自带探测能力。</summary>
|
/// 独立探测设备(EquipmentRole.Detection)+ 火力单元自带探测能力。</summary>
|
||||||
private static List<DetectionSource> BuildDetectionSources(TaskFullConfig config)
|
private static List<DetectionSource> BuildDetectionSources(ScenarioConfig config)
|
||||||
{
|
{
|
||||||
var sources = new List<DetectionSource>();
|
var sources = new List<DetectionSource>();
|
||||||
foreach (var eq in config.Equipment)
|
foreach (var eq in config.Equipment)
|
||||||
@ -493,7 +493,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<DroneWave> BuildDroneWaves(TaskFullConfig config)
|
private static List<DroneWave> BuildDroneWaves(ScenarioConfig config)
|
||||||
{
|
{
|
||||||
var groups = new List<DroneWave>();
|
var groups = new List<DroneWave>();
|
||||||
foreach (var t in config.Targets)
|
foreach (var t in config.Targets)
|
||||||
|
|||||||
@ -29,34 +29,34 @@ namespace CounterDrone.Unity
|
|||||||
var scenarioMgr = gameObject.AddComponent<ScenarioManager>();
|
var scenarioMgr = gameObject.AddComponent<ScenarioManager>();
|
||||||
scenarioMgr.Awake();
|
scenarioMgr.Awake();
|
||||||
var task = scenarioMgr.CreateTask("Verification", "");
|
var task = scenarioMgr.CreateTask("Verification", "");
|
||||||
var taskId = task.Id;
|
var scenarioId = task.Id;
|
||||||
Debug.Log($"2. ScenarioManager OK. Task: {taskId}");
|
Debug.Log($"2. ScenarioManager OK. Task: {scenarioId}");
|
||||||
|
|
||||||
scenarioMgr.SaveScene(taskId, new CombatScene { WindSpeed = 0 });
|
scenarioMgr.SaveScene(scenarioId, new CombatScene { WindSpeed = 0 });
|
||||||
scenarioMgr.SaveTarget(taskId, new TargetConfig
|
scenarioMgr.SaveTarget(scenarioId, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
|
||||||
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
||||||
});
|
});
|
||||||
scenarioMgr.SaveRoute(taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
scenarioMgr.SaveRoute(scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
new List<Waypoint>
|
new List<Waypoint>
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
new Waypoint { PosX = 10000, PosY = 500, PosZ = 0, Speed = 200 },
|
new Waypoint { PosX = 10000, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
});
|
});
|
||||||
scenarioMgr.UpdateStep(taskId, 3);
|
scenarioMgr.UpdateStep(scenarioId, 3);
|
||||||
Debug.Log("3. Scenario configured");
|
Debug.Log("3. Scenario configured");
|
||||||
|
|
||||||
var db = new DatabaseManager(paths).OpenMainDb();
|
var db = new DatabaseManager(paths).OpenMainDb();
|
||||||
SqliteConnectionTracker.Track(db);
|
SqliteConnectionTracker.Track(db);
|
||||||
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
|
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
|
||||||
|
|
||||||
var detail = scenarioMgr.GetDetail(taskId);
|
var detail = scenarioMgr.GetDetail(scenarioId);
|
||||||
var droneGroup = new DroneWave
|
var droneGroup = new DroneWave
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
Target = detail.Targets[0],
|
DroneType = detail.Targets[0].DroneType,
|
||||||
Route = detail.Routes[0],
|
Route = detail.Routes[0],
|
||||||
Waypoints = detail.WaypointGroups["default"],
|
Waypoints = detail.WaypointGroups["default"],
|
||||||
};
|
};
|
||||||
@ -72,7 +72,7 @@ namespace CounterDrone.Unity
|
|||||||
AmmoTypes = new() { AerosolType.InertGas, AerosolType.ActiveMaterial, AerosolType.ActiveFuel },
|
AmmoTypes = new() { AerosolType.InertGas, AerosolType.ActiveMaterial, AerosolType.ActiveFuel },
|
||||||
});
|
});
|
||||||
var result = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths)).Plan(fireUnits, new List<DroneWave> { droneGroup }, detail.Scene, new List<DetectionSource>());
|
var result = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths)).Plan(fireUnits, new List<DroneWave> { droneGroup }, detail.Scene, new List<DetectionSource>());
|
||||||
scenarioMgr.SaveCloud(taskId, new CloudDispersal
|
scenarioMgr.SaveCloud(scenarioId, new CloudDispersal
|
||||||
{
|
{
|
||||||
PositionX = (droneGroup.Waypoints[0].PosX + droneGroup.Waypoints[^1].PosX) / 2,
|
PositionX = (droneGroup.Waypoints[0].PosX + droneGroup.Waypoints[^1].PosX) / 2,
|
||||||
PositionY = (droneGroup.Waypoints[0].PosY + droneGroup.Waypoints[^1].PosY) / 2,
|
PositionY = (droneGroup.Waypoints[0].PosY + droneGroup.Waypoints[^1].PosY) / 2,
|
||||||
@ -92,7 +92,7 @@ namespace CounterDrone.Unity
|
|||||||
CruiseSpeed = u.CruiseSpeed > 0 ? u.CruiseSpeed : null,
|
CruiseSpeed = u.CruiseSpeed > 0 ? u.CruiseSpeed : null,
|
||||||
ReleaseAltitude = u.ReleaseAltitude > 0 ? u.ReleaseAltitude : null,
|
ReleaseAltitude = u.ReleaseAltitude > 0 ? u.ReleaseAltitude : null,
|
||||||
});
|
});
|
||||||
scenarioMgr.SaveDeployment(taskId, equips);
|
scenarioMgr.SaveDeployment(scenarioId, equips);
|
||||||
SqliteConnectionTracker.Untrack(db);
|
SqliteConnectionTracker.Untrack(db);
|
||||||
db.Dispose();
|
db.Dispose();
|
||||||
var plan = result.Best;
|
var plan = result.Best;
|
||||||
@ -100,7 +100,7 @@ namespace CounterDrone.Unity
|
|||||||
|
|
||||||
var runner = gameObject.AddComponent<SimulationRunner>();
|
var runner = gameObject.AddComponent<SimulationRunner>();
|
||||||
runner.Awake();
|
runner.Awake();
|
||||||
runner.LoadAndStart(taskId);
|
runner.LoadAndStart(scenarioId);
|
||||||
runner.Engine.TimeScale = 10f;
|
runner.Engine.TimeScale = 10f;
|
||||||
|
|
||||||
float endTime = Time.realtimeSinceStartup + 60f;
|
float endTime = Time.realtimeSinceStartup + 60f;
|
||||||
@ -116,16 +116,16 @@ namespace CounterDrone.Unity
|
|||||||
|
|
||||||
var reportMgr = gameObject.AddComponent<ReportManager>();
|
var reportMgr = gameObject.AddComponent<ReportManager>();
|
||||||
reportMgr.Awake();
|
reportMgr.Awake();
|
||||||
detail = scenarioMgr.GetDetail(taskId);
|
detail = scenarioMgr.GetDetail(scenarioId);
|
||||||
var simEvents = new List<SimEvent>(runner.Engine.Events);
|
var simEvents = new List<SimEvent>(runner.Engine.Events);
|
||||||
var status = runner.Engine.Drones.Count > 0 ? runner.Engine.Drones[0].Status.ToString() : "Unknown";
|
var status = runner.Engine.Drones.Count > 0 ? runner.Engine.Drones[0].Status.ToString() : "Unknown";
|
||||||
var report = reportMgr.Generate(taskId, detail, simEvents, status, runner.Engine.SimulationTime);
|
var report = reportMgr.Generate(scenarioId, detail, simEvents, status, runner.Engine.SimulationTime);
|
||||||
var path = reportMgr.Export(report.Id);
|
var path = reportMgr.Export(report.Id);
|
||||||
Debug.Log($"6. Report OK. Exported: {path}");
|
Debug.Log($"6. Report OK. Exported: {path}");
|
||||||
|
|
||||||
var replay = gameObject.AddComponent<ReplayController>();
|
var replay = gameObject.AddComponent<ReplayController>();
|
||||||
replay.Awake();
|
replay.Awake();
|
||||||
replay.LoadReplay(taskId);
|
replay.LoadReplay(scenarioId);
|
||||||
Debug.Log($"7. Replay OK. {replay.TotalFrames} frames, {replay.CurrentFrames.Count} records");
|
Debug.Log($"7. Replay OK. {replay.TotalFrames} frames, {replay.CurrentFrames.Count} records");
|
||||||
|
|
||||||
Debug.Log("===== ALL MANAGERS VERIFIED =====");
|
Debug.Log("===== ALL MANAGERS VERIFIED =====");
|
||||||
|
|||||||
@ -24,7 +24,7 @@ namespace CounterDrone.Unity
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>加载回放数据:优先使用传入的 frameStore 内存数据(仿真刚结束),否则读数据库</summary>
|
/// <summary>加载回放数据:优先使用传入的 frameStore 内存数据(仿真刚结束),否则读数据库</summary>
|
||||||
public void LoadReplay(string taskId, FrameDataStore frameStore = null)
|
public void LoadReplay(string scenarioId, FrameDataStore frameStore = null)
|
||||||
{
|
{
|
||||||
if (frameStore != null && frameStore.LiveFrames.Count > 0)
|
if (frameStore != null && frameStore.LiveFrames.Count > 0)
|
||||||
{
|
{
|
||||||
@ -34,7 +34,7 @@ namespace CounterDrone.Unity
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_frameStore ??= new FrameDataStore(_paths);
|
_frameStore ??= new FrameDataStore(_paths);
|
||||||
CurrentFrames = _frameStore.ReadFrames(taskId, 0, int.MaxValue);
|
CurrentFrames = _frameStore.ReadFrames(scenarioId, 0, int.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
TotalFrames = CurrentFrames.Count > 0
|
TotalFrames = CurrentFrames.Count > 0
|
||||||
|
|||||||
@ -25,17 +25,17 @@ namespace CounterDrone.Unity
|
|||||||
|
|
||||||
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
|
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
|
||||||
|
|
||||||
public SimulationReport Generate(string taskId, TaskFullConfig cfg, List<SimEvent> events, string status, float duration)
|
public SimulationReport Generate(string scenarioId, ScenarioConfig cfg, List<SimEvent> events, string status, float duration)
|
||||||
=> _service.Generate(taskId, cfg, events, status, duration);
|
=> _service.Generate(scenarioId, cfg, events, status, duration);
|
||||||
|
|
||||||
public SimulationReport Get(string id) => _service.GetReport(id);
|
public SimulationReport Get(string id) => _service.GetReport(id);
|
||||||
public void Delete(string id) => _service.DeleteReport(id);
|
public void Delete(string id) => _service.DeleteReport(id);
|
||||||
public PagedResult<SimulationReport> Search(string kw, string from, string to, int page, int size)
|
public PagedResult<SimulationReport> Search(string kw, string from, string to, int page, int size)
|
||||||
=> _service.SearchReports(kw, from, to, page, size);
|
=> _service.SearchReports(kw, from, to, page, size);
|
||||||
|
|
||||||
public string Export(string reportId)
|
public string Export(string reportId, string? outputDir = null)
|
||||||
{
|
{
|
||||||
var dir = System.IO.Path.Combine(_paths.GetDataRoot(), "reports");
|
var dir = outputDir ?? System.IO.Path.Combine(_paths.GetDataRoot(), "reports");
|
||||||
return _service.ExportToFile(reportId, dir);
|
return _service.ExportToFile(reportId, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,8 +26,8 @@ namespace CounterDrone.Unity
|
|||||||
_db = new DatabaseManager(paths).OpenMainDb();
|
_db = new DatabaseManager(paths).OpenMainDb();
|
||||||
SqliteConnectionTracker.Track(_db);
|
SqliteConnectionTracker.Track(_db);
|
||||||
_service = new ScenarioService(
|
_service = new ScenarioService(
|
||||||
new SimTaskRepository(_db), new CombatSceneRepository(_db),
|
new ScenarioRepository(_db), new CombatSceneRepository(_db),
|
||||||
new ControlZoneRepository(_db), new TargetConfigRepository(_db),
|
new ControlZoneRepository(_db), new DroneProfileRepository(_db),
|
||||||
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
|
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
|
||||||
new RoutePlanRepository(_db), new WaypointRepository(_db));
|
new RoutePlanRepository(_db), new WaypointRepository(_db));
|
||||||
_dataService = new DataService(
|
_dataService = new DataService(
|
||||||
@ -45,18 +45,18 @@ namespace CounterDrone.Unity
|
|||||||
|
|
||||||
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
|
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
|
||||||
|
|
||||||
public SimTask CreateTask(string name, string number) => _service.CreateTask(name, number);
|
public Scenario CreateTask(string name, string number) => _service.CreateTask(name, number);
|
||||||
public void DeleteTask(string id) => _service.DeleteTask(id);
|
public void DeleteTask(string id) => _service.DeleteTask(id);
|
||||||
public TaskFullConfig GetDetail(string id) => _service.GetTaskDetail(id);
|
public ScenarioConfig GetDetail(string id) => _service.GetTaskDetail(id);
|
||||||
public PagedResult<SimTask> Search(string kw, string from, string to, int page, int size)
|
public PagedResult<Scenario> Search(string kw, string from, string to, int page, int size)
|
||||||
=> _service.SearchTasks(kw, from, to, page, size);
|
=> _service.SearchTasks(kw, from, to, page, size);
|
||||||
|
|
||||||
public void SaveScene(string taskId, CombatScene s) => _service.SaveScene(taskId, s);
|
public void SaveScene(string scenarioId, CombatScene s) => _service.SaveScene(scenarioId, s);
|
||||||
public void SaveTarget(string taskId, TargetConfig t) => _service.SaveTarget(taskId, t);
|
public void SaveTarget(string scenarioId, DroneProfile t) => _service.SaveTarget(scenarioId, t);
|
||||||
public void SaveDeployment(string taskId, List<EquipmentDeployment> e) => _service.SaveDeployment(taskId, e);
|
public void SaveDeployment(string scenarioId, List<EquipmentDeployment> e) => _service.SaveDeployment(scenarioId, e);
|
||||||
public void SaveCloud(string taskId, CloudDispersal c) => _service.SaveCloudDispersal(taskId, c);
|
public void SaveCloud(string scenarioId, CloudDispersal c) => _service.SaveCloudDispersal(scenarioId, c);
|
||||||
public void SaveRoute(string taskId, string waveId, RoutePlan r, List<Waypoint> w) => _service.SaveRoute(taskId, waveId, r, w);
|
public void SaveRoute(string scenarioId, string waveId, RoutePlan r, List<Waypoint> w) => _service.SaveRoute(scenarioId, waveId, r, w);
|
||||||
public void UpdateStep(string taskId, int step) => _service.UpdateStep(taskId, step);
|
public void UpdateStep(string scenarioId, int step) => _service.UpdateStep(scenarioId, step);
|
||||||
|
|
||||||
[ContextMenu("Verify")]
|
[ContextMenu("Verify")]
|
||||||
void Verify()
|
void Verify()
|
||||||
|
|||||||
@ -42,12 +42,12 @@ namespace CounterDrone.Unity
|
|||||||
new RouteTemplateRepository(db));
|
new RouteTemplateRepository(db));
|
||||||
|
|
||||||
var scenario = new ScenarioService(
|
var scenario = new ScenarioService(
|
||||||
new SimTaskRepository(db), new CombatSceneRepository(db),
|
new ScenarioRepository(db), new CombatSceneRepository(db),
|
||||||
new ControlZoneRepository(db), new TargetConfigRepository(db),
|
new ControlZoneRepository(db), new DroneProfileRepository(db),
|
||||||
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
|
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
|
||||||
new RoutePlanRepository(db), new WaypointRepository(db));
|
new RoutePlanRepository(db), new WaypointRepository(db));
|
||||||
|
|
||||||
string taskId;
|
string scenarioId;
|
||||||
|
|
||||||
if (_usePreset)
|
if (_usePreset)
|
||||||
{
|
{
|
||||||
@ -58,7 +58,7 @@ namespace CounterDrone.Unity
|
|||||||
Debug.LogError("无预设想定,请确认 defaults.json 已放入 StreamingAssets");
|
Debug.LogError("无预设想定,请确认 defaults.json 已放入 StreamingAssets");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
taskId = demos.Items[0].Id;
|
scenarioId = demos.Items[0].Id;
|
||||||
Debug.Log($"使用预设想定: {demos.Items[0].Name}");
|
Debug.Log($"使用预设想定: {demos.Items[0].Name}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -67,17 +67,17 @@ namespace CounterDrone.Unity
|
|||||||
var defaults = DefaultData.Load(paths);
|
var defaults = DefaultData.Load(paths);
|
||||||
|
|
||||||
var task = scenario.CreateTask("Unity Demo", "");
|
var task = scenario.CreateTask("Unity Demo", "");
|
||||||
taskId = task.Id;
|
scenarioId = task.Id;
|
||||||
|
|
||||||
scenario.SaveScene(taskId, defaults.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
|
scenario.SaveScene(scenarioId, defaults.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
|
||||||
scenario.SaveTarget(taskId, defaults.Drones.First(p => p.Id == "shahed").ToTargetConfig());
|
scenario.SaveTarget(scenarioId, defaults.Drones.First(p => p.Id == "shahed").ToDroneProfile());
|
||||||
scenario.SaveRoute(taskId, "default",
|
scenario.SaveRoute(scenarioId, "default",
|
||||||
defaults.Formations.First(f => f.Id == "single").ToRoutePlan(),
|
defaults.Formations.First(f => f.Id == "single").ToRoutePlan(),
|
||||||
defaults.Routes.First(r => _routeLength > 6000 ? r.Id == "10km-h500" : r.Id == "5km-h500")
|
defaults.Routes.First(r => _routeLength > 6000 ? r.Id == "10km-h500" : r.Id == "5km-h500")
|
||||||
.ToWaypoints(_droneSpeed));
|
.ToWaypoints(_droneSpeed));
|
||||||
|
|
||||||
// 从火力单元预设构建 FireUnit → Planner 自动生成方案 → 引擎执行
|
// 从火力单元预设构建 FireUnit → Planner 自动生成方案 → 引擎执行
|
||||||
var detail = scenario.GetTaskDetail(taskId);
|
var detail = scenario.GetTaskDetail(scenarioId);
|
||||||
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
|
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
|
||||||
var planner = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths));
|
var planner = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths));
|
||||||
var threats = new List<DroneWave>
|
var threats = new List<DroneWave>
|
||||||
@ -96,18 +96,18 @@ namespace CounterDrone.Unity
|
|||||||
});
|
});
|
||||||
var plan = planner.Plan(fireUnits, threats, detail.Scene, new List<DetectionSource>());
|
var plan = planner.Plan(fireUnits, threats, detail.Scene, new List<DetectionSource>());
|
||||||
|
|
||||||
scenario.SaveCloudDispersal(taskId, new CloudDispersal
|
scenario.SaveCloudDispersal(scenarioId, new CloudDispersal
|
||||||
{
|
{
|
||||||
AerosolType = (int)AerosolType.InertGas,
|
AerosolType = (int)AerosolType.InertGas,
|
||||||
DisperseHeight = detail.Targets[0].TypicalAltitude,
|
DisperseHeight = detail.Targets[0].TypicalAltitude,
|
||||||
Source = "Algorithm",
|
Source = "Algorithm",
|
||||||
});
|
});
|
||||||
scenario.SaveDeployment(taskId, new List<EquipmentDeployment>
|
scenario.SaveDeployment(scenarioId, new List<EquipmentDeployment>
|
||||||
{
|
{
|
||||||
defaults.FireUnits.First(f => f.Id == "ground-light")
|
defaults.FireUnits.First(f => f.Id == "ground-light")
|
||||||
.ToEquipmentDeployment(AerosolType.InertGas, 1, _routeLength / 2f, 0, 50),
|
.ToEquipmentDeployment(AerosolType.InertGas, 1, _routeLength / 2f, 0, 50),
|
||||||
});
|
});
|
||||||
scenario.UpdateStep(taskId, 5);
|
scenario.UpdateStep(scenarioId, 5);
|
||||||
|
|
||||||
Debug.Log($"自定义想定: {plan.Best.ThreatsEngaged} 威胁已分配, {plan.Best.MergedSchedule.Count} 发, 概率 {plan.Best.OverallProbability:P0}");
|
Debug.Log($"自定义想定: {plan.Best.ThreatsEngaged} 威胁已分配, {plan.Best.MergedSchedule.Count} 发, 概率 {plan.Best.OverallProbability:P0}");
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ namespace CounterDrone.Unity
|
|||||||
if (Runner == null) Runner = gameObject.AddComponent<SimulationRunner>();
|
if (Runner == null) Runner = gameObject.AddComponent<SimulationRunner>();
|
||||||
else Runner.enabled = true;
|
else Runner.enabled = true;
|
||||||
Runner.DataService = dataService;
|
Runner.DataService = dataService;
|
||||||
Runner.LoadAndStart(taskId);
|
Runner.LoadAndStart(scenarioId);
|
||||||
Runner.Engine.TimeScale = _timeScale;
|
Runner.Engine.TimeScale = _timeScale;
|
||||||
|
|
||||||
// 定位相机
|
// 定位相机
|
||||||
|
|||||||
@ -23,7 +23,7 @@ namespace CounterDrone.Unity
|
|||||||
public FrameDataStore FrameStore => _frameStore;
|
public FrameDataStore FrameStore => _frameStore;
|
||||||
private IScenarioService _scenario;
|
private IScenarioService _scenario;
|
||||||
private IPathProvider _paths;
|
private IPathProvider _paths;
|
||||||
private string _taskId;
|
private string _scenarioId;
|
||||||
private SQLite.SQLiteConnection _db;
|
private SQLite.SQLiteConnection _db;
|
||||||
|
|
||||||
private readonly Dictionary<string, GameObject> _entityVisuals = new();
|
private readonly Dictionary<string, GameObject> _entityVisuals = new();
|
||||||
@ -43,8 +43,8 @@ namespace CounterDrone.Unity
|
|||||||
SqliteConnectionTracker.Track(_db);
|
SqliteConnectionTracker.Track(_db);
|
||||||
|
|
||||||
_scenario = new ScenarioService(
|
_scenario = new ScenarioService(
|
||||||
new SimTaskRepository(_db), new CombatSceneRepository(_db),
|
new ScenarioRepository(_db), new CombatSceneRepository(_db),
|
||||||
new ControlZoneRepository(_db), new TargetConfigRepository(_db),
|
new ControlZoneRepository(_db), new DroneProfileRepository(_db),
|
||||||
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
|
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
|
||||||
new RoutePlanRepository(_db), new WaypointRepository(_db));
|
new RoutePlanRepository(_db), new WaypointRepository(_db));
|
||||||
|
|
||||||
@ -52,13 +52,13 @@ namespace CounterDrone.Unity
|
|||||||
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefensePlanner(DefaultData.Load(_paths).Ammunition, PlannerConfig.Load(_paths)));
|
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefensePlanner(DefaultData.Load(_paths).Ammunition, PlannerConfig.Load(_paths)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadAndStart(string taskId)
|
public void LoadAndStart(string scenarioId)
|
||||||
{
|
{
|
||||||
_taskId = taskId;
|
_scenarioId = scenarioId;
|
||||||
var detail = _scenario.GetTaskDetail(taskId);
|
var detail = _scenario.GetTaskDetail(scenarioId);
|
||||||
if (detail == null) { Debug.LogError("Task not found"); return; }
|
if (detail == null) { Debug.LogError("Task not found"); return; }
|
||||||
|
|
||||||
_engine.Initialize(taskId);
|
_engine.Initialize(scenarioId);
|
||||||
|
|
||||||
foreach (var drone in _engine.Drones)
|
foreach (var drone in _engine.Drones)
|
||||||
SpawnDroneVisual(drone);
|
SpawnDroneVisual(drone);
|
||||||
|
|||||||
@ -116,17 +116,17 @@
|
|||||||
|
|
||||||
"targets": [
|
"targets": [
|
||||||
{ "Id": "quadcopter", "Name": "[Demo] 小型四旋翼(DJI类)",
|
{ "Id": "quadcopter", "Name": "[Demo] 小型四旋翼(DJI类)",
|
||||||
"TargetType": 0, "PowerType": 0, "Wingspan": 1.2, "TypicalSpeed": 60.0, "TypicalAltitude": 300.0 },
|
"DroneType": 0, "PowerType": 0, "Wingspan": 1.2, "TypicalSpeed": 60.0, "TypicalAltitude": 300.0 },
|
||||||
{ "Id": "electric-scout", "Name": "[Demo] 电推侦察无人机",
|
{ "Id": "electric-scout", "Name": "[Demo] 电推侦察无人机",
|
||||||
"TargetType": 2, "PowerType": 0, "Wingspan": 1.8, "TypicalSpeed": 100.0, "TypicalAltitude": 500.0 },
|
"DroneType": 1, "PowerType": 0, "Wingspan": 1.8, "TypicalSpeed": 100.0, "TypicalAltitude": 500.0 },
|
||||||
{ "Id": "fixed-piston", "Name": "[Demo] 固定翼活塞(Orlan类)",
|
{ "Id": "fixed-piston", "Name": "[Demo] 固定翼活塞(Orlan类)",
|
||||||
"TargetType": 1, "PowerType": 1, "Wingspan": 3.5, "TypicalSpeed": 150.0, "TypicalAltitude": 1000.0 },
|
"DroneType": 1, "PowerType": 1, "Wingspan": 3.5, "TypicalSpeed": 150.0, "TypicalAltitude": 1000.0 },
|
||||||
{ "Id": "shahed", "Name": "[Demo] 活塞巡飞弹(Shahed类)",
|
{ "Id": "shahed", "Name": "[Demo] 活塞巡飞弹(Shahed类)",
|
||||||
"TargetType": 3, "PowerType": 1, "Wingspan": 2.5, "TypicalSpeed": 200.0, "TypicalAltitude": 500.0 },
|
"DroneType": 1, "PowerType": 1, "Wingspan": 2.5, "TypicalSpeed": 200.0, "TypicalAltitude": 500.0 },
|
||||||
{ "Id": "tb2", "Name": "[Demo] 中空长航时(TB2类)",
|
{ "Id": "tb2", "Name": "[Demo] 中空长航时(TB2类)",
|
||||||
"TargetType": 1, "PowerType": 1, "Wingspan": 12.0, "TypicalSpeed": 220.0, "TypicalAltitude": 5500.0 },
|
"DroneType": 1, "PowerType": 1, "Wingspan": 12.0, "TypicalSpeed": 220.0, "TypicalAltitude": 5500.0 },
|
||||||
{ "Id": "cruise-missile", "Name": "[Demo] 巡航导弹(喷气式)",
|
{ "Id": "cruise-missile", "Name": "[Demo] 巡航导弹(喷气式)",
|
||||||
"TargetType": 4, "PowerType": 2, "Wingspan": 1.5, "TypicalSpeed": 200.0, "TypicalAltitude": 2000.0 }
|
"DroneType": 2, "PowerType": 2, "Wingspan": 1.5, "TypicalSpeed": 200.0, "TypicalAltitude": 2000.0 }
|
||||||
],
|
],
|
||||||
|
|
||||||
"detectionEquipment": [
|
"detectionEquipment": [
|
||||||
|
|||||||
@ -12,7 +12,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
var model = new InertGasDamageModel();
|
var model = new InertGasDamageModel();
|
||||||
|
|
||||||
// 活塞发动机暴露于惰性气体
|
// 活塞发动机暴露于惰性气体
|
||||||
var dmg = model.CalculateDamage(TargetType.Piston, PowerType.Piston,
|
var dmg = model.CalculateDamage(DroneType.FixedWing, PowerType.Piston,
|
||||||
AerosolType.InertGas, cloudDensity: 0.1f, exposureTime: 2f, deltaTime: 1f);
|
AerosolType.InertGas, cloudDensity: 0.1f, exposureTime: 2f, deltaTime: 1f);
|
||||||
|
|
||||||
Assert.True(dmg > 0.1f); // 高于基础速率(0.15 × 1.5 = 0.225)
|
Assert.True(dmg > 0.1f); // 高于基础速率(0.15 × 1.5 = 0.225)
|
||||||
@ -23,7 +23,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
var model = new InertGasDamageModel();
|
var model = new InertGasDamageModel();
|
||||||
|
|
||||||
var dmg = model.CalculateDamage(TargetType.Piston, PowerType.Piston,
|
var dmg = model.CalculateDamage(DroneType.FixedWing, PowerType.Piston,
|
||||||
AerosolType.ActiveMaterial, cloudDensity: 0.1f, exposureTime: 2f, deltaTime: 1f);
|
AerosolType.ActiveMaterial, cloudDensity: 0.1f, exposureTime: 2f, deltaTime: 1f);
|
||||||
|
|
||||||
Assert.Equal(0f, dmg);
|
Assert.Equal(0f, dmg);
|
||||||
@ -34,12 +34,12 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
var model = new ActiveMaterialDamageModel();
|
var model = new ActiveMaterialDamageModel();
|
||||||
|
|
||||||
var dmg1 = model.CalculateDamage(TargetType.FixedWing, PowerType.Jet,
|
var dmg1 = model.CalculateDamage(DroneType.FixedWing, PowerType.Jet,
|
||||||
AerosolType.ActiveMaterial, cloudDensity: 0.2f, exposureTime: 1f, deltaTime: 1f);
|
AerosolType.ActiveMaterial, cloudDensity: 0.2f, exposureTime: 1f, deltaTime: 1f);
|
||||||
|
|
||||||
Assert.True(dmg1 >= 0.5f); // 爆发伤害
|
Assert.True(dmg1 >= 0.5f); // 爆发伤害
|
||||||
|
|
||||||
var dmg2 = model.CalculateDamage(TargetType.FixedWing, PowerType.Jet,
|
var dmg2 = model.CalculateDamage(DroneType.FixedWing, PowerType.Jet,
|
||||||
AerosolType.ActiveMaterial, cloudDensity: 0.2f, exposureTime: 2f, deltaTime: 1f);
|
AerosolType.ActiveMaterial, cloudDensity: 0.2f, exposureTime: 2f, deltaTime: 1f);
|
||||||
|
|
||||||
Assert.True(dmg2 < 0.1f); // 后续余伤很小
|
Assert.True(dmg2 < 0.1f); // 后续余伤很小
|
||||||
@ -50,7 +50,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
var model = new ActiveMaterialDamageModel();
|
var model = new ActiveMaterialDamageModel();
|
||||||
|
|
||||||
var dmg = model.CalculateDamage(TargetType.FixedWing, PowerType.Jet,
|
var dmg = model.CalculateDamage(DroneType.FixedWing, PowerType.Jet,
|
||||||
AerosolType.ActiveMaterial, cloudDensity: 0.2f, exposureTime: 0f, deltaTime: 0.1f);
|
AerosolType.ActiveMaterial, cloudDensity: 0.2f, exposureTime: 0f, deltaTime: 0.1f);
|
||||||
|
|
||||||
// 喷气发动机:0.6 × 1.3 = 0.78
|
// 喷气发动机:0.6 × 1.3 = 0.78
|
||||||
@ -62,10 +62,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
var model = new ActiveFuelDamageModel();
|
var model = new ActiveFuelDamageModel();
|
||||||
|
|
||||||
var dmg1 = model.CalculateDamage(TargetType.HighSpeed, PowerType.Jet,
|
var dmg1 = model.CalculateDamage(DroneType.HighSpeed, PowerType.Jet,
|
||||||
AerosolType.ActiveFuel, cloudDensity: 0.1f, exposureTime: 1f, deltaTime: 1f);
|
AerosolType.ActiveFuel, cloudDensity: 0.1f, exposureTime: 1f, deltaTime: 1f);
|
||||||
|
|
||||||
var dmg2 = model.CalculateDamage(TargetType.HighSpeed, PowerType.Jet,
|
var dmg2 = model.CalculateDamage(DroneType.HighSpeed, PowerType.Jet,
|
||||||
AerosolType.ActiveFuel, cloudDensity: 0.1f, exposureTime: 5f, deltaTime: 1f);
|
AerosolType.ActiveFuel, cloudDensity: 0.1f, exposureTime: 5f, deltaTime: 1f);
|
||||||
|
|
||||||
// 5 秒后的伤害应显著大于 1 秒后
|
// 5 秒后的伤害应显著大于 1 秒后
|
||||||
@ -77,7 +77,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
var model = new ActiveFuelDamageModel();
|
var model = new ActiveFuelDamageModel();
|
||||||
|
|
||||||
var dmg = model.CalculateDamage(TargetType.HighSpeed, PowerType.Jet,
|
var dmg = model.CalculateDamage(DroneType.HighSpeed, PowerType.Jet,
|
||||||
AerosolType.ActiveFuel, cloudDensity: 0.1f, exposureTime: 1f, deltaTime: 1f);
|
AerosolType.ActiveFuel, cloudDensity: 0.1f, exposureTime: 1f, deltaTime: 1f);
|
||||||
|
|
||||||
Assert.True(dmg > 0.02f); // 基础速率 × 灵敏度
|
Assert.True(dmg > 0.02f); // 基础速率 × 灵敏度
|
||||||
@ -100,17 +100,17 @@ namespace CounterDrone.Core.Tests
|
|||||||
var router = new DamageModelRouter();
|
var router = new DamageModelRouter();
|
||||||
|
|
||||||
// 惰性气体
|
// 惰性气体
|
||||||
var dmg1 = router.CalculateDamage(TargetType.Piston, PowerType.Piston,
|
var dmg1 = router.CalculateDamage(DroneType.FixedWing, PowerType.Piston,
|
||||||
AerosolType.InertGas, 0.1f, 1f, 1f);
|
AerosolType.InertGas, 0.1f, 1f, 1f);
|
||||||
Assert.True(dmg1 > 0f);
|
Assert.True(dmg1 > 0f);
|
||||||
|
|
||||||
// 活性材料
|
// 活性材料
|
||||||
var dmg2 = router.CalculateDamage(TargetType.FixedWing, PowerType.Jet,
|
var dmg2 = router.CalculateDamage(DroneType.FixedWing, PowerType.Jet,
|
||||||
AerosolType.ActiveMaterial, 0.2f, 0f, 1f);
|
AerosolType.ActiveMaterial, 0.2f, 0f, 1f);
|
||||||
Assert.True(dmg2 > 0f);
|
Assert.True(dmg2 > 0f);
|
||||||
|
|
||||||
// 活性燃料
|
// 活性燃料
|
||||||
var dmg3 = router.CalculateDamage(TargetType.HighSpeed, PowerType.Jet,
|
var dmg3 = router.CalculateDamage(DroneType.HighSpeed, PowerType.Jet,
|
||||||
AerosolType.ActiveFuel, 0.1f, 1f, 1f);
|
AerosolType.ActiveFuel, 0.1f, 1f, 1f);
|
||||||
Assert.True(dmg3 > 0f);
|
Assert.True(dmg3 > 0f);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,8 +34,8 @@ namespace CounterDrone.Core.Tests
|
|||||||
// 验证所有 12 张表存在(含 Meta)
|
// 验证所有 12 张表存在(含 Meta)
|
||||||
var tables = new[]
|
var tables = new[]
|
||||||
{
|
{
|
||||||
"ModelInfo", "AmmunitionSpec", "SimTask", "CombatScene",
|
"ModelInfo", "AmmunitionSpec", "Scenario", "CombatScene",
|
||||||
"ControlZone", "TargetConfig", "EquipmentDeployment",
|
"ControlZone", "DroneProfile", "EquipmentDeployment",
|
||||||
"CloudDispersal", "RoutePlan", "Waypoint",
|
"CloudDispersal", "RoutePlan", "Waypoint",
|
||||||
"SimulationReport"
|
"SimulationReport"
|
||||||
};
|
};
|
||||||
@ -65,13 +65,13 @@ namespace CounterDrone.Core.Tests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void DeleteFrameDb_RemovesFile()
|
public void DeleteFrameDb_RemovesFile()
|
||||||
{
|
{
|
||||||
var taskId = "task_del";
|
var scenarioId = "task_del";
|
||||||
var db = _dbManager.OpenFrameDb(taskId);
|
var db = _dbManager.OpenFrameDb(scenarioId);
|
||||||
db.Close();
|
db.Close();
|
||||||
|
|
||||||
_dbManager.DeleteFrameDb(taskId);
|
_dbManager.DeleteFrameDb(scenarioId);
|
||||||
|
|
||||||
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
|
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{scenarioId}.db");
|
||||||
Assert.False(File.Exists(dbPath));
|
Assert.False(File.Exists(dbPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,9 +43,9 @@ namespace CounterDrone.Core.Tests
|
|||||||
var t = new DroneWave
|
var t = new DroneWave
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
Target = new TargetConfig
|
Target = new DroneProfile
|
||||||
{
|
{
|
||||||
TargetType = (int)TargetType.Piston, PowerType = (int)power,
|
DroneType = (int)DroneType.FixedWing, PowerType = (int)power,
|
||||||
Quantity = 1, TypicalSpeed = speed, TypicalAltitude = alt,
|
Quantity = 1, TypicalSpeed = speed, TypicalAltitude = alt,
|
||||||
},
|
},
|
||||||
Waypoints = new List<Waypoint>
|
Waypoints = new List<Waypoint>
|
||||||
@ -378,9 +378,9 @@ namespace CounterDrone.Core.Tests
|
|||||||
var threat = new DroneWave
|
var threat = new DroneWave
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
Target = new TargetConfig
|
Target = new DroneProfile
|
||||||
{
|
{
|
||||||
TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
|
||||||
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
||||||
},
|
},
|
||||||
Waypoints = new List<Waypoint>
|
Waypoints = new List<Waypoint>
|
||||||
|
|||||||
@ -8,11 +8,11 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
public class DroneEntityTests
|
public class DroneEntityTests
|
||||||
{
|
{
|
||||||
private TargetConfig CreateConfig(float speed = 120, float altitude = 300)
|
private DroneProfile CreateConfig(float speed = 120, float altitude = 300)
|
||||||
{
|
{
|
||||||
return new TargetConfig
|
return new DroneProfile
|
||||||
{
|
{
|
||||||
TargetType = (int)TargetType.Piston,
|
DroneType = (int)DroneType.FixedWing,
|
||||||
PowerType = (int)PowerType.Piston,
|
PowerType = (int)PowerType.Piston,
|
||||||
TypicalSpeed = speed,
|
TypicalSpeed = speed,
|
||||||
TypicalAltitude = altitude,
|
TypicalAltitude = altitude,
|
||||||
|
|||||||
@ -17,7 +17,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
private readonly string _testDir;
|
private readonly string _testDir;
|
||||||
private readonly IScenarioService _scenario;
|
private readonly IScenarioService _scenario;
|
||||||
private readonly SQLite.SQLiteConnection _db;
|
private readonly SQLite.SQLiteConnection _db;
|
||||||
private string _taskId = string.Empty;
|
private string _scenarioId = string.Empty;
|
||||||
|
|
||||||
public EdgeCaseTests()
|
public EdgeCaseTests()
|
||||||
{
|
{
|
||||||
@ -27,8 +27,8 @@ namespace CounterDrone.Core.Tests
|
|||||||
_db = dbm.OpenMainDb();
|
_db = dbm.OpenMainDb();
|
||||||
|
|
||||||
_scenario = new ScenarioService(
|
_scenario = new ScenarioService(
|
||||||
new SimTaskRepository(_db), new CombatSceneRepository(_db),
|
new ScenarioRepository(_db), new CombatSceneRepository(_db),
|
||||||
new ControlZoneRepository(_db), new TargetConfigRepository(_db),
|
new ControlZoneRepository(_db), new DroneProfileRepository(_db),
|
||||||
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
|
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
|
||||||
new RoutePlanRepository(_db), new WaypointRepository(_db));
|
new RoutePlanRepository(_db), new WaypointRepository(_db));
|
||||||
}
|
}
|
||||||
@ -43,20 +43,20 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void EmptyDeployment_EngineRunsWithoutCrash()
|
public void EmptyDeployment_EngineRunsWithoutCrash()
|
||||||
{
|
{
|
||||||
var task = _scenario.CreateTask("空部署", "");
|
var task = _scenario.CreateTask("空部署", "");
|
||||||
_taskId = task.Id;
|
_scenarioId = task.Id;
|
||||||
|
|
||||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
|
||||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
_scenario.SaveTarget(_scenarioId, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
TargetType = (int)TargetType.Piston,
|
DroneType = (int)DroneType.FixedWing,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
TypicalSpeed = 100,
|
TypicalSpeed = 100,
|
||||||
TypicalAltitude = 300,
|
TypicalAltitude = 300,
|
||||||
});
|
});
|
||||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>());
|
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>());
|
||||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal());
|
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal());
|
||||||
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
new List<Waypoint>
|
new List<Waypoint>
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Speed = 100 },
|
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Speed = 100 },
|
||||||
@ -65,7 +65,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
|
|
||||||
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
|
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
|
||||||
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(TestData.Ammo, TestPlannerConfig.Instance));
|
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(TestData.Ammo, TestPlannerConfig.Instance));
|
||||||
engine.Initialize(_taskId);
|
engine.Initialize(_scenarioId);
|
||||||
engine.TimeScale = 4f;
|
engine.TimeScale = 4f;
|
||||||
|
|
||||||
for (int i = 0; i < 500; i++)
|
for (int i = 0; i < 500; i++)
|
||||||
@ -82,23 +82,23 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void ExtremeWind_DroneStillFlies()
|
public void ExtremeWind_DroneStillFlies()
|
||||||
{
|
{
|
||||||
var task = _scenario.CreateTask("飓风", "");
|
var task = _scenario.CreateTask("飓风", "");
|
||||||
_taskId = task.Id;
|
_scenarioId = task.Id;
|
||||||
|
|
||||||
_scenario.SaveScene(_taskId, new CombatScene
|
_scenario.SaveScene(_scenarioId, new CombatScene
|
||||||
{
|
{
|
||||||
WindSpeed = 30, // 最大风速
|
WindSpeed = 30, // 最大风速
|
||||||
WindDirection = (int)WindDirection.E,
|
WindDirection = (int)WindDirection.E,
|
||||||
});
|
});
|
||||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
_scenario.SaveTarget(_scenarioId, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
TargetType = (int)TargetType.Piston,
|
DroneType = (int)DroneType.FixedWing,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
TypicalSpeed = 600, // 高速对抗强风
|
TypicalSpeed = 600, // 高速对抗强风
|
||||||
});
|
});
|
||||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>());
|
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>());
|
||||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal());
|
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal());
|
||||||
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
new List<Waypoint>
|
new List<Waypoint>
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Speed = 600 },
|
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Speed = 600 },
|
||||||
@ -107,7 +107,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
|
|
||||||
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
|
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
|
||||||
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(TestData.Ammo, TestPlannerConfig.Instance));
|
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(TestData.Ammo, TestPlannerConfig.Instance));
|
||||||
engine.Initialize(_taskId);
|
engine.Initialize(_scenarioId);
|
||||||
engine.TimeScale = 4f;
|
engine.TimeScale = 4f;
|
||||||
|
|
||||||
for (int i = 0; i < 500; i++)
|
for (int i = 0; i < 500; i++)
|
||||||
@ -125,21 +125,21 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void LongRoute_CompletesWithoutError()
|
public void LongRoute_CompletesWithoutError()
|
||||||
{
|
{
|
||||||
var task = _scenario.CreateTask("超长航路", "");
|
var task = _scenario.CreateTask("超长航路", "");
|
||||||
_taskId = task.Id;
|
_scenarioId = task.Id;
|
||||||
|
|
||||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
|
||||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
_scenario.SaveTarget(_scenarioId, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
TargetType = (int)TargetType.HighSpeed,
|
DroneType = (int)DroneType.HighSpeed,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
TypicalSpeed = 500,
|
TypicalSpeed = 500,
|
||||||
});
|
});
|
||||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>());
|
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>());
|
||||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal());
|
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal());
|
||||||
|
|
||||||
// 500 km/h, 100km 航程
|
// 500 km/h, 100km 航程
|
||||||
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
new List<Waypoint>
|
new List<Waypoint>
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 500 },
|
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 500 },
|
||||||
@ -148,7 +148,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
|
|
||||||
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
|
var engine = new SimulationEngine(_scenario, new FrameDataStore(new TestPathProvider(_testDir)),
|
||||||
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(TestData.Ammo, TestPlannerConfig.Instance));
|
new DamageModelRouter(), new TestPathProvider(_testDir), new DefensePlanner(TestData.Ammo, TestPlannerConfig.Instance));
|
||||||
engine.Initialize(_taskId);
|
engine.Initialize(_scenarioId);
|
||||||
engine.TimeScale = 4f;
|
engine.TimeScale = 4f;
|
||||||
|
|
||||||
for (int i = 0; i < 5000; i++)
|
for (int i = 0; i < 5000; i++)
|
||||||
|
|||||||
@ -25,9 +25,9 @@ namespace CounterDrone.Core.Tests
|
|||||||
if (Directory.Exists(_testDir)) Directory.Delete(_testDir, true);
|
if (Directory.Exists(_testDir)) Directory.Delete(_testDir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateFakeDb(string taskId)
|
private void CreateFakeDb(string scenarioId)
|
||||||
{
|
{
|
||||||
_store.BeginRecording(taskId);
|
_store.BeginRecording(scenarioId);
|
||||||
_store.RecordFrame(0, 0f, new List<EntitySnapshot> { new EntitySnapshot { EntityId = "d1" } });
|
_store.RecordFrame(0, 0f, new List<EntitySnapshot> { new EntitySnapshot { EntityId = "d1" } });
|
||||||
_store.Flush();
|
_store.Flush();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
private readonly FrameDataStore _frameStore;
|
private readonly FrameDataStore _frameStore;
|
||||||
private readonly SQLiteConnection _mainDb;
|
private readonly SQLiteConnection _mainDb;
|
||||||
private List<AmmunitionSpec> _ammoCatalog;
|
private List<AmmunitionSpec> _ammoCatalog;
|
||||||
private string _taskId = string.Empty;
|
private string _scenarioId = string.Empty;
|
||||||
|
|
||||||
public FullPipelineTests()
|
public FullPipelineTests()
|
||||||
{
|
{
|
||||||
@ -32,8 +32,8 @@ namespace CounterDrone.Core.Tests
|
|||||||
_ammoCatalog = new List<AmmunitionSpec>(TestData.Ammo);
|
_ammoCatalog = new List<AmmunitionSpec>(TestData.Ammo);
|
||||||
|
|
||||||
_scenario = new ScenarioService(
|
_scenario = new ScenarioService(
|
||||||
new SimTaskRepository(_mainDb), new CombatSceneRepository(_mainDb),
|
new ScenarioRepository(_mainDb), new CombatSceneRepository(_mainDb),
|
||||||
new ControlZoneRepository(_mainDb), new TargetConfigRepository(_mainDb),
|
new ControlZoneRepository(_mainDb), new DroneProfileRepository(_mainDb),
|
||||||
new EquipmentDeploymentRepository(_mainDb), new CloudDispersalRepository(_mainDb),
|
new EquipmentDeploymentRepository(_mainDb), new CloudDispersalRepository(_mainDb),
|
||||||
new RoutePlanRepository(_mainDb), new WaypointRepository(_mainDb));
|
new RoutePlanRepository(_mainDb), new WaypointRepository(_mainDb));
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
var engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefensePlanner(_ammoCatalog, TestPlannerConfig.Instance));
|
var engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefensePlanner(_ammoCatalog, TestPlannerConfig.Instance));
|
||||||
|
|
||||||
engine.Initialize(_taskId);
|
engine.Initialize(_scenarioId);
|
||||||
engine.TimeScale = timeScale; // 8倍速加速
|
engine.TimeScale = timeScale; // 8倍速加速
|
||||||
for (int i = 0; i < maxTicks; i++)
|
for (int i = 0; i < maxTicks; i++)
|
||||||
{
|
{
|
||||||
@ -66,22 +66,22 @@ namespace CounterDrone.Core.Tests
|
|||||||
|
|
||||||
private void VerifyAndExportReport(SimulationEngine eng, DroneEntity drone)
|
private void VerifyAndExportReport(SimulationEngine eng, DroneEntity drone)
|
||||||
{
|
{
|
||||||
var detail = _scenario.GetTaskDetail(_taskId)!;
|
var detail = _scenario.GetTaskDetail(_scenarioId)!;
|
||||||
var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime);
|
var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime);
|
||||||
Assert.Contains("对抗结果", report);
|
Assert.Contains("对抗结果", report);
|
||||||
Assert.Contains(detail.Task.Name, report);
|
Assert.Contains(detail.Task.Name, report);
|
||||||
|
|
||||||
var svc = new ReportService(_mainDb, _paths);
|
var svc = new ReportService(_mainDb, _paths);
|
||||||
var r = svc.Generate(_taskId, detail, eng.Events.ToList(), drone.Status.ToString(), eng.SimulationTime);
|
var r = svc.Generate(_scenarioId, detail, eng.Events.ToList(), drone.Status.ToString(), eng.SimulationTime);
|
||||||
svc.ExportToFile(r.Id, ReportOutputDir); // 持久化目录
|
svc.ExportToFile(r.Id, ReportOutputDir); // 持久化目录
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaskFullConfig LoadPreset(string name)
|
private ScenarioConfig LoadPreset(string name)
|
||||||
{
|
{
|
||||||
var results = _scenario.SearchTasks(name, null, null, 1, 1);
|
var results = _scenario.SearchTasks(name, null, null, 1, 1);
|
||||||
Assert.True(results.TotalCount > 0, $"预设想定未找到: {name}");
|
Assert.True(results.TotalCount > 0, $"预设想定未找到: {name}");
|
||||||
_taskId = results.Items[0].Id;
|
_scenarioId = results.Items[0].Id;
|
||||||
return _scenario.GetTaskDetail(_taskId)!;
|
return _scenario.GetTaskDetail(_scenarioId)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SimulationEngine RunPreset(string name, int maxFrames = 8000)
|
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),
|
Assert.All(eng.Events.Where(e => e.Type == SimEventType.MunitionLaunched),
|
||||||
e => Assert.Contains("空基", e.Description));
|
e => Assert.Contains("空基", e.Description));
|
||||||
var drone = eng.Drones[0];
|
var drone = eng.Drones[0];
|
||||||
var detail = _scenario.GetTaskDetail(_taskId)!;
|
var detail = _scenario.GetTaskDetail(_scenarioId)!;
|
||||||
var report = new ReportGenerator().Generate(detail, eng.Events.ToList(), drone.Status, eng.SimulationTime);
|
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}");
|
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);
|
VerifyAndExportReport(eng, drone);
|
||||||
@ -183,24 +183,24 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
// 场景A:无探测设备(上帝视角,从航路起点算)
|
// 场景A:无探测设备(上帝视角,从航路起点算)
|
||||||
var taskA = _scenario.CreateTask("无探测远航路", "");
|
var taskA = _scenario.CreateTask("无探测远航路", "");
|
||||||
_taskId = taskA.Id;
|
_scenarioId = taskA.Id;
|
||||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
||||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
_scenario.SaveTarget(_scenarioId, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default", TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
WaveId = "default", DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
|
||||||
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
||||||
});
|
});
|
||||||
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
new List<Waypoint>
|
new List<Waypoint>
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 },
|
new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
});
|
});
|
||||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>
|
||||||
{
|
{
|
||||||
TestData.All.FireUnits.First(f => f.Id == "ground-light").ToEquipmentDeployment(AerosolType.InertGas, 1, 8000, 0, 50),
|
TestData.All.FireUnits.First(f => f.Id == "ground-light").ToEquipmentDeployment(AerosolType.InertGas, 1, 8000, 0, 50),
|
||||||
});
|
});
|
||||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||||
var engA = RunSimulation(8000);
|
var engA = RunSimulation(8000);
|
||||||
float firstFireA = engA.Events.Where(e => e.Type == SimEventType.MunitionLaunched).Min(e => e.OccurredAt);
|
float firstFireA = engA.Events.Where(e => e.Type == SimEventType.MunitionLaunched).Min(e => e.OccurredAt);
|
||||||
|
|
||||||
@ -208,20 +208,20 @@ namespace CounterDrone.Core.Tests
|
|||||||
// 探测圆边界 X=4000 和 X=16000,无人机从 X=0 飞向 20000,在 X=4000 进入探测
|
// 探测圆边界 X=4000 和 X=16000,无人机从 X=0 飞向 20000,在 X=4000 进入探测
|
||||||
// planner 基于探测边界 X=4000 算到达时间(而非起点 X=0)
|
// planner 基于探测边界 X=4000 算到达时间(而非起点 X=0)
|
||||||
var taskB = _scenario.CreateTask("有探测远航路", "");
|
var taskB = _scenario.CreateTask("有探测远航路", "");
|
||||||
_taskId = taskB.Id;
|
_scenarioId = taskB.Id;
|
||||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
||||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
_scenario.SaveTarget(_scenarioId, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default", TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
WaveId = "default", DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
|
||||||
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
||||||
});
|
});
|
||||||
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
new List<Waypoint>
|
new List<Waypoint>
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 },
|
new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 },
|
||||||
});
|
});
|
||||||
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>
|
||||||
{
|
{
|
||||||
TestData.All.FireUnits.First(f => f.Id == "ground-light").ToEquipmentDeployment(AerosolType.InertGas, 1, 8000, 0, 50),
|
TestData.All.FireUnits.First(f => f.Id == "ground-light").ToEquipmentDeployment(AerosolType.InertGas, 1, 8000, 0, 50),
|
||||||
// 独立探测设备
|
// 独立探测设备
|
||||||
@ -234,7 +234,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
DetectionAccuracy = 50,
|
DetectionAccuracy = 50,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
|
||||||
var engB = RunSimulation(8000);
|
var engB = RunSimulation(8000);
|
||||||
float firstFireB = engB.Events.Where(e => e.Type == SimEventType.MunitionLaunched).Min(e => e.OccurredAt);
|
float firstFireB = engB.Events.Where(e => e.Type == SimEventType.MunitionLaunched).Min(e => e.OccurredAt);
|
||||||
|
|
||||||
|
|||||||
@ -30,8 +30,8 @@ namespace CounterDrone.Core.Tests
|
|||||||
""CriticalProbabilityThreshold"": 0.5,
|
""CriticalProbabilityThreshold"": 0.5,
|
||||||
""MaxInterceptProbability"": 0.95,
|
""MaxInterceptProbability"": 0.95,
|
||||||
""TypeCoefficient"": {
|
""TypeCoefficient"": {
|
||||||
""HighSpeed"": 4.0, ""FixedWing"": 2.0, ""Piston"": 2.0,
|
""HighSpeed"": 4.0, ""FixedWing"": 2.0,
|
||||||
""Rotor"": 1.0, ""Electric"": 1.0
|
""Rotor"": 1.0
|
||||||
},
|
},
|
||||||
""AmmoMatch"": {
|
""AmmoMatch"": {
|
||||||
""Electric"": ""InertGas"", ""Piston"": ""InertGas"", ""Jet"": ""ActiveMaterial""
|
""Electric"": ""InertGas"", ""Piston"": ""InertGas"", ""Jet"": ""ActiveMaterial""
|
||||||
@ -47,7 +47,8 @@ namespace CounterDrone.Core.Tests
|
|||||||
Assert.Equal(0.2f, cfg.CloudOverlapRatio);
|
Assert.Equal(0.2f, cfg.CloudOverlapRatio);
|
||||||
Assert.Equal(0.5f, cfg.CriticalProbabilityThreshold);
|
Assert.Equal(0.5f, cfg.CriticalProbabilityThreshold);
|
||||||
Assert.Equal(0.95f, cfg.MaxInterceptProbability);
|
Assert.Equal(0.95f, cfg.MaxInterceptProbability);
|
||||||
Assert.Equal(4.0f, cfg.TypeCoefficient[TargetType.HighSpeed]);
|
Assert.Equal(4.0f, cfg.TypeCoefficient[DroneType.HighSpeed]);
|
||||||
|
Assert.Equal(2.0f, cfg.TypeCoefficient[DroneType.FixedWing]);
|
||||||
Assert.Equal(AerosolType.InertGas, cfg.AmmoMatch[PowerType.Piston]);
|
Assert.Equal(AerosolType.InertGas, cfg.AmmoMatch[PowerType.Piston]);
|
||||||
Assert.Equal(AerosolType.ActiveMaterial, cfg.AmmoMatch[PowerType.Jet]);
|
Assert.Equal(AerosolType.ActiveMaterial, cfg.AmmoMatch[PowerType.Jet]);
|
||||||
}
|
}
|
||||||
@ -64,7 +65,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
// 缺 CriticalProbabilityThreshold
|
// 缺 CriticalProbabilityThreshold
|
||||||
WriteConfig(@"{ ""CloudOverlapRatio"": 0.2, ""MaxInterceptProbability"": 0.95,
|
WriteConfig(@"{ ""CloudOverlapRatio"": 0.2, ""MaxInterceptProbability"": 0.95,
|
||||||
""TypeCoefficient"": {""Piston"": 2.0}, ""AmmoMatch"": {""Piston"": ""InertGas""} }");
|
""TypeCoefficient"": {""FixedWing"": 2.0}, ""AmmoMatch"": {""Piston"": ""InertGas""} }");
|
||||||
Assert.Throws<InvalidDataException>(() => PlannerConfig.Load(_testDir));
|
Assert.Throws<InvalidDataException>(() => PlannerConfig.Load(_testDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +90,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
// 验证枚举用字符串(非数字)也能正确解析
|
// 验证枚举用字符串(非数字)也能正确解析
|
||||||
WriteConfig(ValidJson);
|
WriteConfig(ValidJson);
|
||||||
var cfg = PlannerConfig.Load(_testDir);
|
var cfg = PlannerConfig.Load(_testDir);
|
||||||
Assert.Equal(1.0f, cfg.TypeCoefficient[TargetType.Rotor]);
|
Assert.Equal(1.0f, cfg.TypeCoefficient[DroneType.Rotor]);
|
||||||
Assert.Equal(AerosolType.InertGas, cfg.AmmoMatch[PowerType.Electric]);
|
Assert.Equal(AerosolType.InertGas, cfg.AmmoMatch[PowerType.Electric]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,10 +33,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void Generate_ProducesReportContent()
|
public void Generate_ProducesReportContent()
|
||||||
{
|
{
|
||||||
var gen = new ReportGenerator();
|
var gen = new ReportGenerator();
|
||||||
var config = new TaskFullConfig
|
var config = new ScenarioConfig
|
||||||
{
|
{
|
||||||
Task = new SimTask { Name = "测试任务", TaskNumber = "SIM-001" },
|
Task = new Scenario { Name = "测试任务", TaskNumber = "SIM-001" },
|
||||||
Targets = new List<TargetConfig> { new TargetConfig { Quantity = 1 } },
|
Targets = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
|
||||||
Equipment = new List<EquipmentDeployment>
|
Equipment = new List<EquipmentDeployment>
|
||||||
{
|
{
|
||||||
new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 3 },
|
new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 3 },
|
||||||
|
|||||||
@ -34,10 +34,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void Generate_And_Get_ReturnsReport()
|
public void Generate_And_Get_ReturnsReport()
|
||||||
{
|
{
|
||||||
var config = new TaskFullConfig
|
var config = new ScenarioConfig
|
||||||
{
|
{
|
||||||
Task = new SimTask { Name = "Test", TaskNumber = "SIM-001" },
|
Task = new Scenario { Name = "Test", TaskNumber = "SIM-001" },
|
||||||
Targets = new List<TargetConfig> { new TargetConfig { Quantity = 1 } },
|
Targets = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
|
||||||
Equipment = new List<EquipmentDeployment>(),
|
Equipment = new List<EquipmentDeployment>(),
|
||||||
Cloud = new CloudDispersal(),
|
Cloud = new CloudDispersal(),
|
||||||
};
|
};
|
||||||
@ -56,10 +56,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void Delete_RemovesReport()
|
public void Delete_RemovesReport()
|
||||||
{
|
{
|
||||||
var config = new TaskFullConfig
|
var config = new ScenarioConfig
|
||||||
{
|
{
|
||||||
Task = new SimTask { Name = "Del", TaskNumber = "SIM-DEL" },
|
Task = new Scenario { Name = "Del", TaskNumber = "SIM-DEL" },
|
||||||
Targets = new List<TargetConfig> { new TargetConfig { Quantity = 1 } },
|
Targets = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
|
||||||
Equipment = new List<EquipmentDeployment>(),
|
Equipment = new List<EquipmentDeployment>(),
|
||||||
Cloud = new CloudDispersal(),
|
Cloud = new CloudDispersal(),
|
||||||
};
|
};
|
||||||
@ -72,10 +72,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void SearchReports_Keyword()
|
public void SearchReports_Keyword()
|
||||||
{
|
{
|
||||||
var cfg = new TaskFullConfig
|
var cfg = new ScenarioConfig
|
||||||
{
|
{
|
||||||
Task = new SimTask { Name = "城市防御", TaskNumber = "SIM-A" },
|
Task = new Scenario { Name = "城市防御", TaskNumber = "SIM-A" },
|
||||||
Targets = new List<TargetConfig> { new TargetConfig { Quantity = 1 } },
|
Targets = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
|
||||||
Equipment = new List<EquipmentDeployment>(),
|
Equipment = new List<EquipmentDeployment>(),
|
||||||
Cloud = new CloudDispersal(),
|
Cloud = new CloudDispersal(),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -22,42 +22,42 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void Dispose() { _db?.Close(); if (Directory.Exists(_testDir)) Directory.Delete(_testDir, true); }
|
public void Dispose() { _db?.Close(); if (Directory.Exists(_testDir)) Directory.Delete(_testDir, true); }
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TargetConfig_DeleteByTaskId_RemovesAll()
|
public void DroneProfile_DeleteByScenarioId_RemovesAll()
|
||||||
{
|
{
|
||||||
var repo = new TargetConfigRepository(_db);
|
var repo = new DroneProfileRepository(_db);
|
||||||
repo.Insert(new TargetConfig { Id = "t1", TaskId = "taskA", Quantity = 1 });
|
repo.Insert(new DroneProfile { Id = "t1", ScenarioId = "taskA", Quantity = 1 });
|
||||||
repo.Insert(new TargetConfig { Id = "t2", TaskId = "taskA", Quantity = 2 });
|
repo.Insert(new DroneProfile { Id = "t2", ScenarioId = "taskA", Quantity = 2 });
|
||||||
repo.Insert(new TargetConfig { Id = "t3", TaskId = "taskB", Quantity = 1 });
|
repo.Insert(new DroneProfile { Id = "t3", ScenarioId = "taskB", Quantity = 1 });
|
||||||
|
|
||||||
repo.DeleteByTaskId("taskA");
|
repo.DeleteByScenarioId("taskA");
|
||||||
|
|
||||||
Assert.Empty(repo.GetByTaskId("taskA"));
|
Assert.Empty(repo.GetByScenarioId("taskA"));
|
||||||
Assert.Single(repo.GetByTaskId("taskB"));
|
Assert.Single(repo.GetByScenarioId("taskB"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Waypoint_DeleteByTaskId_RemovesAll()
|
public void Waypoint_DeleteByScenarioId_RemovesAll()
|
||||||
{
|
{
|
||||||
var repo = new WaypointRepository(_db);
|
var repo = new WaypointRepository(_db);
|
||||||
repo.Insert(new Waypoint { TaskId = "w1", OrderIndex = 0 });
|
repo.Insert(new Waypoint { ScenarioId = "w1", OrderIndex = 0 });
|
||||||
repo.Insert(new Waypoint { TaskId = "w1", OrderIndex = 1 });
|
repo.Insert(new Waypoint { ScenarioId = "w1", OrderIndex = 1 });
|
||||||
repo.Insert(new Waypoint { TaskId = "w2", OrderIndex = 0 });
|
repo.Insert(new Waypoint { ScenarioId = "w2", OrderIndex = 0 });
|
||||||
|
|
||||||
repo.DeleteByTaskId("w1");
|
repo.DeleteByScenarioId("w1");
|
||||||
|
|
||||||
Assert.Empty(repo.GetByTaskId("w1"));
|
Assert.Empty(repo.GetByScenarioId("w1"));
|
||||||
Assert.Single(repo.GetByTaskId("w2"));
|
Assert.Single(repo.GetByScenarioId("w2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void EquipmentDeployment_DeleteByTaskId_Works()
|
public void EquipmentDeployment_DeleteByScenarioId_Works()
|
||||||
{
|
{
|
||||||
var repo = new EquipmentDeploymentRepository(_db);
|
var repo = new EquipmentDeploymentRepository(_db);
|
||||||
repo.Insert(new EquipmentDeployment { Id = "e1", TaskId = "eqA" });
|
repo.Insert(new EquipmentDeployment { Id = "e1", ScenarioId = "eqA" });
|
||||||
repo.Insert(new EquipmentDeployment { Id = "e2", TaskId = "eqA" });
|
repo.Insert(new EquipmentDeployment { Id = "e2", ScenarioId = "eqA" });
|
||||||
|
|
||||||
repo.DeleteByTaskId("eqA");
|
repo.DeleteByScenarioId("eqA");
|
||||||
Assert.Empty(repo.GetByTaskId("eqA"));
|
Assert.Empty(repo.GetByScenarioId("eqA"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,10 +24,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
var dbManager = new DatabaseManager(paths);
|
var dbManager = new DatabaseManager(paths);
|
||||||
_db = dbManager.OpenMainDb();
|
_db = dbManager.OpenMainDb();
|
||||||
_service = new ScenarioService(
|
_service = new ScenarioService(
|
||||||
new SimTaskRepository(_db),
|
new ScenarioRepository(_db),
|
||||||
new CombatSceneRepository(_db),
|
new CombatSceneRepository(_db),
|
||||||
new ControlZoneRepository(_db),
|
new ControlZoneRepository(_db),
|
||||||
new TargetConfigRepository(_db),
|
new DroneProfileRepository(_db),
|
||||||
new EquipmentDeploymentRepository(_db),
|
new EquipmentDeploymentRepository(_db),
|
||||||
new CloudDispersalRepository(_db),
|
new CloudDispersalRepository(_db),
|
||||||
new RoutePlanRepository(_db),
|
new RoutePlanRepository(_db),
|
||||||
@ -101,10 +101,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
Temperature = 25.0,
|
Temperature = 25.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
_service.SaveTarget(task.Id, new TargetConfig
|
_service.SaveTarget(task.Id, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
TargetType = (int)TargetType.FixedWing,
|
DroneType = (int)DroneType.FixedWing,
|
||||||
Quantity = 3,
|
Quantity = 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -199,10 +199,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void SaveTarget_SavesCorrectly()
|
public void SaveTarget_SavesCorrectly()
|
||||||
{
|
{
|
||||||
var task = _service.CreateTask("Target Test", "");
|
var task = _service.CreateTask("Target Test", "");
|
||||||
_service.SaveTarget(task.Id, new TargetConfig
|
_service.SaveTarget(task.Id, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
TargetType = (int)TargetType.Piston,
|
DroneType = (int)DroneType.FixedWing,
|
||||||
Quantity = 5,
|
Quantity = 5,
|
||||||
PowerType = (int)PowerType.Piston,
|
PowerType = (int)PowerType.Piston,
|
||||||
Wingspan = 2.5,
|
Wingspan = 2.5,
|
||||||
@ -212,7 +212,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
|
|
||||||
var detail = _service.GetTaskDetail(task.Id);
|
var detail = _service.GetTaskDetail(task.Id);
|
||||||
var target = detail.Targets[0];
|
var target = detail.Targets[0];
|
||||||
Assert.Equal((int)TargetType.Piston, target.TargetType);
|
Assert.Equal((int)DroneType.FixedWing, target.DroneType);
|
||||||
Assert.Equal(5, target.Quantity);
|
Assert.Equal(5, target.Quantity);
|
||||||
Assert.Equal(120.0, target.TypicalSpeed);
|
Assert.Equal(120.0, target.TypicalSpeed);
|
||||||
}
|
}
|
||||||
@ -392,10 +392,10 @@ namespace CounterDrone.Core.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 4. 步骤2:目标配置
|
// 4. 步骤2:目标配置
|
||||||
_service.SaveTarget(task.Id, new TargetConfig
|
_service.SaveTarget(task.Id, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
TargetType = (int)TargetType.HighSpeed,
|
DroneType = (int)DroneType.HighSpeed,
|
||||||
Quantity = 2,
|
Quantity = 2,
|
||||||
PowerType = (int)PowerType.Jet,
|
PowerType = (int)PowerType.Jet,
|
||||||
TypicalSpeed = 300.0,
|
TypicalSpeed = 300.0,
|
||||||
|
|||||||
@ -19,7 +19,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
private readonly SimulationEngine _engine;
|
private readonly SimulationEngine _engine;
|
||||||
private readonly IScenarioService _scenarioService;
|
private readonly IScenarioService _scenarioService;
|
||||||
private readonly SQLite.SQLiteConnection _mainDb;
|
private readonly SQLite.SQLiteConnection _mainDb;
|
||||||
private string _taskId = string.Empty;
|
private string _scenarioId = string.Empty;
|
||||||
|
|
||||||
public SimulationEngineTests()
|
public SimulationEngineTests()
|
||||||
{
|
{
|
||||||
@ -29,8 +29,8 @@ namespace CounterDrone.Core.Tests
|
|||||||
_mainDb = dbManager.OpenMainDb();
|
_mainDb = dbManager.OpenMainDb();
|
||||||
|
|
||||||
_scenarioService = new ScenarioService(
|
_scenarioService = new ScenarioService(
|
||||||
new SimTaskRepository(_mainDb), new CombatSceneRepository(_mainDb),
|
new ScenarioRepository(_mainDb), new CombatSceneRepository(_mainDb),
|
||||||
new ControlZoneRepository(_mainDb), new TargetConfigRepository(_mainDb),
|
new ControlZoneRepository(_mainDb), new DroneProfileRepository(_mainDb),
|
||||||
new EquipmentDeploymentRepository(_mainDb), new CloudDispersalRepository(_mainDb),
|
new EquipmentDeploymentRepository(_mainDb), new CloudDispersalRepository(_mainDb),
|
||||||
new RoutePlanRepository(_mainDb), new WaypointRepository(_mainDb));
|
new RoutePlanRepository(_mainDb), new WaypointRepository(_mainDb));
|
||||||
|
|
||||||
@ -48,17 +48,17 @@ namespace CounterDrone.Core.Tests
|
|||||||
private void SetupScenario()
|
private void SetupScenario()
|
||||||
{
|
{
|
||||||
var task = _scenarioService.CreateTask("Test", "");
|
var task = _scenarioService.CreateTask("Test", "");
|
||||||
_taskId = task.Id;
|
_scenarioId = task.Id;
|
||||||
_scenarioService.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
_scenarioService.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
|
||||||
_scenarioService.SaveTarget(_taskId, new TargetConfig
|
_scenarioService.SaveTarget(_scenarioId, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "default",
|
WaveId = "default",
|
||||||
TargetType = (int)TargetType.Piston,
|
DroneType = (int)DroneType.FixedWing,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
PowerType = (int)PowerType.Piston,
|
PowerType = (int)PowerType.Piston,
|
||||||
TypicalSpeed = 60,
|
TypicalSpeed = 60,
|
||||||
});
|
});
|
||||||
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
_scenarioService.SaveDeployment(_scenarioId, new List<EquipmentDeployment>
|
||||||
{
|
{
|
||||||
new EquipmentDeployment
|
new EquipmentDeployment
|
||||||
{
|
{
|
||||||
@ -68,7 +68,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
AerosolType = (int)AerosolType.InertGas, MunitionCount = 1, Cooldown = 5,
|
AerosolType = (int)AerosolType.InertGas, MunitionCount = 1, Cooldown = 5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
_scenarioService.SaveCloudDispersal(_taskId, new CloudDispersal
|
_scenarioService.SaveCloudDispersal(_scenarioId, new CloudDispersal
|
||||||
{
|
{
|
||||||
AerosolType = (int)AerosolType.InertGas,
|
AerosolType = (int)AerosolType.InertGas,
|
||||||
DisperseHeight = 300,
|
DisperseHeight = 300,
|
||||||
@ -79,7 +79,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
PositionZ = 0,
|
PositionZ = 0,
|
||||||
PositionMode = (int)PositionMode.AlgorithmRecommended,
|
PositionMode = (int)PositionMode.AlgorithmRecommended,
|
||||||
});
|
});
|
||||||
_scenarioService.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
_scenarioService.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
new List<Waypoint>
|
new List<Waypoint>
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },
|
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },
|
||||||
@ -91,7 +91,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void Initialize_LoadsEntities()
|
public void Initialize_LoadsEntities()
|
||||||
{
|
{
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_scenarioId);
|
||||||
Assert.Single(_engine.Drones);
|
Assert.Single(_engine.Drones);
|
||||||
Assert.Equal(SimulationState.Running, _engine.State);
|
Assert.Equal(SimulationState.Running, _engine.State);
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void Tick_DroneMoves()
|
public void Tick_DroneMoves()
|
||||||
{
|
{
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_scenarioId);
|
||||||
_engine.Tick(1f / 20f);
|
_engine.Tick(1f / 20f);
|
||||||
Assert.True(_engine.Drones[0].PosX > 0);
|
Assert.True(_engine.Drones[0].PosX > 0);
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void PauseAndResume()
|
public void PauseAndResume()
|
||||||
{
|
{
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_scenarioId);
|
||||||
_engine.Pause();
|
_engine.Pause();
|
||||||
Assert.Equal(SimulationState.Paused, _engine.State);
|
Assert.Equal(SimulationState.Paused, _engine.State);
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void Stop_ClosesGracefully()
|
public void Stop_ClosesGracefully()
|
||||||
{
|
{
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_scenarioId);
|
||||||
_engine.Tick(1f / 20f);
|
_engine.Tick(1f / 20f);
|
||||||
_engine.Stop();
|
_engine.Stop();
|
||||||
Assert.Equal(SimulationState.Stopped, _engine.State);
|
Assert.Equal(SimulationState.Stopped, _engine.State);
|
||||||
@ -134,11 +134,11 @@ namespace CounterDrone.Core.Tests
|
|||||||
public void FrameData_IsWritten()
|
public void FrameData_IsWritten()
|
||||||
{
|
{
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_scenarioId);
|
||||||
for (int i = 0; i < 10; i++) _engine.Tick(1f / 20f);
|
for (int i = 0; i < 10; i++) _engine.Tick(1f / 20f);
|
||||||
_engine.Stop(); // 触发 Flush
|
_engine.Stop(); // 触发 Flush
|
||||||
|
|
||||||
var framePath = Path.Combine(_testDir, "frames", $"{_taskId}.db");
|
var framePath = Path.Combine(_testDir, "frames", $"{_scenarioId}.db");
|
||||||
Assert.True(File.Exists(framePath));
|
Assert.True(File.Exists(framePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,19 +149,19 @@ namespace CounterDrone.Core.Tests
|
|||||||
private void SetupScenarioWithDetection()
|
private void SetupScenarioWithDetection()
|
||||||
{
|
{
|
||||||
var task = _scenarioService.CreateTask("Detect", "");
|
var task = _scenarioService.CreateTask("Detect", "");
|
||||||
_taskId = task.Id;
|
_scenarioId = task.Id;
|
||||||
_scenarioService.SaveScene(_taskId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
_scenarioService.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
||||||
_scenarioService.SaveTarget(_taskId, new TargetConfig
|
_scenarioService.SaveTarget(_scenarioId, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "w1",
|
WaveId = "w1",
|
||||||
TargetType = (int)TargetType.Piston,
|
DroneType = (int)DroneType.FixedWing,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
PowerType = (int)PowerType.Piston,
|
PowerType = (int)PowerType.Piston,
|
||||||
TypicalSpeed = 60,
|
TypicalSpeed = 60,
|
||||||
TypicalAltitude = 300,
|
TypicalAltitude = 300,
|
||||||
});
|
});
|
||||||
// 探测设备在 (5000, 0, 0),雷达 3000m,无角度/高度限制
|
// 探测设备在 (5000, 0, 0),雷达 3000m,无角度/高度限制
|
||||||
_scenarioService.SaveDeployment(_taskId, new List<EquipmentDeployment>
|
_scenarioService.SaveDeployment(_scenarioId, new List<EquipmentDeployment>
|
||||||
{
|
{
|
||||||
new EquipmentDeployment
|
new EquipmentDeployment
|
||||||
{
|
{
|
||||||
@ -177,7 +177,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
AerosolType = (int)AerosolType.InertGas, MunitionCount = 1, Cooldown = 5,
|
AerosolType = (int)AerosolType.InertGas, MunitionCount = 1, Cooldown = 5,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
_scenarioService.SaveCloudDispersal(_taskId, new CloudDispersal
|
_scenarioService.SaveCloudDispersal(_scenarioId, new CloudDispersal
|
||||||
{
|
{
|
||||||
AerosolType = (int)AerosolType.InertGas,
|
AerosolType = (int)AerosolType.InertGas,
|
||||||
DisperseHeight = 300,
|
DisperseHeight = 300,
|
||||||
@ -186,7 +186,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
PositionX = 1000, PositionY = 300, PositionZ = 0,
|
PositionX = 1000, PositionY = 300, PositionZ = 0,
|
||||||
PositionMode = (int)PositionMode.AlgorithmRecommended,
|
PositionMode = (int)PositionMode.AlgorithmRecommended,
|
||||||
});
|
});
|
||||||
_scenarioService.SaveRoute(_taskId, "w1", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
_scenarioService.SaveRoute(_scenarioId, "w1", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||||
new List<Waypoint>
|
new List<Waypoint>
|
||||||
{
|
{
|
||||||
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Speed = 60 },
|
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Speed = 60 },
|
||||||
@ -199,7 +199,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
SetupScenarioWithDetection();
|
SetupScenarioWithDetection();
|
||||||
_engine.SetFireSchedule(new List<FireEvent>()); // 不发射弹药
|
_engine.SetFireSchedule(new List<FireEvent>()); // 不发射弹药
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_scenarioId);
|
||||||
|
|
||||||
// 无人机起点 X=0,探测设备在 X=5000 半径 3000 → 探测边界在 X=2000
|
// 无人机起点 X=0,探测设备在 X=5000 半径 3000 → 探测边界在 X=2000
|
||||||
// 速度 60 km/h = 16.67 m/s,到达 X=2000 约需 120 秒
|
// 速度 60 km/h = 16.67 m/s,到达 X=2000 约需 120 秒
|
||||||
@ -224,7 +224,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
// 探测设备在 X=5000 半径 1000 → 入口 X=4000,出口 X=6000
|
// 探测设备在 X=5000 半径 1000 → 入口 X=4000,出口 X=6000
|
||||||
var task = _scenarioService.CreateTask("Reenter", "");
|
var task = _scenarioService.CreateTask("Reenter", "");
|
||||||
_scenarioService.SaveScene(task.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
_scenarioService.SaveScene(task.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
||||||
_scenarioService.SaveTarget(task.Id, new TargetConfig
|
_scenarioService.SaveTarget(task.Id, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "w1", Quantity = 1, TypicalSpeed = 120, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
|
WaveId = "w1", Quantity = 1, TypicalSpeed = 120, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
|
||||||
});
|
});
|
||||||
@ -259,7 +259,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
{
|
{
|
||||||
// 使用普通的 SetupScenario(无探测设备)
|
// 使用普通的 SetupScenario(无探测设备)
|
||||||
SetupScenario();
|
SetupScenario();
|
||||||
_engine.Initialize(_taskId);
|
_engine.Initialize(_scenarioId);
|
||||||
|
|
||||||
Assert.Empty(_engine.DetectionEntities);
|
Assert.Empty(_engine.DetectionEntities);
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ namespace CounterDrone.Core.Tests
|
|||||||
// D1 边界 X=1000,D2 边界 X=3000 → D1 更早发现
|
// D1 边界 X=1000,D2 边界 X=3000 → D1 更早发现
|
||||||
var task = _scenarioService.CreateTask("Fuse", "");
|
var task = _scenarioService.CreateTask("Fuse", "");
|
||||||
_scenarioService.SaveScene(task.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
_scenarioService.SaveScene(task.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
||||||
_scenarioService.SaveTarget(task.Id, new TargetConfig
|
_scenarioService.SaveTarget(task.Id, new DroneProfile
|
||||||
{
|
{
|
||||||
WaveId = "w1", Quantity = 1, TypicalSpeed = 60, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
|
WaveId = "w1", Quantity = 1, TypicalSpeed = 60, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user