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. [] 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) [] 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 >= 96), "reflection alpha must stay legible (>= 96)") 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") [] 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.isWaterSurface map x y, sprintf "reflection (%d,%d) must sit on a reflective surface" x y) Assert.IsTrue(alpha >= 96, "reflection alpha must stay legible (>= 96)") // every streak sits in the same column as a source, within stream range + bridge/own-structure gap. let sourceSet = sources |> Set.ofList for (x, y, _, _) in streaks do Assert.IsTrue( [ 1 .. SceneDetail.reflectionRange + SceneDetail.reflectionGap ] |> List.exists (fun d -> Set.contains (x, y - d) sourceSet), sprintf "reflection (%d,%d) must be in the column below a source" x y) // each light source keeps its kind so the renderer can tint per kind. for (_, _, kind) in SceneDetail.lightSources map do Assert.IsTrue(kind = SceneDetail.BridgeLantern || kind = SceneDetail.RoadLantern || kind = SceneDetail.RedLantern, "light source kind must be one of the three known kinds") // at least one source must produce a full >= 3 tile vertical reflection (leader acceptance). let longest = SceneDetail.lightSources map |> List.map (fun (x, y, _) -> SceneDetail.reflectionLength map x y) |> List.fold max 0 Assert.IsTrue(longest >= 3, sprintf "the longest reflection must span >= 3 tiles, got %d" longest) [] 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") [] 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") [] 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) [] 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")