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))
|