Files
card-game-engine/REVIEW_REPORT.md
xiaoou 712116bca8 fix: fan_stacking验证 + OnEliminate传播 + TurnOrder读DSL
子代理审查(DSL+Core)发现26个问题,本轮修复3个:
1. fan_stacking值验证: 非法值→警告(之前静默以add计算)
2. OnEliminate传播: BuildPhases补映射(PhaseConfig已有属性)
3. TurnOrder读DSL: 之前硬编码counter_clockwise,现读YAML

REVIEW_REPORT.md: 子代理审查完整报告(保留作为参考)
2026-07-04 23:06:12 +08:00

170 lines
10 KiB
Markdown
Raw 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.

# DSL YAML + DslLoader + Core 代码审查报告
审查范围: dsl-examples/*.yaml (5文件) + DslLoader.cs (313行) + Core/WildcardRegistry.cs + Core/MahjongTile.cs + Core/GameState.cs + Core/Deck.cs
审查日期: 2026-07-04
---
## 一、YAML字段不一致 (4 issues)
### 1.1 DeckConfig.WildcardCount/WildcardTile — POCO属性未在任何YAML中使用
- **位置**: DslLoader.cs:168-169 (POCO), 5个YAML文件 deck 段
- **问题**: `DeckConfig` 定义了 `WildcardCount``WildcardTile` 两个属性但所有5个YAML的 deck 段只使用 `generator, include_honors, include_flowers, total` 四个字段。wildcard_count 实际配置在 wildcard_rules 段下,由 `WildcardConfig` 吸收。
- **严重度**: 中 — 字段永远不会被赋值容易混淆使用者。MahjongRoom.cs:292 读取此字段永远为0。
### 1.2 PhaseDslConfig.TurnOrder — DSL有值但BuildPhases硬编码覆盖
- **位置**: DslLoader.cs:215 (POCO), MahjongRoom.cs:102
- **问题**: 所有YAML在 play phase 中配置 `turn_order: counter_clockwise`,但 `BuildPhases()``TurnOrder = "counter_clockwise"` 硬编码,未使用 `p.TurnOrder`
- **严重度**: 中 — 若有人写 `turn_order: clockwise` 会被静默忽略。
### 1.3 PhaseDslConfig.OnEliminate — 未传播到PhaseConfig
- **位置**: DslLoader.cs:213 (POCO), PhaseMachine.cs:20 (PhaseConfig 有 OnEliminate), MahjongRoom.cs:94-118 (BuildPhases)
- **问题**: `phase.on_eliminate` 在 xuezhandaodi.yaml:69 配置为 `hu_paid`,但 BuildPhases 未将 `p.OnEliminate` 复制到 PhaseConfig。PhaseConfig 有 OnEliminate 属性,但从未被赋值。
- **严重度**: 高 — 血战到底的"胡牌后继续"核心逻辑可能失效。
### 1.4 PhaseDslConfig.FuFlag — 未传播到PhaseConfig
- **位置**: DslLoader.cs:214 (FuFlag), xuezhandaodi.yaml:74-78
- **问题**: xuezhandaodi 配置了完整的 `fu_flag` (过水规则),但 BuildPhases 不复制 FuFlag 到 PhaseConfigPhaseConfig 也没有 FuFlag 属性。引擎使用硬编码逻辑处理过水 (MahjongRoom.cs:270,369,397)DSL 配置值未参与。
- **严重度**: 中 — 过水逻辑可用但无法通过 DSL 定制。
---
## 二、DSL字段未消费 (6 issues)
### 2.1 WildcardConfig.FanCalculationPolicy — 引擎未使用
- **位置**: DslLoader.cs:180 (POCO), wuhan.yaml:31 / nanchang.yaml:33 (YAML= `optimal`)
- **问题**: `FanCalculationPolicy` 字段存在于 POCO 和 YAML但在 ScoreEngine 和 MeldsSolver 中完全未被读取。引擎在计算癞子番型时无此策略分支。
- **严重度**: 低 — 当前仅 optimal 一种策略,但若有人配置其他值不会报错也不会生效。
### 2.2 FlowerDslConfig.ReplaceTiles — 引擎未使用
- **位置**: DslLoader.cs:258 (POCO), guangdong_jipinghu.yaml:28 (`BEFORE_GAME_START`)
- **问题**: 广东鸡平胡配置 `replace_tiles: BEFORE_GAME_START`,但 ScoreEngine 和 MahjongRoom 均不读取此字段。花牌替换逻辑在 MahjongRoom.StepTurn() 中硬编码为"摸到花牌立即补牌"。
- **严重度**: 中 — 若 BEFORE_GAME_START 表示"开局前补花"语义,则当前行为不符。
### 2.3 PreHookConfig.Condition — 引擎未评估
- **位置**: DslLoader.cs:193 (POCO), xuezhandaodi.yaml:89-90 (`deck_exhausted AND not hua_zhu`)
- **问题**: `PreHookConfig.Condition` 在 YAML 中有复杂条件表达式,但引擎代码中 PreHook 仅按 name 触发Condition 字符串从未被解析/评估。
- **严重度**: 中 — 条件判断实际在引擎中硬编码DSL condition 仅为文档参考。
### 2.4 FanTypeConfig.Condition — 引擎未评估
- **位置**: DslLoader.cs:202 (POCO), wuhan.yaml:49 / nanchang.yaml:54 (`hand_contains_wildcard`)
- **问题**: `FanTypeConfig.Condition` 被复制到 `FanConfig.Condition` (MahjongRoom.cs:88) 但 MeldsSolver.IdentifyFans() (607-794行) 完全不检查此字段。癞子胡的"手牌含癞子"判断是硬编码在 757 行,并非通过 DSL condition。
- **严重度**: 低 — 引擎自动识别癞子胡condition 冗余但无害。
### 2.5 DrawSubPhase.OnDrawFlower — 部分消费
- **位置**: DslLoader.cs:234 (POCO), guobiao.yaml:97 / guangdong_jipinghu.yaml:75 (`replace`)
- **问题**: `draw.on_draw_flower` 在 YAML 中配置为 `replace`,但引擎在 StepTurn 中硬编码处理花牌补牌逻辑,不读取此配置值。
- **严重度**: 低
### 2.6 DrawSubPhase.OnEmptyDeck — 部分消费
- **位置**: DslLoader.cs:235 (POCO), xuezhandaodi.yaml:54 (`check_ting`)
- **问题**: `draw.on_empty_deck` 在 xuezhandaodi 配置为 `check_ting`,其他为 `exhausted`。引擎代码在 StepTurn 中硬编码检查牌墙耗尽逻辑,不按 DSL 值分支。
- **严重度**: 低
---
## 三、编码重复魔法数字 (7 issues)
### 3.1 WildcardBase=50 重复定义
- **位置**: MahjongTile.cs:11 (const), WildcardRegistry.cs:23-24 (default), DslLoader.cs:176 (default), Deck.cs:8 (default param)
- **问题**: 值 `50` 出现在4个位置。MahjongTile.cs 定义了常量 `WildcardBase = 50`,但 WildcardRegistry 和 DslLoader 各自硬编码默认值。
- **严重度**: 中 — 若需改为60必须同时修改4处。
### 3.2 Wildcard范围 = 9 重复
- **位置**: WildcardRegistry.cs:39 (`_wildcardBase + 9`), MahjongTile.cs:14,18 (`WildcardBase + 9`)
- **问题**: 癞子编码范围 [50, 59] 的长度 9 重复定义。无常量 `WildcardRange`
- **严重度**: 低
### 3.3 Suit常量未提取
- **位置**: MahjongTile.cs:64-70, WildcardRegistry.cs:67,76,78, Deck.cs (间接)
- **问题**: suit 值 (0=万, 1=条, 2=筒, 3=字, 4=花, 5=宝牌) 在 MahjongTile.Suit() 和 WildcardRegistry.ComputeNextTiles() 等处多次硬编码,无枚举或命名常量。
- **严重度**: 低
### 3.4 编码边界值重复
- **位置**: MahjongTile.cs:40-42,64-69, TileToIndex 等
- **问题**: 编码边界 1,9,11,19,21,29,31,37,41,48 在 Suit/Rank/ToString/TileToIndex 中反复硬编码。
- **严重度**: 低 — Encoder/Decoder 集中在一个类,但缺乏常量名可读性差。
### 3.5 牌复制数 4 硬编码
- **位置**: Deck.cs:13 `int count = MahjongTile.IsFlower(t) ? 1 : 4;`
- **问题**: 每张非花牌4张的规则硬编码。MahjongTile.AllTiles() 中也隐式依赖此数。
- **严重度**: 低
### 3.6 MeldsSolver 编码索引范围
- **位置**: MeldsSolver.cs:127-134, 140-154 (counts array size 34, index offset 9/18/27)
- **问题**: counts 数组大小为 34 (27 数字牌 + 7 字牌),偏移量 9,18,27 硬编码,与 MahjongTile 的编码系统存在隐含耦合但无共享常量。
### 3.7 DefaultFanValue 硬编码番值
- **位置**: PhaseMachine.cs:178-184
- **问题**: `DefaultFanValue` 为特定番型硬编码 fallback 值。MeldsSolver.IdentifyFans line 786-792 也有一个超长的硬编码番型列表用作过滤。这两个列表是同一知识的两个拷贝。
- **严重度**: 中 — 添加新番型需同步修改两处。
---
## 四、合规检查遗漏 (10 issues)
### 4.1 fan_stacking 值未验证
- **位置**: DslLoader.cs:57-131 (ValidateRuleCompliance)
- **问题**: `fan_stacking` 合法值应为 `add`, `add_max`, `max_level`。guangdong_jipinghu 使用 `max_level` 且 ScoreEngine.cs:109-117 有相应逻辑,但合规检查未验证此值。若输入 `fan_stacking: multiply` 不会报错。
- **严重度**: 高 — 非法值会导致 ComputeTotalFans 落入 default 分支静默以 add 模式计算。
### 4.2 max_fan 值未验证
- **位置**: DslLoader.cs:147 (MaxFan=MaxValue), YAML中 max_fan
- **问题**: `max_fan` 应为正数,但合规检查未验证。负数会被 Math.Min 截断但无警告。
- **严重度**: 低
### 4.3 win_min_fan 与 fan_stacking 一致性
- **位置**: guobiao.yaml:41 `win_min_fan: 8` + `fan_stacking: add_max`
- **问题**: 国标需要起胡8番合规检查未验证 win_min_fan > 0 且 fan_stacking 支持此语义。
- **严重度**: 中
### 4.4 Phase 类型值未验证
- **位置**: DslLoader.cs:206 (PhaseDslConfig.Type)
- **问题**: `phase.type` 合法值应为 `auto` / `mahjong_turn`,合规检查未验证。
- **严重度**: 低
### 4.5 EndCondition 类型值未验证
- **位置**: DslLoader.cs:245 (EndConditionDsl)
- **问题**: `end_conditions[].type` 在 YAML 中为 `last_one_standing` / `deck_exhausted` / `player_wins`,合规检查未验证此枚举。
- **严重度**: 中 — 非法类型静默跳过。
### 4.6 Action 名未验证
- **位置**: DslLoader.cs:244 (ActionOptionDsl.Action)
- **问题**: YAML 中 actions 包含 `discard, an_kong, bu_kong, win, pung, ming_kong, chi, pass`。合规检查未验证这些动作名。
- **严重度**: 低 — 非法 action 会在 PhaseMachine 中被忽略。
### 4.7 PriorityPolicy 值未验证
- **位置**: DslLoader.cs:241 (`"highest_wins"`)
- **问题**: `priority_policy` 目前仅支持 `highest_wins`,合规检查未验证。
- **严重度**: 低
### 4.8 FuFlag 配置完整性未检查
- **位置**: DslLoader.cs:217-223 (FuFlagConfig), xuezhandaodi.yaml:74-78
- **问题**: fu_flag 有 4 个子字段 (type/set_on/clear_on/effect),合规检查未验证这些值是否合法。
- **严重度**: 中
### 4.9 番型 Excludes/Conflicts 引用完整性
- **位置**: DslLoader.cs:200-201
- **问题**: YAML 中 `excludes``conflicts` 引用的番型名可能指向不存在的番型 (如 wuhan.yaml:39 `excludes: [缺一门, 无字]` 但武汉麻将 fan_types 中无 缺一门/无字)。合规检查未做引用完整性校验。
- **严重度**: 中 — 引用了不存在的番型名不会报错,排除逻辑静默失效。
### 4.10 flower_rules 一致性
- **位置**: guangdong_jipinghu.yaml:28 `replace_tiles: BEFORE_GAME_START`
- **问题**: `replace_tiles``BEFORE_GAME_START` 未在合规检查中验证。
- **严重度**: 低
---
## 五、总结统计
| 类别 | High | Medium | Low | 合计 |
|------|------|--------|-----|------|
| YAML字段不一致 | 1 | 2 | 0 | 3 |
| DSL未消费 | 0 | 4 | 2 | 6 |
| 编码重复魔法数字 | 0 | 2 | 5 | 7 |
| 合规检查遗漏 | 1 | 6 | 3 | 10 |
| **总计** | **2** | **14** | **10** | **26** |
**最高优先级修复**:
1. 🔴 **PhaseDslConfig.OnEliminate 未传播** (1.3) — 血战到底核心逻辑可能受影响
2. 🔴 **fan_stacking 值未验证** (4.1) — 非法值静默以 add 模式计算