# 反无人机仿真系统 — 总体架构设计

> **版本**：V13  
> **日期**：2026-06-16  
> **状态**：已实现（探测实时链路 + 3D 球冠待实现）  
> **Unity 版本**：22.3.62  
> **.NET 版本**：.NET Standard 2.1  
> **变更**：V13 探测行为决策固化——5 项规则确认（回退 Undetected / 融合取最早-同刻取精度高 / 不影响 FireSchedule / 盲区仅可视化 / 3D 球冠）；新增 14.2.1 三维球冠几何（IsInCoverage）+ 14.7 数据模型变更清单；EquipmentDeployment 表加 4 个 3D 探测字段；DetectionSource 同步扩展  
> **历史变更**：V12 探测双链路设计（静态规划+实时探测待实现）；V11 文档校准 + 第十三章性能设计；V10 Group 表移除，批次+火力单元概念升级

---

## 一、系统概述

本系统对新型气溶胶武器的三种防空毁伤机制（吸入式灭火、爆燃式、吸入式爆炸）进行数字化仿真。通过参数化配置与可视化推演，量化评估新型气溶胶武器对不同类型空中威胁源目标（动力系统）的毁伤效能。

系统分为三大模块：

| 模块 | 核心功能 |
|------|----------|
| **模型管理** | 3D 模型导入/预览/删除 |
| **想定管理** | 仿真任务 CRUD，步骤化配置向导，管控区域设置，搜索分页 |
| **仿真报告** | 报告列表，含时序图的完整报告预览，导出 PDF/Word，删除 |

### 要求来源对照

本设计融合两份需求文档：

| 文档 | 侧重 |
|------|------|
| V1.0 反无人机仿真系统功能需求 | UI 交互、业务流程、业务字段 |
| 技术要求终版 | 毁伤机制、装备体系、可视化效果、性能指标、第三方算法集成 |

---

## 二、架构分层

```
┌──────────────────────────────────────────────────────────┐
│                     Unity UI Layer                        │
│  模型管理 │ 想定管理(含管控区域) │ 仿真运行&回放 │ 仿真报告 │
├──────────────────────────────────────────────────────────┤
│                 Unity Application Layer                   │
│  ModelPanel | ScenarioWizard | SimulationRunner |        │
│  ReplayController | ReportPanel                          │
├──────────────────────────────────────────────────────────┤
│                  Service Layer (Pure C#)                  │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│  │ ModelService │ │ScenarioService│ │ SimulationEngine │ │
│  │              │ │ DefensePlanner │ │  ├ DroneEntity    │ │
│  └──────────────┘ └──────────────┘ │  ├ PlatformEntity │ │
│  ┌──────────────┐ ┌──────────────┐ │  ├ MunitionEntity │ │
│  │FrameDataStore│ │ReportService │ │  ├ CloudEntity    │ │
│  │              │ │  ├ ReportGen │ │  │  └ GaussianPuff│ │
│  └──────────────┘ │  └ Markdown   │ │  ├ ControlZone    │ │
│                   └──────────────┘ │  ├ IDamageModel×3 │ │
│                                    │  └ FireSchedule   │ │
│  ┌──────────────────────────────────────────────────────┐│
│  │              Algorithm Layer (自主实现)               ││
│  │  GaussianPuffDispersion | DamageCalculator            ││
│  │  DefaultDefensePlanner | Kinematics(PG稳定度)        ││
│  └──────────────────────────────────────────────────────┘│
├──────────────────────────────────────────────────────────┤
│                    Data Layer                             │
│  ┌────────────────────────────┐ ┌────────────────────┐  │
│  │ main.db (SQLite)           │ │ frames/ (SQLite)   │  │
│  │  11张业务表 + SimEvent     │ │  每任务一个 .db     │  │
│  │  + SimulationReport        │ │  SimFrameRecord     │  │
│  │  < 100MB，永久保留          │ │  6个月 TTL          │  │
│  └────────────────────────────┘ └────────────────────┘  │
│  ┌────────────────────────────────────────────────────┐  │
│  │  FileSystem                                        │  │
│  │  模型文件(.fbx/.obj/.stl/.glb/.gltf) + 预览图      │  │
│  └────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────┘
```

### 各层职责

| 层 | 依赖 | 职责 |
|----|------|------|
| **Data Layer** | 主库 SQLite + 任务分库 SQLite + 文件系统 | 主库存元数据/事件/报告（<100MB，永久保留）；分库按任务存帧数据（6月TTL）；文件系统存模型文件 |
| **Algorithm Layer** | 纯 C#，零外部依赖 | 气溶胶扩散模型、毁伤计算、防御推荐算法（全部自主实现） |
| **Service Layer** | Data Layer + Algorithm Layer | 纯业务逻辑、仿真引擎、录制、报告生成 |
| **Application Layer** | Service Layer + Unity | MonoBehaviour 桥接，生命周期协调，驱动仿真 |
| **UI Layer** | Application Layer | UGUI 页面、弹窗、3D 预览、回放、粒子参数传递 |

### 2.1 程序集拆分：Core 独立于 Unity

后端代码拆分为两个程序集，确保 Core 可脱离 Unity 独立编译和测试：

```
CounterDrone.Core.dll               ← .NET Standard 2.1 类库
                                      零 UnityEngine 引用
                                      可在任何 .NET 环境运行
  ├── /Models         数据模型（POCO）
  ├── /Repository     数据访问（SQLite + 文件存储）
  ├── /Services       业务服务（ModelService, ScenarioService 等）
  ├── /Simulation     仿真引擎 + 实体（SimulationEngine, DroneEntity, ...）
  └── /Algorithms     扩散模型、毁伤模型、防御推荐

CounterDrone.Core.Tests.dll         ← xUnit 测试项目
                                      直接引用 Core.dll
                                      dotnet test 即可运行
  ├── /Unit           单元测试（每个类独立测）
  └── /Integration    集成测试（完整仿真流程）

Unity 项目/Assets/Scripts/          ← Unity 侧
  ├── /Managers        MonoBehaviours 桥接层（薄）
  └── /UI             UGUI 绑定
      引用 → CounterDrone.Core.dll（放在 Assets/Plugins/）
```

### 2.2 Unity 依赖隔离：关键设计

| 隔离点 | 做法 |
|------|------|
| **时间** | `engine.Tick(float deltaTime)` — Unity 传 `Time.deltaTime`，测试传固定值 `0.05f` |
| **文件路径** | Core 使用接口 `IPathProvider` → Unity 实现返回 `Application.persistentDataPath`，测试返回 `./testdata/` |
| **日志** | Core 使用 `Microsoft.Extensions.Logging` 抽象 → Unity 桥接到 `Debug.Log`，测试用控制台 |
| **模型加载** | Core 不涉及 3D 渲染，只管理文件路径和元数据 |
| **粒子参数** | Core 输出 `ParticleParams` 数据对象 → Unity 读值设置 ParticleSystem |

### 2.3 Manager 桥接层示例（极薄）

```csharp
// Unity 侧 SimulationRunner.cs — 仅负责桥接
public class SimulationRunner : MonoBehaviour
{
    private ISimulationEngine _engine;
    private IRecordService _recorder;

    void Start()
    {
        // 通过 DI 或工厂获取实例（Core 创建）
        _engine = SimulationEngineFactory.Create(taskId);
        _recorder = ServiceFactory.CreateRecordService();
    }

    void Update()
    {
        if (_engine.State != SimulationState.Running) return;
        
        // 核心只有这一行：把 Unity 时间传入纯 C# 引擎
        var result = _engine.Tick(Time.deltaTime);
        
        // 更新 Unity 侧的 3D 物体位置（根据 result 中的实体快照）
        UpdateVisuals(result.EntitySnapshots);
    }

    void UpdateVisuals(List<EntitySnapshot> snapshots)
    {
        foreach (var s in snapshots)
        {
            // 找到对应 GameObject，设置 transform
            _entityMap[s.EntityId].transform.position = s.Position;
        }
    }
}
```

### 2.4 测试策略

| 测试类型 | 工具 | 内容 |
|------|------|------|
| **单元测试** | xUnit + Moq | Repository 增删改查、Service 业务逻辑、Algorithm 计算正确性 |
| **仿真确定性测试** | xUnit | 固定输入 + 固定 deltaTime → 断言帧 N 的实体位置 = 预期值 |
| **集成测试** | xUnit + SQLite 文件 | 完整想定配置 → 启动仿真 → 跑 1000 帧 → 断言事件序列和对抗结果 |
| **性能验证** | 实测帧率 | Unity 实测：满载场景渲染帧率从优化前 <20 FPS 提升至 200+ FPS（详见第十四章） |

> Core 测试无需 Unity，`dotnet test` 即可运行全部测试，适合 CI 流水线。

---

## 三、枚举定义

