# 后端对接文档（Unity 前端）

> **版本**：V2.0  
> **日期**：2026-06-20  
> **Unity 版本**：2022.3.62f3c1  

---

## 一、交付物与部署

### 1.1 需要取的文件

```
src/Unity/Assets/Plugins/CounterDrone.Core/   ← 22 个 DLL（拖入 Unity 项目的 Plugins 目录）
src/Unity/Assets/Scripts/Managers/            ← 9 个桥接脚本
```

StreamingAssets 资源文件（放到 Unity 项目 `Assets/StreamingAssets/` 下）：

```
StreamingAssets/
  defaults.json                ← 默认数据（首次运行自动复制到 persistentDataPath）
  planner_config.json          ← 规划器配置
  fonts/CJK-Font.ttf           ← PDF 导出用的中文字体（仅 PDF 导出需要）
```

> 以上资源文件可在仓库的 `data/` 目录找到。字体文件名硬编码为 `CJK-Font.ttf`，可替换为任意 CJK TrueType 字体但不可改名。

### 1.2 DLL 清单（22 个）

全部为纯托管 DLL，无原生库依赖，Unity IL2CPP/Mono 全平台兼容。

| DLL | 用途 |
|-----|------|
| `CounterDrone.Core.dll` | 核心库 |
| `SQLite-net.dll` | SQLite ORM |
| `SQLitePCLRaw.core.dll` / `batteries_v2.dll` / `provider.e_sqlite3.dll` | SQLite 原生提供程序 |
| `System.Text.Json.dll` / `Encodings.Web.dll` / `Microsoft.Bcl.AsyncInterfaces.dll` | JSON 序列化 |
| `System.Memory.dll` / `Buffers.dll` / `Numerics.Vectors.dll` / `Runtime.CompilerServices.Unsafe.dll` / `IO.Pipelines.dll` / `Threading.Tasks.Extensions.dll` | 基础库传递依赖 |
| `PdfSharpCore.dll` | PDF 生成 |
| `ICSharpCode.SharpZipLib.dll` | PdfSharpCore 依赖（ZIP） |
| `SixLabors.Fonts.dll` / `SixLabors.ImageSharp.dll` | PdfSharpCore 依赖（字体/图像） |
| `System.Collections.Concurrent.dll` / `IO.UnmanagedMemoryStream.dll` / `Threading.Tasks.Parallel.dll` / `Threading.dll` / `IO.FileSystem.Primitives.dll` | SixLabors 传递依赖 |

### 1.3 DLL 导入设置

DLL 拖入 `Assets/Plugins/CounterDrone.Core/` 后，在 Inspector 中确认所有 DLL 的平台勾选：**Editor + Standalone + Android + iOS 全部勾选**（所有 DLL 均全平台兼容）。

> **IL2CPP 注意**：若遇 `TypeLoadException`，检查 `Player Settings → Managed Stripping Level`，建议设为 **Low** 或 **Medium**。

### 1.4 更新后端 DLL

```bash
# 方式 A：一键脚本（自动编译 + 复制 DLL + Unity 编译验证）
pwsh scripts/check_unity_build.ps1

# 方式 B：手动
cd src/CounterDrone.Core
dotnet publish -c Release -o ../../unity_plugins
# 然后将 unity_plugins/*.dll 全部复制到 src/Unity/Assets/Plugins/CounterDrone.Core/
```

> **重要**：更新 DLL 后首次运行 Unity 会重建数据库，**历史仿真报告会丢失**（基础数据和预设想定自动重新种子）。这是预期行为。

---

## 二、快速开始

1. 按 1.1 部署 DLL、脚本和 StreamingAssets 资源
2. 创建空 GameObject，挂 `SimulationBootstrap`
3. 运行 — 自动种子数据库，选一个 `[Demo]` 想定，启动仿真

> `SimulationBootstrap` 是参考模板，展示预设模式（一行代码）和自定义模式两种路径。Inspector 勾选 `_usePreset` 切换。

---

## 三、完整工作流（5 步）

```
┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌──────────┐    ┌──────────┐
│ 基础数据 CRUD │ → │  创建想定    │ → │  运行仿真    │ → │  生成报告  │ → │  回放    │
│ DataService  │    │ ScenarioMgr │    │ SimRunner   │    │ ReportMgr │    │ Replay   │
└─────────────┘    └─────────────┘    └─────────────┘    └──────────┘    └──────────┘
```

### 3.1 管理基础数据

```csharp
var ds = GetComponent<ScenarioManager>().DataService;

var drones = ds.GetAllDrones();
ds.SaveDrone(new DroneSpec { Id = "custom", Name = "自定义", TypicalSpeed = 100, ... });
ds.DeleteDrone("custom");

// 7 类基础数据均提供 GetAll/Get/Save/Delete 四个方法
```

### 3.2 创建想定

