1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
namespace LivingVillage.Kernel.Tests
open Microsoft.VisualStudio.TestTools.UnitTesting
open LivingVillage.Kernel
open LivingVillage.Kernel.Sim
module RelationHarness =
let zeroInput = { Input = { MoveX = 0.0f; MoveY = 0.0f } }
let runN (seed: uint64) (count: int) (ticks: int64) : World =
let mutable w = Sim.initialWorldN seed count
for _ in 1L .. ticks do
w <- Sim.step zeroInput w
w
let handWorld (tick: int64) (mem0: MemoryEvent list) (mem1: MemoryEvent list) : World =
let needs = { Hunger = 100.0f; Energy = 100.0f; Social = 100.0f; Money = 50.0f }
let mkNpc id mem =
{ Id = NpcId id
Pos = { X = 0.0f; Y = 0.0f }
Mind =
{ Needs = needs
Personality = personalityOfSeed 1UL
Action = Wander
Target = actionTarget Wander
ActionAge = 0L
EffectDone = false
HungerFlagged = false
Memory = mem } }
{ Tick = tick
Time = 0.0
Rng = Rng.ofSeed 1UL
Avatar = { Pos = { X = 0.0f; Y = 0.0f } }
NoHost = { Reserved = 0UL }
Npcs = [| mkNpc 0 mem0; mkNpc 1 mem1 |]
Events = [] }
let chatted (tick: int64) (partner: int) : MemoryEvent =
{ Tick = tick; Kind = Chatted(NpcId partner); Valence = Sim.chatValence }
let matricesEqual (a: float32[,]) (b: float32[,]) : bool =
Array2D.length1 a = Array2D.length1 b
&& Array2D.length2 a = Array2D.length2 b
&& (let mutable eq = true
for i in 0 .. Array2D.length1 a - 1 do
for j in 0 .. Array2D.length2 a - 1 do
if a.[i, j] <> b.[i, j] then eq <- false
eq)
[<TestClass>]
type RelationTests () =
[<TestMethod>]
member _.RelationMatrixSameSeedIsByteIdentical () =
let wa = RelationHarness.runN 42UL 30 600000L
let wb = RelationHarness.runN 42UL 30 600000L
let ma = Sim.relationMatrix wa
let mb = Sim.relationMatrix wb
if wa.Tick <> wb.Tick then Assert.Fail("ticks must match")
Assert.IsTrue(RelationHarness.matricesEqual ma mb, "same seed must yield byte-identical relation matrix")
[<TestMethod>]
member _.RelationDecayWeightNewExceedsOld () =
let now = 10L * Sim.relationHalfLifeTicks
let fresh = Sim.relationDecayWeight now now
let old = Sim.relationDecayWeight now (now - 2L * Sim.relationHalfLifeTicks)
let older = Sim.relationDecayWeight now (now - 4L * Sim.relationHalfLifeTicks)
if fresh <> 1.0f then Assert.Fail("weight of an event at now must be 1")
if old >= fresh then Assert.Fail("older valence must weigh less than newer valence")
if older >= old then Assert.Fail("weight must strictly decrease with age")
[<TestMethod>]
member _.RelationMatrixMatchesHandComputedDecay () =
// 单次闲聊记忆(tick=0, valence=0.3),now = 1 个半衰期 → 双向累加 2 * 0.3 * e^-1
let w = RelationHarness.handWorld Sim.relationHalfLifeTicks
[ RelationHarness.chatted 0L 1 ] [ RelationHarness.chatted 0L 0 ]
let m = Sim.relationMatrix w
let expected = 2.0f * Sim.chatValence * float32 (System.Math.Exp(-1.0))
if abs (m.[0, 1] - expected) > 1e-6f then Assert.Fail($"m[0,1]={m.[0, 1]} expected={expected}")
if abs (m.[1, 0] - expected) > 1e-6f then Assert.Fail($"m[1,0]={m.[1, 0]} expected={expected}")
if m.[0, 0] <> 0.0f then Assert.Fail("diagonal must be zero")
if m.[1, 1] <> 0.0f then Assert.Fail("diagonal must be zero")
[<TestMethod>]
member _.RelationMatrixPrefersRecentChats () =
let now = 4L * Sim.relationHalfLifeTicks
let freshWorld =
RelationHarness.handWorld now
[ RelationHarness.chatted now 1 ] [ RelationHarness.chatted now 0 ]
let staleWorld =
RelationHarness.handWorld now
[ RelationHarness.chatted (now - 4L * Sim.relationHalfLifeTicks) 1 ]
[ RelationHarness.chatted (now - 4L * Sim.relationHalfLifeTicks) 0 ]
let mf = Sim.relationMatrix freshWorld
let ms = Sim.relationMatrix staleWorld
if mf.[0, 1] <= ms.[0, 1] then Assert.Fail("recent chat memory must outweigh stale chat memory")
if ms.[0, 1] >= Sim.relationThreshold then Assert.Fail("stale single chat must stay below relation threshold")
[<TestMethod>]
member _.RelationCountsRespectThreshold () =
let now = 4L * Sim.relationHalfLifeTicks
// 双向各 1 次新闲聊 → 矩阵 2*0.3 = 0.6 > 0.5 → 有关系
let w = RelationHarness.handWorld now
[ RelationHarness.chatted now 1 ] [ RelationHarness.chatted now 0 ]
let m = Sim.relationMatrix w
let counts = Sim.relationCounts m
if counts.[0] <> 1 then Assert.Fail($"npc0 relation count {counts.[0]}, expected 1")
if counts.[1] <> 1 then Assert.Fail($"npc1 relation count {counts.[1]}, expected 1")
[<TestMethod>]
member _.RelationMatrixFromSimulationIsSymmetricNoNaN () =
let w = RelationHarness.runN 42UL 30 600000L
let m = Sim.relationMatrix w
let n = Array2D.length1 m
if n <> 30 then Assert.Fail($"expected 30 npcs, got {n}")
for i in 0 .. n - 1 do
if m.[i, i] <> 0.0f then Assert.Fail($"diagonal must be zero at {i}")
for j in 0 .. n - 1 do
if m.[i, j] <> m.[j, i] then Assert.Fail($"matrix must be symmetric at ({i},{j})")
if System.Single.IsNaN m.[i, j] then Assert.Fail($"NaN at ({i},{j})")
if System.Single.IsInfinity m.[i, j] then Assert.Fail($"infinite at ({i},{j})")
|