namespace LivingVillage.Desktop.Tests open Microsoft.VisualStudio.TestTools.UnitTesting open LivingVillage.Kernel open LivingVillage.Kernel.Sim open LivingVillage.Desktop open LivingVillage.Desktop.VillagePresentation module private Harness = let atNpc (id: int) (world: World) : World = let npc = world.Npcs.[id] { world with Avatar = { world.Avatar with Pos = npc.Pos } } [] type DesktopTests () = [] member _.InteractOpensTheSixIntentMenu () = let world = Sim.initialWorldN 42UL 4 |> Harness.atNpc 0 let next, view = M5Interaction.apply Interact world M5Interaction.initial Assert.AreEqual(world, next) Assert.AreEqual(DialoguePanel, view.Panel) Assert.AreEqual(6, view.Menu.Value.Options.Length) Assert.IsTrue(M5Interaction.titleText view |> fun text -> text.Contains("闲聊")) [] member _.SelectingAnIntentUpdatesWorldAndChronicle () = let world = Sim.initialWorldN 42UL 4 |> Harness.atNpc 0 let openedWorld, opened = M5Interaction.apply Interact world M5Interaction.initial let next, view = M5Interaction.apply Intent1 openedWorld opened Assert.IsTrue(next.Events |> List.exists (fun event -> match event.Kind with | DialogueEvent _ -> true | _ -> false)) Assert.IsTrue(next.Annals |> List.exists (fun entry -> match entry.Kind with | DialogueAnnal _ -> true | _ -> false)) Assert.IsTrue(view.Chronicle.Contains("dialogue")) Assert.IsTrue(view.Status.Contains("dialogue")) [] member _.NeedsAndObservationPanelsUseKernelViews () = let world = Sim.initialWorldN 42UL 4 |> Harness.atNpc 0 let afterStep = Sim.step { Input = { MoveX = 0.0f; MoveY = 0.0f } } world let _, needsView = M5Interaction.apply ToggleNeeds afterStep M5Interaction.initial let _, observationView = M5Interaction.apply Observe world M5Interaction.initial Assert.AreEqual(afterStep.Avatar.Mind.Needs, needsView.Needs.Value.Needs) Assert.IsTrue(observationView.Observations |> List.exists (fun observation -> observation.Id = NpcId 0)) [] member _.DialogueAndOverlayViewsRejectWorldCommandsUntilClosed () = let world = Sim.initialWorldN 42UL 4 |> Harness.atNpc 0 let openedWorld, dialogueView = M5Interaction.apply Interact world M5Interaction.initial let _, afterObserve = M5Interaction.apply Observe openedWorld dialogueView let _, afterNeeds = M5Interaction.apply ToggleNeeds openedWorld dialogueView let _, afterChronicle = M5Interaction.apply ShowChronicle openedWorld dialogueView let _, needsView = M5Interaction.apply ToggleNeeds world M5Interaction.initial let _, interactionAttempt = M5Interaction.apply Interact world needsView let _, closedView = M5Interaction.apply ClosePanel world dialogueView Assert.AreEqual(dialogueView, afterObserve) Assert.AreEqual(dialogueView, afterNeeds) Assert.AreEqual(dialogueView, afterChronicle) Assert.AreEqual(NeedsPanel, interactionAttempt.Panel) Assert.AreEqual(WorldPanel, closedView.Panel) [] member _.HeadlessSmokeProvesTheThreeHopCausalChain () = let result = Program.M5Smoke.run () Assert.IsTrue(result.Passed, result.Failure) Assert.AreEqual(6, result.MenuOptions) Assert.AreEqual(4, result.RumorPathLength) Assert.IsTrue(result.Chronicle.Contains("dialogue")) Assert.IsTrue(result.RumorTrace.Contains("receiver=-1")) Assert.IsTrue(result.RumorTrace.Contains("depth=3")) [] member _.DayNightStateChangesTheRenderProfile () = let day = M6Presentation.profileAtTick (6L * Sim.ticksPerDay / 24L) let night = M6Presentation.profileAtTick (22L * Sim.ticksPerDay / 24L) Assert.AreEqual(Day, day.Mode) Assert.AreEqual(Night, night.Mode) Assert.AreNotEqual(day.Background, night.Background) Assert.AreNotEqual(day.WorldTint, night.WorldTint) [] member _.DesktopBootstrapMovesAvatarOffTheHomeDoorAndSeparatesVillagers () = let world = WorldBootstrap.initialWorld 42UL Sim.npcCount let avatarPixel : PixelPosition = { X = world.Avatar.Pos.X Y = world.Avatar.Pos.Y } let avatarTile = sceneTileFromPixel avatarPixel let home = scene.Homes |> List.head let renderPlan = VillageArt.sampleRenderPlan () let blockedTiles = (renderPlan.Grounds |> List.map (fun (tile, _) -> VillageArt.worldTile renderPlan tile)) @ (renderPlan.Props |> List.map (fun prop -> VillageArt.worldTile renderPlan prop.Position)) |> List.map (fun tile -> tile.X, tile.Y) |> Set.ofList let sceneTileOf (position: Vec2) : TilePosition = sceneTileFromPixel { X = position.X Y = position.Y } let distanceSquared (first: Vec2) (second: Vec2) : float32 = let dx = second.X - first.X let dy = second.Y - first.Y dx * dx + dy * dy let npcTiles = world.Npcs |> Array.map (fun npc -> sceneTileOf npc.Pos) Assert.AreEqual(6, avatarTile.X) Assert.AreEqual(7, avatarTile.Y) Assert.IsFalse(isNearHomeDoor avatarTile home, "The avatar must start beyond the doorway interaction radius.") Assert.IsTrue( npcTiles |> Array.forall (fun tile -> not (Set.contains (tile.X, tile.Y) blockedTiles)), "A villager must not spawn on the house, water, bridge, or garden footprint.") Assert.IsTrue( world.Npcs |> Array.forall (fun npc -> distanceSquared world.Avatar.Pos npc.Pos >= 96.0f * 96.0f), "Villagers must not overlap the avatar spawn.") for first in 0 .. world.Npcs.Length - 2 do for second in first + 1 .. world.Npcs.Length - 1 do Assert.IsTrue( distanceSquared world.Npcs.[first].Pos world.Npcs.[second].Pos >= 96.0f * 96.0f, sprintf "villagers %d and %d are too close" first second) let repeated = WorldBootstrap.initialWorld 42UL Sim.npcCount Assert.AreEqual(world, repeated) [] member _.DaylightUsesAWarmHighContrastWorldTint () = let day = M6Presentation.profileAtTick (6L * Sim.ticksPerDay / 24L) Assert.IsTrue(day.WorldTint.R > day.WorldTint.G) Assert.IsTrue(day.WorldTint.G > day.WorldTint.B) [] member _.DaylightAutoplayBootstrapStartsAtDawnAndPreservesDefaultStart () = let dawnTick = 6L * Sim.ticksPerDay / 24L let daylightWorld = WorldBootstrap.initialWorldForDaylight true 42UL Sim.npcCount let defaultWorld = WorldBootstrap.initialWorldForDaylight false 42UL Sim.npcCount let daylightProfile = M6Presentation.profileAtTick daylightWorld.Tick let defaultProfile = M6Presentation.profileAtTick defaultWorld.Tick Assert.AreEqual(dawnTick, daylightWorld.Tick) Assert.IsTrue(abs (daylightWorld.Time - float dawnTick * Sim.dtSeconds) < 1e-9) Assert.AreEqual(Day, daylightProfile.Mode) Assert.AreEqual(0L, defaultWorld.Tick) Assert.AreEqual(0.0, defaultWorld.Time) Assert.AreEqual(Night, defaultProfile.Mode)