feat(persona): 人格系统(YAML loader / 角色人格注册+记忆隔离 / 情绪状态机+好感度)

This commit is contained in:
Aska committed 2026-09-06 04:36:31 +08:00
1 parent 653c2489bf
commit 70fd915242
11 files changed
+1568

No files matched your search

+144
View File
@@ -0,0 +1,144 @@
package emotion
import "testing"
func TestDefaultTransitions_StandardEdges(t *testing.T) {
t.Parallel()
d := DefaultTransitions()
if _, ok := d[Calm]; !ok {
t.Errorf("DefaultTransitions missing %q", Calm)
}
if _, ok := d[Angry]; !ok {
t.Errorf("DefaultTransitions missing %q", Angry)
}
}
func TestNewMachine_DefaultTransitionsWhenEmpty(t *testing.T) {
t.Parallel()
m, err := NewMachine(Params{InitialState: Excited})
if err != nil {
t.Fatalf("NewMachine() error: %v", err)
}
if got := m.Current(); got != Excited {
t.Fatalf("Current() = %q, want %q", got, Excited)
}
if !m.CanTransition(Happy) {
t.Errorf("excited should be able to reach happy by default")
}
if m.CanTransition(Sad) {
t.Errorf("excited must not reach sad by default")
}
}
func TestNewMachine_RejectsNegativeDecay(t *testing.T) {
t.Parallel()
if _, err := NewMachine(Params{InitialState: Calm, DecayRate: -1}); err == nil {
t.Fatal("NewMachine() should error on negative decay rate")
}
}
func TestMachine_TransitionFollowsTable(t *testing.T) {
t.Parallel()
tr := map[EmotionState][]EmotionState{
Calm: {Happy, Sad, Excited},
Happy: {Excited, Calm},
Sad: {Calm},
Angry: {Calm},
}
tests := []struct {
name string
initial EmotionState
to EmotionState
wantErr bool
}{
{name: "ok_calm_to_happy", initial: Calm, to: Happy},
{name: "ok_calm_to_excited", initial: Calm, to: Excited},
{name: "ok_happy_to_excited", initial: Happy, to: Excited},
{name: "reject_calm_to_angry", initial: Calm, to: Angry, wantErr: true},
{name: "reject_happy_to_sad", initial: Happy, to: Sad, wantErr: true},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
m, err := NewMachine(Params{InitialState: tt.initial, Transitions: tr})
if err != nil {
t.Fatalf("NewMachine() error: %v", err)
}
err = m.Transition(tt.to)
if tt.wantErr && err == nil {
t.Fatalf("Transition(%q) want error, got nil", tt.to)
}
if !tt.wantErr && err != nil {
t.Fatalf("Transition(%q) want nil, got %v", tt.to, err)
}
if !tt.wantErr && m.Current() != tt.to {
t.Fatalf("Current() = %q, want %q", m.Current(), tt.to)
}
})
}
}
func TestMachine_SetForce(t *testing.T) {
t.Parallel()
m, err := NewMachine(Params{InitialState: Calm})
if err != nil {
t.Fatalf("NewMachine() error: %v", err)
}
m.Set(Angry)
if got := m.Current(); got != Angry {
t.Fatalf("Current() = %q, want %q", got, Angry)
}
}
func TestMachine_AddTransitionExtends(t *testing.T) {
t.Parallel()
tr := map[EmotionState][]EmotionState{Calm: {Happy}}
m, err := NewMachine(Params{InitialState: Calm, Transitions: tr})
if err != nil {
t.Fatalf("NewMachine() error: %v", err)
}
if m.CanTransition(Angry) {
t.Fatalf("calm must not reach angry before AddTransition")
}
m.AddTransition(Calm, Angry)
if !m.CanTransition(Angry) {
t.Errorf("calm should reach angry after AddTransition")
}
}
func TestMachine_DecayMovesTowardCalm(t *testing.T) {
t.Parallel()
m, err := NewMachine(Params{InitialState: Excited, DecayRate: 1})
if err != nil {
t.Fatalf("NewMachine() error: %v", err)
}
if got := m.Decay(); got != Happy {
t.Fatalf("Decay() from excited = %q, want %q", got, Happy)
}
if got := m.Decay(); got != Calm {
t.Fatalf("Decay() from happy = %q, want %q", got, Calm)
}
if got := m.Decay(); got != Calm {
t.Fatalf("Decay() from calm = %q, want %q", got, Calm)
}
}
func TestMachine_DecayDisabledAtZeroRate(t *testing.T) {
t.Parallel()
m, err := NewMachine(Params{InitialState: Angry, DecayRate: 0})
if err != nil {
t.Fatalf("NewMachine() error: %v", err)
}
if got := m.Decay(); got != Angry {
t.Fatalf("Decay() at zero rate = %q, want %q", got, Angry)
}
}