```csharp
var scenario = GetComponent<ScenarioManager>();

// 方式 A：预设想定
var demos = scenario.Search("Demo", null, null, 1, 100);
var scenarioId = demos.Items[0].Id;

// 方式 B：自定义（5 步配置）
var sc = scenario.CreateScenario("我的想定", "");
scenario.SaveScene(sc.Id, new CombatScene { WindSpeed = 5, WindDirection = (int)WindDirection.E });
scenario.SaveScenarioDrone(sc.Id, new ScenarioDrone { DroneSpecId = "shahed", WaveId = "w1", Quantity = 3 });
scenario.SaveDeployment(sc.Id, new List<ScenarioUnit> {
    new() { FireUnitSpecId = "ground-light", Quantity = 2,
            PositionX = 5000, PositionY = 0, PositionZ = 50, AerosolType = 0, MunitionCount = 8 }
});
scenario.SaveRoute(sc.Id, "w1", new RoutePlan(), new List<Waypoint> {
    new() { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
    new() { PosX = 10000, PosY = 500, PosZ = 0, Speed = 200 }
});
scenario.SaveCloud(sc.Id, new CloudDispersal { AerosolType = 0, DisperseHeight = 500 });
scenario.UpdateStep(sc.Id, 5);
```

### 3.3 运行仿真

```csharp
var runner = GetComponent<SimulationRunner>();
runner.LoadAndStart(scenarioId);
runner.Engine.TimeScale = 4f;

// 事件订阅
runner.Engine.OnMunitionLaunched += m => { };
runner.Engine.OnCloudGenerated   += c => { };
runner.Engine.OnDroneDestroyed   += d => { };
runner.Engine.OnSimulationEnded  += () => { };

// 逐帧驱动（在 Update 中调用）
void Update() {
    var result = runner.Engine.Tick(Time.deltaTime);
    foreach (var snap in result.EntitySnapshots) {
        // snap.EntityType, PosX/Y/Z, VelX/Y/Z, Hp, CloudRadius...
    }
}

runner.Stop();
```

### 3.4 生成报告

```csharp
var rm = GetComponent<ReportManager>();
var detail = scenario.GetDetail(scenarioId);
var report = rm.Generate(scenarioId, detail, runner.Engine.Events.ToList(),
    runner.Engine.Drones[0].Status.ToString(), runner.Engine.SimulationTime);

// Generate() 自动导出 .md 文件到 {persistentDataPath}/reports/
// PDF 按需导出：
byte[] pdf = rm.ExportReport(report.Id, "pdf");     // → byte[]
string path = rm.Export(report.Id, dir, "pdf");     // → 文件路径
```

**Generate 参数**：

| 参数 | 类型 | 来源 |
|------|------|------|
| `scenarioId` | string | 创建想定时获得 |
| `config` | ScenarioConfig | `scenario.GetDetail(id)` |
| `events` | List\<SimEvent\> | `runner.Engine.Events.ToList()`（必须 `.ToList()` 拷贝） |
| `finalDroneStatus` | string | `runner.Engine.Drones[0].Status.ToString()` |
| `duration` | float | `runner.Engine.SimulationTime`（秒） |

**导出方法**：
- `ExportReport(id, "pdf")` → `byte[]`，实时渲染 PDF，需字体文件
- `ExportReport(id, "md")` → `byte[]`，从数据库直接读取，快
- `Export(id, dir, "pdf"/"md")` → 文件路径，自动创建目录
- `Export(id)` → 默认导出 MD 到 `{persistentDataPath}/reports/`

> PDF 导出依赖 `StreamingAssets/fonts/CJK-Font.ttf`，缺失抛 `FileNotFoundException`。PDF 渲染同步阻塞主线程（小报告 ~100ms，大报告可达 2s），UI 建议显示 loading。

### 3.5 回放

```csharp
var replay = GetComponent<ReplayController>();
replay.LoadReplay(scenarioId);
for (int i = 0; i < replay.TotalFrames; i++) {
    var frame = replay.GetFrame(i);
    // frame.SimulationTime, frame.EntitySnapshots...
}
```

---

## 四、坐标系与 3D 可视化

### 4.1 坐标系约定

| 项目 | 约定 |
|------|------|
| **向上轴** | **Y 轴 = 高度**（与 Unity 默认一致） |
| **水平面** | X 轴 = 纵深（航路方向），Z 轴 = 横向 |
| **单位** | 米（m） |
| **原点** | 场景一角（非中心），航路通常从 X=0 沿 X 正方向延伸 |
| **典型场景尺寸** | 4000m × 10000m × 1000m（W × L × H，见 `CombatScene.SceneWidth/Length/Height`） |
| **典型无人机高度** | 300~500m（PosY = 500 即 500m 高空） |
| **速度单位** | 航路点 `Waypoint.Speed` = **km/h**；平台 `CruiseSpeed` = **m/s**（引擎内部自动换算） |

> `SimulationRunner` 直接 `transform.position = new Vector3(snap.PosX, snap.PosY, snap.PosZ)`，无需轴变换。

### 4.2 Prefab 搭建

`SimulationRunner` 有 3 个 `[SerializeField]` prefab 字段，Inspector 中拖入：

| 字段 | 用途 | null 时回退 | 建议缩放 |
|------|------|------------|---------|
| `_dronePrefab` | 无人机 | Cube | Runner 自动设 `localScale = Vector3.one * 50f` |
| `_cloudPrefab` | 云团 | Sphere | Runner 按云团半径动态设 `localScale = Vector3.one * CloudRadius` |
| `_platformPrefab` | 发射平台 | Capsule | Runner 自动设 `localScale = (30, 80, 30)` |
| —（无字段） | 弹药 | Cylinder | Runner 自动设 `localScale = (2, 10, 2)` |

**Prefab 组件要求**：

