Files
zeling_v2/Docs_Dev/superpowers/specs/2026-07-27-shared-primitives-decision.md
T

93 lines
5.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
# 小怪 / 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<float> weights); }
}
```
- 语义:权重 ≤ 0 的项**永不被选中**(负权重按 0 处理);无正权重返回 `-1`;含浮点累加误差兜底。
-`BaseGames.Core`(非 Combat)——因为消费者含**战利品掉落**,并非战斗专属。
- 零 GC:调用方复用权重缓冲区(`EnemyAttackSelector._weightBuf``BossBase._candidateWeights`)。
- 覆盖:10 条 EditMode 单测(含"绝不选中零/负权重项"200 次采样断言、权重分布断言)。
- 顺带清理:删除死代码 `SelectWeightedSkill`;消除 `LootResolver` 权重重复计算。
### 为何不做 `ISelectable` 接口 + `WeightedSelector<T>`(初版草案)
深入代码后否决:
- `BossSkillSO` **没有** `priority` 字段(Boss 只用加权随机)→ 强加接口会产生无意义成员;
- `LootTableSO.Entries` 的权重是**动态计算**的(难度加成),无法用静态接口属性表达;
- 候选类型/合格判定两边完全不同,抽到接口层只会造出"上帝抽象"。
→ 改为**更低层、更通用的 `Index(weights)`**,合格性与权重加成留在各自领域内。
## 3. Layer B(冷却):否决理由
全项目冷却实现是**异构**的,不存在可统一的单一形态:
| 系统 | 形态 |
|---|---|
| `ParrySystem` / `DashState` / `ToolSlotManager` / `SkillManager` | 每帧递减的**倒计时器** |
| `BossSkillExecutor` | `Dictionary<string,float>` **截止时刻** |
| `EnemyAbilityBase` | 单个 float **截止时刻**(每组件一个,判定仅 1 行) |
符合"多 key 截止时刻字典"形态的**只有 `BossSkillExecutor` 一处**。为单一消费者建抽象=纯仪式;
强行统一则要重写玩家闪避/弹反/道具/技能 4 个无关系统,风险与收益完全不成比例。
## 4. Layer CHitBox 时序窗口):否决理由
两个消费者**形态本质不同**
- `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 技能);
- 要清理 BDBehavior 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)。