feat(lucky-dice): 提交 demo 前核心流程

This commit is contained in:
JSD\13999
2026-06-23 14:13:08 +08:00
commit 855659bf78
140 changed files with 9267 additions and 0 deletions

View File

@@ -0,0 +1,487 @@
# Lucky Dice 核心流程架构蓝图
## 文档信息
- 来源 PRD[lucky-dice-core-flow-prd.md](../PRD/lucky-dice-core-flow-prd.md)
- 关联需求:[lucky-dice-core-flow-requirements.md](../Requirements/lucky-dice-core-flow-requirements.md)
- 关联规格:[lucky-dice-core-flow-spec.md](../Specs/lucky-dice-core-flow-spec.md)
- 适用项目FishDice Unity 项目
- Unity 版本2022.3.62f2c1
- 生成日期2026-06-22
本文档定义 Lucky Dice 第一版核心闭环的架构蓝图。它不替代需求或接口规格,而是回答后续实现时最容易失控的几个问题:
- 普通 Roll、组合规则、Lucky Dice、目标玩法入口之间怎么分层。
- 哪些模块可以通过配置扩展,哪些模块才需要新增代码。
- 数据从玩家 Roll 到最终玩法模式如何流动。
- 追踪、兜底、测试和 Unity 落地目录应如何组织。
当前项目尚未落地生产 C# 玩法模块,因此本文档描述的是第一版建议架构,而不是对现有代码的复盘。
## 架构定位
Lucky Dice 不是普通骰子的二次 Roll也不是一个独立短玩法本体。它在架构中承担的是“特殊玩法入口选择器”职责
```text
普通 Roll 负责产出组合事实
组合规则负责决定业务行为
Lucky Dice 负责从特殊结果池中选择目标入口
目标玩法模块负责执行 Slap Down / Treasure Heist 等具体内容
```
第一版架构应优先保证三件事:
1. **可扩展**:新增组合、骰子数量、特殊结果、目标玩法模式时优先改配置。
2. **可测试**:组合事实、规则匹配、筛选、权重随机都能用确定性输入测试。
3. **可追踪**:一次 Roll 的所有关键决策能用同一个 `RollSessionId` 串起来。
## 架构原则
### 两套骰子集合隔离
普通骰子集合只表达主循环 Roll 的结果,例如 `2 / 3 / 4 / 5 / 6 / clover`。Lucky Dice 特殊结果集合只表达后续玩法入口,例如 `rocket / thief`
两者不能混用同一套裸字符串,也不能让普通组合匹配器直接处理 Lucky Dice 特殊结果。
### 规则匹配先于行为执行
普通 Roll 结果必须先转换成结构化 `ComboFacts`,再交给规则匹配器。业务行为不应读取“左骰 / 右骰”这类固定位置状态。
```text
DiceRollResult
→ ComboFacts
→ ComboRule
→ RollActionSpec
→ IRollActionExecutor
```
### 配置增长优先于代码增长
组合数量增加时,应增加 `ComboRule` 配置。只有出现新的通用匹配能力时才新增 matcher只有出现新的通用业务行为时才新增 executor。
禁止形成下面这种随组合数量膨胀的结构:
```text
clover_clover -> TriggerLuckyDice()
2_clover -> GrantCloverReward()
3_clover -> GrantCloverReward3()
...
```
### 流程服务不依赖表现层
核心流程可以产出展示所需数据,例如 Lucky Dice 标题、倍率、结果槽和目标玩法反馈,但不能依赖 UI 动画完成时序来做核心决策。
第一版表现层只消费核心结果,不参与筛选、随机和模式解析。
## 总览图
```mermaid
flowchart TD
Player["玩家触发 Roll"] --> RollService["DiceRollService"]
RollService --> FactBuilder["ComboFactBuilder"]
FactBuilder --> RuleMatcher["ComboRuleMatcher"]
RuleMatcher --> ActionDispatcher["RollActionDispatcher"]
ActionDispatcher --> Reward["RewardExecutor"]
ActionDispatcher --> Multiplier["MultiplierExecutor"]
ActionDispatcher --> LuckyTrigger["TriggerLuckyDiceExecutor"]
LuckyTrigger --> LuckyFlow["LuckyDiceFlowService"]
LuckyFlow --> CandidateFilters["CandidateFilters"]
CandidateFilters --> WeightedPicker["WeightedPicker"]
WeightedPicker --> SlotBuilder["ResultSlotBuilder"]
SlotBuilder --> ModeResolver["TargetModeResolver"]
ModeResolver --> Launcher["TargetModeLauncher"]
Launcher --> TargetGameplay["Slap Down / Treasure Heist"]
TargetGameplay --> MainLoop["回到主循环"]
RollService -.-> Trace["RollTrace"]
RuleMatcher -.-> Trace
ActionDispatcher -.-> Trace
LuckyFlow -.-> Trace
ModeResolver -.-> Trace
```
## 分层架构
| 层级 | 主要职责 | 允许依赖 | 禁止依赖 |
| --- | --- | --- | --- |
| 配置层 | 提供骰子集合、规则、候选结果、目标模式映射 | Unity 资源、本地 JSON、静态配置 | UI 动画状态、运行时随机结果 |
| Roll 层 | 根据 DiceSet 生成普通骰子结果 | 配置层、随机源 | 规则、奖励、玩法入口 |
| 组合事实层 | 把普通 Roll 结果转为统计事实 | Roll 层数据 | Lucky Dice 特殊结果、UI |
| 规则匹配层 | 按 matcher 和优先级命中规则 | 组合事实、规则配置 | 行为执行器、目标玩法 |
| 行为执行层 | 按 action type 分发通用行为 | 规则结果、上下文服务 | 组合 key 到独立方法映射 |
| Lucky Dice 层 | 候选池、筛选、权重随机、结果槽 | 候选配置、上下文、随机源 | 普通组合 matcher |
| 模式解析层 | `ResultKey -> TargetKey -> ModeKey` | Lucky Dice 结果、上下文、模式配置 | 短玩法内部实现 |
| 表现层 | 展示 Lucky Dice 标题、倍率、结果槽、跳转反馈 | 核心流程产物 | 核心规则决策 |
| 目标玩法层 | 执行 Slap Down / Treasure Heist 等玩法 | TargetModeEntry | Lucky Dice 随机细节 |
| 追踪层 | 汇总关键决策链路 | 所有核心层事件 | 改变玩法结果 |
依赖方向应从上游输入流向下游结果。追踪层可以被各层写入,但不应反向影响流程决策。
## 组件职责
### DiceSetConfigProvider
负责提供骰子集合配置包括普通骰子面、普通骰子数量、Lucky Dice 特殊结果面、默认结果槽数量。
第一版可以使用 ScriptableObject、JSON 或硬编码本地配置启动,但调用方必须只通过 provider 读取,不把 `2` 颗骰子或 `3` 个结果槽写死到流程逻辑中。
### DiceRollService
负责按骰子集合配置生成 Roll 结果。普通 Roll 会产出多个普通骰子面Lucky Dice 第一版不需要真的 Roll 三颗特殊骰,而是由候选选择结果再生成展示槽。
该服务只负责“随机出可见骰子面”,不负责奖励、倍率或玩法入口。
### ComboFactBuilder
负责把普通 Roll 结果转成 `ComboFacts`
- 原始面列表
- 面数量统计
- 数字列表
- 点数和
- Clover 数量
- 无序归一化 key
- 有序 key
它必须支持 N 颗骰子。`2 + clover``clover + 2` 的无序 key 必须一致。
### ComboRuleMatcher
负责把 `ComboFacts` 与规则配置匹配。matcher 按类型注册,例如 `ExactCombo``AllNumbers``ContainsFace``FaceCountRange`
第一版按优先级从高到低匹配,默认命中高优先级规则后停止继续匹配,除非规则配置声明可以继续匹配。
### RollActionDispatcher
负责把命中规则中的 `RollActionSpec` 分发给对应 executor。
dispatcher 只能维护 `ActionType -> Executor` 注册表,不能维护组合 key 到方法的映射。
### LuckyDiceFlowService
负责 Lucky Dice 的核心入口选择流程:
```text
构建候选池
→ 筛选候选池
→ 处理空池兜底
→ 权重随机
→ 生成展示结果槽
```
它不直接启动 Slap Down 或 Treasure Heist而是把选中结果交给模式解析层。
### TargetModeResolver
负责把 Lucky Dice 结果解析到具体目标玩法模式。
第一版映射:
| ResultKey | TargetKey | ModeKey |
| --- | --- | --- |
| `rocket` | `slap_down` | `slap_down_normal` |
| `thief` | `treasure_heist` | `treasure_heist_normal` |
后续教程模式、bonus 模式应通过 mode rule 扩展,而不是在 Lucky Dice 随机代码里写分支。
### TargetModeLauncher
负责统一启动目标玩法。它接收 `TargetModeEntry`,把 Lucky Dice 的结果、倍率、触发来源、`RollSessionId` 传给目标玩法入口。
目标玩法结束后,通过明确的 completion 回调或流程事件回到主循环。
### RollTrace
负责记录一次 Roll 的关键路径。追踪不是埋点管线本身,第一版可以是本地结构化日志或调试面板数据。
最小字段应覆盖:
- 原始普通骰子结果
- 组合事实和归一化 key
- 命中规则和执行行为
- Lucky Dice 初始候选、过滤过程、兜底类型
- 权重随机输入和选中结果
- 解析出的 TargetKey 和 ModeKey
- 最终结果
## 核心数据流
### 普通 Roll
```mermaid
sequenceDiagram
participant P as Player
participant R as DiceRollService
participant F as ComboFactBuilder
participant M as ComboRuleMatcher
participant A as RollActionDispatcher
participant T as RollTrace
P->>R: Roll(normal_main)
R->>T: record normal faces
R->>F: DiceRollResult
F->>T: record ComboFacts
F->>M: ComboFacts
M->>T: record matched rule
M->>A: RollActionSpec list
A->>T: record executed actions
```
### Lucky Dice
```mermaid
sequenceDiagram
participant A as TriggerLuckyDiceExecutor
participant L as LuckyDiceFlowService
participant F as CandidateFilters
participant W as WeightedPicker
participant S as ResultSlotBuilder
participant M as TargetModeResolver
participant T as RollTrace
A->>L: LuckyDiceContext
L->>T: record initial candidates
L->>F: apply filters
F->>T: record filter decisions
F->>W: filtered candidates
W->>T: record random value and selected result
W->>S: selected candidate
S->>T: record result slots
S->>M: selected candidate + context
M->>T: record target mode
```
## 配置架构
第一版建议至少拆成四类配置:
| 配置 | 示例内容 | 扩展方向 |
| --- | --- | --- |
| DiceSetConfig | `normal_main``lucky_dice`、骰子面、骰子数量、默认槽数 | 支持关卡自定义骰子数量 |
| ComboRuleConfig | 规则 id、优先级、matcher、actions | 策划新增组合语义 |
| LuckyDiceCandidateConfig | result、target、weight、enabled、slot count、default | 扩展特殊结果与概率 |
| TargetModeConfig | target、default mode、tutorial mode、bonus mode | 扩展玩法模式 |
配置读取顺序建议:
```text
本地默认配置
→ 关卡或活动覆盖配置
→ 运行时上下文修正
```
远端热更不在第一版范围内,但数据结构应避免与 Unity 场景对象强绑定,给后续迁移到远端配置留出空间。
## Unity 落地目录建议
如果第一版开始实现 C# 模块,建议按运行时核心、配置、表现、测试分开:
```text
FishDice/Assets/FishDice/
Runtime/
LuckyDice/
Core/
DiceRollService.cs
ComboFactBuilder.cs
ComboRuleMatcher.cs
RollActionDispatcher.cs
LuckyDiceFlowService.cs
TargetModeResolver.cs
Config/
DiceSetConfig.cs
ComboRuleConfig.cs
LuckyDiceCandidateConfig.cs
TargetModeConfig.cs
Actions/
GrantRewardExecutor.cs
AddMultiplierExecutor.cs
TriggerLuckyDiceExecutor.cs
EnterModeExecutor.cs
Filters/
EnabledFilter.cs
ProgressFilter.cs
SourceFilter.cs
CooldownFilter.cs
TutorialFilter.cs
Presentation/
LuckyDicePresenter.cs
LuckyDiceResultSlotView.cs
Trace/
RollTrace.cs
RollTraceLogger.cs
Tests/
EditMode/
LuckyDice/
```
第一版核心流程建议尽量放在纯 C# 类里,减少对 `MonoBehaviour` 的依赖。表现层再通过 Unity 组件消费核心流程产物。
## 错误处理与兜底
### 普通组合未命中
处理策略:
1. 记录 `RollSessionId`、原始骰子面、`NormalizedKey`
2. 走普通奖励兜底,或在调试模式返回明确错误。
3. 不进入 Lucky Dice。
### Lucky Dice 候选池为空
处理策略:
1. 尝试使用配置的 default result。
2. default result 不可用时,降级为普通奖励兜底。
3. 记录每个 filter 过滤前后的候选数量和空池原因。
### 模式解析失败
处理策略:
1. 记录 `ResultKey``TargetKey` 和上下文。
2. 尝试使用目标玩法默认模式。
3. 仍失败时降级为普通奖励兜底,并标记为配置错误。
兜底必须可观测,不能静默吞掉。否则后续配置错误会表现成“玩家只是没进 Lucky Dice”很难定位。
## 测试架构
测试应围绕外部行为和完整链路,而不是私有实现细节。
### 单元测试
| 测试对象 | 重点 |
| --- | --- |
| ComboFactBuilder | N 骰统计、无序 key、有序 key、Clover 数量 |
| ComboRuleMatcher | 优先级、matcher 类型、停止继续匹配 |
| RollActionDispatcher | 按 action type 分发,不按 combo key 分发 |
| CandidateFilters | enabled、progress、source、cooldown、tutorial 过滤 |
| WeightedPicker | 固定随机源下结果可预测 |
| TargetModeResolver | rocket/thief 映射和默认模式 |
### 链路测试
链路测试是第一版最重要的测试口:
```text
给定 Roll 请求
给定 DiceSet 配置
给定 ComboRule 配置
给定 Lucky Dice 候选池
给定固定随机源
断言最终 outcome
```
最小链路用例:
- `2 + 3` 发普通奖励。
- `2 + clover` 发普通奖励并提供 Clover bonus。
- `clover + clover` 触发 Lucky Dice。
- Lucky Dice 选中 `rocket` 后进入 `slap_down_normal`
- Lucky Dice 选中 `thief` 后进入 `treasure_heist_normal`
- 候选池为空时进入可观测兜底。
- 普通骰子数量改成 3 后,组合事实和 matcher 仍可工作。
## 架构决策记录
完整 ADR 存放在 [Docs/Decisions](../Decisions/README.md)。本节只保留摘要,后续决策变更应优先新增或更新独立 ADR 文档。
### ADR-001普通骰子和 Lucky Dice 特殊结果分成两套集合
[ADR-001](../Decisions/adr-001-separate-normal-and-lucky-dice-sets.md)
上下文普通骰子负责奖励组合Lucky Dice 负责玩法入口。两者语义不同。
决策:使用 `DiceSetType.Normal``DiceSetType.Lucky` 区分集合,不让普通组合匹配器处理 Lucky Dice 特殊结果。
结果:避免数字结算和玩法入口混用。代价是配置和调试界面需要明确展示集合类型。
### ADR-002Roll 结果先转组合事实,再匹配规则
[ADR-002](../Decisions/adr-002-build-combo-facts-before-rule-matching.md)
上下文:第一版默认 2 颗骰子,但未来需要支持 3 颗、4 颗或关卡自定义骰子数量。
决策:所有普通 Roll 都先生成 `ComboFacts`matcher 只依赖事实统计,不依赖固定位置。
结果:后续扩骰数量时风险更低。代价是第一版需要多写一层事实构建和测试。
### ADR-003行为按 action type 注册
[ADR-003](../Decisions/adr-003-dispatch-actions-by-action-type.md)
上下文:如果每个组合 key 绑定一个独立方法,组合增长会让执行层失控。
决策:规则只声明 action 列表dispatcher 只按 `ActionType` 找 executor。
结果:新增组合多数情况下只改配置。代价是 action 参数需要有清晰校验。
### ADR-004Lucky Dice 随机源可注入
[ADR-004](../Decisions/adr-004-inject-random-source-for-lucky-dice.md)
上下文:权重随机必须可测试、可回放。
决策Lucky Dice 权重随机通过 `IRandomSource` 或等价接口注入随机源。
结果:测试可预测,线上问题可复盘。代价是运行时需要统一管理随机源生命周期。
## 新功能开发蓝图
### 新增普通组合
1. 确认现有 matcher 能否表达该组合。
2. 能表达时新增 `ComboRuleConfig`
3. 需要新业务效果时,优先复用现有 action。
4. 只有确实出现新的通用行为时,新增 executor。
5. 补一条规则匹配测试和一条链路测试。
### 新增 Lucky Dice 特殊结果
1. 在 Lucky Dice 特殊结果集合中新增 `ResultKey`
2. 在候选配置中声明 `TargetKey`、权重、启用条件、槽数、default 标记。
3. 在目标模式配置中声明默认 `ModeKey`
4. 补候选筛选测试、权重随机测试和目标模式解析测试。
### 新增目标玩法模式
1.`TargetModeConfig` 中增加模式映射。
2. 如模式选择依赖上下文,新增 mode rule。
3. 不修改 Lucky Dice 随机逻辑。
4.`TargetModeResolver` 测试。
### 接入表现动画
1. 核心流程先产出 `LuckyDiceResultSlots``TargetModeEntry`
2. 表现层播放标题、倍率、结果槽和跳转反馈。
3. 动画完成后再调用 launcher或先启动目标玩法再做过场取决于产品节奏。
4. 动画失败不能改变核心选择结果。
## 架构治理
实现和评审时重点检查以下规则:
- 核心流程中没有固定读取第 0/1 颗骰子的业务判断。
- 普通骰子和 Lucky Dice 特殊结果没有混用同一套集合。
- 新增组合时没有新增组合 key 到独立方法的 map。
- Lucky Dice 候选必须先筛选再随机。
- 候选池为空、模式解析失败、未命中规则都有可观测兜底。
- `RollSessionId` 贯穿普通 Roll、规则匹配、Lucky Dice、模式解析、目标玩法启动。
- 表现层不参与规则匹配、候选筛选和权重随机。
## 第一版落地顺序
建议按以下顺序实现:
1. 定义核心数据结构和本地默认配置。
2. 实现普通 Roll、组合事实和规则匹配。
3. 实现 action dispatcher 和普通奖励 / Clover bonus / trigger Lucky Dice 执行器。
4. 实现 Lucky Dice 候选筛选、权重随机、结果槽生成。
5. 实现 `rocket -> slap_down_normal``thief -> treasure_heist_normal` 模式解析。
6. 实现最小表现层:标题、倍率、结果槽、目标玩法跳转反馈。
7. 补齐链路测试和追踪字段。
这个顺序能先把核心闭环跑通,再逐步加表现和更多结果类型,避免一开始被动画和短玩法完整内容拖散。

