summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop.Tests
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-22 11:52:35 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-22 11:52:35 +0800
commitb2a67011ca7eef02c685794d887804e7e50dadf8 (patch)
treefe9b3d02391cd0baf991c144cab4b589b942f7a1 /src/LivingVillage.Desktop.Tests
parent38496525403cefcbfefedcf8377100e662d9e6d4 (diff)
parentecb96a9626b3c5583a995f78b99d6db43ab2dd5b (diff)
downloadliving-village-b2a67011ca7eef02c685794d887804e7e50dadf8.tar.gz
merge: P28 民居第二变体强化 + 512 沿河加密(青灰挑檐B/竖长格栅窗/绿院门,2/3/4 宽分层 16 座外环)— Hermes 复验通过
Diffstat (limited to 'src/LivingVillage.Desktop.Tests')
-rw-r--r--src/LivingVillage.Desktop.Tests/MapGenTests.fs15
-rw-r--r--src/LivingVillage.Desktop.Tests/PrototypeTests.fs53
2 files changed, 68 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop.Tests/MapGenTests.fs b/src/LivingVillage.Desktop.Tests/MapGenTests.fs
index 6486dd7..b737707 100644
--- a/src/LivingVillage.Desktop.Tests/MapGenTests.fs
+++ b/src/LivingVillage.Desktop.Tests/MapGenTests.fs
@@ -256,6 +256,21 @@ type MapGenTests () =
Assert.IsTrue(a.Buildings = b.Buildings)
Assert.AreEqual<string>(MapGen.serialize a, MapGen.serialize b)
+ /// P28:512 沿河民居由 10 -> 16 增密(+6),并出现 2/3/4 三档宽度(附属小筑/民居/大宅),
+ /// 形成聚落层次;默认图仍为 0,不消耗 RNG,故 64/256 checksum 锁定不变。
+ [<TestMethod>]
+ member _.LargeMapHouseDensityHierarchyOnlyGrowsLargeMaps () =
+ Assert.AreEqual<int>(16, (MapGen.paramsForSize 512 384 (uint64 1)).ExtraRiversideHouses)
+ Assert.AreEqual<int>(0, (MapGen.paramsForSize 64 48 (uint64 1)).ExtraRiversideHouses)
+ Assert.AreEqual<int>(0, (MapGen.paramsForSize 256 192 (uint64 1)).ExtraRiversideHouses)
+ let map = MapGen.generateWithSize 512 384 (uint64 4242)
+ Assert.IsTrue(map.Buildings.Length >= 22, sprintf "P28 512 must place >=22 houses, saw %d" map.Buildings.Length)
+ let widths = map.Buildings |> List.map (fun b -> b.Width) |> Set.ofList
+ Assert.IsTrue(widths.Contains 2, "outbuilding width 2 must appear")
+ Assert.IsTrue(widths.Contains 3, "house width 3 must appear")
+ Assert.IsTrue(widths.Contains 4, "hall width 4 must appear")
+ Assert.AreEqual<int>(24, map.Buildings.Length)
+
[<TestMethod>]
member _.DefaultSizeHouseCountUnchanged () =
Assert.AreEqual<int>(4, (MapGen.generateWithSize 64 48 (uint64 4242)).Buildings.Length)
diff --git a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs
index bc3425a..7f67280 100644
--- a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs
+++ b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs
@@ -1171,6 +1171,59 @@ type PrototypeTests () =
Assert.IsTrue((atlasWidth = VillageArt.worldAtlasWidth), "world atlas width must match 32 x 32px slots")
Assert.IsTrue((atlasHeight = VillageArt.worldAtlasHeight), "world atlas height must stay 32px")
+ /// P28:防复发。第二变体(P26)在 512 整图 ~2px/tile 下不可辨,根因是挑檐与第一变体同为
+ /// 近黑黛瓦。此回归直接解码 world 图集,断言变体 B 的挑檐(slot 28)平均亮度显著高于
+ /// 变体 A 的屋脊(slot 8),保证「配色差异」不会在后续编辑中被悄悄抹平。
+ [<TestMethod>]
+ member _.VariantBRoofPaletteIsClearlyLighterThanVariantA () =
+ let worldPath = Path.Combine(AppContext.BaseDirectory, "Assets/jiangnan-world.png")
+ let bytes = File.ReadAllBytes worldPath
+ let be32 offset =
+ (int bytes.[offset] <<< 24)
+ ||| (int bytes.[offset + 1] <<< 16)
+ ||| (int bytes.[offset + 2] <<< 8)
+ ||| int bytes.[offset + 3]
+ Assert.AreEqual<int>(0x89504E47, be32 0)
+ let width = be32 16
+ let height = be32 20
+ Assert.AreEqual<int>(VillageArt.worldAtlasWidth, width)
+ Assert.AreEqual<int>(VillageArt.worldAtlasHeight, height)
+ // Collect IDAT and inflate; the baked atlas is 8-bit RGBA, non-interlaced, filter 0.
+ let mutable offset = 8
+ use idat = new MemoryStream()
+ while offset < bytes.Length do
+ let length = be32 offset
+ let tag = System.Text.Encoding.ASCII.GetString(bytes, offset + 4, 4)
+ if tag = "IDAT" then idat.Write(bytes, offset + 8, length)
+ offset <- offset + 12 + length
+ idat.Position <- 0L
+ use inflater = new System.IO.Compression.ZLibStream(idat, System.IO.Compression.CompressionMode.Decompress)
+ use raw = new MemoryStream()
+ inflater.CopyTo raw
+ let data = raw.ToArray()
+ let stride = width * 4 + 1
+ let pixel (x: int) (y: int) =
+ let rowStart = y * stride
+ Assert.AreEqual<byte>(0uy, data.[rowStart])
+ let p = rowStart + 1 + x * 4
+ (int data.[p], int data.[p + 1], int data.[p + 2], int data.[p + 3])
+ let slotMeanBrightness (slot: int) =
+ let mutable sum = 0.0
+ let mutable count = 0
+ for y in 0 .. height - 1 do
+ for x in slot * 32 .. slot * 32 + 31 do
+ let (r, g, b, a) = pixel x y
+ if a > 0 then
+ sum <- sum + float (r + g + b) / 3.0
+ count <- count + 1
+ Assert.IsTrue(count > 0, sprintf "slot %d must have opaque pixels" slot)
+ sum / float count
+ let roofA = slotMeanBrightness 8 // RoofRidgeSprite: variant A near-black 黛瓦
+ let roofB = slotMeanBrightness 28 // RoofEaveOverhangSprite: variant B light blue-grey 瓦
+ Assert.IsTrue(
+ roofB > roofA + 15.0,
+ sprintf "variant B roof must be clearly lighter than variant A (A=%.1f B=%.1f)" roofA roofB)
+
[<TestMethod>]
member _.CjkGlyphManifestIsUniqueAndMatchesAtlasGrid () =
// 防复发:图集用字增长时,清单必须非空、每行恰 1 个字符、字符唯一,