This commit is contained in:
2026-08-22 00:59:46 +02:00
parent 9250528442
commit a2f0bcb371
6 changed files with 292 additions and 6 deletions
+114
View File
@@ -135,6 +135,7 @@ namespace AnotherReplayReader.Utils
if (factIndex is not null)
{
ValidateTimelineConsistency(claims, factIndex, aiNameToPlayerIndex, structuredKnowledge, issues);
ValidateSimpleClaimEvidence(claims, factIndex, issues);
}
return new AIValidationResult(claims, issues.ToImmutable());
}
@@ -711,6 +712,119 @@ namespace AnotherReplayReader.Utils
}
}
/// <summary>事件/时间线声明只有 claim + evidence,没有 player,因此只校验不依赖归属的事实。</summary>
private static void ValidateSimpleClaimEvidence(
AIMachineReadableClaims claims,
ReplayFactIndex factIndex,
ImmutableArray<AIValidationIssue>.Builder issues)
{
foreach (var claim in claims.EventClaims)
{
ValidateSimpleClaim(claim.Claim, claim.Evidence, "事件声明", factIndex, issues);
}
foreach (var claim in claims.TimelineClaims)
{
ValidateSimpleClaim(claim.Claim, claim.Evidence, "时间线声明", factIndex, issues);
}
}
private static void ValidateSimpleClaim(
string claimText,
ImmutableArray<string> evidenceStrings,
string kind,
ReplayFactIndex factIndex,
ImmutableArray<AIValidationIssue>.Builder issues)
{
foreach (var ev in ParseAllEvidence(evidenceStrings))
{
if (ev.Type == AIEvidenceType.Protocol)
{
var techName = ev.GetTechName();
if (string.IsNullOrWhiteSpace(techName))
{
continue;
}
var allTechChoices = new HashSet<string>(
factIndex.PlayerTechChoices.Values.SelectMany(set => set),
StringComparer.OrdinalIgnoreCase);
if (allTechChoices.Count > 0 && !allTechChoices.Contains(techName))
{
issues.Add(new AIValidationIssue(
AIValidationSeverity.Warning,
AIValidationIssueKind.InvalidMachineReadableClaims,
$"{kind}“{claimText}”引用了未在任何玩家选择中观察到的协议“{techName}”。"));
}
continue;
}
if (ev.Type == AIEvidenceType.Power)
{
var unitIdText = ev.GetUnitId();
if (unitIdText is null || !uint.TryParse(unitIdText, out var unitId))
{
continue;
}
var powerName = ev.GetSpecialPowerName();
if (string.IsNullOrWhiteSpace(powerName))
{
continue;
}
if (!factIndex.UnitIdFirstObservedTime.ContainsKey(unitId))
{
issues.Add(new AIValidationIssue(
AIValidationSeverity.Warning,
AIValidationIssueKind.InvalidMachineReadableClaims,
$"{kind}“{claimText}”的证据引用了回放中不存在的 UnitId {unitIdText}。",
unitIdText));
continue;
}
if (factIndex.UnitIdSpecialPowers.TryGetValue(unitId, out var actualPowers))
{
if (!actualPowers.Contains(powerName))
{
issues.Add(new AIValidationIssue(
AIValidationSeverity.Contradiction,
AIValidationIssueKind.UnitCapabilityContradiction,
$"{kind}“{claimText}”声称 UnitId {unitIdText} 使用了“{powerName}”,但该 UnitId 在回放中使用过:{string.Join("", actualPowers.OrderBy(x => x))}。",
unitIdText));
}
}
else
{
issues.Add(new AIValidationIssue(
AIValidationSeverity.WeakEvidence,
AIValidationIssueKind.WeakEvidence,
$"{kind}“{claimText}”声称 UnitId {unitIdText} 使用了“{powerName}”,但该 UnitId 未观察到任何特殊能力。",
unitIdText));
}
continue;
}
if (ev.Type is AIEvidenceType.Build
or AIEvidenceType.Place
or AIEvidenceType.Produce
or AIEvidenceType.Select
or AIEvidenceType.Sell)
{
var unitIdText = ev.GetUnitId();
if (unitIdText is null || !uint.TryParse(unitIdText, out var unitId))
{
continue;
}
if (!factIndex.UnitIdFirstObservedTime.ContainsKey(unitId))
{
issues.Add(new AIValidationIssue(
AIValidationSeverity.Warning,
AIValidationIssueKind.InvalidMachineReadableClaims,
$"{kind}“{claimText}”的证据引用了回放中不存在的 UnitId {unitIdText}。",
unitIdText));
}
}
}
}
private static void ValidateOwnership(
AIUnitClaim claim,
uint unitId,