refactor: 编组概念拆分为批次(WaveId)+火力单元(FireUnit)
Breaking: Group表/枚举/Service/Repository移除; GroupId->WaveId; DroneGroup->DroneWave; DroneGroupId->DroneWaveId; GroupManager删除 Docs: CHANGELOG 0.7.0; 总体架构 V10; DefensePlanner V4; 实施计划 V1.4; 对接文档 V1.2 Tests: 204/204 pass
This commit is contained in:
parent
665747a846
commit
56d345189e
41
CHANGELOG.md
41
CHANGELOG.md
@ -2,6 +2,47 @@
|
||||
|
||||
---
|
||||
|
||||
## [0.7.0] - 2026-06-15
|
||||
|
||||
### Breaking — 概念升级:编组拆分 + Group 表移除
|
||||
|
||||
- **Group 表移除**:原 `Group` 表(DroneFleet / EquipmentGroup)不再创建。向后兼容保留旧库中的 Group 表,但不再主动读写
|
||||
- **GroupType 枚举删除**:不再区分 DroneFleet / EquipmentGroup
|
||||
- **GroupService / IGroupService / GroupRepository 删除**:不再需要编组管理服务
|
||||
- **Unity GroupManager 删除**:不再需要编组管理桥接
|
||||
|
||||
### Changed — 数据模型重命名
|
||||
|
||||
- **`GroupId` → `WaveId`**:TargetConfig、EquipmentDeployment、RoutePlan、Waypoint 四表的编组外键重命名为批次外键
|
||||
- **RoutePlan 索引重命名**:`(TaskId, GroupId)` → `(TaskId, WaveId)`
|
||||
- **RoutePlanRepository / WaypointRepository**:`GetByTaskAndGroup` → `GetByTaskAndWave`
|
||||
- **IScenarioService.SaveRoute**:`groupId` 参数 → `waveId`
|
||||
|
||||
### Changed — 算法类型重命名
|
||||
|
||||
- **`DroneGroup` → `DroneWave`**:AlgorithmTypes、IDefensePlanner、DefensePlanner、SimulationEngine 全部使用新类型名
|
||||
- **`DroneGroupId` → `DroneWaveId`**:UnitAssignment 中的编队引用 → 批次引用
|
||||
- **`BuildDroneGroups()` → `BuildDroneWaves()`**:SimulationEngine 构建方法重命名
|
||||
|
||||
### Changed — 文档同步
|
||||
|
||||
- 总体架构设计:编组概念升级说明
|
||||
- DefensePlanner 技术方案:DroneGroup → DroneWave
|
||||
- 实施计划:GroupService 移除、多编队 → 多批次
|
||||
- Unity 前端对接文档:DefaultDefenseAdvisor → DefensePlanner,SaveRoute 签名更新
|
||||
- 仿真器实体事件映射:BuildDroneGroups → BuildDroneWaves
|
||||
|
||||
### Removed
|
||||
|
||||
- 7 个文件:Group.cs / GroupRepository.cs / IGroupService.cs / GroupService.cs / GroupManager.cs / GroupServiceTests.cs / GroupRepositoryTests.cs
|
||||
|
||||
### Metrics
|
||||
|
||||
- 测试 208 → **204**(−4,删除 Group 相关测试),全量通过 63s
|
||||
- 0 编译错误,0 编译警告
|
||||
|
||||
---
|
||||
|
||||
## [0.6.0] - 2026-06-15
|
||||
|
||||
### Added — 探测驱动的规划(8.1.1)
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# 反无人机仿真系统 — 总体架构设计
|
||||
|
||||
> **版本**:V9
|
||||
> **日期**:2026-06-13
|
||||
> **版本**:V10
|
||||
> **日期**:2026-06-15
|
||||
> **状态**:已实现
|
||||
> **Unity 版本**:22.3.62
|
||||
> **.NET 版本**:.NET Standard 2.1
|
||||
> **变更**:DefensePlanner 五步规划引擎;FireUnit 通道模型;空基平台飞控;SQLite domain reload 修复;删除所有 fallback 默认值
|
||||
> **变更**:Group 表移除;GroupId → WaveId;DroneGroup → DroneWave;编组概念升级为批次+火力单元
|
||||
|
||||
---
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
| 模块 | 核心功能 |
|
||||
|------|----------|
|
||||
| **模型管理** | 3D 模型导入/预览/删除,编组管理 |
|
||||
| **模型管理** | 3D 模型导入/预览/删除 |
|
||||
| **想定管理** | 仿真任务 CRUD,步骤化配置向导,管控区域设置,搜索分页 |
|
||||
| **仿真报告** | 报告列表,含时序图的完整报告预览,导出 PDF/Word,删除 |
|
||||
|
||||
@ -41,12 +41,12 @@
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ Unity Application Layer │
|
||||
│ ModelPanel | ScenarioWizard | SimulationRunner | │
|
||||
│ ReplayController | ReportPanel | GroupManager │
|
||||
│ ReplayController | ReportPanel │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ Service Layer (Pure C#) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ ModelService │ │ScenarioService│ │ SimulationEngine │ │
|
||||
│ │ GroupService │ │ DefensePlanner │ │ ├ DroneEntity │ │
|
||||
│ │ │ │ DefensePlanner │ │ ├ DroneEntity │ │
|
||||
│ └──────────────┘ └──────────────┘ │ ├ PlatformEntity │ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │ ├ MunitionEntity │ │
|
||||
│ │FrameDataStore│ │ReportService │ │ ├ CloudEntity │ │
|
||||
@ -199,9 +199,19 @@ enum TriggerMode { Time = 0, Area = 1, Manual = 2 }
|
||||
// === 毁伤状态阶段(输出到 StateData 供 Unity 渲染)===
|
||||
enum DamageStage { Normal = 0, EngineAnomaly = 1, AttitudeLoss = 2, Destroyed = 3 }
|
||||
|
||||
// === 编队 & 编组 ===
|
||||
// === 编队 ===
|
||||
enum FormationMode { Single = 0, Formation = 1, Swarm = 2 }
|
||||
enum GroupType { DroneFleet = 0, EquipmentGroup = 1 }
|
||||
|
||||
// === 核心作战概念 ===
|
||||
// 火力单元(FireUnit):能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。
|
||||
// 包含探测(雷达/光电/红外)+ 打击(发射架/火炮)的完整闭环系统。
|
||||
// 配置层 = EquipmentDeployment(LaunchPlatform),含探测字段 + 打击字段。
|
||||
// 独立探测节点(DetectionNode):只探测不打击的侦查节点(如前沿警戒雷达)。
|
||||
// 配置层 = EquipmentDeployment(Detection),仅含探测字段。
|
||||
// 两者都往统一信息网络送探测信息,planner 基于融合后的探测边界规划。
|
||||
// 无人机批次(DroneWave):有共同航路的一组无人机(攻击方)。
|
||||
// 批次关联航路但不独占(多个批次可共享同一条航路)。
|
||||
// 配置层 = TargetConfig(含 WaveId) + RoutePlan(TaskId, WaveId) + Waypoints。
|
||||
|
||||
// === 运行时 ===
|
||||
enum EntityType { Drone = 0, Platform = 1, DetectionEquip = 2, Cloud = 3, Munition = 4 }
|
||||
@ -254,12 +264,11 @@ CounterDroneBackend_Data/
|
||||
SimTask — 仿真任务主表
|
||||
CombatScene — 步骤1:作战场景(含24h时间、天气)
|
||||
ControlZone — 步骤1+:管控区域/电子围栏(NEW)
|
||||
TargetConfig — 步骤2:目标配置
|
||||
EquipmentDeployment — 步骤3:装备部署(搭载平台+探测设备)
|
||||
TargetConfig — 步骤2:目标配置(含 WaveId 关联批次)
|
||||
EquipmentDeployment — 步骤3:装备部署(火力单元含探测+打击,或独立探测节点)
|
||||
CloudDispersal — 步骤4:云团抛撒配置
|
||||
RoutePlan — 步骤5:航路规划(1:N SimTask,多编队)
|
||||
RoutePlan — 步骤5:航路规划(1:N SimTask,多批次;批次关联但不独占航路,FK 关联 TargetConfig.WaveId)
|
||||
Waypoint — 航路点
|
||||
Group — 编组(独立管理)
|
||||
SimulationReport — 仿真报告
|
||||
SimEvent — 仿真事件(时序图数据源)
|
||||
|
||||
@ -267,6 +276,12 @@ CounterDroneBackend_Data/
|
||||
SimFrameRecord — 逐帧位置记录(回放用)
|
||||
```
|
||||
|
||||
> **核心作战概念**:
|
||||
> - **火力单元(FireUnit)**:能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。包含探测(雷达/光电/红外)+ 打击(发射架/火炮)的完整闭环。配置层 = `EquipmentDeployment(LaunchPlatform)`。
|
||||
> - **独立探测节点(DetectionNode)**:只探测不打击的侦查节点(如前沿警戒雷达)。配置层 = `EquipmentDeployment(Detection)`。
|
||||
> - **无人机批次(DroneWave)**:有共同航路的一组无人机(攻击方)。批次关联航路但不独占(多个批次可共享同一条航路)。配置层 = `TargetConfig(WaveId)` + `RoutePlan(TaskId, WaveId)` + `Waypoints`。
|
||||
> - 原来的 `Group`(编组)表已移除:无人机编队概念升级为"批次",装备编组概念不再需要(火力单元是独立作战单位)。
|
||||
|
||||
### 4.3 业务表详细设计
|
||||
|
||||
#### ModelInfo — 模型元数据
|
||||
@ -337,7 +352,7 @@ CounterDroneBackend_Data/
|
||||
|------|------|------|------|
|
||||
| Id | TEXT | PK | GUID |
|
||||
| TaskId | TEXT | FK | |
|
||||
| GroupId | TEXT | FK | 关联无人机编队 Group |
|
||||
| WaveId | TEXT | FK | 关联无人机批次(同一 WaveId 的无人机共享航路) |
|
||||
| TargetType | INTEGER | | 0旋翼 1固定翼 2电推 3活塞 4高速 |
|
||||
| Quantity | INTEGER | DEFAULT 1 | 目标数量 |
|
||||
| PowerType | INTEGER | | 0电推 1活塞 2喷吸气 |
|
||||
@ -355,28 +370,31 @@ CounterDroneBackend_Data/
|
||||
| 活塞 | 2.5 | 120 | 800 |
|
||||
| 高速目标 | 1.5 | 300 | 2000 |
|
||||
|
||||
#### EquipmentDeployment — 步骤3:装备部署(重构)
|
||||
#### EquipmentDeployment — 步骤3:装备部署(火力单元 + 独立探测节点)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| Id | TEXT | PK | GUID |
|
||||
| TaskId | TEXT | FK | |
|
||||
| EquipmentRole | INTEGER | | 0=探测设备 1=发射平台 |
|
||||
| EquipmentRole | INTEGER | | 0=独立探测节点 1=火力单元(发射平台) |
|
||||
| Quantity | INTEGER | DEFAULT 1 | 部署数量 |
|
||||
| GroupId | TEXT | FK | 关联装备编组 Group(火力单元,多批次时用于分配) |
|
||||
| // 以下为发射平台专用字段 |
|
||||
| PlatformType | INTEGER | NULLABLE | 0=空基(大型无人机) 1=地基(炮弹),探测设备为 NULL |
|
||||
| WaveId | TEXT | NULLABLE | 可选关联批次(用于 UI 分组,火力单元是独立作战单位,不强依赖批次) |
|
||||
| // 以下为火力单元(发射平台)专用字段 |
|
||||
| PlatformType | INTEGER | NULLABLE | 0=空基(大型无人机) 1=地基(炮弹),独立探测节点为 NULL |
|
||||
| PositionX | REAL | | 部署位置 |
|
||||
| PositionY | REAL | | |
|
||||
| PositionZ | REAL | | |
|
||||
| AerosolType | INTEGER | NULLABLE | 挂载气溶胶类型,探测设备为 NULL |
|
||||
| AerosolType | INTEGER | NULLABLE | 挂载气溶胶类型,独立探测节点为 NULL |
|
||||
| MunitionCount | INTEGER | NULLABLE | 挂载弹药数量 |
|
||||
| MuzzleVelocity | REAL | NULLABLE | **NEW** 弹药初速 m/s(地基炮弹用) |
|
||||
| ReleaseAltitude | REAL | NULLABLE | **NEW** 弹药释放高度 m(空基平台用) |
|
||||
| Cooldown | REAL | DEFAULT 5 | 发射冷却时间 s(同弹种连发间隔) |
|
||||
| AmmoChangeTime | REAL | DEFAULT 300 | **NEW** 更换弹种所需时间 s(默认 5 分钟) |
|
||||
| // 以下为探测设备专用字段 |
|
||||
| DetectionRadius | REAL | NULLABLE | 探测半径 m |
|
||||
| // 探测能力(火力单元自带 + 独立探测节点均有) |
|
||||
| RadarRange | REAL | NULLABLE | 雷达探测距离 m(雨雾不衰减) |
|
||||
| EORange | REAL | NULLABLE | 光电探测距离 m(受 Visibility 衰减) |
|
||||
| IRRange | REAL | NULLABLE | 红外探测距离 m(不受 Visibility 影响) |
|
||||
| DetectionAccuracy | REAL | NULLABLE | 探测精度 m(位置误差,影响抛撒散布范围) |
|
||||
|
||||
> **装备体系说明**:
|
||||
> - **搭载平台**(2 种):
|
||||
@ -415,12 +433,12 @@ CounterDroneBackend_Data/
|
||||
|
||||
#### RoutePlan — 步骤5:航路规划
|
||||
|
||||
> **支持多批次**:通过 `TaskId + GroupId` 唯一标识一条航路,一个任务可有多个编队各自独立航路。
|
||||
> **支持多批次**:通过 `TaskId + WaveId` 唯一标识一条航路,一个任务可有多个批次各自独立航路。
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| TaskId | TEXT | PK(复合) | |
|
||||
| GroupId | TEXT | PK(复合), FK → Group | 关联编队:同一任务不同编队可有独立航路 |
|
||||
| WaveId | TEXT | PK(复合) | 关联批次:同一任务不同批次可有独立航路(多个批次可共享同一条航路) |
|
||||
| FormationMode | INTEGER | | 0单机 1编队 2蜂群 |
|
||||
| FormationSpacing | REAL | DEFAULT 50 | m |
|
||||
| ETA | TEXT | | 预计到达时间 |
|
||||
@ -438,15 +456,13 @@ CounterDroneBackend_Data/
|
||||
| Altitude | REAL | | 该航点飞行高度 m |
|
||||
| Speed | REAL | | 该段速度 km/h |
|
||||
|
||||
#### Group — 编组
|
||||
#### ~~Group~~ — 编组(已移除)
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| Id | TEXT | PK | GUID |
|
||||
| Name | TEXT | NOT NULL | 编组名称 |
|
||||
| GroupType | INTEGER | | 0=无人机编队 1=装备编组 |
|
||||
| Description | TEXT | | 备注 |
|
||||
| CreatedAt | TEXT | | |
|
||||
> **概念升级**:原 Group 表的两种类型已分别升级:
|
||||
> - `DroneFleet`(无人机编队)→ **无人机批次(DroneWave)**:由 `TargetConfig(WaveId)` + `RoutePlan(TaskId, WaveId)` + `Waypoints` 隐式表达。批次关联航路但不独占(多个批次可共享同一条航路)。
|
||||
> - `EquipmentGroup`(装备编组)→ **不再需要**。火力单元是独立作战的最小单位,不需要上层编组。
|
||||
>
|
||||
> 数据层:`Group` 表保留但不再主动使用(向后兼容)。`TargetConfig.WaveId` 语义为"批次 ID"。
|
||||
|
||||
### 4.4 运行时表
|
||||
|
||||
@ -507,7 +523,7 @@ SimulationEngine
|
||||
├── SimulationState ← 状态(运行/暂停/结束)
|
||||
├── SceneConfig ← 配置快照(含场景边界、管控区域)
|
||||
├── List<DroneEntity> ← 无人机(目标方)
|
||||
│ └── 按编组组织,共享航路
|
||||
│ └── 按批次组织,共享航路
|
||||
├── List<PlatformEntity> ← 搭载平台(发射方)★重构
|
||||
│ ├── AirBasedPlatform ← 空基(大型无人机)
|
||||
│ └── GroundBasedPlatform ← 地基(火炮)
|
||||
@ -613,7 +629,7 @@ Unity 渲染毁伤效果(尾焰异常、姿态失控、坠落)
|
||||
|
||||
```
|
||||
属性:
|
||||
- 编组 GroupId、航路点队列 Waypoint[]
|
||||
- 批次 WaveId、航路点队列 Waypoint[]
|
||||
- 当前位置、速度、朝向
|
||||
- HP(0~1 归一化)
|
||||
- DamageStage(Normal → EngineAnomaly → AttitudeLoss → Destroyed)
|
||||
@ -1080,7 +1096,7 @@ planner 所有策略参数从 `data/planner_config.json` 读取,代码零默
|
||||
// === 防御规划 ===
|
||||
public interface IDefensePlanner
|
||||
{
|
||||
PlannerResult Plan(List<FireUnit> fireUnits, List<DroneGroup> threats, CombatScene environment);
|
||||
PlannerResult Plan(List<FireUnit> fireUnits, List<DroneWave> threats, CombatScene environment);
|
||||
}
|
||||
|
||||
// ThreatProfile / DefenseRecommendation / DefenseSolution 等模型见本节上文
|
||||
@ -1265,7 +1281,7 @@ public interface IFrameDataStore
|
||||
|
||||
| 阶段 | 内容 | 数据来源 |
|
||||
|------|------|------|
|
||||
| **仿真前** | 我方配置(装备类型/数量/编组、弹药类型/数量、部署位置、管控区域) | SimTask + 5 步配置 |
|
||||
| **仿真前** | 我方配置(装备类型/数量、火力单元部署、弹药类型/数量、部署位置、管控区域) | SimTask + 5 步配置 |
|
||||
| **仿真中** | 关键事件时序表、各实体轨迹概要、云团演化数据 | SimEvent + SimFrameRecord |
|
||||
| **仿真后** | 拦截成功率、各阶段耗时、毁伤效果评估、对抗结果判定 | SimEvent 汇总统计 |
|
||||
|
||||
@ -1297,14 +1313,6 @@ interface IModelService
|
||||
ModelInfo GetModel(string id);
|
||||
}
|
||||
|
||||
// === 编组管理 ===
|
||||
interface IGroupService
|
||||
{
|
||||
Group CreateGroup(string name, GroupType type, string description);
|
||||
void DeleteGroup(string id);
|
||||
List<Group> GetGroups(GroupType? type);
|
||||
}
|
||||
|
||||
// === 想定管理 ===
|
||||
interface IScenarioService
|
||||
{
|
||||
@ -1366,7 +1374,7 @@ interface IReportService
|
||||
|
||||
### 数据模型
|
||||
|
||||
一个仿真任务可包含**多种类型、多批次**的无人机,每批次(编队)有独立的航路:
|
||||
一个仿真任务可包含**多种类型、多批次**的无人机,每批次有独立的航路:
|
||||
|
||||
```
|
||||
SimTask "城市防御演习"
|
||||
@ -1374,51 +1382,51 @@ SimTask "城市防御演习"
|
||||
├── ControlZone[](共享)
|
||||
├── CloudDispersal(共享:云团参数,具体拦截方案由算法确定)
|
||||
│
|
||||
├── 编队 A(活塞×3) ← Group.Id
|
||||
│ ├── TargetConfig(类型=活塞,数量=3)
|
||||
│ ├── RoutePlan(航路 A:北→南)
|
||||
├── 批次 A(活塞×3) ← WaveId
|
||||
│ ├── TargetConfig(WaveId=A,类型=活塞,数量=3)
|
||||
│ ├── RoutePlan(TaskId + WaveId=A,航路 A:北→南)
|
||||
│ └── Waypoint[]
|
||||
│
|
||||
├── 编队 B(喷气×2) ← Group.Id
|
||||
│ ├── TargetConfig(类型=高速,数量=2)
|
||||
│ ├── RoutePlan(航路 B:西→东)
|
||||
├── 批次 B(喷气×2) ← WaveId
|
||||
│ ├── TargetConfig(WaveId=B,类型=高速,数量=2)
|
||||
│ ├── RoutePlan(TaskId + WaveId=B,航路 B:西→东)
|
||||
│ └── Waypoint[]
|
||||
│
|
||||
└── 编队 C(旋翼×5) ← Group.Id
|
||||
├── TargetConfig(类型=旋翼,数量=5)
|
||||
├── RoutePlan(航路 C)
|
||||
└── 批次 C(旋翼×5) ← WaveId
|
||||
├── TargetConfig(WaveId=C,类型=旋翼,数量=5)
|
||||
├── RoutePlan(TaskId + WaveId=C,航路 C)
|
||||
└── Waypoint[]
|
||||
```
|
||||
|
||||
**关键变更**:`RoutePlan` 主键从 `TaskId` 改为 `(TaskId, GroupId)` 复合键。
|
||||
**关键变更**:`RoutePlan` 主键为 `(TaskId, WaveId)` 复合键。`Waypoint` 通过 `(TaskId, WaveId)` 关联航路。
|
||||
|
||||
### 推荐算法
|
||||
|
||||
算法需要同时考虑**所有无人机编队**和**可用装备火力单元**,自动匹配分配:
|
||||
算法需要同时考虑**所有无人机批次**和**可用火力单元**,自动匹配分配:
|
||||
|
||||
```csharp
|
||||
// 输入:所有无人机编队 + 已部署的火力单元
|
||||
// 输出:每个编队的拦截方案 + 合并的发射计划
|
||||
|
||||
var allSolutions = new List<DefenseSolution>();
|
||||
var usedPlatforms = new HashSet<int>();
|
||||
var usedFireUnits = new HashSet<string>();
|
||||
|
||||
foreach (var droneGroup in threat.DroneGroups)
|
||||
foreach (var droneWave in threat.DroneWaves)
|
||||
{
|
||||
// 从可用装备中选择合适的火力单元(弹药类型匹配目标动力类型)
|
||||
var availableUnits = fireUnits.Where(u => !usedPlatforms.Contains(u.Id)).ToList();
|
||||
var solution = advisor.RecommendForGroup(droneGroup, environment, availableUnits);
|
||||
// 从可用火力单元中选择合适的(弹药类型匹配目标动力类型)
|
||||
var availableUnits = fireUnits.Where(u => !usedFireUnits.Contains(u.Id)).ToList();
|
||||
var solution = advisor.RecommendForWave(droneWave, environment, availableUnits);
|
||||
|
||||
// 标记占用的平台(一组装备可同时对付一个编队)
|
||||
foreach (var idx in solution.AssignedPlatformIndices)
|
||||
usedPlatforms.Add(idx);
|
||||
// 标记占用的火力单元
|
||||
foreach (var idx in solution.AssignedFireUnitIds)
|
||||
usedFireUnits.Add(idx);
|
||||
|
||||
allSolutions.Add(solution);
|
||||
}
|
||||
|
||||
// 复用判定(不写死"一对一"或"一对多"):
|
||||
// 同种弹药 → 只需冷却时间(秒级),可直接复用
|
||||
// 异种弹药 → 需要 AmmoChangeTime(默认 5 分钟),检查编队间隔是否够
|
||||
// 异种弹药 → 需要 AmmoChangeTime(默认 5 分钟),检查批次间隔是否够
|
||||
// 弹药耗尽 → 不可复用
|
||||
|
||||
var merged = MergeFireSchedules(allSolutions);
|
||||
@ -1435,13 +1443,13 @@ engine.SetFireSchedule(merged);
|
||||
|
||||
| 表 | 变更 |
|
||||
|------|------|
|
||||
| `RoutePlan` | PK 改为 (TaskId, GroupId) 复合键 |
|
||||
| `TargetConfig` | 不变(已有 GroupId FK) |
|
||||
| `Waypoint` | 关联 `(TaskId, GroupId)` 定位航路 |
|
||||
| `RoutePlan` | PK 为 (TaskId, WaveId) 复合键 |
|
||||
| `TargetConfig` | 不变(已有 WaveId) |
|
||||
| `Waypoint` | 关联 `(TaskId, WaveId)` 定位航路 |
|
||||
|
||||
`IScenarioService` 新增:
|
||||
`IScenarioService` 的 `SaveRoute` 签名:
|
||||
```csharp
|
||||
void SaveRoute(string taskId, string groupId, RoutePlan route, List<Waypoint> wps); // groupId 参数
|
||||
void SaveRoute(string taskId, string waveId, RoutePlan route, List<Waypoint> wps); // waveId 参数
|
||||
```
|
||||
|
||||
### 报告
|
||||
@ -1450,10 +1458,10 @@ void SaveRoute(string taskId, string groupId, RoutePlan route, List<Waypoint> wp
|
||||
|
||||
```markdown
|
||||
## 编队汇总
|
||||
| 编队 | 目标 | 数量 | 拦截方案 | 结果 |
|
||||
| 批次 | 目标 | 数量 | 拦截方案 | 结果 |
|
||||
|------|------|------|------|------|
|
||||
| 编队A | 活塞 | 3 | 惰性气体 12发 | ✅ 全毁 |
|
||||
| 编队B | 喷气 | 2 | 活性材料 8发 | ⚠️ 1逃脱 |
|
||||
| 批次A | 活塞 | 3 | 惰性气体 12发 | ✅ 全毁 |
|
||||
| 批次B | 喷气 | 2 | 活性材料 8发 | ⚠️ 1逃脱 |
|
||||
```
|
||||
|
||||
---
|
||||
@ -1473,7 +1481,7 @@ void SaveRoute(string taskId, string groupId, RoutePlan route, List<Waypoint> wp
|
||||
│ └─ 多边形绘制 / 编辑 / 删除 │
|
||||
├────────────────────────────────────────────────────────┤
|
||||
│ 步骤 2:目标配置 │
|
||||
│ ├─ 编组选择(已有编队/新建编队) │
|
||||
│ ├─ 批次选择(已有批次/新建批次) │
|
||||
│ ├─ 目标类型选择(自动填充默认参数) │
|
||||
│ ├─ 数量 / 动力类型 / 翼展 / 速度 / 高度 │
|
||||
│ └─ 预设典型目标库(NEW) │
|
||||
@ -1481,7 +1489,7 @@ void SaveRoute(string taskId, string groupId, RoutePlan route, List<Waypoint> wp
|
||||
│ 步骤 3:装备部署(重构) │
|
||||
│ ├─ 装备角色(探测设备 / 发射平台) │
|
||||
│ ├─ 发射平台类型(空基 / 地基 NEW) │
|
||||
│ ├─ 数量 / 编组 / 部署位置 │
|
||||
│ ├─ 数量 / 部署位置 │
|
||||
│ ├─ 挂载气溶胶类型 / 弹药数量 │
|
||||
│ ├─ 弹药初速 / 释放高度 / 冷却时间 │
|
||||
│ └─ 探测半径(探测设备) │
|
||||
@ -1521,7 +1529,7 @@ void SaveRoute(string taskId, string groupId, RoutePlan route, List<Waypoint> wp
|
||||
| 12 | 第三方数据 | **静态配置(JSON → SQLite)** | 仅提供弹药基础参数和初始扩散状态,不提供运行时算法 |
|
||||
| 13 | 运动学 | 简化运动学 + 风偏 | 航路点线性插值 + 风速矢量 |
|
||||
| 14 | 弹药弹道 | 抛物线(地基)/ 自由落体(空基) | 基于初速、角度、释放高度计算 |
|
||||
| 15 | 编组 | Group 独立实体 | 编组独立管理,任务中引用 |
|
||||
| 15 | 批次 / 火力单元 | WaveId + 火力单元独立作战 | 批次关联航路但不独占;火力单元是最小独立作战单位,不需要上层编组 |
|
||||
| 16 | 管控区域 | ControlZone 表 + Tick 判定 | 电子围栏,侵入即任务失败 |
|
||||
| 17 | 配置向导流程 | **威胁驱动三阶段** | Phase1 定义威胁 → Phase2 算法推荐 → Phase3 审阅确认 |
|
||||
| 18 | 报告导出 | 模板填充 → Markdown | PDF/Word 待调研 |
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
# DefensePlanner 防御规划引擎 — 技术方案
|
||||
|
||||
- **版本**:V3
|
||||
- **日期**:2026-06-13
|
||||
- **版本**:V4
|
||||
- **日期**:2026-06-15
|
||||
- **状态**:已实现
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
DefensePlanner 是防御推荐模块的核心引擎。它接收**可用火力单元池**和**威胁编队列表**,综合考虑弹药匹配、空间可达性、时间约束、资源竞争,输出**最优分配方案**和**临界边际方案**。
|
||||
DefensePlanner 是防御推荐模块的核心引擎。它接收**可用火力单元池**和**威胁批次列表**,综合考虑弹药匹配、空间可达性、时间约束、资源竞争,输出**最优分配方案**和**临界边际方案**。
|
||||
|
||||
**当前问题**:
|
||||
|
||||
@ -54,11 +54,11 @@ class FireUnit {
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 DroneGroup(威胁编队)
|
||||
### 2.2 DroneWave(威胁批次)
|
||||
|
||||
```csharp
|
||||
class DroneGroup {
|
||||
string GroupId; // 编队 ID
|
||||
class DroneWave {
|
||||
string WaveId; // 批次 ID
|
||||
TargetConfig Target; // 类型、数量、动力、翼展、速度、高度
|
||||
List<Waypoint> Waypoints; // 航路点
|
||||
float ArrivalTime; // 预计算:到达防御区域中点的时间 s
|
||||
@ -78,7 +78,7 @@ class DroneGroup {
|
||||
```csharp
|
||||
class UnitAssignment {
|
||||
string FireUnitId; // 分配到的火力单元
|
||||
string DroneGroupId; // 对抗的威胁编队
|
||||
string DroneWaveId; // 对抗的威胁批次
|
||||
AerosolType AmmoType; // 装填的弹药类型
|
||||
int RoundsFired; // 本次发射几发
|
||||
float FirstFireTime; // 首发发射时机 s
|
||||
@ -113,7 +113,7 @@ class PlannerResult {
|
||||
## 4. 内部流程(五步法)
|
||||
|
||||
```
|
||||
输入:List<FireUnit> + List<DroneGroup> + CombatScene
|
||||
输入:List<FireUnit> + List<DroneWave> + CombatScene
|
||||
│
|
||||
▼
|
||||
Step 1 — 威胁排序
|
||||
@ -190,7 +190,7 @@ class InterceptCandidate {
|
||||
|
||||
**候选生成逻辑**:
|
||||
|
||||
对于给定的威胁编队和火力单元:
|
||||
对于给定的威胁批次和火力单元:
|
||||
|
||||
1. **弹药兼容性**:`Unit.AmmoTypes` 包含威胁需要的弹药类型
|
||||
2. **地基可达性**:计算目标与部署点的水平距离,校验 `MuzzleVelocity` 射程
|
||||
@ -239,7 +239,7 @@ for each threat in pending:
|
||||
|
||||
> 作用:给操作员一个置信区间——最优 vs 临界,展示"再少就不够了"的底线。
|
||||
|
||||
### 5.5 多编队合并
|
||||
### 5.5 多批次合并
|
||||
|
||||
```
|
||||
Step 4 的贪心算法天然支持多威胁:
|
||||
@ -258,7 +258,7 @@ Step 4 的贪心算法天然支持多威胁:
|
||||
public interface IDefensePlanner
|
||||
{
|
||||
/// <summary>为给定火力单元池和威胁列表生成规划方案</summary>
|
||||
PlannerResult Plan(List<FireUnit> fireUnits, List<DroneGroup> threats, CombatScene environment);
|
||||
PlannerResult Plan(List<FireUnit> fireUnits, List<DroneWave> threats, CombatScene environment);
|
||||
}
|
||||
```
|
||||
|
||||
@ -273,16 +273,16 @@ public class DefaultDefensePlanner : IDefensePlanner
|
||||
|
||||
public DefaultDefensePlanner(List<AmmunitionSpec> ammoCatalog) { ... }
|
||||
|
||||
public PlannerResult Plan(List<FireUnit> fireUnits, List<DroneGroup> threats, CombatScene env)
|
||||
public PlannerResult Plan(List<FireUnit> fireUnits, List<DroneWave> threats, CombatScene env)
|
||||
{
|
||||
// Step 1-5
|
||||
}
|
||||
|
||||
// 内部方法
|
||||
private List<DroneGroup> Prioritize(List<DroneGroup> threats) { ... }
|
||||
private List<DroneWave> Prioritize(List<DroneWave> threats) { ... }
|
||||
private AerosolType MatchAmmo(PowerType power) { ... }
|
||||
private List<InterceptCandidate> GenerateCandidates(DroneGroup threat, List<FireUnit> units, float now) { ... }
|
||||
private DefensePlan Solve(List<DroneGroup> threats, List<FireUnit> units) { ... }
|
||||
private List<InterceptCandidate> GenerateCandidates(DroneWave threat, List<FireUnit> units, float now) { ... }
|
||||
private DefensePlan Solve(List<DroneWave> threats, List<FireUnit> units) { ... }
|
||||
private DefensePlan DeriveCritical(DefensePlan best) { ... }
|
||||
}
|
||||
```
|
||||
@ -293,7 +293,7 @@ public class DefaultDefensePlanner : IDefensePlanner
|
||||
|--------|--------|------|
|
||||
| `IDefenseAdvisor.Recommend(ThreatProfile)` | `IDefensePlanner.Plan(fireUnits, threats, env)` | ✅ 已替换 |
|
||||
| `IDefenseAdvisor.GetDefenseRecommendation(taskId)` | 不再需要 | ✅ 已删除 |
|
||||
| `DefaultDefenseAdvisor.RecommendMultiGroup()` | Planner 原生支持多编队 | ✅ 已删除 |
|
||||
| `DefaultDefenseAdvisor.RecommendMultiGroup()` | Planner 原生支持多批次 | ✅ 已删除 |
|
||||
|
||||
---
|
||||
|
||||
@ -304,7 +304,7 @@ SimulationEngine.Initialize(taskId)
|
||||
│
|
||||
│ // 引擎内部组装
|
||||
├── BuildFireUnits(config) → List<FireUnit>
|
||||
├── BuildDroneGroups(config) → List<DroneGroup>
|
||||
├── BuildDroneWaves(config) → List<DroneWave>
|
||||
└── _scene (CombatScene)
|
||||
│
|
||||
▼
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
SimulationEngine.Initialize()
|
||||
│
|
||||
├─ BuildFireUnits() → List<FireUnit>
|
||||
├─ BuildDroneGroups() → List<DroneGroup>
|
||||
├─ BuildDroneWaves() → List<DroneWave>
|
||||
│
|
||||
▼
|
||||
IDefensePlanner.Plan(fireUnits, threats, scene)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# 实施计划与任务跟踪
|
||||
|
||||
> **项目**:反无人机仿真系统后端
|
||||
> **文档版本**:V1.3
|
||||
> **更新日期**:2026-06-14
|
||||
> **文档版本**:V1.4
|
||||
> **更新日期**:2026-06-15
|
||||
|
||||
---
|
||||
|
||||
@ -32,7 +32,7 @@ Phase 8 🔄 待开发(天气/物理模型统一已完成)
|
||||
|
||||
| # | 任务 | 状态 |
|
||||
|---|------|------|
|
||||
| 1.1-1.12 | 项目骨架、枚举、数据模型、Repository、ModelService/GroupService、单元测试 | ✅ |
|
||||
| 1.1-1.12 | 项目骨架、枚举、数据模型、Repository、ModelService、单元测试 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
@ -105,7 +105,7 @@ Phase 8 🔄 待开发(天气/物理模型统一已完成)
|
||||
| 6.1 | 创建 Unity 项目,导入 Core.dll | 1h | ✅ | `src/Unity/` 项目,16 个 DLL → `Assets/Plugins/` |
|
||||
| 6.2 | 实现 `UnityPathProvider` | 1h | ✅ | `Application.persistentDataPath` 桥接 |
|
||||
| 6.3 | 实现 `ModelManager` | 3h | ✅ | 导入/删除/查询,含 Verify |
|
||||
| 6.4 | 实现 `ScenarioManager`(5 步配置) | 4h | ✅ | 完整 CRUD + 搜索分页 + 多编队 Route |
|
||||
| 6.4 | 实现 `ScenarioManager`(5 步配置) | 4h | ✅ | 完整 CRUD + 搜索分页 + 多批次 Route |
|
||||
| 6.5 | 实现 `SimulationRunner`(Update 驱动) | 3h | ✅ | Tick 驱动 + 事件订阅 + 实体位置同步 + 炮弹轨迹可视化 |
|
||||
| 6.6 | 实现 `ReplayController`(帧加载) | 3h | ✅ | 从分库加载帧数据,TotalFrames/GetFrame |
|
||||
| 6.7 | 实现 `ReportManager` | 2h | ✅ | 生成 + Markdown 导出 |
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# 后端对接文档(Unity 前端)
|
||||
|
||||
> **版本**:V1.1
|
||||
> **日期**:2026-06-12
|
||||
> **版本**:V1.2
|
||||
> **日期**:2026-06-15
|
||||
> **Unity 版本**:2022.3.62f3c1
|
||||
|
||||
---
|
||||
@ -35,17 +35,21 @@ data/default_ammo.json ← 默认弹药参数(放到 persisten
|
||||
var scenario = GetComponent<ScenarioManager>();
|
||||
var task = scenario.CreateTask("拦截活塞式无人机", "");
|
||||
scenario.SaveScene(task.Id, new CombatScene { WindSpeed = 3 });
|
||||
scenario.SaveTarget(task.Id, new TargetConfig { PowerType = (int)PowerType.Piston, ... });
|
||||
scenario.SaveRoute(task.Id, routePlan, waypoints);
|
||||
scenario.SaveTarget(task.Id, new TargetConfig { WaveId = "default", PowerType = (int)PowerType.Piston, ... });
|
||||
scenario.SaveRoute(task.Id, "default", routePlan, waypoints);
|
||||
|
||||
// 推荐方案(自动从数据库读弹药规格)
|
||||
// 推荐方案(DefensePlanner 五步流水线)
|
||||
var detail = scenario.GetDetail(task.Id);
|
||||
var ammoCatalog = db.Table<AmmunitionSpec>().ToList(); // DatabaseManager 已种子
|
||||
var rec = new DefaultDefenseAdvisor(ammoCatalog).Recommend(new ThreatProfile { ... });
|
||||
var fireUnits = new List<FireUnit> { /* 从 EquipmentDeployment 构建 */ };
|
||||
var threats = new List<DroneWave> { new DroneWave { WaveId = "default", Target = detail.Targets[0], Route = detail.Routes[0], Waypoints = detail.WaypointGroups["default"] } };
|
||||
var plan = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths)).Plan(fireUnits, threats, detail.Scene, new List<DetectionSource>());
|
||||
|
||||
// 应用到想定
|
||||
scenario.SaveCloud(task.Id, rec.Best.RecommendedCloud);
|
||||
scenario.SaveDeployment(task.Id, rec.Best.Platforms.Select(p => new EquipmentDeployment { ... }).ToList());
|
||||
// 应用到想定(发射计划传给引擎,部署方案保存到配置)
|
||||
var mergedSchedule = plan.Best.MergedSchedule;
|
||||
// 部署火力单元到 EquipmentDeployment...
|
||||
scenario.SaveCloud(task.Id, new CloudDispersal { /* 从 plan.Best.Assignments 推算云团参数 */ });
|
||||
scenario.SaveDeployment(task.Id, equips);
|
||||
```
|
||||
|
||||
### 3.2 启动仿真(事件驱动)
|
||||
@ -54,7 +58,7 @@ scenario.SaveDeployment(task.Id, rec.Best.Platforms.Select(p => new EquipmentDep
|
||||
var runner = GetComponent<SimulationRunner>();
|
||||
|
||||
// 推荐方案的 FireSchedule 直接传给引擎
|
||||
runner.Engine.SetFireSchedule(rec.Best.FireSchedule);
|
||||
runner.Engine.SetFireSchedule(plan.Best.MergedSchedule);
|
||||
runner.LoadAndStart(task.Id);
|
||||
runner.Engine.TimeScale = 4f; // 加速
|
||||
|
||||
@ -93,7 +97,7 @@ mgr.SaveScene(id, combatScene);
|
||||
mgr.SaveTarget(id, targetConfig);
|
||||
mgr.SaveDeployment(id, equipmentList);
|
||||
mgr.SaveCloud(id, cloudDispersal);
|
||||
mgr.SaveRoute(id, routePlan, waypoints);
|
||||
mgr.SaveRoute(id, "default", routePlan, waypoints);
|
||||
TaskFullConfig detail = mgr.GetDetail(id);
|
||||
PagedResult<SimTask> result = mgr.Search("关键词", from, to, page, pageSize);
|
||||
```
|
||||
@ -163,4 +167,4 @@ dotnet publish -c Release -o ../../unity_plugins
|
||||
| 实体事件映射 | `docs/design/technical/仿真器实体与事件映射.md` |
|
||||
| 任务跟踪 | `docs/implementation/tasks/实施计划与任务跟踪.md` |
|
||||
| 测试报告 | `test/reports/`(每次集成测试自动生成) |
|
||||
| 测试状态 | **129 测试,95.4% 行覆盖率,12 秒** |
|
||||
| 测试状态 | **204 测试,95.4% 行覆盖率,63 秒** |
|
||||
|
||||
@ -46,10 +46,10 @@ namespace CounterDrone.Core.Algorithms
|
||||
public float MuzzleVelocity { get; set; } = 800f;
|
||||
}
|
||||
|
||||
/// <summary>无人机编队(规划器输入)</summary>
|
||||
public class DroneGroup
|
||||
/// <summary>无人机批次(规划器输入)</summary>
|
||||
public class DroneWave
|
||||
{
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
public string WaveId { get; set; } = string.Empty;
|
||||
public TargetConfig Target { get; set; } = new();
|
||||
public RoutePlan Route { get; set; } = new();
|
||||
public List<Waypoint> Waypoints { get; set; } = new();
|
||||
@ -120,7 +120,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
public class UnitAssignment
|
||||
{
|
||||
public string FireUnitId { get; set; } = string.Empty;
|
||||
public string DroneGroupId { get; set; } = string.Empty;
|
||||
public string DroneWaveId { get; set; } = string.Empty;
|
||||
public AerosolType AmmoType { get; set; }
|
||||
public int RoundsFired { get; set; }
|
||||
public float FirstFireTime { get; set; }
|
||||
|
||||
@ -26,7 +26,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
// 五步流水线
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
public PlannerResult Plan(List<FireUnit> fireUnits, List<DroneGroup> threats,
|
||||
public PlannerResult Plan(List<FireUnit> fireUnits, List<DroneWave> threats,
|
||||
CombatScene environment, List<DetectionSource> detectionSources)
|
||||
{
|
||||
var result = new PlannerResult();
|
||||
@ -108,7 +108,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
// Step 3-4: 候选生成 → 贪心分配
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
private DefensePlan Solve(List<DroneGroup> sortedThreats,
|
||||
private DefensePlan Solve(List<DroneWave> sortedThreats,
|
||||
List<FireUnit> fireUnits, CombatScene env)
|
||||
{
|
||||
var plan = new DefensePlan();
|
||||
@ -215,7 +215,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
plan.Assignments.Add(new UnitAssignment
|
||||
{
|
||||
FireUnitId = unit.Id,
|
||||
DroneGroupId = threat.GroupId,
|
||||
DroneWaveId = threat.WaveId,
|
||||
AmmoType = neededAmmo,
|
||||
RoundsFired = rounds,
|
||||
FirstFireTime = fireEvents.Count > 0 ? fireEvents[0].FireTime : 0,
|
||||
@ -236,7 +236,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
var a2 = _ammoCatalog.FirstOrDefault(s =>
|
||||
s.AerosolType == (int)MatchAmmo(_config, (PowerType)threat.Target.PowerType));
|
||||
int totalRounds = plan.Assignments
|
||||
.Where(a => a.DroneGroupId == threat.GroupId)
|
||||
.Where(a => a.DroneWaveId == threat.WaveId)
|
||||
.Sum(a => a.RoundsFired);
|
||||
if (totalRounds > 0)
|
||||
threatProbs.Add(ComputeInterceptProbability(threat, a2, totalRounds, env));
|
||||
@ -253,7 +253,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
// 候选生成(使用真实物理)
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
private List<InterceptCandidate> GenerateCandidates(DroneGroup threat,
|
||||
private List<InterceptCandidate> GenerateCandidates(DroneWave threat,
|
||||
List<FireUnit> availableUnits, CombatScene env)
|
||||
{
|
||||
var candidates = new List<InterceptCandidate>();
|
||||
@ -281,7 +281,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private InterceptCandidate? BuildGroundBasedCandidate(DroneGroup threat,
|
||||
private InterceptCandidate? BuildGroundBasedCandidate(DroneWave threat,
|
||||
FireUnit unit, AerosolType ammoType, AmmunitionSpec ammo,
|
||||
float effectiveR, CombatScene env)
|
||||
{
|
||||
@ -311,7 +311,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
};
|
||||
}
|
||||
|
||||
private InterceptCandidate? BuildAirBasedCandidate(DroneGroup threat,
|
||||
private InterceptCandidate? BuildAirBasedCandidate(DroneWave threat,
|
||||
FireUnit unit, AerosolType ammoType, AmmunitionSpec ammo,
|
||||
float effectiveR, CombatScene env)
|
||||
{
|
||||
@ -358,7 +358,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
return (effectiveR, expansionTime, rPhase2);
|
||||
}
|
||||
|
||||
private int CalcRoundsNeeded(DroneGroup threat, AmmunitionSpec ammo,
|
||||
private int CalcRoundsNeeded(DroneWave threat, AmmunitionSpec ammo,
|
||||
CombatScene env, bool isAirBased, float turbulentRadius)
|
||||
{
|
||||
var cloudModel = new CloudExpansionModel(ammo, env);
|
||||
@ -371,7 +371,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
return cloudModel.RoundsNeeded(requiredCoverage, spacing);
|
||||
}
|
||||
|
||||
private float ComputeInterceptProbability(DroneGroup threat,
|
||||
private float ComputeInterceptProbability(DroneWave threat,
|
||||
AmmunitionSpec ammo, int rounds, CombatScene env)
|
||||
{
|
||||
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
|
||||
@ -390,7 +390,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
// 发射事件生成(真实物理)
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
private List<FireEvent> GenerateFireEventsAt(DroneGroup threat, FireUnit unit,
|
||||
private List<FireEvent> GenerateFireEventsAt(DroneWave threat, FireUnit unit,
|
||||
AerosolType ammoType, AmmunitionSpec ammo, CombatScene env, float targetOffset,
|
||||
int yLane, int yLanes, float formationWidth)
|
||||
{
|
||||
@ -517,7 +517,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
var reduced = new UnitAssignment
|
||||
{
|
||||
FireUnitId = assignment.FireUnitId,
|
||||
DroneGroupId = assignment.DroneGroupId,
|
||||
DroneWaveId = assignment.DroneWaveId,
|
||||
AmmoType = assignment.AmmoType,
|
||||
RoundsFired = rounds,
|
||||
FirstFireTime = assignment.FirstFireTime,
|
||||
@ -537,7 +537,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
// 辅助
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
private static Vector3 ThreatMidpoint(DroneGroup threat)
|
||||
private static Vector3 ThreatMidpoint(DroneWave threat)
|
||||
{
|
||||
if (threat.Waypoints.Count < 2)
|
||||
return new Vector3(0, (float)threat.Target.TypicalAltitude, 0);
|
||||
|
||||
@ -8,10 +8,10 @@ namespace CounterDrone.Core.Algorithms
|
||||
public interface IDefensePlanner
|
||||
{
|
||||
/// <param name="fireUnits">可用的火力单元(空基/地基统一表达)</param>
|
||||
/// <param name="threats">威胁编队列表</param>
|
||||
/// <param name="threats">威胁批次列表</param>
|
||||
/// <param name="environment">作战环境(含天气,影响光电探测距离)</param>
|
||||
/// <param name="detectionSources">统一信息网络的探测源列表(独立探测设备 + 火力单元自带探测)。可为空列表。</param>
|
||||
PlannerResult Plan(List<FireUnit> fireUnits, List<DroneGroup> threats,
|
||||
PlannerResult Plan(List<FireUnit> fireUnits, List<DroneWave> threats,
|
||||
CombatScene environment, List<DetectionSource> detectionSources);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,9 +68,8 @@ namespace CounterDrone.Core
|
||||
db.CreateTable<EquipmentDeployment>();
|
||||
db.CreateTable<CloudDispersal>();
|
||||
db.CreateTable<RoutePlan>();
|
||||
db.CreateIndex("RoutePlan", new[] { "TaskId", "GroupId" }, true);
|
||||
db.CreateIndex("RoutePlan", new[] { "TaskId", "WaveId" }, true);
|
||||
db.CreateTable<Waypoint>();
|
||||
db.CreateTable<Group>();
|
||||
db.CreateTable<SimulationReport>();
|
||||
|
||||
db.CreateIndex("SimTask", "TaskNumber");
|
||||
|
||||
@ -92,7 +92,7 @@ namespace CounterDrone.Core.Models
|
||||
Destroyed = 3
|
||||
}
|
||||
|
||||
// === 编队 & 编组 ===
|
||||
// === 编队 & 批次 ===
|
||||
public enum FormationMode
|
||||
{
|
||||
Single = 0,
|
||||
@ -100,12 +100,6 @@ namespace CounterDrone.Core.Models
|
||||
Swarm = 2
|
||||
}
|
||||
|
||||
public enum GroupType
|
||||
{
|
||||
DroneFleet = 0,
|
||||
EquipmentGroup = 1
|
||||
}
|
||||
|
||||
// === 运行时 ===
|
||||
public enum EntityType
|
||||
{
|
||||
|
||||
@ -17,7 +17,7 @@ namespace CounterDrone.Core.Models
|
||||
|
||||
public int Quantity { get; set; } = 1;
|
||||
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
public string WaveId { get; set; } = string.Empty;
|
||||
|
||||
// 发射平台专用
|
||||
public int? PlatformType { get; set; }
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>编组</summary>
|
||||
[Table("Group")]
|
||||
public class Group
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
|
||||
[NotNull]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public int GroupType { get; set; } = (int)Models.GroupType.DroneFleet;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public string CreatedAt { get; set; } = DateTime.UtcNow.ToString("o");
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>步骤5:航路规划 — 多编队支持:每个(任务,编队)一条航路</summary>
|
||||
/// <summary>步骤5:航路规划 — 多批次支持:每个(任务,批次)一条航路</summary>
|
||||
[Table("RoutePlan")]
|
||||
public class RoutePlan
|
||||
{
|
||||
@ -14,7 +14,7 @@ namespace CounterDrone.Core.Models
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
[Indexed]
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
public string WaveId { get; set; } = string.Empty;
|
||||
|
||||
public int FormationMode { get; set; } = (int)Models.FormationMode.Single;
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ namespace CounterDrone.Core.Models
|
||||
[Indexed]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
public string WaveId { get; set; } = string.Empty;
|
||||
|
||||
public int TargetType { get; set; } = (int)Models.TargetType.Rotor;
|
||||
|
||||
|
||||
@ -11,9 +11,9 @@ namespace CounterDrone.Core.Models
|
||||
public List<TargetConfig> Targets { get; set; } = new();
|
||||
public List<EquipmentDeployment> Equipment { get; set; } = new();
|
||||
public CloudDispersal Cloud { get; set; } = new();
|
||||
/// <summary>多编队航路(多批次支持)</summary>
|
||||
/// <summary>多批次航路(多批次支持)</summary>
|
||||
public List<RoutePlan> Routes { get; set; } = new();
|
||||
/// <summary>按 GroupId 分组的航路点</summary>
|
||||
/// <summary>按 WaveId 分组的航路点</summary>
|
||||
public Dictionary<string, List<Waypoint>> WaypointGroups { get; set; } = new();
|
||||
}
|
||||
|
||||
|
||||
@ -13,8 +13,8 @@ namespace CounterDrone.Core.Models
|
||||
[Indexed]
|
||||
public string TaskId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>关联的编队 ID(多编队支持)</summary>
|
||||
public string GroupId { get; set; } = string.Empty;
|
||||
/// <summary>关联的批次 ID(多批次支持)</summary>
|
||||
public string WaveId { get; set; } = string.Empty;
|
||||
|
||||
[NotNull]
|
||||
public int OrderIndex { get; set; }
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CounterDrone.Core.Models;
|
||||
using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Repository
|
||||
{
|
||||
public class GroupRepository : BaseRepository<Group>
|
||||
{
|
||||
public GroupRepository(SQLiteConnection db) : base(db) { }
|
||||
|
||||
public List<Group> GetByType(int groupType)
|
||||
{
|
||||
return Db.Table<Group>()
|
||||
.Where(g => g.GroupType == groupType)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,10 +14,10 @@ namespace CounterDrone.Core.Repository
|
||||
return Db.Table<RoutePlan>().Where(r => r.TaskId == taskId).ToList();
|
||||
}
|
||||
|
||||
public RoutePlan GetByTaskAndGroup(string taskId, string groupId)
|
||||
public RoutePlan GetByTaskAndWave(string taskId, string waveId)
|
||||
{
|
||||
return Db.Table<RoutePlan>()
|
||||
.FirstOrDefault(r => r.TaskId == taskId && r.GroupId == groupId);
|
||||
.FirstOrDefault(r => r.TaskId == taskId && r.WaveId == waveId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,10 +17,10 @@ namespace CounterDrone.Core.Repository
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public List<Waypoint> GetByTaskAndGroup(string taskId, string groupId)
|
||||
public List<Waypoint> GetByTaskAndWave(string taskId, string waveId)
|
||||
{
|
||||
return Db.Table<Waypoint>()
|
||||
.Where(w => w.TaskId == taskId && w.GroupId == groupId)
|
||||
.Where(w => w.TaskId == taskId && w.WaveId == waveId)
|
||||
.OrderBy(w => w.OrderIndex)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
|
||||
namespace CounterDrone.Core.Services
|
||||
{
|
||||
public class GroupService : IGroupService
|
||||
{
|
||||
private readonly GroupRepository _repo;
|
||||
|
||||
public GroupService(GroupRepository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public Group CreateGroup(string name, GroupType type, string description)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("编组名称不能为空");
|
||||
|
||||
var group = new Group
|
||||
{
|
||||
Name = name,
|
||||
GroupType = (int)type,
|
||||
Description = description,
|
||||
};
|
||||
|
||||
_repo.Insert(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
public void DeleteGroup(string id)
|
||||
{
|
||||
_repo.Delete(id);
|
||||
}
|
||||
|
||||
public List<Group> GetGroups(GroupType? type)
|
||||
{
|
||||
if (type.HasValue)
|
||||
return _repo.GetByType((int)type.Value);
|
||||
return _repo.GetAll();
|
||||
}
|
||||
|
||||
public Group GetGroup(string id)
|
||||
{
|
||||
return _repo.GetById(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Services
|
||||
{
|
||||
/// <summary>编组管理服务</summary>
|
||||
public interface IGroupService
|
||||
{
|
||||
Group CreateGroup(string name, GroupType type, string description);
|
||||
void DeleteGroup(string id);
|
||||
List<Group> GetGroups(GroupType? type);
|
||||
Group GetGroup(string id);
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,7 @@ namespace CounterDrone.Core.Services
|
||||
List<EquipmentDeployment> GetDetections(string taskId);
|
||||
|
||||
void SaveCloudDispersal(string taskId, CloudDispersal cloud);
|
||||
void SaveRoute(string taskId, string groupId, RoutePlan route, List<Waypoint> waypoints);
|
||||
void SaveRoute(string taskId, string waveId, RoutePlan route, List<Waypoint> waypoints);
|
||||
void UpdateStep(string taskId, int step);
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ namespace CounterDrone.Core.Services
|
||||
Cloud = _cloudRepo.GetById(id) ?? new CloudDispersal { TaskId = id },
|
||||
Routes = _routeRepo.GetByTaskId(id),
|
||||
WaypointGroups = _waypointRepo.GetByTaskId(id)
|
||||
.GroupBy(w => w.GroupId)
|
||||
.GroupBy(w => w.WaveId)
|
||||
.ToDictionary(g => g.Key, g => g.OrderBy(w => w.OrderIndex).ToList()),
|
||||
};
|
||||
}
|
||||
@ -189,11 +189,11 @@ namespace CounterDrone.Core.Services
|
||||
TouchTask(taskId);
|
||||
}
|
||||
|
||||
public void SaveRoute(string taskId, string groupId, RoutePlan route, List<Waypoint> waypoints)
|
||||
public void SaveRoute(string taskId, string waveId, RoutePlan route, List<Waypoint> waypoints)
|
||||
{
|
||||
route.TaskId = taskId;
|
||||
route.GroupId = groupId;
|
||||
var existing = _routeRepo.GetByTaskAndGroup(taskId, groupId);
|
||||
route.WaveId = waveId;
|
||||
var existing = _routeRepo.GetByTaskAndWave(taskId, waveId);
|
||||
if (existing != null)
|
||||
{
|
||||
route.Id = existing.Id;
|
||||
@ -202,13 +202,13 @@ namespace CounterDrone.Core.Services
|
||||
else
|
||||
_routeRepo.Insert(route);
|
||||
|
||||
// 删除该编队的旧航路点,插入新的
|
||||
var oldWps = _waypointRepo.GetByTaskAndGroup(taskId, groupId);
|
||||
// 删除该批次的旧航路点,插入新的
|
||||
var oldWps = _waypointRepo.GetByTaskAndWave(taskId, waveId);
|
||||
foreach (var w in oldWps) _waypointRepo.Delete(w.Id);
|
||||
for (int i = 0; i < waypoints.Count; i++)
|
||||
{
|
||||
waypoints[i].TaskId = taskId;
|
||||
waypoints[i].GroupId = groupId;
|
||||
waypoints[i].WaveId = waveId;
|
||||
waypoints[i].OrderIndex = i;
|
||||
if (string.IsNullOrEmpty(waypoints[i].Id))
|
||||
waypoints[i].Id = Guid.NewGuid().ToString();
|
||||
|
||||
@ -8,7 +8,7 @@ namespace CounterDrone.Core.Simulation
|
||||
public class DroneEntity
|
||||
{
|
||||
public string Id { get; }
|
||||
public string GroupId { get; }
|
||||
public string WaveId { get; }
|
||||
public TargetType TargetType { get; }
|
||||
public PowerType PowerType { get; }
|
||||
public float Wingspan { get; }
|
||||
@ -27,11 +27,11 @@ namespace CounterDrone.Core.Simulation
|
||||
/// <summary>沿航路已飞行弧长(米),由 RouteGeometry 驱动</summary>
|
||||
private float _traveledArc;
|
||||
|
||||
public DroneEntity(string id, string groupId, TargetConfig config, List<Waypoint> route,
|
||||
public DroneEntity(string id, string waveId, TargetConfig config, List<Waypoint> route,
|
||||
int formationIndex, float lateralSpacing, int longitudinalIndex, float longitudinalSpacing, FormationMode mode)
|
||||
{
|
||||
Id = id;
|
||||
GroupId = groupId;
|
||||
WaveId = waveId;
|
||||
TargetType = (TargetType)config.TargetType;
|
||||
PowerType = (PowerType)config.PowerType;
|
||||
Wingspan = (float)config.Wingspan;
|
||||
|
||||
@ -88,8 +88,8 @@ namespace CounterDrone.Core.Simulation
|
||||
_drones.Clear();
|
||||
foreach (var target in config.Targets)
|
||||
{
|
||||
var route = config.Routes.FirstOrDefault(r => r.GroupId == target.GroupId);
|
||||
var wps = config.WaypointGroups.GetValueOrDefault(target.GroupId, new List<Waypoint>());
|
||||
var route = config.Routes.FirstOrDefault(r => r.WaveId == target.WaveId);
|
||||
var wps = config.WaypointGroups.GetValueOrDefault(target.WaveId, new List<Waypoint>());
|
||||
if (route == null || wps.Count == 0) continue;
|
||||
var mode = (FormationMode)route.FormationMode;
|
||||
var lateralSpacing = (float)route.LateralSpacing;
|
||||
@ -100,7 +100,7 @@ namespace CounterDrone.Core.Simulation
|
||||
{
|
||||
int latIdx = i % latCount;
|
||||
int longIdx = i / latCount;
|
||||
_drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.GroupId,
|
||||
_drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.WaveId,
|
||||
target, wps, latIdx, lateralSpacing, longIdx, longSpacing, mode));
|
||||
}
|
||||
}
|
||||
@ -137,7 +137,7 @@ namespace CounterDrone.Core.Simulation
|
||||
if (_fireSchedule.Count == 0)
|
||||
{
|
||||
var fireUnits = BuildFireUnits(config);
|
||||
var threats = BuildDroneGroups(config);
|
||||
var threats = BuildDroneWaves(config);
|
||||
if (fireUnits.Count > 0 && threats.Count > 0)
|
||||
{
|
||||
var detectionSources = BuildDetectionSources(config);
|
||||
@ -378,16 +378,16 @@ namespace CounterDrone.Core.Simulation
|
||||
return sources;
|
||||
}
|
||||
|
||||
private static List<DroneGroup> BuildDroneGroups(TaskFullConfig config)
|
||||
private static List<DroneWave> BuildDroneWaves(TaskFullConfig config)
|
||||
{
|
||||
var groups = new List<DroneGroup>();
|
||||
var groups = new List<DroneWave>();
|
||||
foreach (var t in config.Targets)
|
||||
{
|
||||
var route = config.Routes.FirstOrDefault(r => r.GroupId == t.GroupId);
|
||||
var wps = config.WaypointGroups.GetValueOrDefault(t.GroupId, new List<Waypoint>());
|
||||
groups.Add(new DroneGroup
|
||||
var route = config.Routes.FirstOrDefault(r => r.WaveId == t.WaveId);
|
||||
var wps = config.WaypointGroups.GetValueOrDefault(t.WaveId, new List<Waypoint>());
|
||||
groups.Add(new DroneWave
|
||||
{
|
||||
GroupId = t.GroupId,
|
||||
WaveId = t.WaveId,
|
||||
Target = t,
|
||||
Route = route ?? new RoutePlan(),
|
||||
Waypoints = wps,
|
||||
|
||||
Binary file not shown.
@ -1,45 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
using CounterDrone.Core.Services;
|
||||
using UnityEngine;
|
||||
|
||||
namespace CounterDrone.Unity
|
||||
{
|
||||
/// <summary>编组管理桥接 — 无人机编队和装备编组的 CRUD</summary>
|
||||
public class GroupManager : MonoBehaviour
|
||||
{
|
||||
private IGroupService _service;
|
||||
private SQLite.SQLiteConnection _db;
|
||||
|
||||
public IGroupService Service => _service;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
var paths = new UnityPathProvider();
|
||||
_db = new DatabaseManager(paths).OpenMainDb();
|
||||
SqliteConnectionTracker.Track(_db);
|
||||
_service = new GroupService(new GroupRepository(_db));
|
||||
}
|
||||
|
||||
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
|
||||
|
||||
public Group Create(string name, GroupType type, string desc = "")
|
||||
=> _service.CreateGroup(name, type, desc);
|
||||
|
||||
public void Delete(string id) => _service.DeleteGroup(id);
|
||||
|
||||
public List<Group> GetByType(GroupType? type = null) => _service.GetGroups(type);
|
||||
|
||||
public Group Get(string id) => _service.GetGroup(id);
|
||||
|
||||
[ContextMenu("Verify")]
|
||||
void Verify()
|
||||
{
|
||||
Awake();
|
||||
var g = Create("测试编队", GroupType.DroneFleet, "自动验证");
|
||||
Debug.Log($"GroupManager OK. Created: {g.Name} ({g.Id})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24c080ea3d6cbcf45aa804fd10269e4a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -35,7 +35,7 @@ namespace CounterDrone.Unity
|
||||
scenarioMgr.SaveScene(taskId, new CombatScene { WindSpeed = 0 });
|
||||
scenarioMgr.SaveTarget(taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
||||
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
||||
});
|
||||
@ -54,9 +54,9 @@ namespace CounterDrone.Unity
|
||||
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
|
||||
|
||||
var detail = scenarioMgr.GetDetail(taskId);
|
||||
var droneGroup = new DroneGroup
|
||||
var droneGroup = new DroneWave
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
Target = detail.Targets[0],
|
||||
Route = detail.Routes[0],
|
||||
Waypoints = detail.WaypointGroups["default"],
|
||||
@ -72,7 +72,7 @@ namespace CounterDrone.Unity
|
||||
TotalMunitions = 3,
|
||||
AmmoTypes = new() { AerosolType.InertGas, AerosolType.ActiveMaterial, AerosolType.ActiveFuel },
|
||||
});
|
||||
var result = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths)).Plan(fireUnits, new List<DroneGroup> { 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
|
||||
{
|
||||
PositionX = (droneGroup.Waypoints[0].PosX + droneGroup.Waypoints[^1].PosX) / 2,
|
||||
|
||||
@ -53,7 +53,7 @@ namespace CounterDrone.Unity
|
||||
});
|
||||
scenario.SaveTarget(taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
||||
Quantity = 1, TypicalSpeed = _droneSpeed, TypicalAltitude = 500,
|
||||
});
|
||||
@ -68,9 +68,9 @@ namespace CounterDrone.Unity
|
||||
var detail = scenario.GetTaskDetail(taskId);
|
||||
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
|
||||
var planner = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths));
|
||||
var droneGroup = new DroneGroup
|
||||
var droneGroup = new DroneWave
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
Target = detail.Targets[0],
|
||||
Route = detail.Routes[0],
|
||||
Waypoints = detail.WaypointGroups["default"],
|
||||
@ -86,7 +86,7 @@ namespace CounterDrone.Unity
|
||||
TotalMunitions = 3,
|
||||
AmmoTypes = new() { AerosolType.InertGas, AerosolType.ActiveMaterial, AerosolType.ActiveFuel },
|
||||
});
|
||||
var result = planner.Plan(fireUnits, new List<DroneGroup> { droneGroup }, detail.Scene, new List<DetectionSource>());
|
||||
var result = planner.Plan(fireUnits, new List<DroneWave> { droneGroup }, detail.Scene, new List<DetectionSource>());
|
||||
scenario.SaveCloudDispersal(taskId, new CloudDispersal
|
||||
{
|
||||
PositionX = (droneGroup.Waypoints[0].PosX + droneGroup.Waypoints[^1].PosX) / 2,
|
||||
|
||||
@ -31,12 +31,12 @@ namespace CounterDrone.Core.Tests
|
||||
{
|
||||
var db = _dbManager.OpenMainDb();
|
||||
|
||||
// 验证所有 12 张表存在
|
||||
// 验证所有 11 张表存在
|
||||
var tables = new[]
|
||||
{
|
||||
"ModelInfo", "AmmunitionSpec", "SimTask", "CombatScene",
|
||||
"ControlZone", "TargetConfig", "EquipmentDeployment",
|
||||
"CloudDispersal", "RoutePlan", "Waypoint", "Group",
|
||||
"CloudDispersal", "RoutePlan", "Waypoint",
|
||||
"SimulationReport"
|
||||
};
|
||||
|
||||
|
||||
@ -37,12 +37,12 @@ namespace CounterDrone.Core.Tests
|
||||
AmmoTypes = new() { AerosolType.InertGas },
|
||||
};
|
||||
|
||||
private static DroneGroup MakeThreat(PowerType power = PowerType.Piston, float speed = 120f,
|
||||
private static DroneWave MakeThreat(PowerType power = PowerType.Piston, float speed = 120f,
|
||||
float startX = 0, float endX = 10000, float alt = 500)
|
||||
{
|
||||
var t = new DroneGroup
|
||||
var t = new DroneWave
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
Target = new TargetConfig
|
||||
{
|
||||
TargetType = (int)TargetType.Piston, PowerType = (int)power,
|
||||
@ -57,7 +57,7 @@ namespace CounterDrone.Core.Tests
|
||||
return t;
|
||||
}
|
||||
|
||||
private PlannerResult Plan(List<FireUnit> units, DroneGroup threat,
|
||||
private PlannerResult Plan(List<FireUnit> units, DroneWave threat,
|
||||
CombatScene? env = null)
|
||||
=> new DefensePlanner(TestAmmo, TestPlannerConfig.Instance).Plan(units, new() { threat }, env ?? new CombatScene(), new List<DetectionSource>());
|
||||
|
||||
@ -291,8 +291,8 @@ namespace CounterDrone.Core.Tests
|
||||
[Fact]
|
||||
public void MultiThreat_BothEngaged()
|
||||
{
|
||||
var a = MakeThreat(PowerType.Piston, 120); a.GroupId = "g0";
|
||||
var b = MakeThreat(PowerType.Jet, 300); b.GroupId = "g1";
|
||||
var a = MakeThreat(PowerType.Piston, 120); a.WaveId = "g0";
|
||||
var b = MakeThreat(PowerType.Jet, 300); b.WaveId = "g1";
|
||||
var result = new DefensePlanner(TestAmmo, TestPlannerConfig.Instance).Plan(
|
||||
new() { MakeGroundUnit("u0", 5000), MakeGroundUnit("u1", 5100) },
|
||||
new() { a, b }, new CombatScene(), new List<DetectionSource>());
|
||||
@ -405,9 +405,9 @@ namespace CounterDrone.Core.Tests
|
||||
// Z 向航路:从 (5000,0) 飞向 (5000,10000),即沿 +Z 方向
|
||||
// 之前 offset 写死 X 轴时,多发云团会沿 X 发散到 5000±N×spacing,偏离航路
|
||||
// 改用 RouteGeometry 后,offset 沿航路切向(+Z),云团应集中在 X=5000
|
||||
var threat = new DroneGroup
|
||||
var threat = new DroneWave
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
Target = new TargetConfig
|
||||
{
|
||||
TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
||||
|
||||
@ -49,7 +49,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.Piston,
|
||||
Quantity = 1,
|
||||
TypicalSpeed = 100,
|
||||
@ -92,7 +92,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.Piston,
|
||||
Quantity = 1,
|
||||
TypicalSpeed = 600, // 高速对抗强风
|
||||
@ -131,7 +131,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.HighSpeed,
|
||||
Quantity = 1,
|
||||
TypicalSpeed = 500,
|
||||
|
||||
@ -114,7 +114,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.FixedWing,
|
||||
PowerType = (int)PowerType.Jet,
|
||||
Quantity = 1,
|
||||
@ -147,7 +147,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.Electric,
|
||||
PowerType = (int)PowerType.Electric,
|
||||
Quantity = 1,
|
||||
@ -189,7 +189,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default", TargetType = (int)TargetType.Piston,
|
||||
WaveId = "default", TargetType = (int)TargetType.Piston,
|
||||
PowerType = (int)PowerType.Piston,
|
||||
Quantity = 1,
|
||||
TypicalSpeed = 200, TypicalAltitude = 500,
|
||||
@ -273,7 +273,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default", TargetType = (int)TargetType.Piston,
|
||||
WaveId = "default", TargetType = (int)TargetType.Piston,
|
||||
PowerType = (int)PowerType.Piston,
|
||||
Quantity = 1,
|
||||
TypicalSpeed = 200, TypicalAltitude = 500,
|
||||
@ -333,7 +333,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.HighSpeed,
|
||||
PowerType = (int)PowerType.Jet,
|
||||
Quantity = 1,
|
||||
@ -377,7 +377,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.Piston,
|
||||
PowerType = (int)PowerType.Piston,
|
||||
Quantity = 1,
|
||||
@ -443,7 +443,7 @@ namespace CounterDrone.Core.Tests
|
||||
});
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.Piston,
|
||||
PowerType = (int)PowerType.Piston,
|
||||
Quantity = 1,
|
||||
@ -502,7 +502,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default", TargetType = (int)TargetType.Piston,
|
||||
WaveId = "default", TargetType = (int)TargetType.Piston,
|
||||
PowerType = (int)PowerType.Piston,
|
||||
Quantity = 3,
|
||||
TypicalSpeed = 200, TypicalAltitude = 500,
|
||||
@ -557,7 +557,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default", TargetType = (int)TargetType.Piston,
|
||||
WaveId = "default", TargetType = (int)TargetType.Piston,
|
||||
PowerType = (int)PowerType.Piston,
|
||||
Quantity = 3,
|
||||
TypicalSpeed = 150, TypicalAltitude = 500,
|
||||
@ -619,7 +619,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default", TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
||||
WaveId = "default", TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
||||
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
||||
});
|
||||
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||
@ -644,7 +644,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0, Visibility = 10000 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default", TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
||||
WaveId = "default", TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
|
||||
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
|
||||
});
|
||||
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Repository;
|
||||
using SQLite;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class GroupRepositoryTests : IDisposable
|
||||
{
|
||||
private readonly string _testDir;
|
||||
private readonly SQLiteConnection _db;
|
||||
private readonly GroupRepository _repo;
|
||||
|
||||
public GroupRepositoryTests()
|
||||
{
|
||||
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
|
||||
var paths = new TestPathProvider(_testDir);
|
||||
var dbManager = new DatabaseManager(paths);
|
||||
_db = dbManager.OpenMainDb();
|
||||
_repo = new GroupRepository(_db);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_db?.Close();
|
||||
if (Directory.Exists(_testDir))
|
||||
Directory.Delete(_testDir, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Insert_And_GetById_ReturnsGroup()
|
||||
{
|
||||
var group = new Group
|
||||
{
|
||||
Name = "Alpha Fleet",
|
||||
GroupType = (int)GroupType.DroneFleet,
|
||||
Description = "Test drone formation"
|
||||
};
|
||||
|
||||
_repo.Insert(group);
|
||||
|
||||
var result = _repo.GetById(group.Id);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("Alpha Fleet", result.Name);
|
||||
Assert.Equal((int)GroupType.DroneFleet, result.GroupType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetByType_FiltersCorrectly()
|
||||
{
|
||||
_repo.Insert(new Group { Name = "Fleet A", GroupType = (int)GroupType.DroneFleet });
|
||||
_repo.Insert(new Group { Name = "Fleet B", GroupType = (int)GroupType.DroneFleet });
|
||||
_repo.Insert(new Group { Name = "Equip G1", GroupType = (int)GroupType.EquipmentGroup });
|
||||
|
||||
var fleets = _repo.GetByType((int)GroupType.DroneFleet);
|
||||
Assert.Equal(2, fleets.Count);
|
||||
|
||||
var equipGroups = _repo.GetByType((int)GroupType.EquipmentGroup);
|
||||
Assert.Single(equipGroups);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delete_RemovesGroup()
|
||||
{
|
||||
var group = new Group { Name = "ToDelete" };
|
||||
_repo.Insert(group);
|
||||
_repo.Delete(group.Id);
|
||||
|
||||
Assert.Null(_repo.GetById(group.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Services;
|
||||
using SQLite;
|
||||
using Xunit;
|
||||
|
||||
namespace CounterDrone.Core.Tests
|
||||
{
|
||||
public class GroupServiceTests : IDisposable
|
||||
{
|
||||
private readonly string _testDir;
|
||||
private readonly SQLiteConnection _db;
|
||||
private readonly IGroupService _service;
|
||||
|
||||
public GroupServiceTests()
|
||||
{
|
||||
_testDir = Path.Combine(Path.GetTempPath(), $"cd_test_{Guid.NewGuid():N}");
|
||||
var paths = new TestPathProvider(_testDir);
|
||||
var dbManager = new DatabaseManager(paths);
|
||||
_db = dbManager.OpenMainDb();
|
||||
_service = new GroupService(new Repository.GroupRepository(_db));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_db?.Close();
|
||||
if (Directory.Exists(_testDir))
|
||||
Directory.Delete(_testDir, true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateGroup_Valid_ReturnsGroup()
|
||||
{
|
||||
var group = _service.CreateGroup("Alpha", GroupType.DroneFleet, "Test fleet");
|
||||
|
||||
Assert.NotNull(group);
|
||||
Assert.Equal("Alpha", group.Name);
|
||||
Assert.NotEmpty(group.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateGroup_EmptyName_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
_service.CreateGroup("", GroupType.DroneFleet, ""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetGroups_WithTypeFilter()
|
||||
{
|
||||
_service.CreateGroup("Fleet A", GroupType.DroneFleet, "");
|
||||
_service.CreateGroup("Fleet B", GroupType.DroneFleet, "");
|
||||
_service.CreateGroup("Equip X", GroupType.EquipmentGroup, "");
|
||||
|
||||
var fleets = _service.GetGroups(GroupType.DroneFleet);
|
||||
Assert.Equal(2, fleets.Count);
|
||||
|
||||
var all = _service.GetGroups(null);
|
||||
Assert.Equal(3, all.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteGroup_RemovesGroup()
|
||||
{
|
||||
var group = _service.CreateGroup("DeleteMe", GroupType.DroneFleet, "");
|
||||
_service.DeleteGroup(group.Id);
|
||||
|
||||
Assert.Null(_service.GetGroup(group.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetGroup_ById_ReturnsCorrectGroup()
|
||||
{
|
||||
var created = _service.CreateGroup("Target", GroupType.EquipmentGroup, "desc");
|
||||
var fetched = _service.GetGroup(created.Id);
|
||||
|
||||
Assert.Equal("Target", fetched.Name);
|
||||
Assert.Equal("desc", fetched.Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,7 +103,7 @@ namespace CounterDrone.Core.Tests
|
||||
|
||||
_service.SaveTarget(task.Id, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.FixedWing,
|
||||
Quantity = 3,
|
||||
});
|
||||
@ -201,7 +201,7 @@ namespace CounterDrone.Core.Tests
|
||||
var task = _service.CreateTask("Target Test", "");
|
||||
_service.SaveTarget(task.Id, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.Piston,
|
||||
Quantity = 5,
|
||||
PowerType = (int)PowerType.Piston,
|
||||
@ -394,7 +394,7 @@ namespace CounterDrone.Core.Tests
|
||||
// 4. 步骤2:目标配置
|
||||
_service.SaveTarget(task.Id, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.HighSpeed,
|
||||
Quantity = 2,
|
||||
PowerType = (int)PowerType.Jet,
|
||||
|
||||
@ -53,7 +53,7 @@ namespace CounterDrone.Core.Tests
|
||||
_scenarioService.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenarioService.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default",
|
||||
WaveId = "default",
|
||||
TargetType = (int)TargetType.Piston,
|
||||
Quantity = 1,
|
||||
PowerType = (int)PowerType.Piston,
|
||||
@ -147,7 +147,7 @@ namespace CounterDrone.Core.Tests
|
||||
{
|
||||
var task = _scenarioService.CreateTask("t", "");
|
||||
_scenarioService.SaveScene(task.Id, new CombatScene());
|
||||
_scenarioService.SaveTarget(task.Id, new TargetConfig { GroupId = "d", Quantity = 1, TypicalSpeed = 60, TypicalAltitude = 300 });
|
||||
_scenarioService.SaveTarget(task.Id, new TargetConfig { WaveId = "d", Quantity = 1, TypicalSpeed = 60, TypicalAltitude = 300 });
|
||||
_scenarioService.SaveDeployment(task.Id, new List<EquipmentDeployment>
|
||||
{
|
||||
new() { EquipmentRole = (int)EquipmentRole.LaunchPlatform, PlatformType = (int)PlatformType.AirBased,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user