Polish combat scene validation

This commit is contained in:
2026-08-28 02:26:14 +00:00
parent 3ad34956c5
commit a2610757cd
6 changed files with 63 additions and 818 deletions

View File

@@ -196,13 +196,10 @@ func validateExplanation(value string) (string, error) {
if trimmed == "" {
return "", validatorErrorf("validator explanation must not be blank")
}
if trimmed != value {
return "", validatorErrorf("validator explanation must be trimmed")
}
if utf8.RuneCountInString(value) > maximumExplanationRunes {
if utf8.RuneCountInString(trimmed) > maximumExplanationRunes {
return "", validatorErrorf("validator explanation exceeds %d Unicode code points", maximumExplanationRunes)
}
return value, nil
return trimmed, nil
}
func Spec() pipeline.ValidatorSpec {

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"io"
"os"
"reflect"
"strings"
@@ -24,6 +25,7 @@ func TestValidatorInterpretsCombatSemanticsResponses(t *testing.T) {
approved bool
reasonCode string
guidance string
correction string
wantError string
}{
{name: "approve non-combat", kind: dnd.SceneKindNarrative, response: completionResponse{Verdict: "approved", Explanation: "No active encounter occurs."}, approved: true},
@@ -34,7 +36,7 @@ func TestValidatorInterpretsCombatSemanticsResponses(t *testing.T) {
{name: "inconsistent removed", kind: dnd.SceneKindNarrative, response: completionResponse{Verdict: "combat_should_be_removed", Explanation: "The group only plans."}, wantError: "inconsistent"},
{name: "unknown verdict", kind: dnd.SceneKindNarrative, response: completionResponse{Verdict: "uncertain", Explanation: "The group only plans."}, wantError: "unsupported"},
{name: "blank explanation", kind: dnd.SceneKindNarrative, response: completionResponse{Verdict: "approved", Explanation: " "}, wantError: "blank"},
{name: "untrimmed explanation", kind: dnd.SceneKindNarrative, response: completionResponse{Verdict: "approved", Explanation: " evidence"}, wantError: "trimmed"},
{name: "trim explanation", kind: dnd.SceneKindNarrative, response: completionResponse{Verdict: "combat_should_be_added", Explanation: " The combatants exchange attacks.\n"}, reasonCode: ReasonCodeActiveCombatNotClassified, correction: "Return kind: combat for this scene. The combatants exchange attacks."},
{name: "oversized explanation", kind: dnd.SceneKindNarrative, response: completionResponse{Verdict: "approved", Explanation: strings.Repeat("界", 513)}, wantError: "exceeds"},
} {
t.Run(test.name, func(t *testing.T) {
@@ -51,6 +53,9 @@ func TestValidatorInterpretsCombatSemanticsResponses(t *testing.T) {
if err != nil || result.Approved != test.approved || result.ReasonCode != test.reasonCode || !strings.Contains(result.CorrectionGuidance, test.guidance) {
t.Fatalf("Validate() = %#v, %v", result, err)
}
if test.correction != "" && result.CorrectionGuidance != test.correction {
t.Fatalf("CorrectionGuidance = %q, want %q", result.CorrectionGuidance, test.correction)
}
if !result.Approved && (strings.Contains(result.CorrectionGuidance, Key) || strings.Contains(result.CorrectionGuidance, result.ReasonCode) || strings.Contains(result.CorrectionGuidance, request.Chunk.ID)) {
t.Fatalf("correction guidance leaked opaque application detail: %q", result.CorrectionGuidance)
}
@@ -188,6 +193,9 @@ func TestEvaluationCasesAreStrictAndCoverVerdicts(t *testing.T) {
if err := decoder.Decode(&cases); err != nil {
t.Fatalf("decode evaluation cases: %v", err)
}
if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
t.Fatalf("decode trailing evaluation data: %v, want EOF", err)
}
seenNames, seenVerdicts := map[string]bool{}, map[string]bool{}
for _, value := range cases {
if strings.TrimSpace(value.Name) == "" || seenNames[value.Name] || len(value.TranscriptUnits) == 0 || strings.TrimSpace(value.ProposedKind) == "" || strings.TrimSpace(value.ReviewerRationale) == "" {