```csharp
// === 任务 ===
enum TaskStatus { Draft = 0, Configuring = 1, Completed = 2 }

// === 场景 ===
enum SceneType { Urban = 0, Plain = 1, Mountain = 2, Coast = 3 }
enum WeatherType { Sunny = 0, Overcast = 1, Fog = 2, Rain = 3, Night = 4 }
enum WindDirection { N = 0, NE = 1, E = 2, SE = 3, S = 4, SW = 5, W = 6, NW = 7 }

// === 目标 ===
enum TargetType { Rotor = 0, FixedWing = 1, Electric = 2, Piston = 3, HighSpeed = 4 }
enum PowerType { Electric = 0, Piston = 1, Jet = 2 }

// === 装备 & 搭载平台 ===
enum PlatformType { AirBased = 0, GroundBased = 1 }      // 空基(大型无人机) / 地基(炮弹)
enum EquipmentRole { Detection = 0, LaunchPlatform = 1 }  // 装备角色：探测 / 发射平台

// === 气溶胶 & 毁伤 ===
enum AerosolType { InertGas = 0, ActiveMaterial = 1, ActiveFuel = 2 }
// 对应的毁伤机制名称：吸入式灭火 / 爆燃式 / 吸入式爆炸
enum ReleaseMode { Single = 0, Continuous = 1, Pulse = 2 }
enum TriggerMode { Time = 0, Area = 1, Manual = 2 }

// === 毁伤状态阶段（EntitySnapshot.DamageStage，前端直读；入库时进 StateData JSON）===
enum DamageStage { Normal = 0, EngineAnomaly = 1, AttitudeLoss = 2, Destroyed = 3 }

// === 编队 ===
enum FormationMode { Single = 0, Formation = 1, Swarm = 2 }

// === 核心作战概念 ===
// 火力单元（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 }
enum SimEventType
{
    TargetDetected = 0,         // 探测发现目标【待实现，当前未触发，详见第十四章】
    MunitionLaunched = 1,
    CloudGenerated = 2,
    DroneEnteredCloud = 3,
    DroneDestroyed = 4,
    DroneReachedTarget = 5,
    ZoneIntruded = 6,           // 无人机侵入管控区域
    WaypointReached = 7,
    SimulationEnd = 8
}

// === 报告 ===
enum InterceptResult { Success = 0, Partial = 1, Failed = 2 }
enum PositionMode { PointSelect = 0, ManualInput = 1 }
```

---

## 四、数据模型设计

### 4.1 存储架构：主库 + 任务分库

```
CounterDroneBackend_Data/
├── main.db                        ← 主库（< 100MB，永久保留）
│    ├── 11 张业务配置表
│    ├── SimEvent                   ← 事件表（~30 条/任务）
│    └── SimulationReport
│
├── frames/                        ← 帧数据分库（6 个月 TTL）
│    ├── {taskId_001}.db            ← 单任务 ~161 MB（30min 仿真）
│    └── {taskId_002}.db
│
└── models/                        ← 模型文件 + 预览图
```

### 4.2 数据表总览

共 11 张业务表（主库） + 1 张运行时表（主库） + 1 张运行时表（任务分库）：

```
主库（main.db）— 永久保留：
  ModelInfo            — 模型元数据
  AmmunitionSpec       — 弹药基础参数（第三方数据，NEW）
  SimTask              — 仿真任务主表
  CombatScene          — 步骤1：作战场景（含24h时间、天气）
  ControlZone          — 步骤1+：管控区域/电子围栏（NEW）
  TargetConfig         — 步骤2：目标配置（含 WaveId 关联批次）
  EquipmentDeployment  — 步骤3：装备部署（火力单元含探测+打击，或独立探测节点）
  CloudDispersal       — 步骤4：云团抛撒配置
  RoutePlan            — 步骤5：航路规划（1:N SimTask，多批次；批次关联但不独占航路，FK 关联 TargetConfig.WaveId）
  Waypoint             — 航路点
  SimulationReport     — 仿真报告
  SimEvent             — 仿真事件（时序图数据源）

任务分库（frames/{taskId}.db）— 6 个月 TTL：
  SimFrameRecord       — 逐帧位置记录（回放用）
```

> **核心作战概念**：
> - **火力单元（FireUnit）**：能独立完成搜索、跟踪、瞄准并实施打击的最小作战实体。包含探测（雷达/光电/红外）+ 打击（发射架/火炮）的完整闭环。配置层 = `EquipmentDeployment(LaunchPlatform)`。
> - **独立探测节点（DetectionNode）**：只探测不打击的侦查节点（如前沿警戒雷达）。配置层 = `EquipmentDeployment(Detection)`。
> - **无人机批次（DroneWave）**：有共同航路的一组无人机（攻击方）。批次关联航路但不独占（多个批次可共享同一条航路）。配置层 = `TargetConfig(WaveId)` + `RoutePlan(TaskId, WaveId)` + `Waypoints`。
> - 原来的 `Group`（编组）表已移除：无人机编队概念升级为"批次"，装备编组概念不再需要（火力单元是独立作战单位）。

### 4.3 业务表详细设计

#### ModelInfo — 模型元数据

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| Id | TEXT | PK | GUID |
| Name | TEXT | NOT NULL | 模型名称 |
| ModelType | TEXT | | 分类标签 |
| FilePath | TEXT | | persistentDataPath 下的相对路径 |
| FileSize | REAL | | MB |
| PreviewPath | TEXT | | 预览图路径 |
| HasHighResTexture | INTEGER | DEFAULT 0 | 是否含 2K+ 贴图 |
| CreatedAt | TEXT | | 导入时间 ISO8601 |

> 支持格式：**.fbx / .obj / .stl / .glb / .gltf**，单文件 ≤ 500MB，支持 2K 及以上分辨率贴图。

#### SimTask — 仿真任务主表

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| Id | TEXT | PK | GUID |
| Name | TEXT | NOT NULL | 任务名称 |
| TaskNumber | TEXT | UNIQUE | SIM-yyyyMMdd-xxx |
| Status | INTEGER | DEFAULT 0 | 0=草稿 1=配置中 2=已完成 |
| CurrentStep | INTEGER | DEFAULT 1 | 当前配置步骤 1-5 |
| TickRate | INTEGER | DEFAULT 20 | 仿真 Tick 频率（Hz） |
| CreatedAt | TEXT | | |
| UpdatedAt | TEXT | | |

#### CombatScene — 步骤1：作战场景

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| TaskId | TEXT | PK, FK | 1:1 SimTask |
| SceneType | INTEGER | | 0城市 1平原 2山区 3海岸 |
| TimeOfDay | TEXT | DEFAULT "12:00" | **NEW** 24h 制时间，如 "14:30" |
| WeatherType | INTEGER | | 0晴 1阴 2雾 3雨 4夜 |
| WindSpeed | REAL | DEFAULT 5 | m/s，范围 0-30 |
| WindDirection | INTEGER | DEFAULT 0 | 八方位 0-7 |
| Temperature | REAL | DEFAULT 20 | ℃ |
| Humidity | REAL | DEFAULT 60 | %RH |
| Pressure | REAL | DEFAULT 1013 | hPa |
| Visibility | REAL | DEFAULT 5000 | m，范围 100-10000 |
| SceneWidth | REAL | DEFAULT 4000 | **NEW** 场景宽 m（≥4m） |
| SceneLength | REAL | DEFAULT 10000 | **NEW** 场景纵深 m（≥100m） |
| SceneHeight | REAL | DEFAULT 1000 | **NEW** 场景高 m（≥10m） |

> 场景空间范围最低要求：4m × 100m × 10m。默认值 4000m × 10000m × 1000m 可配置。

#### ControlZone — 步骤1扩展：管控区域 / 电子围栏（NEW）

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| Id | TEXT | PK | GUID |
| TaskId | TEXT | FK | |
| Name | TEXT | | 区域名称 |
| VerticesJson | TEXT | NOT NULL | 多边形顶点 JSON：`[{"x":0,"y":0,"z":0},...]` |
| MinAltitude | REAL | | 区域底高 m |
| MaxAltitude | REAL | | 区域顶高 m |
| OrderIndex | INTEGER | | 多个区域的顺序 |

> 管控区域在场景设置（地形+天气）之后配置。无人机进入管控区域 → 触发 ZoneIntruded 事件 → 该无人机任务失败。

#### TargetConfig — 步骤2：目标配置

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| Id | TEXT | PK | GUID |
| TaskId | TEXT | FK | |
| WaveId | TEXT | FK | 关联无人机批次（同一 WaveId 的无人机共享航路） |
| TargetType | INTEGER | | 0旋翼 1固定翼 2电推 3活塞 4高速 |
| Quantity | INTEGER | DEFAULT 1 | 目标数量 |
| PowerType | INTEGER | | 0电推 1活塞 2喷吸气 |
| Wingspan | REAL | | m |
| TypicalSpeed | REAL | | km/h |
| TypicalAltitude | REAL | | m |

> 目标类型默认值：

| 目标类型 | 翼展(m) | 典型速度(km/h) | 典型飞行高度(m) |
|----------|---------|---------------|----------------|
| 旋翼 | 1.2 | 60 | 300 |
| 固定翼 | 3.5 | 150 | 1000 |
| 电推 | 1.8 | 100 | 500 |
| 活塞 | 2.5 | 120 | 800 |
| 高速目标 | 1.5 | 300 | 2000 |

