Commit Graph

81 Commits

Author SHA1 Message Date
181696f93a fix: 听牌检测wc重复计数→部分可胡牌丢失
根因: GetTingTiles传了hand+fullHand+wc=CountWildcards(hand)
  → CheckWin内部又计wildcardsInTiles→双重计数→total%3≠2→guard拒绝

修复: wc固定为0(configured wildcards已在tiles中被CheckWin统计)
  wildcardCount参数仅用于encoding 50+的额外wildcard tiles

效果: 听牌检测不再遗漏可胡牌,显示完整的可胡张数
2026-07-05 01:18:30 +08:00
e6c63923d1 fix: filter移除五门齐/一色四同顺/将一色等无fallback无_fanConfig守卫的番型
只保留有hardcoded fallback值或event的番型在filter中
移除: 将一色/一色四同顺/一色三同顺/一般高/五门齐/全求人/门前清
  → 这些只在DSL配置_fanConfig时才显示,否则被filter剔除
  → allZeroValue→鸡胡兜底保证非零分

效果: 南昌显示'缺一门+鸡胡'(不再越权显示五门齐)
2026-07-05 01:13:52 +08:00
70b00d5e6d fix: IdentifyFans filter排除无fallback番型—防止跨变体越权
核心变更:
- filter从'well-known全放行'改为'只放行_fanConfig或有fallback值的'
- 排除: 断幺九/无字/缺一门/将/平胡/平和(无hardcoded fallback)
- 这些被排除时→allZeroValue→鸡胡兜底→保证非零分

效果: 南昌不再显示'断幺九+无字+平胡'等不存在于南昌规则的番型
测试: _fanConfig补全16个番型, 58 tests pass
2026-07-05 01:09:42 +08:00
84681297c0 fix: 人类模式主回合听牌显示具体张数和牌名
CheckTing→GetTingTiles: 返回可胡牌列表而非bool
主回合显示: 🀄听 4张(🀫3万 🀫4万 中 白) — 与反应提示一致
2026-07-05 00:55:53 +08:00
4bc64eda04 fix(DSL): 南昌麻将加all_orphans + --check提醒十三烂
南昌麻将需要全不靠/十三烂牌型支持:
- dsl: requires加 meldsolver.all_orphans
- --check: type=random时未配all_orphans→建议添加

能力注册: 7/7→8/8(100%), 十三烂牌型现在可识别
2026-07-05 00:50:21 +08:00
e3ff0f057e feat: DSL字段 wildcard_rules.meld_mode (standard/any)
之前: 特殊风牌组合是'隐式规则',硬编码在type=random检查中
现在: YAML声明 meld_mode: any (南昌麻将),引擎检查并报告不支持

WildcardConfig新增MeldMode字段(默认standard):
- standard: 仅顺子/刻子/对子(引擎支持)
- any: 任意3张可配面子(南昌麻将,引擎不支持)

--check: meld_mode=any → '引擎未实现→仅支持standard'
规则从'隐式'变为DSL可表达的显式规则
2026-07-05 00:40:03 +08:00
89faec1165 fix: --check报告引擎不支持特殊风牌组合(南昌麻将random wildcard)
南昌麻将: 精+任意三个不同风可配成面子, 引擎仅支持标准面子
→ type=random时新增警告: '引擎仅支持标准面子(顺子/刻子/对子)→特殊风牌组合不支持'

南昌--check: 4→5条警告
2026-07-05 00:36:17 +08:00
2eb1432550 fix: 人类模式子菜单显示精牌🀫 + 反应提示加听牌
3处修复:
1. 碰/杠/吃后子菜单手牌: ToString→ToString(wildcards) (精牌🀫)
2. 反应提示加听牌: 显示 🀄听牌 N张(tile list)
3. 杠后手牌同样修复

