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个)
This commit is contained in:
xiaoou
2026-07-04 23:11:29 +08:00
parent 712116bca8
commit 1576253caa
5 changed files with 62 additions and 3 deletions

View File

@ -5,7 +5,7 @@ public class MahjongDeck
public List<int> Tiles { get; private set; } = new();
public MahjongDeck(bool includeHonors = false, bool includeFlowers = false, int wildcardCount = 0,
int? expectedTotal = null, int wildcardBase = 50)
int? expectedTotal = null, int wildcardBase = 50) // default matches MahjongTile.WildcardBase
{
var allTiles = MahjongTile.AllTiles(includeHonors, includeFlowers, wildcardCount);
foreach (var t in allTiles)

View File

@ -20,8 +20,8 @@ public class WildcardRegistry
/// <summary>The tile revealed from deck to determine random wildcards (骰子翻牌定精).</summary>
public int? RevealedTile { get; set; }
public WildcardRegistry() { _wildcardBase = 50; }
public WildcardRegistry(int wildcardBase) { _wildcardBase = wildcardBase > 0 ? wildcardBase : 50; }
public WildcardRegistry() { _wildcardBase = MahjongTile.WildcardBase; }
public WildcardRegistry(int wildcardBase) { _wildcardBase = wildcardBase > 0 ? wildcardBase : MahjongTile.WildcardBase; }
/// <summary>Non-extra wildcard tiles — normal tiles (e.g. 红中=35) that ALSO act as wildcards.</summary>
public IReadOnlySet<int> ConfiguredWildcards => _wildcards;

View File

@ -132,6 +132,62 @@ public class CapabilityRegistry
if (recognized < rules.FanTypes.Count)
warnings.Add($"番型识别率: {recognized}/{rules.FanTypes.Count}→{rules.FanTypes.Count - recognized} 个番型得分为0");
// Excludes/Conflicts 引用完整性: 引用的番型名必须存在于 fan_types
var fanNames = rules.FanTypes.Select(f => f.Name).ToHashSet();
foreach (var f in rules.FanTypes)
{
if (f.Excludes != null)
foreach (var excluded in f.Excludes)
if (!fanNames.Contains(excluded))
warnings.Add($"番型「{f.Name}」excludes 引用了不存在的番型「{excluded}」→排除规则失效");
if (f.Conflicts != null)
foreach (var conflict in f.Conflicts)
if (!fanNames.Contains(conflict))
warnings.Add($"番型「{f.Name}」conflicts 引用了不存在的番型「{conflict}」→冲突规则失效");
}
// max_fan 合理性
if (rules.MaxFan < 0)
warnings.Add($"max_fan={rules.MaxFan} 非法→应为正数或0");
// Phase type 验证
var validPhaseTypes = new HashSet<string> { "auto", "mahjong_turn" };
foreach (var p in rules.Phases)
if (!validPhaseTypes.Contains(p.Type))
warnings.Add($"phase「{p.Name}」type={p.Type} 非法→仅支持 auto/mahjong_turn");
// EndCondition type 验证
var validEndTypes = new HashSet<string> { "player_wins", "deck_exhausted", "last_one_standing" };
foreach (var p in rules.Phases)
if (p.EndConditions != null)
foreach (var ec in p.EndConditions)
if (!validEndTypes.Contains(ec.Type))
warnings.Add($"phase「{p.Name}」end_condition type={ec.Type} 非法→仅支持 player_wins/deck_exhausted/last_one_standing");
// Action 名验证
var validActions = new HashSet<string> { "discard", "an_kong", "bu_kong", "win", "chi", "pung", "ming_kong", "pass" };
foreach (var p in rules.Phases)
{
var allActions = new List<ActionOptionDsl>();
if (p.SubPhases?.SelfAction?.Options != null) allActions.AddRange(p.SubPhases.SelfAction.Options);
if (p.SubPhases?.OthersReaction?.Options != null) allActions.AddRange(p.SubPhases.OthersReaction.Options);
foreach (var a in allActions)
if (!validActions.Contains(a.Action))
warnings.Add($"phase「{p.Name}」action={a.Action} 非法→引擎忽略");
}
// PriorityPolicy 验证
var validPolicies = new HashSet<string> { "highest_wins" };
foreach (var p in rules.Phases)
if (p.SubPhases?.OthersReaction?.PriorityPolicy != null
&& !validPolicies.Contains(p.SubPhases.OthersReaction.PriorityPolicy))
warnings.Add($"priority_policy={p.SubPhases.OthersReaction.PriorityPolicy} 非法→仅支持 highest_wins");
// FanCalculationPolicy 未消费警告
if (rules.WildcardRules != null && !string.IsNullOrEmpty(rules.WildcardRules.FanCalculationPolicy)
&& rules.WildcardRules.FanCalculationPolicy != "optimal")
warnings.Add($"wildcard_rules.fan_calculation_policy={rules.WildcardRules.FanCalculationPolicy} 引擎未实现→仅optimal生效");
return warnings;
}
}

View File

@ -102,6 +102,7 @@ public class MahjongRoom
TurnOrder = p.TurnOrder ?? "counter_clockwise",
ParallelElimination = p.ParallelElimination,
OnEliminate = p.OnEliminate,
FuFlag = p.FuFlag,
SelfActions = p.SubPhases?.SelfAction?.Options?.Select(o => new ActionOption
{
Action = o.Action, Priority = o.Priority, Condition = o.Condition

View File

@ -1,6 +1,7 @@
namespace RuleEngine.Phase;
using RuleEngine.Core;
using RuleEngine.Dsl;
using RuleEngine.Patterns;
public class PhaseConfig
@ -18,6 +19,7 @@ public class PhaseConfig
public List<EndCondition> EndConditions { get; set; } = new();
public bool ParallelElimination { get; set; }
public string? OnEliminate { get; set; }
public FuFlagConfig? FuFlag { get; set; }
public int MaxFan { get; set; } = int.MaxValue;
}