| 组件 | 必需 | 说明 |
|------|------|------|
| `Renderer` | ✅ | Runner 通过 `GetComponent<Renderer>().material` 设置颜色 |
| `Collider` | 可选 | Runner 不依赖 Collider |
| 脚本组件 | 不需要 | 位置由 Runner 每帧驱动，prefab 不需要自带移动脚本 |

**云团可视化建议**：prefab 用 Sphere 即可（Runner 设红色半透明材质）。如需粒子效果，可自行扩展——Runner 每帧更新 `CloudRadius`（缩放）和 `CloudOpacity`（材质 alpha），粒子系统可读取这两个值驱动参数。

### 4.3 SimulationRunner 可视化机制

Runner 的 `Update()` 每帧执行：

1. 调用 `engine.Tick(Time.deltaTime)` → 获取 `SimulationFrameResult`
2. 遍历 `result.EntitySnapshots`：
   - 新实体（EntityId 未见过）→ Instantiate prefab
   - 已有实体 → 更新 `transform.position`
   - Cloud 类型 → 更新 `localScale`（按 `CloudRadius`）和材质 alpha（按 `CloudOpacity`）
3. 遍历 `engine.Munitions` 同步炮弹位置，清理已消失的弹药 GameObject

**实体颜色约定**（Runner 硬编码，可自行修改）：
- 无人机：prefab 原始材质
- 云团：红色半透明 `(1, 0, 0, CloudOpacity)`
- 平台：蓝色半透明 `(0.2, 0.5, 1, 0.8)`
- 弹药：黄色 `(1, 0.92, 0.016, 1)`

### 4.4 相机定位参考

`SimulationBootstrap` 中的相机设置（可供参考）：

```csharp
// 俯瞰视角
cam.transform.position = new Vector3(midX, 2000, -3000);  // midX = 场景中点
cam.transform.LookAt(new Vector3(midX, 500, 0));          // 看向 500m 高度
```

### 4.5 探测范围可视化

`DetectionEntity` 提供 `IsInRange(x, y, z, visibility, out effectiveRange)` 方法。如需绘制 3D 球冠范围，可从 `SensorSpec` 读取 `RadarRange`/`EORange`/`IRRange` 和 `MinElevation`/`MaxElevation`/`MinDetectAlt`/`MaxDetectAlt`，用 wireframe sphere 或 mesh 表示。

### 4.6 管控区域可视化

`ControlZone` 的 `VerticesJson` 存储多边形顶点 JSON：`[{"x":0,"y":0,"z":0},...]`。配合 `MinAltitude`/`MaxAltitude` 构成 3D 棱柱区域。前端可用 `LineRenderer` 画底面多边形 + 垂直线段表示高度范围。

---

## 五、Manager API 完整签名

所有 Manager 继承 `MonoBehaviour`，在 `Awake()` 中初始化内部服务。**必须在 `Awake` 之后（如 `Start`）才能调用**，`OnDisable` 后不可调用。

### ScenarioManager

```csharp
public IScenarioService Service { get; }
public IDataService DataService { get; }       // 基础数据 CRUD（7 类）

// Scenario CRUD
public Scenario CreateScenario(string name, string number);
public Scenario UpdateScenario(Scenario s);
public void DeleteScenario(string id);
public ScenarioConfig GetDetail(string id);
public PagedResult<Scenario> Search(string kw, string from, string to, int page, int size);

// CombatScene
public void SaveScene(string scenarioId, CombatScene s);
public CombatScene GetScene(string scenarioId);

// ControlZone
public void SaveControlZones(string scenarioId, List<ControlZone> z);
public List<ControlZone> GetControlZones(string scenarioId);
public void DeleteControlZone(string id);

// ScenarioDrone
public ScenarioDrone SaveScenarioDrone(string scenarioId, ScenarioDrone d);
public ScenarioDrone AddScenarioDrone(string scenarioId, ScenarioDrone d);
public void UpdateScenarioDrone(ScenarioDrone d);
public void DeleteScenarioDrone(string id);
public List<ScenarioDrone> GetScenarioDrones(string scenarioId);

// ScenarioUnit（部署）
public void SaveDeployment(string scenarioId, List<ScenarioUnit> e);
public ScenarioUnit AddDeploymentUnit(string scenarioId, ScenarioUnit u);
public void UpdateDeploymentUnit(ScenarioUnit u);
public void DeleteDeploymentUnit(string id);
public List<ScenarioUnit> GetDeploymentUnits(string scenarioId);

// Detection（探测设备，复用 ScenarioUnit）
public void AddDetection(string scenarioId, ScenarioUnit d);
public void DeleteDetection(string id);
public List<ScenarioUnit> GetDetections(string scenarioId);

// CloudDispersal
public void SaveCloud(string scenarioId, CloudDispersal c);
public CloudDispersal GetCloud(string scenarioId);
public void DeleteCloud(string scenarioId);

// Route & Waypoint
public void SaveRoute(string scenarioId, string waveId, RoutePlan r, List<Waypoint> w);
public List<RoutePlan> GetRoutes(string scenarioId);
public void DeleteRoute(string id);
public List<Waypoint> GetWaypoints(string scenarioId);
public List<Waypoint> GetWaypointsByWave(string scenarioId, string waveId);
public void DeleteWaypoint(string id);

// Misc
public void UpdateStep(string scenarioId, int step);
public EnumMetadata GetEnums();    // 枚举中英文对照（下拉框数据源）
```

### IDataService（通过 `ScenarioManager.DataService` 访问）

