diff --git a/Docs_Dev/superpowers/specs/2026-07-29-enemy-ai-composable-modules-design.md b/Docs_Dev/superpowers/specs/2026-07-29-enemy-ai-composable-modules-design.md index fda43b14..f7009dc0 100644 --- a/Docs_Dev/superpowers/specs/2026-07-29-enemy-ai-composable-modules-design.md +++ b/Docs_Dev/superpowers/specs/2026-07-29-enemy-ai-composable-modules-design.md @@ -67,7 +67,38 @@ if (c.Engagement == EngagementStyle.ChaseAbility && c.PatrolBetweenChases) ## 3. 架构 -### 3.1 四层 +> ### 修订(2026-07-29,T9 执行中):死亡层取消,四层收为三层 +> +> 初稿把「死亡」做成第三个可插拔层(`IDeathModule` + `TerminalDeath` / `AbilityDeath` / +> `TwoStageDeath`)。实施 T9 时的质量审查发现 `TwoStageDeath` **在真实运行中必然卡死**, +> 追根后确认整个死亡层放错了层。已取消该层,骨架自己声明 `Death` 终态。 +> +> **为什么必然卡死**:`EnemyBase.PerformDeath` 先 `ForceState(EnemyStateType.Dead)` +> (终态,`ForceState` 拒绝任何转出)再 `_brain.Send(AiSignal.Died)`。 +> `EnemyBrainContext.IsControllable` 是 `CurrentState == Controlled`,此时已永久为假。 +> `AiRuntime.Tick` 的顺序是:事件转换(不受门限制)→ **`IsControllable` 让位门,为假即 return** +> → 条件转换。于是 `Died` 事件能把状态切进死亡链,但此后**任何条件边都不再被求值**—— +> `TwoStageDeath` 的 `preDone` 边永远不触发,前段演出播完就永久停住。 +> +> **为什么 `AbilityDeath` 也不成立**:敌人确实能活到下一帧(`PerformDeath` 在死亡动画 +> `OnEnd` 才归还对象池),死亡态的 `OnEnter` 会执行。但那时 `_abilities.InterruptAll(Dead)` +> 已跑过、碰撞体已关、`Dead` 动画已在播——此刻再触发一个能力是在和 `PerformDeath` +> 自己的死亡演出打架。 +> +> **根因**:死亡在本项目里完全归物理层:`PerformDeath`(切终态/清效果/关碰撞/播死亡动画/ +> 归还池/广播事件)、`EnemyDeathSequence`(死亡前摇无敌演出,播完回调 `PerformDeath`; +> 前摇动画上可挂 `SpawnProjectile` 事件配合 `EnemySpawnerOnEvent` 生成小怪——E004/E005 +> 的「两段死亡 + 生成幼蛭」正是由它覆盖)、`EnemyAnimationConfigSO.Dead`。 +> **AI 图需要 `Death` 态只是为了让全局 `Died` 边有去处并让决策停止。死亡不是一个决策。** +> +> 初稿之所以做成一层,是因为旧 `PerceptionStateMachine.Config` 有 `DeathAbilityId` 字段—— +> 而其唯一使用者 E001 把它留空,注释写明「死亡演出走物理状态机」。那个字段从一开始就是 +> 投机的,初稿把投机原样放大成了一个三模块的层。按 YAGNI 与第 6 条取消。 +> +> 受影响:`IDeathModule` 契约删除;三个死亡模块删除;`PerceptionSkeleton.Add` 去掉 +> `death` 参数、自行声明 `Death` 终态;模块总数 8 → 5。 + +### 3.1 三层 ``` PerceptionSkeleton 固定:感知升降级规则(唯一不可插拔的部分)