962837c9275ee915c507b27e208291658374386a

Author
TheEdgeOfRage <git@theedgeofrage.com>
Committer
TheEdgeOfRage <git@theedgeofrage.com>
Date

Message

Extract applyAndSpeak helper to deduplicate post-reply pipeline

Diff

 1diff --git a/internal/game/orchestrator.go b/internal/game/orchestrator.go
 2index a0222dde8221ee36489477110916fccac86be3e1..cd77b9a3f7cfd885d6486a63465edfa08e365ade 100644
 3--- a/internal/game/orchestrator.go
 4+++ b/internal/game/orchestrator.go
 5@@ -176,15 +176,8 @@ func (o *Orchestrator) advance(ctx context.Context, displayAction string) TurnRe
 6 		return res
 7 	}
 8 	res.Reply = reply
 9-	o.applyReplyAndCompact(ctx, reply)
10-	shown, desc := o.handleNewPartner(ctx, reply)
11-
12-	if hasSpeech(shown) {
13-		o.lastKana = shown.Kana
14-		if err := o.speechOut.Speak(ctx, shown.Kana); err != nil {
15-			res.SpeakErr = err
16-		}
17-	}
18+	shown, desc, speakErr := o.applyAndSpeak(ctx, reply)
19+	res.SpeakErr = speakErr
20 	o.state.RecordDisplay(displayFromTurn(displayAction, shown, desc, llm.JudgeResult{}, false))
21 	return res
22 }
23@@ -255,15 +248,8 @@ func (o *Orchestrator) FinishSpeak(ctx context.Context) TurnResult {
24 	res.Reply = reply
25 	res.HasJudge = true
26 	res.JudgeErr = jerr
27-	o.applyReplyAndCompact(ctx, reply)
28-	shown, desc := o.handleNewPartner(ctx, reply)
29-
30-	if hasSpeech(shown) {
31-		o.lastKana = shown.Kana
32-		if err := o.speechOut.Speak(ctx, shown.Kana); err != nil {
33-			res.SpeakErr = err
34-		}
35-	}
36+	shown, desc, speakErr := o.applyAndSpeak(ctx, reply)
37+	res.SpeakErr = speakErr
38 	judge := llm.JudgeResult{}
39 	if jerr == nil {
40 		judge = jres
41@@ -272,6 +258,20 @@ func (o *Orchestrator) FinishSpeak(ctx context.Context) TurnResult {
42 	return res
43 }
44 
45+// applyAndSpeak runs the shared post-reply pipeline: record the reply in state,
46+// handle any new partner, and play speech if present. Returns the shown reply
47+// (after potential new-partner rewrite), its description, and any speech error.
48+func (o *Orchestrator) applyAndSpeak(ctx context.Context, reply llm.GameReply) (llm.GameReply, string, error) {
49+	o.applyReplyAndCompact(ctx, reply)
50+	shown, desc := o.handleNewPartner(ctx, reply)
51+	var speakErr error
52+	if hasSpeech(shown) {
53+		o.lastKana = shown.Kana
54+		speakErr = o.speechOut.Speak(ctx, shown.Kana)
55+	}
56+	return shown, desc, speakErr
57+}
58+
59 // applyReplyAndCompact records the reply in the session and, when it moved the
60 // player, compacts the closed segment into a summary. Compaction failures fall
61 // back to a minimal placeholder so state always stays consistent.