DroneProfile→ScenarioDrone / EquipmentDeployment→ScenarioUnit 重命名

- 模型表: ScenarioDrone(原DroneProfile) / ScenarioUnit(原EquipmentDeployment)
- ScenarioConfig.Equipment→Units
- 数据库旧表清理 + scenariosVersion 4→5
- 238 测试全过
This commit is contained in:
tian 2026-06-18 16:36:25 +08:00
parent 7413077514
commit 537671ab3a
40 changed files with 237 additions and 235 deletions

View File

@ -9,7 +9,7 @@
- **空基/地基物理统一**:去除 DefensePlanner 所有平台类型分支,统一用 `InterceptCalculator.Compute`(删 `ComputeHorizontal`),统一用 `MuzzleVelocity`(删 `CruiseSpeed` 分支)
- **抛物线选解策略**:两解逐一计算距离匹配 + `fireTime>0` 可行性,选最早拦截的解(非简单选高角/低角)
- **`TargetType``DroneType`**:删 `Electric`/`Piston`,重编号 `HighSpeed=2``TargetConfig.DroneType` 属性同步改名
- **`TargetConfig``DroneProfile`**:核心模型重命名
- **`TargetConfig``ScenarioDrone`**:核心模型重命名
- **`SimTask``Scenario`**:想定主表重命名,`ScenarioNumber` 取代 `TaskNumber`
- **`TaskFullConfig``ScenarioConfig`**:聚合配置重命名,`Task` 属性 → `Info`
- **`TaskId``ScenarioId`**:所有 FK 重命名
@ -17,9 +17,9 @@
### Added — 模型业务属性
- **`DroneProfile` / `DroneSpec`**+`Model`(型号)、+`Description`(描述/用途)
- **`ScenarioDrone` / `DroneSpec`**+`Model`(型号)、+`Description`(描述/用途)
- **`Scenario`**+`Description`(想定描述)
- **`EquipmentDeployment` / `FireUnitSpec`**+`Description`
- **`ScenarioUnit` / `FireUnitSpec`**+`Description`
- **`ReportManager.Export(reportId, outputDir)`**:支持指定导出路径
### Changed — 数据库迁移
@ -96,7 +96,7 @@
- **EarliestDetection 采样法**:线段-圆求交改为沿航路采样步长≤50m`IsInCoverage`
- **DetectionEntity**:运行时探测实体 + per-drone 状态机Undetected ⇄ DetectedTick 第5步扫描
- **实时探测事件**`OnTargetDetected` / `SimEventType.TargetDetected` / `PlanningFailed`
- **EquipmentDeployment + DetectionSource**+4 个 3D 球冠字段MinElevation/MaxElevation/MinDetectAlt/MaxDetectAlt
- **ScenarioUnit + DetectionSource**+4 个 3D 球冠字段MinElevation/MaxElevation/MinDetectAlt/MaxDetectAlt
- **默认数据全部加入 3D 球冠参数**(地基 -5°~85°、空基 -80°~30°
### Added — Planner 诊断 + 拦截点计算
@ -109,7 +109,7 @@
### Changed — 消除硬编码
- **速度从 waypoint.Speed 读取**`DroneEntity.CruiseSpeed` / `GetDroneSpeedKph` 不再用 `DroneProfile.TypicalSpeed`
- **速度从 waypoint.Speed 读取**`DroneEntity.CruiseSpeed` / `GetDroneSpeedKph` 不再用 `ScenarioDrone.TypicalSpeed`
- **`AmmunitionSpec` +`Phase2Duration`**30s`CloudExpansionModel` 不再写死 `30f`
- **`GetArrivalTime` 基于 DetectArc**:从探测点到中点算到达时间(而非航路起点)
- **TestData 改为从 seeded 数据库读取**(不再内嵌 JSON 重复 defaults.json
@ -137,7 +137,7 @@
### Changed — 数据模型重命名
- **`GroupId``WaveId`**DroneProfile 四表的编组外键重命名为批次外键
- **`GroupId``WaveId`**ScenarioDrone 四表的编组外键重命名为批次外键
- **RoutePlan 索引重命名**`(ScenarioId, GroupId)` → `(ScenarioId, WaveId)`
- **RoutePlanRepository / WaypointRepository**`GetByScenarioAndGroup` → `GetByScenarioAndWave`
- **IScenarioService.SaveRoute**`groupId` 参数 → `waveId`
@ -173,8 +173,8 @@
- **DetectionCalculator 静态工具类**:探测能力评估的唯一实现(与 Kinematics/RouteGeometry 同范式)。光电受 Visibility 衰减(`有效=基准×min(1,Visibility/基准)`),雷达/红外不受影响;统一信息网络找最早探测点(线段-圆求交)
- **探测源模型**DetectionSource独立探测设备 + 火力单元自带探测统一表达(雷达/光电/红外三距离 + 精度)
- **EquipmentDeployment 扩展**:删单一 `DetectionRadius`,加 `RadarRange/EORange/IRange/DetectionAccuracy` 四字段
- **FireUnit 探测字段激活**BuildFireUnits 从 EquipmentDeployment 读取并赋值(原为死代码)
- **ScenarioUnit 扩展**:删单一 `DetectionRadius`,加 `RadarRange/EORange/IRange/DetectionAccuracy` 四字段
- **FireUnit 探测字段激活**BuildFireUnits 从 ScenarioUnit 读取并赋值(原为死代码)
- **planner 接口扩展**`IDefensePlanner.Plan` 加第 4 参数 `detectionSources`
- **PlannerConfig 加 DefaultDetectionAccuracy**(无探测时回退精度)
- 单元测试DetectionCalculator 13 项、探测驱动规划 2 项

View File

@ -6,7 +6,7 @@
> **变更**V14 新增 DataService 基础数据 CRUD + FrameDataStore 内存回放架构
> **Unity 版本**22.3.62
> **.NET 版本**.NET Standard 2.1
> **变更**V13 探测行为决策固化——5 项规则确认(回退 Undetected / 融合取最早-同刻取精度高 / 不影响 FireSchedule / 盲区仅可视化 / 3D 球冠);新增 14.2.1 三维球冠几何IsInCoverage+ 14.7 数据模型变更清单;EquipmentDeployment 表加 4 个 3D 探测字段DetectionSource 同步扩展
> **变更**V13 探测行为决策固化——5 项规则确认(回退 Undetected / 融合取最早-同刻取精度高 / 不影响 FireSchedule / 盲区仅可视化 / 3D 球冠);新增 14.2.1 三维球冠几何IsInCoverage+ 14.7 数据模型变更清单;ScenarioUnit 表加 4 个 3D 探测字段DetectionSource 同步扩展
> **历史变更**V12 探测双链路设计(静态规划+实时探测待实现V11 文档校准 + 第十三章性能设计V10 Group 表移除,批次+火力单元概念升级
---
@ -207,13 +207,13 @@ enum FormationMode { Single = 0, Formation = 1, Swarm = 2 }
// === 核心作战概念 ===
// 火力单元FireUnit能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。
// 包含探测(雷达/光电/红外)+ 打击(发射架/火炮)的完整闭环系统。
// 配置层 = EquipmentDeployment(LaunchPlatform),含探测字段 + 打击字段。
// 配置层 = ScenarioUnit(LaunchPlatform),含探测字段 + 打击字段。
// 独立探测节点DetectionNode只探测不打击的侦查节点如前沿警戒雷达
// 配置层 = EquipmentDeployment(Detection),仅含探测字段。
// 配置层 = ScenarioUnit(Detection),仅含探测字段。
// 两者都往统一信息网络送探测信息planner 基于融合后的探测边界规划。
// 无人机批次DroneWave有共同航路的一组无人机攻击方
// 批次关联航路但不独占(多个批次可共享同一条航路)。
// 配置层 = DroneProfile(含 WaveId) + RoutePlan(ScenarioId, WaveId) + Waypoints。
// 配置层 = ScenarioDrone(含 WaveId) + RoutePlan(ScenarioId, WaveId) + Waypoints。
// === 运行时 ===
enum EntityType { Drone = 0, Platform = 1, DetectionEquip = 2, Cloud = 3, Munition = 4 }
@ -266,10 +266,10 @@ CounterDroneBackend_Data/
Scenario — 仿真任务主表
CombatScene — 步骤1作战场景含24h时间、天气
ControlZone — 步骤1+:管控区域/电子围栏NEW
DroneProfile — 步骤2目标配置含 WaveId 关联批次)
EquipmentDeployment — 步骤3装备部署火力单元含探测+打击,或独立探测节点)
ScenarioDrone — 步骤2目标配置含 WaveId 关联批次)
ScenarioUnit — 步骤3装备部署火力单元含探测+打击,或独立探测节点)
CloudDispersal — 步骤4云团抛撒配置
RoutePlan — 步骤5航路规划1:N Scenario多批次批次关联但不独占航路FK 关联 DroneProfile.WaveId
RoutePlan — 步骤5航路规划1:N Scenario多批次批次关联但不独占航路FK 关联 ScenarioDrone.WaveId
Waypoint — 航路点
SimulationReport — 仿真报告
SimEvent — 仿真事件(时序图数据源)
@ -279,9 +279,9 @@ CounterDroneBackend_Data/
```
> **核心作战概念**
> - **火力单元FireUnit**:能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。包含探测(雷达/光电/红外)+ 打击(发射架/火炮)的完整闭环。配置层 = `EquipmentDeployment(LaunchPlatform)`。
> - **独立探测节点DetectionNode**:只探测不打击的侦查节点(如前沿警戒雷达)。配置层 = `EquipmentDeployment(Detection)`。
> - **无人机批次DroneWave**:有共同航路的一组无人机(攻击方)。批次关联航路但不独占(多个批次可共享同一条航路)。配置层 = `DroneProfile(WaveId)` + `RoutePlan(ScenarioId, WaveId)` + `Waypoints`
> - **火力单元FireUnit**:能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。包含探测(雷达/光电/红外)+ 打击(发射架/火炮)的完整闭环。配置层 = `ScenarioUnit(LaunchPlatform)`。
> - **独立探测节点DetectionNode**:只探测不打击的侦查节点(如前沿警戒雷达)。配置层 = `ScenarioUnit(Detection)`。
> - **无人机批次DroneWave**:有共同航路的一组无人机(攻击方)。批次关联航路但不独占(多个批次可共享同一条航路)。配置层 = `ScenarioDrone(WaveId)` + `RoutePlan(ScenarioId, WaveId)` + `Waypoints`
> - 原来的 `Group`(编组)表已移除:无人机编队概念升级为"批次",装备编组概念不再需要(火力单元是独立作战单位)。
### 4.3 业务表详细设计
@ -349,7 +349,7 @@ CounterDroneBackend_Data/
> 管控区域在场景设置(地形+天气)之后配置。无人机进入管控区域 → 触发 ZoneIntruded 事件 → 该无人机任务失败。
#### DroneProfile — 步骤2无人机批次参数
#### ScenarioDrone — 步骤2无人机批次参数
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
@ -373,7 +373,7 @@ CounterDroneBackend_Data/
| 固定翼 | 3.5 | 150 | 1000 |
| 高速目标 | 1.5 | 300 | 2000 |
#### EquipmentDeployment — 步骤3装备部署火力单元 + 独立探测节点)
#### ScenarioUnit — 步骤3装备部署火力单元 + 独立探测节点)
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
@ -468,10 +468,10 @@ CounterDroneBackend_Data/
#### ~~Group~~ — 编组(已移除)
> **概念升级**:原 Group 表的两种类型已分别升级:
> - `DroneFleet`(无人机编队)→ **无人机批次DroneWave**:由 `DroneProfile(WaveId)` + `RoutePlan(ScenarioId, WaveId)` + `Waypoints` 隐式表达。批次关联航路但不独占(多个批次可共享同一条航路)。
> - `DroneFleet`(无人机编队)→ **无人机批次DroneWave**:由 `ScenarioDrone(WaveId)` + `RoutePlan(ScenarioId, WaveId)` + `Waypoints` 隐式表达。批次关联航路但不独占(多个批次可共享同一条航路)。
> - `EquipmentGroup`(装备编组)→ **不再需要**。火力单元是独立作战的最小单位,不需要上层编组。
>
> 数据层:`Group` 表保留但不再主动使用(向后兼容)。`DroneProfile.WaveId` 语义为"批次 ID"。
> 数据层:`Group` 表保留但不再主动使用(向后兼容)。`ScenarioDrone.WaveId` 语义为"批次 ID"。
### 4.4 运行时表
@ -1395,8 +1395,8 @@ interface IScenarioService
// 步骤保存
void SaveScene(string scenarioId, CombatScene scene);
void SaveControlZones(string scenarioId, List<ControlZone> zones); // NEW
void SaveDroneProfile(string scenarioId, DroneProfile target);
void SaveDeployment(string scenarioId, List<EquipmentDeployment> equips); // 重构
void SaveScenarioDrone(string scenarioId, ScenarioDrone target);
void SaveDeployment(string scenarioId, List<ScenarioUnit> equips); // 重构
void SaveCloudDispersal(string scenarioId, CloudDispersal cloud);
void SaveRoute(string scenarioId, RoutePlan route, List<Waypoint> wps);
void UpdateStep(string scenarioId, int step);
@ -1457,17 +1457,17 @@ Scenario "城市防御演习"
├── CloudDispersal共享云团参数具体拦截方案由算法确定
├── 批次 A活塞×3 ← WaveId
│ ├── DroneProfileWaveId=A类型=活塞,数量=3
│ ├── ScenarioDroneWaveId=A类型=活塞,数量=3
│ ├── RoutePlanScenarioId + WaveId=A航路 A北→南
│ └── Waypoint[]
├── 批次 B喷气×2 ← WaveId
│ ├── DroneProfileWaveId=B类型=高速,数量=2
│ ├── ScenarioDroneWaveId=B类型=高速,数量=2
│ ├── RoutePlanScenarioId + WaveId=B航路 B西→东
│ └── Waypoint[]
└── 批次 C旋翼×5 ← WaveId
├── DroneProfileWaveId=C类型=旋翼,数量=5
├── ScenarioDroneWaveId=C类型=旋翼,数量=5
├── RoutePlanScenarioId + WaveId=C航路 C
└── Waypoint[]
```
@ -1518,7 +1518,7 @@ engine.SetFireSchedule(merged);
| 表 | 变更 |
|------|------|
| `RoutePlan` | PK 为 (ScenarioId, WaveId) 复合键 |
| `DroneProfile` | 不变(已有 WaveId |
| `ScenarioDrone` | 不变(已有 WaveId |
| `Waypoint` | 关联 `(ScenarioId, WaveId)` 定位航路 |
`IScenarioService``SaveRoute` 签名:
@ -1909,7 +1909,7 @@ FOR EACH 飞行无人机(维护"本帧已发现该目标的最优探测设备"
| 改动项 | 位置 | 内容 | 状态 |
|--------|------|------|------|
| **EquipmentDeployment 表加字段** | 4.3 | `MinElevation`/`MaxElevation`(度)、`MinDetectAlt`/`MaxDetectAlt`m均可空 | 待加列 |
| **ScenarioUnit 表加字段** | 4.3 | `MinElevation`/`MaxElevation`(度)、`MinDetectAlt`/`MaxDetectAlt`m均可空 | 待加列 |
| **DetectionSource 类加字段** | 14.5.1 | 同上 4 个三维几何字段 | 待加属性 |
| **DetectionCalculator.IsInCoverage** | 14.2.1 | 三维球冠判定(水平距离 + 俯仰角 + 高度门限planner 与实时探测共用 | 待实现 |
| **DetectionCalculator.EffectiveRange** | 14.2 | 现有签名不变返回综合水平距离基准3D 判定在其上层 | 已实现(基础) |
@ -1917,7 +1917,7 @@ FOR EACH 飞行无人机(维护"本帧已发现该目标的最优探测设备"
| **SimulationEngine 加 _detectionEntities** | 5.1 | Initialize 时从配置构建 `List<DetectionEntity>`Tick 第5步遍历 | 待实现 |
| **Tick 第5步实时探测** | 5.3 / 14.5.2 | 扫描 + 状态机 + 决策1/2/3/4 落地,触发 `TargetDetected` | 待实现 |
> **数据库迁移**`EquipmentDeployment` 新增 4 列均为 NULLABLE不影响存量数据`SimulationEngine.BuildDetectionSources` 读取新字段构造 `DetectionSource`(缺失时按"无角度/高度限制"的退化球冠处理,等价当前 2D 行为,保证平滑过渡)。
> **数据库迁移**`ScenarioUnit` 新增 4 列均为 NULLABLE不影响存量数据`SimulationEngine.BuildDetectionSources` 读取新字段构造 `DetectionSource`(缺失时按"无角度/高度限制"的退化球冠处理,等价当前 2D 行为,保证平滑过渡)。
---

View File

@ -59,7 +59,7 @@ class FireUnit {
```csharp
class DroneWave {
string WaveId; // 批次 ID
DroneProfile Profile; // 类型、数量、动力、翼展、速度、高度
ScenarioDrone Profile; // 类型、数量、动力、翼展、速度、高度
List<Waypoint> Waypoints; // 航路点
float ArrivalTime; // 预计算:到达防御区域中点的时间 s
}
@ -344,7 +344,7 @@ PlannerResult.Best.MergedSchedule
### 🔒 不变
- `Kinematics` / `GaussianPuffDispersion` / `DamageModelRouter`
- 所有 Repository 和数据模型(EquipmentDeployment 新增 GunCount/ChannelsPerGun/ChannelInterval
- 所有 Repository 和数据模型(ScenarioUnit 新增 GunCount/ChannelsPerGun/ChannelInterval
- `ScenarioService` 接口
---

View File

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

View File

@ -181,7 +181,7 @@ Phase 12 ✅ 空基/地基统一规划 + 编队轴 + LaneDivider
| 8.1.1 | 探测设备搜索逻辑 | ✅ 事前规划:`DetectionCalculator` 算统一信息网络最早探测点planner 基于探测边界算到达时间;天气衰减光电;精度影响散布。**实时探测设计完成**(总体架构设计第十四章),待 Phase 10 开发 |
| 8.1.2 | 蜂群运动模型 | ⬜ `FormationMode.Swarm` 枚举已定义,需差异化行为(随机扰动、个体差异) |
| 8.1.3 | 空基平台 + DefensePlanner | ✅ 五步规划引擎,通道模型,物理间隔错发,路径积分毁伤判定 |
| 8.1.4 | 预置典型目标库 | ⬜ 具体无人机型号 JSON 配置(如 DJI Mavic 3、Shahed-136 等),导入 `DroneProfile` 默认值 |
| 8.1.4 | 预置典型目标库 | ⬜ 具体无人机型号 JSON 配置(如 DJI Mavic 3、Shahed-136 等),导入 `ScenarioDrone` 默认值 |
| 8.1.5 | 毁伤曲线参数校准 | ✅ RequiredExposureSeconds 替代硬编码,密度阈值统一在引擎检查 |
| 8.1.6 | Fallback/default 清理 | ✅ 删除所有硬编码默认值和静默 fallback参数缺失即报错 |
@ -241,7 +241,7 @@ Phase 12 ✅ 空基/地基统一规划 + 编队轴 + LaneDivider
| # | 任务 | 预估 | 状态 | 说明 |
|---|------|------|------|------|
| T1.1 | EquipmentDeployment 加 4 列 | 0.5h | ✅ | `MinElevation`/`MaxElevation`/`MinDetectAlt`/`MaxDetectAlt`,均 NULLABLE |
| T1.1 | ScenarioUnit 加 4 列 | 0.5h | ✅ | `MinElevation`/`MaxElevation`/`MinDetectAlt`/`MaxDetectAlt`,均 NULLABLE |
| T1.2 | DetectionSource 类扩展 | 0.5h | ✅ | 同步加 4 个三维几何属性float.MaxValue=无限制,退化 2D |
| T1.3 | BuildDetectionSources 读取新字段 | 0.5h | ✅ | 缺失时退化球冠float.MaxValue保证存量数据平滑过渡 |

View File

@ -14,14 +14,14 @@ planner 是参谋,只能基于侦查信息制定方案。假设我方有统一
### 变更内容
1. **新增 DetectionCalculator**:探测能力评估的唯一实现。光电受 Visibility 衰减,雷达/红外不受影响;统一信息网络找最早探测点
2. **EquipmentDeployment 扩展**:删单一 DetectionRadius加 RadarRange/EORange/IRange/DetectionAccuracy
2. **ScenarioUnit 扩展**:删单一 DetectionRadius加 RadarRange/EORange/IRange/DetectionAccuracy
3. **FireUnit 探测字段激活**BuildFireUnits 赋值(原为死代码)
4. **planner 接口扩展**Plan 加第 4 参数 detectionSources到达时间基于探测边界
5. **SimulationEngine 构建探测源**:独立探测 + 火力单元自带探测统一传入 planner
## 影响范围
- [x] 接口变更IDefensePlanner.Plan 加参数;EquipmentDeployment 字段变更(删 DetectionRadius
- [x] 接口变更IDefensePlanner.Plan 加参数;ScenarioUnit 字段变更(删 DetectionRadius
- [ ] 数据库变更:新字段为 nullableSQLite 自动处理
- [ ] UI 变更
- [x] 文档变更:实施计划 8.1.1、CHANGELOG、VERSION

View File

@ -72,7 +72,7 @@ var scenarioId = demos.Items[0].Id;
// 方式 B自定义
var sc = scenario.CreateScenario("我的想定", "");
scenario.SaveScene(sc.Id, new CombatScene { WindSpeed = 5, WindDirection = (int)WindDirection.E });
scenario.SaveDroneProfile(sc.Id, new DroneProfile {
scenario.SaveScenarioDrone(sc.Id, new ScenarioDrone {
WaveId = "w1", Model = "Shahed-136", Description = "活塞巡飞弹",
Quantity = 1, DroneType = (int)DroneType.FixedWing,
TypicalSpeed = 200, TypicalAltitude = 500
@ -81,7 +81,7 @@ scenario.SaveRoute(sc.Id, "w1", new RoutePlan(), new List<Waypoint> {
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
new() { PosX = 10000, PosY = 500, PosZ = 0, Speed = 200 }
});
scenario.SaveDeployment(sc.Id, new List<EquipmentDeployment> {
scenario.SaveDeployment(sc.Id, new List<ScenarioUnit> {
new() { EquipmentRole = 1, PlatformType = 1, Quantity = 1,
PositionX = 5000, AerosolType = 0, MunitionCount = 8,
Description = "地面轻型平台" }
@ -150,7 +150,7 @@ mgr.DataService.GetAllEnvironments() / GetAllFormations() / GetAllRoutes()
// 想定 CRUD
mgr.CreateScenario(name, number) / mgr.DeleteScenario(id)
mgr.Search(kw, from, to, page, size)
mgr.SaveScene / SaveDroneProfile / SaveDeployment / SaveCloud / SaveRoute / UpdateStep
mgr.SaveScene / SaveScenarioDrone / SaveDeployment / SaveCloud / SaveRoute / UpdateStep
mgr.GetDetail(id) → ScenarioConfig
```
@ -223,8 +223,8 @@ replay.TotalFrames / replay.GetFrame(index)
|------|------|------|
| `Scenario` | `Scenario` | 想定任务 |
| `CombatScene` | `CombatScene` | 作战环境 |
| `DroneProfile` | `DroneProfile` | 无人机批次参数 |
| `EquipmentDeployment` | `EquipmentDeployment` | 装备部署 |
| `ScenarioDrone` | `ScenarioDrone` | 无人机批次参数 |
| `ScenarioUnit` | `ScenarioUnit` | 装备部署 |
| `CloudDispersal` | `CloudDispersal` | 云团抛撒配置 |
| `RoutePlan` | `RoutePlan` | 航路规划 |
| `Waypoint` | `Waypoint` | 航路点 |
@ -243,17 +243,17 @@ replay.TotalFrames / replay.GetFrame(index)
| 模型 | 属性 | 类型 | 说明 |
|------|------|------|------|
| `DroneProfile` | `Model` | string | 型号 |
| `DroneProfile` | `Description` | string | 描述/用途 |
| `DroneProfile` | `DroneType` | DroneType | Rotor / FixedWing / HighSpeed |
| `DroneProfile` | `PowerType` | PowerType | Electric / Piston / Jet |
| `DroneProfile` | `TypicalSpeed` | double | 典型速度 (km/h) |
| `DroneProfile` | `TypicalAltitude` | double | 典型高度 (m) |
| `ScenarioDrone` | `Model` | string | 型号 |
| `ScenarioDrone` | `Description` | string | 描述/用途 |
| `ScenarioDrone` | `DroneType` | DroneType | Rotor / FixedWing / HighSpeed |
| `ScenarioDrone` | `PowerType` | PowerType | Electric / Piston / Jet |
| `ScenarioDrone` | `TypicalSpeed` | double | 典型速度 (km/h) |
| `ScenarioDrone` | `TypicalAltitude` | double | 典型高度 (m) |
| `Scenario` | `ScenarioNumber` | string | 想定编号(唯一) |
| `Scenario` | `Description` | string | 想定描述 |
| `EquipmentDeployment` | `Description` | string | 部署备注 |
| `EquipmentDeployment` | `MuzzleVelocity` | double? | 初速 (m/s) |
| `EquipmentDeployment` | `CruiseSpeed` | double? | 巡航速度 (m/s),空基专用 |
| `ScenarioUnit` | `Description` | string | 部署备注 |
| `ScenarioUnit` | `MuzzleVelocity` | double? | 初速 (m/s) |
| `ScenarioUnit` | `CruiseSpeed` | double? | 巡航速度 (m/s),空基专用 |
| `DroneSpec` | `Model` | string | 型号 |
| `DroneSpec` | `Description` | string | 描述/用途 |
| `FireUnitSpec` | `Description` | string | 描述/用途 |

View File

@ -51,7 +51,7 @@ namespace CounterDrone.Core.Algorithms
public class DroneWave
{
public string WaveId { get; set; } = string.Empty;
public DroneProfile Profile { get; set; } = new();
public ScenarioDrone Profile { get; set; } = new();
public RoutePlan Route { get; set; } = new();
public List<Waypoint> Waypoints { get; set; } = new();

View File

@ -90,7 +90,7 @@ namespace CounterDrone.Core.Algorithms
// Step 1: 威胁指数
// ═══════════════════════════════════════════════
private static float CalcThreatIndex(PlannerConfig config, DroneProfile target)
private static float CalcThreatIndex(PlannerConfig config, ScenarioDrone target)
{
float typeCoef = config.TypeCoefficient.GetValueOrDefault((DroneType)target.DroneType, 1f);
float speedCoef = (float)target.TypicalSpeed / 60f;

View File

@ -82,34 +82,36 @@ namespace CounterDrone.Core
.FirstOrDefault(m => m.Key == "scenariosVersion")?.Value;
// 版本未变 → 跳过
if (currentVersion == "4") return;
if (currentVersion == "5") return;
var defaults = DefaultData.Load(_paths);
var scenario = new ScenarioService(
new ScenarioRepository(db), new CombatSceneRepository(db),
new ControlZoneRepository(db), new DroneProfileRepository(db),
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
new ControlZoneRepository(db), new ScenarioDroneRepository(db),
new ScenarioUnitRepository(db), new CloudDispersalRepository(db),
new RoutePlanRepository(db), new WaypointRepository(db));
DefaultScenarios.Seed(scenario, defaults);
db.InsertOrReplace(new MetaEntry { Key = "scenariosVersion", Value = "4" });
db.InsertOrReplace(new MetaEntry { Key = "scenariosVersion", Value = "5" });
}
private void CreateMainTables(SQLiteConnection db)
{
// 删除旧版本残留表V1.6 重命名)
// 删除旧版本残留表
db.Execute("DROP TABLE IF EXISTS SimTask");
db.Execute("DROP TABLE IF EXISTS TargetConfig");
db.Execute("DROP TABLE IF EXISTS SimulationReport"); // TaskNumber→ScenarioNumber
db.Execute("DROP TABLE IF EXISTS DroneProfile");
db.Execute("DROP TABLE IF EXISTS EquipmentDeployment");
db.Execute("DROP TABLE IF EXISTS SimulationReport");
db.CreateTable<ModelInfo>();
db.CreateTable<AmmunitionSpec>();
db.CreateTable<Scenario>();
db.CreateTable<CombatScene>();
db.CreateTable<ControlZone>();
db.CreateTable<DroneProfile>();
db.CreateTable<EquipmentDeployment>();
db.CreateTable<ScenarioDrone>();
db.CreateTable<ScenarioUnit>();
db.CreateTable<CloudDispersal>();
db.CreateTable<RoutePlan>();
db.CreateIndex("RoutePlan", new[] { "ScenarioId", "WaveId" }, true);
@ -124,8 +126,8 @@ namespace CounterDrone.Core
db.CreateIndex("Scenario", "ScenarioNumber");
db.CreateIndex("ControlZone", "ScenarioId");
db.CreateIndex("DroneProfile", "ScenarioId");
db.CreateIndex("EquipmentDeployment", "ScenarioId");
db.CreateIndex("ScenarioDrone", "ScenarioId");
db.CreateIndex("ScenarioUnit", "ScenarioId");
db.CreateIndex("Waypoint", "ScenarioId");
db.CreateIndex("SimulationReport", "ScenarioId");
}

View File

@ -125,10 +125,10 @@ namespace CounterDrone.Core
public double? MinDetectAlt { get; set; }
public double? MaxDetectAlt { get; set; }
public EquipmentDeployment ToEquipmentDeployment(AerosolType ammoType, int quantity,
public ScenarioUnit ToScenarioUnit(AerosolType ammoType, int quantity,
double posX, double posY, double posZ)
{
return new EquipmentDeployment
return new ScenarioUnit
{
EquipmentRole = (int)Models.EquipmentRole.LaunchPlatform,
PlatformType = PlatformType,
@ -171,9 +171,9 @@ namespace CounterDrone.Core
public double TypicalSpeed { get; set; }
public double TypicalAltitude { get; set; }
public DroneProfile ToDroneProfile(string waveId = "default")
public ScenarioDrone ToScenarioDrone(string waveId = "default")
{
return new DroneProfile
return new ScenarioDrone
{
WaveId = waveId,
DroneType = DroneType,
@ -205,9 +205,9 @@ namespace CounterDrone.Core
public double? MinDetectAlt { get; set; }
public double? MaxDetectAlt { get; set; }
public EquipmentDeployment ToEquipmentDeployment(double posX, double posY, double posZ)
public ScenarioUnit ToScenarioUnit(double posX, double posY, double posZ)
{
return new EquipmentDeployment
return new ScenarioUnit
{
EquipmentRole = (int)Models.EquipmentRole.Detection,
Quantity = 1,

View File

@ -37,10 +37,10 @@ namespace CounterDrone.Core
{
var t = s.CreateScenario("[Demo] 无防御-无人机抵达目标", "");
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveDroneProfile(t.Id, d.Drones.First(p => p.Id == "shahed").ToDroneProfile());
s.SaveScenarioDrone(t.Id, d.Drones.First(p => p.Id == "shahed").ToScenarioDrone());
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.SaveDeployment(t.Id, new List<EquipmentDeployment>());
s.SaveDeployment(t.Id, new List<ScenarioUnit>());
s.UpdateStep(t.Id, 5);
}
@ -48,10 +48,10 @@ namespace CounterDrone.Core
{
var t = s.CreateScenario("[Demo] 管控区域侵入", "");
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveDroneProfile(t.Id, d.Drones.First(p => p.Id == "electric-scout").ToDroneProfile());
s.SaveScenarioDrone(t.Id, d.Drones.First(p => p.Id == "electric-scout").ToScenarioDrone());
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.SaveDeployment(t.Id, new List<EquipmentDeployment>());
s.SaveDeployment(t.Id, new List<ScenarioUnit>());
s.SaveControlZones(t.Id, new List<ControlZone>
{
new ControlZone
@ -71,15 +71,15 @@ namespace CounterDrone.Core
scene.WindSpeed = 5;
scene.WindDirection = (int)WindDirection.W;
s.SaveScene(t.Id, scene);
s.SaveDroneProfile(t.Id, d.Drones.First(p => p.Id == "shahed").ToDroneProfile());
s.SaveScenarioDrone(t.Id, d.Drones.First(p => p.Id == "shahed").ToScenarioDrone());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(),
new List<Waypoint> {
new() { PosX = 6600, PosY = 500, PosZ = 0, Speed = 200 },
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
});
s.SaveDeployment(t.Id, new List<EquipmentDeployment>
s.SaveDeployment(t.Id, new List<ScenarioUnit>
{
d.FireUnits.First(f => f.Id == "ground-light").ToEquipmentDeployment(AerosolType.InertGas, 1, 0, 0, 50),
d.FireUnits.First(f => f.Id == "ground-light").ToScenarioUnit(AerosolType.InertGas, 1, 0, 0, 50),
});
s.SaveCloudDispersal(t.Id, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
s.UpdateStep(t.Id, 5);
@ -89,15 +89,15 @@ namespace CounterDrone.Core
{
var t = s.CreateScenario("[Demo] 喷气式拦截-活性材料", "");
s.SaveScene(t.Id, d.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
s.SaveDroneProfile(t.Id, d.Drones.First(p => p.Id == "cruise-missile").ToDroneProfile());
s.SaveScenarioDrone(t.Id, d.Drones.First(p => p.Id == "cruise-missile").ToScenarioDrone());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(),
new List<Waypoint> {
new() { PosX = 18000, PosY = 500, PosZ = 0, Speed = 200 },
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
});
s.SaveDeployment(t.Id, new List<EquipmentDeployment>
s.SaveDeployment(t.Id, new List<ScenarioUnit>
{
d.FireUnits.First(f => f.Id == "ground-standard").ToEquipmentDeployment(AerosolType.ActiveMaterial, 1, 0, 0, 50),
d.FireUnits.First(f => f.Id == "ground-standard").ToScenarioUnit(AerosolType.ActiveMaterial, 1, 0, 0, 50),
});
s.SaveCloudDispersal(t.Id, new CloudDispersal { AerosolType = (int)AerosolType.ActiveMaterial, DisperseHeight = 500 });
s.UpdateStep(t.Id, 5);
@ -110,15 +110,15 @@ namespace CounterDrone.Core
scene.WindSpeed = 5;
scene.WindDirection = (int)WindDirection.E;
s.SaveScene(t.Id, scene);
s.SaveDroneProfile(t.Id, d.Drones.First(p => p.Id == "shahed").ToDroneProfile());
s.SaveScenarioDrone(t.Id, d.Drones.First(p => p.Id == "shahed").ToScenarioDrone());
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "single").ToRoutePlan(),
new List<Waypoint> {
new() { PosX = 9500, PosY = 500, PosZ = 0, Speed = 200 },
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
});
s.SaveDeployment(t.Id, new List<EquipmentDeployment>
s.SaveDeployment(t.Id, new List<ScenarioUnit>
{
d.FireUnits.First(f => f.Id == "air-standard").ToEquipmentDeployment(AerosolType.InertGas, 3, 5000, 1000, 0),
d.FireUnits.First(f => f.Id == "air-standard").ToScenarioUnit(AerosolType.InertGas, 3, 5000, 1000, 0),
});
s.SaveCloudDispersal(t.Id, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
s.UpdateStep(t.Id, 5);
@ -131,19 +131,19 @@ namespace CounterDrone.Core
scene.WindSpeed = 5;
scene.WindDirection = (int)WindDirection.E;
s.SaveScene(t.Id, scene);
var target = d.Drones.First(p => p.Id == "shahed").ToDroneProfile();
var target = d.Drones.First(p => p.Id == "shahed").ToScenarioDrone();
target.Quantity = 3;
s.SaveDroneProfile(t.Id, target);
s.SaveScenarioDrone(t.Id, target);
s.SaveRoute(t.Id, "default", d.Formations.First(f => f.Id == "line-3").ToRoutePlan(),
new List<Waypoint> {
new() { PosX = 10000, PosY = 500, PosZ = 0, Speed = 200 },
new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
});
s.SaveDeployment(t.Id, new List<EquipmentDeployment>
s.SaveDeployment(t.Id, new List<ScenarioUnit>
{
d.FireUnits.First(f => f.Id == "air-standard").ToEquipmentDeployment(AerosolType.InertGas, 1, 5000, 1000, 0),
d.FireUnits.First(f => f.Id == "air-standard").ToEquipmentDeployment(AerosolType.InertGas, 1, 5300, 1000, 0),
d.FireUnits.First(f => f.Id == "air-standard").ToEquipmentDeployment(AerosolType.InertGas, 1, 5600, 1000, 0),
d.FireUnits.First(f => f.Id == "air-standard").ToScenarioUnit(AerosolType.InertGas, 1, 5000, 1000, 0),
d.FireUnits.First(f => f.Id == "air-standard").ToScenarioUnit(AerosolType.InertGas, 1, 5300, 1000, 0),
d.FireUnits.First(f => f.Id == "air-standard").ToScenarioUnit(AerosolType.InertGas, 1, 5600, 1000, 0),
});
s.SaveCloudDispersal(t.Id, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
s.UpdateStep(t.Id, 5);

View File

@ -8,8 +8,8 @@ namespace CounterDrone.Core.Models
public Scenario Info { get; set; } = new();
public CombatScene Scene { get; set; } = new();
public List<ControlZone> ControlZones { get; set; } = new();
public List<DroneProfile> Drones { get; set; } = new();
public List<EquipmentDeployment> Equipment { get; set; } = new();
public List<ScenarioDrone> Drones { get; set; } = new();
public List<ScenarioUnit> Units { get; set; } = new();
public CloudDispersal Cloud { get; set; } = new();
/// <summary>多批次航路(多批次支持)</summary>
public List<RoutePlan> Routes { get; set; } = new();

View File

@ -4,8 +4,8 @@ using SQLite;
namespace CounterDrone.Core.Models
{
/// <summary>步骤2目标配置</summary>
[Table("DroneProfile")]
public class DroneProfile
[Table("ScenarioDrone")]
public class ScenarioDrone
{
[PrimaryKey]
public string Id { get; set; } = Guid.NewGuid().ToString();

View File

@ -4,8 +4,8 @@ using SQLite;
namespace CounterDrone.Core.Models
{
/// <summary>步骤3装备部署</summary>
[Table("EquipmentDeployment")]
public class EquipmentDeployment
[Table("ScenarioUnit")]
public class ScenarioUnit
{
[PrimaryKey]
public string Id { get; set; } = Guid.NewGuid().ToString();

View File

@ -5,13 +5,13 @@ using SQLite;
namespace CounterDrone.Core.Repository
{
public class DroneProfileRepository : BaseRepository<DroneProfile>
public class ScenarioDroneRepository : BaseRepository<ScenarioDrone>
{
public DroneProfileRepository(SQLiteConnection db) : base(db) { }
public ScenarioDroneRepository(SQLiteConnection db) : base(db) { }
public List<DroneProfile> GetByScenarioId(string scenarioId)
public List<ScenarioDrone> GetByScenarioId(string scenarioId)
{
return Db.Table<DroneProfile>().Where(t => t.ScenarioId == scenarioId).ToList();
return Db.Table<ScenarioDrone>().Where(t => t.ScenarioId == scenarioId).ToList();
}
public void DeleteByScenarioId(string scenarioId)

View File

@ -5,19 +5,19 @@ using SQLite;
namespace CounterDrone.Core.Repository
{
public class EquipmentDeploymentRepository : BaseRepository<EquipmentDeployment>
public class ScenarioUnitRepository : BaseRepository<ScenarioUnit>
{
public EquipmentDeploymentRepository(SQLiteConnection db) : base(db) { }
public ScenarioUnitRepository(SQLiteConnection db) : base(db) { }
public List<EquipmentDeployment> GetByScenarioId(string scenarioId)
public List<ScenarioUnit> GetByScenarioId(string scenarioId)
{
return Db.Table<EquipmentDeployment>().Where(e => e.ScenarioId == scenarioId).ToList();
return Db.Table<ScenarioUnit>().Where(e => e.ScenarioId == scenarioId).ToList();
}
/// <summary>按任务和角色查询装备EquipmentRole: 0=Detection, 1=LaunchPlatform</summary>
public List<EquipmentDeployment> GetByScenarioIdAndRole(string scenarioId, int equipmentRole)
public List<ScenarioUnit> GetByScenarioIdAndRole(string scenarioId, int equipmentRole)
{
return Db.Table<EquipmentDeployment>()
return Db.Table<ScenarioUnit>()
.Where(e => e.ScenarioId == scenarioId && e.EquipmentRole == equipmentRole)
.ToList();
}

View File

@ -13,15 +13,15 @@ namespace CounterDrone.Core.Services
void SaveScene(string scenarioId, CombatScene scene);
void SaveControlZones(string scenarioId, List<ControlZone> zones);
void SaveDroneProfile(string scenarioId, DroneProfile target);
void SaveDeployment(string scenarioId, List<EquipmentDeployment> equips);
void SaveScenarioDrone(string scenarioId, ScenarioDrone target);
void SaveDeployment(string scenarioId, List<ScenarioUnit> equips);
/// <summary>添加单个探测设备(独立于 SaveDeployment不覆盖火力单元</summary>
void AddDetection(string scenarioId, EquipmentDeployment detection);
void AddDetection(string scenarioId, ScenarioUnit detection);
/// <summary>删除探测设备</summary>
void DeleteDetection(string detectionId);
/// <summary>获取任务的所有探测设备EquipmentRole.Detection</summary>
List<EquipmentDeployment> GetDetections(string scenarioId);
List<ScenarioUnit> GetDetections(string scenarioId);
void SaveCloudDispersal(string scenarioId, CloudDispersal cloud);
void SaveRoute(string scenarioId, string waveId, RoutePlan route, List<Waypoint> waypoints);

View File

@ -13,7 +13,7 @@ namespace CounterDrone.Core.Services
Simulation.DroneStatus finalDroneStatus, float simulationDuration)
{
var sb = new StringBuilder();
var platforms = config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform).ToList();
var platforms = config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform).ToList();
var target = config.Drones.FirstOrDefault();
var scene = config.Scene;
var destroyed = events.Count(e => e.Type == SimEventType.DroneDestroyed);
@ -106,9 +106,9 @@ namespace CounterDrone.Core.Services
var platToEquip = new Dictionary<string, int>();
var unitLaunchCounts = new Dictionary<int, int>();
int platIdx = 1, unitNum = 0;
for (int ei = 0; ei < config.Equipment.Count; ei++)
for (int ei = 0; ei < config.Units.Count; ei++)
{
var eq = config.Equipment[ei];
var eq = config.Units[ei];
if (eq.EquipmentRole != (int)EquipmentRole.LaunchPlatform) continue;
int channels = (eq.GunCount ?? 1) * (eq.ChannelsPerGun ?? 1);
for (int j = 0; j < eq.Quantity; j++)
@ -132,7 +132,7 @@ namespace CounterDrone.Core.Services
sb.AppendLine($"| # | 平台类型 | 位置 (X,Y,Z) | 弹药类型 | 挂载量 | 发射数 |");
sb.AppendLine($"|---|---------|-------------|---------|:------:|:------:|");
int unitNum2 = 0;
foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
foreach (var eq in config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
{
int channels = (eq.GunCount ?? 1) * (eq.ChannelsPerGun ?? 1);
for (int j = 0; j < eq.Quantity; j++)
@ -146,7 +146,7 @@ namespace CounterDrone.Core.Services
}
sb.AppendLine();
// 探测设备
var detections = config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.Detection).ToList();
var detections = config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.Detection).ToList();
if (detections.Count > 0)
{
sb.AppendLine($"### 探测设备");

View File

@ -36,7 +36,7 @@ namespace CounterDrone.Core.Services
ScenarioNumber = config.Info.ScenarioNumber,
CompletedAt = DateTime.UtcNow.ToString("o"),
TargetCount = total,
EquipmentCount = config.Equipment.Count,
EquipmentCount = config.Units.Count,
InterceptResult = (int)result,
Content = _generator.Generate(config, events, Simulation.DroneStatus.Flying, duration),
};

View File

@ -12,8 +12,8 @@ namespace CounterDrone.Core.Services
private readonly ScenarioRepository _scenarioRepo;
private readonly CombatSceneRepository _sceneRepo;
private readonly ControlZoneRepository _zoneRepo;
private readonly DroneProfileRepository _targetRepo;
private readonly EquipmentDeploymentRepository _equipRepo;
private readonly ScenarioDroneRepository _droneRepo;
private readonly ScenarioUnitRepository _unitRepo;
private readonly CloudDispersalRepository _cloudRepo;
private readonly RoutePlanRepository _routeRepo;
private readonly WaypointRepository _waypointRepo;
@ -22,8 +22,8 @@ namespace CounterDrone.Core.Services
ScenarioRepository scenarioRepo,
CombatSceneRepository sceneRepo,
ControlZoneRepository zoneRepo,
DroneProfileRepository targetRepo,
EquipmentDeploymentRepository equipRepo,
ScenarioDroneRepository targetRepo,
ScenarioUnitRepository equipRepo,
CloudDispersalRepository cloudRepo,
RoutePlanRepository routeRepo,
WaypointRepository waypointRepo)
@ -31,8 +31,8 @@ namespace CounterDrone.Core.Services
_scenarioRepo = scenarioRepo;
_sceneRepo = sceneRepo;
_zoneRepo = zoneRepo;
_targetRepo = targetRepo;
_equipRepo = equipRepo;
_droneRepo = targetRepo;
_unitRepo = equipRepo;
_cloudRepo = cloudRepo;
_routeRepo = routeRepo;
_waypointRepo = waypointRepo;
@ -96,8 +96,8 @@ namespace CounterDrone.Core.Services
Info = scenario,
Scene = _sceneRepo.GetById(id) ?? new CombatScene { ScenarioId = id },
ControlZones = _zoneRepo.GetByScenarioId(id),
Drones = _targetRepo.GetByScenarioId(id),
Equipment = _equipRepo.GetByScenarioId(id),
Drones = _droneRepo.GetByScenarioId(id),
Units = _unitRepo.GetByScenarioId(id),
Cloud = _cloudRepo.GetById(id) ?? new CloudDispersal { ScenarioId = id },
Routes = _routeRepo.GetByScenarioId(id),
WaypointGroups = _waypointRepo.GetByScenarioId(id)
@ -131,51 +131,51 @@ namespace CounterDrone.Core.Services
TouchScenario(scenarioId);
}
public void SaveDroneProfile(string scenarioId, DroneProfile target)
public void SaveScenarioDrone(string scenarioId, ScenarioDrone target)
{
target.ScenarioId = scenarioId;
if (string.IsNullOrEmpty(target.Id))
target.Id = Guid.NewGuid().ToString();
var existing = _targetRepo.GetById(target.Id);
var existing = _droneRepo.GetById(target.Id);
if (existing != null)
_targetRepo.Update(target);
_droneRepo.Update(target);
else
_targetRepo.Insert(target);
_droneRepo.Insert(target);
TouchScenario(scenarioId);
}
public void SaveDeployment(string scenarioId, List<EquipmentDeployment> equips)
public void SaveDeployment(string scenarioId, List<ScenarioUnit> equips)
{
_equipRepo.DeleteByScenarioId(scenarioId);
_unitRepo.DeleteByScenarioId(scenarioId);
foreach (var e in equips)
{
e.ScenarioId = scenarioId;
if (string.IsNullOrEmpty(e.Id))
e.Id = Guid.NewGuid().ToString();
}
_equipRepo.InsertAll(equips);
_unitRepo.InsertAll(equips);
TouchScenario(scenarioId);
}
public void AddDetection(string scenarioId, EquipmentDeployment detection)
public void AddDetection(string scenarioId, ScenarioUnit detection)
{
detection.ScenarioId = scenarioId;
detection.EquipmentRole = (int)EquipmentRole.Detection;
if (string.IsNullOrEmpty(detection.Id))
detection.Id = Guid.NewGuid().ToString();
_equipRepo.Insert(detection);
_unitRepo.Insert(detection);
TouchScenario(scenarioId);
}
public void DeleteDetection(string detectionId)
{
_equipRepo.Delete(detectionId);
_unitRepo.Delete(detectionId);
}
public List<EquipmentDeployment> GetDetections(string scenarioId)
public List<ScenarioUnit> GetDetections(string scenarioId)
{
return _equipRepo.GetByScenarioIdAndRole(scenarioId, (int)EquipmentRole.Detection);
return _unitRepo.GetByScenarioIdAndRole(scenarioId, (int)EquipmentRole.Detection);
}
public void SaveCloudDispersal(string scenarioId, CloudDispersal cloud)

View File

@ -38,7 +38,7 @@ namespace CounterDrone.Core.Simulation
/// 查找弧长 s 所在段:最大的 i 使 _cumArc[i] >= s。</summary>
private readonly float[] _cumArc;
public DroneEntity(string id, string waveId, DroneProfile config, List<Waypoint> route,
public DroneEntity(string id, string waveId, ScenarioDrone config, List<Waypoint> route,
int formationIndex, float lateralSpacing, int longitudinalIndex, float longitudinalSpacing,
FormationMode mode, int lateralAxis = 2, int longitudinalAxis = 0)
{

View File

@ -46,7 +46,7 @@ namespace CounterDrone.Core.Simulation
/// <summary>Ready 包含:冷却就绪 + 有弹药 + 非飞行中</summary>
public bool Ready => CooldownRemaining <= 0 && MunitionCount > 0 && State != PlatformState.FlyingToTarget;
public PlatformEntity(string id, EquipmentDeployment config)
public PlatformEntity(string id, ScenarioUnit config)
{
Id = id;
Role = (EquipmentRole)config.EquipmentRole;

View File

@ -127,7 +127,7 @@ namespace CounterDrone.Core.Simulation
_entityCounter = 0;
_platforms.Clear();
foreach (var equip in config.Equipment)
foreach (var equip in config.Units)
if (equip.EquipmentRole != (int)EquipmentRole.Detection)
for (int i = 0; i < equip.Quantity; i++)
{
@ -430,7 +430,7 @@ namespace CounterDrone.Core.Simulation
private static List<FireUnit> BuildFireUnits(ScenarioConfig config)
{
var units = new List<FireUnit>();
foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
foreach (var eq in config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
{
int qty = Math.Max(1, eq.Quantity);
int gunCount = eq.GunCount ?? 1;
@ -467,7 +467,7 @@ namespace CounterDrone.Core.Simulation
private static List<DetectionSource> BuildDetectionSources(ScenarioConfig config)
{
var sources = new List<DetectionSource>();
foreach (var eq in config.Equipment)
foreach (var eq in config.Units)
{
// 只要有任何探测字段非 null 且 > 0就是有效探测源无论是否独立探测设备
bool hasDetection = (eq.RadarRange ?? 0) > 0 || (eq.EORange ?? 0) > 0 || (eq.IRRange ?? 0) > 0;

View File

@ -33,7 +33,7 @@ namespace CounterDrone.Unity
Debug.Log($"2. ScenarioManager OK. scenario: {scenarioId}");
scenarioMgr.SaveScene(scenarioId, new CombatScene { WindSpeed = 0 });
scenarioMgr.SaveDroneProfile(scenarioId, new DroneProfile
scenarioMgr.SaveScenarioDrone(scenarioId, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
@ -80,9 +80,9 @@ namespace CounterDrone.Unity
DisperseHeight = droneGroup.Profile.TypicalAltitude,
});
var equips = new List<EquipmentDeployment>();
var equips = new List<ScenarioUnit>();
foreach (var u in fireUnits)
equips.Add(new EquipmentDeployment
equips.Add(new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)u.Type, Quantity = 1,

View File

@ -27,8 +27,8 @@ namespace CounterDrone.Unity
SqliteConnectionTracker.Track(_db);
_service = new ScenarioService(
new ScenarioRepository(_db), new CombatSceneRepository(_db),
new ControlZoneRepository(_db), new DroneProfileRepository(_db),
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
new ControlZoneRepository(_db), new ScenarioDroneRepository(_db),
new ScenarioUnitRepository(_db), new CloudDispersalRepository(_db),
new RoutePlanRepository(_db), new WaypointRepository(_db));
_dataService = new DataService(
new AmmunitionSpecRepository(_db), new FireUnitSpecRepository(_db),
@ -52,8 +52,8 @@ namespace CounterDrone.Unity
=> _service.SearchScenarios(kw, from, to, page, size);
public void SaveScene(string scenarioId, CombatScene s) => _service.SaveScene(scenarioId, s);
public void SaveDroneProfile(string scenarioId, DroneProfile t) => _service.SaveDroneProfile(scenarioId, t);
public void SaveDeployment(string scenarioId, List<EquipmentDeployment> e) => _service.SaveDeployment(scenarioId, e);
public void SaveScenarioDrone(string scenarioId, ScenarioDrone t) => _service.SaveScenarioDrone(scenarioId, t);
public void SaveDeployment(string scenarioId, List<ScenarioUnit> e) => _service.SaveDeployment(scenarioId, e);
public void SaveCloud(string scenarioId, CloudDispersal c) => _service.SaveCloudDispersal(scenarioId, c);
public void SaveRoute(string scenarioId, string waveId, RoutePlan r, List<Waypoint> w) => _service.SaveRoute(scenarioId, waveId, r, w);
public void UpdateStep(string scenarioId, int step) => _service.UpdateStep(scenarioId, step);

View File

@ -43,8 +43,8 @@ namespace CounterDrone.Unity
var scenario = new ScenarioService(
new ScenarioRepository(db), new CombatSceneRepository(db),
new ControlZoneRepository(db), new DroneProfileRepository(db),
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
new ControlZoneRepository(db), new ScenarioDroneRepository(db),
new ScenarioUnitRepository(db), new CloudDispersalRepository(db),
new RoutePlanRepository(db), new WaypointRepository(db));
string scenarioId;
@ -70,7 +70,7 @@ namespace CounterDrone.Unity
scenarioId = sc.Id;
scenario.SaveScene(scenarioId, defaults.Environments.First(w => w.Id == "sunny-calm").ToCombatScene());
scenario.SaveDroneProfile(scenarioId, defaults.Drones.First(p => p.Id == "shahed").ToDroneProfile());
scenario.SaveScenarioDrone(scenarioId, defaults.Drones.First(p => p.Id == "shahed").ToScenarioDrone());
scenario.SaveRoute(scenarioId, "default",
defaults.Formations.First(f => f.Id == "single").ToRoutePlan(),
defaults.Routes.First(r => _routeLength > 6000 ? r.Id == "10km-h500" : r.Id == "5km-h500")
@ -102,10 +102,10 @@ namespace CounterDrone.Unity
DisperseHeight = detail.Drones[0].TypicalAltitude,
Source = "Algorithm",
});
scenario.SaveDeployment(scenarioId, new List<EquipmentDeployment>
scenario.SaveDeployment(scenarioId, new List<ScenarioUnit>
{
defaults.FireUnits.First(f => f.Id == "ground-light")
.ToEquipmentDeployment(AerosolType.InertGas, 1, _routeLength / 2f, 0, 50),
.ToScenarioUnit(AerosolType.InertGas, 1, _routeLength / 2f, 0, 50),
});
scenario.UpdateStep(scenarioId, 5);

View File

@ -44,8 +44,8 @@ namespace CounterDrone.Unity
_scenario = new ScenarioService(
new ScenarioRepository(_db), new CombatSceneRepository(_db),
new ControlZoneRepository(_db), new DroneProfileRepository(_db),
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
new ControlZoneRepository(_db), new ScenarioDroneRepository(_db),
new ScenarioUnitRepository(_db), new CloudDispersalRepository(_db),
new RoutePlanRepository(_db), new WaypointRepository(_db));
_frameStore = new FrameDataStore(_paths);

View File

@ -35,7 +35,7 @@ namespace CounterDrone.Core.Tests
var tables = new[]
{
"ModelInfo", "AmmunitionSpec", "Scenario", "CombatScene",
"ControlZone", "DroneProfile", "EquipmentDeployment",
"ControlZone", "ScenarioDrone", "ScenarioUnit",
"CloudDispersal", "RoutePlan", "Waypoint",
"SimulationReport"
};

View File

@ -43,7 +43,7 @@ namespace CounterDrone.Core.Tests
var t = new DroneWave
{
WaveId = "default",
Profile = new DroneProfile
Profile = new ScenarioDrone
{
DroneType = (int)DroneType.FixedWing, PowerType = (int)power,
Quantity = 1, TypicalSpeed = speed, TypicalAltitude = alt,
@ -378,7 +378,7 @@ namespace CounterDrone.Core.Tests
var threat = new DroneWave
{
WaveId = "default",
Profile = new DroneProfile
Profile = new ScenarioDrone
{
DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,

View File

@ -8,9 +8,9 @@ namespace CounterDrone.Core.Tests
{
public class DroneEntityTests
{
private DroneProfile CreateConfig(float speed = 120, float altitude = 300)
private ScenarioDrone CreateConfig(float speed = 120, float altitude = 300)
{
return new DroneProfile
return new ScenarioDrone
{
DroneType = (int)DroneType.FixedWing,
PowerType = (int)PowerType.Piston,

View File

@ -28,8 +28,8 @@ namespace CounterDrone.Core.Tests
_scenario = new ScenarioService(
new ScenarioRepository(_db), new CombatSceneRepository(_db),
new ControlZoneRepository(_db), new DroneProfileRepository(_db),
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
new ControlZoneRepository(_db), new ScenarioDroneRepository(_db),
new ScenarioUnitRepository(_db), new CloudDispersalRepository(_db),
new RoutePlanRepository(_db), new WaypointRepository(_db));
}
@ -46,7 +46,7 @@ namespace CounterDrone.Core.Tests
_scenarioId = scenario.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
_scenario.SaveDroneProfile(_scenarioId, new DroneProfile
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
@ -54,7 +54,7 @@ namespace CounterDrone.Core.Tests
TypicalSpeed = 100,
TypicalAltitude = 300,
});
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>());
_scenario.SaveDeployment(_scenarioId, new List<ScenarioUnit>());
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal());
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
@ -89,14 +89,14 @@ namespace CounterDrone.Core.Tests
WindSpeed = 30, // 最大风速
WindDirection = (int)WindDirection.E,
});
_scenario.SaveDroneProfile(_scenarioId, new DroneProfile
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
Quantity = 1,
TypicalSpeed = 600, // 高速对抗强风
});
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>());
_scenario.SaveDeployment(_scenarioId, new List<ScenarioUnit>());
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal());
_scenario.SaveRoute(_scenarioId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
@ -128,14 +128,14 @@ namespace CounterDrone.Core.Tests
_scenarioId = scenario.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
_scenario.SaveDroneProfile(_scenarioId, new DroneProfile
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.HighSpeed,
Quantity = 1,
TypicalSpeed = 500,
});
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>());
_scenario.SaveDeployment(_scenarioId, new List<ScenarioUnit>());
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal());
// 500 km/h, 100km 航程

View File

@ -33,8 +33,8 @@ namespace CounterDrone.Core.Tests
_scenario = new ScenarioService(
new ScenarioRepository(_mainDb), new CombatSceneRepository(_mainDb),
new ControlZoneRepository(_mainDb), new DroneProfileRepository(_mainDb),
new EquipmentDeploymentRepository(_mainDb), new CloudDispersalRepository(_mainDb),
new ControlZoneRepository(_mainDb), new ScenarioDroneRepository(_mainDb),
new ScenarioUnitRepository(_mainDb), new CloudDispersalRepository(_mainDb),
new RoutePlanRepository(_mainDb), new WaypointRepository(_mainDb));
_frameStore = new FrameDataStore(_paths);
@ -185,7 +185,7 @@ namespace CounterDrone.Core.Tests
var scenarioA = _scenario.CreateScenario("无探测远航路", "");
_scenarioId = scenarioA.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenario.SaveDroneProfile(_scenarioId, new DroneProfile
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default", DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
@ -196,9 +196,9 @@ namespace CounterDrone.Core.Tests
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 },
});
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>
_scenario.SaveDeployment(_scenarioId, new List<ScenarioUnit>
{
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").ToScenarioUnit(AerosolType.InertGas, 1, 8000, 0, 50),
});
_scenario.SaveCloudDispersal(_scenarioId, new CloudDispersal { AerosolType = (int)AerosolType.InertGas, DisperseHeight = 500 });
var engA = RunSimulation(8000);
@ -210,7 +210,7 @@ namespace CounterDrone.Core.Tests
var scenarioB = _scenario.CreateScenario("有探测远航路", "");
_scenarioId = scenarioB.Id;
_scenario.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenario.SaveDroneProfile(_scenarioId, new DroneProfile
_scenario.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default", DroneType = (int)DroneType.FixedWing, PowerType = (int)PowerType.Piston,
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
@ -221,11 +221,11 @@ namespace CounterDrone.Core.Tests
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 },
});
_scenario.SaveDeployment(_scenarioId, new List<EquipmentDeployment>
_scenario.SaveDeployment(_scenarioId, new List<ScenarioUnit>
{
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").ToScenarioUnit(AerosolType.InertGas, 1, 8000, 0, 50),
// 独立探测设备
new EquipmentDeployment
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1,

View File

@ -36,11 +36,11 @@ namespace CounterDrone.Core.Tests
var config = new ScenarioConfig
{
Info = new Scenario { Name = "测试任务", ScenarioNumber = "SIM-001" },
Drones = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>
Drones = new List<ScenarioDrone> { new ScenarioDrone { Quantity = 1 } },
Units = new List<ScenarioUnit>
{
new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 3 },
new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1 },
new ScenarioUnit { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 3 },
new ScenarioUnit { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1 },
},
Cloud = new CloudDispersal { AerosolType = (int)AerosolType.InertGas },
};

View File

@ -37,8 +37,8 @@ namespace CounterDrone.Core.Tests
var config = new ScenarioConfig
{
Info = new Scenario { Name = "Test", ScenarioNumber = "SIM-001" },
Drones = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>(),
Drones = new List<ScenarioDrone> { new ScenarioDrone { Quantity = 1 } },
Units = new List<ScenarioUnit>(),
Cloud = new CloudDispersal(),
};
@ -59,8 +59,8 @@ namespace CounterDrone.Core.Tests
var config = new ScenarioConfig
{
Info = new Scenario { Name = "Del", ScenarioNumber = "SIM-DEL" },
Drones = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>(),
Drones = new List<ScenarioDrone> { new ScenarioDrone { Quantity = 1 } },
Units = new List<ScenarioUnit>(),
Cloud = new CloudDispersal(),
};
@ -75,8 +75,8 @@ namespace CounterDrone.Core.Tests
var cfg = new ScenarioConfig
{
Info = new Scenario { Name = "城市防御", ScenarioNumber = "SIM-A" },
Drones = new List<DroneProfile> { new DroneProfile { Quantity = 1 } },
Equipment = new List<EquipmentDeployment>(),
Drones = new List<ScenarioDrone> { new ScenarioDrone { Quantity = 1 } },
Units = new List<ScenarioUnit>(),
Cloud = new CloudDispersal(),
};

View File

@ -22,12 +22,12 @@ namespace CounterDrone.Core.Tests
public void Dispose() { _db?.Close(); if (Directory.Exists(_testDir)) Directory.Delete(_testDir, true); }
[Fact]
public void DroneProfile_DeleteByScenarioId_RemovesAll()
public void ScenarioDrone_DeleteByScenarioId_RemovesAll()
{
var repo = new DroneProfileRepository(_db);
repo.Insert(new DroneProfile { Id = "t1", ScenarioId = "scenarioA", Quantity = 1 });
repo.Insert(new DroneProfile { Id = "t2", ScenarioId = "scenarioA", Quantity = 2 });
repo.Insert(new DroneProfile { Id = "t3", ScenarioId = "scenarioB", Quantity = 1 });
var repo = new ScenarioDroneRepository(_db);
repo.Insert(new ScenarioDrone { Id = "t1", ScenarioId = "scenarioA", Quantity = 1 });
repo.Insert(new ScenarioDrone { Id = "t2", ScenarioId = "scenarioA", Quantity = 2 });
repo.Insert(new ScenarioDrone { Id = "t3", ScenarioId = "scenarioB", Quantity = 1 });
repo.DeleteByScenarioId("scenarioA");
@ -50,11 +50,11 @@ namespace CounterDrone.Core.Tests
}
[Fact]
public void EquipmentDeployment_DeleteByScenarioId_Works()
public void ScenarioUnit_DeleteByScenarioId_Works()
{
var repo = new EquipmentDeploymentRepository(_db);
repo.Insert(new EquipmentDeployment { Id = "e1", ScenarioId = "eqA" });
repo.Insert(new EquipmentDeployment { Id = "e2", ScenarioId = "eqA" });
var repo = new ScenarioUnitRepository(_db);
repo.Insert(new ScenarioUnit { Id = "e1", ScenarioId = "eqA" });
repo.Insert(new ScenarioUnit { Id = "e2", ScenarioId = "eqA" });
repo.DeleteByScenarioId("eqA");
Assert.Empty(repo.GetByScenarioId("eqA"));

View File

@ -27,8 +27,8 @@ namespace CounterDrone.Core.Tests
new ScenarioRepository(_db),
new CombatSceneRepository(_db),
new ControlZoneRepository(_db),
new DroneProfileRepository(_db),
new EquipmentDeploymentRepository(_db),
new ScenarioDroneRepository(_db),
new ScenarioUnitRepository(_db),
new CloudDispersalRepository(_db),
new RoutePlanRepository(_db),
new WaypointRepository(_db));
@ -101,7 +101,7 @@ namespace CounterDrone.Core.Tests
Temperature = 25.0,
});
_service.SaveDroneProfile(scenario.Id, new DroneProfile
_service.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
@ -196,10 +196,10 @@ namespace CounterDrone.Core.Tests
}
[Fact]
public void SaveDroneProfile_SavesCorrectly()
public void SaveScenarioDrone_SavesCorrectly()
{
var scenario = _service.CreateScenario("Drone Test", "");
_service.SaveDroneProfile(scenario.Id, new DroneProfile
_service.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
@ -221,9 +221,9 @@ namespace CounterDrone.Core.Tests
public void SaveDeployment_SavesMultiple()
{
var scenario = _service.CreateScenario("Equip Test", "");
var equips = new List<EquipmentDeployment>
var equips = new List<ScenarioUnit>
{
new EquipmentDeployment
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
@ -232,7 +232,7 @@ namespace CounterDrone.Core.Tests
MuzzleVelocity = 800.0,
MunitionCount = 3,
},
new EquipmentDeployment
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1,
@ -243,9 +243,9 @@ namespace CounterDrone.Core.Tests
_service.SaveDeployment(scenario.Id, equips);
var detail = _service.GetScenarioDetail(scenario.Id);
Assert.Equal(2, detail.Equipment.Count);
Assert.Equal(2, detail.Units.Count);
var platform = detail.Equipment.Find(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
var platform = detail.Units.Find(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
Assert.NotNull(platform);
Assert.Equal(2, platform.Quantity);
Assert.Equal(800.0, platform.MuzzleVelocity);
@ -392,7 +392,7 @@ namespace CounterDrone.Core.Tests
});
// 4. 步骤2目标配置
_service.SaveDroneProfile(scenario.Id, new DroneProfile
_service.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.HighSpeed,
@ -403,15 +403,15 @@ namespace CounterDrone.Core.Tests
_service.UpdateStep(scenario.Id, 2);
// 5. 步骤3装备部署
_service.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new EquipmentDeployment
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1,
RadarRange = 6000.0,
},
new EquipmentDeployment
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
@ -469,7 +469,7 @@ namespace CounterDrone.Core.Tests
Assert.Equal(300.0, detail.Drones[0].TypicalSpeed);
// 装备
Assert.Equal(2, detail.Equipment.Count);
Assert.Equal(2, detail.Units.Count);
// 云团
Assert.Equal((int)AerosolType.ActiveMaterial, detail.Cloud.AerosolType);
@ -493,7 +493,7 @@ namespace CounterDrone.Core.Tests
public void AddDetection_PersistsAndQueryable()
{
var scenario = _service.CreateScenario("探测设备测试", "");
_service.AddDetection(scenario.Id, new EquipmentDeployment
_service.AddDetection(scenario.Id, new ScenarioUnit
{
Quantity = 1,
PositionX = 5000, PositionY = 0, PositionZ = 0,
@ -513,17 +513,17 @@ namespace CounterDrone.Core.Tests
{
var scenario = _service.CreateScenario("探测过滤测试", "");
// 一个火力单元
_service.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new EquipmentDeployment
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
Quantity = 1, PositionX = 1000,
},
});
// 两个探测设备(独立添加)
_service.AddDetection(scenario.Id, new EquipmentDeployment { RadarRange = 5000 });
_service.AddDetection(scenario.Id, new EquipmentDeployment { EORange = 3000 });
_service.AddDetection(scenario.Id, new ScenarioUnit { RadarRange = 5000 });
_service.AddDetection(scenario.Id, new ScenarioUnit { EORange = 3000 });
var detections = _service.GetDetections(scenario.Id);
Assert.Equal(2, detections.Count); // 只有探测设备,不含火力单元
@ -533,8 +533,8 @@ namespace CounterDrone.Core.Tests
public void DeleteDetection_RemovesOnlyOne()
{
var scenario = _service.CreateScenario("探测删除测试", "");
_service.AddDetection(scenario.Id, new EquipmentDeployment { RadarRange = 5000 });
var det2 = new EquipmentDeployment { EORange = 3000 };
_service.AddDetection(scenario.Id, new ScenarioUnit { RadarRange = 5000 });
var det2 = new ScenarioUnit { EORange = 3000 };
_service.AddDetection(scenario.Id, det2);
_service.DeleteDetection(det2.Id);
@ -548,17 +548,17 @@ namespace CounterDrone.Core.Tests
public void GetScenarioDetail_IncludesDetections()
{
var scenario = _service.CreateScenario("想定含探测", "");
_service.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
_service.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new EquipmentDeployment { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 1 },
new ScenarioUnit { EquipmentRole = (int)EquipmentRole.LaunchPlatform, Quantity = 1 },
});
_service.AddDetection(scenario.Id, new EquipmentDeployment { RadarRange = 10000 });
_service.AddDetection(scenario.Id, new ScenarioUnit { RadarRange = 10000 });
var detail = _service.GetScenarioDetail(scenario.Id);
// Equipment 包含火力单元 + 探测设备
Assert.Equal(2, detail.Equipment.Count);
Assert.Contains(detail.Equipment, e => e.EquipmentRole == (int)EquipmentRole.Detection);
Assert.Contains(detail.Equipment, e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
Assert.Equal(2, detail.Units.Count);
Assert.Contains(detail.Units, e => e.EquipmentRole == (int)EquipmentRole.Detection);
Assert.Contains(detail.Units, e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
}
}
}

View File

@ -30,8 +30,8 @@ namespace CounterDrone.Core.Tests
_scenarioService = new ScenarioService(
new ScenarioRepository(_mainDb), new CombatSceneRepository(_mainDb),
new ControlZoneRepository(_mainDb), new DroneProfileRepository(_mainDb),
new EquipmentDeploymentRepository(_mainDb), new CloudDispersalRepository(_mainDb),
new ControlZoneRepository(_mainDb), new ScenarioDroneRepository(_mainDb),
new ScenarioUnitRepository(_mainDb), new CloudDispersalRepository(_mainDb),
new RoutePlanRepository(_mainDb), new WaypointRepository(_mainDb));
_engine = new SimulationEngine(_scenarioService, new FrameDataStore(paths),
@ -50,7 +50,7 @@ namespace CounterDrone.Core.Tests
var scenario = _scenarioService.CreateScenario("Test", "");
_scenarioId = scenario.Id;
_scenarioService.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0 });
_scenarioService.SaveDroneProfile(_scenarioId, new DroneProfile
_scenarioService.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "default",
DroneType = (int)DroneType.FixedWing,
@ -58,9 +58,9 @@ namespace CounterDrone.Core.Tests
PowerType = (int)PowerType.Piston,
TypicalSpeed = 60,
});
_scenarioService.SaveDeployment(_scenarioId, new List<EquipmentDeployment>
_scenarioService.SaveDeployment(_scenarioId, new List<ScenarioUnit>
{
new EquipmentDeployment
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
@ -151,7 +151,7 @@ namespace CounterDrone.Core.Tests
var scenario = _scenarioService.CreateScenario("Detect", "");
_scenarioId = scenario.Id;
_scenarioService.SaveScene(_scenarioId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveDroneProfile(_scenarioId, new DroneProfile
_scenarioService.SaveScenarioDrone(_scenarioId, new ScenarioDrone
{
WaveId = "w1",
DroneType = (int)DroneType.FixedWing,
@ -161,15 +161,15 @@ namespace CounterDrone.Core.Tests
TypicalAltitude = 300,
});
// 探测设备在 (5000, 0, 0),雷达 3000m无角度/高度限制
_scenarioService.SaveDeployment(_scenarioId, new List<EquipmentDeployment>
_scenarioService.SaveDeployment(_scenarioId, new List<ScenarioUnit>
{
new EquipmentDeployment
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.Detection,
Quantity = 1, PositionX = 5000, PositionY = 0, PositionZ = 0,
RadarRange = 3000,
},
new EquipmentDeployment
new ScenarioUnit
{
EquipmentRole = (int)EquipmentRole.LaunchPlatform,
PlatformType = (int)PlatformType.GroundBased,
@ -224,11 +224,11 @@ namespace CounterDrone.Core.Tests
// 探测设备在 X=5000 半径 1000 → 入口 X=4000出口 X=6000
var scenario = _scenarioService.CreateScenario("Reenter", "");
_scenarioService.SaveScene(scenario.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveDroneProfile(scenario.Id, new DroneProfile
_scenarioService.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "w1", Quantity = 1, TypicalSpeed = 120, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
});
_scenarioService.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
_scenarioService.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 5000, PositionY = 0, PositionZ = 0, RadarRange = 1000 },
new() { EquipmentRole = (int)EquipmentRole.LaunchPlatform, PlatformType = (int)PlatformType.GroundBased,
@ -275,11 +275,11 @@ namespace CounterDrone.Core.Tests
// D1 边界 X=1000D2 边界 X=3000 → D1 更早发现
var scenario = _scenarioService.CreateScenario("Fuse", "");
_scenarioService.SaveScene(scenario.Id, new CombatScene { WindSpeed = 0, Visibility = 10000 });
_scenarioService.SaveDroneProfile(scenario.Id, new DroneProfile
_scenarioService.SaveScenarioDrone(scenario.Id, new ScenarioDrone
{
WaveId = "w1", Quantity = 1, TypicalSpeed = 60, PowerType = (int)PowerType.Piston, TypicalAltitude = 300,
});
_scenarioService.SaveDeployment(scenario.Id, new List<EquipmentDeployment>
_scenarioService.SaveDeployment(scenario.Id, new List<ScenarioUnit>
{
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 3000, PositionY = 0, PositionZ = 0, RadarRange = 2000, DetectionAccuracy = 50 },
new() { EquipmentRole = (int)EquipmentRole.Detection, Quantity = 1, PositionX = 7000, PositionY = 0, PositionZ = 0, RadarRange = 4000, DetectionAccuracy = 30 },

Binary file not shown.