summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Kernel.Tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Kernel.Tests')
-rw-r--r--src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj1
-rw-r--r--src/LivingVillage.Kernel.Tests/RelationTests.fs122
2 files changed, 123 insertions, 0 deletions
diff --git a/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj b/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj
index d1d3c19..65e50db 100644
--- a/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj
+++ b/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj
@@ -7,6 +7,7 @@
<ItemGroup>
<Compile Include="DeterminismTests.fs" />
+ <Compile Include="RelationTests.fs" />
</ItemGroup>
<ItemGroup>
diff --git a/src/LivingVillage.Kernel.Tests/RelationTests.fs b/src/LivingVillage.Kernel.Tests/RelationTests.fs
new file mode 100644
index 0000000..f1d9673
--- /dev/null
+++ b/src/LivingVillage.Kernel.Tests/RelationTests.fs
@@ -0,0 +1,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})")