#### EquipmentDeployment — 步骤3：装备部署（火力单元 + 独立探测节点）

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| Id | TEXT | PK | GUID |
| TaskId | TEXT | FK | |
| EquipmentRole | INTEGER | | 0=独立探测节点 1=火力单元(发射平台) |
| Quantity | INTEGER | DEFAULT 1 | 部署数量 |
| WaveId | TEXT | NULLABLE | 可选关联批次（用于 UI 分组，火力单元是独立作战单位，不强依赖批次） |
| // 以下为火力单元（发射平台）专用字段 |
| PlatformType | INTEGER | NULLABLE | 0=空基(大型无人机) 1=地基(炮弹)，独立探测节点为 NULL |
| PositionX | REAL | | 部署位置 |
| PositionY | REAL | | |
| PositionZ | REAL | | |
| 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 分钟） |
| // 探测能力（火力单元自带 + 独立探测节点均有） |
| RadarRange | REAL | NULLABLE | 雷达探测距离 m（雨雾不衰减） |
| 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 种）：
>   - 空基（大型无人机）：飞到释放点投放弹药 → 弹药自由落体/滑翔至云团生成点
>   - 地基（炮弹）：地面发射 → 抛物线弹道 → 到达释放点生成云团
> - **探测设备**：目标搜索发现与跟踪，输出目标坐标给发射平台
> - 弹药类型决定生成云团的 AerosolType 和覆盖体积

#### CloudDispersal — 步骤4：云团抛撒配置

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| TaskId | TEXT | PK, FK | 1:1 SimTask |
| AerosolType | INTEGER | | 0吸入式灭火 1爆燃式 2吸入式爆炸 |
| DisperseHeight | REAL | DEFAULT 300 | m |
| TriggerMode | INTEGER | | 0时间 1区域 2手动 |
| SpreadAngle | REAL | DEFAULT 0 | 度 |
| ReleaseMode | INTEGER | | 0单次 1连续 2脉冲 |
| InitialScale | REAL | DEFAULT 1000 | m³ |
| Duration | REAL | DEFAULT 60 | s |
| ReleaseInterval | REAL | DEFAULT 5 | s |
| TotalAmount | REAL | DEFAULT 50 | kg |
| PositionX | REAL | | 抛撒坐标 |
| PositionY | REAL | | |
| PositionZ | REAL | | |
| PositionMode | INTEGER | | 0三维点选 1手动输入 2算法推荐（NEW） |
| // 算法推荐结果（NEW） |
| RecommendedPosX | REAL | NULLABLE | 算法推荐最佳抛撒位置 |
| RecommendedPosY | REAL | NULLABLE | |
| RecommendedPosZ | REAL | NULLABLE | |
| RecommendedTiming | REAL | NULLABLE | 算法推荐最佳触发时机（仿真秒） |
| CriticalPosX | REAL | NULLABLE | 最危险值—抛撒位置 |
| CriticalPosY | REAL | NULLABLE | |
| CriticalPosZ | REAL | NULLABLE | |
| CriticalTiming | REAL | NULLABLE | 最危险值—触发时机 |

#### RoutePlan — 步骤5：航路规划

> **支持多批次**：通过 `TaskId + WaveId` 唯一标识一条航路，一个任务可有多个批次各自独立航路。

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| TaskId | TEXT | PK(复合) | |
| WaveId | TEXT | PK(复合) | 关联批次：同一任务不同批次可有独立航路（多个批次可共享同一条航路） |
| FormationMode | INTEGER | | 0单机 1编队 2蜂群 |
| FormationSpacing | REAL | DEFAULT 50 | m |
| ETA | TEXT | | 预计到达时间 |

#### Waypoint — 航路点

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| Id | TEXT | PK | GUID |
| TaskId | TEXT | FK | |
| OrderIndex | INTEGER | NOT NULL | 航路顺序 |
| PosX | REAL | | 坐标（支持三维点选） |
| PosY | REAL | | |
| PosZ | REAL | | |
| Altitude | REAL | | 该航点飞行高度 m |
| Speed | REAL | | 该段速度 km/h |

#### ~~Group~~ — 编组（已移除）

> **概念升级**：原 Group 表的两种类型已分别升级：
> - `DroneFleet`（无人机编队）→ **无人机批次（DroneWave）**：由 `TargetConfig(WaveId)` + `RoutePlan(TaskId, WaveId)` + `Waypoints` 隐式表达。批次关联航路但不独占（多个批次可共享同一条航路）。
> - `EquipmentGroup`（装备编组）→ **不再需要**。火力单元是独立作战的最小单位，不需要上层编组。
>
> 数据层：`Group` 表保留但不再主动使用（向后兼容）。`TargetConfig.WaveId` 语义为"批次 ID"。

### 4.4 运行时表

> **注意**：SimEvent 和 SimulationReport 存储在主库 main.db；SimFrameRecord 存储在各任务的独立分库 frames/{taskId}.db 中。

#### SimFrameRecord — 逐帧记录

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| Id | INTEGER | PK AUTOINCREMENT | |
| TaskId | TEXT | FK | |
| FrameIndex | INTEGER | | 帧序号 |
| Timestamp | REAL | | 仿真时间（秒） |
| EntityId | TEXT | | |
| EntityType | INTEGER | | 实体类型枚举 |
| PosX | REAL | | |
| PosY | REAL | | |
| PosZ | REAL | | |
| RotX | REAL | | |
| RotY | REAL | | |
| RotZ | REAL | | |
| StateData | TEXT | | JSON，由 EntityType 决定结构。**仅 Flush 入库时生成**，仿真期间保持空 |

> **StateData 双轨设计**：仿真热路径上 `EntitySnapshot` 携带**强类型字段**（`Hp`/`CloudRadius`/`CloudOpacity` 等），前端直接读取，零 JSON 解析；仅 `FrameDataStore.Flush` 入库时按 `EntityType` 序列化为 JSON 字符串写入 `SimFrameRecord.StateData`，供历史回放解析。
>
> StateData JSON 按实体类型差异化（字段名与 Flush 中模板严格一致）：
> ```json
> // 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 — 仿真事件（时序图数据源）

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| Id | TEXT | PK | GUID |
| TaskId | TEXT | FK | |
| OccurredAt | REAL | | 仿真时间（秒） |
| EventType | INTEGER | | 事件类型枚举 |
| SourceId | TEXT | | 源实体 ID |
| TargetId | TEXT | | 目标实体 ID |
| DataJson | TEXT | | 事件携带的结构化数据 |
| Description | TEXT | | 事件描述 |

---

## 五、仿真引擎设计

### 5.1 核心实体关系

```
SimulationEngine
│
├── SimulationState              ← 状态（运行/暂停/结束）
├── SceneConfig                  ← 配置快照（含场景边界、管控区域）
├── List<DroneEntity>            ← 无人机（目标方）
│     └── 按批次组织，共享航路
├── List<PlatformEntity>         ← 搭载平台（发射方）★重构
│     ├── AirBasedPlatform       ← 空基（大型无人机）
│     └── GroundBasedPlatform    ← 地基（火炮）
├── List<DetectionEntity>        ← 探测设备（统一信息网络）★实时探测【待实现】
│     ├── 持有 DetectionSource（位置/雷达/光电/红外距离/精度）
│     └── 每个威胁的探测状态机：Undetected → Detected → Tracked
├── List<MunitionEntity>         ← 飞行中的弹药
│     ├── 空基投放：短程自由落体/滑翔
│     └── 地基发射：抛物线弹道
├── List<CloudEntity>            ← 气溶胶云团 ★核心毁伤载体★
│     └── GaussianPuffDispersion（三阶段扩散）
├── List<ControlZone>            ← 管控区域（电子围栏）
├── FireSchedule                 ← 发射计划（推荐算法生成）
└── 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 的静态计划协同：观测印证规划 / 补救漏网目标


═══ 公共后段（两条链路最终汇入）═══════════════════════════════

火控分配（分配给可用搭载平台）
    │
    ├── 空基平台：飞至投放点
    │       │
    │       ▼
    │   投放弹药（自由落体/滑翔至云团生成点）
    │
    └── 地基平台：计算射击诸元
            │
            ▼
        发射炮弹（抛物线弹道飞向目标区域）
            │
            ▼
    弹药到达释放点 → 生成 CloudEntity
    MunitionEntity 销毁
            │
            ▼
云团扩散膨胀（三阶段模型：爆轰→湍流→高斯）
    │ 持续膨胀 + 消散计时
    ▼
无人机进入云团范围
    │ 施加毁伤（IDamageModel 接口，三种机制自主实现）
    │ 吸入式灭火 / 爆燃式 / 吸入式爆炸
    ▼
无人机被摧毁 / 存活
    │ 毁伤阶段状态输出到 StateData
    ▼
Unity 渲染毁伤效果（尾焰异常、姿态失控、坠落）
```

> **当前实现状态**：链路 A（静态规划）已完整实现，是拦截时机的唯一来源；链路 B（实时探测）尚未接入 Tick，`TargetDetected` 事件已定义但未触发。详见第十五章探测设备设计。

### 5.3 仿真 Tick 流程

```
1. 【发射阶段】
   按 FireSchedule 执行：
   ├─ SimulationTime ≥ FireTime → 平台发射弹药（空基先 CommandFlyTo，地基直接 Release）
   ├─ 空基平台到达投放点 → 投放弹药（继承载机速度）
   └─ 触发 OnMunitionLaunched 事件

2. 【弹药飞行 → 云团生成】
   FOR EACH MunitionEntity：
   ├─ 抛物线弹道（地基）/ 自由落体+水平惯性（空基）
   └─ 到达释放高度 → AlgorithmFactory.Create<ICloudDispersionModel>() → 生成 CloudEntity
       → 触发 OnCloudGenerated，弹药移除（倒序索引）

