summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop.Tests/P42LaunchFxTests.fs
blob: 9c1fe6d88db0a625c429defa5cddf3541098c30d (plain)
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
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))