diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-20 15:14:23 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-20 15:14:23 +0800 |
| commit | 9724663602b4b1eb755bf334c475dc2c80e4b386 (patch) | |
| tree | 681bfcc9f7bb6422ed80142702cfff1498f064b1 /src/LivingVillage.Kernel.Tests | |
| parent | 7e197babfbb68d7f7d1866d6a0ca47cf17d3af23 (diff) | |
| download | living-village-9724663602b4b1eb755bf334c475dc2c80e4b386.tar.gz | |
feat(m6a): add save speed and day-night controls
Diffstat (limited to 'src/LivingVillage.Kernel.Tests')
| -rw-r--r-- | src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj | 1 | ||||
| -rw-r--r-- | src/LivingVillage.Kernel.Tests/M6aTests.fs | 99 |
2 files changed, 100 insertions, 0 deletions
diff --git a/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj b/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj index 2a3ba3c..b30fc8f 100644 --- a/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj +++ b/src/LivingVillage.Kernel.Tests/LivingVillage.Kernel.Tests.fsproj @@ -11,6 +11,7 @@ <Compile Include="RumorTests.fs" /> <Compile Include="TradeTests.fs" /> <Compile Include="M5Tests.fs" /> + <Compile Include="M6aTests.fs" /> </ItemGroup> <ItemGroup> diff --git a/src/LivingVillage.Kernel.Tests/M6aTests.fs b/src/LivingVillage.Kernel.Tests/M6aTests.fs new file mode 100644 index 0000000..8a318be --- /dev/null +++ b/src/LivingVillage.Kernel.Tests/M6aTests.fs @@ -0,0 +1,99 @@ +namespace LivingVillage.Kernel.Tests + +open Microsoft.VisualStudio.TestTools.UnitTesting +open LivingVillage.Kernel +open LivingVillage.Kernel.Sim + +module private M6aHarness = + + let zeroInput = { Input = { MoveX = 0.0f; MoveY = 0.0f } } + + let atNpc (id: int) (world: World) : World = + let npc = world.Npcs.[id] + { world with Avatar = { world.Avatar with Pos = npc.Pos } } + + let readyForChat (world: World) : World = + { world with + Npcs = + world.Npcs + |> Array.map (fun npc -> + { npc with + Mind = + { npc.Mind with + Action = Wander + Target = actionTarget Wander + ActionAge = minActionTicks + EffectDone = true } }) } + + let withDialogue (world: World) : World = + match Sim.chooseDialogue (NpcId 0) SmallTalk (atNpc 0 world) with + | DialogueSucceeded (_, next) -> next + | DialogueRejected (failure, _) -> Assert.Fail($"dialogue rejected: {failure}"); Unchecked.defaultof<World> + + let withChat (world: World) : World = + match Sim.chat { Narrator = NpcId 0; Receiver = NpcId 1 } (readyForChat world) with + | ChatSucceeded (Some _, next) -> next + | ChatSucceeded (None, _) -> Assert.Fail("chat should create a rumor"); Unchecked.defaultof<World> + | ChatRejected (failure, _) -> Assert.Fail($"chat rejected: {failure}"); Unchecked.defaultof<World> + + let withTrade (world: World) : World = + match Sim.trade { Buyer = NpcId 0; Seller = NpcId 1; Item = Food; Quantity = 1 } world with + | TradeSucceeded next -> next + | TradeRejected (failure, _) -> Assert.Fail($"trade rejected: {failure}"); Unchecked.defaultof<World> + + let advance (inputs: Input list) (world: World) : World = + inputs |> List.fold (fun current input -> Sim.step { Input = input } current) world + +[<TestClass>] +type M6aTests () = + + [<TestMethod>] + member _.SaveLoadPreservesWorldAndDeterministicContinuation () = + let world = + Sim.initialWorldN 7UL 4 + |> M6aHarness.withDialogue + |> M6aHarness.withChat + |> M6aHarness.withTrade + let encoded = WorldSave.save world + + match WorldSave.load encoded with + | Error failure -> Assert.Fail($"save should load: {failure}") + | Ok restored -> + Assert.AreEqual<string>(encoded, WorldSave.save restored) + let inputs = + [ { MoveX = 0.5f; MoveY = -0.25f } + { MoveX = 0.0f; MoveY = 0.0f } + { MoveX = -1.0f; MoveY = 0.75f } + { MoveX = 0.25f; MoveY = 0.0f } ] + let continued = M6aHarness.advance inputs world + let restoredContinued = M6aHarness.advance inputs restored + Assert.AreEqual<string>(WorldSave.save continued, WorldSave.save restoredContinued) + Assert.AreEqual<int64>(world.Tick, restored.Tick) + Assert.AreEqual<uint64>(world.Rng.State, restored.Rng.State) + Assert.IsFalse(restored.Events.IsEmpty) + Assert.IsFalse(restored.Rumors.IsEmpty) + Assert.IsFalse(restored.Annals.IsEmpty) + Assert.AreEqual<Avatar>(world.Avatar, restored.Avatar) + CollectionAssert.AreEqual(world.Npcs, restored.Npcs) + + [<TestMethod>] + member _.SimulationClockSupportsPauseAndRequestedSpeeds () = + let initial = SimulationControl.initial + Assert.AreEqual<int>(1, SimulationControl.stepsPerFrame initial) + Assert.AreEqual<int>(2, SimulationControl.stepsPerFrame (SimulationControl.setSpeed TwoX initial)) + Assert.AreEqual<int>(5, SimulationControl.stepsPerFrame (SimulationControl.setSpeed FiveX initial)) + let paused = SimulationControl.togglePause initial + Assert.IsTrue(paused.Paused) + Assert.AreEqual<int>(0, SimulationControl.stepsPerFrame paused) + let resumed = SimulationControl.togglePause paused + Assert.IsFalse(resumed.Paused) + Assert.AreEqual<SimulationSpeed>(OneX, resumed.Speed) + + [<TestMethod>] + member _.ConfiguredLegacyStepCountsRemainExactUntilManualSpeedSelection () = + let control, legacySteps = SimulationControl.initialForConfiguredSteps 7 + Assert.AreEqual<int option>(Some 7, legacySteps) + Assert.AreEqual<int>(7, SimulationControl.stepsPerFrameWithLegacy legacySteps control) + Assert.AreEqual<int>(0, SimulationControl.stepsPerFrameWithLegacy legacySteps (SimulationControl.togglePause control)) + let manuallySelected = SimulationControl.setSpeed TwoX control + Assert.AreEqual<int>(2, SimulationControl.stepsPerFrameWithLegacy None manuallySelected) |
