diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-22 12:15:06 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-22 12:15:06 +0800 |
| commit | 4034f55ee0b9cbd8c9db5f850e10e21e15fb7274 (patch) | |
| tree | 4ca4e86827d4b318c0e3f53f89213d33f9d21a53 /src/LivingVillage.Desktop.Tests | |
| parent | 9260b3810b29488d0a9e7d16d14bbc3b5f9f6ce0 (diff) | |
| parent | a70a057fce7afa5787b2d4a037d7016f1120ef6a (diff) | |
| download | living-village-4034f55ee0b9cbd8c9db5f850e10e21e15fb7274.tar.gz | |
merge: P29 512 沿河组团化 + 夜景灯光径向光晕(lanternFalloff 二次衰减/桥头与门窗 halo)— Hermes 复验通过
Diffstat (limited to 'src/LivingVillage.Desktop.Tests')
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/MapGenTests.fs | 79 | ||||
| -rw-r--r-- | src/LivingVillage.Desktop.Tests/PrototypeTests.fs | 30 |
2 files changed, 109 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop.Tests/MapGenTests.fs b/src/LivingVillage.Desktop.Tests/MapGenTests.fs index b737707..d83a144 100644 --- a/src/LivingVillage.Desktop.Tests/MapGenTests.fs +++ b/src/LivingVillage.Desktop.Tests/MapGenTests.fs @@ -276,6 +276,85 @@ type MapGenTests () = Assert.AreEqual<int>(4, (MapGen.generateWithSize 64 48 (uint64 4242)).Buildings.Length) Assert.AreEqual<int>(4, (MapGen.generateWithSize 256 192 (uint64 4242)).Buildings.Length) + // ---- P29:512 聚落组团(group seeding 仅大图启用;行内成组、组间留档) ---- + + [<TestMethod>] + member _.ClusterSeedOnlyEnabledForLargeMaps () = + Assert.AreEqual<uint64>(0UL, (MapGen.paramsForSize 64 48 (uint64 4242)).ClusterSeed) + Assert.AreEqual<uint64>(0UL, (MapGen.paramsForSize 256 192 (uint64 4242)).ClusterSeed) + Assert.AreNotEqual<uint64>(0UL, (MapGen.paramsForSize 512 384 (uint64 4242)).ClusterSeed) + Assert.AreEqual<uint64>(0UL, (MapGen.defaultParams (uint64 4242)).ClusterSeed) + + [<TestMethod>] + member _.LargeMapHousesFormClusteredRowsWithControlledGaps () = + let map = MapGen.generateWithSize 512 384 (uint64 4242) + let rowClusters (row: MapGen.Building list) = + let sorted = row |> List.sortBy (fun b -> b.Left) + let clusters = ResizeArray<MapGen.Building list>() + let mutable current: MapGen.Building list = [] + let mutable previousEnd = System.Int32.MinValue + for building in sorted do + if current <> [] && building.Left - previousEnd <= 2 then + current <- building :: current + else + if current <> [] then clusters.Add(List.rev current) + current <- [ building ] + previousEnd <- building.Left + building.Width - 1 + if current <> [] then clusters.Add(List.rev current) + List.ofSeq clusters + let clusteredRows = map.Buildings |> List.groupBy (fun b -> b.Top) |> List.map (snd >> rowClusters) + let clusterSizes = clusteredRows |> List.collect (List.map List.length) + Assert.IsTrue(clusterSizes |> List.exists (fun n -> n >= 3), "at least one row-sized house cluster") + // 行内出现多个簇 => 组间留有可控空档,而不是一整排等距。 + let rowCount = map.Buildings |> List.map (fun b -> b.Top) |> List.distinct |> List.length + Assert.IsTrue(clusterSizes.Length > rowCount, "rows must split into separated groups") + for clustersInRow in clusteredRows do + match clustersInRow with + | first :: rest -> + let mutable previous = first + for cluster in rest do + let prevEnd = previous |> List.map (fun b -> b.Left + b.Width - 1) |> List.max + let nextStart = cluster |> List.map (fun b -> b.Left) |> List.min + Assert.IsTrue(nextStart - prevEnd >= 3, "groups must be separated by a controlled gap") + previous <- cluster + | [] -> () + + /// 组团参数下,512 world 同 seed 两次生成逐字节一致(checksum 且序列化 diff 为空)。 + [<TestMethod>] + member _.LargeMapClusterLayoutChecksumIsEmptyDiffAcrossRuns () = + let a = MapGen.generateWithSize 512 384 (uint64 4242) + let b = MapGen.generateWithSize 512 384 (uint64 4242) + Assert.IsTrue(a.Tiles = b.Tiles, "tiles must be byte-identical across runs") + Assert.AreEqual<int64>(checksum a, checksum b) + Assert.AreEqual<string>(MapGen.serialize a, MapGen.serialize b) + Assert.IsTrue(a.Buildings = b.Buildings) + Assert.IsTrue(a.Decorations = b.Decorations) + + /// P29 垂柳成丛:岸边出现相邻 2-3 株的柳丛,且不再是均匀逐格散布。 + [<TestMethod>] + member _.LargeMapWillowsGrowInGroves () = + let map = MapGen.generateWithSize 512 384 (uint64 4242) + let willows = map.Decorations |> List.filter (fun d -> d.Kind = MapGen.Willow) + Assert.IsTrue(willows.Length > 0, "large map must place willows") + let groveSizes = + willows + |> List.groupBy (fun d -> d.Y) + |> List.collect (fun (_, row) -> + let xs = row |> List.map (fun d -> d.X) |> List.sort + let sizes = ResizeArray<int>() + let mutable current = 0 + let mutable previous = System.Int32.MinValue + for x in xs do + if previous >= 0 && x - previous = 1 then + current <- current + 1 + else + if current > 0 then sizes.Add current + current <- 1 + previous <- x + if current > 0 then sizes.Add current + List.ofSeq sizes) + Assert.IsTrue(groveSizes |> List.exists (fun n -> n >= 2), "willows must form groves of adjacent trunks") + // ---- P25:大图不产孤立石板 tile(装饰石块默认开启仅限小图) ---- [<TestMethod>] diff --git a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs index 7f67280..7b35837 100644 --- a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs +++ b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs @@ -663,6 +663,36 @@ type PrototypeTests () = Assert.IsTrue(row.[0] = center && row.[radius] < center, "core is brightest, rim is dimmer") [<TestMethod>] + member _.LanternFalloffIsMaxAtCoreAndMonotonicWithoutPlateau () = + // P29:夜景灯光衰减函数——半径 0 处最大(=1),向半径边缘严格单调递减至 0,无平台。 + let radius = 96.0f + Assert.AreEqual<float32>(1.0f, VillageArt.lanternFalloff 0.0f radius) + Assert.AreEqual<float32>(0.0f, VillageArt.lanternFalloff radius radius) + Assert.AreEqual<float32>(0.0f, VillageArt.lanternFalloff (radius + 7.0f) radius) + Assert.IsTrue(abs (VillageArt.lanternFalloff 0.0f 0.0f) < 1e-6f, "zero radius must contribute no light") + let samples = [ for i in 0 .. int radius -> VillageArt.lanternFalloff (float32 i) radius ] + for value in samples do + Assert.IsTrue(value >= 0.0f && value <= 1.0f, "falloff must stay within [0,1]") + samples + |> List.pairwise + |> List.iter (fun (a, b) -> Assert.IsTrue(b < a, sprintf "falloff must strictly decrease (no plateau): %f then %f" a b)) + // 二次衰减:一半半径处约为 0.75,且严格高于线性 0.5。 + Assert.IsTrue(abs (VillageArt.lanternFalloff (radius / 2.0f) radius - 0.75f) < 1e-4f) + + [<TestMethod>] + member _.NightLightTilesCombineBridgeLanternsAndHouseWindows () = + // P29:夜景光源 = 桥头灯笼 + 民居门窗暖光,确定性纯函数。 + let map = MapGen.generateWithSize 512 384 (uint64 4242) + let lights = VillageArt.nightLightTiles map + Assert.IsTrue(lights.Length > 0, "large map must emit night lights") + Assert.IsTrue(lights.Length > map.Buildings.Length, "windows add light sources beyond the houses themselves") + Assert.AreEqual<(int * int) list>(lights, VillageArt.nightLightTiles map) + for lantern in VillageArt.bridgeLanternTiles map do + Assert.IsTrue(lights |> List.contains lantern, sprintf "bridge lantern (%d,%d) must be a light source" (fst lantern) (snd lantern)) + for building in map.Buildings do + Assert.IsTrue(lights |> List.contains (building.DoorX, building.DoorY), "every house door must carry warm light") + + [<TestMethod>] member _.CharacterFeetSitOnTheTileGroundLine () = Assert.AreEqual<int>(16, VillageArt.characterFeetOffset) let destination = VillageArt.characterDestination { X = 100.0f; Y = 200.0f } (Vector2(0.0f, 0.0f)) |
