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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
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 } }
[<TestClass>]
type DesktopTests () =
[<TestMethod>]
member _.InteractOpensTheSixIntentMenu () =
let world = Sim.initialWorldN 42UL 4 |> Harness.atNpc 0
let next, view = M5Interaction.apply Interact world M5Interaction.initial
Assert.AreEqual<World>(world, next)
Assert.AreEqual<M5Panel>(DialoguePanel, view.Panel)
Assert.AreEqual<int>(6, view.Menu.Value.Options.Length)
Assert.IsTrue(M5Interaction.titleText view |> fun text -> text.Contains("闲聊"))
[<TestMethod>]
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"))
[<TestMethod>]
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<Needs>(afterStep.Avatar.Mind.Needs, needsView.Needs.Value.Needs)
Assert.IsTrue(observationView.Observations |> List.exists (fun observation -> observation.Id = NpcId 0))
[<TestMethod>]
member _.HeadlessSmokeProvesTheThreeHopCausalChain () =
let result = Program.M5Smoke.run ()
Assert.IsTrue(result.Passed, result.Failure)
Assert.AreEqual<int>(6, result.MenuOptions)
Assert.AreEqual<int>(4, result.RumorPathLength)
Assert.IsTrue(result.Chronicle.Contains("dialogue"))
Assert.IsTrue(result.RumorTrace.Contains("receiver=-1"))
Assert.IsTrue(result.RumorTrace.Contains("depth=3"))
[<TestMethod>]
member _.DayNightStateChangesTheRenderProfile () =
let day = M6Presentation.profileAtTick (6L * Sim.ticksPerDay / 24L)
let night = M6Presentation.profileAtTick (22L * Sim.ticksPerDay / 24L)
Assert.AreEqual<M6DayNight>(Day, day.Mode)
Assert.AreEqual<M6DayNight>(Night, night.Mode)
Assert.AreNotEqual(day.Background, night.Background)
Assert.AreNotEqual(day.WorldTint, night.WorldTint)
[<TestMethod>]
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<int>(6, avatarTile.X)
Assert.AreEqual<int>(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>(world, repeated)
[<TestMethod>]
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)
[<TestMethod>]
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<int64>(dawnTick, daylightWorld.Tick)
Assert.IsTrue(abs (daylightWorld.Time - float dawnTick * Sim.dtSeconds) < 1e-9)
Assert.AreEqual<M6DayNight>(Day, daylightProfile.Mode)
Assert.AreEqual<int64>(0L, defaultWorld.Tick)
Assert.AreEqual<float>(0.0, defaultWorld.Time)
Assert.AreEqual<M6DayNight>(Night, defaultProfile.Mode)
|