Files
Fishdice/FishDice/Docs/Architecture/lucky-dice-core-flow-architecture-blueprint.md
2026-06-23 14:13:08 +08:00

488 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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.
# 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. 补齐链路测试和追踪字段。
这个顺序能先把核心闭环跑通,再逐步加表现和更多结果类型,避免一开始被动画和短玩法完整内容拖散。