From 91b7204d45f4f11d9cd75afcfbea9bd7034a3de4 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 22 Sep 2026 09:00:11 +0800 Subject: fix(mapgen): 大图关闭孤立装饰石块,杜绝草地上游离石板 tile(P25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Params.DecorativeStones(默认 true);paramsForSize 对 >256x192 关闭, 512x384 不再产生 292 个孤立石板(stone 非路网计数归零,石块全部属于路网)。 - 64x48/256x192 保持开启,checksum 6042052/1284656962 不变。 - 新增 NoIsolatedStoneTiles / DecorativeStonesDisabledOnLargeMaps 回归;Desktop 134 -> 136。 --- src/LivingVillage.Desktop.Tests/MapGenTests.fs | 31 ++++++++++++++++++++ src/LivingVillage.Desktop/MapGen.fs | 40 +++++++++++++++----------- 2 files changed, 54 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/LivingVillage.Desktop.Tests/MapGenTests.fs b/src/LivingVillage.Desktop.Tests/MapGenTests.fs index 4a09865..f426a8a 100644 --- a/src/LivingVillage.Desktop.Tests/MapGenTests.fs +++ b/src/LivingVillage.Desktop.Tests/MapGenTests.fs @@ -261,6 +261,37 @@ type MapGenTests () = Assert.AreEqual(4, (MapGen.generateWithSize 64 48 (uint64 4242)).Buildings.Length) Assert.AreEqual(4, (MapGen.generateWithSize 256 192 (uint64 4242)).Buildings.Length) + // ---- P25:大图不产孤立石板 tile(装饰石块默认开启仅限小图) ---- + + [] + member _.DecorativeStonesDisabledOnLargeMaps () = + Assert.IsTrue((MapGen.paramsForSize 64 48 (uint64 1)).DecorativeStones) + Assert.IsTrue((MapGen.paramsForSize 256 192 (uint64 1)).DecorativeStones) + Assert.IsFalse((MapGen.paramsForSize 512 384 (uint64 1)).DecorativeStones) + + [] + member _.LargeMapHasNoIsolatedStoneTiles () = + let map = MapGen.generateWithSize 512 384 (uint64 4242) + let pathSet = System.Collections.Generic.HashSet(map.Paths |> List.map (fun (x, y) -> y * map.Width + x)) + let stone x y = map.Tiles.[y * map.Width + x] = int MapGen.GroundTile.Stone + let mutable isolated = 0 + for y in 0 .. map.Height - 1 do + for x in 0 .. map.Width - 1 do + if stone x y && not (pathSet.Contains(y * map.Width + x)) then + let neighbours = + [ (x + 1, y); (x - 1, y); (x, y + 1); (x, y - 1) ] + |> List.filter (fun (nx, ny) -> nx >= 0 && nx < map.Width && ny >= 0 && ny < map.Height) + |> List.filter (fun (nx, ny) -> stone nx ny) + |> List.length + if neighbours = 0 then isolated <- isolated + 1 + Assert.AreEqual(0, isolated) + // 装饰关闭后,大图所有石板都应属于路网。 + let stonesOutsidePaths = + [ for y in 0 .. map.Height - 1 do + for x in 0 .. map.Width - 1 do + if stone x y && not (pathSet.Contains(y * map.Width + x)) then yield (x, y) ] + Assert.AreEqual(0, stonesOutsidePaths.Length) + /// 锁定当前默认尺寸的生成结果,防止 P23 参数适配无意改变 64x48 / 256x192。 [] member _.DefaultSizeChecksumsArePinned () = diff --git a/src/LivingVillage.Desktop/MapGen.fs b/src/LivingVillage.Desktop/MapGen.fs index 1e63ed2..63b14bc 100644 --- a/src/LivingVillage.Desktop/MapGen.fs +++ b/src/LivingVillage.Desktop/MapGen.fs @@ -32,7 +32,9 @@ module MapGen = SpawnColumns: int SpawnRows: int /// P24:大图额外沿河民居数量(沿现有路网泊松散布)。默认 0,64x48/256x192 不受影响。 - ExtraRiversideHouses: int } + ExtraRiversideHouses: int + /// P25:是否放置孤立装饰石块。默认 true;大图关闭,避免草地上出现游离石板 tile。 + DecorativeStones: bool } /// P20 第二步:一条贯穿全图的东西向河道(每列恰好一段,宽度下限 RiverWidth)。 type River = @@ -73,7 +75,8 @@ module MapGen = CoreSide = 14 SpawnColumns = 5 SpawnRows = 6 - ExtraRiversideHouses = 0 } + ExtraRiversideHouses = 0 + DecorativeStones = true } /// P23 尺寸自适应参数:河道数量随高度增长(约每 96 行一条),其余沿用默认。 /// 64x48 / 256x192 仍得到 2 条河道,与既有输出逐字节一致;512x384 得到 4 条。 @@ -85,7 +88,8 @@ module MapGen = Width = width Height = height RiverCount = max base_.RiverCount (max 1 (height / 96)) - ExtraRiversideHouses = (if large then 10 else 0) } + ExtraRiversideHouses = (if large then 10 else 0) + DecorativeStones = not large } // ---- splitmix64 与 value noise(无外部依赖、无时钟) ---- @@ -203,20 +207,22 @@ module MapGen = let attempts = max 100 ((w * h) / 16) let mutable placed: (int * int) list = [] let mutable scatterRng = p.Seed ^^^ 0xC0FFEEUL - for _ in 1 .. attempts do - let nextState, raw = splitmix scatterRng - scatterRng <- nextState - let x = int (raw % uint64 w) - let y = int ((raw >>> 20) % uint64 h) - let far = - placed - |> List.forall (fun (px, py) -> - let dx = px - x - let dy = py - y - dx * dx + dy * dy > minSq) - if far && not (inCore x y) && tiles.[idx x y] = int GroundTile.Grass then - tiles.[idx x y] <- int GroundTile.Stone - placed <- (x, y) :: placed + // P25:大图禁用孤立装饰石块,避免草地上出现游离石板 tile(64x48/256x192 保持开启)。 + if p.DecorativeStones then + for _ in 1 .. attempts do + let nextState, raw = splitmix scatterRng + scatterRng <- nextState + let x = int (raw % uint64 w) + let y = int ((raw >>> 20) % uint64 h) + let far = + placed + |> List.forall (fun (px, py) -> + let dx = px - x + let dy = py - y + dx * dx + dy * dy > minSq) + if far && not (inCore x y) && tiles.[idx x y] = int GroundTile.Grass then + tiles.[idx x y] <- int GroundTile.Stone + placed <- (x, y) :: placed // 5) 交通:桥 + 石板路网 + 沿河民居(白墙黑瓦矩形色块,门朝远岸路)。 let mutable structureRng = p.Seed ^^^ 0x51AB1EUL -- cgit v1.2.3