View File

@@ -0,0 +1,20 @@
# Lucky Dice 架构决策记录
本目录记录 Lucky Dice 核心流程第一版中的重要架构决策。ADR 用来保存当时的背景、约束、备选方案和取舍结果,避免后续实现或扩展时重新讨论已经明确的架构边界。
## ADR 列表
| 编号 | 决策 | 状态 |
| --- | --- | --- |
| [ADR-001](adr-001-separate-normal-and-lucky-dice-sets.md) | 普通骰子和 Lucky Dice 特殊结果分成两套集合 | Accepted |
| [ADR-002](adr-002-build-combo-facts-before-rule-matching.md) | Roll 结果先转组合事实,再匹配规则 | Accepted |
| [ADR-003](adr-003-dispatch-actions-by-action-type.md) | 行为按 action type 注册和分发 | Accepted |
| [ADR-004](adr-004-inject-random-source-for-lucky-dice.md) | Lucky Dice 权重随机使用可注入随机源 | Accepted |
## 状态说明
- `Accepted`:当前采用的决策。
- `Superseded by ADR-XXX`:已经被后续 ADR 替代。
- `Deprecated`:不再推荐,但未被某个明确新 ADR 替代。
ADR 不应删除。决策变化时新增一份 ADR并在旧 ADR 的状态里标记替代关系。

View File

@@ -0,0 +1,116 @@
# ADR-001: 普通骰子和 Lucky Dice 特殊结果分成两套集合
## Status
Accepted
## Date
2026-06-22
## Context
FishDice 第一版需要同时支持普通骰子主循环和 Lucky Dice 特殊分支。普通 Roll 的骰子面是 `2 / 3 / 4 / 5 / 6 / clover`它们的职责是参与组合事实、规则匹配、普通奖励、Clover bonus 和触发 Lucky Dice。
Lucky Dice 进入后使用的是特殊结果,例如 `rocket``thief`。这些结果的职责不是点数结算,而是决定后续进入哪个短玩法,以及进入该玩法的哪个模式。
如果两类结果混用同一套裸字符串或同一个骰子集合,会带来几个风险:
- 普通组合匹配器可能错误处理 Lucky Dice 特殊结果。
- 策划配置容易把数字奖励结果和玩法入口结果混在一起。
- 日志中很难判断某个 key 是普通骰子面还是 Lucky Dice 入口。
- 后续扩展更多特殊结果时,会污染普通 Roll 的规则空间。
## Decision
普通骰子和 Lucky Dice 特殊结果使用两套独立集合,并在数据模型中保留集合类型。
建议在代码中使用等价于以下概念的结构:
```csharp
public enum DiceSetType
{
Normal,
Lucky
}
public readonly struct DiceFace
{
public DiceSetType SetType { get; }
public string Key { get; }
public int? NumberValue { get; }
}
```
普通骰子集合只包含主循环 Roll 面。Lucky Dice 集合只包含特殊入口结果。普通组合事实构建器只接受 `DiceSetType.Normal` 的 Roll 结果,遇到 Lucky Dice 特殊结果时必须拒绝或返回结构化错误。
## Alternatives Considered
### 使用同一个 DiceFace 字符串集合
优点:
- 第一版实现最省事。
- 配置表字段少。
缺点:
- `clover``rocket``thief`、数字面都会落在同一个匹配空间。
- 普通 matcher 需要不断写排除逻辑,防止特殊结果进入普通奖励结算。
- 日志和调试工具无法从类型上识别语义边界。
结论:拒绝。它会把第一版的简单实现成本转化成后续规则扩展和排查成本。
### 使用同一个集合,但通过 key 前缀区分
示例:`normal_2``normal_clover``lucky_rocket`
优点:
- 比裸字符串更可读。
- 不需要额外枚举字段。
缺点:
- 类型边界依赖命名约定,配置错误时很难在编译或校验阶段发现。
- matcher 仍然会看到同一批 key需要人工遵守前缀规则。
- 后续如果出现多套普通骰子或多套特殊结果,前缀会越来越复杂。
结论:拒绝。前缀可以作为日志展示或配置命名辅助,但不能替代结构化集合类型。
### 使用两套独立数据模型
普通骰子面和 Lucky Dice 特殊结果完全使用不同 class。
优点:
- 类型隔离最强。
- 不容易误用。
缺点:
- Roll、展示槽、日志等通用结构需要重复建模。
- 第一版通用流程会变得偏重。
结论:暂不采用。当前使用共同的 `DiceFace` 概念加 `DiceSetType` 已能满足隔离需求。
## Consequences
正向影响:
- 普通 Roll 和 Lucky Dice 的语义边界清晰。
- 普通组合 matcher 不需要理解特殊玩法入口。
- 日志可以明确记录本次处理的是 `Normal` 还是 `Lucky` 集合。
- 后续新增特殊结果不会污染普通奖励规则。
代价和约束:
- 配置工具和调试界面必须展示集合类型。
- 配置加载需要校验普通集合不能包含 Lucky Dice 特殊结果。
- 代码中不能到处裸写 key需要集中定义 key 或配置入口。
## Related Documents
- [Lucky Dice 核心流程 PRD](../PRD/lucky-dice-core-flow-prd.md)
- [Lucky Dice 核心流程规格](../Specs/lucky-dice-core-flow-spec.md)
- [Lucky Dice 核心流程架构蓝图](../Architecture/lucky-dice-core-flow-architecture-blueprint.md)

View File