7 类基础数据，每类 4 个方法（`GetAll*` / `Get*(id)` / `Save*(spec)` / `Delete*(id)`）：

| 基础数据类型 | 方法前缀 | 模型类 |
|---|---|---|
| 弹药 | `Ammo` | `AmmunitionSpec` |
| 火力单元 | `FireUnit` | `FireUnitSpec` |
| 无人机 | `Drone` | `DroneSpec` |
| 传感器 | `Sensor` | `SensorSpec` |
| 环境 | `Environment` | `EnvironmentSpec` |
| 编队 | `Formation` | `FormationTemplate` |
| 航线 | `Route` | `RouteTemplate` |

```csharp
List<DroneSpec> GetAllDrones();
DroneSpec GetDrone(string id);
void SaveDrone(DroneSpec spec);
void DeleteDrone(string id);
// 其余 6 类签名相同，替换前缀即可
```

### SimulationRunner

```csharp
public FrameDataStore FrameStore { get; }
public SimulationEngine Engine { get; }      // 核心引擎
public IDataService DataService { get; set; }

public void LoadAndStart(string scenarioId);
public void Stop();
```

通过 `runner.Engine` 访问的成员：

```csharp
// ── 状态 ──
runner.Engine.State              // SimulationState: Idle/Running/Paused/Stopped/Completed
runner.Engine.TimeScale          // float，时间倍率（默认 1，设 4 = 4 倍速）
runner.Engine.SimulationTime     // float，仿真累计时间（秒）
runner.Engine.FrameIndex         // int，当前帧序号
runner.Engine.RecordFrames       // bool，是否录制帧数据（回放用），默认 true

// ── 实体列表（IReadOnlyList，只读） ──
runner.Engine.Drones             // IReadOnlyList<DroneEntity>
runner.Engine.Clouds             // IReadOnlyList<CloudEntity>
runner.Engine.Munitions          // IReadOnlyList<MunitionEntity>
runner.Engine.Platforms          // IReadOnlyList<PlatformEntity>
runner.Engine.DetectionEntities  // IReadOnlyList<DetectionEntity>
runner.Engine.Events             // IReadOnlyList<SimEvent>（仿真期间持续追加）

// ── 方法 ──
runner.Engine.Tick(float dt)     // → SimulationFrameResult（推进一帧）
runner.Engine.Pause()            // Running → Paused
runner.Engine.Resume()           // Paused → Running
runner.Engine.Stop()             // → Stopped + Flush 帧数据到数据库

// ── 事件（Action 委托） ──
runner.Engine.OnMunitionLaunched   // Action<MunitionEntity>
runner.Engine.OnCloudGenerated     // Action<CloudEntity>
runner.Engine.OnDroneDestroyed     // Action<DroneEntity>
runner.Engine.OnDroneReachedTarget // Action<DroneEntity>
runner.Engine.OnZoneIntruded       // Action<DroneEntity, ControlZoneEntity>
runner.Engine.OnTargetDetected     // Action<DroneEntity, DetectionEntity>
runner.Engine.OnSimulationEnded    // Action
```

### ReportManager

```csharp
public IReportService Service { get; }

public SimulationReport Generate(string scenarioId, ScenarioConfig cfg,
    List<SimEvent> events, string status, float duration);
public SimulationReport Get(string id);
public void Delete(string id);
public PagedResult<SimulationReport> Search(string kw, string from, string to, int page, int size);

public byte[] ExportReport(string reportId, string format);              // "pdf" | "md"
public string Export(string reportId, string outputDir, string format);  // "pdf" | "md"
public string Export(string reportId);                                   // 默认 MD 到 reports 目录
```

### ReplayController

```csharp
public int TotalFrames { get; }
public int CurrentFrameIndex { get; }
public List<SimFrameRecord> CurrentFrames { get; }

public void LoadReplay(string scenarioId, FrameDataStore frameStore = null);
public List<SimFrameRecord> GetFrame(int frameIndex);
```

> `LoadReplay` 优先用内存帧数据（仿真刚结束），无则从 SQLite 帧库加载（历史回放）。

### ModelManager

```csharp
public IModelService Service { get; }

public ModelInfo Import(string filePath, string name);
public ModelInfo Import(string filePath, string name, int entityType, string desc);
public ModelInfo Add(ModelInfo model);
public ModelInfo UpdateModel(ModelInfo model);
public void Delete(string id);
public List<ModelInfo> GetAll();
public ModelInfo Get(string id);
```

---

## 六、枚举值清单

### 6.1 有官方中文翻译的枚举（GetEnums() 返回）

这 12 个枚举通过 `scenario.GetEnums()` 获取中英文对照，可直接用于下拉框：

```csharp
var enums = scenario.GetEnums();
// enums.DroneType, enums.PowerType, enums.PlatformType, enums.AerosolType,
// enums.WeatherType, enums.WindDirection, enums.SceneType, enums.FormationMode,
// enums.TriggerMode, enums.ReleaseMode, enums.EquipmentRole, enums.EntityType
```

