package save import ( "path/filepath" "testing" "time" "mc/internal/block" "mc/internal/world" "mc/internal/worldgen" ) // loadSaveReg 加载注册表。 func loadSaveReg(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 } // newGen 创建生成器。 func newGen(t *testing.T, reg *block.Registry, seed int64) *worldgen.Generator { t.Helper() g, err := worldgen.New(reg, seed, 0.005, 0.01, 0.1) if err != nil { t.Fatalf("创建生成器失败: %v", err) } return g } // TestChunkRoundtrip 区块序列化往返(存档格式.md §4)。 func TestChunkRoundtrip(t *testing.T) { reg := loadSaveReg(t) g := newGen(t, reg, 777) c := g.Generate(3, -5) c.SetBlockLight(8, 70, 8, 13) dec, err := DecodeChunk(EncodeChunk(c)) if err != nil { t.Fatalf("反序列化失败: %v", err) } if dec.CX != 3 || dec.CZ != -5 { t.Fatalf("坐标不一致: %d,%d", dec.CX, dec.CZ) } if dec.Block(8, 70, 8) != c.Block(8, 70, 8) { t.Fatal("方块数据不一致") } if dec.BlockLight(8, 70, 8) != 13 { t.Fatalf("方块光不一致: %d", dec.BlockLight(8, 70, 8)) } if dec.SkyLight(8, 100, 8) != c.SkyLight(8, 100, 8) { t.Fatal("天空光不一致") } } // TestRegionWriteRead 区域文件写入/读取(含 CRC 与跨重启重开)。 func TestRegionWriteRead(t *testing.T) { reg := loadSaveReg(t) g := newGen(t, reg, 99) dir := t.TempDir() data := EncodeChunk(g.Generate(1, 1)) region, err := OpenRegion(dir, 0, 0) if err != nil { t.Fatalf("打开区域失败: %v", err) } if err := region.WriteChunk(1, 1, data); err != nil { t.Fatalf("写区块失败: %v", err) } // 模拟重启:重新打开区域 region2, err := OpenRegion(dir, 0, 0) if err != nil { t.Fatalf("重开区域失败: %v", err) } got, ok, err := region2.ReadChunk(1, 1) if err != nil || !ok { t.Fatalf("读区块失败: ok=%v err=%v", ok, err) } if string(got) != string(data) { t.Fatal("读取数据不一致") } // 未保存区块应返回 ok=false if _, ok, _ := region2.ReadChunk(2, 2); ok { t.Fatal("未保存区块应返回 false") } } // TestSaveLoadWorld 世界级存取:放火把 → 保存 → 载入 → 火把与光仍在。 func TestSaveLoadWorld(t *testing.T) { reg := loadSaveReg(t) seed := int64(2024) g := newGen(t, reg, seed) w, err := world.New(reg, g, 3) if err != nil { t.Fatalf("创建世界失败: %v", err) } defer w.Close() // 生成并等待区块 deadline := time.Now().Add(15 * time.Second) for time.Now().Before(deadline) { w.Update(8, 64, 8, 8*time.Millisecond) if w.Block(8, 0, 8) != block.Air { break } time.Sleep(2 * time.Millisecond) } // 放一个火把 torch, _ := reg.ID("torch") var surf int32 for y := int32(255); y >= 0; y-- { if w.Block(8, y, 8) != block.Air { surf = y break } } w.SetBlock(8, surf+1, 8, block.NewState(torch, 0)) dir := t.TempDir() if err := SaveWorld(w, dir, "TestWorld", seed, 1000); err != nil { t.Fatalf("保存失败: %v", err) } // 载入到新世界 g2 := newGen(t, reg, seed) w2, err := world.New(reg, g2, 3) if err != nil { t.Fatalf("创建世界失败: %v", err) } defer w2.Close() if gotSeed, err := LoadWorld(w2, dir, "TestWorld"); err != nil { t.Fatalf("载入失败: %v", err) } else if gotSeed != seed { t.Fatalf("种子不一致: %d", gotSeed) } // 火把及其光 if s := w2.Block(8, surf+1, 8); s.ID() != torch { t.Fatalf("火把丢失: %v", s) } if l := w2.BlockLight(7, surf+1, 8); l != 13 { t.Fatalf("火把光丢失: %d", l) } }