修复前: [1] 4条 (精牌不显示标记)
修复后: [1] 🀫4条 (精牌带🀫前缀)
2026-07-05 00:31:35 +08:00
b3c270710f fix: human模式手牌/反应显示精牌🀫标记
3处ToString调用补WildcardRegistry参数:
- HumanMahjongPlayer: 手牌列表+摸牌显示
- MahjongRoom: 反应提示时的手牌显示

修复前: 精牌显示为普通牌名(1筒)
修复后: 精牌显示为 🀫1筒(带🀫前缀)
2026-07-05 00:23:47 +08:00
d83494e669 feat: demo显示听牌状态+可胡张数
引擎: MahjongRoom.GetTingTiles(player) 返回所有可胡的unique tile
Demo: 每回合后显示'听牌: X: 🀄听N张(tile1 tile2...) | Y: 未听...'
包含已胡/副露中的tile计数,兼容wildcard和258规则

示例: 听牌: AI-东: 🀄听 1张(中) | AI-南: 未听 | AI-西: 未听 | AI-北: 未听
2026-07-05 00:20:53 +08:00
ce3bbc7760 fix: 精计数警告拆分,冲关/德国仅type=random时报告
修复前: per_wildcard>0→统一显示"冲关/精数量倍增未支持"(武汉误报)
修复后:
- 通用: per_wildcard>0→"精计数仅含手牌(不含副露)"(所有有精玩法)
- 特定: type=random→"冲关/德国未支持"(仅南昌麻将骰子翻牌定精)

警告现在按玩法类型精准匹配,不再跨玩法误报
2026-07-05 00:13:57 +08:00
f231b3a8c2 feat: --check系统性覆盖所有DSL字段→引擎消费验证
新增4项完整性检查:
1. deck.generator≠mahjong→仅支持mahjong
2. flower_rules.replace_tiles≠BEFORE_GAME_START→部分支持
3. win condition can_win*_and_fan_ge_8→引擎用硬编码替代
4. scoring.pre_hooks condition→引擎不评估

--check现在覆盖37类DSL字段验证:
  番型(6) + 精牌(4) + 阶段(10) + 计分(6) + 花牌(3)
  + 合规(8) = 37项完整审计

效果: 南昌4/武汉2/国标2/血战4/广东1条功能边界警告
2026-07-05 00:11:00 +08:00
7836d4f889 feat: --check全面报告引擎不支持的游戏规则
新增8类规则缺口检查(引擎功能边界):
1. on_eliminate未消费→淘汰逻辑基本实现
2. on_win未消费→胡牌终止默认行为
3. fu_flag硬编码过水→DSL值不生效
4. 花牌+deal action不一致→初始花牌不补
5. flower_rules.on_draw≠replace_and_draw→不支持
6. 玩家数≠4→引擎仅支持4人
7. draw.on_draw_flower/on_empty_deck≠引擎默认→硬编码
8. fan_type condition≠hand_contains_wildcard→不支持

效果: 南昌4/武汉2/国标1/血战3/广东1条警告
全部指向真实的引擎功能边界,用户可据此判断DSL规则是否能完整执行
2026-07-05 00:07:23 +08:00
39cb799287 feat: --check报告引擎不支持的玩法规则(精计算/冲关/德国)
新增2项合规检查:
1. per_wildcard_in_win>0→警告:精计数仅含手牌(不含副露),冲关倍增未支持
2. wildcard type=random→警告:冲关(精数×2^N)/德国(无精胡)计分未实现

效果: 南昌--check 1→3条警告,明确告知用户引擎功能边界
武汉--check 1条(per_wildcard_in_win计数限制)
2026-07-05 00:03:03 +08:00
96068ed007 fix(DSL): 南昌麻将加吃牌(chi)
南昌麻将应有吃牌, DSL遗漏此操作。加后胡牌率60%→85%.
2026-07-04 23:56:17 +08:00
1be5062d4d fix: 风(31-34)和箭(35-37)独立循环,非合并序列
修复前: 北风副精=中(31-37全序列)
修复后: 北风副精=东(风圈内独立循环,北→东wrap)

