重命名核心模型:TargetConfig→DroneProfile, SimTask→Scenario, TaskFullConfig→ScenarioConfig, TaskId→ScenarioId

- DroneType 枚举清理:移除 Electric/Piston,重编号 HighSpeed=2
- planner_config.json / defaults.json 同步迁移
- 238 单元测试全部通过
This commit is contained in:
tian 2026-06-18 15:02:58 +08:00
parent def3996f4f
commit ca0ce8aa52
66 changed files with 508 additions and 512 deletions

View File

@ -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`

View File

@ -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": [

View File

@ -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",

View File

@ -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 MB30min 仿真) │ ├── {scenarioId_001}.db ← 单任务 ~161 MB30min 仿真)
│ └── {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.dbSimFrameRecord 存储在各任务的独立分库 frames/{taskId}.db 中。 > **注意**SimEvent 和 SimulationReport 存储在主库 main.dbSimFrameRecord 存储在各任务的独立分库 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 渲染毁伤效果(尾焰异常、姿态失控、坠落)
- 当前位置、速度、朝向 - 当前位置、速度、朝向
- HP0~1 归一化) - HP0~1 归一化)
- DamageStageNormal → EngineAnomaly → AttitudeLoss → Destroyed - DamageStageNormal → 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
│ ├── TargetConfigWaveId=A类型=活塞,数量=3 │ ├── DroneProfileWaveId=A类型=活塞,数量=3
│ ├── RoutePlanTaskId + WaveId=A航路 A北→南 │ ├── RoutePlanScenarioId + WaveId=A航路 A北→南
│ └── Waypoint[] │ └── Waypoint[]
├── 批次 B喷气×2 ← WaveId ├── 批次 B喷气×2 ← WaveId
│ ├── TargetConfigWaveId=B类型=高速,数量=2 │ ├── DroneProfileWaveId=B类型=高速,数量=2
│ ├── RoutePlanTaskId + WaveId=B航路 B西→东 │ ├── RoutePlanScenarioId + WaveId=B航路 B西→东
│ └── Waypoint[] │ └── Waypoint[]
└── 批次 C旋翼×5 ← WaveId └── 批次 C旋翼×5 ← WaveId
├── TargetConfigWaveId=C类型=旋翼,数量=5 ├── DroneProfileWaveId=C类型=旋翼,数量=5
├── RoutePlanTaskId + WaveId=C航路 C ├── RoutePlanScenarioId + 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 参数
``` ```
### 报告 ### 报告

View File

@ -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>

View File

@ -39,7 +39,7 @@
## 数据流 ## 数据流
``` ```
想定配置EquipmentDeployment + TargetConfig + RoutePlan + CombatScene 想定配置EquipmentDeployment + DroneProfile + RoutePlan + CombatScene
SimulationEngine.Initialize() SimulationEngine.Initialize()

View File

@ -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 测试启用

View File

@ -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)
``` ```

View File

@ -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)

View File

@ -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;

View File

@ -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();

View File

@ -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
{ {

View File

@ -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);
} }

View File

@ -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);
} }

View File

@ -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;

View File

@ -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();

View File

@ -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");
} }
} }
} }

View File

@ -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,

View File

@ -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(),

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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>

View File

@ -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; }

View File

@ -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;

View File

@ -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;

View File

@ -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);
} }

View 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);
}
}
}

View File

@ -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);
} }

View File

@ -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);
} }
} }
} }

View File

@ -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));

View File

@ -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);
}
}
}

View File

@ -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);
} }

View File

@ -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);

View File

@ -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);
} }
} }

View File

@ -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

View File

@ -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"),

View File

@ -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");

View File

@ -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;

View File

@ -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);
} }

View File

@ -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)

View File

@ -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 =====");

View File

@ -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

View File

@ -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);
} }

View File

@ -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()

View File

@ -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;
// 定位相机 // 定位相机

View File

@ -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);

View File

@ -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": [

View File

@ -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);
} }

View File

@ -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));
} }

View File

@ -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>

View File

@ -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,

View File

@ -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++)

View File

@ -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();
} }

View File

@ -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);

View File

@ -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]);
} }
} }

View File

@ -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 },

View File

@ -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(),
}; };

View File

@ -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"));
} }
} }
} }

View File

@ -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,

View File

@ -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=1000D2 边界 X=3000 → D1 更早发现 // D1 边界 X=1000D2 边界 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,
}); });