| 枚举 | 成员 = 值 | 中文 |
|------|----------|------|
| **DroneType** | Rotor=0, FixedWing=1, HighSpeed=2 | 旋翼/固定翼/高速目标 |
| **PowerType** | Electric=0, Piston=1, Jet=2 | 电推/活塞/喷气 |
| **PlatformType** | AirBased=0, GroundBased=1 | 空基/地基 |
| **AerosolType** | InertGas=0, ActiveMaterial=1, ActiveFuel=2 | 惰性气体(吸入式灭火)/活性材料(爆燃式)/活性燃料(吸入式爆炸) |
| **WeatherType** | Sunny=0, Overcast=1, Fog=2, Rain=3, Night=4 | 晴/阴/雾/雨/夜 |
| **WindDirection** | N=0, NE=1, E=2, SE=3, S=4, SW=5, W=6, NW=7 | 北/东北/东/东南/南/西南/西/西北 |
| **SceneType** | Urban=0, Plain=1, Mountain=2, Coast=3 | 城市/平原/山区/海岸 |
| **FormationMode** | Single=0, Formation=1, Swarm=2 | 单机/编队/蜂群 |
| **TriggerMode** | Time=0, Area=1, Manual=2 | 时间触发/区域触发/手动触发 |
| **ReleaseMode** | Single=0, Continuous=1, Pulse=2 | 单次齐射/连续释放/脉冲释放 |
| **EquipmentRole** | Detection=0, LaunchPlatform=1 | 探测设备/发射平台 |
| **EntityType** | Drone=0, Platform=1, DetectionEquip=2, Cloud=3, Munition=4 | 无人机/发射平台/探测设备/云团/弹药 |

### 6.2 无官方翻译的枚举（前端需自行映射中文）

| 枚举 | 命名空间 | 成员 = 值 | 含义 |
|------|---------|----------|------|
| **SimulationState** | `Simulation` | Idle=0, Running=1, Paused=2, Stopped=3, Completed=4 | 空闲/运行/暂停/停止/完成 |
| **DroneStatus** | `Simulation` | Flying=0, Destroyed=1, ReachedTarget=2, ZoneIntruded=3 | 飞行中/已摧毁/到达目标/侵入管控区 |
| **PlatformState** | `Simulation` | Idle=0, FlyingToTarget=1, ReadyToRelease=2 | 空闲/飞向目标/准备投放 |
| **SimEventType** | `Models` | TargetDetected=0, MunitionLaunched=1, CloudGenerated=2, DroneEnteredCloud=3, DroneDestroyed=4, DroneReachedTarget=5, ZoneIntruded=6, WaypointReached=7, SimulationEnd=8, PlanningFailed=9 | 目标发现/弹药发射/云团生成/进入云团/摧毁/到达目标/侵入管控区/到达航点/仿真结束/规划失败 |
| **InterceptResult** | `Models` | Success=0, Partial=1, Failed=2 | 成功拦截/部分拦截/拦截失败 |
| **DamageStage** | `Models` | Normal=0, EngineAnomaly=1, AttitudeLoss=2, Destroyed=3 | 正常/引擎异常/姿态丧失/摧毁 |
| **ScenarioStatus** | `Models` | Draft=0, Configuring=1, Completed=2 | 草稿/配置中/已完成 |

> **EntitySnapshot.EntityType** 用的是 `EntityType` 枚举（int），判断实体类型时用 `(EntityType)snap.EntityType` 或直接比较 int 值。

---

## 七、关键数据结构

### EntitySnapshot（每帧推送）

| 字段 | 类型 | 说明 |
|------|------|------|
| `EntityId` | string | 实体唯一 ID（属性） |
| `EntityType` | EntityType | Drone=0/Platform=1/DetectionEquip=2/Cloud=3/Munition=4（属性） |
| `PosX/Y/Z` | float | 三维位置（Y=高度，米） |
| `RotX/Y/Z` | float | 旋转 |
| `VelX/Y/Z` | float | 瞬时速度 (m/s) |
| `Hp` | float | 无人机血量 (0~1) |
| `DamageStage` | int | 毁伤阶段（0正常/1引擎异常/2姿态丧失/3摧毁） |
| `PlatformStateStr` | string? | 平台状态：Idle/FlyingToTarget/ReadyToRelease |
| `CloudRadius` | float | 云团有效半径（米） |
| `CloudOpacity` | float | 不透明度 (0~1) |
| `CloudPhase` | int | 扩散阶段 (1/2/3) |
| `CloudElapsed` | float | 云团已存在时间 (s) |
| `ModelId` | string? | 3D 模型 ID（Unity 实例化用） |

> 除 `EntityId`/`EntityType` 是属性外，其余均为**公共字段**（非属性）。

### SimEvent（仿真事件）

```csharp
public class SimEvent
{
    public SimEventType Type { get; set; }       // 事件类型（见枚举表）
    public float OccurredAt { get; set; }        // 发生时刻（仿真秒）
    public string SourceId { get; set; }         // 触发实体 ID
    public string TargetId { get; set; }         // 目标实体 ID
    public string DataJson { get; set; }         // 扩展数据（JSON）
    public string Description { get; set; }      // 人类可读描述
}
```

### SimulationFrameResult（Tick 返回值）

```csharp
public class SimulationFrameResult
{
    public List<EntitySnapshot> EntitySnapshots { get; set; }  // 本帧所有实体快照
    public List<SimEvent> NewEvents { get; set; }              // 本帧新事件
    public SimulationState State { get; set; }                 // 当前仿真状态
    public int FrameIndex { get; set; }                        // 帧序号
    public float SimulationTime { get; set; }                  // 仿真累计时间
}
```

### PagedResult&lt;T&gt;（分页查询返回值）

