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