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

5.7 KiB
Raw Blame History

小怪 / Boss 共享底座:抽取决策记录

日期:2026-07-27  状态:已实施(Layer A),B/C 明确否决 背景:小怪走 EnemyAiBrain + PerceptionStateMachine + EnemyAbilityBaseBoss 走 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(战利品掉落;权重被算了两遍——累加一遍、抽取时再算一遍, 两处公式若改动不同步即产生分布偏差)

抽取结果

namespace BaseGames.Core
{
    /// 调用方把候选的"有效权重"填入列表(不合格候选填 0),本原语只负责按权重挑索引。
    public static class WeightedPick { public static int Index(IReadOnlyList<float> weights); }
}
  • 语义:权重 ≤ 0 的项永不被选中(负权重按 0 处理);无正权重返回 -1;含浮点累加误差兜底。
  • BaseGames.Core(非 Combat)——因为消费者含战利品掉落,并非战斗专属。
  • 零 GC:调用方复用权重缓冲区(EnemyAttackSelector._weightBufBossBase._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 已暴露决策所需 APIUseBossSkillWeighted / IsBossSkillExecuting / IsHPBelow / CurrentPhase / BeginPhaseTransition / IsPhaseTransitioning), 只需一个 IBossControl facet 挂进 IAiContext(同 ICombatant 模式,保证 lambda 不捕获具体实例), BossSkillExecutor 的富执行层原样保留

当前不必要。触发条件(任一出现再做):

  • 出现第 2/3 个 Boss,或需要"精英怪 / mini-boss"中间形态(要混用小怪状态 + Boss 技能);
  • 要清理 BDBehavior Designer)迁移债——现存 StopBehaviorTreeBD_* 注释残留;
  • 希望 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)。