# 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)