Per-crow banter voice system: three-layer prompt (memory, voice, line)

This commit is contained in:
Smokey committed 2026-10-27 23:07:33 -06:00
1 parent 017d11cd99
commit fbf9fe67a6
6 files changed
+235 -3

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
package main
import (
"context"
"strings"
"testing"
"time"
)
func TestVoicedPromptHasThreeLayers(t *testing.T) {
p, _ := personaOf("Hugin")
s := voicedBanterSystem(p, voices["Hugin"])
for _, want := range []string{
markerBanter,
"You are Hugin, Thought",
"MEMORY:", "LINE:", // layer 1
"Favor:", "Rhythm:", "Never: Hedging", "Never: System words", // layer 2
"word for word", "Hand the turn back: end with a dare", // layer 3
} {
if !strings.Contains(s, want) {
t.Errorf("voiced prompt missing %q", want)
}
}
if !strings.HasPrefix(s, markerBanter) {
t.Error("voiced prompt must start with the banter marker")
}
}
func TestParseVoicedDropsMemoryAndKeepsLine(t *testing.T) {
got, ok := parseVoiced("MEMORY: I flew that road once.\nLINE: \"Slow as ever, Munin. Race me?\"", "Hugin")
if !ok || got != "Slow as ever, Munin. Race me?" {
t.Fatalf("got %q %v", got, ok)
}
if got, ok := parseVoiced("MEMORY: x\nLINE:\nRace me, Munin.", "Hugin"); !ok || got != "Race me, Munin." {
t.Fatalf("line on next row: got %q %v", got, ok)
}
// A memory with no line must never reach the chat.
if got, ok := parseVoiced("MEMORY: I flew that road once.", "Hugin"); ok {
t.Fatalf("leaked memory: %q", got)
}
// A reply that ignored the format (or came from an unrelated model) is dropped.
for _, bad := range []string{"Race me, Munin.", "User Safety: safe"} {
if got, ok := parseVoiced(bad, "Hugin"); ok {
t.Fatalf("accepted unlabeled reply %q", got)
}
}
// The line is still cleaned and validated.
if _, ok := parseVoiced("MEMORY: x\nLINE: see https://example.com", "Hugin"); ok {
t.Fatal("accepted a link")
}
}
func TestVoicedCrowSpeaksThroughMemoryAndLine(t *testing.T) {
b := &promptBrain{reply: "MEMORY: Odin feared for him less. Still stings.\nLINE: Back by breakfast, Munin. Try to keep up."}
em := &recordEmitter{}
c := newChatter(b, em, &flockMemory{})
c.moment(context.Background(), "L-1", "Munin", "Hugin", "The human approved Munin's answer.")
c.wait(5 * time.Second)
if len(em.events) != 1 {
t.Fatalf("got %d lines", len(em.events))
}
if got := em.events[0]["text"]; got != "Back by breakfast, Munin. Try to keep up." {
t.Fatalf("memory leaked or line wrong: %q", got)
}
if !strings.Contains(b.prompts[0], "MEMORY:") || !strings.Contains(b.prompts[0], "Odin's two ravens") {
t.Fatalf("voiced prompt or retrieved lore missing:\n%s", b.prompts[0])
}
}
func TestUnvoicedCrowKeepsPlainBanter(t *testing.T) {
b := &promptBrain{reply: "Nice try."}
em := &recordEmitter{}
c := newChatter(b, em, &flockMemory{})
c.moment(context.Background(), "L-1", "Hugin", "Soot", "The human rejected Hugin's answer.")
c.wait(5 * time.Second)
if len(em.events) != 1 || em.events[0]["text"] != "Nice try." {
t.Fatalf("got %v", em.events)
}
if strings.Contains(b.prompts[0], "MEMORY:") {
t.Error("an unvoiced crow must not get the two-step prompt")
}
}
func TestEveryVoiceBelongsToAPersonaAndIsComplete(t *testing.T) {
for name, v := range voices {
if _, ok := personaOf(name); !ok {
t.Errorf("voice %q has no persona", name)
}
if len(v.Diction) == 0 || len(v.Rhythm) == 0 || len(v.Never) == 0 || v.Hook == "" {
t.Errorf("voice %q is incomplete", name)
}
}
}