summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop.Tests/P40ArtTests.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop.Tests/P40ArtTests.fs')
-rw-r--r--src/LivingVillage.Desktop.Tests/P40ArtTests.fs104
1 files changed, 104 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop.Tests/P40ArtTests.fs b/src/LivingVillage.Desktop.Tests/P40ArtTests.fs
new file mode 100644
index 0000000..549640e
--- /dev/null
+++ b/src/LivingVillage.Desktop.Tests/P40ArtTests.fs
@@ -0,0 +1,104 @@
+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.
+[<TestClass>]
+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
+
+ [<TestMethod>]
+ 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)")
+
+ [<TestMethod>]
+ 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<string>(a, b)
+
+ [<TestMethod>]
+ 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<int>(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<string>(
+ (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 ";"))
+
+ [<TestMethod>]
+ 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")
+
+ [<TestMethod>]
+ 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<string>(
+ (cc0PavingTiles map |> Set.toList |> List.sort |> List.map string |> String.concat ";"),
+ (cc0PavingTiles map2 |> Set.toList |> List.sort |> List.map string |> String.concat ";"))
+ Assert.AreEqual<string>(
+ (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 ";"))
+
+ [<TestMethod>]
+ 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<int>(21, cc0TileCount)