From 68b7434ddc3c88d3e1f010c66c0e8643681c244e Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Tue, 16 Jun 2026 10:59:32 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=80=BB=E4=BD=93=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E6=A0=A1=E5=87=86=E4=B8=8E=E6=8E=A2=E6=B5=8B?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E8=AE=BE=E8=AE=A1=E8=A1=A5=E5=85=A8=EF=BC=88?= =?UTF-8?q?V10=E2=86=92V13=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 文档与代码对齐 + 探测设备行为设计固化。 1. 文档校准(修正与实现不符的错误): - AlgorithmFactory 实际为 Func 工厂委托(非 Type+Activator) - IDefensePlanner.Plan 为 4 参数(含 detectionSources) - IDamageModel 补 RequiredExposureSeconds - FrameDataStore 是类非 IFrameDataStore 接口;删除不存在的 IRecordService - DroneEntity 运动模型为弧长驱动(不受风偏);Tick 流程顺序对齐 - StateData 改为强类型字段+入库时序列化的双轨说明 2. 新增第十三章 性能设计(Tick 热路径优化,实测 <20 到 200+ FPS): - 航路几何缓存、仿真期零字符串分配、多边形顶点缓存 - 帧数据内存缓存+批量落库、毁伤快速排斥、移除索引复用 3. 新增第十四章 探测设备设计(统一信息网络): - 区分事前静态规划(已实现)与运行时实时探测(待实现)双链路 - 三维球冠探测几何(俯仰角+高度门限,IsInCoverage) - 5 项行为决策确认:回退 Undetected / 融合取最早-同刻取精度高 / 不影响 FireSchedule / 盲区仅可视化 / 支持 3D 球冠 - EquipmentDeployment 加 4 个 3D 探测字段 + 数据模型变更清单 4. 附带:技术要求终版.txt 错别字修正(晕图案 改 云团) --- docs/design/architecture/总体架构设计.md | 567 +++++++++++++++++++---- docs/requirements/技术要求终版.txt | 2 +- 2 files changed, 465 insertions(+), 104 deletions(-) diff --git a/docs/design/architecture/总体架构设计.md b/docs/design/architecture/总体架构设计.md index f41a4da..e2c94bd 100644 --- a/docs/design/architecture/总体架构设计.md +++ b/docs/design/architecture/总体架构设计.md @@ -1,11 +1,12 @@ # 反无人机仿真系统 — 总体架构设计 -> **版本**:V10 -> **日期**:2026-06-15 -> **状态**:已实现 +> **版本**:V13 +> **日期**:2026-06-16 +> **状态**:已实现(探测实时链路 + 3D 球冠待实现) > **Unity 版本**:22.3.62 > **.NET 版本**:.NET Standard 2.1 -> **变更**:Group 表移除;GroupId → WaveId;DroneGroup → DroneWave;编组概念升级为批次+火力单元 +> **变更**:V13 探测行为决策固化——5 项规则确认(回退 Undetected / 融合取最早-同刻取精度高 / 不影响 FireSchedule / 盲区仅可视化 / 3D 球冠);新增 14.2.1 三维球冠几何(IsInCoverage)+ 14.7 数据模型变更清单;EquipmentDeployment 表加 4 个 3D 探测字段;DetectionSource 同步扩展 +> **历史变更**:V12 探测双链路设计(静态规划+实时探测待实现);V11 文档校准 + 第十三章性能设计;V10 Group 表移除,批次+火力单元概念升级 --- @@ -165,7 +166,7 @@ public class SimulationRunner : MonoBehaviour | **单元测试** | xUnit + Moq | Repository 增删改查、Service 业务逻辑、Algorithm 计算正确性 | | **仿真确定性测试** | xUnit | 固定输入 + 固定 deltaTime → 断言帧 N 的实体位置 = 预期值 | | **集成测试** | xUnit + SQLite 文件 | 完整想定配置 → 启动仿真 → 跑 1000 帧 → 断言事件序列和对抗结果 | -| **性能测试** | BenchmarkDotNet | Tick() 单帧耗时 P99 < 5ms | +| **性能验证** | 实测帧率 | Unity 实测:满载场景渲染帧率从优化前 <20 FPS 提升至 200+ FPS(详见第十四章) | > Core 测试无需 Unity,`dotnet test` 即可运行全部测试,适合 CI 流水线。 @@ -196,7 +197,7 @@ enum AerosolType { InertGas = 0, ActiveMaterial = 1, ActiveFuel = 2 } enum ReleaseMode { Single = 0, Continuous = 1, Pulse = 2 } enum TriggerMode { Time = 0, Area = 1, Manual = 2 } -// === 毁伤状态阶段(输出到 StateData 供 Unity 渲染)=== +// === 毁伤状态阶段(EntitySnapshot.DamageStage,前端直读;入库时进 StateData JSON)=== enum DamageStage { Normal = 0, EngineAnomaly = 1, AttitudeLoss = 2, Destroyed = 3 } // === 编队 === @@ -215,15 +216,15 @@ enum FormationMode { Single = 0, Formation = 1, Swarm = 2 } // === 运行时 === enum EntityType { Drone = 0, Platform = 1, DetectionEquip = 2, Cloud = 3, Munition = 4 } -enum EventType +enum SimEventType { - TargetDetected = 0, + TargetDetected = 0, // 探测发现目标【待实现,当前未触发,详见第十四章】 MunitionLaunched = 1, CloudGenerated = 2, DroneEnteredCloud = 3, DroneDestroyed = 4, DroneReachedTarget = 5, - ZoneIntruded = 6, // 无人机侵入管控区域 + ZoneIntruded = 6, // 无人机侵入管控区域 WaypointReached = 7, SimulationEnd = 8 } @@ -395,6 +396,11 @@ CounterDroneBackend_Data/ | EORange | REAL | NULLABLE | 光电探测距离 m(受 Visibility 衰减) | | IRRange | REAL | NULLABLE | 红外探测距离 m(不受 Visibility 影响) | | DetectionAccuracy | REAL | NULLABLE | 探测精度 m(位置误差,影响抛撒散布范围) | +| // 三维球冠几何(决策 5,待新增字段;详见第十四章) | +| MinElevation | REAL | NULLABLE | 俯仰角下限(度) | +| MaxElevation | REAL | NULLABLE | 俯仰角上限(度) | +| MinDetectAlt | REAL | NULLABLE | 可探测高度下界 m | +| MaxDetectAlt | REAL | NULLABLE | 可探测高度上界 m | > **装备体系说明**: > - **搭载平台**(2 种): @@ -484,18 +490,19 @@ CounterDroneBackend_Data/ | RotX | REAL | | | | RotY | REAL | | | | RotZ | REAL | | | -| StateData | TEXT | | JSON,包含:DestroyStage、CloudRadius、CloudColor 等 | +| StateData | TEXT | | JSON,由 EntityType 决定结构。**仅 Flush 入库时生成**,仿真期间保持空 | -> StateData JSON 结构(供 Unity 粒子系统 & 毁伤渲染): +> **StateData 双轨设计**:仿真热路径上 `EntitySnapshot` 携带**强类型字段**(`Hp`/`CloudRadius`/`CloudOpacity` 等),前端直接读取,零 JSON 解析;仅 `FrameDataStore.Flush` 入库时按 `EntityType` 序列化为 JSON 字符串写入 `SimFrameRecord.StateData`,供历史回放解析。 +> +> StateData JSON 按实体类型差异化(字段名与 Flush 中模板严格一致): > ```json -> { -> "damageStage": 0, // DamageStage 枚举 -> "hp": 0.75, // 剩余血量 0~1 -> "cloudRadius": 150.0, // 云团当前半径 m(CloudEntity) -> "cloudOpacity": 0.8, // 云团透明度(CloudEntity) -> "cloudColor": "#AABBCC", // 云团颜色(CloudEntity) -> "particleEmitRate": 500 // 粒子发射率(CloudEntity) -> } +> // Drone: +> { "damageStage": 0, "hp": 0.75 } // DamageStage 枚举;剩余血量 0~1 +> // Platform(仅空基平台记录快照): +> { "state": "Flying" } // 平台状态字符串 +> // Cloud: +> { "radius": 150.0, "opacity": 0.8, "phase": 2, "elapsed": 35.0 } +> // radius=当前半径m, opacity=透明度, phase=扩散阶段(1/2/3), elapsed=已存在秒数 > ``` #### SimEvent — 仿真事件(时序图数据源) @@ -527,7 +534,9 @@ SimulationEngine ├── List ← 搭载平台(发射方)★重构 │ ├── AirBasedPlatform ← 空基(大型无人机) │ └── GroundBasedPlatform ← 地基(火炮) -├── List ← 探测设备 +├── List ← 探测设备(统一信息网络)★实时探测【待实现】 +│ ├── 持有 DetectionSource(位置/雷达/光电/红外距离/精度) +│ └── 每个威胁的探测状态机:Undetected → Detected → Tracked ├── List ← 飞行中的弹药 │ ├── 空基投放:短程自由落体/滑翔 │ └── 地基发射:抛物线弹道 @@ -538,12 +547,43 @@ SimulationEngine └── FrameDataStore ← 帧数据写入(任务分库) ``` +> **探测设备的两阶段角色**(详见第十五章): +> - **事前规划阶段**(已实现):`DefensePlanner` 调用 `DetectionCalculator.EarliestDetection` 静态估算每个威胁的最早发现弧长,反推拦截时机。 +> - **运行时实时探测**(待实现):`DetectionEntity` 在 Tick 内扫描飞行无人机,首次进入探测范围 → 触发 `TargetDetected` 事件。两阶段共用同一 `EffectiveRange` 物理公式保证一致。 + ### 5.2 拦截链路(完整流程) +系统存在**两条并行的探测链路**,分别服务于"事前规划"和"运行时观测": + ``` -探测设备扫描 - │ 发现目标 → 输出目标坐标 +═══ 链路 A:事前规划(已实现,开环)═════════════════════════════ + +仿真启动前 DefensePlanner.Plan(): + DetectionCalculator.EarliestDetection(threatRoute, sources, visibility) + │ 静态计算每个威胁航路进入探测网的最早弧长 ▼ + 拦截时机 = 最早发现弧长 + 弹药飞行时间反推 + │ 一次性生成 FireSchedule(开环,不依赖运行时观测) + ▼ + SimulationEngine 按 FireSchedule 时间表执行发射 + + +═══ 链路 B:运行时实时探测(待实现,闭环观测)═════════════════ + +Tick 内 DetectionEntity 扫描飞行无人机: + │ 首次进入 EffectiveRange → 状态 Undetected→Detected + ▼ + 触发 OnTargetDetected(TargetDetected 事件 → 写 SimEvent) + │ 输出目标当前坐标(含探测精度散布) + ▼ + 持续跟踪(Detected→Tracked)→ 坐标更新 + │ (未来:驱动动态火控重分配 / 自适应拦截) + ▼ + 与链路 A 的静态计划协同:观测印证规划 / 补救漏网目标 + + +═══ 公共后段(两条链路最终汇入)═══════════════════════════════ + 火控分配(分配给可用搭载平台) │ ├── 空基平台:飞至投放点 @@ -574,53 +614,70 @@ SimulationEngine Unity 渲染毁伤效果(尾焰异常、姿态失控、坠落) ``` +> **当前实现状态**:链路 A(静态规划)已完整实现,是拦截时机的唯一来源;链路 B(实时探测)尚未接入 Tick,`TargetDetected` 事件已定义但未触发。详见第十五章探测设备设计。 + ### 5.3 仿真 Tick 流程 ``` 1. 【发射阶段】 按 FireSchedule 执行: - ├─ SimulationTime ≥ FireTime → 平台发射弹药 + ├─ SimulationTime ≥ FireTime → 平台发射弹药(空基先 CommandFlyTo,地基直接 Release) + ├─ 空基平台到达投放点 → 投放弹药(继承载机速度) └─ 触发 OnMunitionLaunched 事件 2. 【弹药飞行 → 云团生成】 FOR EACH MunitionEntity: - ├─ 抛物线弹道 / 自由落体 - └─ 到达释放高度 → 生成 CloudEntity → 触发 OnCloudGenerated + ├─ 抛物线弹道(地基)/ 自由落体+水平惯性(空基) + └─ 到达释放高度 → AlgorithmFactory.Create() → 生成 CloudEntity + → 触发 OnCloudGenerated,弹药移除(倒序索引) 3. 【云团演化】 FOR EACH CloudEntity: - ├─ GaussianPuffDispersion.Tick():三阶段扩散 - ├─ 更新粒子参数(半径、透明度 → 写入 StateData) - └─ 消散条件满足 → 移除 + ├─ GaussianPuffDispersion.Tick():三阶段扩散(爆轰→湍流→高斯) + ├─ 更新粒子参数(半径、透明度) + └─ 消散条件满足 → 移除(倒序索引) -4. 【毁伤判定】 - FOR EACH DroneEntity × CloudEntity: - ├─ 无人机坐标 ∈ 云团有效范围? - ├─ 是 → IDamageModel.CalculateDamage() - ├─ drone.HP -= damage - └─ HP ≤ 0 → 触发 OnDroneDestroyed +4. 【无人机移动】 + ├─ 预过滤 Flying 状态无人机到复用缓冲 _flyingDrones(避免多次遍历) + ├─ SavePreviousPosition(为毁伤路径积分保存上一帧位置) + └─ FOR EACH 飞行 DroneEntity:Update(deltaTime)(弧长驱动,详见 5.4) + 到达终点 → ReachedTarget → 触发 OnDroneReachedTarget -5. 【无人机移动】 - FOR EACH 存活 DroneEntity: - ├─ 沿航路点线性插值 - ├─ 风偏影响 - └─ 到达终点 → 触发 OnDroneReachedTarget +5. 【实时探测扫描】★待实现(见第十五章) + FOR EACH DetectionEntity × 飞行无人机: + ├─ effRange = DetectionCalculator.EffectiveRange(radar, eo, ir, visibility) + ├─ ‖drone - detector‖ ≤ effRange? + ├─ 是 且 drone 探测状态 == Undetected → 状态置 Detected + │ ├─ 触发 OnTargetDetected(TargetDetected 事件,SourceId=探测设备, TargetId=无人机) + │ └─ 输出含精度散布的目标坐标(散布半径 = DetectionCalculator.SpreadRadius(accuracy)) + ├─ 持续在范围内 → 状态升 Tracked(坐标持续刷新) + └─ 离开范围 → (策略待定:保持 Tracked 或回退) + > 当前此步缺失:探测只在事前 planner 静态计算(链路 A),Tick 内无实时探测。 -6. 【平台冷却】 - FOR EACH PlatformEntity:Cooldown -= dt +6. 【毁伤判定】 FOR EACH 飞行无人机 × CloudEntity: + ├─ 快速排斥 1:cloud.CoreDensity < 有效浓度阈值 → 跳过 + ├─ 快速排斥 2:‖drone - cloudCenter‖ > 2×EffectiveRadius → 跳过(不可能穿云) + ├─ DamageAssessment.PathInSphere:积分无人机本帧路径段在球体内的长度 inCloudDistance + │ (云团参考系修正:扣除本帧风偏位移 cloudD×dt,使无人机表现为"穿云") + ├─ exposureIncrement = inCloudDistance / (TypicalSpeed/3.6) + ├─ drone.ExposureTime += exposureIncrement + ├─ dmg = IDamageModel.CalculateDamage(...);drone.ApplyDamage(dmg) + └─ HP ≤ 0 → Destroyed → 触发 OnDroneDestroyed -7. 【管控区域判定】 - FOR EACH 存活 DroneEntity: - ├─ Point-in-Polygon 判定 - └─ 侵入 → 触发 OnZoneIntruded +7. 【平台冷却】 + FOR EACH PlatformEntity:Update(deltaTime) -8. 【结束判定】 - 所有无人机 Status ≠ Flying → 触发 OnSimulationEnded +8. 【管控区域判定】 + FOR EACH 飞行无人机 × ControlZone: + ├─ 高度区间检查 + Point-in-Polygon(顶点 2D 数组构造时缓存,每帧零分配) + └─ 侵入 → MarkZoneIntruded → 触发 OnZoneIntruded 9. 【结束判定】 - ├─ 所有无人机 (被摧毁 OR 到达目标 OR 侵入管控区) → SimulationEnd - ├─ 停止录制 - └─ 更新 SimulationState + 所有无人机 Status ≠ Flying → SimulationState.Completed → 触发 OnSimulationEnded + +10. 【录制】(若 RecordFrames) + ├─ CollectSnapshots:填充强类型字段(位置/Hp/云团半径等)到复用池,零字符串分配 + └─ FrameDataStore.RecordFrame:仅追加内存缓冲 List ``` ### 5.4 实体行为模型 @@ -636,10 +693,13 @@ Unity 渲染毁伤效果(尾焰异常、姿态失控、坠落) - 目标类型 TargetType、动力类型 PowerType - 存活状态(飞行中/抵达目标/侵入管控区/被摧毁) -运动模型(简化运动学): - - 两航路点之间线性插值 - - 实际位移 = 航向位移 + 风速矢量 × deltaTime - - 到达判定:||currentPos - waypointPos|| < 1m +运动模型(弧长驱动,统一在 RouteGeometry): + - 构造时一次性预计算航路几何缓存:_totalArc(总弧长)、_segLen[](各段段长)、_cumArc[](累积弧长) + - 每 Tick:_traveledArc += (TypicalSpeed/3.6) × deltaTime + - 在 _cumArc[] 中定位所在段,分段线性插值得到 3D 位置 + - 无人机严格按航路匀速飞行,**不受风偏影响**(真实无人机有飞控修正航向) + - 云团受风偏影响 → 毁伤判定中对云团做参考系修正(详见 5.3 第4步) + - 到达判定:_traveledArc ≥ _totalArc → ReachedTarget 结束条件(任一触发即该无人机结束): - 被毁伤(HP ≤ 0)→ Destroyed @@ -735,31 +795,33 @@ Unity 渲染毁伤效果(尾焰异常、姿态失控、坠落) /// 算法工厂 — 统一管理所有算法组件的创建与替换 public static class AlgorithmFactory { - // 注册表:每个接口 → 实现类型 - private static readonly Dictionary _registry = new() + // 注册表:每个接口 → 创建委托(Func) + private static readonly Dictionary> _registry = new() { - { typeof(ICloudDispersionModel), typeof(GaussianPuffDispersion) }, - { typeof(IDamageModel), typeof(DamageModelRouter) }, - { typeof(IDefensePlanner), typeof(DefaultDefensePlanner) }, + [typeof(ICloudDispersionModel)] = () => new GaussianPuffDispersion(), + [typeof(IDamageModel)] = () => new DamageModelRouter(), }; /// 替换某个算法实现(第三方接入点) - public static void Register() where TImpl : TInterface + public static void Register(Func factory) { - _registry[typeof(TInterface)] = typeof(TImpl); + _registry[typeof(TInterface)] = factory; } /// 创建实例,SimulationEngine 统一调用此方法 - public static T Create(params object[] args) + public static T Create() where T : class { - var implType = _registry[typeof(T)]; - return (T)Activator.CreateInstance(implType, args); + if (_registry.TryGetValue(typeof(T), out var factory)) + return (T)factory(); + throw new InvalidOperationException($"No implementation for {typeof(T).Name}"); } } ``` -> **替换示例**:将来第三方提供 `ThirdPartyDispersion.dll`,只需一行: -> `AlgorithmFactory.Register();` +> **注**:`IDefensePlanner` 由 DI 直接注入 `SimulationEngine`(不经过 AlgorithmFactory,因为它是单例策略对象,不是每云团/每帧 new)。AlgorithmFactory 仅管理**每实例**的算法组件(云团扩散、毁伤模型)。 +> +> **替换示例**:将来第三方提供 `ThirdPartyDispersion.dll`,只需一行: +> `AlgorithmFactory.Register(() => new ThirdPartyDispersion());` > 无需改动 SimulationEngine 任何代码。 ### 6.2 第三方提供的数据 @@ -908,12 +970,15 @@ public interface IDamageModel /// 气溶胶类型 /// 当前位置云团密度(kg/m³) /// 累计暴露时间(s) - /// 本次 Tick 时长 + /// 本次增量暴露时长(s) /// 本次毁伤值(0~1 归一化) float CalculateDamage(TargetType droneType, PowerType powerType, AerosolType aerosolType, float cloudDensity, float exposureTime, float deltaTime); - + + /// 达到 100% 毁伤所需的连续暴露时间 (s),供 planner 估算拦截可行性 + float RequiredExposureSeconds(TargetType droneType, PowerType powerType, AerosolType aerosolType); + DamageStage GetDamageStage(float accumulatedDamage); } ``` @@ -1096,7 +1161,12 @@ planner 所有策略参数从 `data/planner_config.json` 读取,代码零默 // === 防御规划 === public interface IDefensePlanner { - PlannerResult Plan(List fireUnits, List threats, CombatScene environment); + /// 可用的火力单元(空基/地基统一表达) + /// 威胁批次列表 + /// 作战环境(含天气,影响光电探测距离) + /// 统一信息网络的探测源列表(独立探测设备 + 火力单元自带探测)。可为空列表。 + PlannerResult Plan(List fireUnits, List threats, + CombatScene environment, List detectionSources); } // ThreatProfile / DefenseRecommendation / DefenseSolution 等模型见本节上文 @@ -1152,7 +1222,8 @@ public interface IDefensePlanner ┌─ 步骤 3:SimulationEngine 无感知切换 ───────────────┐ │ │ │ var dispersion = AlgorithmFactory │ -│ .Create(ammo, env, pos, t);│ +│ .Create(); │ +│ dispersion.Initialize(ammo, env, pos, t); │ │ │ │ // 后续代码完全不变,无论底层是自主实现还是第三方 DLL │ │ dispersion.Tick(deltaTime, windSpeed, windDir); │ @@ -1184,36 +1255,34 @@ public interface IDefensePlanner ## 七、数据录制与回放 -### 7.1 IFrameDataStore 接口 +### 7.1 FrameDataStore 实现 + +`FrameDataStore` 是**具体类**(非接口),采用内存缓存 + 仿真结束批量落库策略,避免每帧磁盘 IO: ```csharp -/// 帧数据存储 — 每任务一个独立 SQLite 文件 -public interface IFrameDataStore +/// 帧数据存储 — 内存缓存,仿真结束时批量写入 SQLite +public class FrameDataStore { - /// 为任务创建帧数据库文件 - void CreateTaskDb(string taskId); - - /// 批量写入一帧所有实体状态 - void WriteFrame(string taskId, int frameIdx, float timestamp, - List entities); - - /// 按帧范围读取(回放用) - List ReadFrames(string taskId, int fromFrame, int toFrame); - - /// 删除任务的帧数据库 + void BeginRecording(string taskId); // 仿真启动:清空缓冲区 + void RecordFrame(int frameIdx, float timestamp, // 每 Tick:仅写内存缓冲(零字符串分配) + List snapshots); + void Flush(); // 仿真结束:序列化 JSON + 单次事务批量入库 + void Discard(); // 丢弃未落库的缓冲 + List ReadFrames(string taskId, int fromFrame, int toFrame); // 回放读 void DeleteTaskDb(string taskId); - - /// 清理过期的帧数据库(定时调用) - void CleanupExpired(DateTime olderThan); + int CleanupExpired(); // TTL 清理(按 RetentionDays,默认 180 天) + int BufferedFrameCount { get; } // 已缓冲帧数 } ``` +> **关键设计**:`RecordFrame` 在仿真热路径上**只搬强类型标量字段**(位置/血量/云团半径等)到内存 `List`,不做任何 JSON 拼接。JSON 序列化推迟到 `Flush` 一次性完成(详见第十四章性能设计)。 + ### 7.2 录制 -- 仿真启动时创建 `frames/{taskId}.db` +- 仿真启动时 `BeginRecording` 清空内存缓冲区 - 录制帧率 = 仿真 TickRate(每想定可配置,默认 20Hz) -- 每 Tick 调用 `WriteFrame()` 写入该帧所有实体状态 -- SQLite 事务批量提交(如每 50 帧一批,减少磁盘 IO) +- 每 Tick 调用 `RecordFrame()`,仅追加到内存 `List`,零磁盘 IO、零字符串分配 +- 仿真结束 `Flush()`:序列化为 `SimFrameRecord` + 单事务 `InsertAll` 批量写入 `frames/{taskId}.db` - 仿真事件 `SimEvent` 直接写入主库 `main.db`(每任务 ~30 条,轻量) ### 7.3 回放 @@ -1338,7 +1407,7 @@ interface IScenarioService interface ISimulationEngine { void Initialize(string taskId); - SimulationFrameResult Tick(float deltaTime); + SimulationFrameResult Tick(float deltaTime); // 返回本帧快照+新事件(复用对象池) void Pause(); void Resume(); void Stop(); @@ -1346,15 +1415,18 @@ interface ISimulationEngine } // === 数据录制 === -interface IRecordService +// 说明:录制职责由具体类 FrameDataStore 承担(无 IRecordService 接口)。 +// SimulationEngine 内部持有 FrameDataStore,每 Tick 调 RecordFrame(仅内存), +// Stop/完成时调 Flush 批量落库。事件 SimEvent 由引擎直接写入主库。 +class FrameDataStore { - void StartRecording(string taskId, int tickRate); - void WriteFrame(int frameIdx, float timestamp, List entities); - void WriteEvent(SimEvent evt); - void StopRecording(); - List GetFrames(string taskId, int from, int to); - List GetEvents(string taskId); - void CleanupExpired(DateTime olderThan); // TTL 清理 + void BeginRecording(string taskId); + void RecordFrame(int frameIdx, float timestamp, List entities); + void Flush(); // 批量落库 + void Discard(); + List ReadFrames(string taskId, int fromFrame, int toFrame); + void DeleteTaskDb(string taskId); + int CleanupExpired(); // TTL 清理(RetentionDays,默认 180) } // === 仿真报告 === @@ -1527,7 +1599,7 @@ void SaveRoute(string taskId, string waveId, RoutePlan route, List wps | 10 | 毁伤模型 | **三种机制自主实现** | 吸入式灭火(阈值型)、爆燃式(触发型)、吸入式爆炸(指数累积型) | | 11 | 防御方案推荐 | **IDefensePlanner(自主实现)** | 五步规划:威胁排序 → 弹药匹配 → 候选生成 → 贪心分配 → 时序生成 | | 12 | 第三方数据 | **静态配置(JSON → SQLite)** | 仅提供弹药基础参数和初始扩散状态,不提供运行时算法 | -| 13 | 运动学 | 简化运动学 + 风偏 | 航路点线性插值 + 风速矢量 | +| 13 | 运动学 | 弧长驱动匀速运动 | 无人机严格按航路匀速飞行(不受风偏,飞控修正);云团受风偏,毁伤判定做参考系修正 | | 14 | 弹药弹道 | 抛物线(地基)/ 自由落体(空基) | 基于初速、角度、释放高度计算 | | 15 | 批次 / 火力单元 | WaveId + 火力单元独立作战 | 批次关联航路但不独占;火力单元是最小独立作战单位,不需要上层编组 | | 16 | 管控区域 | ControlZone 表 + Tick 判定 | 电子围栏,侵入即任务失败 | @@ -1552,14 +1624,303 @@ void SaveRoute(string taskId, string waveId, RoutePlan route, List wps --- -## 十三、待后续讨论 +## 十三、性能设计(Tick 热路径优化) + +> 本节记录仿真引擎 `SimulationEngine.Tick()` 热路径上的性能优化。实测:Unity 端满载场景渲染帧率从优化前 **<20 FPS(卡顿)** 提升至 **200+ FPS**。 + +### 13.1 设计原则:零分配热路径 + +`Tick` 在仿真期间以 TickRate(默认 20Hz)被 Unity `Update` 反复调用,且每帧需向 Unity 传回所有实体快照。优化的核心原则是**仿真热路径上零(或极少)托管堆分配**,把所有可复用的对象、数组、字符串构造移出热路径: + +| 原则 | 手段 | +|------|------| +| 对象池复用 | 快照列表、事件列表、帧结果、移除索引缓冲、飞行无人机缓冲均 `Clear()` 复用,不每帧 new | +| 延迟序列化 | 强类型字段在仿真期流转,JSON/字符串只在 `Flush` 一次性生成 | +| 几何预计算 | 航路、多边形顶点等静态数据构造时算一次,缓存供每帧查询 | +| 批量落库 | 帧数据先全量驻留内存,仿真结束单事务写入,杜绝每帧磁盘 IO | + +### 13.2 关键优化点 + +#### A. DroneEntity 航路几何缓存(消除每帧重复几何运算) + +**问题**:原实现每帧对每架无人机调用 `RouteGeometry.TotalLength(Route)`(O(N) 全扫求和)+ `PositionAt`(O(N) 重扫定位),航路静态却每帧重算。 + +**优化**:构造时一次性预计算 `_totalArc`(总弧长)、`_segLen[]`(各段段长)、`_cumArc[]`(累积弧长)。`Update()` 内 `_traveledArc += speed×dt`,在 `_cumArc[]` 线性定位所在段(航点数通常 ≤10)后插值。 + +**收益**:每架无人机每帧从 O(N) ×2 降为 O(N) ×1(仅定位,无重算总长)。无人机数 × 航点数 × FPS 规模下收益显著。 + +#### B. 仿真期零字符串分配 + JSON 推迟到 Flush(最大收益项) + +**问题**:原实现每帧每实体在 `CollectSnapshots` 拼 JSON 字符串(`StateData`),Unity 端 `SimulationRunner` 又对每个云团 `JsonDocument.Parse` 解析 —— 拼装 + 解析两端分配,是帧率杀手。 + +**优化**: +- `EntitySnapshot` 新增强类型字段(`DamageStage`/`Hp`/`CloudRadius`/`CloudOpacity`/`CloudPhase`/`CloudElapsed`/`PlatformStateStr`),`CollectSnapshots` 直接填标量,零字符串分配。 +- Unity 端直读 `snap.CloudRadius`/`snap.CloudOpacity`,移除 `JsonDocument.Parse`。 +- JSON 序列化推迟到 `FrameDataStore.Flush`:按 `EntityType` switch 拼装(模板与历史格式逐字节一致),单次完成。 + +**收益**:热路径上彻底消除字符串分配与 JSON 解析。**这是帧率从 <20 提升到 200+ 的主因**。 + +#### C. ControlZoneEntity 顶点 2D 缓存(消除每帧数组分配) + +**问题**:`ContainsPoint` 每 tick 被 `O(飞行无人机 × 管控区)` 调用,原实现每次 `new (float X, float Z)[]` 并逐元素拷贝。 + +**优化**:构造时缓存 `_vertices2D`(水平投影顶点数组),`ContainsPoint` 直接传给 `Kinematics.PointInPolygon`(数组隐式转 `ReadOnlySpan`,零分配)。 + +#### D. 帧数据内存缓存 + 批量落库 + +**问题**:每帧写 SQLite 会产生磁盘 IO 与事务开销。 + +**优化**:`FrameDataStore` 用 `List`(struct,连续存储)做内存缓冲,`RecordFrame` 仅追加;`Flush` 时序列化 + 单事务 `InsertAll` 批量入库。仿真期间零磁盘 IO。 + +#### E. 毁伤判定快速排斥 + +`O(无人机 × 云团)` 嵌套循环前两层裁剪: +1. `cloud.CoreDensity < 有效浓度阈值` → 跳过(浓度不足不可能造成毁伤)。 +2. `‖drone − cloudCenter‖² > (2R)²` → 跳过(距离超 2 倍有效半径,本帧不可能穿云),避免进入 `PathInSphere` 解析计算。 + +#### F. 移除索引复用 + 预过滤 + +- 弹药/云团移除用倒序索引缓冲 `_removalIndices`,避免 `ToList()`。 +- 飞行无人机预过滤到复用缓冲 `_flyingDrones`,避免后续多步重复 `Where`。 + +### 13.3 优化前后对照 + +| 场景 | 优化前 | 优化后 | +|------|--------|--------| +| Unity 渲染帧率(满载) | <20 FPS(卡顿) | 200+ FPS | +| 每帧 GC 分配 | 每实体 1 个 JSON 字符串 + 解析端 DOM | 零字符串分配 | +| 每帧无人机几何运算 | O(无人机×航点) ×2 重算 | O(无人机×航点) ×1 定位 | +| 每帧管控区数组分配 | 每次调用 new 数组 | 构造时缓存,零分配 | +| 帧数据磁盘 IO | 每帧写库 | 仿真结束单次批量 | + +### 13.4 后续可扩展方向(当前规模无需) + +当实体规模大幅增长(如数百架无人机 + 数十云团)时,可能出现的新瓶颈与应对: +- 毁伤判定 `O(drone × cloud)` 嵌套 → 空间分区(均匀网格 / AABB 树)。 +- 回放 `ReplayController.GetFrame` 用 `Where().ToList()` 线性扫描 → 改为按 FrameIndex 建索引或分组字典。 +- 极长回放全量加载内存 → 分段/流式读取。 + +> 按 YAGNI,上述在当前规模(<30 FPS 需求、200+ FPS 实测)下不做,仅记录备查。 + +--- + +## 十四、探测设备设计(统一信息网络) + +> 探测设备是"统一信息网络"的感知层,负责对来袭威胁的搜索、发现与跟踪。本系统支持三种探测手段,统一抽象为 `DetectionSource`,由独立探测节点(`EquipmentRole.Detection`)和火力单元自带的探测能力共同构成探测网。 + +### 14.1 设计现状:两阶段角色 + +探测设备在系统中承担两个阶段的角色,**两者共用同一套物理公式**(`DetectionCalculator`)以保证一致: + +| 阶段 | 角色 | 状态 | 输入 | 输出 | +|------|------|------|------|------| +| **事前规划** | 静态估算最早发现点 | ✅ 已实现 | 威胁航路 + 探测网 + 能见度 | 每个威胁的最早发现弧长 `DetectArc` + 精度 | +| **运行时实时** | 扫描→发现→跟踪 | ⏳ 待实现 | 飞行无人机实时位置 + 探测网 | `TargetDetected` 事件 + 跟踪坐标流 | + +- **事前规划**(链路 A):`DefensePlanner.Plan` 调 `DetectionCalculator.EarliestDetection`,沿威胁航路求其进入探测网范围的最早点,反推拦截时机。这是当前拦截时机的唯一来源(开环)。 +- **运行时实时**(链路 B):`DetectionEntity` 在 Tick 内扫描,产生"何时真正发现"的事件流与可视化。**当前未接入 Tick**,`TargetDetected` 事件已定义(`SimEventType.TargetDetected = 0`)但全代码库未触发。 + +### 14.2 三种探测手段 + +| 手段 | 字段 | 特性 | 受能见度影响 | +|------|------|------|------| +| **雷达** | `RadarRange` | 全天候,三维波束 | 否 | +| **光电(EO)** | `EORange` | 可见光成像 | 是:有效 = 基准 × min(1, Visibility/基准) | +| **红外(IR)** | `IRRange` | 热源探测,夜间可用 | 否 | + +综合有效距离取三者最大(任一手段发现即算发现): + +```csharp +// DetectionCalculator.EffectiveRange —— planner 与运行时实时共用 +// 注:返回的是"最大水平距离基准",3D 球冠判定在其上层(见 14.2.1) +float EffectiveRange(float radarRange, float eoRange, float irRange, float visibility) +{ + float effRadar = radarRange; + float effEO = eoRange > 0 ? eoRange * Math.Min(1f, visibility / eoRange) : 0f; // 光电衰减 + float effIR = irRange; + return Math.Max(effRadar, Math.Max(effEO, effIR)); +} +``` + +> **统一原则**:planner 的静态估算与运行时实时扫描必须用同一个探测几何判定,否则会出现"规划以为能发现、运行时却没发现"的不一致。 + +### 14.2.1 三维球冠探测几何(决策 5) + +**当前实现**:`EffectiveRange` 仅做水平面距离判定(`EarliestEntryArc` 求线段-圆交点),忽略高度与俯仰。这是简化模型。 + +**目标态**:探测范围为以设备为中心的**三维球冠**——由最大水平距离、俯仰角范围、高度门限共同界定,反映真实雷达/光电/红外的波束形状。 + +``` + MaxDetectAlt ─────────────── 高度上界 + ╱ ╲ + ╱ ╲ ← 俯仰角上限 MaxElevation + ╱ 球冠 ╲ + 设备 ●──────╱ ◌ ╲────── EffectiveRange(最大水平距离) + ╲ 区域 ╱ + ╲ ╱ ← 俯仰角下限 MinElevation + ╲ ╱ + MinDetectAlt ─────────────── 高度下界 +``` + +**目标判定函数**(待实现,planner 与实时探测共用): + +```csharp +// DetectionCalculator.IsInCoverage —— 三维球冠判定 +// ① 水平距离 ≤ EffectiveRange(含能见度衰减后的综合距离) +// ② 俯仰角 ∈ [MinElevation, MaxElevation](设备指向目标的高低角) +// ③ 目标高度 ∈ [MinDetectAlt, MaxDetectAlt] +bool IsInCoverage( + Vector3 target, Vector3 detector, + float effectiveRange, + float minElevation, float maxElevation, + float minDetectAlt, float maxDetectAlt) +{ + float dx = target.X - detector.X; + float dy = target.Y - detector.Y; // 高度差 + float dz = target.Z - detector.Z; + float horizDistSq = dx * dx + dz * dz; + if (horizDistSq > effectiveRange * effectiveRange) return false; // ① + if (target.Y < minDetectAlt || target.Y > maxDetectAlt) return false; // ③ + float horizDist = MathF.Sqrt(horizDistSq); + if (horizDist < 0.0001f) return true; // 正顶/正下,按角度门限放行 + float elevation = MathF.Atan2(dy, horizDist) * (180f / MathF.PI); // 俯仰角(度) + return elevation >= minElevation && elevation <= maxElevation; // ② +} +``` + +> **planner 适配**:`EarliestDetection`/`EarliestEntryArc` 当前用线段-圆求交,升级 3D 后需改为"沿航路逐采样点调用 `IsInCoverage`"(解析求三维球冠-折线交点复杂度高,采样法足够且与实时探测判定一致)。这是 3D 升级的主要工作量。 + +### 14.3 探测源构成 + +`DetectionSource` 来自两类装备(`SimulationEngine.BuildDetectionSources`): + +``` +统一信息网络探测源 = 独立探测节点 + 火力单元自带探测 + (EquipmentRole.Detection) (LaunchPlatform 的 RadarRange/EO/IR 字段) +``` + +判定规则:任一探测字段(RadarRange/EORange/IRRange)非 null 且 > 0,即为有效探测源。 + +### 14.4 探测精度与抛撒散布 + +- `DetectionAccuracy`(m):探测设备的位置误差。 +- 精度越差 → planner 增大横向覆盖范围,对冲定位不确定性: + ```csharp + // DetectionCalculator.SpreadRadius + float SpreadRadius(float accuracy) => Math.Max(0f, accuracy); // 散布半径 ≈ 精度值 + ``` +- 运行时实时探测输出目标坐标时,同样按此散布半径叠加位置扰动(待实现)。 + +### 14.5 实时探测设计(待实现) + +#### 14.5.1 DetectionSource / DetectionEntity + +`DetectionSource` 需扩展三维几何参数(决策 5): + +```csharp +public class DetectionSource +{ + public Vector3 Position { get; set; } + public float RadarRange { get; set; } // 雷达水平距离 m + public float EORange { get; set; } // 光电水平距离 m(受 Visibility 衰减) + public float IRRange { get; set; } // 红外水平距离 m + public float Accuracy { get; set; } // 探测精度 m(位置误差) + // ── 三维球冠几何参数(决策 5,待新增)── + public float MinElevation { get; set; } // 俯仰角下限(度) + public float MaxElevation { get; set; } // 俯仰角上限(度) + public float MinDetectAlt { get; set; } // 可探测高度下界 m + public float MaxDetectAlt { get; set; } // 可探测高度上界 m +} + +// 待新增:仿真运行时的探测设备实体 +public class DetectionEntity +{ + public string Id { get; } + public DetectionSource Source { get; } + // 每个威胁的探测状态机(按无人机 Id 索引) + // Undetected ⇄ Detected(决策 1:离开范围回退 Undetected,再次进入重新触发事件) +} +``` + +#### 14.5.2 Tick 内扫描逻辑(接入 5.3 第5步) + +``` +FOR EACH 飞行无人机(维护"本帧已发现该目标的最优探测设备"暂存): + bestDetector = null; bestTime = +∞; bestAccuracy = +∞ // 决策 2:最早发现,同时则精度高 + + FOR EACH DetectionEntity: + effRange = DetectionCalculator.EffectiveRange(雷达, 光电, 红外, 场景能见度) + inCover = DetectionCalculator.IsInCoverage( // 三维球冠判定(14.2.1) + drone.Pos, detector.Pos, effRange, + MinElevation, MaxElevation, MinDetectAlt, MaxDetectAlt) + IF inCover: + IF 该无人机对此设备状态 == Undetected: + 标记"候选首次发现",按决策 2 比较更新 bestDetector(最早;同 tick 取精度高) + 该无人机对此设备状态 → Detected + ELSE: + 该无人机对此设备状态 → Undetected // 决策 1:离开即回退 + + // 本帧所有设备遍历完后,统一触发至多一次 TargetDetected(决策 2 融合) + IF bestDetector != null: + 触发 OnTargetDetected( + SimEvent { Type = TargetDetected, + SourceId = bestDetector.Id, // 最早/精度最高者 + TargetId = drone.Id, + OccurredAt = SimulationTime }) + > 决策 3/4:实时探测只产生事件与可视化,不回调 planner、不改 FireSchedule、不触发额外拦截 +``` + +#### 14.5.3 TargetDetected 事件契约 + +| 字段 | 取值 | +|------|------| +| `Type` | `SimEventType.TargetDetected` | +| `SourceId` | 探测设备 Id | +| `TargetId` | 被发现的无人机 Id | +| `OccurredAt` | 发现时刻(仿真秒) | +| `DataJson` | 发现点坐标 + 探测精度(待定义结构) | +| `Description` | 如"雷达 R1 发现 drone_3 @(x,y,z)" | + +事件写入主库 `SimEvent` 表,是**时序图**的核心数据源之一(与 MunitionLaunched/DroneDestroyed 等并列)。 + +### 14.6 探测行为决策(已确认) + +下列探测行为规则已确认,实现时严格遵循: + +| # | 议题 | 决策 | 说明 | +|---|------|------|------| +| 1 | 目标离开探测范围后状态 | **回退 Undetected** | 失去接触即认为丢失目标。若再次进入(同一设备或另一设备)→ 重新置 Detected 并**再次触发 `TargetDetected`**(对应真实防空"失跟→重新捕获",事件流允许多次发现记录) | +| 2 | 多设备发现同一目标的融合 | **取最早发现者;同时发现取精度最高者** | `TargetDetected.SourceId` 取最早发现该目标的设备;若同一 tick 内多设备同时首次发现,取 `DetectionAccuracy` 值最小(精度最高)的设备 | +| 3 | 实时探测是否影响 FireSchedule | **不影响** | FireSchedule 保持 planner 一次性生成(链路 A)。实时探测是**纯只读观测层**,不回调 planner、不改发射计划 | +| 4 | 探测盲区/漏网目标 | **仅可视化提示,不触发额外行为** | 配合决策 3:实时探测整体定位为观测+展示(时序图/告警文字/UI 标记),不驱动任何拦截逻辑 | +| 5 | 高度与角度范围 | **支持,3D 球冠几何** | 探测范围从纯水平面圆升级为以设备为中心的三维球冠(详见 14.2.1);几何参数随装备配置录入(详见 14.7) | + +### 14.7 数据模型变更(实现清单) + +实现实时探测 + 3D 球冠所需的数据模型改动,集中在此便于落地追踪: + +| 改动项 | 位置 | 内容 | 状态 | +|--------|------|------|------| +| **EquipmentDeployment 表加字段** | 4.3 | `MinElevation`/`MaxElevation`(度)、`MinDetectAlt`/`MaxDetectAlt`(m),均可空 | 待加列 | +| **DetectionSource 类加字段** | 14.5.1 | 同上 4 个三维几何字段 | 待加属性 | +| **DetectionCalculator.IsInCoverage** | 14.2.1 | 三维球冠判定(水平距离 + 俯仰角 + 高度门限),planner 与实时探测共用 | 待实现 | +| **DetectionCalculator.EffectiveRange** | 14.2 | 现有签名不变(返回综合水平距离基准),3D 判定在其上层 | 已实现(基础) | +| **planner 适配 3D** | 14.2.1 | `EarliestDetection` 由"线段-圆求交"改为"沿航路采样点调 IsInCoverage" | 待改造 | +| **SimulationEngine 加 _detectionEntities** | 5.1 | Initialize 时从配置构建 `List`,Tick 第5步遍历 | 待实现 | +| **Tick 第5步实时探测** | 5.3 / 14.5.2 | 扫描 + 状态机 + 决策1/2/3/4 落地,触发 `TargetDetected` | 待实现 | + +> **数据库迁移**:`EquipmentDeployment` 新增 4 列均为 NULLABLE,不影响存量数据;`SimulationEngine.BuildDetectionSources` 读取新字段构造 `DetectionSource`(缺失时按"无角度/高度限制"的退化球冠处理,等价当前 2D 行为,保证平滑过渡)。 + +--- + +## 十五、待后续讨论 | # | 议题 | 说明 | |---|------|------| | 1 | 第三方 DLL 的具体接口规范 | 需与供应商对齐函数签名、数据结构、调用约定 | | 2 | 3 种毁伤机制的差异化细节 | 吸入式灭火 vs 爆燃式 vs 吸入式爆炸的毁伤曲线差异 | -| 3 | 探测设备融合逻辑 | 多设备发现同一目标时的数据融合策略 | -| 4 | 火控分配策略 | 多目标时的优先级分配规则 | +| 3 | 探测设备融合逻辑 | 多设备发现同一目标时的数据融合策略(→ 见 14.6 待决策 #2) | +| 4 | 火控分配策略 | 多目标时的优先级分配规则(→ 实时探测接入后见 14.6 待决策 #3) | | 5 | 蜂群模式运动模型 | 随机扰动范围、个体行为差异 | | 6 | 报告模板具体格式 | 统计维度、图表需求 | | 7 | 场景类型对仿真的影响 | 山区是否影响探测/运动 | diff --git a/docs/requirements/技术要求终版.txt b/docs/requirements/技术要求终版.txt index 5d8b9cb..4352466 100644 --- a/docs/requirements/技术要求终版.txt +++ b/docs/requirements/技术要求终版.txt @@ -31,7 +31,7 @@ 类型、数量、挂载气溶胶的类型、数量(一发炮弹打出的云团大概覆盖体积) -根据算法自动推荐晕图案的抛洒位置、抛洒时机, +根据算法自动推荐云团的抛洒位置、抛洒时机, 最佳值和最危险值 ]:支持自定义云团的抛撒位置、抛撒时机、风速风向参数。    (2)目标配置:预置典型目标库,包括多种无人机。