# FishDice 随机逻辑解释

← 返回总览 随机逻辑解释

# 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 随机选择。