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
|
namespace LivingVillage.Desktop.Tests
open Microsoft.VisualStudio.TestTools.UnitTesting
open LivingVillage.Kernel
open LivingVillage.Desktop
// ---- P16 启动画面 / 开始界面 ----
[<TestClass>]
type P16LaunchTests () =
[<TestMethod>]
member _.LaunchRevealAndVeilAreDeterministicAndComplete () =
Assert.AreEqual<float32>(0.0f, LaunchScreen.revealProgress 0L)
Assert.AreEqual<float32>(1.0f, LaunchScreen.revealProgress LaunchScreen.fadeInFrames)
Assert.AreEqual<float32>(1.0f, LaunchScreen.revealProgress 1000L)
Assert.AreEqual<float32>(1.0f, LaunchScreen.veilAlpha 0L)
Assert.AreEqual<float32>(0.0f, LaunchScreen.veilAlpha LaunchScreen.durationFrames)
Assert.IsTrue(LaunchScreen.veilAlpha 30L > LaunchScreen.veilAlpha 90L, "veil must decrease monotonically")
Assert.IsFalse(LaunchScreen.isComplete (LaunchScreen.durationFrames - 1L))
Assert.IsTrue(LaunchScreen.isComplete LaunchScreen.durationFrames)
[<TestMethod>]
member _.LaunchSkipIsEscOrSpaceAndTextUsesTheAtlasPath () =
Assert.IsTrue(LaunchScreen.skipRequested true false)
Assert.IsTrue(LaunchScreen.skipRequested false true)
Assert.IsFalse(LaunchScreen.skipRequested false false)
Assert.IsTrue(PixelText.isRenderable LaunchScreen.title)
Assert.IsTrue(PixelText.isRenderable LaunchScreen.skipHint)
Assert.IsTrue(PixelText.isRenderable LaunchScreen.subtitle)
Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains LaunchScreen.skipHint)
[<TestClass>]
type P16StartMenuTests () =
[<TestMethod>]
member _.StartMenuIsExactlyThreeItemsWithChineseLabels () =
let state = MenuState.create SimulationControl.initial false
Assert.AreEqual<int>(3, MenuState.items state |> List.length)
Assert.AreEqual<string list>([ "开始"; "读档"; "退出" ], MenuState.items state |> List.map MenuState.itemLabel)
[<TestMethod>]
member _.StartMenuNavigationSelectsEachItemAndWraps () =
let state = MenuState.create SimulationControl.initial false
let down1 = MenuState.move Down state
let down2 = MenuState.move Down down1
let wrap = MenuState.move Down down2
Assert.AreEqual<MenuItem>(NewGameItem, MenuState.selectedItem state)
Assert.AreEqual<MenuItem>(LoadItem, MenuState.selectedItem down1)
Assert.AreEqual<MenuItem>(ExitItem, MenuState.selectedItem down2)
Assert.AreEqual<MenuItem>(NewGameItem, MenuState.selectedItem wrap)
Assert.AreEqual<MenuCommand>(StartNewGame, MenuState.update Confirm state |> snd)
Assert.AreEqual<MenuCommand>(LoadGame, MenuState.update Confirm down1 |> snd)
Assert.AreEqual<MenuCommand>(ExitGame, MenuState.update Confirm down2 |> snd)
[<TestMethod>]
member _.ReturningToMenuOffersContinueAsTheFirstOfThreeItems () =
let playing = MenuState.create SimulationControl.initial true |> MenuState.enterGame
let main = MenuState.openMain playing
Assert.AreEqual<MenuItem list>([ ContinueItem; LoadItem; ExitItem ], MenuState.items main)
Assert.AreEqual<string list>([ "继续"; "读档"; "退出" ], MenuState.items main |> List.map MenuState.itemLabel)
Assert.AreEqual<MenuCommand>(ContinueGame, MenuState.update Confirm main |> snd)
// ---- P16 HUD 顶部窄信息条 ----
[<TestClass>]
type P16HudTests () =
let world = Sim.initialWorldN 42UL 4
[<TestMethod>]
member _.HudBarIsANarrowStripNotAFullscreenBlock () =
Assert.IsTrue(HudLayout.barHeight <= 40, "HUD bar must stay a narrow strip")
Assert.IsTrue(HudLayout.barHeight * 4 < 720, "HUD bar must not dominate the viewport")
[<TestMethod>]
member _.HudSegmentsAreDeterministicAndUseRealState () =
let control = SimulationControl.initial
let segments = HudLayout.segments world control
Assert.AreEqual<string list>([ "时段"; "时间"; "倍速"; "体力"; "钱币" ], segments |> List.map (fun segment -> segment.Label))
Assert.AreEqual<HudLayout.Segment list>(segments, HudLayout.segments world control)
let energy = segments |> List.find (fun segment -> segment.Label = "体力")
Assert.AreEqual<string>(sprintf "%.0f" world.Avatar.Mind.Needs.Energy, energy.Value)
let money = segments |> List.find (fun segment -> segment.Label = "钱币")
Assert.AreEqual<string>(sprintf "%.0f" world.Avatar.Mind.Needs.Money, money.Value)
[<TestMethod>]
member _.HudLayoutPlacesSegmentsDeterministicallyWithinWidth () =
let placed = HudLayout.layout (HudLayout.segments world SimulationControl.initial)
Assert.AreEqual<int>(5, placed.Length)
let (firstX, _, _) = List.head placed
Assert.AreEqual<int>(HudLayout.paddingX, firstX)
placed
|> List.pairwise
|> List.iter (fun ((x1, w1, _), (x2, _, _)) ->
Assert.IsTrue(x2 >= x1 + w1, sprintf "segments overlap: %d+%d vs %d" x1 w1 x2))
let (lastX, lastWidth, _) = List.last placed
Assert.IsTrue(lastX + lastWidth <= 1280, "last segment must fit inside a 1280 viewport")
[<TestMethod>]
member _.HudClockAndPeriodArePureFunctions () =
let ticksPerHour = Sim.ticksPerDay / 24L
Assert.AreEqual<string>("12:00", M6Presentation.clockTimeLabel (12L * ticksPerHour))
Assert.AreEqual<string>("00:00", M6Presentation.clockTimeLabel 0L)
Assert.AreEqual<string>("06:30", M6Presentation.clockTimeLabel (6L * ticksPerHour + ticksPerHour / 2L))
Assert.AreEqual<string>("白天", M6Presentation.timePeriodLabel (12L * ticksPerHour))
Assert.AreEqual<string>("夜晚", M6Presentation.timePeriodLabel 0L)
Assert.AreEqual<string>("清晨", M6Presentation.timePeriodLabel (6L * ticksPerHour))
Assert.AreEqual<string>("黄昏", M6Presentation.timePeriodLabel (19L * ticksPerHour))
[<TestMethod>]
member _.HudNightPaletteLeansCoolAndLabelsAreAtlasBacked () =
let day = HudLayout.palette Day
let night = HudLayout.palette Night
Assert.IsTrue(night.Accent.B > night.Accent.R, "night accent should lean cool/blue (winter-night tint)")
Assert.IsTrue(day.Accent.R > day.Accent.B, "day accent should lean warm")
for label in [ "时段"; "时间"; "倍速"; "体力"; "钱币" ] do
Assert.IsTrue(PixelText.isRenderable label, sprintf "HUD label %s must render" label)
Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains "时段")
Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains "体力")
Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains "钱币")
|