docs: 总体架构设计校准与探测设备设计补全(V10→V13)

文档与代码对齐 + 探测设备行为设计固化。

1. 文档校准(修正与实现不符的错误):
   - AlgorithmFactory 实际为 Func<object> 工厂委托(非 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 错别字修正(晕图案 改 云团)
This commit is contained in:
tian 2026-06-16 10:59:32 +08:00
parent ce9d44db7e
commit 68b7434ddc
2 changed files with 465 additions and 104 deletions

View File

@ -1,11 +1,12 @@
# 反无人机仿真系统 — 总体架构设计
> **版本**V10
> **日期**2026-06-15
> **状态**:已实现
> **版本**V13
> **日期**2026-06-16
> **状态**:已实现(探测实时链路 + 3D 球冠待实现)
> **Unity 版本**22.3.62
> **.NET 版本**.NET Standard 2.1
> **变更**Group 表移除GroupId → WaveIdDroneGroup → 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, // 云团当前半径 mCloudEntity
> "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<PlatformEntity> ← 搭载平台(发射方)★重构
│ ├── AirBasedPlatform ← 空基(大型无人机)
│ └── GroundBasedPlatform ← 地基(火炮)
├── List<DetectionEntity> ← 探测设备
├── List<DetectionEntity> ← 探测设备(统一信息网络)★实时探测【待实现】
│ ├── 持有 DetectionSource位置/雷达/光电/红外距离/精度)
│ └── 每个威胁的探测状态机Undetected → Detected → Tracked
├── List<MunitionEntity> ← 飞行中的弹药
│ ├── 空基投放:短程自由落体/滑翔
│ └── 地基发射:抛物线弹道
@ -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
触发 OnTargetDetectedTargetDetected 事件 → 写 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<ICloudDispersionModel>() → 生成 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 飞行 DroneEntityUpdate(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
│ ├─ 触发 OnTargetDetectedTargetDetected 事件SourceId=探测设备, TargetId=无人机)
│ └─ 输出含精度散布的目标坐标(散布半径 = DetectionCalculator.SpreadRadius(accuracy)
├─ 持续在范围内 → 状态升 Tracked坐标持续刷新
└─ 离开范围 → (策略待定:保持 Tracked 或回退)
> 当前此步缺失:探测只在事前 planner 静态计算(链路 ATick 内无实时探测。
6. 【平台冷却】
FOR EACH PlatformEntityCooldown -= dt
6. 【毁伤判定】 FOR EACH 飞行无人机 × CloudEntity
├─ 快速排斥 1cloud.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 PlatformEntityUpdate(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<FrameRecord>
```
### 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 渲染毁伤效果(尾焰异常、姿态失控、坠落)
/// <summary>算法工厂 — 统一管理所有算法组件的创建与替换</summary>
public static class AlgorithmFactory
{
// 注册表:每个接口 → 实现类型
private static readonly Dictionary<Type, Type> _registry = new()
// 注册表:每个接口 → 创建委托Func<object>
private static readonly Dictionary<Type, Func<object>> _registry = new()
{
{ typeof(ICloudDispersionModel), typeof(GaussianPuffDispersion) },
{ typeof(IDamageModel), typeof(DamageModelRouter) },
{ typeof(IDefensePlanner), typeof(DefaultDefensePlanner) },
[typeof(ICloudDispersionModel)] = () => new GaussianPuffDispersion(),
[typeof(IDamageModel)] = () => new DamageModelRouter(),
};
/// <summary>替换某个算法实现(第三方接入点)</summary>
public static void Register<TInterface, TImpl>() where TImpl : TInterface
public static void Register<TInterface>(Func<object> factory)
{
_registry[typeof(TInterface)] = typeof(TImpl);
_registry[typeof(TInterface)] = factory;
}
/// <summary>创建实例SimulationEngine 统一调用此方法</summary>
public static T Create<T>(params object[] args)
public static T Create<T>() 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<ICloudDispersionModel, ThirdPartyDispersion>();`
> **注**`IDefensePlanner` 由 DI 直接注入 `SimulationEngine`(不经过 AlgorithmFactory因为它是单例策略对象不是每云团/每帧 new。AlgorithmFactory 仅管理**每实例**的算法组件(云团扩散、毁伤模型)。
>
> **替换示例**:将来第三方提供 `ThirdPartyDispersion.dll`,只需一行:
> `AlgorithmFactory.Register<ICloudDispersionModel>(() => new ThirdPartyDispersion());`
> 无需改动 SimulationEngine 任何代码。
### 6.2 第三方提供的数据
@ -908,12 +970,15 @@ public interface IDamageModel
/// <param name="aerosolType">气溶胶类型</param>
/// <param name="cloudDensity">当前位置云团密度kg/m³</param>
/// <param name="exposureTime">累计暴露时间s</param>
/// <param name="deltaTime">本次 Tick 时长</param>
/// <param name="deltaTime">本次增量暴露时长s</param>
/// <returns>本次毁伤值0~1 归一化)</returns>
float CalculateDamage(TargetType droneType, PowerType powerType,
AerosolType aerosolType, float cloudDensity,
float exposureTime, float deltaTime);
/// <summary>达到 100% 毁伤所需的连续暴露时间 (s),供 planner 估算拦截可行性</summary>
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<FireUnit> fireUnits, List<DroneWave> threats, CombatScene environment);
/// <param name="fireUnits">可用的火力单元(空基/地基统一表达)</param>
/// <param name="threats">威胁批次列表</param>
/// <param name="environment">作战环境(含天气,影响光电探测距离)</param>
/// <param name="detectionSources">统一信息网络的探测源列表(独立探测设备 + 火力单元自带探测)。可为空列表。</param>
PlannerResult Plan(List<FireUnit> fireUnits, List<DroneWave> threats,
CombatScene environment, List<DetectionSource> detectionSources);
}
// ThreatProfile / DefenseRecommendation / DefenseSolution 等模型见本节上文
@ -1152,7 +1222,8 @@ public interface IDefensePlanner
┌─ 步骤 3SimulationEngine 无感知切换 ───────────────┐
│ │
│ var dispersion = AlgorithmFactory │
│ .Create<ICloudDispersionModel>(ammo, env, pos, t);│
│ .Create<ICloudDispersionModel>(); │
│ 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
/// <summary>帧数据存储 — 每任务一个独立 SQLite 文件</summary>
public interface IFrameDataStore
/// <summary>帧数据存储 — 内存缓存,仿真结束时批量写入 SQLite</summary>
public class FrameDataStore
{
/// <summary>为任务创建帧数据库文件</summary>
void CreateTaskDb(string taskId);
/// <summary>批量写入一帧所有实体状态</summary>
void WriteFrame(string taskId, int frameIdx, float timestamp,
List<EntitySnapshot> entities);
/// <summary>按帧范围读取(回放用)</summary>
List<SimFrameRecord> ReadFrames(string taskId, int fromFrame, int toFrame);
/// <summary>删除任务的帧数据库</summary>
void BeginRecording(string taskId); // 仿真启动:清空缓冲区
void RecordFrame(int frameIdx, float timestamp, // 每 Tick仅写内存缓冲零字符串分配
List<EntitySnapshot> snapshots);
void Flush(); // 仿真结束:序列化 JSON + 单次事务批量入库
void Discard(); // 丢弃未落库的缓冲
List<SimFrameRecord> ReadFrames(string taskId, int fromFrame, int toFrame); // 回放读
void DeleteTaskDb(string taskId);
/// <summary>清理过期的帧数据库(定时调用)</summary>
void CleanupExpired(DateTime olderThan);
int CleanupExpired(); // TTL 清理(按 RetentionDays默认 180 天)
int BufferedFrameCount { get; } // 已缓冲帧数
}
```
> **关键设计**`RecordFrame` 在仿真热路径上**只搬强类型标量字段**(位置/血量/云团半径等)到内存 `List<FrameRecord>`,不做任何 JSON 拼接。JSON 序列化推迟到 `Flush` 一次性完成(详见第十四章性能设计)。
### 7.2 录制
- 仿真启动时创建 `frames/{taskId}.db`
- 仿真启动时 `BeginRecording` 清空内存缓冲区
- 录制帧率 = 仿真 TickRate每想定可配置默认 20Hz
- 每 Tick 调用 `WriteFrame()` 写入该帧所有实体状态
- SQLite 事务批量提交(如每 50 帧一批,减少磁盘 IO
- 每 Tick 调用 `RecordFrame()`,仅追加到内存 `List<FrameRecord>`,零磁盘 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<EntitySnapshot> entities);
void WriteEvent(SimEvent evt);
void StopRecording();
List<SimFrameRecord> GetFrames(string taskId, int from, int to);
List<SimEvent> GetEvents(string taskId);
void CleanupExpired(DateTime olderThan); // TTL 清理
void BeginRecording(string taskId);
void RecordFrame(int frameIdx, float timestamp, List<EntitySnapshot> entities);
void Flush(); // 批量落库
void Discard();
List<SimFrameRecord> 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<Waypoint> 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<Waypoint> 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<FrameRecord>`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<DetectionEntity>`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 | 场景类型对仿真的影响 | 山区是否影响探测/运动 |

View File

@ -31,7 +31,7 @@
类型、数量、挂载气溶胶的类型、数量(一发炮弹打出的云团大概覆盖体积)
根据算法自动推荐晕图案的抛洒位置、抛洒时机,
根据算法自动推荐云团的抛洒位置、抛洒时机,
最佳值和最危险值
]:支持自定义云团的抛撒位置、抛撒时机、风速风向参数。
   2目标配置预置典型目标库包括多种无人机。