3. 【云团演化】
   FOR EACH CloudEntity：
   ├─ GaussianPuffDispersion.Tick()：三阶段扩散（爆轰→湍流→高斯）
   ├─ 更新粒子参数（半径、透明度）
   └─ 消散条件满足 → 移除（倒序索引）

4. 【无人机移动】
   ├─ 预过滤 Flying 状态无人机到复用缓冲 _flyingDrones（避免多次遍历）
   ├─ SavePreviousPosition（为毁伤路径积分保存上一帧位置）
   └─ FOR EACH 飞行 DroneEntity：Update(deltaTime)（弧长驱动，详见 5.4）
       到达终点 → ReachedTarget → 触发 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 飞行无人机 × 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 PlatformEntity：Update(deltaTime)

8. 【管控区域判定】
   FOR EACH 飞行无人机 × ControlZone：
   ├─ 高度区间检查 + Point-in-Polygon（顶点 2D 数组构造时缓存，每帧零分配）
   └─ 侵入 → MarkZoneIntruded → 触发 OnZoneIntruded

9. 【结束判定】
   所有无人机 Status ≠ Flying → SimulationState.Completed → 触发 OnSimulationEnded

10. 【录制】（若 RecordFrames）
    ├─ CollectSnapshots：填充强类型字段（位置/Hp/云团半径等）到复用池，零字符串分配
    └─ FrameDataStore.RecordFrame：仅追加内存缓冲 List<FrameRecord>
```

### 5.4 实体行为模型

#### DroneEntity（无人机）

```
属性：
  - 批次 WaveId、航路点队列 Waypoint[]
  - 当前位置、速度、朝向
  - HP（0~1 归一化）
  - DamageStage（Normal → EngineAnomaly → AttitudeLoss → Destroyed）
  - 目标类型 TargetType、动力类型 PowerType
  - 存活状态（飞行中/抵达目标/侵入管控区/被摧毁）

运动模型（弧长驱动，统一在 RouteGeometry）：
  - 构造时一次性预计算航路几何缓存：_totalArc（总弧长）、_segLen[]（各段段长）、_cumArc[]（累积弧长）
  - 每 Tick：_traveledArc += (TypicalSpeed/3.6) × deltaTime
  - 在 _cumArc[] 中定位所在段，分段线性插值得到 3D 位置
  - 无人机严格按航路匀速飞行，**不受风偏影响**（真实无人机有飞控修正航向）
  - 云团受风偏影响 → 毁伤判定中对云团做参考系修正（详见 5.3 第4步）
  - 到达判定：_traveledArc ≥ _totalArc → ReachedTarget

结束条件（任一触发即该无人机结束）：
  - 被毁伤（HP ≤ 0）→ Destroyed
  - 到达攻击目标 → ReachedTarget
  - 侵入管控区域 → ZoneIntruded
```

#### PlatformEntity（搭载平台）★重构

```
空基平台（AirBasedPlatform）：
  - 位置（动态，飞向投放点）
  - 挂载弹药类型 AerosolType、数量 MunitionCount
  - 释放高度 ReleaseAltitude
  - 冷却计时器
  - 行为：接收目标坐标 → 计算拦截航路 → 飞至投放点 → 投放弹药

地基平台（GroundBasedPlatform）：
  - 固定位置
  - 弹药初速 MuzzleVelocity
  - 挂载弹药类型 AerosolType、数量 MunitionCount
  - 冷却计时器
  - 行为：接收目标坐标 → 计算射击诸元 → 发射炮弹
```

#### MunitionEntity（飞行中弹药）★重构

```
属性：
  - 弹药类型 AerosolType
  - 发射方式 LaunchMode（AirDrop / GroundArtillery）
  - 当前位置、速度
  - 目标坐标（云团生成点）

行为（两种弹道）：
  - AirDrop（空基投放）：
      初始位置 = 投放点，初始速度 = 平台速度
      运动 = 自由落体 + 水平惯性（简化）
      到达条件 = 当前位置.Z ≤ 云团生成高度

  - GroundArtillery（地基炮弹）：
      初始位置 = 发射点，初始速度 = MuzzleVelocity
      运动 = 抛物线弹道
        x(t) = x0 + v0*cos(θ)*cos(φ)*t
        y(t) = y0 + v0*cos(θ)*sin(φ)*t
        z(t) = z0 + v0*sin(θ)*t - 0.5*g*t²
      到达条件 = z(t) ≤ 云团生成高度
```

#### CloudEntity（气溶胶云团）

```
属性：
  - 气溶胶类型 AerosolType
  - 中心坐标
  - 当前半径（调用第三方 DLL 扩散模型计算）
  - 最大半径
  - 持续时间、剩余时间
  - 粒子系统参数（半径、透明度、颜色 → 输出到 StateData）

行为（委托第三方 DLL）：
  - 扩散膨胀：GaussianPuffDispersion.Tick()（三阶段）
  - 消散计时
  - 毁伤判定：IDamageModel.CalculateDamage()（三种机制自主实现）
```

---

## 六、算法设计 — 接口驱动 & 可替换架构 ★关键

### 设计原则

所有算法组件通过**接口隔离**，SimulationEngine 只依赖接口，不感知具体实现。每个算法组件可独立替换为第三方实现，无需修改引擎代码。

```
                    SimulationEngine
                          │
                    ┌─────┼─────┐
                    ▼     ▼     ▼
              ICloudDispersionModel    ← 扩散模型接口
              IDamageModel             ← 毁伤模型接口
              IDefensePlanner         ← 防御规划接口
                    │
            ┌───────┼───────┐
            ▼       ▼       ▼
      自主实现    第三方实现   混合（部分自研+部分外购）
   (Default)   (Future)    (Future)
```

### 6.1 算法工厂与注册

```csharp
/// <summary>算法工厂 — 统一管理所有算法组件的创建与替换</summary>
public static class AlgorithmFactory
{
    // 注册表：每个接口 → 创建委托（Func<object>）
    private static readonly Dictionary<Type, Func<object>> _registry = new()
    {
        [typeof(ICloudDispersionModel)] = () => new GaussianPuffDispersion(),
        [typeof(IDamageModel)]          = () => new DamageModelRouter(),
    };

    /// <summary>替换某个算法实现（第三方接入点）</summary>
    public static void Register<TInterface>(Func<object> factory)
    {
        _registry[typeof(TInterface)] = factory;
    }

    /// <summary>创建实例，SimulationEngine 统一调用此方法</summary>
    public static T Create<T>() where T : class
    {
        if (_registry.TryGetValue(typeof(T), out var factory))
            return (T)factory();
        throw new InvalidOperationException($"No implementation for {typeof(T).Name}");
    }
}
```

> **注**：`IDefensePlanner` 由 DI 直接注入 `SimulationEngine`（不经过 AlgorithmFactory，因为它是单例策略对象，不是每云团/每帧 new）。AlgorithmFactory 仅管理**每实例**的算法组件（云团扩散、毁伤模型）。
>
> **替换示例**：将来第三方提供 `ThirdPartyDispersion.dll`，只需一行：
> `AlgorithmFactory.Register<ICloudDispersionModel>(() => new ThirdPartyDispersion());`
> 无需改动 SimulationEngine 任何代码。

### 6.2 第三方提供的数据

```csharp
/// <summary>第三方提供的弹药基础参数（数据配置，非运行时 DLL）</summary>
public class AmmunitionSpec
{
    public AerosolType AerosolType;        // 气溶胶类型
    public string Name;                    // 弹药名称
    
    // 初始扩散状态
    public float InitialRadius;            // 初始云团半径（m）
    public float InitialVolume;            // 初始云团体积（m³）
    public float CoreDensity;              // 核心密度（kg/m³）
    public float EdgeDensity;              // 边缘密度（kg/m³）
    public float InitialTemperature;       // 初始温度（K）
    public float BuoyancyFactor;           // 浮力系数
    