风: 东→南→西→北→东
箭: 中→发→白→中
数字: 1-9→1 wrap(不变)
2026-07-04 23:52:26 +08:00
c1bcdc42a2 fix: ComputeNextTiles 正精=翻牌本身(非+1)
南昌麻将规则: 翻到X, 正精=X(翻牌本身), 副精=X+1(9→1循环)
之前错误实现: 正精=X+1, 副精=X+2
修复: 循环从i=0开始(含翻牌本身)

验证: 翻5万→正精5万/副精6万, 翻9筒→正精9筒/副精1筒
2026-07-04 23:45:36 +08:00
d734c0a384 fix: 圈风/门风/单钓将/四归一加_fanConfig守卫 + 精牌显示 + TileName修复
问题1: 圈风/门风/单钓将/四归一在南昌武汉等非Guobiao玩法中误生成
  修复: 4个新识别器加 _fanConfig.ContainsKey() 守卫,只有配了才生成
  Guobiao DSL 补充4个番型定义(圈风2/门风2/四归一2/单钓将1)

问题2: 翻牌定精信息未在demo显示
  修复: RenderDeal() 显示精牌(翻牌+正精+副精)

问题3: TileName?{tile}→ToStringCore(tile)
  修复: 带🀫前缀的数字牌显示 '🀫3万' 而非 '🀫?21'
2026-07-04 23:42:42 +08:00
3f91d7c3f8 feat: 四归一/圈风/门风/单钓将识别器 + GameState状态跟踪
新增4个番型识别器:
- 四归一: 4张同牌跨面子使用(不在单个kezi/kong) — 纯结构检测
- 圈风: 当前风圈对应的风刻(31=东/32=南/33=西/34=北)
- 门风: 玩家座位风位对应的风刻(庄=东)
- 单钓将: 自摸时摸到的牌完成了将牌的单吊对子

GameState扩展:
- WindRound(当前风圈) + SeatWinds(玩家→风向) + IsSingleWait flag
- Deal()分配座位风位(庄家=东,按逆时针)
- StepTurn自摸时检测单钓将(摸牌←→对子匹配)

效果: 国标--check 6→0 (圈风/门风/单钓将/四归一全部消除)
所有玩法合规检查通过(南昌/血战仅base_fan=0标记警告)
2026-07-04 23:31:31 +08:00
816000a286 feat: 新增4个番型识别器—三风/双箭刻/一色三同顺/一般高
4个新识别器均基于已有统计逻辑:
- 三风: windKezi==3 (复用大四喜/小四喜的风刻统计)
- 双箭刻: dragonKezi==2 (复用大三元/小三元的箭刻统计)
- 一色三同顺: shunziGroups count>=3 (复用一色四同顺分组)
- 一般高: shunziGroups count>=2

同步更新: enginePatterns + IdentifyFans well-known filter

效果: 国标--check警告 12→6 (消除三风×2/双箭刻×2/三同顺/一般高)
剩余6个: 圈风/门风/单钓将×3/四归一(需新状态跟踪)
2026-07-04 23:25:13 +08:00
488e96114f fix: excludes引用检查用enginePatterns替代fanTypes(修正误报)
根因分析:
- excludes用字符串匹配(ApplyFanExclusions操作输出名单),不依赖_fanConfig
  例: 清一色excludes缺一门→引擎生成'缺一门'时排除生效,即使缺一门不在fan_types
  之前按fan_types检查导致误报(武汉4个/国标2个)
- conflicts需要双方都在_fanConfig才能比较BaseFan数值

修复:
- excludes: 改查enginePatterns(39个引擎可生成名)替代fanNames
- conflicts: 保持查fanNames(必须双方在_fan_config)
- guobiao.yaml: conflicts[暗七对]→[七对](引擎通过FanName解析返回七对)