@@ -0,0 +1,117 @@
# ADR-002: Roll 结果先转组合事实,再匹配规则
## Status
Accepted
## Date
2026-06-22
## Context
Lucky Dice 第一版普通 Roll 默认使用 2 颗骰子,但 PRD 明确要求骰子数量必须来自配置,并保留后续扩展到 3 颗、4 颗或特殊关卡自定义骰子数量的能力。
普通 Roll 的规则也不只包含精确组合。首批规则需要覆盖:
- 双 Clover 触发 Lucky Dice。
- 单 Clover 加数字发放普通奖励并提供 bonus。
- 纯数字结果发放普通奖励。
后续还需要支持对子、N 连、指定面数量、点数和区间、骰子数量约束等统计型规则。
如果规则匹配直接读取固定位置,例如左骰和右骰,就会把第一版默认值 `2` 固化到系统里。后续扩展 3 骰时,规则、测试和日志都要大范围修改。
## Decision
普通 Roll 结果必须先转换成结构化 `ComboFacts`,再进入规则匹配。
`ComboFacts` 至少包含:
- `DiceSetType`
- `DiceCount`
- 原始 `Faces`
- `FaceCounts`
- `NumberValues`
- `NumberSum`
- `CloverCount`
- `HasClover`
- `NormalizedKey`
- `OrderedKey`
普通规则默认使用无序事实。`2 + clover``clover + 2` 必须归一为同一个 `NormalizedKey`。只有规则明确声明需要顺序语义时,才允许读取 `OrderedKey`
matcher 必须基于 `ComboFacts` 中的统计信息判断,不能依赖固定的左骰、右骰字段。
## Alternatives Considered
### 在规则里直接读取骰子数组位置
示例:`faces[0] == clover && faces[1] == clover`
优点:
- 第一版双骰场景写起来最快。
- 不需要单独的事实构建层。
缺点:
- 默认写死 2 骰。
- 顺序和无序语义容易混乱。
- 后续 3 骰、4 骰会导致规则分支爆炸。
- 测试会绑定实现细节,而不是外部行为。
结论:拒绝。它与可配置骰子数量的核心要求冲突。
### 只生成字符串组合 key
示例:直接生成 `2_clover``clover_clover`,规则只匹配 key。
优点:
- 对精确组合匹配足够简单。
- 配置和日志易读。
缺点:
- 难以表达统计型规则,例如至少 2 个 Clover、三连、点数和区间。
- matcher 会被迫解析字符串,或者需要维护大量派生 key。
- 原始骰子和统计信息缺失,不利于调试。
结论:拒绝作为唯一事实来源。`NormalizedKey` 可以作为 `ComboFacts` 的一个字段,但不能替代完整事实。
### 由每个 matcher 自己计算统计信息
优点:
- 不需要统一事实模型。
- matcher 内部可以自由处理自己的需求。
缺点:
- 多个 matcher 重复遍历和统计。
- 不同 matcher 对 key、顺序、数字值的理解可能不一致。
- 日志难以记录统一的匹配输入。
结论:拒绝。统一事实模型更利于一致性和测试。
## Consequences
正向影响:
- 后续扩展骰子数量时,核心规则仍基于统计事实工作。
- 规则匹配器可以用少量通用 matcher 覆盖多种组合语义。
- 日志能记录原始结果、统计结果、无序 key 和有序 key。
- 测试可以直接验证外部行为和事实结构。
代价和约束:
- 第一版需要实现并测试 `ComboFactBuilder`
- `ComboFacts` 字段需要保持稳定,不能随意为某个规则塞临时字段。
- Lucky Dice 特殊结果不能进入普通 `ComboFactBuilder`
## Related Documents
- [Lucky Dice 核心流程需求](../Requirements/lucky-dice-core-flow-requirements.md)
- [Lucky Dice 核心流程规格](../Specs/lucky-dice-core-flow-spec.md)
- [ADR-001](adr-001-separate-normal-and-lucky-dice-sets.md)

View File

@@ -0,0 +1,116 @@
# ADR-003: 行为按 action type 注册和分发
## Status
Accepted
## Date
2026-06-22
## Context
PRD 要求普通骰子的不同组合能触发不同业务行为,并且后续可以新增组合、骰子数量、特殊结果或目标玩法。第一版最小组合包括:
- 数字 + 数字:普通奖励。
- 数字 + Clover普通奖励加 Clover bonus。
- Clover + Clover触发 Lucky Dice。
这些组合行为本质上由少量通用动作组合而成,例如发奖、增加倍率、触发 Lucky Dice、进入玩法模式、显示弹窗。
如果为每个组合 key 绑定一个独立方法,第一版可以很快写完,但后续新增组合时执行层会不断膨胀,形成难以维护的分支表。
## Decision
规则配置只声明要执行的 action 列表。执行层通过 `ActionType` 注册和分发通用 executor。
示例:
```text
grant_reward -> GrantRewardExecutor
add_multiplier -> AddMultiplierExecutor
trigger_lucky_dice -> TriggerLuckyDiceExecutor
enter_mode -> EnterModeExecutor
show_popup -> ShowPopupExecutor
```
`RollActionDispatcher` 只能维护 `ActionType -> IRollActionExecutor` 注册表,不能维护 `combo key -> method` 注册表。
组合规则增长时,优先新增或修改规则配置。只有出现新的通用行为类型时,才新增 executor。
## Alternatives Considered
### 每个组合 key 绑定独立方法
示例:
```text
clover_clover -> TriggerLuckyDice()
number_clover -> GrantCloverBonus()
number_number -> GrantNormalReward()
```
优点:
- 第一版逻辑直观。
- 单个组合方法容易断点调试。
缺点:
- 组合数量增长会直接推动方法数量增长。
- 相同行为会在多个方法里重复。
- 策划新增组合含义时,工程通常必须新增方法。
- 很难支持一个组合同时执行多个通用行为。
结论:拒绝。它正是 PRD 中要求避免的硬编码分支。
### 规则 matcher 直接执行业务行为
优点:
- 少一层 dispatcher。
- matcher 命中后可以立刻执行。
缺点:
- matcher 同时承担判断和副作用,测试困难。
- 同一个 matcher 无法复用于不同业务动作。
- 规则优先级、继续匹配、行为顺序会混在一起。
结论:拒绝。匹配和执行必须分离。
### 用事件系统广播所有命中结果
优点:
- 行为扩展灵活。
- 多个系统可以监听同一个结果。
缺点:
- 第一版流程需要确定性和可追踪,广播式事件容易让执行顺序不透明。
- 兜底、失败和链路测试会更复杂。
- 当前没有足够多的跨系统监听需求。
结论:暂不采用。后续如接入更多外围系统,可以在 executor 内部再发布领域事件,但核心流程仍由 action list 驱动。
## Consequences
正向影响:
- 新增组合多数情况下只改规则配置。
- 通用行为可以复用和组合。
- 链路测试可以断言 action list 和最终 outcome。
- 未注册 action 可以返回结构化错误并写入追踪。
代价和约束:
- action 参数必须有校验,避免配置错误运行到一半才失败。
- executor 的职责边界需要清楚,不能把组合判断重新塞回 executor。
- dispatcher 必须保留执行顺序和失败策略。
## Related Documents
- [Lucky Dice 核心流程 PRD](../PRD/lucky-dice-core-flow-prd.md)
- [Lucky Dice 核心流程规格](../Specs/lucky-dice-core-flow-spec.md)
- [ADR-002](adr-002-build-combo-facts-before-rule-matching.md)

View File

@@ -0,0 +1,113 @@
# ADR-004: Lucky Dice 权重随机使用可注入随机源
## Status
Accepted
## Date
2026-06-22
## Context
Lucky Dice 需要在候选结果池筛选后按权重随机选择一个结果。第一版至少需要支持:
- `rocket` 进入 `slap_down_normal`
- `thief` 进入 `treasure_heist_normal`
- 候选结果可按启用状态、玩家进度、触发来源、冷却状态和新手期规则筛选。
- 候选池为空时有可观测兜底。
测试策略要求固定随机源下的权重随机结果可预测。PRD 还要求每次 Roll 都能通过同一个 `RollSessionId` 串起普通 Roll、规则匹配、Lucky Dice 结果和目标玩法启动,方便排查和回放。
如果 Lucky Dice 流程直接调用 Unity 或系统随机,会让链路测试不稳定,也难以复盘线上问题。
## Decision
Lucky Dice 权重随机必须通过可注入随机源执行。
建议使用等价于以下概念的接口:
```csharp
public interface IRandomSource
{
int Range(int minInclusive, int maxExclusive);
float Value01();
}
```
`WeightedPicker` 接收筛选后的候选列表、上下文和随机源。它需要记录:
- 候选列表快照。
- 每个候选的有效权重。
- 随机值。
- 最终选中的 `ResultKey`
测试环境可以注入固定序列随机源。运行时环境可以注入 Unity 随机适配器或项目统一随机服务。
## Alternatives Considered
### 直接使用 UnityEngine.Random
优点:
- Unity 项目中最容易使用。
- 不需要额外接口。
缺点:
- 测试难以稳定复现。
- 多个系统共享全局随机状态,结果容易受调用顺序影响。
- 回放和问题定位难度高。
结论:拒绝在核心逻辑中直接使用。可以通过适配器在运行时接入 Unity 随机。
### 直接使用 System.Random
优点:
- 可指定 seed。
- 不依赖 Unity API适合 EditMode 测试。
缺点:
- 如果在核心服务内部自行 new仍然无法由测试控制。
- seed 生命周期和 RollSessionId 的关系不透明。
- 后续如果项目已有统一随机服务,需要再改接口。
结论:不直接在核心流程中创建。可以作为 `IRandomSource` 的一个实现。
### 配置固定结果,不做随机
优点:
- 第一版实现和测试最简单。
- 结果完全可控。
缺点:
- 不满足 Lucky Dice 候选结果支持权重的需求。
- 后续接入概率策略时需要重构核心流程。
- 无法提前验证候选筛选和权重随机之间的边界。
结论:拒绝。第一版可以配置极简权重,但流程必须保留权重随机。
## Consequences
正向影响:
- 链路测试可以稳定断言 Lucky Dice 最终结果。
- 回放和问题排查可以复用相同随机输入。
- `WeightedPicker` 不依赖 Unity API更容易做纯 C# 测试。
- 后续接入活动概率策略时,随机入口清晰。
代价和约束:
- 需要实现至少两个随机源:运行时随机源和测试固定随机源。
- 需要记录随机值和候选权重,否则可注入随机源的排查价值会下降。
- 随机源生命周期需要统一管理,避免同一 Roll 中不同阶段使用不同来源。
## Related Documents
- [Lucky Dice 核心流程 PRD](../PRD/lucky-dice-core-flow-prd.md)
- [Lucky Dice 核心流程规格](../Specs/lucky-dice-core-flow-spec.md)
- [ADR-003](adr-003-dispatch-actions-by-action-type.md)

View File