    // 弹药基础参数
    public float EffectiveConcentration;   // 有效毁伤浓度阈值（kg/m³）
    public float DispersionRateBase;       // 基础扩散速率（m/s）
    public float MaxRadius;                // 最大有效半径（m）
    public float MaxDuration;              // 最大持续时间（s）
    public ParticleRenderParams Particles; // 粒子渲染参数
}
```

> 第三方以 JSON 配置文件形式交付 `AmmunitionSpec` 数组，系统启动时加载到 SQLite `AmmunitionSpec` 表。

### 6.2.1 系统内置默认值（第三方提供前使用）

基于 155mm 炮弹载荷的物理合理估计：

| 参数 | 惰性气体弹 | 活性材料弹 | 活性燃料弹 |
|------|------|------|------|
| 药剂质量 (SourceStrength) | 10 kg | 12 kg | 10 kg |
| 爆发药 TNT 当量 (BurstChargeKg) | 1.5 kg | 4.0 kg | 1.5 kg |
| 有效浓度阈值 (EffectiveConcentration) | 0.0001 kg/m³ | 0.0002 kg/m³ | 0.0001 kg/m³ |
| 湍流扩散系数 (TurbulentExpansionK) | 3.0 | 4.0 | 3.0 |
| 最大半径 (MaxRadius) | 100 m | 80 m | 90 m |
| 最大持续时间 (MaxDuration) | 120 s | 90 s | 100 s |
| Phase 1 初始半径 (R₀) | 3.75 m | 5.14 m | 3.75 m |

> **数据来源**：10kg烟幕剂，抛射药/烟幕剂质量比 5%-20%，TNT当量取 1.5kg（典型值）。活性材料弹取爆炸分散型上限 4kg。

### 6.3 扩散模型设计 — 高斯烟团模型（Gaussian Puff）V2

#### 6.3.1 模型选择

采用**高斯烟团扩散模型**，使用**Pasquill-Gifford 稳定度分类**计算扩散系数 σ。

#### 6.3.2 大气稳定度映射

从天气类型和风速推导稳定度等级，无需额外参数：

| 天气 | 风速 | 稳定度 | 太阳辐射 | 湍流 |
|------|------|------|------|------|
| 晴天 | ≤ 5 m/s | A（极不稳定） | 强 | 强 |
| 晴天 | > 5 m/s | B（不稳定） | 中 | 中 |
| 阴天 | 任意 | D（中性） | 弱 | 中 |
| 雨天 | 任意 | D（中性） | 弱 | 中 |
| 雾天 | 任意 | E（稳定） | 无 | 弱 |
| 夜间 | 任意 | F（极稳定） | 无 | 最弱 |

#### 6.3.3 σ 扩散系数（Pasquill-Gifford）

扩散距离 x 米后，水平扩散系数 σ_y、垂直扩散系数 σ_z（单位 m）：

| 稳定度 | σ_y | σ_z |
|------|------|------|
| A | 0.22x / (1+0.0001x)^0.5 | 0.20x |
| B | 0.16x / (1+0.0001x)^0.5 | 0.12x |
| C | 0.11x / (1+0.0001x)^0.5 | 0.08x / (1+0.0002x)^0.5 |
| D | 0.08x / (1+0.0001x)^0.5 | 0.06x / (1+0.0015x)^0.5 |
| E | 0.06x / (1+0.0001x)^0.5 | 0.03x / (1+0.0003x)^0.5 |
| F | 0.04x / (1+0.0001x)^0.5 | 0.016x / (1+0.0003x)^0.5 |

> 对于瞬时释放的烟团（Puff），扩散距离 x 取云团中心随风漂移的距离：x = u × t，其中 u 为风速，t 为经过时间。若风速为 0，取 x ≈ 1m（纯湍流扩散）。

#### 6.3.4 浓度场公式

云团内任意点 (x, y, z) 在时刻 t 的浓度：

```
C(x, y, z, t) = ────────────── × exp[ -½( (x-ut)/σy )² ]
                 (2π)^(3/2) σy σy σz   
                 × exp[ -½( y/σy )² ]
                 × { exp[ -½( (z-H)/σz )² ] + exp[ -½( (z+H)/σz )² ] }

其中：
  Q  = 源强（释放物质总量，kg），来自 AmmunitionSpec
  u  = 风速（m/s）
  H  = 有效释放高度（m）
  σy, σz = 扩散系数（由 6.3.2 和 6.3.3 确定）
```

#### 6.3.5 可判定指标

| 指标 | 计算 | 说明 |
|------|------|------|
| 中心浓度 | C_max = Q / [(2π)^(3/2) σy² σz] | 云团最浓处 |
| 有效半径 | R_eff = min{ r | C(中心, r) < C_threshold } | 浓度超过阈值的范围 |
| 消散条件 | C_max < C_threshold / 10 或 经过时间 > MaxDuration | 浓度可忽略 |

> **简化**：实时判定时，无人机位于云团内当且仅当 C(无人机坐标) ≥ EffectiveConcentration。不需要球体假设，直接用高斯公式计算。

#### 6.3.6 AmmunitionSpec 调整

| 字段 | 变更 | 说明 |
|------|------|------|
| `DispersionRateBase` | ❌ 删除 | 由 PG 稳定度代替 |
| `InitialRadius` | 保留 | 仍作初始 σ 参考 |
| `SourceStrength` | ✅ 新增 | 源强 Q（kg），取代线性模型 |

> 浓度 C 的单位为 kg/m³，无人机是否受损取决于 C ≥ EffectiveConcentration。

#### 6.3.7 接口（不变）

```csharp
public interface ICloudDispersionModel
{
    void Initialize(AmmunitionSpec ammo, CombatScene env, Vector3 releasePos, float releaseTime);
    void Tick(float deltaTime, float windSpeed, WindDirection windDir);
    Vector3 Center { get; }
    float Radius { get; }
    float CoreDensity { get; }
    float EffectiveRadius { get; }
    ParticleParams Particles { get; }
    bool IsDissipated { get; }
}
```

### 6.4 毁伤模型设计

三种气溶胶对应三种毁伤机制，我们自主实现：

#### 6.4.1 共同接口

```csharp
public interface IDamageModel
{
    /// <summary>每 Tick 计算毁伤值</summary>
    /// <param name="droneType">目标类型</param>
    /// <param name="powerType">动力类型（影响毁伤效率）</param>
    /// <param name="aerosolType">气溶胶类型</param>
    /// <param name="cloudDensity">当前位置云团密度（kg/m³）</param>
    /// <param name="exposureTime">累计暴露时间（s）</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);
}
```

#### 6.4.2 三种机制的差异

| 机制 | 气溶胶类型 | 毁伤原理 | 毁伤曲线特征 |
|------|------|------|------|
| **吸入式灭火** | 惰性气体 | 发动机吸入惰性气体 → 氧气浓度下降 → 熄火 | 阈值型：密度达标后快速生效，延迟短 |
| **爆燃式** | 活性材料 | 材料接触高温发动机表面 → 化学反应爆燃 | 触发型：密度 + 温度双条件，瞬间高伤害 |
| **吸入式爆炸** | 活性燃料 | 燃料被吸入进气道 → 内部爆炸 | 累积型：随暴露时间指数增长 |

```csharp
// 实现示例伪代码

// 吸入式灭火：阈值判定
class InertGasDamageModel : IDamageModel
{
    float CalculateDamage(...) {
        if (cloudDensity < effectiveThreshold) return 0;  // 浓度不够，无伤害
        // 超过阈值后，线性累积毁伤
        return damageRate * deltaTime;
    }
}

// 爆燃式：瞬时触发
class ActiveMaterialDamageModel : IDamageModel
{
    float CalculateDamage(...) {
        if (cloudDensity < triggerThreshold) return 0;
        if (!triggered) { triggered = true; return burstDamage; }  // 一次性爆发
        return residualDamage * deltaTime;  // 后续余伤
    }
}

// 吸入式爆炸：指数累积
class ActiveFuelDamageModel : IDamageModel
{
    float CalculateDamage(...) {
        if (cloudDensity < effectiveThreshold) return 0;
        // 累积暴露 → 指数增长
        return baseRate * Mathf.Exp(exposureTime * factor) * deltaTime;
    }
}
```

#### 6.4.3 毁伤阶段判定

```
accumulatedDamage:    0.0 ─────── 0.25 ─────── 0.6 ─────── 1.0
DamageStage:        Normal   EngineAnomaly  AttitudeLoss  Destroyed

Normal:         正常运行
EngineAnomaly:  发动机异常（Unity: 尾焰变色/不稳定）
AttitudeLoss:   姿态失控（Unity: 偏航/滚转）
Destroyed:      被摧毁（Unity: 坠落动画）
```

### 6.5 防御推荐算法设计

#### 6.5.1 Step A：气溶胶选型

基于**规则匹配**，根据目标动力类型推荐最佳毁伤机制：

| 目标动力 | 推荐气溶胶 | 理由 |
|------|------|------|
| 电推式 | 惰性气体（吸入式灭火） | 电动机无高温，爆燃/爆炸不适用；惰性气体使电调/散热失效 |
| 活塞式 | 惰性气体（吸入式灭火） | 活塞发动机依赖氧气，窒息最有效 |
| 喷吸气式 | 活性材料（爆燃式） | 喷气发动机高温表面触发爆燃 |
| — | 活性燃料（吸入式爆炸） | 适用于高价值高速目标，追求一击必杀 |

```csharp
// 选型规则表（可配置）
static readonly Dictionary<PowerType, AerosolType> MatchTable = new()
{
    { PowerType.Electric, AerosolType.InertGas },
    { PowerType.Piston,   AerosolType.InertGas },
    { PowerType.Jet,      AerosolType.ActiveMaterial },
};
```

#### 6.5.2 Step B：云团参数推荐 — 时空交汇优化

```
问题：
  输入  = 目标航路 R(t)、风速 Vw、搭载平台位置 Pp、弹药飞行时间 Tf
  输出 = 最优抛撒位置 Ps 和触发时机 Ts
  
方法：
  1. 将目标航路离散为时间点 t₁, t₂, ..., tₙ
  2. 对每个 tᵢ：
     a. 无人机位置 = R(tᵢ)
     b. 考虑炮弹飞行时间 Tf，抛撒时刻 = tᵢ - Tf
     c. 云团在 [tᵢ - Tf, tᵢ - Tf + Duration] 内的扩散范围
     d. 计算交汇窗口 = 无人机航路 ∩ 云团扩散范围的时空重叠
  3. 选择交汇窗口最大的 (Ps, Ts) 作为最佳值
  4. 选择交汇窗口刚好有效的边界作为最危险值

