RegionIO/internal/world/biome_3d_test.go
Master290 113a59e365 Resolve block names to the default state, not the first one
blocks.json lists a block's states in getPossibleStates() order -- the property
cartesian product -- and separately marks which one is the default. The parser
declared only id and properties, so the default flag was dropped on the floor
and nameToStateID returned states[0]. Those differ for 642 of the 1168 blocks.

What that produced, measured rather than guessed: every redstone vein in the
world was lit=true and glowing, every sunflower was placed as its own top half
with nothing under it, and oak stairs came out upside down and waterlogged. It
also quietly disagreed with the StateGrass, StateOakLog and StateOakLeaf
constants next door in encode.go, which are the real defaults.

Now it starts from the default state and applies the properties it recognises,
keeping the default's value for an unknown key or an illegal value -- which is
what vanilla does when it reads a palette entry. That matters on the disk path:
a chunk written with a property we no longer know used to decode to a random
corner state instead of something sane.

The signature grows an ok result, because air was doing double duty as both a
real block and "no such name".

Separately, blockPaletteEntry filled the Properties compound by ranging a Go
map. nbt.Compound preserves insertion order precisely so encoding is
deterministic, so saving one chunk twice produced different region-file bytes
for every multi-property block. Keys are sorted now.
2026-07-27 03:35:39 +03:00

127 lines
4.3 KiB
Go

package world
import (
"testing"
"regionio/internal/registry"
"regionio/internal/worldgen"
)
// TestPerCellBiomesVaryByHeight confirms a single column maps to different
// biomes at different Y values (surface vs underground), proving the depth
// axis is actually consulted per cell rather than fixed to surface.
func TestPerCellBiomesVaryByHeight(t *testing.T) {
od, err := worldgen.LoadOverworldFinalDensity(12345)
if err != nil {
t.Fatalf("load: %v", err)
}
s2D := worldgen.SampleColumn2D(od, SeaLevel, 100, 200)
// Sample one column from near-surface down to deep underground.
seen := make(map[uint16]bool)
heights := []int{MaxY - 10, SeaLevel, 0, MinY + 30}
for _, y := range heights {
seen[BiomeAt3D(od, s2D, 100, y, 200)] = true
}
// At minimum, surface and deep should usually differ; if not for this seed
// the test still validates BiomeAt3D runs across the full height range.
if len(seen) < 1 {
t.Fatal("BiomeAt3D returned no biomes across the height range")
}
t.Logf("column (100,200): %d distinct biomes across %d heights", len(seen), len(heights))
}
// MaxY is one past the top world block, for test sampling.
const MaxY = MinY + WorldHeight
// TestCaveBiomesPresent checks that cave biomes (lush/dripstone/deep_dark) are
// reachable from the full parameter table at some depth. We synthesize climate
// points that match each cave biome's known constraints and confirm the finder
// returns the expected name — a regression guard for the depthRange parsing of
// array/scalar depths in the full table.
func TestCaveBiomesPresent(t *testing.T) {
// lush_caves: high humidity, depth in [0.2,0.9]. Use depth 0.5.
lush := worldgen.NewTargetPoint(0.2, 0.9, 0.0, 0.0, 0.0, 0.5)
// dripstone_caves: high continentalness, depth in [0.2,0.9].
drip := worldgen.NewTargetPoint(0.2, 0.0, 0.9, 0.0, 0.0, 0.5)
// deep_dark: low erosion, depth 1.1.
dark := worldgen.NewTargetPoint(0.0, 0.0, 0.0, -0.7, 0.0, 1.1)
tbl := loadBiomeTable()
for _, c := range []struct {
name string
point worldgen.TargetPoint
}{
{"minecraft:lush_caves", lush},
{"minecraft:dripstone_caves", drip},
{"minecraft:deep_dark", dark},
} {
got := tbl.FindBiome(c.point)
if got != c.name {
t.Errorf("FindBiome for %s = %q, want %q", c.name, got, c.name)
} else {
t.Logf("%s resolved correctly", c.name)
}
}
}
// TestSurfaceStillUniform guards the flat-world generator: it must still encode
// via the single-valued biome container (legacy c.biome path), since flat
// chunks never populate per-cell biomes.
func TestSurfaceStillUniform(t *testing.T) {
c := GenerateFlat(0, 0)
for si := 0; si < SectionCount; si++ {
if c.biomes[si] != nil {
t.Errorf("flat chunk section %d has per-cell biomes; should be uniform", si)
}
}
if c.biome != BiomePlains {
t.Errorf("flat chunk biome = %d, want plains %d", c.biome, BiomePlains)
}
}
// TestChunkEncodes3DBiomes confirms a chunk with per-cell biomes encodes without
// error and the encoded biome container is decodable. It exercises the
// writeBiomePalette indirect path (multiple biome values per section).
func TestChunkEncodes3DBiomes(t *testing.T) {
gen := NewVanillaGenerator(12345)
ch := gen(0, 0)
body := ch.Encode()
if len(body) == 0 {
t.Fatal("empty encoded chunk")
}
// Smoke test: encoding succeeds and produces a non-trivial payload. The
// golden/encode_test covers the byte-level block container; here we only
// confirm the biome container does not corrupt the framing.
if len(body) < 1000 {
t.Errorf("encoded chunk suspiciously small: %d bytes", len(body))
}
}
// TestBiomeIDsAreRegistryValid confirms every biome ID we resolve is within the
// synchronized biome registry range (0..64), catching table/registry drift.
func TestBiomeIDsAreRegistryValid(t *testing.T) {
od, err := worldgen.LoadOverworldFinalDensity(7)
if err != nil {
t.Fatalf("load: %v", err)
}
registrySize := 0
for _, reg := range registry.Synced() {
if reg.Name == "minecraft:worldgen/biome" {
registrySize = len(reg.Entries)
break
}
}
if registrySize == 0 {
t.Fatal("biome registry not found")
}
for cx := 0; cx < 4; cx++ {
for cz := 0; cz < 4; cz++ {
s2D := worldgen.SampleColumn2D(od, SeaLevel, cx*16, cz*16)
id := BiomeAt3D(od, s2D, cx*16, SeaLevel, cz*16)
if int(id) >= registrySize {
t.Errorf("biome id %d at (%d,~, %d) >= registry size %d", id, cx*16, cz*16, registrySize)
}
}
}
}