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
|
namespace LivingVillage.Desktop.Tests
open Microsoft.VisualStudio.TestTools.UnitTesting
open LivingVillage.Desktop
/// P47 scene detail:
/// * water reflections reuse waterFrameTickAt phase and only land on water;
/// * bank reeds / floating props are deterministic map+tick pure functions;
/// * the near foreground roof layer uses one unified shadow direction.
[<TestClass>]
type P47SceneDetailTests () =
let map = MapGen.generate (MapGen.defaultParams 42UL)
let assertInt (expected: int) (actual: int) (message: string) =
Assert.IsTrue((expected = actual), sprintf "%s: expected %d actual %d" message expected actual)
[<TestMethod>]
member _.ReflectionHelpersAreDeterministicAndBounded () =
assertInt 3 (fst SceneDetail.shadowOffsetPx) "shadow dx"
assertInt 4 (snd SceneDetail.shadowOffsetPx) "shadow dy"
// alpha strictly decreases with distance and never goes negative.
let alphas = [ for d in 1 .. 6 -> SceneDetail.reflectionAlphaAt d ]
Assert.IsTrue(alphas |> List.forall (fun a -> a >= 0), "reflection alpha must be >= 0")
Assert.IsTrue(alphas.[0] > alphas.[1] && alphas.[1] > alphas.[2], "alpha must decrease with distance")
// wobble is exactly the 3-phase water ramp mapped to -1/0/+1 and is deterministic.
for tick in [ 0L; 32L; 64L; 96L ] do
for x in [ 0; 3; 9 ] do
for y in [ 0; 2 ] do
let expected =
match VillageArt.waterFrameTickAt tick x y with
| 0 -> -1
| 1 -> 0
| _ -> 1
assertInt expected (SceneDetail.reflectionWobbleAt tick x y) "wobble must reuse waterFrameTickAt"
Assert.IsTrue(abs (SceneDetail.reflectionWobbleAt tick x y) <= 1, "wobble bounded to +/-1px")
[<TestMethod>]
member _.ReflectionsOnlyLandOnWaterBelowALightSource () =
let sources = SceneDetail.lightSourceTiles map
Assert.IsTrue(not (List.isEmpty sources), "the generator map must have bank light sources")
let streaks = SceneDetail.reflectionStreaks map
Assert.IsTrue(not (List.isEmpty streaks), "there must be at least one reflection streak")
for (x, y, alpha) in streaks do
Assert.IsTrue(SceneDetail.isWater map x y, sprintf "reflection (%d,%d) must sit on water" x y)
Assert.IsTrue(alpha > 0, "reflection alpha must be positive")
// every streak sits directly below its source within reflectionRange.
let sourceSet = sources |> Set.ofList
for (x, y, _) in streaks do
Assert.IsTrue([ 1 .. SceneDetail.reflectionRange ] |> List.exists (fun d -> Set.contains (x, y - d) sourceSet),
sprintf "reflection (%d,%d) must be under a source" x y)
[<TestMethod>]
member _.BankReedsAndFloatersAreDenseAndDeterministic () =
let reeds = SceneDetail.bankReedTiles map
Assert.IsTrue(not (List.isEmpty reeds), "bank reeds must be non-empty")
Assert.IsTrue(reeds |> List.distinct |> List.length = reeds.Length, "reeds must be distinct")
for (x, y) in reeds do
Assert.IsTrue(not (SceneDetail.isWater map x y), "reeds sit on land")
let nearWater =
SceneDetail.isWater map (x - 1) y
|| SceneDetail.isWater map (x + 1) y
|| SceneDetail.isWater map x (y - 1)
|| SceneDetail.isWater map x (y + 1)
Assert.IsTrue(nearWater, "reeds hug the water edge")
Assert.IsTrue(x % 4 <> 0, "extra reeds interleave the existing spacing-4 set")
let floats = SceneDetail.floatingProps map
Assert.IsTrue(not (List.isEmpty floats), "floating props must be non-empty")
Assert.IsTrue(floats |> List.distinct |> List.length = floats.Length, "floaters must be distinct")
for (x, y, _) in floats do
Assert.IsTrue(SceneDetail.isWater map x y && SceneDetail.isWater map (x + 1) y,
"floaters sit on open water")
// both float kinds appear across the map.
let kinds = floats |> List.map (fun (_, _, k) -> k) |> List.distinct
Assert.IsTrue(kinds.Length >= 1, "float kinds present")
[<TestMethod>]
member _.FloatDriftAndFrameAreBoundedAndMoveWithTick () =
for tick in [ 0L; 100L; 5000L ] do
for seed in [ 0; 7; 31 ] do
let dx, dy = SceneDetail.floatDrift tick seed
Assert.IsTrue(dx >= -2 && dx <= 2, sprintf "float dx %d out of [-2,2]" dx)
Assert.IsTrue(dy >= -1 && dy <= 1, sprintf "float dy %d out of [-1,1]" dy)
assertInt (SceneDetail.floatFrame tick seed) (SceneDetail.floatFrame tick seed) "frame deterministic"
Assert.IsTrue(SceneDetail.floatFrame tick seed >= 0 && SceneDetail.floatFrame tick seed <= 1, "frame in 0/1")
let driftOverTime = [ for t in 0L .. 40L -> SceneDetail.floatDrift t 3 ]
Assert.IsTrue(driftOverTime |> List.distinct |> List.length > 1, "drift must move with tick")
[<TestMethod>]
member _.ForegroundRooftopsAreStaggeredAndCoverTheWidth () =
let width, height = 1280, 720
let rows = SceneDetail.foregroundRooftops width height 0L
Assert.IsTrue(rows.Length >= width / 32, "foreground spans the viewport width")
for (_, y, w) in rows do
let stagger = (height - 64) - y
Assert.IsTrue(stagger >= 0 && stagger <= 24 && stagger % 8 = 0,
sprintf "stagger %d must be one of 0/8/16/24" stagger)
assertInt 32 w "foreground roof cell width"
// deterministic for the same tick, and slowly drifts across ticks.
Assert.IsTrue(SceneDetail.foregroundRooftops width height 0L = SceneDetail.foregroundRooftops width height 0L)
Assert.IsTrue(SceneDetail.foregroundRooftops width height 0L <> SceneDetail.foregroundRooftops width height 240L)
[<TestMethod>]
member _.FloaterAtlasContract () =
assertInt 96 FloaterArt.atlasWidth "floater atlas width"
assertInt 16 FloaterArt.atlasHeight "floater atlas height"
assertInt 24 FloaterArt.cellWidth "floater cell width"
assertInt 16 FloaterArt.cellHeight "floater cell height"
for kind in [ FloaterArt.duckIndex; FloaterArt.leafIndex ] do
for frame in 0 .. FloaterArt.frameCount - 1 do
let rect = FloaterArt.sourceRectangle kind frame
Assert.IsTrue(rect.X >= 0 && rect.X + rect.Width <= FloaterArt.atlasWidth, "source rect inside atlas")
assertInt FloaterArt.cellHeight rect.Height "source cell height"
// four distinct cells, duck and leaf never share a frame cell.
let cells =
[ for kind in [ FloaterArt.duckIndex; FloaterArt.leafIndex ] do
for frame in 0 .. FloaterArt.frameCount - 1 -> FloaterArt.sourceRectangle kind frame ]
Assert.IsTrue(cells |> List.distinct |> List.length = 4, "four distinct floater cells")
|