简化实现：
  - 云团近似为匀速膨胀球体
  - 无人机匀速直线飞行（取平均速度）
  - 解析解：计算无人机航迹与云团膨胀球面的两个交点
  - 最佳 = 无人机在云团中时间最长
  - 最危险 = 无人机刚好擦边
```

##### 风偏补偿（天气纳入规划）

云团生成后会被风吹偏。为使云团中心在无人机到达穿越点时正好漂移回航路，抛撒点须**逆风预置**：

```
穿越点 (tx, tz)  = 无人机航路上的目标点（不变，用于 txArrival 计算）
抛撒点 (cloudGenX, cloudGenZ) = (tx, tz) − windVec × expansionTime
  windVec = Kinematics.WindToVector(WindDirection, WindSpeed)   // 只影响 X/Z
  expansionTime ≈ 30s（Phase 2 湍流膨胀时长）
```

- **弹药瞄准抛撒点**：地基 `dist = |cloudGen − platform|`；空基 `distToCloud = |cloudGen − platform|`
- **FireEvent.TargetX/Z = cloudGen**：引擎据此生成云团，云团被风吹 `windVec × expansionTime` 后回到穿越点
- **天气（稳定度）间接影响**：`CloudExpansionModel.RadiusAt` 已根据 `env.WeatherType` 推导 Pasquill 稳定度，影响 `expansionTime` 与有效半径估算；修复 `GaussianPuffDispersion` 写死 Sunny 的 bug 后，预估与运行时一致

> `tx, tz`（穿越点）与 `cloudGenX, cloudGenZ`（抛撒点）在有风时不同，无风时相同。原代码混淆二者，导致有风场景规划错误。

##### 物理模型统一原则（planner 与引擎共用）

**planner 禁止写任何本地运动学/几何/毁伤公式**，全部调用与引擎共享的独立工具类。每个物理量只有一个实现：

```
共享工具类（唯一实例）           planner 用途              引擎用途
─────────────────────────────────────────────────────────────────
Kinematics（质点运动学）         弹道/风矢量/到达时间       MunitionEntity/DroneEntity
RouteGeometry（航路几何）        穿越点弧长/位置/切向       DroneEntity 位置更新
CloudExpansionModel（云团膨胀）   有效半径/膨胀时间/弹药数   CloudEntity 半径演化
DamageAssessment（毁伤几何）     （暂未直接用，估算用模型）  PathInSphere 穿云路径
```

- **RouteGeometry**：纯静态工具类。`TotalLength`/`PositionAt(arcLen)`/`ArcLengthNearestTo(x,z)`/`TangentAt(arcLen)`/`TravelTimeTo(arcLen,speed)`。planner 用它定位穿越点、沿航路分布多发、算到达时间；DroneEntity 用它做弧长驱动的位置更新（`_traveledArc += speed×dt` → `PositionAt`）。
- **云团重叠**：间距 `= 2R × (1 − CloudOverlapRatio)`，重叠比例由 `planner_config.json` 驱动（默认 20%）。`CloudExpansionModel.RoundsNeeded` 接受 `spacing` 参数，公式仍在共享模块，planner 只传配置值。

##### 配置外置（planner_config.json）

planner 所有策略参数从 `data/planner_config.json` 读取，代码零默认值：

```json
{
  "CloudOverlapRatio": 0.2,
  "CriticalProbabilityThreshold": 0.5,
  "MaxInterceptProbability": 0.95,
  "TypeCoefficient": { "HighSpeed": 4.0, "FixedWing": 2.0, ... },
  "AmmoMatch": { "Electric": "InertGas", "Piston": "InertGas", "Jet": "ActiveMaterial" }
}
```

- `PlannerConfig.Load(IPathProvider)` 从 dataRoot 加载；文件缺失或字段非法即抛异常
- `DefaultDefensePlanner` 构造函数必传 `PlannerConfig`

#### 6.5.3 Step C：反推平台部署

```
根据云团方案反推：

  Q: 需要覆盖多大面积？
  → 根据目标数量、编队间距、航路宽度 → 需要的云团数

  Q: 每个云团需要多少弹药？
  → 云团体积 / 单发覆盖体积 → 弹药数

  Q: 用什么平台？
  → 抛撒点距目标 > 平台航程？→ 地基
  → 抛撒点距目标 ≤ 平台航程？→ 空基
  → 根据弹药初速 / 飞行时间校验可行性

  Q: 探测设备需要多大半径？
  → 探测半径 ≥ 从平台发现目标到完成拦截期间目标飞过的距离
```

#### 6.5.4 接口

```csharp
// === 防御规划 ===
public interface IDefensePlanner
{
    /// <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 等模型见本节上文
```

### 6.6 新增数据表：AmmunitionSpec

| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| Id | TEXT | PK | GUID |
| AerosolType | INTEGER | | 气溶胶类型 |
| Name | TEXT | | 弹药名称 |
| InitialRadius | REAL | | 初始云团半径 m |
| InitialVolume | REAL | | 初始云团体积 m³ |
| CoreDensity | REAL | | 核心密度 kg/m³ |
| EdgeDensity | REAL | | 边缘密度 kg/m³ |
| InitialTemperature | REAL | | 初始温度 K |
| BuoyancyFactor | REAL | | 浮力系数 |
| EffectiveConcentration | REAL | | 有效毁伤浓度阈值 |
| DispersionRateBase | REAL | | 基础扩散速率 m/s |
| MaxRadius | REAL | | 最大有效半径 m |
| MaxDuration | REAL | | 最大持续时间 s |
| ParticlesJson | TEXT | | 粒子渲染参数 JSON |

> 第三方以 JSON 文件交付，导入到此表。系统启动时若表为空则从默认配置文件加载。

### 6.7 算法替换完整示例

以下是将来用第三方 DLL 替换扩散模型的完整流程，演示接口驱动架构的灵活性：

```
┌─ 步骤 1：第三方提供实现 ─────────────────────────────┐
│                                                        │
│  ThirdPartyDispersion.dll                              │
│    └─ class ThirdPartyPuff : ICloudDispersionModel     │
│         {                                              │
│             void Initialize(AmmunitionSpec, ...) → P/Invoke → native_init()
│             void Tick(float, float, WindDirection)    → P/Invoke → native_tick()
│             Vector3 Center         → P/Invoke → native_center()
│             float Radius           → P/Invoke → native_radius()
│             ...                                        │
│         }                                              │
│                                                        │
└────────────────────────────────────────────────────────┘

┌─ 步骤 2：一行注册 ──────────────────────────────────┐
│                                                        │
│  AlgorithmFactory.Register                            │
│      <ICloudDispersionModel, ThirdPartyPuff>();       │
│                                                        │
└────────────────────────────────────────────────────────┘

┌─ 步骤 3：SimulationEngine 无感知切换 ───────────────┐
│                                                        │
│  var dispersion = AlgorithmFactory                     │
│      .Create<ICloudDispersionModel>();                 │
│  dispersion.Initialize(ammo, env, pos, t);             │
│                                                        │
│  // 后续代码完全不变，无论底层是自主实现还是第三方 DLL  │
│  dispersion.Tick(deltaTime, windSpeed, windDir);       │
│  if (dispersion.IsDissipated) { ... }                  │
│                                                        │
└────────────────────────────────────────────────────────┘
```

**替换粒度**：每个算法组件独立替换，可以实现如下组合：

```
组合 A（当前）：
  ICloudDispersionModel → GaussianPuffDispersion（自主）
  IDamageModel          → DamageModelRouter（自主）
  IDefensePlanner       → DefaultDefensePlanner（自主）

组合 B（将来可能）：
  ICloudDispersionModel → ThirdPartyPuff（第三方 DLL）
  IDamageModel          → DamageModelRouter（自主保留）
  IDefensePlanner       → DefaultDefensePlanner（自主保留）

组合 C（将来可能）：
  ICloudDispersionModel → ThirdPartyPuff（第三方 DLL）
  IDamageModel          → ThirdPartyDamage（第三方 DLL）
  IDefensePlanner       → DefaultDefensePlanner（自主保留）
```

---

## 七、数据录制与回放

### 7.1 FrameDataStore 实现

`FrameDataStore` 是**具体类**（非接口），采用内存缓存 + 仿真结束批量落库策略，避免每帧磁盘 IO：

```csharp
/// <summary>帧数据存储 — 内存缓存，仿真结束时批量写入 SQLite</summary>
public class FrameDataStore
{
    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);
    int CleanupExpired();                            // TTL 清理（按 RetentionDays，默认 180 天）
    int BufferedFrameCount { get; }                  // 已缓冲帧数
}
```

> **关键设计**：`RecordFrame` 在仿真热路径上**只搬强类型标量字段**（位置/血量/云团半径等）到内存 `List<FrameRecord>`，不做任何 JSON 拼接。JSON 序列化推迟到 `Flush` 一次性完成（详见第十四章性能设计）。

### 7.2 录制

- 仿真启动时 `BeginRecording` 清空内存缓冲区
- 录制帧率 = 仿真 TickRate（每想定可配置，默认 20Hz）
- 每 Tick 调用 `RecordFrame()`，仅追加到内存 `List<FrameRecord>`，零磁盘 IO、零字符串分配
- 仿真结束 `Flush()`：序列化为 `SimFrameRecord` + 单事务 `InsertAll` 批量写入 `frames/{taskId}.db`
- 仿真事件 `SimEvent` 直接写入主库 `main.db`（每任务 ~30 条，轻量）

### 7.3 回放

- ReplayController 协程驱动
- 打开 `frames/{taskId}.db`，按 FrameIndex 范围查询
- WHERE FrameIndex BETWEEN a AND b 命中索引，毫秒级
- 回放帧率由前端控制（可与录制不同）
- 支持暂停 / 快进 / 后退 / 跳转

### 7.4 容量估算

以默认 20Hz TickRate，16 实体为例：

| 仿真时长 | 帧数 | 行数（16实体） | 单任务大小 |
|------|------|------|------|
| 5 min | 6,000 | 96,000 | ~27 MB |
| 15 min | 18,000 | 288,000 | ~80 MB |
| 30 min | 36,000 | 576,000 | ~161 MB |
| 60 min | 72,000 | 1,152,000 | ~322 MB |

6 个月累计（假设日均 5 次仿真，每次 30min）：

| 项目 | 数据 |
|------|------|
| 仿真总次数 | 5 × 180 = 900 次 |
| 帧数据总大小 | 900 × 161 MB ≈ **145 GB** |
| main.db | < **100 MB** |
| 全部合计 | **~145 GB** |

### 7.5 TTL 清理策略

```
仿真完成 → 生成 frames/{taskId}.db
    │
    ▼
  CleanupExpired() 每天执行一次：
    ├─ 扫描 frames/ 目录
    ├─ 检查 {taskId}.db 的创建时间
    └─ 超过 6 个月 → 删除文件

