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) } } }