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