diff --git a/Docs_Dev/superpowers/specs/2026-07-27-shared-primitives-decision.md b/Docs_Dev/superpowers/specs/2026-07-27-shared-primitives-decision.md new file mode 100644 index 00000000..cee884ef --- /dev/null +++ b/Docs_Dev/superpowers/specs/2026-07-27-shared-primitives-decision.md @@ -0,0 +1,92 @@ +# 小怪 / Boss 共享底座:抽取决策记录 + +> 日期:2026-07-27  状态:已实施(Layer A),B/C 明确否决 +> 背景:小怪走 `EnemyAiBrain + PerceptionStateMachine + EnemyAbilityBase`,Boss 走 +> `BossBase + BossSkillExecutor + BossSkillSO`。两套决策/技能层并行演进,出现机制原语的平行实现。 + +## 1. 结论摘要 + +| 层 | 内容 | 决策 | 依据 | +|---|---|---|---| +| **A** | 加权随机取索引 | ✅ **已抽取** `BaseGames.Core.WeightedPick` | **4 份**平行实现,且已出现分歧 | +| **B** | 冷却记账 | ❌ **否决** | 全项目冷却是**异构**的,符合该形态的只有 1 个消费者 | +| **C** | HitBox 时序窗口 | ❌ **否决** | 仅 2 个**形态不同**的消费者,且两处均无测试覆盖 | + +核心原则:**只抽"两边同构且会漂移"的原语;不为"看起来像"的浅层重复付重构风险**。 + +## 2. Layer A:已实施 + +### 抽取前的 4 份平行实现 +1. `EnemyAttackSelector.SelectByWeight`(小怪选招) +2. `BossBase.UseBossSkillWeighted`(Boss 选招,带上一招 0.3× 防重复惩罚) +3. `BossSkillExecutor.SelectWeightedSkill`(**零调用者的死代码**,与 2 近似重复) +4. `LootResolver.Resolve`(战利品掉落;**权重被算了两遍**——累加一遍、抽取时再算一遍, + 两处公式若改动不同步即产生分布偏差) + +### 抽取结果 +```csharp +namespace BaseGames.Core +{ + /// 调用方把候选的"有效权重"填入列表(不合格候选填 0),本原语只负责按权重挑索引。 + public static class WeightedPick { public static int Index(IReadOnlyList weights); } +} +``` +- 语义:权重 ≤ 0 的项**永不被选中**(负权重按 0 处理);无正权重返回 `-1`;含浮点累加误差兜底。 +- 放 `BaseGames.Core`(非 Combat)——因为消费者含**战利品掉落**,并非战斗专属。 +- 零 GC:调用方复用权重缓冲区(`EnemyAttackSelector._weightBuf`、`BossBase._candidateWeights`)。 +- 覆盖:10 条 EditMode 单测(含"绝不选中零/负权重项"200 次采样断言、权重分布断言)。 +- 顺带清理:删除死代码 `SelectWeightedSkill`;消除 `LootResolver` 权重重复计算。 + +### 为何不做 `ISelectable` 接口 + `WeightedSelector`(初版草案) +深入代码后否决: +- `BossSkillSO` **没有** `priority` 字段(Boss 只用加权随机)→ 强加接口会产生无意义成员; +- `LootTableSO.Entries` 的权重是**动态计算**的(难度加成),无法用静态接口属性表达; +- 候选类型/合格判定两边完全不同,抽到接口层只会造出"上帝抽象"。 +→ 改为**更低层、更通用的 `Index(weights)`**,合格性与权重加成留在各自领域内。 + +## 3. Layer B(冷却):否决理由 + +全项目冷却实现是**异构**的,不存在可统一的单一形态: + +| 系统 | 形态 | +|---|---| +| `ParrySystem` / `DashState` / `ToolSlotManager` / `SkillManager` | 每帧递减的**倒计时器** | +| `BossSkillExecutor` | `Dictionary` **截止时刻** | +| `EnemyAbilityBase` | 单个 float **截止时刻**(每组件一个,判定仅 1 行) | + +符合"多 key 截止时刻字典"形态的**只有 `BossSkillExecutor` 一处**。为单一消费者建抽象=纯仪式; +强行统一则要重写玩家闪避/弹反/道具/技能 4 个无关系统,风险与收益完全不成比例。 + +## 4. Layer C(HitBox 时序窗口):否决理由 + +两个消费者**形态本质不同**: +- `MeleeAttackAbility.PlayAttackStep`:**归一化**(`hitBoxEnterT/ExitT` × duration)、**单** HitBox、逐帧 `Time.deltaTime` 推进、需跑满整段时长。 +- `BossSkillExecutor.ExecutePatternCoroutine`:**绝对秒数**(windup/active/recovery)、**多** HitBox 齐开齐关、`WaitForSeconds` 推进。 +- 玩家侧 `WeaponHitBoxInstance`(外部动画事件驱动开关)/ `SkillHitBoxInstance`(池化实例生命周期)是**另一种语义**,不是同一回事。 + +叠加关键风险:**两处都无自动化测试覆盖**。重构等于在无测试保护下改动战斗手感时序, +一个细微的时序回归难以发现。重复本身很浅(开/等/关约 5 行),不构成维护危害。 +→ 判定:**收益 < 风险,不做**。若将来出现第 3 个同形态消费者,可重新评估。 + +## 5. 后续路线:Boss 决策层走 BrainGraph(延后,按需触发) + +**可行性高**:`BossBase` 已暴露决策所需 API(`UseBossSkillWeighted` / `IsBossSkillExecuting` / +`IsHPBelow` / `CurrentPhase` / `BeginPhaseTransition` / `IsPhaseTransitioning`), +只需一个 `IBossControl` facet 挂进 `IAiContext`(同 `ICombatant` 模式,保证 lambda 不捕获具体实例), +`BossSkillExecutor` 的富执行层**原样保留**。 + +**当前不必要**。触发条件(任一出现再做): +- 出现第 2/3 个 Boss,或需要"精英怪 / mini-boss"中间形态(要混用小怪状态 + Boss 技能); +- 要清理 BD(Behavior Designer)迁移债——现存 `StopBehaviorTree`、`BD_*` 注释残留; +- 希望 Boss 也吃上小怪那套工具链(状态 Inspector、`TransitionRecord`、EditMode 测试)。 + +**执行建议**:不要拿已能跑的 ChaoFeng 开刀验证;**等下一个新 Boss 时用 BrainGraph 从零搭** +(决策走图 + `IBossControl`,执行仍用 `BossSkillExecutor`),验证顺畅后再回迁 ChaoFeng。 + +**终态**:决策层统一 BrainGraph + 机制原语统一底座 + 执行层按复杂度分层 +(小怪 `EnemyAbilityBase` / Boss `BossSkillExecutor`)——既消重复,又保留 Boss 编排自由。 + +## 6. 注记 + +- `UseBossSkillWeighted` 目前**无调用者**(原 BD 任务已移除),是等待决策层接入的 Boss API,故保留。 +- 验证:编译 0 错误 0 警告;EditMode **177/177 通过**(原 167 + 新增 10)。