@@ -0,0 +1,144 @@
# Lucky Dice 核心流程 PRD
## 问题陈述
FishDice 需要一套可扩展的骰子主循环和 Lucky Dice 特殊分支。当前目标不是只做一次普通骰子结算,而是让普通骰子的不同组合能够触发不同业务行为,并在双四叶草等稀有组合出现时进入 Lucky Dice。Lucky Dice 需要使用独立的特殊结果池,经过筛选和随机后产出目标结果,再把玩家带入 Slap Down、Treasure Heist 等短玩法。
如果把每个组合直接绑定到一个独立执行方法后续新增组合、骰子数量、特殊结果或目标玩法时会形成越来越大的硬编码分支。产品和策划侧也需要能清楚表达“什么组合触发什么行为”“Lucky Dice 怎么筛选结果”“同一个目标玩法进入什么模式”,否则第一版完成后很难继续扩展活动玩法。
## 解决方案
建立一套分层的玩法规则系统。
普通 Roll 使用普通骰子集合。第一版默认骰子面为 `2 / 3 / 4 / 5 / 6 / Clover`,默认投 2 颗骰子但骰子数量必须来自配置。Roll 结果先转换成结构化组合事实,再由规则匹配器按优先级匹配规则,并执行规则配置的行为列表。
Lucky Dice 使用独立的特殊结果集合,不与普通数字骰混用。它不是“普通骰子再摇一次”,而是一个特殊玩法入口选择流程:构建候选结果池,按玩家上下文筛选候选,按权重随机,生成结果槽展示,再解析目标玩法和具体进入模式。
第一版只保留核心闭环,忽略复杂动画表现。玩家只需要看到 Lucky Dice 标题、倍率、结果槽图标和目标玩法跳转。托盘、骰子飞入、粒子爆发、白烟转场等表现不进入第一版必做范围。
## 用户故事
1. 作为玩家,我希望普通掷骰能产出清晰结果,从而知道每次 Roll 都有意义。
2. 作为玩家,我希望纯数字结果能发放普通奖励,从而保证普通流程有稳定收益。
3. 作为玩家,我希望单个 Clover 也有轻微特殊反馈,从而让 Clover 即使不触发 Lucky Dice 也有价值。
4. 作为玩家,我希望双 Clover 能触发 Lucky Dice从而让稀有组合带来惊喜。
5. 作为玩家,我希望 Lucky Dice 是一个独立流程,从而明确知道触发了特殊事件。
6. 作为玩家,我希望 Lucky Dice 结果能清楚显示特殊图标,从而知道即将进入哪个短玩法。
7. 作为玩家,我希望 Rocket 结果进入 Slap Down从而让结果和后续玩法有明确绑定。
8. 作为玩家,我希望 Thief 结果进入 Treasure Heist从而让不同 Lucky Dice 结果有不同意义。
9. 作为玩家,我希望目标玩法结束后能回到主循环,从而保持游戏流程连续。
10. 作为策划,我希望普通骰子数量可配置,从而未来可以支持 3 颗、4 颗或特殊关卡自定义骰子数量。
11. 作为策划,我希望 Lucky Dice 结果槽数量可配置,从而未来可以支持 2 格、3 格、4 格或按玩法自定义槽位。
12. 作为策划,我希望组合规则数据化,从而新增组合含义时不需要每次都让工程新增独立方法。
13. 作为策划我希望组合匹配支持精确组合、面数量、对子、N 连、点数和区间等能力,从而覆盖简单和复杂规则。
14. 作为策划,我希望组合规则支持优先级,从而让双 Clover 这类稀有触发优先于普通奖励规则。
15. 作为策划,我希望组合行为由可复用行为组成,从而一个组合可以同时发奖、加倍率、展示反馈或进入 Lucky Dice。
16. 作为策划,我希望 Lucky Dice 候选结果可筛选,从而未解锁、未开启或冷却中的目标玩法不会被抽中。
17. 作为策划,我希望 Lucky Dice 候选结果支持权重,从而可以调节不同结果概率。
18. 作为策划,我希望新手期规则可以影响 Lucky Dice 结果,从而更好地控制早期体验。
19. 作为策划,我希望一个 Lucky Dice 结果先解析目标玩法再解析具体模式从而同一玩法可以支持教程、普通、bonus 等模式。
20. 作为策划,我希望 Lucky Dice 候选池为空时有兜底行为,从而配置错误不会卡死玩家流程。
21. 作为开发者,我希望普通骰子和 Lucky Dice 特殊结果分属两套骰子集合,从而避免数字结算和目标玩法入口混用。
22. 作为开发者,我希望 Roll 结果先转成组合事实,从而规则可以基于数据匹配,而不是依赖固定位置判断。
23. 作为开发者,我希望组合事实保留原始骰子、数量统计和归一化 key从而便于调试和未来扩展。
24. 作为开发者,我希望规则匹配只使用少量通用匹配器,从而系统优先通过配置扩展,而不是不断增加代码分支。
25. 作为开发者,我希望行为分发按行为类型注册,从而不需要维护巨大的“组合 key 到方法”映射表。
26. 作为开发者,我希望 Lucky Dice 随机源可注入,从而测试和回放可以稳定复现。
27. 作为开发者,我希望每次 Roll 都携带同一个会话 id从而普通 Roll、规则匹配、Lucky Dice 结果和目标玩法启动可以串起来排查。
28. 作为测试人员,我希望组合事实和规则匹配有确定性测试,从而普通组合和稀有组合都稳定可靠。
29. 作为测试人员,我希望兜底场景被覆盖,从而候选池为空、模式映射失败等问题可以被观测到。
30. 作为产品负责人,我希望第一版范围足够收敛,从而先验证玩法闭环,再投入完整动画表现。
## 实现决策
- 使用两套骰子集合:普通 Roll 骰子和 Lucky Dice 特殊结果骰子。
- 普通 Roll 第一版使用 `2 / 3 / 4 / 5 / 6 / Clover`
- 普通 Roll 第一版默认 2 颗骰子,但数量必须来自骰子集合配置。
- Lucky Dice 第一版默认 3 个结果槽,但数量必须来自候选结果、目标玩法或骰子集合配置。
- 普通 Roll 结果必须先转换成组合事实,再进入规则匹配。
- 组合事实必须包含骰子数量、原始骰子面、面数量统计、数字列表、点数和、Clover 数量、无序归一化 key、有序 key。
- 普通组合默认按无序组合匹配。
- 只有规则明确声明需要顺序语义时,才使用有序 key。
- 不允许每个组合 key 绑定一个独立方法。
- 使用少量可复用匹配器注册表。
- 首批匹配器包括精确组合、全数字、包含指定面、指定面数量、指定面数量区间、骰子数量、数字对子、N 连、点数和区间。
- 规则必须支持优先级。
- 第一版默认命中高优先级规则后停止继续匹配,除非规则配置另有声明。
- 使用少量可复用行为执行器注册表。
- 首批行为包括发放奖励、增加倍率、触发 Lucky Dice、进入玩法模式、显示弹窗。
- 第一版双 Clover 触发 Lucky Dice。
- 第一版单 Clover 加数字发放普通奖励并提供 Clover bonus。
- 第一版纯数字结果发放普通奖励。
- Lucky Dice 必须先构建候选池,再做随机。
- Lucky Dice 必须先筛选候选池,再做随机。
- 首批筛选器包括是否开启、玩家进度、触发来源、冷却状态、新手期控制。
- Lucky Dice 筛选后按权重随机。
- Lucky Dice 候选池为空时必须有可观测兜底。
- 第一优先兜底是配置的默认结果。
- 如果默认结果也不可用,则降级为普通奖励兜底。
- Lucky Dice 随机结果按 `ResultKey → TargetKey → ModeKey` 解析。
- Rocket 第一版解析到 Slap Down 普通模式。
- Thief 第一版解析到 Treasure Heist 普通模式。
- 教程模式和 bonus 模式作为扩展设计保留,第一版只要求普通模式可用。
- 第一版表现层只需要 Lucky Dice 标题、倍率显示、可配置结果槽和目标玩法跳转反馈。
- 每次 Roll 必须产出追踪信息包括原始结果、命中规则、执行行为、Lucky Dice 候选决策、选中结果和最终模式。
## 测试决策
最高层测试口应放在完整 Roll 解析流程:给定 Roll 请求、骰子集合配置、组合规则、Lucky Dice 候选池和受控随机源系统应产出最终结果例如普通奖励、Lucky Dice 进入 Slap Down、Lucky Dice 进入 Treasure Heist。
低层测试仍然需要,但它们应服务于完整流程测试,而不是替代完整流程测试。
- 测试外部行为,不测试私有实现细节。
- 测试可见骰子结果能正确生成组合事实。
- 测试 `2 + Clover``Clover + 2` 都能归一为同一个无序组合。
- 测试至少一个 3 骰组合事实,确保系统不写死 2 颗骰子。
- 测试双 Clover 优先级高于泛用 Clover 行为。
- 测试纯数字结果发放普通奖励。
- 测试单 Clover 加数字发放普通奖励并提供 Clover bonus。
- 测试行为分发按行为类型执行,而不是按组合 key 执行。
- 测试双 Clover 能进入 Lucky Dice。
- 测试 Lucky Dice 筛选器能过滤未开启、未解锁或来源不匹配的候选。
- 测试固定随机源下的权重随机结果可预测。
- 测试 Lucky Dice 候选池为空时进入兜底。
- 测试 Rocket 解析到 Slap Down 普通模式。
- 测试 Thief 解析到 Treasure Heist 普通模式。
- 测试 Lucky Dice 结果槽数量来自配置。
- 测试普通骰子数量从 2 改为 3 后,组合事实和匹配器仍按统计结果工作。
- 测试追踪信息足够解释最终结果。
当前项目还没有既有玩法测试套件和生产 C# 模块,因此这些是第一版实现建议采用的测试口,而不是对现有测试的引用。
## 不在范围内
- 完整复刻参考 Lucky Dice 动画。
- 托盘动画、骰子飞入、粒子爆发、白烟转场和完整表现节奏。
- 完整实现 Slap Down 玩法。
- 完整实现 Treasure Heist 玩法。
- 实现所有 Lucky Dice 特殊结果类型。
- 远端配置热更新。
- 复杂运营活动概率策略。
- 埋点管线集成,第一版只要求本地追踪字段。
- 为每个组合 key 写独立业务方法。
- 假设普通骰子永远只有 2 颗。
- 假设 Lucky Dice 永远只有 3 个结果槽。
## 补充说明
本地 HTML 拆解中的竞品流程应作为产品参考,而不是第一版逐帧复刻目标。第一版核心价值是建立可扩展闭环:
```text
普通 Roll
→ 组合事实
→ 规则匹配
→ 行为执行
→ 触发 Lucky Dice
→ 候选筛选
→ 权重随机
→ 目标玩法模式解析
→ 回到主循环
```
第一版应优先保证清晰和可调试。每个关键决策都应能从追踪信息中解释:为什么命中某个组合、为什么进入 Lucky Dice、哪些候选被过滤、最终选中了什么结果、启动了哪个玩法模式。
`to-prd` 技能说明中提到需要发布到 issue tracker 并添加 `ready-for-agent` 标签。但当前项目没有暴露 issue tracker 集成或 triage label 配置,所以本 PRD 先作为本地项目文档落地。

View File

