feat(persona): 人格系统(YAML loader / 角色人格注册+记忆隔离 / 情绪状态机+好感度)
This commit is contained in:
1 parent
653c2489bf
commit
70fd915242
11 files changed
+1568
No files matched your search
@@ -0,0 +1,147 @@
|
||||
package emotion
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func mustMachine(t *testing.T, s EmotionState) *Machine {
|
||||
t.Helper()
|
||||
m, err := NewMachine(Params{InitialState: s})
|
||||
if err != nil {
|
||||
t.Fatalf("NewMachine(%q) error: %v", s, err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func TestManager_EmotionFollowsPersona(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mg := NewManager()
|
||||
a, err := NewAffection(50, 0, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAffection() error: %v", err)
|
||||
}
|
||||
mg.Set("personaA", mustMachine(t, Excited), a)
|
||||
mg.Set("personaB", mustMachine(t, Calm), nil)
|
||||
|
||||
// Transition personaA; personaB must be untouched.
|
||||
if err := mg.Transition("personaA", Happy); err != nil {
|
||||
t.Fatalf("Transition(personaA, happy) error: %v", err)
|
||||
}
|
||||
mA, _, ok := mg.Get("personaA")
|
||||
if !ok {
|
||||
t.Fatal("personaA not found")
|
||||
}
|
||||
if mA.Current() != Happy {
|
||||
t.Fatalf("personaA Current() = %q, want %q", mA.Current(), Happy)
|
||||
}
|
||||
mB, _, _ := mg.Get("personaB")
|
||||
if mB.Current() != Calm {
|
||||
t.Fatalf("personaB Current() = %q, want %q (isolation)", mB.Current(), Calm)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManager_AdjustAffectionIsolated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mg := NewManager()
|
||||
affA, err := NewAffection(40, 0, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAffection() error: %v", err)
|
||||
}
|
||||
affB, err := NewAffection(10, 0, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAffection() error: %v", err)
|
||||
}
|
||||
mg.Set("personaA", mustMachine(t, Calm), affA)
|
||||
mg.Set("personaB", mustMachine(t, Calm), affB)
|
||||
|
||||
before, after, err := mg.AdjustAffection("personaA", 30)
|
||||
if err != nil {
|
||||
t.Fatalf("AdjustAffection(personaA) error: %v", err)
|
||||
}
|
||||
if before != 40 || after != 70 {
|
||||
t.Fatalf("AdjustAffection(personaA) before/after = %d/%d, want 40/70", before, after)
|
||||
}
|
||||
if got := affB.Value(); got != 10 {
|
||||
t.Fatalf("personaB value = %d, want 10 (isolation)", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManager_TransitionUnknownPersona(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mg := NewManager()
|
||||
mg.Set("personaA", mustMachine(t, Calm), nil)
|
||||
err := mg.Transition("ghost", Happy)
|
||||
if !errors.Is(err, ErrUnknownPersona) {
|
||||
t.Fatalf("Transition(ghost) error = %v, want ErrUnknownPersona", err)
|
||||
}
|
||||
if _, _, err := mg.AdjustAffection("ghost", 1); !errors.Is(err, ErrUnknownPersona) {
|
||||
t.Fatalf("AdjustAffection(ghost) error = %v, want ErrUnknownPersona", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManager_SetGetRemoveCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mg := NewManager()
|
||||
mg.Set("a", mustMachine(t, Calm), nil)
|
||||
mg.Set("b", mustMachine(t, Happy), nil)
|
||||
if got := mg.Count(); got != 2 {
|
||||
t.Fatalf("Count() = %d, want 2", got)
|
||||
}
|
||||
if _, _, ok := mg.Get("a"); !ok {
|
||||
t.Fatal("Get(a) not found")
|
||||
}
|
||||
if !mg.Remove("a") {
|
||||
t.Fatal("Remove(a) should report true")
|
||||
}
|
||||
if mg.Remove("a") {
|
||||
t.Fatal("Remove(a) again should report false")
|
||||
}
|
||||
if got := mg.Count(); got != 1 {
|
||||
t.Fatalf("Count() after remove = %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManager_ConcurrentAccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mg := NewManager()
|
||||
const n = 8
|
||||
for i := 0; i < n; i++ {
|
||||
id := idOf(i)
|
||||
aff, _ := NewAffection(50, 0, 100)
|
||||
mg.Set(id, mustMachine(t, Excited), aff)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < n; i++ {
|
||||
id := idOf(i)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, _, _ = mg.Get(id)
|
||||
_, _, _ = mg.AdjustAffection(id, 5)
|
||||
_ = mg.Transition(id, Happy)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
id := idOf(i)
|
||||
m, _, ok := mg.Get(id)
|
||||
if !ok {
|
||||
t.Fatalf("persona %s missing after concurrent access", id)
|
||||
}
|
||||
if m.Current() != Happy {
|
||||
t.Fatalf("persona %s Current() = %q, want %q", id, m.Current(), Happy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func idOf(i int) string { return fmt.Sprintf("persona%d", i) }
|
||||
Reference in new issue
Block a user