Simplify D&D combat turn extraction contract

This commit is contained in:
2026-07-22 19:14:14 +00:00
parent 2cbaf20e55
commit 90481a0e4b
36 changed files with 176 additions and 1070 deletions

View File

@@ -48,10 +48,10 @@ func TestProductionCombatPipelineRetriesMergesNormalizesAndWritesJSON(t *testing
}
client := &fakeCombatLLMClient{responses: []string{
combatTestInvalidEnumResponse("unsupported", "attack"),
combatTestTurnResponse("mira thorn", "turn", "watches", "mira thorn", 1, 1),
combatTestTurnResponse("Mira Thorn", "reaction", "asks", "Hooded Guard", 2, 2),
combatTestTurnResponse("Hooded Guard", "turn", "attacks", "mira thorn", 3, 3),
combatTestInvalidEnumResponse("unsupported"),
combatTestTurnResponse("mira thorn", "turn", 1),
combatTestTurnResponse("Mira Thorn", "reaction", 2),
combatTestTurnResponse("Hooded Guard", "turn", 3),
}}
prepared, err := pipeline.Prepare(materialized, registries, pipeline.ModuleDependencies{LLM: client})
if err != nil {
@@ -86,8 +86,8 @@ func TestProductionCombatPipelineRetriesMergesNormalizesAndWritesJSON(t *testing
if err != nil {
t.Fatalf("Decode(combat output) error = %v", err)
}
if len(value.CombatTurns) != 3 || value.CombatTurns[0].Actor != "Mira Thorn" || value.CombatTurns[0].Actions[0].Targets[0] != "Mira Thorn" || value.CombatTurns[1].Actor != "Mira Thorn" || value.CombatTurns[2].Actor != "Hooded Guard" {
t.Fatalf("normalized combat output = %#v, want ordered canonical actors and targets", value)
if len(value.CombatTurns) != 3 || value.CombatTurns[0].Actor != "Mira Thorn" || value.CombatTurns[1].Actor != "Mira Thorn" || value.CombatTurns[2].Actor != "Hooded Guard" {
t.Fatalf("normalized combat output = %#v, want ordered canonical actors", value)
}
for _, turn := range value.CombatTurns {
for _, ref := range turn.SourceRefs {
@@ -96,8 +96,8 @@ func TestProductionCombatPipelineRetriesMergesNormalizesAndWritesJSON(t *testing
}
}
}
if !hasCombatWarning(output.Warnings, "combat_turn_not_near_source") || !hasCombatWarning(output.Warnings, combatnormalize.ReasonCodeActorCanonicalized) || !hasCombatWarning(output.Warnings, combatnormalize.ReasonCodeTargetCanonicalized) {
t.Fatalf("warnings = %#v, want relatedness and registry normalization warnings", output.Warnings)
if !hasCombatWarning(output.Warnings, combatnormalize.ReasonCodeActorCanonicalized) {
t.Fatalf("warnings = %#v, want registry normalization warning", output.Warnings)
}
if len(output.Manifest.References) != 2 {
t.Fatalf("manifest references = %#v, want separate extract and normalize provenance", output.Manifest.References)
@@ -140,9 +140,9 @@ func TestProductionCombatPipelineAttributesExhaustedInvalidEnumsToShapeValidatio
t.Fatalf("Resolve() error = %v, want nil", err)
}
client := &fakeCombatLLMClient{responses: []string{
combatTestInvalidEnumResponse("unsupported", "attack"),
combatTestInvalidEnumResponse("turn", "unsupported"),
combatTestInvalidEnumResponse("unsupported", "unsupported"),
combatTestInvalidEnumResponse("unsupported"),
combatTestInvalidEnumResponse("invalid"),
combatTestInvalidEnumResponse("unknown"),
}}
prepared, err := pipeline.Prepare(effective.ResolvedPipeline, registries, pipeline.ModuleDependencies{LLM: client})
if err != nil {
@@ -312,12 +312,12 @@ func (client *fakeCombatLLMClient) CompleteStructured(ctx context.Context, req c
return contracts.StructuredCompletionResponse{Content: content, Provider: "test", Model: "combat-fake"}, nil
}
func combatTestInvalidEnumResponse(turnKind, category string) string {
return fmt.Sprintf(`{"combat_turns":[{"actor":"Aria","turn_kind":%q,"round":1,"actions":[{"category":%q,"declaration":"watches","targets":["Mira"],"resolution":null}],"summary":"invalid candidate","source_refs":[{"start_unit_id":1,"end_unit_id":1}]}]}`, turnKind, category)
func combatTestInvalidEnumResponse(turnKind string) string {
return fmt.Sprintf(`{"combat_turns":[{"actor":"Aria","turn_kind":%q,"source_refs":[{"start_unit_id":1,"end_unit_id":1}]}]}`, turnKind)
}
func combatTestTurnResponse(actor, turnKind, declaration, target string, round, unit int) string {
return fmt.Sprintf(`{"combat_turns":[{"actor":%q,"turn_kind":%q,"round":%d,"actions":[{"category":"attack","declaration":%q,"targets":[%q],"resolution":"observed"}],"summary":%q,"source_refs":[{"start_unit_id":%d,"end_unit_id":%d}]}]}`, actor, turnKind, round, declaration, target, declaration, unit, unit)
func combatTestTurnResponse(actor, turnKind string, unit int) string {
return fmt.Sprintf(`{"combat_turns":[{"actor":%q,"turn_kind":%q,"source_refs":[{"start_unit_id":%d,"end_unit_id":%d}]}]}`, actor, turnKind, unit, unit)
}
func hasCombatWarning(warnings []contracts.Warning, reason string) bool {