```csharp
public class PagedResult<T>
{
    public List<T> Items { get; set; }      // 当前页数据
    public int TotalCount { get; set; }     // 总记录数
    public int Page { get; set; }           // 当前页码（从 1 开始）
    public int PageSize { get; set; }       // 每页条数
    public int TotalPages { get; }          // 总页数（计算属性）
}
```

### EnumMetadata / EnumItem（GetEnums 返回值）

```csharp
public class EnumItem
{
    public string Name { get; set; }         // 英文名
    public string ChineseName { get; set; }  // 中文名
    public int Value { get; set; }           // 枚举值
}

public class EnumMetadata
{
    public List<EnumItem> DroneType { get; set; }
    public List<EnumItem> PowerType { get; set; }
    public List<EnumItem> PlatformType { get; set; }
    public List<EnumItem> AerosolType { get; set; }
    public List<EnumItem> WeatherType { get; set; }
    public List<EnumItem> WindDirection { get; set; }
    public List<EnumItem> SceneType { get; set; }
    public List<EnumItem> FormationMode { get; set; }
    public List<EnumItem> TriggerMode { get; set; }
    public List<EnumItem> ReleaseMode { get; set; }
    public List<EnumItem> EquipmentRole { get; set; }
    public List<EnumItem> EntityType { get; set; }
}
```

### SimulationReport

| 字段 | 类型 | 说明 |
|------|------|------|
| `Id` | string | 报告 ID（主键） |
| `ScenarioId` | string | 想定 ID |
| `ScenarioName` | string | 想定名称 |
| `ScenarioNumber` | string | 想定编号 |
| `CompletedAt` | string | 完成时间（UTC ISO 8601） |
| `DroneCount` | int | 无人机总数 |
| `UnitCount` | int | 装备总数 |
| `InterceptResult` | int | 拦截结果（0成功/1部分/2失败） |
| `Content` | string | Markdown 报告文本（快速展示用） |
| `ReportDataJson` | string? | 结构化数据 JSON（PDF 导出用） |

---

## 八、运行时实体属性

仿真运行时通过 `runner.Engine.Drones` / `Clouds` / `Munitions` / `Platforms` / `DetectionEntities` 直接访问。

| 实体 | 关键属性 |
|------|---------|
| **DroneEntity** | Id, PosX/Y/Z, Hp, Status(DroneStatus), DroneType, PowerType, Wingspan, CruiseSpeed, Route, TraveledArc, TotalArc, Progress, ModelId |
| **CloudEntity** | Id, PosX/Y/Z, Radius, Density, Phase(1/2/3), Elapsed, IsDissipated, AerosolType |
| **MunitionEntity** | Id, PosX/Y/Z, Velocity(Vx,Vy,Vz), StartX/Y/Z, LaunchTime, ElapsedTime, LaunchAngle, Azimuth, MuzzleVelocity, FlightDuration, TargetX/Y/Z, HasArrived |
| **PlatformEntity** | Id, PosX/Y/Z, CurrentVelocity, State(PlatformState), MunitionCount, CooldownRemaining, MuzzleVelocity, CruiseSpeed, ReleaseAltitude, TargetX/Y/Z, FlightDistance, Ready, CanRelease |
| **DetectionEntity** | Id, PosX/Y/Z, Source, IsInRange(x,y,z,visibility,out range), IsDetected(droneId), ModelId |
| **ControlZoneEntity** | Id, Name, Vertices(List\<Vector3\>), MinAltitude, MaxAltitude, ContainsPoint(x,y,z) |

---

## 九、核心模型完整字段

### 9.1 ScenarioConfig（想定配置聚合根 — GetDetail 返回值）

```
ScenarioConfig
  Info: Scenario                        // 想定主表
  Scene: CombatScene                    // 步骤1：作战环境
  ControlZones: List<ControlZone>       // 步骤1：管控区域
  Drones: List<ScenarioDrone>           // 步骤2：威胁目标
  Units: List<ScenarioUnit>             // 步骤3：我方部署（含探测+发射平台）
  Cloud: CloudDispersal                 // 步骤4：云团抛撒配置
  Routes: List<RoutePlan>               // 步骤5：航路规划（多批次）
  WaypointGroups: Dictionary<string, List<Waypoint>>  // 按 WaveId 分组的航路点
```

### 9.2 Scenario（想定主表）

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `Id` | string | GUID | 主键 |
| `Name` | string | "" | 想定名称 |
| `ScenarioNumber` | string | "" | 想定编号（唯一） |
| `Description` | string | "" | 描述 |
| `Status` | int | 0 | 0=草稿 1=配置中 2=已完成 |
| `CurrentStep` | int | 1 | 当前配置步骤（1~5） |
| `TickRate` | int | 20 | 仿真帧率 |
| `CreatedAt` | string | UTC ISO | 创建时间 |
| `UpdatedAt` | string | UTC ISO | 更新时间 |

### 9.3 CombatScene（步骤 1：作战环境）

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `ScenarioId` | string | "" | 主键 = 想定 ID |
| `SceneType` | int | 1(Plain) | 0=城市 1=平原 2=山区 3=海岸 |
| `TimeOfDay` | string | "12:00" | 时间段 |
| `WeatherType` | int | 0(Sunny) | 0=晴 1=阴 2=雾 3=雨 4=夜 |
| `WindSpeed` | double | 5.0 | 风速 m/s |
| `WindDirection` | int | 0(N) | 0~7 = N/NE/E/SE/S/SW/W/NW |
| `Temperature` | double | 20.0 | 温度 °C |
| `Humidity` | double | 60.0 | 湿度 %RH |
| `Pressure` | double | 1013.0 | 气压 hPa |
| `Visibility` | double | 5000.0 | 能见度 m |
| `SceneWidth` | double | 4000.0 | 场景宽 m |
| `SceneLength` | double | 10000.0 | 场景长 m |
| `SceneHeight` | double | 1000.0 | 场景高 m |

