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

766 lines
23 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 核心流程规格
## 文档信息
- 来源需求:[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 颗时,优先新增统计型规则,不新增固定位置分支。