namespace LivingVillage.Desktop.Tests open Microsoft.VisualStudio.TestTools.UnitTesting open LivingVillage.Kernel.Sim open LivingVillage.Desktop open LivingVillage.Desktop.VillageArt /// P40 art-deepening slice: /// * paving slabs follow the traffic network (paths / doorways / bridges) and the /// isolated decorative scatter is dropped, so cc0 ground reads as coherent bands; /// * red door lanterns complement the stone road lanterns with a distinct form, /// both feeding the existing P14 premultiplied-alpha night-glow path; /// * every placement stays a pure function of the map, so the same seed yields /// byte-identical positions across day and night frames. [] type P40ArtTests () = let seed = 4242UL let map = MapGen.generateWithSize 256 192 seed let indexOf (x: int) (y: int) = y * map.Width + x let isStone (x: int) (y: int) = x >= 0 && x < map.Width && y >= 0 && y < map.Height && map.Tiles.[indexOf x y] = int MapGen.GroundTile.Stone [] member _.PavingCoversTrafficNetworkAndDropsIsolatedScatter () = let paving = cc0PavingTiles map Assert.IsTrue(paving.Count > 0, "seed 4242 should pave something") // Every path / bridge anchor tile that is stone must be paved. for (x, y) in map.Paths @ map.Bridges do if isStone x y then Assert.IsTrue(paving.Contains(indexOf x y), sprintf "path/bridge stone (%d,%d) must be paved" x y) // Every paved tile must be stone and touch the network (itself or a 4-neighbour). let anchors = (map.Paths @ map.Bridges) |> List.map (fun (x, y) -> indexOf x y) |> Set.ofList let anchorAt (x: int) (y: int) = x >= 0 && x < map.Width && y >= 0 && y < map.Height && Set.contains (indexOf x y) anchors for i in paving do let x = i % map.Width let y = i / map.Width Assert.IsTrue(isStone x y, "paving must only replace stone tiles") Assert.IsTrue( anchorAt x y || anchorAt (x - 1) y || anchorAt (x + 1) y || anchorAt x (y - 1) || anchorAt x (y + 1), sprintf "paved tile (%d,%d) is disconnected from the traffic network" x y) // The isolated decorative scatter must NOT be paved (that is the whole point). Assert.IsTrue(paving.Count < (map.Tiles |> Array.filter (fun c -> c = int MapGen.GroundTile.Stone) |> Array.length), "paving must be a strict subset of all stone (scatter dropped)") [] member _.PavingIsDeterministicForTheSameSeed () = let map2 = MapGen.generateWithSize 256 192 seed let a = cc0PavingTiles map |> Set.toList |> List.sort |> List.map (fun i -> sprintf "%d" i) |> String.concat ";" let b = cc0PavingTiles map2 |> Set.toList |> List.sort |> List.map (fun i -> sprintf "%d" i) |> String.concat ";" Assert.AreEqual(a, b) [] member _.RedLanternsHangAtDoorwaysAndAreDeterministic () = let lanterns = cc0RedLanternTiles map let doors = (map.Buildings @ map.Farmhouses) |> List.map (fun b -> (b.DoorX, b.DoorY)) |> List.distinct Assert.IsTrue(lanterns.Length > 0, "seed 4242 should hang red lanterns") Assert.AreEqual(doors.Length, lanterns.Length) for (x, y) in lanterns do Assert.IsTrue(List.contains (x, y) doors, "a red lantern hangs exactly at a doorway") Assert.AreEqual( (lanterns |> List.sort |> List.map (fun (x, y) -> sprintf "%d:%d" x y) |> String.concat ";"), (cc0RedLanternTiles map |> List.sort |> List.map (fun (x, y) -> sprintf "%d:%d" x y) |> String.concat ";")) [] member _.RedAndStoneLanternsFeedTheSameNightGlowPath () = let cc0Lights = nightLightTilesWith true map |> Set.ofList let fallbackLights = nightLightTilesWith false map |> Set.ofList // cc0 mode adds both the road stone lanterns and the red door lanterns. for t in cc0RedLanternTiles map do Assert.IsTrue(Set.contains t cc0Lights, "red lantern must be a cc0 night light") for t in cc0LanternTiles map 12 do Assert.IsTrue(Set.contains t cc0Lights, "road lantern must be a cc0 night light") // Red lanterns hang at doorways, which are already house lights, so the // fallback set may legitimately contain that tile; what must NOT leak is // the road stone lantern set (a cc0-only placement). for t in cc0LanternTiles map 12 do Assert.IsFalse(Set.contains t fallbackLights, "road lantern must not leak into the fallback light set") Assert.IsTrue(nightLightTiles map = nightLightTilesWith false map, "nightLightTiles keeps the old set") Assert.IsTrue(Set.isSubset fallbackLights cc0Lights, "cc0 light set is a superset of the fallback set") [] member _.SameSeedPlacementsAreStableAcrossGeneration () = // Day and night frames reuse the same map; re-generating it must reproduce // every P40 placement byte-for-byte (no clock, no hidden randomness). let map2 = MapGen.generateWithSize 256 192 seed Assert.AreEqual( (cc0PavingTiles map |> Set.toList |> List.sort |> List.map string |> String.concat ";"), (cc0PavingTiles map2 |> Set.toList |> List.sort |> List.map string |> String.concat ";")) Assert.AreEqual( (cc0RedLanternTiles map |> List.sort |> List.map (fun (x, y) -> sprintf "%d:%d" x y) |> String.concat ";"), (cc0RedLanternTiles map2 |> List.sort |> List.map (fun (x, y) -> sprintf "%d:%d" x y) |> String.concat ";")) [] member _.P40AtlasIndicesAreInRange () = for index in [ cc0TileIndexPaving; cc0TileIndexRedLantern ] do Assert.IsTrue(index >= 0 && index < cc0TileCount, sprintf "P40 tile index %d out of atlas" index) Assert.AreEqual(21, cc0TileCount)