### 9.4 ControlZone（管控区域）

| 字段 | 类型 | 说明 |
|------|------|------|
| `Id` | string | 主键 |
| `ScenarioId` | string | 想定 ID |
| `Name` | string | 区域名称 |
| `VerticesJson` | string | 多边形顶点 JSON：`[{"x":0,"y":0,"z":0},...]` |
| `MinAltitude` | double | 高度下限 m |
| `MaxAltitude` | double | 高度上限 m |
| `OrderIndex` | int | 排序序号 |

### 9.5 ScenarioDrone（步骤 2：威胁目标）

| 字段 | 类型 | 说明 |
|------|------|------|
| `Id` | string | 主键 |
| `ScenarioId` | string | 想定 ID |
| `DroneSpecId` | string | FK → DroneSpec |
| `WaveId` | string | 批次 ID（如 "w1"） |
| `Quantity` | int | 数量 |

### 9.6 ScenarioUnit（步骤 3：我方部署）

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `Id` | string | GUID | 主键 |
| `ScenarioId` | string | "" | 想定 ID |
| `EquipmentRole` | int | 1 | 0=探测设备 1=发射平台 |
| `LaunchPlatformSpecId` | string | "" | FK → LaunchPlatformSpec（发射平台时填） |
| `SensorSpecId` | string | "" | FK → SensorSpec（探测设备时填） |
| `Quantity` | int | 1 | 数量 |
| `PositionX/Y/Z` | double | 0 | 部署位置（Y=高度，米） |
| `AerosolType` | int? | null | 挂载弹药类型（0/1/2） |
| `AmmunitionSpecId` | string | "" | FK → AmmunitionSpec（挂载的弹药规格，前端配置用） |
| `MunitionCount` | int? | null | 弹药数量 |
| `WaveId` | string | "" | 关联批次 |
| `Description` | string | "" | 备注 |
| `Source` | int | 0 | 0=手动 1=算法推荐 |

### 9.7 CloudDispersal（步骤 4：云团抛撒配置）

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `ScenarioId` | string | "" | 主键 = 想定 ID |
| `AerosolType` | int | 0 | 0=惰性气体 1=活性材料 2=活性燃料 |
| `DisperseHeight` | double | 300.0 | 抛撒高度 m |
| `TriggerMode` | int | 0 | 0=时间 1=区域 2=手动 |
| `SpreadAngle` | double | 0 | 扩散方向 ° |
| `ReleaseMode` | int | 0 | 0=单次 1=连续 2=脉冲 |
| `InitialScale` | double | 1000.0 | 初始规模 m³ |
| `Duration` | double | 60.0 | 持续时间 s |
| `ReleaseInterval` | double | 5.0 | 释放间隔 s |
| `TotalAmount` | double | 50.0 | 释放总量 kg |
| `PositionX/Y/Z` | double | 0 | 抛撒位置 |
| `PositionMode` | int | 1 | 0=点选 1=手动输入 2=算法推荐 |
| `Source` | string | "Manual" | "Manual" 或 "Algorithm" |
| `SalvoRounds` | int | 0 | 推荐弹药数（算法填充） |
| `SalvoSpacing` | double | 0 | 云团间距 m（算法填充） |
| `EstimatedProbability` | double? | null | 算法估计拦截概率 |
| `RecommendedTiming` | double? | null | 推荐抛撒时机 s |
| `RecommendedPosX/Y/Z` | double? | null | 推荐抛撒位置 |
| `CriticalPosX/Y/Z` | double? | null | 关键拦截位置 |
| `CriticalTiming` | double? | null | 关键拦截时机 s |

### 9.8 RoutePlan（步骤 5：航路规划）

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `Id` | string | GUID | 主键 |
| `ScenarioId` | string | "" | 想定 ID |
| `WaveId` | string | "" | 批次 ID |
| `FormationMode` | int | 0 | 0=单机 1=编队 2=蜂群 |
| `LateralSpacing` | double | 50.0 | 横向间距 m |
| `LateralCount` | int? | null | 横向架数（null=全部） |
| `LongitudinalCount` | int? | null | 纵深架数（null=1） |
| `LongitudinalSpacing` | double | 50.0 | 纵向间距 m |
| `LateralAxis` | int | 2 | 横向展开轴：0=X 1=Y(垂直) 2=Z(默认) |
| `LongitudinalAxis` | int | 0 | 纵向串列轴：0=X(默认) 1=Y 2=Z |
| `ETA` | string | "" | 预计到达时间 |

### 9.9 Waypoint（航路点）

| 字段 | 类型 | 说明 |
|------|------|------|
| `Id` | string | 主键 |
| `ScenarioId` | string | 想定 ID |
| `WaveId` | string | 批次 ID |
| `OrderIndex` | int | 顺序索引（从 0 开始） |
| `PosX` | double | X 坐标 m |
| `PosY` | double | Y 坐标 = 高度 m |
| `PosZ` | double | Z 坐标 m |
| `Altitude` | double | 高度 m（与 PosY 冗余） |
| `Speed` | double | 该段速度 **km/h** |

