146 lines
4.0 KiB
Go
146 lines
4.0 KiB
Go
package worldgen
|
||
|
||
import (
|
||
"bytes"
|
||
"path/filepath"
|
||
"testing"
|
||
|
||
"mc/internal/block"
|
||
)
|
||
|
||
// loadReg 加载共享注册表(blocks.json)。
|
||
func loadReg(t *testing.T) *block.Registry {
|
||
t.Helper()
|
||
r, err := block.Load(filepath.Join("..", "..", "assets", "config", "blocks.json"))
|
||
if err != nil {
|
||
t.Fatalf("加载方块注册表失败: %v", err)
|
||
}
|
||
return r
|
||
}
|
||
|
||
// TestDeterminism 验证生成确定性:同 seed 两次生成逐字节一致(世界生成.md §2.2)。
|
||
func TestDeterminism(t *testing.T) {
|
||
g, err := New(loadReg(t), 12345, 0.005, 0.01, 0.1)
|
||
if err != nil {
|
||
t.Fatalf("创建生成器失败: %v", err)
|
||
}
|
||
a := g.Generate(-1, 3)
|
||
b := g.Generate(-1, 3)
|
||
sa, sb := a.Snapshot(), b.Snapshot()
|
||
if !bytes.Equal(stateBytes(sa), stateBytes(sb)) {
|
||
t.Fatal("同 seed 生成结果不一致")
|
||
}
|
||
}
|
||
|
||
// stateBytes 把方块状态切片序列化为字节(比较用)。
|
||
func stateBytes(s []block.State) []byte {
|
||
out := make([]byte, len(s)*4)
|
||
for i, v := range s {
|
||
out[i*4] = byte(v >> 24)
|
||
out[i*4+1] = byte(v >> 16)
|
||
out[i*4+2] = byte(v >> 8)
|
||
out[i*4+3] = byte(v)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// TestTerrainStructure 验证地形结构:基岩层完整、地表为群系方块、海平面下灌水。
|
||
func TestTerrainStructure(t *testing.T) {
|
||
reg := loadReg(t)
|
||
bedrock, _ := reg.ID("bedrock")
|
||
water, _ := reg.ID("water")
|
||
air := uint16(0)
|
||
|
||
// caveDensity=1.0 关闭洞穴,便于验证灌水逻辑
|
||
g, err := New(reg, 777, 0.005, 0.01, 1.0)
|
||
if err != nil {
|
||
t.Fatalf("创建生成器失败: %v", err)
|
||
}
|
||
|
||
ch := g.Generate(0, 0)
|
||
// 基岩层:y=0 全部为基岩(世界生成.md §6:基岩层保留)
|
||
for lx := 0; lx < 16; lx++ {
|
||
for lz := 0; lz < 16; lz++ {
|
||
if id := ch.Block(lx, 0, lz).ID(); id != bedrock {
|
||
t.Fatalf("(%d,0,%d) 非基岩: ID=%d", lx, lz, id)
|
||
}
|
||
}
|
||
}
|
||
// 每个地形柱:海平面以下要么是水要么是地形方块,不允许空气
|
||
for lx := 0; lx < 16; lx++ {
|
||
for lz := 0; lz < 16; lz++ {
|
||
h := g.heightAt(int32(lx), int32(lz))
|
||
for y := 1; y < seaLevel; y++ {
|
||
id := ch.Block(lx, y, lz).ID()
|
||
if y > h {
|
||
if id != water {
|
||
t.Fatalf("(%d,%d,%d) 海平面下应为水,实际 ID=%d", lx, y, lz, id)
|
||
}
|
||
} else if id == air {
|
||
t.Fatalf("(%d,%d,%d) 地形柱内出现空气", lx, y, lz)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestVillage 村庄生成:存在村庄中心的区块应有木板/圆石建筑(村民系统.md §2)。
|
||
func TestVillage(t *testing.T) {
|
||
reg := loadReg(t)
|
||
planks, _ := reg.ID("oak_planks")
|
||
cobble, _ := reg.ID("cobblestone")
|
||
g, err := New(reg, 424242, 0.005, 0.01, 0.1)
|
||
if err != nil {
|
||
t.Fatalf("创建生成器失败: %v", err)
|
||
}
|
||
// 扫描网格点找村庄
|
||
found := 0
|
||
for gx := int32(-2); gx <= 2; gx++ {
|
||
for gz := int32(-2); gz <= 2; gz++ {
|
||
vx, vz := g.villageCenter(gx, gz)
|
||
b := g.biomeAt(vx, vz)
|
||
if b != biomePlains && b != biomeForest {
|
||
continue
|
||
}
|
||
cx, cz := floorDiv32(vx, 16), floorDiv32(vz, 16)
|
||
ch := g.Generate(cx, cz)
|
||
plankCnt, cobbleCnt := 0, 0
|
||
snap := ch.Snapshot()
|
||
for i := 0; i < 16*16*256; i++ {
|
||
switch snap[i].ID() {
|
||
case planks:
|
||
plankCnt++
|
||
case cobble:
|
||
cobbleCnt++
|
||
}
|
||
}
|
||
// 建筑跨区块:中心区块至少包含水井(圆石)与部分房屋(木板)
|
||
if plankCnt < 10 || cobbleCnt == 0 {
|
||
t.Fatalf("村庄区块 (%d,%d) 建筑过少: 木板 %d 圆石 %d", cx, cz, plankCnt, cobbleCnt)
|
||
}
|
||
found++
|
||
}
|
||
}
|
||
if found == 0 {
|
||
t.Fatal("5×5 网格点内未发现村庄")
|
||
}
|
||
}
|
||
|
||
// TestOres 验证矿物只出现在规定深度区间(挖矿与矿物.md §2:y=100 只允许石头/煤矿)。
|
||
func TestOres(t *testing.T) {
|
||
reg := loadReg(t)
|
||
g, err := New(reg, 4242, 0.005, 0.01, 0.1)
|
||
if err != nil {
|
||
t.Fatalf("创建生成器失败: %v", err)
|
||
}
|
||
stone, _ := reg.ID("stone")
|
||
coal, _ := reg.ID("coal_ore")
|
||
for i := 0; i < 2000; i++ {
|
||
s := g.oreAt(int32(i), 100, int32(i)) // y=100:只允许石头或煤矿
|
||
id := s.ID()
|
||
if id != stone && id != coal {
|
||
t.Fatalf("y=100 出现异常方块: ID=%d", id)
|
||
}
|
||
}
|
||
}
|