summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop.Tests/P57MenuHighlightTests.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-29 02:05:39 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-29 02:05:39 +0800
commitcbd89dbff21a66725132eaf1c37e2fb870e72f8d (patch)
tree0696454739e3479ecb697d000c02841c06d7319e /src/LivingVillage.Desktop.Tests/P57MenuHighlightTests.fs
parent1a67f2bf57e58ef748e71e1bcc2affe901444c39 (diff)
downloadliving-village-cbd89dbff21a66725132eaf1c37e2fb870e72f8d.tar.gz
p57: 菜单/启动水面第二层高光波光——逐列相位错开(步进2.4rad)/更快(周期80 vs 96 tick)/更亮更窄细高光条 + 昼夜 tint 衰减参数 + 帧计数纯函数回归;ANALYZE splash hl/main std=274.97 corr=-0.017、menu hl/main std=279.77 corr=-0.011、层间相关<0.95、相位差2.4rad、无黑块白块
Diffstat (limited to 'src/LivingVillage.Desktop.Tests/P57MenuHighlightTests.fs')
-rw-r--r--src/LivingVillage.Desktop.Tests/P57MenuHighlightTests.fs105
1 files changed, 105 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop.Tests/P57MenuHighlightTests.fs b/src/LivingVillage.Desktop.Tests/P57MenuHighlightTests.fs
new file mode 100644
index 0000000..74f8eba
--- /dev/null
+++ b/src/LivingVillage.Desktop.Tests/P57MenuHighlightTests.fs
@@ -0,0 +1,105 @@
+namespace LivingVillage.Desktop.Tests
+
+open Microsoft.VisualStudio.TestTools.UnitTesting
+open LivingVillage.Desktop
+
+/// P57 美术 A3:菜单/启动水面第二层高光波光(P46 遗留短板)。
+///
+/// 高光层必须与主水带**可分辨**:更亮更窄、列相位错开、推进速度略不同;
+/// 全为帧计数纯函数(无随机),且**不得改变**既有 waterPhase / lantern / 云影输出。
+[<TestClass>]
+type P57MenuHighlightTests () =
+
+ let twoPi = 2.0f * float32 System.Math.PI
+ let assertClose (expected: float32) (actual: float32) (message: string) =
+ Assert.IsTrue((abs (expected - actual) < 1e-4f), sprintf "%s: expected %f actual %f" message expected actual)
+ let assertInt (expected: int) (actual: int) (message: string) =
+ Assert.IsTrue((expected = actual), sprintf "%s: expected %d actual %d" message expected actual)
+
+ let visibleColumn (frame: int64) (rowIndex: int) : int =
+ [ 0 .. 63 ]
+ |> List.tryFind (fun column -> MenuAmbience.highlightVisible frame column rowIndex)
+ |> Option.defaultValue -1
+
+ [<TestMethod>]
+ member _.HighlightPhaseOffsetsAdjacentColumnsByANonZeroStep () =
+ for frame in [ 0L; 16L; 150L; 4097L ] do
+ for rowIndex in [ 0; 1 ] do
+ for column in [ 0; 5; 21; 38 ] do
+ let step =
+ MenuAmbience.highlightPhase frame (column + 1) rowIndex
+ - MenuAmbience.highlightPhase frame column rowIndex
+ assertClose MenuAmbience.highlightColumnStep step (sprintf "frame %d col %d row %d" frame column rowIndex)
+ Assert.IsTrue(abs step > 1e-3f, "adjacent highlight columns must be phase-shifted")
+
+ [<TestMethod>]
+ member _.HighlightMovesAtADifferentSpeedThanTheMainWaterBand () =
+ // 主水带连续相位速率 = (2π/3)/32;高光层 = 2π/period,二者比值应略偏离 1(本实现 1.2)。
+ let mainDelta =
+ MenuAmbience.waterPhase 151L 5 0 - MenuAmbience.waterPhase 150L 5 0
+ let highlightDelta =
+ MenuAmbience.highlightPhase 151L 5 0 - MenuAmbience.highlightPhase 150L 5 0
+ Assert.IsTrue(highlightDelta > 0.0f && mainDelta > 0.0f, "both layers must advance with the frame")
+ let ratio = highlightDelta / mainDelta
+ assertClose 1.2f ratio "highlight speed must be slightly different (1.2x)"
+ Assert.IsTrue(abs (highlightDelta - mainDelta) > 1e-3f, "the two layers must not move at the same speed")
+
+ [<TestMethod>]
+ member _.HighlightLayerIsBrighterAndNarrowerThanTheMainBand () =
+ // 更窄:条更薄、更短。
+ Assert.IsTrue(MenuAmbience.highlightThickness < 3, "highlight bar must be thinner than the 3px main crest")
+ Assert.IsTrue(MenuAmbience.highlightBarWidth < 32, "highlight bar must be narrower than a tile")
+ // 更亮:纯白高光 > 主水带波峰色 (225,240,245) 的 luma。
+ let r, g, b = MenuAmbience.highlightRgb
+ let highlightLuma = 0.299f * float32 r + 0.587f * float32 g + 0.114f * float32 b
+ let crestLuma = 0.299f * 225.0f + 0.587f * 240.0f + 0.114f * 245.0f
+ Assert.IsTrue(highlightLuma > crestLuma, sprintf "highlight luma %.1f must exceed crest luma %.1f" highlightLuma crestLuma)
+ // 更窄的可见条件(阈值更高 → 亮列更稀疏),但仍有可见列。
+ let frame = 150L
+ let highlightCount = [ 0 .. 39 ] |> List.filter (fun c -> MenuAmbience.highlightVisible frame c 0) |> List.length
+ let mainCount = [ 0 .. 39 ] |> List.filter (fun c -> MenuAmbience.waterColumnCrest frame c 0) |> List.length
+ Assert.IsTrue(highlightCount > 0, "some highlight columns must be lit")
+ Assert.IsTrue(highlightCount <= mainCount, sprintf "highlight %d must be no denser than main crest %d" highlightCount mainCount)
+
+ [<TestMethod>]
+ member _.HighlightIsDeterministicAndBounded () =
+ for frame in [ 0L; 1L; 33L; 173L; 99999L; 100000L; 500000L ] do
+ for column in [ 0; 13; 39 ] do
+ for rowIndex in [ 0; 1 ] do
+ assertClose
+ (MenuAmbience.highlightPhase frame column rowIndex)
+ (MenuAmbience.highlightPhase frame column rowIndex)
+ "highlight phase must be deterministic"
+ let alpha = MenuAmbience.highlightAlpha MenuAmbience.highlightDayLight frame column rowIndex
+ Assert.IsTrue(alpha >= 0 && alpha <= 255, sprintf "alpha %d out of [0,255]" alpha)
+
+ [<TestMethod>]
+ member _.NightLightDimmsTheHighlightButKeepsItReadable () =
+ let frame = 150L
+ let column = visibleColumn frame 0
+ Assert.IsTrue(column >= 0, "need a lit column to compare day/night")
+ let day = MenuAmbience.highlightAlpha MenuAmbience.highlightDayLight frame column 0
+ let night = MenuAmbience.highlightAlpha MenuAmbience.highlightNightLight frame column 0
+ Assert.IsTrue(night < day, sprintf "night alpha %d must be below day %d" night day)
+ Assert.IsTrue(night >= int (0.35f * float32 day), sprintf "night alpha %d must stay readable (>= 35%% of day)" night)
+ Assert.IsTrue(night >= 20, sprintf "night alpha %d must not vanish" night)
+
+ [<TestMethod>]
+ member _.ExistingWaterLanternCloudOutputsArePinned () =
+ // 回归:高光层不得改变既有 waterPhase / lantern / 云影输出帧。
+ assertInt 0 (VillageArt.waterFrameTickAt 150L 5 0) "waterFrameTickAt pin"
+ assertClose 1.317237f (MenuAmbience.waterColumnBrightness 150L 5 0) "main water brightness pin"
+ assertInt 89 (MenuAmbience.lanternBreathAlpha 50L 0) "lantern breath pin"
+ assertInt 70 (MenuAmbience.cloudOffsetPx 0 140L 1280) "cloud offset pin"
+ Assert.AreEqual<int list>([ 624; 656 ], (TitleScreen.layout 1280 720).WaterRows)
+
+ [<TestMethod>]
+ member _.P57ShotScriptWaterFramesAreOrderedAndDistinct () =
+ Assert.IsTrue(P57ShotScript.splashFrame > 0L && P57ShotScript.splashFrame < LaunchScreen.durationFrames)
+ Assert.IsTrue(P57ShotScript.menuFrame >= 120L, "menu frame must wait for the entrance to settle")
+ let names =
+ [ P57ShotScript.splashBaseName
+ P57ShotScript.splashHighlightName
+ P57ShotScript.menuBaseName
+ P57ShotScript.menuHighlightName ]
+ assertInt names.Length (names |> List.distinct |> List.length) "shot names distinct"