---

## 十、基础数据模型完整字段

### 10.1 DroneSpec（无人机模板）

| 字段 | 类型 | 说明 |
|------|------|------|
| `Id` | string | 主键 |
| `Name` | string | 名称 |
| `DroneType` | int | 0=旋翼 1=固定翼 2=高速 |
| `Model` | string | 型号名称 |
| `Description` | string | 描述 |
| `PowerType` | int | 0=电推 1=活塞 2=喷气 |
| `Wingspan` | double | 翼展 m |
| `TypicalSpeed` | double | 典型速度 km/h |
| `TypicalAltitude` | double | 典型高度 m |
| `ModelId` | string | 3D 模型 ID（FK → ModelInfo） |

### 10.2 LaunchPlatformSpec（发射平台模板）

| 字段 | 类型 | 说明 |
|------|------|------|
| `Id` | string | 主键 |
| `Name` | string | 名称 |
| `Description` | string | 描述 |
| `PlatformType` | int | 0=空基 1=地基 |
| `GunCount` | int | 火炮数量 |
| `ChannelsPerGun` | int | 每炮通道数 |
| `ChannelInterval` | double | 同通道发射间隔 s |
| `Cooldown` | double | 射击冷却 s |
| `AmmoChangeTime` | double | 换弹时间 s |
| `MuzzleVelocity` | double | 初速 m/s |
| `CruiseSpeed` | double | 巡航速度 m/s（空基） |
| `ReleaseAltitude` | double | 释放高度 m（空基） |
| `ModelId` | string | 3D 模型 ID |

### 10.3 SensorSpec（传感器模板）

| 字段 | 类型 | 说明 |
|------|------|------|
| `Id` | string | 主键 |
| `Name` | string | 名称 |
| `RadarRange` | double | 雷达探测距离 m（0=无） |
| `EORange` | double | 光电探测距离 m |
| `IRRange` | double | 红外探测距离 m |
| `Accuracy` | double | 探测精度（位置误差 m） |
| `MinElevation` | double? | 最小仰角 |
| `MaxElevation` | double? | 最大仰角 |
| `MinDetectAlt` | double? | 最小可探测高度 m |
| `MaxDetectAlt` | double? | 最大可探测高度 m |
| `ModelId` | string | 3D 模型 ID |

### 10.4 AmmunitionSpec（弹药模板）

| 字段 | 类型 | 说明 |
|------|------|------|
| `Id` | string | 主键 |
| `AerosolType` | int | 0=惰性气体 1=活性材料 2=活性燃料 |
| `Name` | string | 名称 |
| `InitialRadius` | double | 初始半径 m |
| `InitialVolume` | double | 初始体积 m³ |
| `CoreDensity` | double | 核心密度 |
| `EdgeDensity` | double | 边缘密度 |
| `InitialTemperature` | double | 初始温度 |
| `BuoyancyFactor` | double | 浮力系数 |
| `EffectiveConcentration` | double | 有效浓度 |
| `SourceStrength` | double | 源强 Q (kg) |
| `DispersionRateBase` | double | 基础扩散速率 m/s |
| `BurstChargeKg` | double | 爆发药 TNT 当量 kg |
| `TurbulentExpansionK` | double | 湍流扩散系数 |
| `Phase2Duration` | double | Phase 2 持续时间 s |
| `MaxRadius` | double | 最大半径 m |
| `MaxDuration` | double | 最大持续时间 s |
| `ParticlesJson` | string | 粒子渲染参数 JSON |

### 10.5 EnvironmentSpec / FormationTemplate / RouteTemplate / ModelInfo

| 模型 | 关键字段 |
|------|---------|
| **EnvironmentSpec** | Id, Name, WeatherType, WindSpeed, WindDirection, Temperature, Humidity, Pressure, Visibility |
| **FormationTemplate** | Id, Name, FormationMode, LateralCount, LongitudinalCount, LateralSpacing, LongitudinalSpacing, LateralAxis, LongitudinalAxis |
| **RouteTemplate** | Id, Name, WaypointsJson(`[{"x":0,"y":0,"z":0},...]`) |
| **ModelInfo** | Id, Name, EntityType(0=Drone/1=Platform/2=DetectionEquip), Description, FilePath, FileSize, PreviewPath, HasHighResTexture, CreatedAt |

---

## 十一、默认数据

首次运行自动种子，含 6 个 `[Demo]` 预设想定：

| 类别 | 示例 ID |
|------|---------|
| 弹药 | `inert` / `active` / `fuel` |
| 编队 | `single` / `line-3` / `swarm-10` |
| 航线 | `5km-h500` / `10km-h500` / `20km-h500` |
| 火力单元 | `ground-light` / `air-standard` |
| 无人机 | `shahed` / `quadcopter` / `cruise-missile` |
| 传感器 | `radar-mr` / `eo-station` |
| 环境 | `sunny-calm` / `fog` / `night` |

---

## 十二、参考

| 文档 | 位置 |
|------|------|
| 架构设计 | `docs/design/architecture/总体架构设计.md` |
| 实体事件映射 | `docs/design/technical/仿真器实体与事件映射.md` |
| 测试状态 | **262 测试，15 秒（全部通过）** |