可选扩展：
  - 归档：移到 archive/ 目录并压缩（.db.gz）
  - 降采样：删除前先保留每 N 帧 1 帧的精简版
```

### 7.6 为什么分库而非单库

| 对比 | 单库（全部在 main.db） | 分库（每任务一个 .db） |
|------|------|------|
| 帧表行数（6月） | 5.18 亿行 | 每库 57.6 万行 |
| 查询延迟 | 秒级（需精细索引） | 毫秒级 |
| 单文件体积 | > 100 GB | 每文件 ~161 MB |
| 备份/VACUUM | 灾难级 | 无痛 |
| 数据损坏影响 | 全部丢失 | 仅一个任务 |
| 运维（删除/归档） | 复杂 SQL | 直接删文件 |

---

## 八、报告生成

### 8.1 报告结构

技术要求终版要求报告包含**时序图**和三个分析维度：

| 阶段 | 内容 | 数据来源 |
|------|------|------|
| **仿真前** | 我方配置（装备类型/数量、火力单元部署、弹药类型/数量、部署位置、管控区域） | SimTask + 5 步配置 |
| **仿真中** | 关键事件时序表、各实体轨迹概要、云团演化数据 | SimEvent + SimFrameRecord |
| **仿真后** | 拦截成功率、各阶段耗时、毁伤效果评估、对抗结果判定 | SimEvent 汇总统计 |

### 8.2 对抗结果判定逻辑

```
成功拦截：所有无人机 被摧毁（未抵达目标、未侵入管控区）
部分拦截：部分无人机被摧毁，其余抵达目标或侵入管控区
拦截失败：所有无人机抵达目标或侵入管控区（无摧毁）
```

### 8.3 导出

- 格式：PDF / Word（默认 PDF）
- 模板占位符填充后输出
- 技术选型：QuestPDF 或其他 .NET Standard 2.1 兼容方案（待调研）

---

## 九、服务接口概要

```csharp
// === 模型管理 ===
interface IModelService
{
    ModelInfo ImportModel(string filePath, string name);
    void DeleteModel(string id);
    List<ModelInfo> GetAllModels();
    ModelInfo GetModel(string id);
}

// === 想定管理 ===
interface IScenarioService
{
    SimTask CreateTask(string name, string taskNumber);
    void DeleteTask(string id);
    PagedResult<SimTask> SearchTasks(string kw, DateTime? from, DateTime? to, int page, int size);
    TaskFullConfig GetTaskDetail(string id);

    // 步骤保存
    void SaveScene(string taskId, CombatScene scene);
    void SaveControlZones(string taskId, List<ControlZone> zones);       // NEW
    void SaveTarget(string taskId, TargetConfig target);
    void SaveDeployment(string taskId, List<EquipmentDeployment> equips); // 重构
    void SaveCloudDispersal(string taskId, CloudDispersal cloud);
    void SaveRoute(string taskId, RoutePlan route, List<Waypoint> wps);
    void UpdateStep(string taskId, int step);

    // 综合防御推荐（NEW — 多编队版）
    DefenseRecommendation GetDefenseRecommendation(string taskId);
}

// === 仿真引擎 ===
interface ISimulationEngine
{
    void Initialize(string taskId);
    SimulationFrameResult Tick(float deltaTime);   // 返回本帧快照+新事件（复用对象池）
    void Pause();
    void Resume();
    void Stop();
    SimulationState State { get; }
}

// === 数据录制 ===
// 说明：录制职责由具体类 FrameDataStore 承担（无 IRecordService 接口）。
//       SimulationEngine 内部持有 FrameDataStore，每 Tick 调 RecordFrame（仅内存），
//       Stop/完成时调 Flush 批量落库。事件 SimEvent 由引擎直接写入主库。
class FrameDataStore
{
    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）
}

// === 仿真报告 ===
interface IReportService
{
    SimulationReport Generate(string taskId);
    SimulationReport GetReport(string id);
    void DeleteReport(string id);
    byte[] ExportReport(string id, string format); // "pdf" | "word"
    PagedResult<SimulationReport> SearchReports(string kw, DateTime? from, DateTime? to, int page, int size);
}
```

---

## 九-A、多批次多编队设计

### 数据模型

一个仿真任务可包含**多种类型、多批次**的无人机，每批次有独立的航路：

```
SimTask "城市防御演习"
├── CombatScene（共享）
├── ControlZone[]（共享）
├── CloudDispersal（共享：云团参数，具体拦截方案由算法确定）
│
├── 批次 A（活塞×3）              ← WaveId
│   ├── TargetConfig（WaveId=A，类型=活塞，数量=3）
│   ├── RoutePlan（TaskId + WaveId=A，航路 A：北→南）
│   └── Waypoint[]
│
├── 批次 B（喷气×2）              ← WaveId
│   ├── TargetConfig（WaveId=B，类型=高速，数量=2）
│   ├── RoutePlan（TaskId + WaveId=B，航路 B：西→东）
│   └── Waypoint[]
│
└── 批次 C（旋翼×5）              ← WaveId
    ├── TargetConfig（WaveId=C，类型=旋翼，数量=5）
    ├── RoutePlan（TaskId + WaveId=C，航路 C）
    └── Waypoint[]
```

**关键变更**：`RoutePlan` 主键为 `(TaskId, WaveId)` 复合键。`Waypoint` 通过 `(TaskId, WaveId)` 关联航路。

### 推荐算法

算法需要同时考虑**所有无人机批次**和**可用火力单元**，自动匹配分配：

```csharp
// 输入：所有无人机编队 + 已部署的火力单元
// 输出：每个编队的拦截方案 + 合并的发射计划

var allSolutions = new List<DefenseSolution>();
var usedFireUnits = new HashSet<string>();

foreach (var droneWave in threat.DroneWaves)
{
    // 从可用火力单元中选择合适的（弹药类型匹配目标动力类型）
    var availableUnits = fireUnits.Where(u => !usedFireUnits.Contains(u.Id)).ToList();
    var solution = advisor.RecommendForWave(droneWave, environment, availableUnits);

    // 标记占用的火力单元
    foreach (var idx in solution.AssignedFireUnitIds)
        usedFireUnits.Add(idx);

    allSolutions.Add(solution);
}

// 复用判定（不写死"一对一"或"一对多"）：
//   同种弹药 → 只需冷却时间（秒级），可直接复用
//   异种弹药 → 需要 AmmoChangeTime（默认 5 分钟），检查批次间隔是否够
//   弹药耗尽 → 不可复用

