world: prove undecorated base terrain bit-exact against vanilla
TestVanillaBaseTerrainParity compares our noise+surface+carver+aquifer+ vein pipeline against a vanilla capture whose biomes carry no features and no structure sets: all 393,216 cells match. The plain-state fixture mismatches the base-terrain diagnostic used to attribute to carver or aquifer defects are therefore feature outputs wearing plain states — monster-room cave_air, geode voids, lava-lake lava — and its doc comment now says so.
This commit is contained in:
parent
f150d2e717
commit
f0a7cf815d
3 changed files with 190 additions and 13 deletions
|
|
@ -10,20 +10,20 @@ import (
|
|||
)
|
||||
|
||||
// base_terrain_diagnostic_test.go splits the vanilla fixture's block
|
||||
// mismatches into classes that name the responsible subsystem, instead of
|
||||
// leaving one undifferentiated pile. The ore-replay diagnostics measure how
|
||||
// feature placement drifts; this one answers why: which cells already differ
|
||||
// before any feature runs.
|
||||
// mismatches into classes by what could have written each side. The ore-replay
|
||||
// diagnostics measure how feature placement drifts; this one attributes it.
|
||||
//
|
||||
// Every fixture cell is compared against the undecorated base chunk. A cell
|
||||
// where both sides are plain states (air family, fluids, bedrock, stone,
|
||||
// deepslate) cannot be a feature write on either side — it is a direct
|
||||
// base-terrain defect (aquifer edge, carver boundary, surface rule) and comes
|
||||
// with sample coordinates for targeted fixes. Cells where exactly one side is
|
||||
// a recognizably feature-placed state count as a flip toward that side; the
|
||||
// remaining ambiguous pairs (disk outputs such as clay or gravel look like
|
||||
// natural terrain) are bucketed separately so they cannot pollute the plain
|
||||
// signal.
|
||||
// Every fixture cell is compared against the undecorated base chunk. Plain-
|
||||
// versus-plain pairs (air family, fluids, bedrock, stone, deepslate) used to
|
||||
// be read as direct base-terrain defects, but TestVanillaBaseTerrainParity
|
||||
// settled that question: against a vanilla capture whose biomes carry no
|
||||
// features, our undecorated pipeline matches exactly. Plain pairs here are
|
||||
// therefore feature outputs wearing plain states — monster-room cave_air,
|
||||
// geode voids, lava-lake lava — and the sample coordinates point at the
|
||||
// feature, not at the noise stack. Cells where exactly one side is a
|
||||
// recognizably feature-placed state count as a flip toward that side; the
|
||||
// remaining ambiguous pairs (disk outputs such as clay look like natural
|
||||
// terrain) are bucketed separately.
|
||||
//
|
||||
// Run with REGIONIO_BASE_TERRAIN_DIAGNOSTIC=1.
|
||||
|
||||
|
|
|
|||
BIN
internal/world/testdata/vanilla_base_12345.bin
vendored
Normal file
BIN
internal/world/testdata/vanilla_base_12345.bin
vendored
Normal file
Binary file not shown.
177
internal/world/vanilla_base_test.go
Normal file
177
internal/world/vanilla_base_test.go
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"regionio/internal/worldgen"
|
||||
)
|
||||
|
||||
// vanillaBaseFixture holds vanilla overworld terrain captured with every biome
|
||||
// feature stage and structure set emptied out (cmd/vanillacapture
|
||||
// -featureless -blocks-only). It is ground truth for exactly what our
|
||||
// undecorated pipeline reproduces: density terrain, surface rules, carvers,
|
||||
// aquifers, and noise-router veins — with none of the feature drift that makes
|
||||
// the full fixture's mismatches hard to attribute.
|
||||
const vanillaBaseFixture = "testdata/vanilla_base_12345.bin"
|
||||
|
||||
func TestVanillaBaseTerrainParity(t *testing.T) {
|
||||
f, err := os.Open(vanillaBaseFixture)
|
||||
if err != nil {
|
||||
if os.Getenv("REGIONIO_REQUIRE_BASE_PARITY") == "1" {
|
||||
t.Fatalf("required base-terrain fixture: %v", err)
|
||||
}
|
||||
t.Skip("base-terrain fixture not installed; run cmd/vanillacapture -featureless -blocks-only")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var header [16]byte
|
||||
if _, err := io.ReadFull(f, header[:]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(header[:8]) != "RIOBASE1" {
|
||||
t.Fatalf("bad base fixture magic %q", header[:8])
|
||||
}
|
||||
seed := int64(binary.BigEndian.Uint64(header[8:16]))
|
||||
if seed != 12345 {
|
||||
t.Fatalf("base fixture seed=%d", seed)
|
||||
}
|
||||
|
||||
type baseChunk struct {
|
||||
x, z int32
|
||||
blocks []uint16
|
||||
}
|
||||
var chunks []baseChunk
|
||||
for {
|
||||
var coords [8]byte
|
||||
if _, err := io.ReadFull(f, coords[:]); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
chunk := baseChunk{
|
||||
x: int32(binary.BigEndian.Uint32(coords[:4])),
|
||||
z: int32(binary.BigEndian.Uint32(coords[4:])),
|
||||
blocks: make([]uint16, 16*16*WorldHeight),
|
||||
}
|
||||
var state [2]byte
|
||||
for index := range chunk.blocks {
|
||||
if _, err := io.ReadFull(f, state[:]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
chunk.blocks[index] = binary.BigEndian.Uint16(state[:])
|
||||
}
|
||||
chunks = append(chunks, chunk)
|
||||
}
|
||||
if len(chunks) == 0 {
|
||||
t.Fatal("empty base fixture")
|
||||
}
|
||||
|
||||
// The capture must actually be featureless: any state that only a feature
|
||||
// places means the derived datapack did not load and this test would
|
||||
// compare against decorated terrain without saying so. Copper and
|
||||
// deepslate iron ores are exempt — the noise-router OreVeinifier owns
|
||||
// those, and the veins belong to base terrain.
|
||||
veinOres := map[string]bool{
|
||||
"minecraft:copper_ore": true,
|
||||
"minecraft:deepslate_copper_ore": true,
|
||||
"minecraft:iron_ore": true,
|
||||
"minecraft:deepslate_iron_ore": true,
|
||||
}
|
||||
for _, chunk := range chunks {
|
||||
for _, state := range chunk.blocks {
|
||||
name := stateLabel(state)
|
||||
featureOnly := strings.HasSuffix(name, "_log") ||
|
||||
strings.HasSuffix(name, "_leaves") || strings.HasSuffix(name, "_planks") ||
|
||||
name == "minecraft:moss_block" || name == "minecraft:magma_block" ||
|
||||
name == "minecraft:calcite" || name == "minecraft:smooth_basalt" ||
|
||||
name == "minecraft:amethyst_block" || name == "minecraft:budding_amethyst" ||
|
||||
(strings.HasSuffix(name, "_ore") && !veinOres[name])
|
||||
if featureOnly {
|
||||
t.Fatalf("capture at (%d,%d) contains %s; the featureless datapack did not load",
|
||||
chunk.x, chunk.z, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
od, err := worldgen.LoadOverworldFinalDensity(seed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fluidPicker := worldgen.OverworldFluidPicker(od.SeaLevel)
|
||||
veins := worldgen.NewOreVeinifier(od)
|
||||
carver, err := worldgen.NewCarver(od, seed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
initCarverReplaceable(carver.ReplaceableBlocks())
|
||||
|
||||
var total, exact int
|
||||
pairs := make(map[[2]uint16]int)
|
||||
samples := make(map[[2]uint16][]string)
|
||||
yRange := make(map[[2]uint16][2]int)
|
||||
for _, chunk := range chunks {
|
||||
got := generateVanillaWithoutDecoration(od, fluidPicker, veins, carver, seed, chunk.x, chunk.z)
|
||||
index := 0
|
||||
for y := MinY; y < MinY+WorldHeight; y++ {
|
||||
for z := 0; z < 16; z++ {
|
||||
for x := 0; x < 16; x++ {
|
||||
ours := got.GetBlock(x, y, z)
|
||||
want := chunk.blocks[index]
|
||||
index++
|
||||
total++
|
||||
if ours == want {
|
||||
exact++
|
||||
continue
|
||||
}
|
||||
key := [2]uint16{ours, want}
|
||||
pairs[key]++
|
||||
rng := yRange[key]
|
||||
if rng[0] == 0 || y < rng[0] {
|
||||
rng[0] = y
|
||||
}
|
||||
if y > rng[1] {
|
||||
rng[1] = y
|
||||
}
|
||||
yRange[key] = rng
|
||||
if len(samples[key]) < 10 {
|
||||
absX, absZ := int(chunk.x)*16+x, int(chunk.z)*16+z
|
||||
samples[key] = append(samples[key], fmt.Sprintf("(%d,%d,%d)", absX, y, absZ))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys := make([][2]uint16, 0, len(pairs))
|
||||
for key := range pairs {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
if pairs[keys[i]] != pairs[keys[j]] {
|
||||
return pairs[keys[i]] > pairs[keys[j]]
|
||||
}
|
||||
return stateLabel(keys[i][0])+stateLabel(keys[i][1]) < stateLabel(keys[j][0])+stateLabel(keys[j][1])
|
||||
})
|
||||
t.Logf("undecorated base parity: exact %d/%d (%.3f%%), mismatched pairs %d",
|
||||
exact, total, percent(exact, total), len(pairs))
|
||||
const maxPairs = 25
|
||||
for i, key := range keys {
|
||||
if i >= maxPairs {
|
||||
t.Logf("... %d more pairs", len(keys)-maxPairs)
|
||||
break
|
||||
}
|
||||
rng := yRange[key]
|
||||
t.Logf("base %s -> %s: %d, y=%d..%d, samples %v",
|
||||
stateLabel(key[0]), stateLabel(key[1]), pairs[key], rng[0], rng[1], samples[key])
|
||||
}
|
||||
if os.Getenv("REGIONIO_REQUIRE_BASE_PARITY") == "1" && exact != total {
|
||||
t.Fatalf("base-terrain parity failed: %d mismatches", total-exact)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue