FishDice 随机逻辑:从权重候选到分层池漏斗
这页解释当前仓库里两套随机决策:一套服务 Lucky Dice 候选选择,另一套是更通用的 Pool Funnel 分层随机池。它们共同的设计目标是:随机可注入、权重可解释、结果可回放、失败可观测。
一句话模型
随机并不是“从列表里随手挑一个”。框架会先筛候选、算权重、只在最高优先级集合里抽取,再把随机值、选中项、状态变化和后续决策写入结果对象或 Trace。
IRandomSource
随机源由外部注入,测试可以用固定序列复现结果。
Weight
候选通过权重参与抽取,权重为 0 的项不会进入抽奖池。
Trace
Pool Funnel 会记录每层权重、随机值、命中结果、状态前后差异。
流程图:Pool Funnel 随机决策
1. 输入请求
RandomPoolDefinition + PoolFunnelRequest 进入服务。
2. 解析入口层
检查
EntryLayerIds,过滤不可用层,按 priority 选入口。
3. 读取状态
从
IRandomPoolStateStore 取当前层 luck value 和 roll count。
4. 计算权重
默认公式:
BaseWeight + Factor * LuckValue,小于 0 归零。
5. 筛候选池
只保留权重大于 0 的结果,再取最高 priority 的结果组。
6. 权重抽取
Next(0, totalWeight) 得到随机游标,累加权重命中结果。
7. 执行动作
把选中结果声明的 actions 交给 dispatcher,默认只排队成功结果。
8. 沉淀状态
默认将
LuckValue += LuckValueDelta,并让 roll count +1。
9. 判断去向
结果配置决定 Stop 或 NextLayer;NextLayer 会进入下一层循环。
10. 输出结果
返回 outcome、每层详情和
PoolFunnelTrace,便于复盘。
这个流程的关键不是“随机算法多复杂”,而是把随机前后的上下文都保留下来:入口层为什么可用、候选为什么被过滤、某个结果为什么权重更高、抽中的随机游标落在哪个区间。
框架图:随机池组件关系
配置与输入
RandomPoolDefinition
定义 pool id、入口层列表、所有随机层。
RandomLayerDefinition
定义 layer id、优先级、可用性 key、结果集合。
RandomResultDefinition
定义 result key、priority、基础权重、幸运值因子、状态增量和下一步决策。
核心编排
PoolFunnelRandomService
负责入口层解析、循环 RollLayer、处理空池、处理 NextLayer、生成最终结果。
RollLayer
读取状态 → 算全部权重 → 取最高优先级候选 → 权重抽取 → action → settlement → decision。
PoolFunnelRunResult
输出 session、pool、每层结果、最终 outcome 和可回放 Trace。
可替换策略
IRandomSource
控制随机值来源,线上用真实随机,测试用脚本随机。
ILayerAvailabilityPolicy
判断层是否开放,默认 AlwaysAvailable。
IResultWeightCalculator
计算结果权重,默认线性幸运值公式。
IPoolSettlementStrategy
处理抽中后的状态变化,默认按 delta 修改幸运值。
IFunnelDecisionStrategy
决定停在当前结果,还是进入下一层。
核心公式
默认权重计算来自 LinearLuckValueWeightCalculator:
weight = max(0, BaseWeight + LuckValueWeightFactor * LuckValue)
抽取时先求最高优先级候选的总权重,然后生成 [0, totalWeight) 的随机数。遍历候选并累加权重,随机数落入哪个区间,就选中哪个结果。
priority 比 weight 更先发生作用:低优先级结果即使权重很高,也不会和最高优先级结果同池竞争。
Lucky Dice 候选随机
先过滤。只保留 enabled、满足 player level、满足 trigger source 的候选。
再按权重抽取。权重大于 0 的候选进入池子,使用同样的累加区间方式命中结果。
最后兜底。如果没有可抽候选,优先找 default candidate;仍没有时返回
NormalRewardFallback。补展示槽。选中候选后决定 result slot count,候选未配置时使用 Lucky Dice 骰子集默认槽数。
因此 Lucky Dice 的随机逻辑更轻:它没有多层状态沉淀,也不循环跳层;它主要服务“从当前可用特殊结果里选一个入口”。
为什么这样设计
| 问题 | 设计回答 | 收益 |
|---|---|---|
| 测试里怎么复现随机? | 随机源走 IRandomSource 注入。 | 固定随机序列即可断言选中项。 |
| 概率为什么变化? | 权重计算器显式依赖基础权重和幸运值。 | 策划参数、保底逻辑、状态变化都能解释。 |
| 结果池为空怎么办? | Pool Funnel 返回 NoWeightedResult;Lucky Dice 走 default 或普通奖励兜底。 | 失败路径不会静默吞掉。 |
| 以后要换概率算法怎么办? | 可替换 weight calculator、settlement、decision strategy。 | 核心编排稳定,策略可以独立演进。 |
代码证据
- PoolFunnelRandomService.cs:52-88:入口层解析、循环滚层和 NextLayer 处理。
- PoolFunnelRandomService.cs:117-169:权重计算、最高优先级候选、随机游标、状态沉淀和决策。
- PoolFunnelStrategies.cs:35-74:默认可用性、线性权重和 delta settlement。
- LuckyDiceFlowService.cs:16-49:Lucky Dice 候选过滤、权重选择、默认结果兜底。
- LuckyDiceRollFlowService.cs:65-90:只有 action 产生
LuckyDiceRequestedEffect时才进入 Lucky Dice 随机选择。