var merged = MergeFireSchedules(allSolutions);
engine.SetFireSchedule(merged);
```

**关键原则**：
- 算法决定分配，而非写死"一对一"或"一对多"
- 决策依据：弹药类型匹配、时间窗口重叠、火力单元能力
- 人工可调整算法推荐结果
- 引擎不感知分组——只看合并后的 `FireSchedule`

### 数据表变更

| 表 | 变更 |
|------|------|
| `RoutePlan` | PK 为 (TaskId, WaveId) 复合键 |
| `TargetConfig` | 不变（已有 WaveId） |
| `Waypoint` | 关联 `(TaskId, WaveId)` 定位航路 |

`IScenarioService` 的 `SaveRoute` 签名：
```csharp
void SaveRoute(string taskId, string waveId, RoutePlan route, List<Waypoint> wps);  // waveId 参数
```

### 报告

报告新增多编队汇总：

```markdown
## 编队汇总
| 批次 | 目标 | 数量 | 拦截方案 | 结果 |
|------|------|------|------|------|
| 批次A | 活塞 | 3 | 惰性气体 12发 | ✅ 全毁 |
| 批次B | 喷气 | 2 | 活性材料 8发 | ⚠️ 1逃脱 |
```

---

## 十、配置向导步骤（整合后）

整合需求 V1.0 和技术要求终版后，配置向导保持 5 步但内容调整：

```
┌────────────────────────────────────────────────────────┐
│  步骤 1：作战场景设置                                    │
│  ├─ 场景类型（城市/平原/山区/海岸）                       │
│  ├─ 时间段（24h 制 NEW）                                │
│  ├─ 天气 + 风速/风向/温度/湿度/气压/能见度                │
│  ├─ 场景空间范围（宽/纵深/高 NEW）                       │
│  └─ 管控区域（电子围栏 NEW）                              │
│       └─ 多边形绘制 / 编辑 / 删除                         │
├────────────────────────────────────────────────────────┤
│  步骤 2：目标配置                                        │
│  ├─ 批次选择（已有批次/新建批次）                          │
│  ├─ 目标类型选择（自动填充默认参数）                      │
│  ├─ 数量 / 动力类型 / 翼展 / 速度 / 高度                  │
│  └─ 预设典型目标库（NEW）                                 │
├────────────────────────────────────────────────────────┤
│  步骤 3：装备部署（重构）                                 │
│  ├─ 装备角色（探测设备 / 发射平台）                        │
│  ├─ 发射平台类型（空基 / 地基 NEW）                       │
│  ├─ 数量 / 部署位置                                │
│  ├─ 挂载气溶胶类型 / 弹药数量                              │
│  ├─ 弹药初速 / 释放高度 / 冷却时间                         │
│  └─ 探测半径（探测设备）                                   │
├────────────────────────────────────────────────────────┤
│  步骤 4：云团抛撒配置                                     │
│  ├─ 气溶胶类型（吸入式灭火/爆燃式/吸入式爆炸）            │
│  ├─ 释放参数（高度/触发模式/扩散方向/规模/持续等）        │
│  ├─ 抛撒位置（三维点选 / 手动输入 / 算法推荐 NEW）        │
│  ├─ 算法推荐按钮 → 显示最佳值 & 最危险值（NEW）           │
│  └─ 可点选采纳推荐值                                      │
├────────────────────────────────────────────────────────┤
│  步骤 5：航路规划                                         │
│  ├─ 编队模式（单机/编队/蜂群）                             │
│  ├─ 编队间距 / 预计到达时间                                │
│  ├─ 航路点画布（三维点选添加/调整/删除 NEW）              │
│  └─ 航路点列表（坐标/高度/速度）                           │
└────────────────────────────────────────────────────────┘
```

---

## 十一、关键技术决策

| # | 决策 | 选择 | 理由 |
|---|------|------|------|
| 1 | 数据持久化 | SQLite (sqlite-net) | 支持复杂查询、筛选、分页 |
| 2 | 模型文件格式 | .fbx / .obj / .stl / .glb / .gltf | 合并两份需求，≤500MB，2K+贴图 |
| 3 | 模型文件存储 | Application.persistentDataPath | 不与 Unity Assets 耦合 |
| 4 | 序列化方案 | System.Text.Json | Unity 2022+ 内置支持 |
| 5 | Service/Data 层 | 零 UnityEngine 依赖 | 可独立单元测试 |
| 6 | 仿真驱动方式 | 固定 TickRate 帧驱动 | 每想定可配置（默认 20Hz），保证确定性 |
| 7 | 仿真并发 | 单仿真 | 简化状态管理 |
| 8 | 拦截机制 | 多阶段链路 | 探测→火控→发射→弹药飞行→云团→毁伤 |
| 9 | 云团扩散模型 | **高斯烟团模型（自主实现）** | 简化球体膨胀 + 风速漂移 + 密度衰减，适合实时 20Hz |
| 10 | 毁伤模型 | **三种机制自主实现** | 吸入式灭火（阈值型）、爆燃式（触发型）、吸入式爆炸（指数累积型） |
| 11 | 防御方案推荐 | **IDefensePlanner（自主实现）** | 五步规划：威胁排序 → 弹药匹配 → 候选生成 → 贪心分配 → 时序生成 |
| 12 | 第三方数据 | **静态配置（JSON → SQLite）** | 仅提供弹药基础参数和初始扩散状态，不提供运行时算法 |
| 13 | 运动学 | 弧长驱动匀速运动 | 无人机严格按航路匀速飞行（不受风偏，飞控修正）；云团受风偏，毁伤判定做参考系修正 |
| 14 | 弹药弹道 | 抛物线（地基）/ 自由落体（空基） | 基于初速、角度、释放高度计算 |
| 15 | 批次 / 火力单元 | WaveId + 火力单元独立作战 | 批次关联航路但不独占；火力单元是最小独立作战单位，不需要上层编组 |
| 16 | 管控区域 | ControlZone 表 + Tick 判定 | 电子围栏，侵入即任务失败 |
| 17 | 配置向导流程 | **威胁驱动三阶段** | Phase1 定义威胁 → Phase2 算法推荐 → Phase3 审阅确认 |
| 18 | 报告导出 | 模板填充 → Markdown | PDF/Word 待调研 |
| 19 | 坐标系统 | 右手系 Y-up，米为单位 | 与 Unity 一致 |
| 20 | 空间范围 | 最低 4×100×10m，默认更大 | 可配置 |
| 21 | 帧数据存储 | **主库 + 任务分库** | 主库存元数据+事件（<100MB永久）；分库每任务存帧数据（6月TTL），单文件~161MB，直接删文件运维 |
| 22 | 程序集拆分 | **Core 独立于 Unity** | Core 为 .NET Standard 2.1 纯类库，零 UnityEngine 依赖；Unity 仅作桥接；dotnet test 即可全量测试 |

---

## 十二、性能指标（来自技术要求终版）

| 指标 | 要求 | 说明 |
|------|------|------|
| 最大目标数 | ≥ 5 独立运动实体（3D 显示） | 显示层面，非逻辑上限 |
| 渲染帧率 | ≥ 30 FPS（满载 + 粒子特效） | Unity 前端职责 |
| 仿真空间 | ≥ 4m × 100m × 10m | 默认更大，可配置 |
| 粒子系统 | 云团扩散/流动/色彩 | Unity 根据后端参数渲染 |
| 毁伤效果 | 尾焰异常、姿态失控、坠落 | Unity 根据 DamageStage 渲染 |

---

## 十三、性能设计（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 的事前规划与运行时实时探测**必须用同一个 3D 球冠判定模型**（`IsInCoverage`）。原因：仿真是按 planner 规划执行的，planner 的探测判定就是仿真的事实依据——若 planner 用比真实探测能力更宽松的几何（如 2D 圆），会规划出"未探测却拦截"的物理错误结局（详见 14.2.1）。

### 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 必须升级到 3D（正确性硬要求，非精度优化）**：
> `EarliestDetection`/`EarliestEntryArc` 当前用线段-圆求交（纯 2D），必须改为"沿航路逐采样点调用 `IsInCoverage`"。
> **原因**：仿真是按 planner 规划执行的——planner 判定"威胁能被发现"→ 据此规划拦截→ 仿真照此执行→ 威胁被摧毁。若 planner 用 2D 判定"能发现"，但威胁实际高度超出 3D 球冠范围（探测设备真实能力达不到），就会出现**"根本没探测到、却被规划拦截并摧毁"的物理错误结局**——防空系统不可能拦截它没发现的目标。
>
> 因此 planner 与实时探测**必须共用同一个 3D 球冠判定**（`IsInCoverage`），这不是为了"好看的统一"，而是正确性硬约束。
>
> **采样法 vs 解析法**：三维球冠-折线解析求交复杂度高，改用沿航路采样（步长 ≤ 50m）。采样引入的弧长误差 ≤ 一个步长，换算到推荐抛撒时机 ≤ ~1.5s（典型 120km/h），远小于云团膨胀窗口（~30s）和云团重叠冗余，**不影响火力计划结果**。所以采样误差可接受，但 2D→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"。**正确性硬要求**：planner 判定=仿真事实，必须与真实 3D 探测能力一致，否则"未探测却拦截" | 待改造 |
| **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 | 探测设备融合逻辑 | 多设备发现同一目标时的数据融合策略（→ 见 14.6 待决策 #2） |
| 4 | 火控分配策略 | 多目标时的优先级分配规则（→ 实时探测接入后见 14.6 待决策 #3） |
| 5 | 蜂群模式运动模型 | 随机扰动范围、个体行为差异 |
| 6 | 报告模板具体格式 | 统计维度、图表需求 |
| 7 | 场景类型对仿真的影响 | 山区是否影响探测/运动 |
| 8 | PDF/Word 导出库选型 | QuestPDF 或其他 .NET Standard 2.1 方案 |
| 9 | 预置典型目标库内容 | 具体包含哪些无人机型号及参数 |
| 10 | 扩散模型的大气稳定度参数 | 不同天气条件下 σ 的增长速率如何量化 |
| 11 | 毁伤曲线的具体数值 | 三种机制的 damageRate、threshold、burstDamage 等参数取值 |
| 12 | 时空交汇优化的精度 | 离散采样步长 vs 解析解的选择 |