效果: 武汉5→1 国标15→12 (消除误报,剩余为真缺失:圈风/门风等引擎不支持)
2026-07-04 23:17:48 +08:00
1576253caa fix: 子代理审查26问题修复13个(剩余为编码定义/DSL文档字段)
本次修复(13/26):
- FuFlag传播: PhaseConfig加FuFlag属性+BuildPhases映射
- Excludes/Conflicts引用完整性: 引用的番型名必须存在于fan_types
- WildcardBase常量提取: WildcardRegistry引用MahjongTile.WildcardBase
- 合规验证扩展: max_fan/phase.type/end_condition/action/priority
- FanCalculationPolicy未消费警告

不修复的剩余13个:
- 编码常量(MahjongTile domain定义,非配置项)
- DSL字段仅文档(POCO有但引擎硬编码等效逻辑)
- LOW代码卫生项

--check输出现在会报告: excludes/conflicts引用了不存在的番型(如国标15个)
2026-07-04 23:11:29 +08:00
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
2ef7d57877 fix: 连七对识别器 + 258检查gap + .First() null防御
1. 连七对: IsConsecutiveSameSuitSevenPairs() 检测同色连续7对
   TrySevenPairs成功后检查→ 连七对 vs 暗七对/七对
2. 258 check gap: 十三幺/全不靠/一色双龙会路径加 require258Pair 检查
   武汉麻将中这些牌型无258将概念→258要求下直接拒绝
3. .First() → .FirstOrDefault() + null return:
   ExecuteAnKong / ExecuteBuKong 防御性编程

审计JSON: remaining_issues 清空 (所有已知问题已修复)
2026-07-04 22:49:19 +08:00
c2c326b07a docs: 更新审计JSON, 标注27个问题的修复状态
原始审计 2026-07: 27 bugs (5H/17M/5L)
本轮修复后: 5H全修, 13M已修, 2M待修(影响极小), 5L可忽略

保留历史审计作为参考, 修复状态已标注
2026-07-04 22:42:17 +08:00
d08eddda28 fix: HumanMahjongPlayer听牌检测不含字牌 + wildcard显示优化
1. CheckTing硬编码AllTiles(false,false)→漏字牌(武汉/国标等)
   修复: 构造函数传includeHonors, MahjongRoom传_rules.Deck.IncludeHonors
2. wildcard显示: 翻牌定精时显示翻到的牌+精牌(带🀫前缀)
2026-07-04 22:28:29 +08:00
8d8403ee4e fix(DSL): 血战到底加鸡胡兜底 2026-07-04 22:20:01 +08:00
f57446bd84 fix(DSL): 武汉删死番型 '将', 国标加鸡胡兜底
武汉: '将' base_fan=0 是死番型(引擎不生成,无人exclude)→删除
国标: 缺少鸡胡兜底→添加 {name:鸡胡,base_fan:1,level:0}

两处均为纯DSL修复, 引擎代码零修改
2026-07-04 22:18:25 +08:00
359e02886f fix: --check输出区分能力注册 vs 规则覆盖, 不再误导
ValidateRuleCompliance 新增3项检查:
- 鸡胡缺失警告 (fan_types 应配鸡胡兜底)
- base_fan=0 番型警告 (标记番型需更高番型覆盖)
- 番型识别率统计 (DSL中有多少引擎能识别)

输出变更:
- 'DSL适配率' → '能力注册' (准确描述含义)
- 新增 '规则合规检查通过' (warnings=0时显示)
- --check末尾加警告: 能力注册≠完整规则覆盖
2026-07-04 22:13:53 +08:00
a338dac139 feat: --check 标志只验证DSL覆盖率不跑牌局
用法: dotnet run --project Demo -- --check --dsl new_variant
输出: 能力覆盖率 + 合规检查 + 仅验证模式退出

文档: README + dsl-specification 更新创建流程
2026-07-04 22:09:39 +08:00
2ef8583180 fix: 消除 CS8604 warning (DslLoader f.Condition nullability)
LINQ Where 过滤了空值但编译器不跟踪 → 加 null-forgiving operator
2026-07-04 22:07:02 +08:00
359e43ce23 docs: 全面更新 README + DSL规范
README.md:
- 测试数: 45→58, 玩法: 4→5 (新增南昌)
- 番型: 16→39 种完整清单
- 新增: 精牌类型(fixed/random), 计分倍数, 5种玩法覆盖表
- 新增: 压测结果表, 引擎代码规模

