diff options
Diffstat (limited to 'src/LivingVillage.Desktop.Tests/P42LaunchFxTests.fs')
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/P42LaunchFxTests.fs | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop.Tests/P42LaunchFxTests.fs b/src/LivingVillage.Desktop.Tests/P42LaunchFxTests.fs new file mode 100644 index 0000000..9c1fe6d --- /dev/null +++ b/src/LivingVillage.Desktop.Tests/P42LaunchFxTests.fs @@ -0,0 +1,79 @@ +namespace LivingVillage.Desktop.Tests + +open Microsoft.VisualStudio.TestTools.UnitTesting +open LivingVillage.Desktop + +/// P42 launch/menu environment FX: +/// * water ripples whose brightness phase is a pure function of (frame, column, row); +/// * eave/bank lantern breathing glow through the shared P14 premultiplied-alpha path; +/// * a deterministic shot table for the LV_P42_SHOT evidence hook. +/// Every function is frame-driven, so re-rendering the same frame is byte-identical. +[<TestClass>] +type P42LaunchFxTests () = + + [<TestMethod>] + member _.RippleAndBreathAreDeterministicForTheSameFrame () = + for frame in [ 0L; 1L; 17L; 30L; 150L; 4097L ] do + for column in [ 0; 1; 7; 39 ] do + for rowIndex in [ 0; 1 ] do + Assert.AreEqual<float32>( + TitleScreen.waterRippleBrightness frame column rowIndex, + TitleScreen.waterRippleBrightness frame column rowIndex) + Assert.AreEqual<int>( + TitleScreen.waterRippleLift frame column rowIndex, + TitleScreen.waterRippleLift frame column rowIndex) + for index in [ 0; 1; 3 ] do + Assert.AreEqual<int>( + TitleScreen.lanternBreathAlpha frame index, + TitleScreen.lanternBreathAlpha frame index) + + [<TestMethod>] + member _.RipplePhaseMovesWithTheFrame () = + // The same column changes brightness as the frame advances (the ripple travels), + // and adjacent columns differ at a fixed frame (the ripple is per-tile). + let values = [ 0L .. 24L ] |> List.map (fun f -> TitleScreen.waterRippleBrightness f 5 0) + Assert.IsTrue(values |> List.distinct |> List.length > 1, "ripple must move with the frame") + let atFrame = [ 0 .. 12 ] |> List.map (fun c -> TitleScreen.waterRippleBrightness 40L c 0) + Assert.IsTrue(atFrame |> List.distinct |> List.length > 1, "ripple must vary across columns") + // Over a full period it swings both above and below the base brightness. + let overPeriod = [ for f in 0L .. 80L -> TitleScreen.waterRippleBrightness f 5 0 ] + Assert.IsTrue(overPeriod |> List.exists (fun v -> v > 1.0f) && overPeriod |> List.exists (fun v -> v < 1.0f), + "ripple must swing above and below the base brightness") + + [<TestMethod>] + member _.BreathAmplitudeIsBoundedAndVaries () = + let samples = + [ for frame in 0L .. 120L do + for index in 0 .. 5 do + yield TitleScreen.lanternBreathAlpha frame index ] + Assert.IsTrue(samples |> List.forall (fun a -> a >= 40 && a <= 96), + "breath alpha must stay in [40, 96] so it never goes dark or saturates") + Assert.IsTrue(samples |> List.distinct |> List.length > 1, "breath must vary with frame/index") + // Out-of-step lanterns: two adjacent lights differ at the same frame. + Assert.AreNotEqual<int>(TitleScreen.lanternBreathAlpha 50L 0, TitleScreen.lanternBreathAlpha 50L 1) + + [<TestMethod>] + member _.P42ShotTableIsOrderedAndNamesAreDistinct () = + Assert.IsTrue(P42ShotScript.splashEarlyFrame < P42ShotScript.splashLateFrame) + Assert.IsTrue(LaunchScreen.revealProgress P42ShotScript.splashEarlyFrame > 0.0f) + Assert.IsTrue(LaunchScreen.revealProgress P42ShotScript.splashLateFrame < 1.0f, + "both splash FX frames must still show the water band") + Assert.IsTrue(P42ShotScript.menuFrame >= 120L, "menu shot must wait for the entrance to settle") + let names = [ P42ShotScript.splashEarlyName; P42ShotScript.splashLateName; P42ShotScript.menuName ] + Assert.AreEqual<int>(names.Length, names |> List.distinct |> List.length) + + [<TestMethod>] + member _.RippleAmplitudeStaysAboveVisibilityBudget () = + // Guards against a regression that quietly lowers the ripple back below visibility. + Assert.IsTrue(TitleScreen.waterRippleAmplitude >= 0.25f, + "ripple amplitude must stay >= 0.25 to read at 1280x720 (1x)") + let samples = + [ for frame in 0L .. 40L do + for column in 0 .. 39 do + yield TitleScreen.waterRippleBrightness frame column 0 ] + let peakToPeak = (List.max samples) - (List.min samples) + Assert.IsTrue(peakToPeak >= 0.5f, sprintf "ripple peak-to-peak %.3f must be >= 0.50" peakToPeak) + // Adjacent 28px tile columns must differ enough to be visible at 1x. + let brightness = TitleScreen.waterRippleBrightness 150L + let adjacent = [ for column in 0 .. 38 -> abs (brightness column 0 - brightness (column + 1) 0) ] + Assert.IsTrue(List.max adjacent >= 0.2f, sprintf "max adjacent-column ripple step %.3f must be >= 0.20" (List.max adjacent)) |