@@ -0,0 +1,532 @@
# Lucky Dice 核心流程功能划分
## 文档信息
- 父级 PRD[lucky-dice-core-flow-prd.md](../PRD/lucky-dice-core-flow-prd.md)
- 需求文档:[lucky-dice-core-flow-requirements.md](../Requirements/lucky-dice-core-flow-requirements.md)
- 技术规格:[lucky-dice-core-flow-spec.md](../Specs/lucky-dice-core-flow-spec.md)
- 架构蓝图:[lucky-dice-core-flow-architecture-blueprint.md](../Architecture/lucky-dice-core-flow-architecture-blueprint.md)
- 架构决策:[Docs/Decisions](../Decisions/README.md)
- 生成日期2026-06-22
本文档把 Lucky Dice 核心流程拆成可实现、可验收、可并行推进的功能包。它面向后续开发排期和任务拆分,不替代 PRD、需求或规格文档。
## 总目标
### 问题
FishDice 需要一套可扩展的普通骰子主循环和 Lucky Dice 特殊分支。第一版不能只写死几组骰子结果,也不能把每个组合绑定到独立业务方法,否则后续新增组合、骰子数量、特殊结果或目标玩法模式时会快速失控。
### 解决方案
把核心闭环拆成配置、普通 Roll、组合事实、规则匹配、行为执行、Lucky Dice 筛选随机、目标模式解析、最小表现和追踪测试几个功能包。每个功能包都有明确输入输出和验收口,优先保证玩法闭环、可配置、可测试和可排查。
### 第一版影响
- 玩家能完成普通 Roll并看到普通奖励、Clover bonus 或 Lucky Dice 触发结果。
- 策划能通过规则和候选配置表达基础组合和 Lucky Dice 入口。
- 开发能在不写组合专用方法的前提下扩展新组合。
- 测试能用固定输入和固定随机源验证完整链路。
## 用户角色
| 角色 | 关注点 |
| --- | --- |
| 玩家 | Roll 结果清晰,双 Clover 有惊喜Lucky Dice 能看到目标玩法反馈 |
| 策划 | 骰子数量、组合规则、候选结果、权重和模式可配置 |
| 开发 | 模块边界清晰,新增组合不堆硬编码,随机和追踪可测试 |
| 测试 | 能覆盖组合事实、规则优先级、Lucky Dice 兜底和完整链路 |
| 产品负责人 | 第一版范围收敛,先验证闭环,不被复杂动画和完整短玩法拖散 |
## 功能包总览
| 编号 | 功能包 | 优先级 | 目标 | 依赖 |
| --- | --- | --- | --- | --- |
| F0 | 本地配置与核心常量 | P0 | 提供第一版默认配置和 key 入口 | 无 |
| F1 | 普通 Roll 结果生成 | P0 | 根据 DiceSet 配置生成普通骰子结果 | F0 |
| F2 | 组合事实构建 | P0 | 把 Roll 结果转成可匹配事实 | F0, F1 |
| F3 | 组合规则匹配 | P0 | 按优先级命中普通组合规则 | F0, F2 |
| F4 | 行为执行与分发 | P0 | 按 action type 执行奖励、倍率和 Lucky Dice 触发 | F3 |
| F5 | Lucky Dice 候选筛选与权重随机 | P0 | 从特殊结果池选出目标结果 | F0, F4 |
| F6 | 目标玩法与模式解析 | P0 | 把 Lucky Dice 结果解析成目标玩法模式 | F5 |
| F7 | 最小表现与目标玩法占位入口 | P1 | 展示 Lucky Dice 标题、倍率、结果槽和跳转反馈 | F5, F6 |
| F8 | 追踪与调试信息 | P0 | 用 RollSessionId 串起关键决策 | F1-F6 |
| F9 | 测试套件 | P0 | 覆盖事实、匹配、筛选、随机和链路 | F1-F8 |
| F10 | 后续扩展预留 | P2 | 为更多组合、模式、动画和远端配置留口 | F0-F9 |
P0 是第一版闭环必须完成的功能包。P1 是第一版玩家可见体验的最小表现。P2 是明确预留但不要求第一版完整实现的扩展。
## F0 本地配置与核心常量
### 目标
建立第一版默认配置和统一 key 入口,让后续模块不在业务逻辑里散落裸字符串和硬编码数量。
### 功能需求
- 提供普通骰子集合 `normal_main`
- 普通骰子面为 `2 / 3 / 4 / 5 / 6 / clover`
- 普通骰子数量来自 DiceSet 配置,第一版配置值为 2。
- 提供 Lucky Dice 特殊结果集合 `lucky_dice`
- Lucky Dice 第一版结果至少包含 `rocket``thief`
- Lucky Dice 默认结果槽数量来自配置,第一版配置值为 3。
- 提供普通组合规则配置。
- 提供 Lucky Dice 候选结果配置。
- 提供目标玩法模式映射配置。
### 非功能需求
- 配置结构不能绑定具体 Unity 场景对象。
- key 应集中定义或集中加载,避免业务代码到处裸写。
- 配置加载失败时需要返回结构化错误。
### 验收标准
- 能读取普通骰子集合、Lucky Dice 集合、组合规则、候选结果和模式映射。
- 修改普通骰子数量配置后,核心流程读取到新数量。
- 修改 Lucky Dice 结果槽数量配置后,展示槽构建读取到新数量。
- 普通骰子集合和 Lucky Dice 特殊结果集合有明确类型区分。
### 不在范围内
- 远端配置热更新。
- 策划可视化配置编辑器。
- 完整配置校验工具链。
## F1 普通 Roll 结果生成
### 目标
根据普通 DiceSet 配置生成一次普通 Roll 结果,并携带同一个 `RollSessionId`
### 功能需求
- 接收玩家 Roll 请求。
- 从配置获取普通骰子集合和骰子数量。
- 按配置生成 `DiceRollResult`
- `DiceRollResult` 包含 DiceSetId、DiceSetType、DiceCount、Faces、Multiplier、TriggerSource、RollSessionId。
- 不决定奖励、组合规则或 Lucky Dice 入口。
### 非功能需求
- 支持注入随机源或可控 Roll 输入,方便测试。
- 不写死两颗骰子位置。
- 生成结果数量必须与配置 DiceCount 一致。
### 用户故事
作为玩家,我希望普通掷骰能产出清晰结果,从而知道每次 Roll 都有意义。
### 验收标准
- 给定 `DiceCount = 2`Roll 结果包含 2 个普通骰子面。
- 给定 `DiceCount = 3`Roll 结果包含 3 个普通骰子面。
- Roll 结果携带 `RollSessionId`
- Roll 层不调用奖励、规则匹配或 Lucky Dice 服务。
## F2 组合事实构建
### 目标
把普通 Roll 结果转换成结构化 `ComboFacts`,供规则匹配使用。
### 功能需求
-`DiceRollResult` 构建 `ComboFacts`
- 统计原始面列表、面数量、数字列表、点数和、Clover 数量。
- 生成无序 `NormalizedKey`
- 生成有序 `OrderedKey`
- `2 + clover``clover + 2` 归一为相同无序 key。
- 拒绝或报错处理 Lucky Dice 特殊结果进入普通组合事实构建。
### 非功能需求
- 必须支持 N 颗骰子。
- 不依赖固定左骰、右骰字段。
- 输出字段应可用于日志和测试断言。
### 用户故事
作为开发者,我希望 Roll 结果先转成组合事实,从而规则可以基于数据匹配,而不是依赖固定位置判断。
### 验收标准
- `2 + 3` 生成 `NumberSum = 5``CloverCount = 0``NormalizedKey = 2_3`
- `2 + clover``clover + 2` 生成相同 `NormalizedKey`
- `clover + clover` 生成 `CloverCount = 2`
- `2 + 2 + clover` 能生成 3 骰事实。
- Lucky Dice 特殊结果不会被当成普通组合事实处理。
## F3 组合规则匹配
### 目标
按规则优先级和通用 matcher 匹配普通组合,输出命中规则和 action 列表。
### 功能需求
- 支持规则优先级。
- 第一版默认命中高优先级规则后停止继续匹配。
- 支持首批 matcher
- `ExactCombo`
- `AllNumbers`
- `ContainsFace`
- `FaceCount`
- `FaceCountRange`
- `DiceCount`
- `NumberPair`
- `NumberOfAKind`
- `NumberSumRange`
- 第一版默认规则:
- 双 Clover 触发 Lucky Dice。
- 单 Clover 加数字发普通奖励和 Clover bonus。
- 纯数字发普通奖励。
### 非功能需求
- matcher 只按 matcher type 注册,不按组合 key 注册。
- 规则匹配不执行业务副作用。
- 匹配结果需要包含命中规则 id、优先级、matcher type。
### 用户故事
作为策划,我希望组合规则支持优先级,从而让双 Clover 这类稀有触发优先于普通奖励规则。
### 验收标准
- `clover_clover` 命中 Lucky Dice 规则,而不是泛用 Clover 规则。
- `2_clover` 命中 Clover bonus 规则。
- `2_6` 命中纯数字普通奖励规则。
- 高优先级规则优先于低优先级规则。
- 3 骰事实可以通过统计型 matcher 命中规则。
## F4 行为执行与分发
### 目标
把命中规则中的 action list 按 `ActionType` 分发给通用行为执行器。
### 功能需求
- 支持 `grant_reward`
- 支持 `add_multiplier`
- 支持 `trigger_lucky_dice`
- 支持 `enter_mode` 扩展口。
- 支持 `show_popup` 扩展口。
- 多个 action 按规则配置顺序执行。
- 未注册 action 返回结构化错误并写入追踪。
### 非功能需求
- dispatcher 只维护 `ActionType -> Executor` 注册表。
- 不允许维护 `combo key -> method` 映射。
- executor 不重新做组合匹配判断。
### 用户故事
作为开发者,我希望行为分发按行为类型注册,从而不需要维护巨大的“组合 key 到方法”映射表。
### 验收标准
- 纯数字规则执行普通奖励 action。
- 单 Clover 规则执行普通奖励和倍率或 bonus action。
- 双 Clover 规则执行 Lucky Dice 触发 action。
- 新增组合但复用现有 action 时,不需要新增 executor。
- 未注册 action 能被追踪到。
## F5 Lucky Dice 候选筛选与权重随机
### 目标
Lucky Dice 触发后,从独立特殊结果池中构建候选、筛选候选、处理兜底,并按权重随机选中一个结果。
### 功能需求
- 构建 Lucky Dice 候选池。
- 候选包含 ResultKey、TargetKey、Weight、Enabled、MinLevel、SourceFilter、CooldownRule、IsDefault、ResultSlotCount。
- 支持首批筛选器:
- EnabledFilter
- ProgressFilter
- SourceFilter
- CooldownFilter
- TutorialFilter
- 筛选后按权重随机。
- 权重随机使用可注入随机源。
- 候选池为空时优先使用 default result。
- default result 不可用时降级为普通奖励兜底。
### 非功能需求
- Lucky Dice 特殊结果不与普通骰子集合混用。
- 随机结果可通过固定随机源复现。
- 筛选过程需要记录每个 filter 前后的候选数量。
### 用户故事
作为策划,我希望 Lucky Dice 候选结果可筛选,从而未解锁、未开启或冷却中的目标玩法不会被抽中。
### 验收标准
- 未开启候选会被过滤。
- 玩家进度不足候选会被过滤。
- 来源不匹配候选会被过滤。
- 固定随机源下可以稳定选中预期结果。
- 候选池为空时进入可观测兜底。
- `rocket``thief` 可以作为第一版有效候选。
## F6 目标玩法与模式解析
### 目标
把 Lucky Dice 选中的结果解析为目标玩法和具体模式。
### 功能需求
- 支持 `ResultKey -> TargetKey -> ModeKey` 两级解析。
- `rocket` 解析到 `slap_down_normal`
- `thief` 解析到 `treasure_heist_normal`
- 保留教程模式和 bonus 模式扩展规则。
- 输出 `TargetModeEntry`,包含 TargetKey、ModeKey、ResultKey、Multiplier、TriggerSource、RollSessionId。
### 非功能需求
- 模式解析不参与 Lucky Dice 随机。
- 目标玩法启动不反向影响候选筛选。
- 模式解析失败必须可兜底、可追踪。
### 用户故事
作为玩家,我希望 Rocket 结果进入 Slap Down从而让结果和后续玩法有明确绑定。
### 验收标准
- `rocket` 解析为 `TargetKey = slap_down``ModeKey = slap_down_normal`
- `thief` 解析为 `TargetKey = treasure_heist``ModeKey = treasure_heist_normal`
- 模式解析结果携带原始 `RollSessionId`
- 模式映射失败时进入可观测兜底。
## F7 最小表现与目标玩法占位入口
### 目标
提供第一版玩家可见反馈Lucky Dice 标题、倍率、结果槽图标和目标玩法跳转反馈。
### 功能需求
- 展示 Lucky Dice 标题。
- 展示当前倍率。
- 按结果槽数量展示特殊图标。
- 第一版结果槽可全部展示同一个 ResultKey。
- 触发目标玩法入口反馈。
- 目标玩法结束后能回到主循环。
- Slap Down 和 Treasure Heist 第一版可以是占位入口,不要求完整玩法内容。
### 非功能需求
- 表现层不参与规则匹配、候选筛选和权重随机。
- 动画失败不能改变核心选择结果。
- 结果槽数量来自配置或候选,不写死在 UI 中。
### 用户故事
作为玩家,我希望 Lucky Dice 结果能清楚显示特殊图标,从而知道即将进入哪个短玩法。
### 验收标准
- Lucky Dice 触发后能看到标题、倍率和结果槽。
- 选中 `rocket` 时展示 Rocket 结果并进入 Slap Down 占位入口。
- 选中 `thief` 时展示 Thief 结果并进入 Treasure Heist 占位入口。
- 结果槽数量修改为 2 或 4 时,表现层能按配置展示。
### 不在范围内
- 托盘动画。
- 骰子飞入。
- 粒子爆发。
- 白烟转场。
- 完整 Slap Down 和 Treasure Heist 玩法。
## F8 追踪与调试信息
### 目标
让一次 Roll 从普通结果到最终玩法模式的关键决策可解释、可回放、可排查。
### 功能需求
- 每次 Roll 创建或携带同一个 `RollSessionId`
- 记录普通骰子结果和骰子数量。
- 记录组合事实和 `NormalizedKey`
- 记录命中规则、优先级和执行 action。
- 记录 Lucky Dice 初始候选和筛选结果。
- 记录兜底类型。
- 记录权重随机输入和选中结果。
- 记录最终 TargetKey、ModeKey 和 FinalOutcome。
### 非功能需求
- 追踪层不改变玩法决策。
- 第一版可以使用本地结构化日志或调试数据结构。
- 字段命名应稳定,方便后续接入埋点。
### 用户故事
作为开发者,我希望每次 Roll 都携带同一个会话 id从而普通 Roll、规则匹配、Lucky Dice 结果和目标玩法启动可以串起来排查。
### 验收标准
- 普通奖励链路可以从 trace 解释为什么发奖。
- 双 Clover 链路可以从 trace 解释为什么进入 Lucky Dice。
- Lucky Dice 链路可以从 trace 看到哪些候选被过滤。
- 模式解析失败或候选池为空时trace 中有兜底原因。
## F9 测试套件
### 目标
用测试锁住第一版核心闭环,避免后续扩展组合或结果时破坏基础行为。
### 功能需求
- 组合事实测试。
- 规则匹配测试。
- 行为分发测试。
- Lucky Dice 筛选测试。
- 权重随机测试。
- 目标模式解析测试。
- 完整链路测试。
- 兜底场景测试。
### 非功能需求
- 测试外部行为,不测试私有实现细节。
- 使用固定随机源。
- 关键链路测试应覆盖 `RollSessionId`
### 验收标准
- `2 + 3` 最终发普通奖励。
- `2 + clover` 最终发普通奖励并提供 Clover bonus。
- `clover + clover` 最终进入 Lucky Dice。
- Lucky Dice 选中 `rocket` 后进入 `slap_down_normal`
- Lucky Dice 选中 `thief` 后进入 `treasure_heist_normal`
- 候选池为空时走兜底。
- 普通骰子从 2 改为 3 后,组合事实和 matcher 仍工作。
## F10 后续扩展预留
### 目标
明确第一版不做但架构需要保留的扩展方向,防止第一版把未来道路堵死。
### 扩展方向
- 更多普通组合,例如对子、三连、点数和区间、指定数字组合、有序组合。
- 更多 Lucky Dice 特殊结果,例如 Chest、Bomb、Key。
- 教程模式和 bonus 模式。
- 按活动或关卡调整权重。
- 远端配置热更新。
- 完整表现动画。
- 完整 Slap Down 和 Treasure Heist 玩法内容。
- 埋点管线集成。
### 约束
- 扩展普通组合优先新增配置,不新增组合专用方法。
- 扩展特殊结果优先新增候选和模式映射。
- 扩展动画不能改变核心随机和模式解析结果。
- 接入埋点时复用 `RollSessionId` 和现有 trace 字段。
## 依赖顺序
```mermaid
flowchart TD
F0["F0 本地配置与核心常量"] --> F1["F1 普通 Roll 结果生成"]
F1 --> F2["F2 组合事实构建"]
F2 --> F3["F3 组合规则匹配"]
F3 --> F4["F4 行为执行与分发"]
F4 --> F5["F5 Lucky Dice 筛选与随机"]
F5 --> F6["F6 目标玩法与模式解析"]
F5 --> F7["F7 最小表现"]
F6 --> F7
F1 --> F8["F8 追踪"]
F3 --> F8
F5 --> F8
F6 --> F8
F1 --> F9["F9 测试"]
F2 --> F9
F3 --> F9
F4 --> F9
F5 --> F9
F6 --> F9
```
## 建议迭代切片
### Slice 1普通 Roll 到普通奖励
包含 F0、F1、F2、F3、F4 的普通奖励部分、F8 基础字段、F9 基础测试。
验收:
- 纯数字结果发普通奖励。
- 单 Clover 加数字发普通奖励和 bonus。
- 组合事实和规则命中可追踪。
### Slice 2双 Clover 触发 Lucky Dice
包含 F4 的 `trigger_lucky_dice`、F5、F6、F8 Lucky Dice 字段、F9 Lucky Dice 测试。
验收:
- 双 Clover 不发普通奖励,而是进入 Lucky Dice。
- `rocket` 能解析到 Slap Down 普通模式。
- `thief` 能解析到 Treasure Heist 普通模式。
- 候选池为空有兜底。
### Slice 3最小玩家可见闭环
包含 F7 和链路测试补齐。
验收:
- Lucky Dice 标题、倍率、结果槽可见。
- 目标玩法占位入口可达。
- 目标玩法结束后能回到主循环。
### Slice 4扩展性验证
包含 3 骰配置测试、结果槽数量变化测试、一个统计型 matcher 示例。
验收:
- 普通骰子数量改为 3 后不改核心流程。
- 结果槽数量改为 2 或 4 后表现层按配置展示。
- 新增一个组合规则时复用已有 matcher 和 action。
## 总体验收标准
- 普通骰子结果能转换为结构化组合事实。
- 普通骰子数量和 Lucky Dice 结果槽数量来自配置。
- 规则系统通过少量通用 matcher 命中普通组合。
- 双 Clover 优先触发 Lucky Dice。
- 行为执行层只按 action type 分发。
- Lucky Dice 先筛选候选,再按权重随机。
- `rocket` 进入 `slap_down_normal`
- `thief` 进入 `treasure_heist_normal`
- 候选池为空、模式解析失败、未注册 action 都有可观测兜底。
- `RollSessionId` 能串起完整链路。
## 第一版不做
- 完整复刻参考 Lucky Dice 动画。
- 托盘、飞入、粒子爆发、白烟转场。
- 完整 Slap Down 玩法。
- 完整 Treasure Heist 玩法。
- 所有 Lucky Dice 特殊结果类型。
- 远端配置热更新。
- 复杂运营活动概率策略。
- 正式埋点管线集成。
- 为每个组合 key 编写独立业务方法。
- 假设普通骰子永远只有 2 颗。
- 假设 Lucky Dice 永远只有 3 个结果槽。