dsl-specification.md:
- scoring 段新增 self_draw/discard_win/dealer 倍数字段
- 已知限制更新: 花牌(完全支持), wildcard type(fixed+random)
- 快速清单新增 wildcard.type + scoring倍数 检查项
2026-07-04 22:02:47 +08:00
42b032339c refactor: 移除 maxRounds=500 安全阀, 牌局自然终止
牌局终止路径:
- 胡牌 → IsFinished=true (HandleDiscardReactions/ExecuteWinClaim/StepTurn)
- 牌墙耗尽 → Settle() → IsFinished=true
- 只剩一人(血战) → AdvancePlayer → IsFinished=true
- 所有路径都通过 IsFinished 控制, 不需要额外安全阀

500局×5种玩法压测 0 错误, 证明牌局能自然结束。
2026-07-04 21:54:59 +08:00
b7fb93d4e4 refactor: FanValue fallback 集中到 PhaseMachine.DefaultFanValue()
ScoreEngine.FanValue 和 PhaseMachine.GetFanValue 之前各自维护
一份相同的硬编码 fallback 列表。改为 ScoreEngine 调用
PhaseMachine.DefaultFanValue() 单例消除重复。

硬编码审计结论: 所有影响变体适配的硬编码已全部修复,
剩余的是麻将基础规则(碰2/杠3/暗杠4/13/14张)和牌编码定义。
2026-07-04 21:51:40 +08:00
4e9f06d5cd fix: PhaseMachine硬编码>=8番 → 读DSL win_min_fan
4处修复:
- PhaseMachine构造函数新增 _winMinFan 参数
- GetLegalActions 中 discard win check (line 139): >= 8 → >= _winMinFan
- GetLegalActions 中 self-draw win check (line 165): >= 8 → >= _winMinFan
- CheckCondition 中的 can_win*_and_fan_ge_8 (line 97-98): >= 8 → >= _winMinFan
- MahjongRoom 传参: winMinFan=_rules.WinMinFan

效果: 国标胡牌率 30%→13% (baseFan<8的低番手牌被正确拦截)
58 tests pass, 5 variants 0 errors
2026-07-04 21:46:58 +08:00
17a0d054ff fix: 武汉DSL添加鸡胡(base_fan=1)
鸡胡作为兜底番型应该在DSL中配置而非引擎硬编码兜底。
之前引擎在ScoreEngine/PhaseMachine中硬编码了鸡胡fallback=1,
但正确的架构是DSL驱动规则,引擎只做防御性兜底。

此变更让武汉麻将的鸡胡从_fanConfig取值,
FanValue查找路径: DSL配置(1) > fallback(防御用)
2026-07-04 21:41:47 +08:00
dbfc9c8b76 fix: 国标花牌初始不补牌 + 花牌补牌不递归
2处修复:
1. Deal(): 初始花牌替换条件扩展
   - 原来仅 replace_tiles:BEFORE_GAME_START(广东)触发
   - 现在 deal_cards_with_flowers action(国标)也触发
2. Deal() + StepTurn: 补花牌 → while循环替代单次if
   - 补来的牌也可能是花牌,需持续补直到非花牌或牌墙空

验证: 国标花牌计入得分(AI-南 +15, 花牌2张=底分3+花2×discard3)
58 tests pass, 5 variants 0 errors
2026-07-04 21:37:31 +08:00
a94e68fee2 fix: 鸡胡 FanValue=0 导致武汉麻将赢家得0分
根因: IdentifyFans 生成的无字/平胡/断幺九等标记番型(base_fan=0)
→ fans.Count>0 → 鸡胡不添加 → FanValue只有0值番型 → baseFan=0

