diff --git a/CHANGELOG.md b/CHANGELOG.md index cde938a..6ebefcb 100644 --- a/CHANGELOG.md +++ b/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) diff --git a/docs/design/architecture/总体架构设计.md b/docs/design/architecture/总体架构设计.md index 6876635..f41a4da 100644 --- a/docs/design/architecture/总体架构设计.md +++ b/docs/design/architecture/总体架构设计.md @@ -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 ← 无人机(目标方) -│ └── 按编组组织,共享航路 +│ └── 按批次组织,共享航路 ├── List ← 搭载平台(发射方)★重构 │ ├── 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 fireUnits, List threats, CombatScene environment); + PlannerResult Plan(List fireUnits, List 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 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(); -var usedPlatforms = new HashSet(); +var usedFireUnits = new HashSet(); -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 wps); // groupId 参数 +void SaveRoute(string taskId, string waveId, RoutePlan route, List wps); // waveId 参数 ``` ### 报告 @@ -1450,10 +1458,10 @@ void SaveRoute(string taskId, string groupId, RoutePlan route, List 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 wp │ └─ 多边形绘制 / 编辑 / 删除 │ ├────────────────────────────────────────────────────────┤ │ 步骤 2:目标配置 │ -│ ├─ 编组选择(已有编队/新建编队) │ +│ ├─ 批次选择(已有批次/新建批次) │ │ ├─ 目标类型选择(自动填充默认参数) │ │ ├─ 数量 / 动力类型 / 翼展 / 速度 / 高度 │ │ └─ 预设典型目标库(NEW) │ @@ -1481,7 +1489,7 @@ void SaveRoute(string taskId, string groupId, RoutePlan route, List wp │ 步骤 3:装备部署(重构) │ │ ├─ 装备角色(探测设备 / 发射平台) │ │ ├─ 发射平台类型(空基 / 地基 NEW) │ -│ ├─ 数量 / 编组 / 部署位置 │ +│ ├─ 数量 / 部署位置 │ │ ├─ 挂载气溶胶类型 / 弹药数量 │ │ ├─ 弹药初速 / 释放高度 / 冷却时间 │ │ └─ 探测半径(探测设备) │ @@ -1521,7 +1529,7 @@ void SaveRoute(string taskId, string groupId, RoutePlan route, List wp | 12 | 第三方数据 | **静态配置(JSON → SQLite)** | 仅提供弹药基础参数和初始扩散状态,不提供运行时算法 | | 13 | 运动学 | 简化运动学 + 风偏 | 航路点线性插值 + 风速矢量 | | 14 | 弹药弹道 | 抛物线(地基)/ 自由落体(空基) | 基于初速、角度、释放高度计算 | -| 15 | 编组 | Group 独立实体 | 编组独立管理,任务中引用 | +| 15 | 批次 / 火力单元 | WaveId + 火力单元独立作战 | 批次关联航路但不独占;火力单元是最小独立作战单位,不需要上层编组 | | 16 | 管控区域 | ControlZone 表 + Tick 判定 | 电子围栏,侵入即任务失败 | | 17 | 配置向导流程 | **威胁驱动三阶段** | Phase1 定义威胁 → Phase2 算法推荐 → Phase3 审阅确认 | | 18 | 报告导出 | 模板填充 → Markdown | PDF/Word 待调研 | diff --git a/docs/design/technical/DefensePlanner-技术方案.md b/docs/design/technical/DefensePlanner-技术方案.md index 2c8390c..a9b9b4c 100644 --- a/docs/design/technical/DefensePlanner-技术方案.md +++ b/docs/design/technical/DefensePlanner-技术方案.md @@ -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 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 + List + CombatScene +输入:List + List + 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 { /// 为给定火力单元池和威胁列表生成规划方案 - PlannerResult Plan(List fireUnits, List threats, CombatScene environment); + PlannerResult Plan(List fireUnits, List threats, CombatScene environment); } ``` @@ -273,16 +273,16 @@ public class DefaultDefensePlanner : IDefensePlanner public DefaultDefensePlanner(List ammoCatalog) { ... } - public PlannerResult Plan(List fireUnits, List threats, CombatScene env) + public PlannerResult Plan(List fireUnits, List threats, CombatScene env) { // Step 1-5 } // 内部方法 - private List Prioritize(List threats) { ... } + private List Prioritize(List threats) { ... } private AerosolType MatchAmmo(PowerType power) { ... } - private List GenerateCandidates(DroneGroup threat, List units, float now) { ... } - private DefensePlan Solve(List threats, List units) { ... } + private List GenerateCandidates(DroneWave threat, List units, float now) { ... } + private DefensePlan Solve(List threats, List 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 - ├── BuildDroneGroups(config) → List + ├── BuildDroneWaves(config) → List └── _scene (CombatScene) │ ▼ diff --git a/docs/design/technical/仿真器实体与事件映射.md b/docs/design/technical/仿真器实体与事件映射.md index fc833c0..306378c 100644 --- a/docs/design/technical/仿真器实体与事件映射.md +++ b/docs/design/technical/仿真器实体与事件映射.md @@ -41,7 +41,7 @@ SimulationEngine.Initialize() │ ├─ BuildFireUnits() → List - ├─ BuildDroneGroups() → List + ├─ BuildDroneWaves() → List │ ▼ IDefensePlanner.Plan(fireUnits, threats, scene) diff --git a/docs/implementation/tasks/实施计划与任务跟踪.md b/docs/implementation/tasks/实施计划与任务跟踪.md index 061ff59..90b2ba4 100644 --- a/docs/implementation/tasks/实施计划与任务跟踪.md +++ b/docs/implementation/tasks/实施计划与任务跟踪.md @@ -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 导出 | diff --git a/docs/对接文档_Unity前端.md b/docs/对接文档_Unity前端.md index 6f1e992..78e3b84 100644 --- a/docs/对接文档_Unity前端.md +++ b/docs/对接文档_Unity前端.md @@ -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(); 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().ToList(); // DatabaseManager 已种子 -var rec = new DefaultDefenseAdvisor(ammoCatalog).Recommend(new ThreatProfile { ... }); +var fireUnits = new List { /* 从 EquipmentDeployment 构建 */ }; +var threats = new List { 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()); -// 应用到想定 -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(); // 推荐方案的 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 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 秒** | diff --git a/src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs b/src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs index 01b11c1..eb31381 100644 --- a/src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs +++ b/src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs @@ -46,10 +46,10 @@ namespace CounterDrone.Core.Algorithms public float MuzzleVelocity { get; set; } = 800f; } - /// 无人机编队(规划器输入) - public class DroneGroup + /// 无人机批次(规划器输入) + 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 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; } diff --git a/src/CounterDrone.Core/Algorithms/DefensePlanner.cs b/src/CounterDrone.Core/Algorithms/DefensePlanner.cs index e5e2653..17307a5 100644 --- a/src/CounterDrone.Core/Algorithms/DefensePlanner.cs +++ b/src/CounterDrone.Core/Algorithms/DefensePlanner.cs @@ -26,7 +26,7 @@ namespace CounterDrone.Core.Algorithms // 五步流水线 // ═══════════════════════════════════════════════ - public PlannerResult Plan(List fireUnits, List threats, + public PlannerResult Plan(List fireUnits, List threats, CombatScene environment, List detectionSources) { var result = new PlannerResult(); @@ -108,7 +108,7 @@ namespace CounterDrone.Core.Algorithms // Step 3-4: 候选生成 → 贪心分配 // ═══════════════════════════════════════════════ - private DefensePlan Solve(List sortedThreats, + private DefensePlan Solve(List sortedThreats, List 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 GenerateCandidates(DroneGroup threat, + private List GenerateCandidates(DroneWave threat, List availableUnits, CombatScene env) { var candidates = new List(); @@ -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 GenerateFireEventsAt(DroneGroup threat, FireUnit unit, + private List 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); diff --git a/src/CounterDrone.Core/Algorithms/IDefensePlanner.cs b/src/CounterDrone.Core/Algorithms/IDefensePlanner.cs index 5117e7c..7d1d13f 100644 --- a/src/CounterDrone.Core/Algorithms/IDefensePlanner.cs +++ b/src/CounterDrone.Core/Algorithms/IDefensePlanner.cs @@ -8,10 +8,10 @@ namespace CounterDrone.Core.Algorithms public interface IDefensePlanner { /// 可用的火力单元(空基/地基统一表达) - /// 威胁编队列表 + /// 威胁批次列表 /// 作战环境(含天气,影响光电探测距离) /// 统一信息网络的探测源列表(独立探测设备 + 火力单元自带探测)。可为空列表。 - PlannerResult Plan(List fireUnits, List threats, + PlannerResult Plan(List fireUnits, List threats, CombatScene environment, List detectionSources); } } diff --git a/src/CounterDrone.Core/DatabaseManager.cs b/src/CounterDrone.Core/DatabaseManager.cs index 701d1e5..c24496d 100644 --- a/src/CounterDrone.Core/DatabaseManager.cs +++ b/src/CounterDrone.Core/DatabaseManager.cs @@ -68,9 +68,8 @@ namespace CounterDrone.Core db.CreateTable(); db.CreateTable(); db.CreateTable(); - db.CreateIndex("RoutePlan", new[] { "TaskId", "GroupId" }, true); + db.CreateIndex("RoutePlan", new[] { "TaskId", "WaveId" }, true); db.CreateTable(); - db.CreateTable(); db.CreateTable(); db.CreateIndex("SimTask", "TaskNumber"); diff --git a/src/CounterDrone.Core/Models/Enums.cs b/src/CounterDrone.Core/Models/Enums.cs index ba37077..93e2085 100644 --- a/src/CounterDrone.Core/Models/Enums.cs +++ b/src/CounterDrone.Core/Models/Enums.cs @@ -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 { diff --git a/src/CounterDrone.Core/Models/EquipmentDeployment.cs b/src/CounterDrone.Core/Models/EquipmentDeployment.cs index ac1913d..b4003ec 100644 --- a/src/CounterDrone.Core/Models/EquipmentDeployment.cs +++ b/src/CounterDrone.Core/Models/EquipmentDeployment.cs @@ -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; } diff --git a/src/CounterDrone.Core/Models/Group.cs b/src/CounterDrone.Core/Models/Group.cs deleted file mode 100644 index 15155d7..0000000 --- a/src/CounterDrone.Core/Models/Group.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using SQLite; - -namespace CounterDrone.Core.Models -{ - /// 编组 - [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"); - } -} diff --git a/src/CounterDrone.Core/Models/RoutePlan.cs b/src/CounterDrone.Core/Models/RoutePlan.cs index 13b6c28..e1089f2 100644 --- a/src/CounterDrone.Core/Models/RoutePlan.cs +++ b/src/CounterDrone.Core/Models/RoutePlan.cs @@ -3,7 +3,7 @@ using SQLite; namespace CounterDrone.Core.Models { - /// 步骤5:航路规划 — 多编队支持:每个(任务,编队)一条航路 + /// 步骤5:航路规划 — 多批次支持:每个(任务,批次)一条航路 [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; diff --git a/src/CounterDrone.Core/Models/TargetConfig.cs b/src/CounterDrone.Core/Models/TargetConfig.cs index 603d9e4..d27cb77 100644 --- a/src/CounterDrone.Core/Models/TargetConfig.cs +++ b/src/CounterDrone.Core/Models/TargetConfig.cs @@ -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; diff --git a/src/CounterDrone.Core/Models/TaskFullConfig.cs b/src/CounterDrone.Core/Models/TaskFullConfig.cs index d4ef3e8..711bf65 100644 --- a/src/CounterDrone.Core/Models/TaskFullConfig.cs +++ b/src/CounterDrone.Core/Models/TaskFullConfig.cs @@ -11,9 +11,9 @@ namespace CounterDrone.Core.Models public List Targets { get; set; } = new(); public List Equipment { get; set; } = new(); public CloudDispersal Cloud { get; set; } = new(); - /// 多编队航路(多批次支持) + /// 多批次航路(多批次支持) public List Routes { get; set; } = new(); - /// 按 GroupId 分组的航路点 + /// 按 WaveId 分组的航路点 public Dictionary> WaypointGroups { get; set; } = new(); } diff --git a/src/CounterDrone.Core/Models/Waypoint.cs b/src/CounterDrone.Core/Models/Waypoint.cs index 67ac110..845c4dd 100644 --- a/src/CounterDrone.Core/Models/Waypoint.cs +++ b/src/CounterDrone.Core/Models/Waypoint.cs @@ -13,8 +13,8 @@ namespace CounterDrone.Core.Models [Indexed] public string TaskId { get; set; } = string.Empty; - /// 关联的编队 ID(多编队支持) - public string GroupId { get; set; } = string.Empty; + /// 关联的批次 ID(多批次支持) + public string WaveId { get; set; } = string.Empty; [NotNull] public int OrderIndex { get; set; } diff --git a/src/CounterDrone.Core/Repository/GroupRepository.cs b/src/CounterDrone.Core/Repository/GroupRepository.cs deleted file mode 100644 index 0a268c9..0000000 --- a/src/CounterDrone.Core/Repository/GroupRepository.cs +++ /dev/null @@ -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 - { - public GroupRepository(SQLiteConnection db) : base(db) { } - - public List GetByType(int groupType) - { - return Db.Table() - .Where(g => g.GroupType == groupType) - .ToList(); - } - } -} diff --git a/src/CounterDrone.Core/Repository/RoutePlanRepository.cs b/src/CounterDrone.Core/Repository/RoutePlanRepository.cs index 0cf5d1d..f2c2bb1 100644 --- a/src/CounterDrone.Core/Repository/RoutePlanRepository.cs +++ b/src/CounterDrone.Core/Repository/RoutePlanRepository.cs @@ -14,10 +14,10 @@ namespace CounterDrone.Core.Repository return Db.Table().Where(r => r.TaskId == taskId).ToList(); } - public RoutePlan GetByTaskAndGroup(string taskId, string groupId) + public RoutePlan GetByTaskAndWave(string taskId, string waveId) { return Db.Table() - .FirstOrDefault(r => r.TaskId == taskId && r.GroupId == groupId); + .FirstOrDefault(r => r.TaskId == taskId && r.WaveId == waveId); } } } diff --git a/src/CounterDrone.Core/Repository/WaypointRepository.cs b/src/CounterDrone.Core/Repository/WaypointRepository.cs index 81d917e..cba1bdd 100644 --- a/src/CounterDrone.Core/Repository/WaypointRepository.cs +++ b/src/CounterDrone.Core/Repository/WaypointRepository.cs @@ -17,10 +17,10 @@ namespace CounterDrone.Core.Repository .ToList(); } - public List GetByTaskAndGroup(string taskId, string groupId) + public List GetByTaskAndWave(string taskId, string waveId) { return Db.Table() - .Where(w => w.TaskId == taskId && w.GroupId == groupId) + .Where(w => w.TaskId == taskId && w.WaveId == waveId) .OrderBy(w => w.OrderIndex) .ToList(); } diff --git a/src/CounterDrone.Core/Services/GroupService.cs b/src/CounterDrone.Core/Services/GroupService.cs deleted file mode 100644 index fe94156..0000000 --- a/src/CounterDrone.Core/Services/GroupService.cs +++ /dev/null @@ -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 GetGroups(GroupType? type) - { - if (type.HasValue) - return _repo.GetByType((int)type.Value); - return _repo.GetAll(); - } - - public Group GetGroup(string id) - { - return _repo.GetById(id); - } - } -} diff --git a/src/CounterDrone.Core/Services/IGroupService.cs b/src/CounterDrone.Core/Services/IGroupService.cs deleted file mode 100644 index 611c1b4..0000000 --- a/src/CounterDrone.Core/Services/IGroupService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using CounterDrone.Core.Models; - -namespace CounterDrone.Core.Services -{ - /// 编组管理服务 - public interface IGroupService - { - Group CreateGroup(string name, GroupType type, string description); - void DeleteGroup(string id); - List GetGroups(GroupType? type); - Group GetGroup(string id); - } -} diff --git a/src/CounterDrone.Core/Services/IScenarioService.cs b/src/CounterDrone.Core/Services/IScenarioService.cs index f5c27db..325256d 100644 --- a/src/CounterDrone.Core/Services/IScenarioService.cs +++ b/src/CounterDrone.Core/Services/IScenarioService.cs @@ -24,7 +24,7 @@ namespace CounterDrone.Core.Services List GetDetections(string taskId); void SaveCloudDispersal(string taskId, CloudDispersal cloud); - void SaveRoute(string taskId, string groupId, RoutePlan route, List waypoints); + void SaveRoute(string taskId, string waveId, RoutePlan route, List waypoints); void UpdateStep(string taskId, int step); } } diff --git a/src/CounterDrone.Core/Services/ScenarioService.cs b/src/CounterDrone.Core/Services/ScenarioService.cs index d169592..d8e8fd9 100644 --- a/src/CounterDrone.Core/Services/ScenarioService.cs +++ b/src/CounterDrone.Core/Services/ScenarioService.cs @@ -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 waypoints) + public void SaveRoute(string taskId, string waveId, RoutePlan route, List 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(); diff --git a/src/CounterDrone.Core/Simulation/DroneEntity.cs b/src/CounterDrone.Core/Simulation/DroneEntity.cs index 2240195..bf1b862 100644 --- a/src/CounterDrone.Core/Simulation/DroneEntity.cs +++ b/src/CounterDrone.Core/Simulation/DroneEntity.cs @@ -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 /// 沿航路已飞行弧长(米),由 RouteGeometry 驱动 private float _traveledArc; - public DroneEntity(string id, string groupId, TargetConfig config, List route, + public DroneEntity(string id, string waveId, TargetConfig config, List 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; diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs index fe069cf..a83440b 100644 --- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs +++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs @@ -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()); + var route = config.Routes.FirstOrDefault(r => r.WaveId == target.WaveId); + var wps = config.WaypointGroups.GetValueOrDefault(target.WaveId, new List()); 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 BuildDroneGroups(TaskFullConfig config) + private static List BuildDroneWaves(TaskFullConfig config) { - var groups = new List(); + var groups = new List(); 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()); - groups.Add(new DroneGroup + var route = config.Routes.FirstOrDefault(r => r.WaveId == t.WaveId); + var wps = config.WaypointGroups.GetValueOrDefault(t.WaveId, new List()); + groups.Add(new DroneWave { - GroupId = t.GroupId, + WaveId = t.WaveId, Target = t, Route = route ?? new RoutePlan(), Waypoints = wps, diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll index 1ccf79d..548e9da 100644 Binary files a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll and b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll differ diff --git a/src/Unity/Assets/Scripts/Managers/GroupManager.cs b/src/Unity/Assets/Scripts/Managers/GroupManager.cs deleted file mode 100644 index a8ce477..0000000 --- a/src/Unity/Assets/Scripts/Managers/GroupManager.cs +++ /dev/null @@ -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 -{ - /// 编组管理桥接 — 无人机编队和装备编组的 CRUD - 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 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})"); - } - } -} diff --git a/src/Unity/Assets/Scripts/Managers/GroupManager.cs.meta b/src/Unity/Assets/Scripts/Managers/GroupManager.cs.meta deleted file mode 100644 index 499ff8c..0000000 --- a/src/Unity/Assets/Scripts/Managers/GroupManager.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 24c080ea3d6cbcf45aa804fd10269e4a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs index dc30612..7f7d93d 100644 --- a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs +++ b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs @@ -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().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 }, detail.Scene, new List()); + var result = new DefensePlanner(ammoCatalog, PlannerConfig.Load(paths)).Plan(fireUnits, new List { droneGroup }, detail.Scene, new List()); scenarioMgr.SaveCloud(taskId, new CloudDispersal { PositionX = (droneGroup.Waypoints[0].PosX + droneGroup.Waypoints[^1].PosX) / 2, diff --git a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs index a58761b..26fe2e9 100644 --- a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs +++ b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs @@ -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().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 }, detail.Scene, new List()); + var result = planner.Plan(fireUnits, new List { droneGroup }, detail.Scene, new List()); scenario.SaveCloudDispersal(taskId, new CloudDispersal { PositionX = (droneGroup.Waypoints[0].PosX + droneGroup.Waypoints[^1].PosX) / 2, diff --git a/test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs b/test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs index 3e6867f..8180035 100644 --- a/test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs +++ b/test/unit/CounterDrone.Core.Tests/DatabaseManagerTests.cs @@ -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" }; diff --git a/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs b/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs index c08d25a..c5daec1 100644 --- a/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs +++ b/test/unit/CounterDrone.Core.Tests/DefensePlannerTests.cs @@ -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 units, DroneGroup threat, + private PlannerResult Plan(List units, DroneWave threat, CombatScene? env = null) => new DefensePlanner(TestAmmo, TestPlannerConfig.Instance).Plan(units, new() { threat }, env ?? new CombatScene(), new List()); @@ -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()); @@ -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, diff --git a/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs b/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs index bcd447e..cd8292f 100644 --- a/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs +++ b/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs @@ -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, diff --git a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs index 5089cd9..4dc2ee0 100644 --- a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs +++ b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs @@ -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 }, diff --git a/test/unit/CounterDrone.Core.Tests/GroupRepositoryTests.cs b/test/unit/CounterDrone.Core.Tests/GroupRepositoryTests.cs deleted file mode 100644 index 9a90feb..0000000 --- a/test/unit/CounterDrone.Core.Tests/GroupRepositoryTests.cs +++ /dev/null @@ -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)); - } - } -} diff --git a/test/unit/CounterDrone.Core.Tests/GroupServiceTests.cs b/test/unit/CounterDrone.Core.Tests/GroupServiceTests.cs deleted file mode 100644 index 03de66a..0000000 --- a/test/unit/CounterDrone.Core.Tests/GroupServiceTests.cs +++ /dev/null @@ -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(() => - _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); - } - } -} diff --git a/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs b/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs index c0c876b..59585af 100644 --- a/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs +++ b/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs @@ -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, diff --git a/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs b/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs index cb873ef..7a93603 100644 --- a/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs +++ b/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs @@ -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 { new() { EquipmentRole = (int)EquipmentRole.LaunchPlatform, PlatformType = (int)PlatformType.AirBased,