feat(lucky-dice): 提交 demo 前核心流程
This commit is contained in:
@@ -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-002:Roll 结果先转组合事实,再匹配规则
|
||||
|
||||
[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-004:Lucky 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. 补齐链路测试和追踪字段。
|
||||
|
||||
这个顺序能先把核心闭环跑通,再逐步加表现和更多结果类型,避免一开始被动画和短玩法完整内容拖散。
|
||||
20
FishDice/Docs/Decisions/README.md
Normal file
20
FishDice/Docs/Decisions/README.md
Normal 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 的状态里标记替代关系。
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
144
FishDice/Docs/PRD/lucky-dice-core-flow-prd.md
Normal file
144
FishDice/Docs/PRD/lucky-dice-core-flow-prd.md
Normal 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 先作为本地项目文档落地。
|
||||
532
FishDice/Docs/Plans/lucky-dice-core-flow-feature-breakdown.md
Normal file
532
FishDice/Docs/Plans/lucky-dice-core-flow-feature-breakdown.md
Normal 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 个结果槽。
|
||||
354
FishDice/Docs/Requirements/lucky-dice-core-flow-requirements.md
Normal file
354
FishDice/Docs/Requirements/lucky-dice-core-flow-requirements.md
Normal 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:数字对子。
|
||||
- NumberOfAKind:N 个相同数字,例如三连、四连。
|
||||
- NumberSumRange:点数和落在区间内。
|
||||
|
||||
规则需要支持优先级,优先级高的规则先匹配。
|
||||
|
||||
最小规则配置:
|
||||
|
||||
```text
|
||||
优先级 100:clover_clover → trigger_lucky_dice
|
||||
优先级 50:contains Clover count 1 → grant_reward + clover_bonus
|
||||
优先级 10:all_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
|
||||
```
|
||||
|
||||
含义:
|
||||
|
||||
- ResultKey:Lucky 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 从普通结果到最终行为的关键决策。
|
||||
765
FishDice/Docs/Specs/lucky-dice-core-flow-spec.md
Normal file
765
FishDice/Docs/Specs/lucky-dice-core-flow-spec.md
Normal 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 颗时,优先新增统计型规则,不新增固定位置分支。
|
||||
Reference in New Issue
Block a user