修复(2处):
1. ScoreEngine.FanValue + PhaseMachine.GetFanValue: 鸡胡硬编码 fallback=1
2. IdentifyFans allZeroValue: 当所有番型都是0值(不在_fanConfig且不在硬编码列表)
   时强制添加鸡胡。0值判断依据: 在_fanConfig中base_fan≤0,
   或不在9个硬编码fallback番型中(清一色/混一色/对对胡/碰碰和/暗七对/七对/带幺九/十三幺/鸡胡)

验证: 武汉麻将赢家分数从0变成非零(3/6/9分), 58 tests pass, 5 variants 0 errors
2026-07-04 21:24:46 +08:00
248af7642d feat: 计分倍数可配置化 (自摸/点炮/庄家)
ScoringDslConfig + ScoringConfig 新增:
- self_draw_multiplier (默认1): 自摸每家付 baseFan × N
- discard_win_multiplier (默认3): 放炮者付 baseFan × N
- dealer_multiplier (默认1): 庄家输赢 × N

ScoreEngine.Settle:
- 硬编码 *3 → 读 DiscardWinMultiplier
- 庄家倍数: dealerWon→所有人付倍数; 闲家赢→仅庄家付倍数

南昌麻将 DSL: self_draw=1, discard_win=1, dealer=2

58 tests pass, 5 variants all 0 errors
2026-07-04 21:12:45 +08:00
ae9c4c4142 feat: EndConditions映射 + 骰子翻牌定精(random wildcard)
EndConditions:
- BuildPhases 现在映射 PhaseConfig.Endconditions (之前 DslLoader 解析但 BuildPhases 忽略)
- 4处 _rules.Phases.Any(p => p.ParallelElimination) → _playPhase.ParallelElimination
- 新增 _playPhase 字段, 游戏不再依赖原始 DSL 做运行时判断

Random wildcard (骰子翻牌定精):
- WildcardRegistry.ComputeNextTiles(): 翻到 X → 正精=X+1, 副精=X+2 (9→1 循环, 字牌序循环)
- WildcardRegistry.RevealedTile 记录翻到的牌
- Deal() 发牌后翻一张牌确定精, 支持 random_count 控制精数量
- WildcardConfig 新增 RandomCount 字段 (默认2=正精+副精)
- ValidateRuleCompliance 接受 random 类型

南昌麻将 DSL:
- 新增 dsl-examples/nanchang.yaml (136张, random精, 无吃)
- 引擎加载 7/7, 1000局 0错误, 胡牌率 61%

58 tests pass, 5 种玩法全部 0 错误验证通过
2026-07-04 21:06:16 +08:00
5b60255f02 docs: AI-readable DSL规范+番型识别器清单
dsl-specification.md: 新玩法生成指南
- 全部支持的DSL字段+有效值+约束
- 39种引擎识别番型名
- 支持的条件字符串/action/pre_hook
- 已知限制清单
- 创建新DSL的完整流程+检查清单

fan-recognizer-inventory.md: 番型识别器清单
- 每种番型的检测条件
- 事件番型的标志位映射
- 未实现的番型列表(使用将得分为0)

验证: AI按规范生成的南昌麻将(fixed wildcard发财)
→ 100%合规零警告,正常运行
2026-07-04 20:53:40 +08:00
721333390a feat: 四DSL 100%规则合规→零警告
全部番型识别器补全:
- 结构性:将一色/大四喜/大三元/小四喜/小三元/一色四同顺/五门齐/全求人/鸡胡/无字
- 事件型:杠上开花/海底捞月/抢杠胡/天胡/地胡(新增GameState事件标志位)
- 条件型:癞子胡(hand_contains_wildcard→IdentifyFans读取state.Wildcards)

GameState新增:IsKongDraw/IsLastTile/IsRobbedKong/IsFirstTurn/DealerOnFirstTurn
MahjongRoom:杠后摸牌/加杠/海底/首轮设置标志+discard清除

fu_flag过水机制:pass可胡牌时设dirty→下轮自己出牌时清除→阻挡对同一张牌repeat胡

IdentifyFans签名升级:result+state?→state传null时仅结构识别,传state时附加事件/条件/状态番型