View File

@@ -0,0 +1,354 @@
# Lucky Dice 核心流程需求
## 目标
本文档定义 FishDice 中普通骰子流程与 Lucky Dice 特殊分支的最小核心逻辑。当前阶段忽略复杂动画表现,只保留玩法闭环、规则扩展口、随机筛选口和目标玩法模式解析口。
核心目标:
- 普通骰子流程可以通过不同组合触发不同业务行为。
- 双四叶草组合可以触发 Lucky Dice 特殊流程。
- Lucky Dice 内部使用独立的特殊骰子结果池,不与普通数字骰混用。
- 骰子数量和结果槽数量需要可配置,第一版可以使用 2 颗普通骰和 3 个 Lucky Dice 结果槽,但规则系统不能写死这个数量。
- Lucky Dice 结果进入目标玩法前,需要保留筛选、随机和模式解析扩展点。
- 执行层避免维护巨大的“组合 key 到方法”的硬编码映射。
## 概念边界
### 普通骰子
普通流程使用一套主循环骰子面:
```text
2 / 3 / 4 / 5 / 6 / Clover
```
其中 `1``Clover` 替代。普通骰子负责主循环奖励、倍率、特殊触发等常规行为。
普通骰子的数量需要由骰子集合配置决定。第一版默认使用 2 颗普通骰,但系统需要支持后续扩展到 3 颗、4 颗或特殊关卡自定义数量。
最小组合语义:
- 数字 + 数字:普通奖励。
- 数字 + Clover普通奖励加小额 bonus 或倍率反馈。
- Clover + Clover触发 Lucky Dice。
后续可扩展更多组合含义,例如对子、三连、指定数量命中、点数和区间、指定数字组合、顺序组合等。
### Lucky Dice 特殊骰子
Lucky Dice 进入后使用另一套特殊结果骰子,不再使用普通数字骰。
示例特殊结果:
```text
Rocket / Thief / Chest / Bomb / Key
```
这套结果的职责是决定后续玩法入口,而不是做普通点数结算。
Lucky Dice 的结果槽数量也需要可配置。竞品拆解中表现为 3 格相同特殊结果,第一版可以固定为 3但数据结构和播放流程需要允许后续扩展为 2 格、4 格或按玩法目标定义槽数量。
最小语义:
- Rocket进入 Slap Down。
- Thief进入 Treasure Heist。
## 总体流程
```text
玩家触发普通 Roll
→ 产出普通骰子结果
→ 提取组合事实 ComboFacts
→ 按优先级匹配普通组合规则
→ 执行规则配置的行为列表
→ 普通奖励:发放奖励并结束
→ Lucky Dice进入 Lucky Dice 流程
Lucky Dice 流程
→ 构建特殊结果候选池
→ 按上下文筛选候选结果
→ 按权重随机一个结果
→ 按结果槽数量显示相同或配置指定的特殊结果
→ 解析目标玩法与进入模式
→ 启动目标玩法
→ 目标玩法结束后回到主循环
```
## 普通组合规则需求
### 组合 key
组合规则允许使用字符串 key 作为数据 ID例如
```text
clover_clover
number_number
number_clover
```
使用字符串 key 的目的:
- 便于配置表、JSON、远端配置和日志使用。
- 新增组合时不一定需要新增枚举或改代码。
- 规则识别与执行逻辑可以解耦。
约束:
- 不允许在业务代码中到处裸写字符串。
- 组合 key 只作为规则索引或配置 ID。
- 原始骰子结果、数量统计、是否有序等信息必须保留在结构化数据中。
### 组合事实
普通 Roll 产出后,需要生成组合事实,供规则匹配器使用。
至少包含:
```text
DiceSetType 骰子集合类型,普通骰子或 Lucky Dice
DiceCount 本次 Roll 实际骰子数量
Faces 原始骰子面列表
FaceCounts 每个骰子面的数量
NumberSum 数字骰点数和
NumberValues 本次出现的数字点数列表
CloverCount 四叶草数量
HasClover 是否包含四叶草
NormalizedKey 无序归一化 key
OrderedKey 有序 key
```
组合事实不能假设只有 2 颗骰子。`Faces``FaceCounts` 必须支持任意数量,规则匹配器应基于数量统计和条件表达,而不是只读取左骰、右骰两个固定位置。
普通组合默认按无序处理,例如:
```text
clover + 2
2 + clover
```
都归一为:
```text
2_clover
```
如果后续需要区分左骰、右骰或先后顺序,可以在规则中显式声明使用有序 key例如
```text
clover_then_rocket
rocket_then_clover
```
### 规则匹配
组合系统不应为每个组合写独立方法,而应使用少量通用匹配器。
首批匹配器需求:
- ExactCombo精确匹配组合 key例如 `clover_clover`
- AllNumbers全部为数字骰。
- ContainsFace包含指定面例如包含 1 个 Clover。
- FaceCount指定面数量达到要求。
- FaceCountRange指定面数量落在区间内例如至少 2 个 Clover。
- DiceCount本次骰子数量满足要求例如只匹配 2 骰或 3 骰规则。
- NumberPair数字对子。
- NumberOfAKindN 个相同数字,例如三连、四连。
- NumberSumRange点数和落在区间内。
规则需要支持优先级,优先级高的规则先匹配。
最小规则配置:
```text
优先级 100clover_clover → trigger_lucky_dice
优先级 50contains Clover count 1 → grant_reward + clover_bonus
优先级 10all_numbers → grant_reward
```
当后续加入更多骰子数量时,应优先使用统计型规则,例如:
```text
Clover count >= 2 → trigger_lucky_dice
NumberOfAKind count 3 → grant_reward + combo_bonus
DiceCount 4 + NumberSumRange 18-24 → grant_reward + high_sum_bonus
```
## 行为执行需求
执行层只注册少量通用行为执行器,不维护“所有组合 key 到方法”的巨大 map。
规则配置只描述要执行哪些行为,行为执行器按行为类型处理。
首批行为类型:
- grant_reward发放普通奖励。
- add_multiplier调整倍率或临时倍率。
- trigger_lucky_dice进入 Lucky Dice。
- enter_mode进入指定玩法模式。
- show_popup显示提示或轻量弹窗。
执行器注册表只随行为类型增长,不随组合数量增长。
示例:
```text
grant_reward → GrantRewardExecutor
trigger_lucky_dice → TriggerLuckyDiceExecutor
enter_mode → EnterModeExecutor
```
组合扩展优先通过新增规则配置完成;只有出现新的通用行为类型时,才新增执行器。
## Lucky Dice 筛选与随机需求
Lucky Dice 结果不能直接从全部特殊结果中随机,必须经过候选池构建、筛选和权重随机。
### 候选结果
候选结果至少包含:
```text
ResultKey 特殊结果 ID例如 rocket / thief
TargetKey 目标玩法 ID例如 slap_down / treasure_heist
Weight 随机权重
Enabled 是否启用
MinLevel 最低等级或进度要求
SourceFilter 允许的触发来源
CooldownRule 冷却或次数限制
```
### 筛选器
首批筛选器需求:
- EnabledFilter过滤未开启结果。
- ProgressFilter过滤玩家进度不满足的结果。
- SourceFilter过滤当前触发来源不允许的结果。
- CooldownFilter过滤处于冷却或次数已满的玩法。
- TutorialFilter新手期可强制或限制候选结果。
筛选器应可组合,筛选后如果候选池为空,需要有兜底策略。
兜底策略:
- 优先使用配置的 default result。
- 如果 default result 不可用,走普通奖励兜底。
- 兜底发生时需要打日志,方便排查配置问题。
### 随机选择
筛选后的候选结果按权重随机。
随机需求:
- 支持普通权重随机。
- 支持按上下文调整权重,例如活动期间提高某结果权重。
- 支持新手期固定结果或半随机结果。
- 随机结果需要可记录,方便回放、埋点和问题排查。
## 目标玩法与模式解析
Lucky Dice 的 `ResultKey` 不直接等于最终进入模式。需要分为两层:
```text
ResultKey → TargetKey → ModeKey
```
含义:
- ResultKeyLucky Dice 抽中的特殊结果,例如 `rocket`
- TargetKey目标玩法例如 `slap_down`
- ModeKey目标玩法的具体进入模式例如 `slap_down_normal`
示例:
```text
rocket → slap_down
slap_down + 新手期 → slap_down_tutorial
slap_down + 普通状态 → slap_down_normal
slap_down + 高倍率 → slap_down_bonus
```
首批模式解析规则:
- TutorialModeRule新手期进入教程模式。
- BonusModeRule高倍率或特殊上下文进入 bonus 模式。
- DefaultModeRule默认进入普通模式。
目标玩法启动时,需要携带:
```text
TargetKey
ModeKey
ResultKey
Multiplier
TriggerSource
RollSessionId
```
## 最小可交付范围
第一版只需要实现以下玩法闭环:
### 普通骰子
```text
骰子面2 / 3 / 4 / 5 / 6 / Clover
默认骰子数量2
骰子数量来源DiceSet 配置
```
组合行为:
```text
数字 + 数字 → 普通奖励
数字 + Clover → 普通奖励 + Clover bonus
Clover + Clover → 触发 Lucky Dice
```
### Lucky Dice
特殊结果:
```text
Rocket → SlapDownNormal
Thief → TreasureHeistNormal
默认结果槽数量3
结果槽数量来源Lucky Dice 结果或玩法配置
```
表现最小化:
```text
显示 Lucky Dice 标题
→ 显示倍率
→ 按结果槽数量显示特殊图标
→ 进入目标玩法
```
复杂动画如托盘、骰子飞入、粒子爆发、白烟转场等不进入第一版核心逻辑要求。
## 非目标
第一版不要求:
- 完整复刻竞品动画时长和粒子效果。
- 实现所有特殊结果类型。
- 实现复杂运营活动权重策略。
- 实现完整短玩法内容。
- 实现远端配置热更新。
## 验收标准
- 普通骰子结果能被转换为结构化组合事实。
- 普通骰子数量和 Lucky Dice 结果槽数量来自配置,核心规则不写死为 2 骰或 3 格。
- 规则系统能通过少量通用匹配器命中普通组合。
- 双四叶草能触发 Lucky Dice而不是直接发普通奖励。
- 行为执行层只依赖少量通用执行器,不存在按组合数量增长的大型方法 map。
- Lucky Dice 能先筛选候选结果,再按权重随机。
- Lucky Dice 结果能解析到目标玩法和具体模式。
- Rocket 能进入 Slap Down 普通模式。
- Thief 能进入 Treasure Heist 普通模式。
- 候选池为空时存在可观测兜底。
- 日志或调试信息能追踪一次 Roll 从普通结果到最终行为的关键决策。

