vampire-like/skills/simulation-development/references/SimulationDevelopmentSkill.md

312 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SimulationWorld Architecture Specification
## 文档定位
本文件是 `SimulationWorld` 的架构规范与扩展开发约束。
用途分为两部分:
- 作为当前 `SimulationWorld` 实现的架构总览,说明模块职责、依赖边界、运行链路和数据所有权。
- 作为后续扩展、重构、性能优化和回归修复时的约束文档,防止破坏核心不变量。
文档与实现冲突时,当前分支源码优先;提交前必须同步修正文档。
## 适用范围
- `Assets/GameMain/Scripts/Simulation/*`
- `Assets/GameMain/Scripts/Procedure/Game/GameStateBattle.cs`
- `Assets/GameMain/Scripts/Procedure/Game/ProcedureGame.cs`
- `Assets/GameMain/Scripts/Utility/AIUtility.cs`
- `Assets/Tests/Simulation/EditMode/*`
- `Assets/Tests/Simulation/PlayMode/*`
## 架构目标
- 将战斗中的敌人、投射物、掉落物运行时状态收口到统一仿真容器。
- 将热路径逻辑与 Unity 表现层解耦,避免在仿真阶段直接读写 `Transform`
- 为 Job/Burst 提供稳定的数据通道、生命周期管理和主线程结算收口点。
- 保持 `SimulationWorld` 对外是单一战斗调度入口,而不是分散的业务入口集合。
- 保证扩展新仿真对象或新碰撞规则时,能够沿着既有管线接入,而不是旁路修改。
## 非目标
- 不负责完整战斗规则定义。伤害公式、碰撞业务语义仍由 `AIUtility` 和实体逻辑承担。
- 不负责实体创建策略。实体创建与隐藏仍由外部流程和 Entity 系统负责。
- 不追求全局 ECS 化。本模块仍以 `SimulationWorld + partial + Native 容器` 为中心组织。
- 不在 Job 中直接驱动表现层、事件系统或 Unity 对象生命周期。
## 外部依赖与系统边界
`SimulationWorld` 处于战斗流程中层,位于 `GameStateBattle` 和具体实体逻辑之间。
上游依赖:
- `GameStateBattle.OnUpdate` 驱动每帧 `Tick`
- `GameEntry.Event` 提供实体显示/隐藏事件,用于同步仿真容器生命周期。
- `GameEntry.Entity` 提供实体查询、隐藏和表现事件消费。
下游协作:
- `AIUtility` 负责伤害与碰撞业务结算。
- Enemy/Projectile/Drop 实体提供初始化所需运行时数据。
- Presentation 子模块负责把仿真结果写回表现层。
边界要求:
- 外部业务代码不得直接增删 `_enemies`、`_projectiles`、`_pickups`。
- 外部业务代码不得绕过 `SimulationWorld` 直接维护仿真索引。
- `SimulationWorld` 不直接拥有实体生成权,只消费实体生命周期事件。
## 模块结构
`SimulationWorld` 使用 `partial` 拆分,职责按以下边界划分:
- `SimulationWorld.cs`
- 核心组件入口、主状态容器、基础依赖、Unity 生命周期入口。
- `SimulationWorld.SimEntityState.cs`
- 敌人、投射物、掉落物的仿真态创建、更新、删除和清空。
- `SimulationWorld.EntitySync.cs`
- 监听实体 show/hide 事件,将实体生命周期映射到仿真容器。
- `DataChannel/SimulationWorld.JobDataChannel.cs`
- Native 容器持有、初始化、清理、容量准备、仿真数据到 Job 数据的转换。
- `Jobs/SimulationWorld.EnemyJobs.cs`
- 每帧仿真主编排、敌人移动与互斥分离 Job 调度。
- `Jobs/SimulationWorld.ProjectileJobs.cs`
- 投射物移动、寿命处理、越界回收。
- `Jobs/SimulationWorld.CollisionPipeline.cs`
- 投射物与区域碰撞查询构建、候选筛选、主线程命中结算。
- `SimulationWorld.TargetSelectionSpatialIndex.cs`
- 敌人目标选择空间索引。
- `Presentation/SimulationWorld.TransformSync.cs`
- `LateUpdate` 表现写回。
- `Presentation/SimulationWorld.HitPresentation.cs`
- 命中事件的表现消费桥。
## 核心数据所有权
### 主容器
- `_enemies`
- `_projectiles`
- `_pickups`
这些容器是真实仿真态所有者。Job 输入输出缓冲只是当前帧的镜像通道,不是持久源数据。
### 绑定关系
- `EntityBinding` 维护 `EntityId <-> SimulationIndex` 双向映射。
- 容器删除使用 `swap-back`
- 发生尾元素覆盖时,必须同步 `RemapIndex`
- 删除完成后再 `Unbind`,避免索引悬挂。
### Native 容器
- Job 通道一律使用 `Allocator.Persistent`
- 生命周期由 `InitializeJobDataChannels` / `DisposeJobDataChannels` 集中管理。
- 帧间复用时使用 `Clear`,不允许用临时重建替代正常复用。
- Job 数据与主容器数据之间的转换必须集中在 `JobDataChannel` 侧完成。
## 生命周期模型
### 实体进入仿真
统一由 `EntitySync` 监听实体显示事件后触发:
- Enemy group -> `RegisterEnemyLifecycle`
- Drop group -> `RegisterPickupLifecycle`
- Bullet / Projectile / EnemyProjectile group -> `RegisterProjectileLifecycle`
### 实体退出仿真
统一由 `EntitySync` 监听实体隐藏事件后触发:
- Enemy -> `UnregisterEnemyLifecycle`
- Drop -> `UnregisterPickupLifecycle`
- Projectile 相关 group -> `UnregisterProjectileLifecycle`
### 清场
`ClearSimulationState` 负责:
- 清空主容器
- 清空投射物回收与结算缓存
- 清空区域碰撞请求与命中缓存
- 清空 Job 通道
- 清空全部 `EntityBinding`
## 运行时执行链路
### 帧级入口
1. `GameStateBattle.OnUpdate`
2. `_enemyManager.OnUpdate(...)`
3. `SimulationWorld.Tick(...)`
4. `SimulationWorld.LateUpdate()`
### Tick 总流程
`SimulationWorld.Tick` 是战斗仿真的唯一主入口。
约束:
-`UseSimulationMovement == false` 时,直接返回。
- `Tick` 只负责逻辑仿真与结算,不直接写 `Transform`
### 每帧仿真管线
当前实现的标准顺序为:
1. Early Return
- `DeltaTime <= 0` 时只清理碰撞临时通道和统计。
2. BuildInput
-`_enemies` / `_projectiles` 同步为 Job 输入。
- 准备敌人输出、投射物输出、碰撞查询缓冲。
3. StateUpdate
- 调度敌人移动 Job。
- 调度投射物移动 Job。
4. Schedule
- 按需调度敌人互斥分离 Job。
- 合并敌人与投射物 Job 依赖。
5. Complete
- 等待本帧仿真 Job 完成。
6. Collision
- 构建碰撞查询。
- 构建敌人碰撞桶。
- 生成候选并统计。
7. WriteBack
- 把输出写回主容器。
- 在主线程结算碰撞与伤害。
- 回收失效投射物。
### LateUpdate
`LateUpdate` 只做表现写回,不做逻辑判定:
- 敌人位置/朝向写回
- 投射物位置/朝向写回
## 线程模型与边界
### Job/Burst 允许做的事
- 读取 Job 输入缓冲
- 写入 Job 输出缓冲
- 写入 NativeHashMap / NativeList 等碰撞与分桶数据
- 执行纯数据计算
### Job/Burst 禁止做的事
- 读写 `Transform`
- 操作 GameObject / Entity 生命周期
- 调用事件系统
- 直接调用 `AIUtility.PerformCollision`
- 进行托管分配、LINQ、装箱
### 主线程必须做的事
- 应用输出到仿真主容器
- 命中结算与伤害计算
- 投射物失效回收
- 命中表现事件派发
- `LateUpdate` 表现写回
## 子系统约束
### 敌人仿真
固定接入点:
- BuildInput
- Movement
- Separation
- WriteBack
约束:
- 敌人状态必须以 `EnemySimData` 为中心流动。
- 互斥与移动结果必须先写入输出缓冲,再统一提交。
- 与目标选择相关的空间索引脏标记必须在主容器变更时维护。
### 投射物仿真
固定接入点:
- BuildInput
- Movement
- Collision Query
- Resolve
- Recycle
约束:
- 投射物生命周期状态必须由 `ProjectileSimData.Active``State` 共同表达。
- 投射物实际隐藏与移除只能在主线程回收阶段完成。
### 碰撞管线
职责:
- 构建投射物查询和区域查询
- 生成 broad-phase 候选
- 在主线程做最终业务结算
约束:
- Broad-phase 只能筛候选,不能替代最终命中判定。
- Area Query 必须保留 `SourceWasActiveAtQueryTime` 快照语义。
- 候选去重与区域命中去重只能在主线程收口。
### 目标选择空间索引
职责:
- 提供按位置查询最近敌人的能力。
约束:
- 仅在仿真启用时对外提供结果。
- 敌人主容器变更后必须标记脏。
- 索引是缓存,不是源数据;源数据仍是 `_enemies`
### Presentation
职责:
- 将仿真层结果写回表现层。
- 消费命中表现事件。
约束:
- Presentation 只消费仿真结果,不反向修改仿真逻辑状态。
- 任何新增表现都应接在 `Presentation` 子模块,而不是接回 Job 或业务结算热路径。
## 不可破坏的不变量
### 生命周期单入口
仿真容器的增删必须只经过 `EntitySync``SimEntityState`
### 数据单一事实来源
主容器是持续态事实来源Job 缓冲只是帧级副本。
### 逻辑与表现分离
逻辑阶段不写 `Transform`,表现阶段不做业务结算。
### 索引一致性
任何 `swap-back` 删除都必须同步 remap否则视为架构级错误。
### 主线程结算收口
伤害、事件派发、实体隐藏和回收必须回到主线程。
### 空间索引与碰撞桶是缓存
它们可以重建,不可被外部业务当作持久数据依赖。
## 扩展开发流程
### Step 0判定接入位置
先判断新需求属于:
- 新仿真态字段
- 新执行阶段逻辑
- 新碰撞查询类型
- 新表现桥接
不要一开始就直接改 `Tick` 主流程。
### Step 1扩状态
先补 `SimData`、必要的 Job 输入输出结构和转换逻辑。
### Step 2接生命周期
如果是新实体类型,先定义 show/hide 到仿真态的映射,再进入执行阶段。
### Step 3接执行管线
优先复用已有阶段;只有确实无法收纳时才新增阶段,并补可观测的 profiler 标记。
### Step 4接主线程结算
需要业务判定、伤害、事件派发、实体隐藏时,一律回主线程收口。
### Step 5接表现
视觉写回、命中反馈、临时特效都放在 `Presentation` 侧。
### Step 6补测试
至少覆盖:
- 正常行为
- 空容器和边界条件
- 删除后的索引稳定性
- 碰撞去重或快照语义
- 与旧路径一致的关键行为
### Step 7更新文档
修改模块边界、数据契约、不变量或执行阶段时,必须同步更新本文件。
## 回归关注点
- `ClearSimulationState` 是否把主容器、缓存和 binding 一并清干净。
- 删除路径是否保持 `swap-back + remap` 一致。
- Job Native 容器是否有泄漏或容量管理回退。
- 是否在热路径引入托管分配。
- 是否让表现逻辑重新侵入仿真逻辑。
- 是否破坏 Area Query 快照语义。
- 是否破坏碰撞候选与命中去重。
## 测试建议
至少保留并持续扩展以下类型的测试:
- Tick 行为正确性
- 主线程与 Job 管线一致性
- 最近敌查询正确性
- 投射物候选上限与玩家候选覆盖
- Area Query 快照语义
- 清场和 Battle 循环稳定性
## 维护原则
如果未来需要继续扩展为多模式仿真开关、更多 Job 管线层级或新的仿真对象类型,应优先维护以下三点:
- `Tick` 仍然只有一个主入口
- 仿真态生命周期仍然只有一个注册/反注册入口
- 主线程结算与表现写回边界不被打穿