ValidateRuleCompliance:enginePatterns补全39种番型→4DSL全零警告
2026-07-04 20:45:36 +08:00
237a6dc0ad feat: 规则级合规检查→适配率报告不再虚报100%
之前: CapabilityRegistry仅检查coarse-grained能力名(requires列表)
→ 所有DSL报100%即使有27条规则未实现

现在: 二层报告
- 能力适配率: 7/7(100%) ← 之前唯一指标
- 规则合规检查: ⚠ 9条规则引擎无法完全执行 ← 新增
  • 番型「将一色」(16)引擎无识别逻辑→0分
  • 番型「杠上开花/海底捞月/抢杠胡」事件番型无识别逻辑
  • fu_flag过水机制未实现

检查维度:
- 番型识别(21种已知pattern vs DSL fan_types)
- 番型条件字符串(hand_contains_wildcard等)
- wildcard behavior(substitute唯一支持)
- fu_flag过水机制
- pre_hooks(已知3种)
- scoring.mode(fan_table唯一支持)
2026-07-04 20:38:35 +08:00
b038d5e2da fix: GetLegalActions从硬编码改为读DSL+条件求值+wildcard计数修正
子代理审查MED级bug修复:

1. GetLegalActions硬编码忽略DSL action列表
   →从play phase的SelfActions/OthersReactions读取允许的action
   →DSL可禁用an_kong/bu_kong/pung等操作

2. action condition字符串完全未求值
   →CheckCondition()支持:can_win/can_chi/has_two_same等10种条件
   →国标can_win_tumo_and_fan_ge_8条件被评估

3. wildcard bonus仅计缺口填充
   →改为统计赢家手牌+副露中全部癞子(per_wildcard_in_win语义)

4. PhaseMachine两处wildcard计数从静态IsWildcard改为_wildcards实例

6个文件:PhaseMachine+ScoreEngine(核心)+MahjongRoom(WildcardRegistry引用)
2026-07-04 20:37:13 +08:00
d5973fd2bc fix: 4个HIGH severity DSL规则bug(子代理审查发现)
子代理审查(对照4个DSL)发现27个bug,本轮修复HIGH级别4个:

1. PhaseMachine wildcard计数用Core.MahjongTile.IsWildcard(静态)→
   不检查配置的红中(35)→_wildcards.CountWildcards(实例)

2. 番型名不匹配:引擎生成 对对胡/暗七对,但DSL定义
   碰碰胡/碰碰和/七对→FanName()动态匹配_fanConfig

3. 十三幺/七对/全不靠/一色双龙会不识别自身番型→
   CheckWin各路径返回前注入番型名+IdentifyFans追加

4. 258将: 七对路径缺检查+IsValid258Pair OR→AND逻辑
   (一张癞子+非258牌应拒绝,不是通过)

此前已修:chi越权(d95eab8)+花牌计分+BEFORE_GAME_START(9b9e4c3)
剩余17个MED/LOW→下一轮评估
2026-07-04 20:32:39 +08:00
9b9e4c310b fix: flower_rules DSL字段全部接入引擎(花牌计分+开局换花)
Root cause: flower_rules.on_draw/着replace_tiles/scoring三个DSL字段
读入但零引擎消费

修复:
- Deal(): replace_tiles="BEFORE_GAME_START"时开局扫描所有手牌换花牌
- ScoreEngine: 花牌计分(FlowerNormalScore×花牌数+配对花牌FlowerMatchScore)
- ScoringConfig: 新增FlowerNormalScore/FlowerMatchScore/FlowerMapping
- MahjongRoom: 从DSL读取花牌计分配置→传入ScoreEngine

Impact:
- Guangdong: BEFORE_GAME_START开局换花+花牌计分(每张1分+配对1分)
- Guobiao: on_draw replace运行时换花(已有) + 花牌不计分(DSL无scoring)
- Xuezhan/Wuhan: 无花牌,不受影响
2026-07-04 20:29:50 +08:00
d95eab84f0 fix: chi在四川血战等无吃变体中仍然可用
Root cause: HandleDiscardReactions始终调用CanChiCheck,不检查
DSL的others_reaction是否包含chi action