View File

@@ -0,0 +1,765 @@
# Lucky Dice 核心流程规格
## 文档信息
- 来源需求:[lucky-dice-core-flow-requirements.md](../Requirements/lucky-dice-core-flow-requirements.md)
- 适用范围FishDice 第一版普通骰子主循环与 Lucky Dice 特殊分支
- 文档目标:把玩法需求拆成可实现、可测试、可扩展的系统规格
## 设计目标
Lucky Dice 核心流程需要支持两层玩法:
1. 普通骰子主循环:玩家按骰子集合配置 Roll 普通骰子,根据组合事实匹配规则并执行奖励或特殊触发行为。
2. Lucky Dice 特殊流程:当普通骰子出现双四叶草或配置声明的触发条件时,进入独立的特殊结果池,经过筛选、权重随机和模式解析后启动目标玩法。
第一版只做核心闭环,不实现复杂动画、远端热更、完整短玩法内容和复杂运营权重策略。系统边界必须保留扩展点,避免后续新增组合或特殊结果时不断堆硬编码分支。
第一版默认值:
- 普通骰子数量默认 2 颗,但必须来自 DiceSet 配置。
- Lucky Dice 结果槽数量默认 3 格,但必须来自 Lucky Dice 结果或目标玩法配置。
- 规则系统、组合事实和展示流程不得把 2 颗骰子或 3 格结果槽写死为不可扩展假设。
## 总体架构
核心模块按职责拆分为以下几层:
```text
Roll 输入层
→ 骰子结果生成层
→ 组合事实提取层
→ 组合规则匹配层
→ 行为执行层
→ Lucky Dice 流程层
→ 目标玩法模式解析层
→ 目标玩法启动层
```
各层只依赖前一层产出的结构化数据,不直接读取 UI 状态或动画状态。
### 模块职责
| 模块 | 职责 | 不负责 |
| --- | --- | --- |
| DiceSetConfigProvider | 提供骰子集合、骰子数量、结果槽数量等配置 | 执行 Roll 或业务行为 |
| DiceRollService | 按骰子集合配置生成普通骰子或 Lucky Dice 结果 | 决定奖励、玩法入口 |
| ComboFactBuilder | 把普通 Roll 结果转换为结构化组合事实 | 执行业务行为 |
| ComboRuleMatcher | 按优先级匹配组合规则 | 直接发奖或进玩法 |
| RollActionDispatcher | 按行为类型分发到通用执行器 | 按组合 key 维护巨大方法 map |
| LuckyDiceFlowService | 构建候选池、筛选、随机、生成可展示结果槽 | 直接决定目标模式细节 |
| TargetModeResolver | 把 ResultKey/TargetKey 解析为 ModeKey | 执行目标玩法内容 |
| TargetModeLauncher | 统一启动目标玩法 | 参与 Lucky Dice 随机 |
| RollTraceLogger | 记录一次 Roll 的关键决策链路 | 影响玩法决策 |
## 核心流程
### 普通 Roll 流程
```text
PlayerRollRequest
→ DiceSetConfigProvider.GetNormalDiceSet(request)
→ DiceRollService.Roll(NormalDiceSet)
→ ComboFactBuilder.Build(result)
→ ComboRuleMatcher.Match(facts, ruleSet)
→ RollActionDispatcher.Execute(matchedRule.Actions)
→ grant_reward发放普通奖励并结束
→ grant_reward + add_multiplier发奖并应用 Clover bonus
→ trigger_lucky_dice进入 LuckyDiceFlowService
```
普通 Roll 第一版默认两颗骰子,骰子面为:
```text
2 / 3 / 4 / 5 / 6 / Clover
```
骰子数量必须从 `DiceSetConfig.DiceCount` 读取。第一版配置值可以是 2`DiceRollService``ComboFactBuilder` 和 matcher 都必须按 `Faces.Count``DiceCount` 处理,不读取固定的左骰、右骰两个位置。
### Lucky Dice 流程
```text
TriggerLuckyDiceAction
→ LuckyDiceFlowService.BuildCandidatePool(context)
→ LuckyDiceFlowService.ApplyFilters(pool, context)
→ LuckyDiceFlowService.ResolveEmptyPoolFallback(filteredPool, context)
→ LuckyDiceFlowService.WeightedPick(filteredPool, context)
→ LuckyDiceFlowService.BuildResultSlots(result, slotConfig)
→ LuckyDicePresentation.ShowResultSlots(resultSlots)
→ TargetModeResolver.Resolve(result, context)
→ TargetModeLauncher.Launch(targetEntry)
→ TargetModeCompletion 回到主循环
```
第一版 Lucky Dice 默认展示 3 格相同特殊图标,不要求实现托盘、飞入、粒子爆发、白烟转场等表现。结果槽数量必须从 Lucky Dice 结果或目标玩法配置读取,后续可扩展为 2 格、4 格或按玩法定义不同槽数。
## 数据模型
### DiceSetConfig
骰子集合配置定义一次 Roll 使用哪套骰子、投几颗骰子,以及该集合是否拥有展示槽概念。
```csharp
public sealed class DiceSetConfig
{
public string DiceSetId { get; init; }
public DiceSetType SetType { get; init; }
public int DiceCount { get; init; }
public IReadOnlyList<DiceFaceDefinition> Faces { get; init; }
public int? DefaultResultSlotCount { get; init; }
}
```
约束:
- `DiceCount` 必须大于 0。
- 第一版普通骰子 `DiceCount = 2`,但核心逻辑不得依赖该常量。
- Lucky Dice 如果不真实 Roll 多颗特殊骰,可以将 `DiceCount` 理解为结果选择次数;第一版只随机一次,再由展示槽配置生成多个相同槽。
- `DefaultResultSlotCount` 用于展示层默认槽数,第一版 Lucky Dice 为 3。
### DiceFace
普通骰子面与 Lucky Dice 特殊结果必须区分集合类型,不能混用同一个裸字符串列表。
```csharp
public enum DiceSetType
{
Normal,
Lucky
}
public readonly struct DiceFace
{
public DiceSetType SetType { get; }
public string Key { get; }
public int? NumberValue { get; }
}
```
约束:
- 普通数字面使用 `Key = "2" ... "6"``NumberValue` 为对应数字。
- 普通四叶草使用 `Key = "clover"``NumberValue = null`
- Lucky Dice 特殊结果使用 `DiceSetType.Lucky`,例如 `rocket``thief`
- 业务代码不得到处裸写这些 key必须集中在定义表或常量入口。
### DiceRollResult
```csharp
public sealed class DiceRollResult
{
public string RollSessionId { get; init; }
public string DiceSetId { get; init; }
public DiceSetType SetType { get; init; }
public int DiceCount { get; init; }
public IReadOnlyList<DiceFace> Faces { get; init; }
public int Multiplier { get; init; }
public string TriggerSource { get; init; }
}
```
约束:
- `DiceCount` 必须等于本次配置要求的投掷数量。
- `Faces.Count` 必须等于本次实际产出的骰子面数量。
- 普通 Roll 中 `DiceCount``Faces.Count` 不一致时,应拒绝进入规则匹配并记录错误。
### ComboFacts
`ComboFacts` 是普通组合匹配的唯一输入。
```csharp
public sealed class ComboFacts
{
public DiceSetType DiceSetType { get; init; }
public int DiceCount { get; init; }
public IReadOnlyList<DiceFace> Faces { get; init; }
public IReadOnlyDictionary<string, int> FaceCounts { get; init; }
public int NumberSum { get; init; }
public IReadOnlyList<int> NumberValues { get; init; }
public int CloverCount { get; init; }
public bool HasClover { get; init; }
public string NormalizedKey { get; init; }
public string OrderedKey { get; init; }
}
```
归一化规则:
- 默认按无序组合处理。
- `2 + clover``clover + 2` 都归一为 `2_clover`
- `clover + clover` 归一为 `clover_clover`
- N 颗骰子的 `NormalizedKey` 由所有面 key 排序后拼接,例如 `2_2_clover``3_3_3`
- 后续若需要有序语义,由规则显式声明使用 `OrderedKey`
约束:
- `ComboFacts` 不能假设只有 2 颗骰子。
- matcher 必须基于 `DiceCount``Faces``FaceCounts``NumberValues` 等统计信息判断。
- 只有有序规则才能读取 `OrderedKey`,普通规则默认使用无序事实。
### ComboRule
```csharp
public sealed class ComboRule
{
public string RuleId { get; init; }
public int Priority { get; init; }
public ComboMatcherSpec Matcher { get; init; }
public IReadOnlyList<RollActionSpec> Actions { get; init; }
public bool StopAfterMatched { get; init; } = true;
}
```
第一版规则:
| Priority | Matcher | Actions |
| --- | --- | --- |
| 100 | ExactCombo `clover_clover` | `trigger_lucky_dice` |
| 50 | ContainsFace `clover` count 1 | `grant_reward`, `add_multiplier` |
| 10 | AllNumbers | `grant_reward` |
匹配策略:
-`Priority` 从高到低匹配。
- 同优先级按配置顺序匹配。
- 第一版默认命中第一条 `StopAfterMatched = true` 的规则后停止。
- 未命中任何规则时走可观测兜底,默认发普通奖励或返回明确错误,由产品配置决定。
### ComboMatcherSpec
```csharp
public sealed class ComboMatcherSpec
{
public string MatcherType { get; init; }
public string ComboKey { get; init; }
public string FaceKey { get; init; }
public int? RequiredCount { get; init; }
public int? MinCount { get; init; }
public int? MaxCount { get; init; }
public int? DiceCount { get; init; }
public int? OfAKindCount { get; init; }
public int? MinSum { get; init; }
public int? MaxSum { get; init; }
public bool UseOrderedKey { get; init; }
}
```
首批 matcher
- `ExactCombo`
- `AllNumbers`
- `ContainsFace`
- `FaceCount`
- `FaceCountRange`
- `DiceCount`
- `NumberPair`
- `NumberOfAKind`
- `NumberSumRange`
新增组合语义优先新增规则配置;只有出现新的通用匹配能力时才新增 matcher。
N 颗骰子扩展示例:
| Matcher | 例子 | 含义 |
| --- | --- | --- |
| `FaceCountRange` | `faceKey = clover`, `minCount = 2` | 至少 2 个 Clover |
| `DiceCount` | `diceCount = 3` | 只匹配 3 骰规则 |
| `NumberOfAKind` | `ofAKindCount = 3` | 任意数字三连 |
| `NumberSumRange` | `minSum = 18`, `maxSum = 24` | 点数和落在 18 到 24 |
### RollActionSpec
```csharp
public sealed class RollActionSpec
{
public string ActionType { get; init; }
public IReadOnlyDictionary<string, string> Params { get; init; }
}
```
首批 action
- `grant_reward`
- `add_multiplier`
- `trigger_lucky_dice`
- `enter_mode`
- `show_popup`
执行层只按 `ActionType` 找执行器,不按组合 key 找方法。
### LuckyDiceCandidate
```csharp
public sealed class LuckyDiceCandidate
{
public string ResultKey { get; init; }
public string TargetKey { get; init; }
public int? ResultSlotCount { get; init; }
public int Weight { get; init; }
public bool Enabled { get; init; }
public int MinLevel { get; init; }
public IReadOnlySet<string> SourceFilter { get; init; }
public CooldownRule CooldownRule { get; init; }
public bool IsDefault { get; init; }
}
```
第一版候选配置:
| ResultKey | TargetKey | ResultSlotCount | Weight | Enabled | Mode 默认结果 |
| --- | --- | --- | --- | --- | --- |
| `rocket` | `slap_down` | 3 | 1 | true | `slap_down_normal` |
| `thief` | `treasure_heist` | 3 | 1 | true | `treasure_heist_normal` |
`ResultSlotCount` 允许为空。为空时使用 Lucky Dice 集合配置或目标玩法配置中的默认槽数。
### LuckyDiceResultSlots
Lucky Dice 随机只选中一个特殊结果;展示层按槽配置生成实际槽位。
```csharp
public sealed class LuckyDiceResultSlots
{
public string ResultKey { get; init; }
public string TargetKey { get; init; }
public int SlotCount { get; init; }
public IReadOnlyList<string> SlotResultKeys { get; init; }
}
```
第一版要求:
- `SlotCount` 默认 3。
- `SlotResultKeys` 第一版全部填入同一个 `ResultKey`
- 后续如某玩法需要不同槽位结果,可通过配置扩展 `SlotResultKeys` 的生成策略。
### LuckyDiceContext
```csharp
public sealed class LuckyDiceContext
{
public string RollSessionId { get; init; }
public string TriggerSource { get; init; }
public int NormalDiceCount { get; init; }
public int LuckyResultSlotCount { get; init; }
public int PlayerLevel { get; init; }
public int Multiplier { get; init; }
public bool IsTutorial { get; init; }
public IReadOnlyDictionary<string, object> RuntimeTags { get; init; }
}
```
### TargetModeEntry
```csharp
public sealed class TargetModeEntry
{
public string TargetKey { get; init; }
public string ModeKey { get; init; }
public string ResultKey { get; init; }
public int Multiplier { get; init; }
public string TriggerSource { get; init; }
public string RollSessionId { get; init; }
}
```
## 接口边界
### 组合事实构建
```csharp
public interface IComboFactBuilder
{
ComboFacts Build(DiceRollResult result);
}
```
要求:
- 只接受普通骰子结果构建普通组合事实。
- 如果传入 Lucky Dice 结果,应返回错误或拒绝处理,避免两套骰子语义混用。
- 输出必须保留 DiceCount、原始 Faces、FaceCounts、NumberValues、NormalizedKey、OrderedKey。
- 不允许只读取固定两个骰子位置;必须遍历 `Faces`
### 组合规则匹配
```csharp
public interface IComboRuleMatcher
{
ComboRuleMatchResult Match(ComboFacts facts, IReadOnlyList<ComboRule> rules);
}
```
要求:
- matcher 由 `MatcherType` 注册,不由组合 key 注册。
- 匹配结果需要包含命中的 `RuleId``Priority``MatcherType` 和关键输入 facts供日志追踪。
### 行为执行
```csharp
public interface IRollActionExecutor
{
string ActionType { get; }
RollActionResult Execute(RollActionContext context, RollActionSpec action);
}
```
要求:
- `RollActionDispatcher` 只维护 `ActionType -> IRollActionExecutor` 注册表。
- 不允许维护 `clover_clover -> SomeMethod` 这类随组合数量增长的 map。
- 多个 action 按规则配置顺序执行。
- action 失败时需要返回结构化失败结果,并进入统一日志。
### Lucky Dice 筛选
```csharp
public interface ILuckyDiceCandidateFilter
{
string FilterType { get; }
IReadOnlyList<LuckyDiceCandidate> Apply(
IReadOnlyList<LuckyDiceCandidate> candidates,
LuckyDiceContext context);
}
```
首批 filter
- `EnabledFilter`
- `ProgressFilter`
- `SourceFilter`
- `CooldownFilter`
- `TutorialFilter`
筛选器按固定顺序执行,顺序建议为:
```text
Enabled → Progress → Source → Cooldown → Tutorial
```
### 权重随机
```csharp
public interface IWeightedPicker
{
LuckyDiceCandidate Pick(
IReadOnlyList<LuckyDiceCandidate> candidates,
LuckyDiceContext context,
IRandomSource random);
}
```
要求:
- 权重小于等于 0 的候选不参与普通随机。
- 随机源可注入,便于测试和回放。
- 选中结果必须记录候选池快照、权重、随机值和最终 ResultKey。
### Lucky Dice 结果槽构建
```csharp
public interface ILuckyDiceResultSlotBuilder
{
LuckyDiceResultSlots BuildSlots(
LuckyDiceCandidate selected,
LuckyDiceContext context,
DiceSetConfig luckyDiceSetConfig);
}
```
要求:
- 槽数优先级:候选结果配置 `ResultSlotCount` → 目标玩法配置 → Lucky Dice 集合配置 `DefaultResultSlotCount`
- 第一版每个槽都展示同一个 `ResultKey`
- 槽数小于等于 0 时返回结构化错误并进入 Lucky Dice 兜底。
### 模式解析
```csharp
public interface ITargetModeResolver
{
TargetModeEntry Resolve(LuckyDiceCandidate selected, LuckyDiceContext context);
}
```
解析优先级:
1. `TutorialModeRule`
2. `BonusModeRule`
3. `DefaultModeRule`
第一版映射:
| ResultKey | TargetKey | Default ModeKey |
| --- | --- | --- |
| `rocket` | `slap_down` | `slap_down_normal` |
| `thief` | `treasure_heist` | `treasure_heist_normal` |
## 兜底与错误处理
### 普通组合未命中
处理策略:
1. 记录 `RollSessionId`、原始 Faces、NormalizedKey。
2. 使用配置的普通奖励兜底,或返回可见错误给调试面板。
3. 不进入 Lucky Dice。
第一版建议默认发普通奖励兜底,避免玩家流程中断。
### Lucky Dice 候选池为空
处理策略:
1. 尝试使用配置的 default result。
2. 如果 default result 不可用,执行普通奖励兜底。
3. 记录空池原因:初始候选数量、每个 filter 过滤前后数量、最终兜底类型。
### 目标模式解析失败
处理策略:
1. 记录 `ResultKey``TargetKey`、上下文标签。
2. 如果存在 TargetKey 默认模式,降级到默认模式。
3. 如果不存在默认模式,返回 Lucky Dice 失败并执行普通奖励兜底。
## 可观测性
每次 Roll 必须拥有同一个 `RollSessionId`,贯穿普通 Roll、规则匹配、Lucky Dice、模式解析和目标玩法启动。
最小日志字段:
```text
RollSessionId
TriggerSource
NormalFaces
NormalDiceCount
ComboNormalizedKey
MatchedRuleId
MatchedRulePriority
ExecutedActions
LuckyDiceInitialCandidates
LuckyDiceFilteredCandidates
LuckyDiceFallbackType
LuckyDiceSelectedResultKey
LuckyDiceSelectedTargetKey
LuckyDiceResultSlotCount
LuckyDiceSlotResultKeys
ResolvedModeKey
FinalOutcome
```
日志目标:
- 复盘一次 Roll 为什么发奖、为什么进 Lucky Dice、为什么进某个玩法。
- 发现配置问题时能看出是规则未命中、候选池为空、权重异常还是模式解析失败。
## 配置示例
第一版可以使用本地静态配置或 ScriptableObject远端配置热更新不在范围内。
### 骰子集合配置示例
```json
{
"diceSetId": "normal_main",
"setType": "Normal",
"diceCount": 2,
"faces": ["2", "3", "4", "5", "6", "clover"]
}
```
```json
{
"diceSetId": "lucky_dice",
"setType": "Lucky",
"diceCount": 1,
"defaultResultSlotCount": 3,
"faces": ["rocket", "thief", "chest", "bomb", "key"]
}
```
### 普通组合规则示例
```json
[
{
"ruleId": "normal_clover_clover_lucky_dice",
"priority": 100,
"matcher": {
"matcherType": "ExactCombo",
"comboKey": "clover_clover"
},
"actions": [
{ "actionType": "trigger_lucky_dice", "params": {} }
],
"stopAfterMatched": true
},
{
"ruleId": "normal_number_clover_bonus",
"priority": 50,
"matcher": {
"matcherType": "ContainsFace",
"faceKey": "clover",
"requiredCount": 1
},
"actions": [
{ "actionType": "grant_reward", "params": {} },
{ "actionType": "add_multiplier", "params": { "reason": "clover_bonus" } }
],
"stopAfterMatched": true
},
{
"ruleId": "normal_all_numbers_reward",
"priority": 10,
"matcher": {
"matcherType": "AllNumbers"
},
"actions": [
{ "actionType": "grant_reward", "params": {} }
],
"stopAfterMatched": true
}
]
```
后续多骰规则示例:
```json
[
{
"ruleId": "normal_at_least_two_clovers_lucky_dice",
"priority": 100,
"matcher": {
"matcherType": "FaceCountRange",
"faceKey": "clover",
"minCount": 2
},
"actions": [
{ "actionType": "trigger_lucky_dice", "params": {} }
],
"stopAfterMatched": true
},
{
"ruleId": "normal_three_of_a_kind_bonus",
"priority": 60,
"matcher": {
"matcherType": "NumberOfAKind",
"ofAKindCount": 3
},
"actions": [
{ "actionType": "grant_reward", "params": {} },
{ "actionType": "add_multiplier", "params": { "reason": "three_of_a_kind" } }
],
"stopAfterMatched": true
}
]
```
### Lucky Dice 候选示例
```json
[
{
"resultKey": "rocket",
"targetKey": "slap_down",
"resultSlotCount": 3,
"weight": 1,
"enabled": true,
"minLevel": 0,
"sourceFilter": ["normal_roll"],
"isDefault": true
},
{
"resultKey": "thief",
"targetKey": "treasure_heist",
"resultSlotCount": 3,
"weight": 1,
"enabled": true,
"minLevel": 0,
"sourceFilter": ["normal_roll"],
"isDefault": false
}
]
```
## 测试规格
### 组合事实测试
- `2 + 3` 生成 `NumberSum = 5``CloverCount = 0``NormalizedKey = "2_3"`
- `2 + clover``clover + 2` 都生成 `NormalizedKey = "2_clover"`
- `clover + clover` 生成 `CloverCount = 2``NormalizedKey = "clover_clover"`
- `2 + 2 + clover` 生成 `DiceCount = 3``NumberValues = [2, 2]``NormalizedKey = "2_2_clover"`
- Lucky Dice 特殊结果不能被普通 `ComboFactBuilder` 当成普通组合处理。
### 规则匹配测试
- `clover_clover` 命中优先级 100 的 Lucky Dice 规则。
- `2_clover` 命中优先级 50 的 Clover bonus 规则。
- `2_6` 命中优先级 10 的 AllNumbers 规则。
- 当多条规则同时可命中时,高优先级先执行。
- 3 骰配置下,`FaceCountRange clover min 2` 能命中任意至少 2 个 Clover 的组合。
- `NumberOfAKind count 3` 能命中三个相同数字,不依赖具体数字 key。
### 行为执行测试
- `grant_reward` 调用奖励执行器。
- `trigger_lucky_dice` 调用 Lucky Dice 流程入口。
- dispatcher 只按 `ActionType` 分发,不依赖组合 key。
- 未注册的 action 返回结构化错误并记录日志。
### Lucky Dice 测试
- Enabled=false 的候选被过滤。
- 玩家等级不足时候选被 `ProgressFilter` 过滤。
- 候选池为空时使用 default result。
- default result 不可用时走普通奖励兜底并记录日志。
- 权重随机可以通过固定随机源得到可预测结果。
- Lucky Dice 结果槽数量来自候选结果、目标玩法或 DiceSet 默认配置。
- 第一版 `rocket` 生成 3 个 `rocket` 展示槽。
- `rocket` 解析到 `slap_down_normal`
- `thief` 解析到 `treasure_heist_normal`
### 链路测试
- 普通 Roll 出 `clover + clover` 后,最终进入 Lucky Dice而不是发普通奖励。
- Lucky Dice 选中 `rocket` 后,启动 Slap Down 普通模式。
- Lucky Dice 选中 `thief` 后,启动 Treasure Heist 普通模式。
- 同一个 `RollSessionId` 能串起普通结果、命中规则、Lucky Dice 候选、随机结果和最终 ModeKey。
- 普通骰子数量从 2 改为 3 时,组合事实与 matcher 仍按统计结果工作,不出现固定左右骰逻辑。
## 验收映射
| 需求验收点 | 规格落点 |
| --- | --- |
| 普通骰子结果能转换为结构化组合事实 | `ComboFacts`、组合事实测试 |
| 普通骰子数量和 Lucky Dice 结果槽数量来自配置 | `DiceSetConfig``LuckyDiceResultSlots`、槽构建测试 |
| 少量通用匹配器命中普通组合 | `ComboMatcherSpec`、规则匹配测试 |
| 双四叶草触发 Lucky Dice | 普通规则优先级 100、链路测试 |
| 执行层不维护大型组合 map | `IRollActionExecutor`、行为执行测试 |
| Lucky Dice 先筛选再权重随机 | Lucky Dice 筛选、权重随机接口 |
| Lucky Dice 结果解析到目标玩法和模式 | `TargetModeResolver`、模式映射 |
| Rocket 进入 Slap Down 普通模式 | 第一版模式映射、Lucky Dice 测试 |
| Thief 进入 Treasure Heist 普通模式 | 第一版模式映射、Lucky Dice 测试 |
| 候选池为空时有可观测兜底 | 兜底与错误处理、日志字段 |
| 日志能追踪一次 Roll 的关键决策 | 可观测性字段、链路测试 |
## 第一版不做
- 复杂动画完整复刻。
- 所有 Lucky Dice 特殊结果类型。
- 复杂运营活动权重策略。
- 完整短玩法内容实现。
- 远端配置热更新。
- 按组合 key 直接绑定业务方法。
## 后续扩展建议
- 当组合规则增多后,把本地配置迁移到 ScriptableObject 或 JSON 配置表。
- 当 Lucky Dice 特殊结果超过第一版范围后,补充结果图标、目标玩法、默认模式和冷却规则的配置校验。
- 当接入埋点时,直接复用 `RollSessionId` 和可观测性字段,避免另起一套追踪链路。
- 当新增有序组合时,只新增规则声明与 matcher 参数,不修改 `ComboFacts` 的原始结果保存方式。
- 当普通骰子扩展到 3 颗或 4 颗时,优先新增统计型规则,不新增固定位置分支。