1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
namespace LivingVillage.Desktop.Tests
open Microsoft.VisualStudio.TestTools.UnitTesting
open LivingVillage.Kernel
open LivingVillage.Desktop
/// P20 第一步回归:确定性生成器 + 尺寸参数化 + 30 spawn 核心可达 + 视口裁剪。
[<TestClass>]
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<int>()
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)
[<TestMethod>]
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<uint64>(a.Seed, b.Seed)
Assert.AreEqual<int64>(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<int64>(checksum large1, checksum large2)
[<TestMethod>]
member _.DifferentSeedsProduceDifferentTerrain () =
let a = MapGen.generateWithSize 256 192 (uint64 1)
let b = MapGen.generateWithSize 256 192 (uint64 2)
Assert.AreNotEqual<int64>(checksum a, checksum b, "different seeds must differ")
[<TestMethod>]
member _.LargeMapThirtySpawnsAllReachCore () =
let map = MapGen.generateWithSize 256 192 (uint64 4242)
Assert.AreEqual<int>(256, map.Width)
Assert.AreEqual<int>(192, map.Height)
Assert.AreEqual<int>(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>(int MapGen.GroundTile.Water, map.Tiles.[y * map.Width + x], sprintf "spawn (%d,%d) must be walkable" x y)
[<TestMethod>]
member _.DefaultSizeMatchesLegacyBounds () =
let map = MapGen.generate (MapGen.defaultParams (uint64 4242))
Assert.AreEqual<int>(64, map.Width)
Assert.AreEqual<int>(48, map.Height)
Assert.AreEqual<int>(64 * 48, map.Tiles.Length)
Assert.IsTrue(map.ReachabilityOk)
Assert.AreEqual<int>(30, map.Spawns.Length)
[<TestMethod>]
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<int>((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<int>(0, ox0)
Assert.AreEqual<int>(0, oy0)
Assert.AreEqual<int>(1280 / Sim.tilePixels + 1, ox1)
Assert.AreEqual<int>(720 / Sim.tilePixels + 1, oy1)
// ---- P20 第二步:河道连续性 + 桥/路/建筑 + 交通可达 ----
[<TestMethod>]
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)
[<TestMethod>]
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")
[<TestMethod>]
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")
[<TestMethod>]
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")
// ---- P20 第三步:序列化确定性 + 默认可玩世界接入生成器站位 ----
[<TestMethod>]
member _.SameSeedSerializationIsByteIdentical () =
let a = MapGen.generateWithSize 256 192 (uint64 4242)
let b = MapGen.generateWithSize 256 192 (uint64 4242)
Assert.AreEqual<string>(MapGen.serialize a, MapGen.serialize b)
let c = MapGen.generateWithSize 256 192 (uint64 7)
Assert.AreNotEqual<string>(MapGen.serialize a, MapGen.serialize c)
[<TestMethod>]
member _.MapWorldPlacementUsesReachableCoreSpawns () =
let map = MapGen.generateWithSize 256 192 (uint64 4242)
let world = WorldBootstrap.initialWorldInMap false 42UL Sim.npcCount map
Assert.AreEqual<int>(Sim.npcCount, world.Npcs.Length)
let visited = MapGen.floodFill map (fst map.Spawns.Head) (snd map.Spawns.Head)
for npc in world.Npcs do
let tx = int npc.Pos.X / Sim.tilePixels
let ty = int npc.Pos.Y / Sim.tilePixels
Assert.IsTrue(visited.[ty * map.Width + tx], sprintf "npc tile (%d,%d) must be reachable from the core" tx ty)
let ax = int world.Avatar.Pos.X / Sim.tilePixels
let ay = int world.Avatar.Pos.Y / Sim.tilePixels
Assert.IsTrue(visited.[ay * map.Width + ax], "avatar spawn must sit in the reachable core")
Assert.IsTrue(ax >= map.Core.MinX && ax <= map.Core.MaxX && ay >= map.Core.MinY && ay <= map.Core.MaxY, "avatar spawn must sit in the core")
[<TestMethod>]
member _.VisibleRangeClampsAtLegacyMapEdges () =
let x0, y0, x1, y1 = MapGen.visibleTileRange 64 48 0 0 2048 1536
Assert.AreEqual<int>(0, x0)
Assert.AreEqual<int>(0, y0)
Assert.AreEqual<int>(63, x1)
Assert.AreEqual<int>(47, y1)
Assert.AreEqual<int>(64 * 48, MapGen.visibleTileCount 64 48 0 0 2048 1536)
|