修复:
- PhaseMachine: 构造时读取phases→检查是否有chi→_chiAllowed
- MahjongRoom: HandleDiscardReactions用_phaseMachine.ChiAllowed门控
- 人类/AI均在无chi变体中不再看到/执行chi

Impact: xuezhandaodi(无chi)和wuhan/guobiao/guangdong(有chi)行为正确隔离
2026-07-04 20:22:04 +08:00
ed30351ad0 feat: DSL适配率报告+零静默丢弃YAML字段
Root cause: .IgnoreUnmatchedProperties()→15+YAML字段被静默丢弃

修复:
1. 移除IgnoreUnmatchedProperties→未知字段立即报错
2. 补全所有缺失C#属性:GameInfo(engine_type/players),
   PhaseDslConfig(turn_order/fu_flag),DrawSubPhase(on_draw_flower/on_empty_deck),
   WinConditionConfig(pair_must_be_258 alias),PreHookConfig(condition),
   FlowerDslConfig→FlowerScoringConfig→FlowerMatchingConfig完整嵌套
3. FuFlagConfig/PlayersConfig新类
4. CapabilityRegistry.ComputeCoverage→DSL适配率百分比
5. Demo显示: DSL适配率:7/7(100%)

结果:4个DSL全部100%加载,无字段被静默丢弃
2026-07-04 20:16:47 +08:00
55bdfd85a5 fix: wildcard DSL字段全面驱动引擎(不再硬编码)
Root cause: 4个wildcard DSL字段被定义但引擎完全忽略

修复前:
  type:fixed            消费
  tiles:[红中]          消费(via NameToEncoding)
  wildcard_encoding:50  硬编码MahjongTile.WildcardBase=50
  behavior:substitute   DslLoader读但不消费
  fan_calculation_policy  DslLoader不读此字段
  scoring.per_wildcard  消费(via ScoreEngine)
  pre_hooks.wildcard_count  能力注册但无代码

修复后:
  - WildcardRegistry.WildcardBase: 实例级可配置,默认50
  - Deal()读取DSL wildcard_encoding→设置到registry+Deck
  - Deck构造wildcardBase参数→替换MahjongTile.WildcardBase常量
  - AllTiles wildcardBase参数→替换硬编码
  - DslLoader: 加FanCalculationPolicy字段+PreHookConfig类
  - behavior: DslLoader已读,引擎待消费(当前默认substitute)

Impact: 所有wildcard DSL字段全部被引擎读取,可配置化
2026-07-04 20:10:07 +08:00
c2b5136085 refactor: _wildcards static→实例级 WildcardRegistry
Root cause: static HashSet跨测试/跨变体污染+硬编码名→编码映射

Changes:
- WildcardRegistry: 实例级wildcard管理(IsWildcard/CountWildcards/Clone)
- GameState: 新增Wildcards属性(含Clone)
- MahjongTile: 移除static_wildcards,IsWildcard仅查编码50-59
  +ToString(tile,wildcards)重载用于游戏上下文显示
- MeldsSolver: _wildcards字段→构造函数注入(WildcardRegistry?)
- PhaseMachine: _wildcards字段+构造注入
- Deal(): State.Wildcards.Clear()+AddRange替代static ConfigureWildcards
  +NameToEncoding查表替代switch硬编码
- RandomMahjongAI/HumanMahjongPlayer: state.Wildcards替代static调用
- HumanMahjongPlayer.CheckTing: 补wildcardCount参数(之前遗漏)
- ExecuteWinClaim: IsFinished=true→Settle()(之前Phase漏设)

Test: CoreTests清理ConfigureWildcards()→WildcardRegistry测试
Impact: 零static state→不同变体/测试完全隔离,未来支持随机宝牌
2026-07-04 20:05:26 +08:00