namespace LivingVillage.Desktop.Tests open Microsoft.VisualStudio.TestTools.UnitTesting open LivingVillage.Kernel open LivingVillage.Desktop /// P20 第一步回归:确定性生成器 + 尺寸参数化 + 30 spawn 核心可达 + 视口裁剪。 [] type MapGenTests () = let tilesEqual (a: MapGen.Result) (b: MapGen.Result) (message: string) = if a.Tiles.Length <> b.Tiles.Length then Assert.Fail(sprintf "%s: tile count %d <> %d" message a.Tiles.Length b.Tiles.Length) for i in 0 .. a.Tiles.Length - 1 do if a.Tiles.[i] <> b.Tiles.[i] then Assert.Fail(sprintf "%s: tile %d = %d, expected %d" message i a.Tiles.[i] b.Tiles.[i]) let checksum (map: MapGen.Result) : int64 = map.Tiles |> Array.mapi (fun i v -> int64 i * int64 (v + 1)) |> Array.sum let waterRunsInColumn (map: MapGen.Result) (x: int) : int list = let runs = ResizeArray() let mutable run = 0 for y in 0 .. map.Height - 1 do if map.Tiles.[y * map.Width + x] = int MapGen.GroundTile.Water then run <- run + 1 elif run > 0 then runs.Add run run <- 0 if run > 0 then runs.Add run List.ofSeq runs let waterNeighbourCount (map: MapGen.Result) (x: int) (y: int) : int = [ (1, 0); (-1, 0); (0, 1); (0, -1) ] |> List.sumBy (fun (dx, dy) -> let nx, ny = x + dx, y + dy if nx >= 0 && nx < map.Width && ny >= 0 && ny < map.Height && map.Tiles.[ny * map.Width + nx] = int MapGen.GroundTile.Water then 1 else 0) [] member _.SameSeedGeneratesByteIdenticalTerrain () = let a = MapGen.generateWithSize 64 48 (uint64 4242) let b = MapGen.generateWithSize 64 48 (uint64 4242) tilesEqual a b "64x48 same seed" Assert.AreEqual(a.Seed, b.Seed) Assert.AreEqual(checksum a, checksum b) let large1 = MapGen.generateWithSize 256 192 (uint64 7) let large2 = MapGen.generateWithSize 256 192 (uint64 7) tilesEqual large1 large2 "256x192 same seed" Assert.AreEqual(checksum large1, checksum large2) [] member _.DifferentSeedsProduceDifferentTerrain () = let a = MapGen.generateWithSize 256 192 (uint64 1) let b = MapGen.generateWithSize 256 192 (uint64 2) Assert.AreNotEqual(checksum a, checksum b, "different seeds must differ") [] member _.LargeMapThirtySpawnsAllReachCore () = let map = MapGen.generateWithSize 256 192 (uint64 4242) Assert.AreEqual(256, map.Width) Assert.AreEqual(192, map.Height) Assert.AreEqual(30, map.Spawns.Length) Assert.IsTrue(map.ReachabilityOk, "all 30 spawns must reach the core") Assert.IsTrue(map.ReachableTiles > 30, "core region must be walkable") for (x, y) in map.Spawns do Assert.AreNotEqual(int MapGen.GroundTile.Water, map.Tiles.[y * map.Width + x], sprintf "spawn (%d,%d) must be walkable" x y) [] member _.DefaultSizeMatchesLegacyBounds () = let map = MapGen.generate (MapGen.defaultParams (uint64 4242)) Assert.AreEqual(64, map.Width) Assert.AreEqual(48, map.Height) Assert.AreEqual(64 * 48, map.Tiles.Length) Assert.IsTrue(map.ReachabilityOk) Assert.AreEqual(30, map.Spawns.Length) [] member _.VisibleRangeCullsLargeMapToViewport () = let total = 256 * 192 let x0, y0, x1, y1 = MapGen.visibleTileRange 256 192 1000 1000 1280 720 let count = MapGen.visibleTileCount 256 192 1000 1000 1280 720 Assert.AreEqual((x1 - x0 + 1) * (y1 - y0 + 1), count) Assert.IsTrue(count < total, "viewport culling must draw strictly fewer tiles than the whole map") let ox0, oy0, ox1, oy1 = MapGen.visibleTileRange 256 192 0 0 1280 720 Assert.AreEqual(0, ox0) Assert.AreEqual(0, oy0) Assert.AreEqual(1280 / Sim.tilePixels + 1, ox1) Assert.AreEqual(720 / Sim.tilePixels + 1, oy1) // ---- P20 第二步:河道连续性 + 桥/路/建筑 + 交通可达 ---- [] member _.RiversAreContinuousWithoutGapsOrIsolatedTiles () = let map = MapGen.generateWithSize 256 192 (uint64 4242) Assert.IsTrue(map.Rivers.Length >= 1, "at least one river band") let minWidth = map.Rivers |> List.map (fun river -> river.Width) |> List.min Assert.IsTrue(minWidth >= 3, "river width lower bound") for x in 1 .. map.Width - 2 do let runs = waterRunsInColumn map x if runs.Length <> map.Rivers.Length then Assert.Fail(sprintf "column %d must hold exactly %d contiguous runs, saw %d" x map.Rivers.Length runs.Length) for run in runs do Assert.IsTrue(run >= minWidth, sprintf "column %d has a run of %d < min width %d" x run minWidth) for y in 0 .. map.Height - 1 do for x in 0 .. map.Width - 1 do if map.Tiles.[y * map.Width + x] = int MapGen.GroundTile.Water then Assert.IsTrue(waterNeighbourCount map x y >= 2, sprintf "water tile (%d,%d) is isolated" x y) [] member _.StructuresContainBridgesRoadsAndBuildingsAlongRivers () = let map = MapGen.generateWithSize 256 192 (uint64 4242) Assert.IsTrue(map.Bridges.Length >= map.Rivers.Length, "at least one bridge per river") Assert.IsTrue(map.Paths.Length > 0, "stone road network exists") Assert.IsTrue(map.Buildings.Length >= 4, "at least four riverside building blocks") for building in map.Buildings do Assert.IsTrue(MapGen.isWalkable map building.DoorX building.DoorY, sprintf "door (%d,%d) must be walkable" building.DoorX building.DoorY) let nearWater = [ for y in max 0 (building.Top - 12) .. min (map.Height - 1) (building.Top + building.Height + 12) do for x in max 0 (building.Left - 12) .. min (map.Width - 1) (building.Left + building.Width + 12) -> map.Tiles.[y * map.Width + x] = int MapGen.GroundTile.Water ] |> List.exists id Assert.IsTrue(nearWater, "building block must sit along a river") [] member _.BridgesKeepBothRiverBanksReachableFromSpawns () = let map = MapGen.generateWithSize 256 192 (uint64 4242) let startX, startY = map.Spawns.Head let visited = MapGen.floodFill map startX startY for (x, y) in map.Spawns do Assert.IsTrue(visited.[y * map.Width + x], sprintf "spawn (%d,%d) unreachable" x y) for building in map.Buildings do Assert.IsTrue(visited.[building.DoorY * map.Width + building.DoorX], "building door unreachable via bridge/road") for (x, y) in map.Bridges do Assert.IsTrue(visited.[y * map.Width + x], sprintf "bridge tile (%d,%d) unreachable" x y) Assert.IsTrue(map.BridgeCrossingsOk, "each bridge must connect both river banks") [] member _.StructuresAreDeterministicAcrossRuns () = let a = MapGen.generateWithSize 256 192 (uint64 4242) let b = MapGen.generateWithSize 256 192 (uint64 4242) Assert.IsTrue(a.Rivers = b.Rivers) Assert.IsTrue(a.Bridges = b.Bridges) Assert.IsTrue(a.Paths = b.Paths) Assert.IsTrue(a.Buildings = b.Buildings) let c = MapGen.generateWithSize 256 192 (uint64 7) Assert.IsTrue(a.Buildings <> c.Buildings || a.Bridges <> c.Bridges, "different seeds must relocate structures") [] member _.VisibleRangeClampsAtLegacyMapEdges () = let x0, y0, x1, y1 = MapGen.visibleTileRange 64 48 0 0 2048 1536 Assert.AreEqual(0, x0) Assert.AreEqual(0, y0) Assert.AreEqual(63, x1) Assert.AreEqual(47, y1) Assert.AreEqual(64 * 48, MapGen.visibleTileCount 64 48 0 0 2048 1536)