Commit exhaustive vanilla worldgen fixture

This commit is contained in:
Daniar Mannanov 2026-08-10 23:54:04 +03:00
parent 064f0f1cca
commit be8f030698
9 changed files with 223 additions and 14 deletions

View file

@ -29,3 +29,13 @@ jobs:
go-version: '1.26.x' go-version: '1.26.x'
cache: true cache: true
- run: go test -race ./... - run: go test -race ./...
parity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26.x'
cache: true
- run: go test ./internal/world -run TestVanillaBlockParity -count=1 -v

View file

@ -174,7 +174,9 @@ run at all.
`cmd/vanillacapture` runs the official bundler jar in an isolated temporary world, force-loads fixed `cmd/vanillacapture` runs the official bundler jar in an isolated temporary world, force-loads fixed
chunks, reads their region files, and writes `internal/world/testdata/vanilla_overworld_12345.bin`. chunks, reads their region files, and writes `internal/world/testdata/vanilla_overworld_12345.bin`.
The fixture contains every block state and 4x4x4 biome cell. Java 25 is required. `make parity` The fixture contains every block state, 4x4x4 biome cell, and three heightmaps. Java 25 is required. `make parity`
requires the fixture and fails when it is absent; ordinary `go test ./...` skips that one test so a requires the fixture and fails when it is absent; ordinary `go test ./...` skips that one test so a
fresh checkout remains buildable without Mojang's non-redistributable jar. The older optional fresh checkout remains buildable without Mojang's non-redistributable jar. Once the fixture is
committed, ordinary CI guards the measured baseline while `make parity` requires exact equality.
The older optional
`/tmp/vanilla_ground.json` height report remains diagnostic only. `/tmp/vanilla_ground.json` height report remains diagnostic only.

View file

@ -69,8 +69,9 @@ session movement/broadcasts. A 16-client lifecycle test exercises overlapping
ticket ownership, bounded global frame work, packet output, and cleanup after ticket ownership, bounded global frame work, packet output, and cleanup after
disconnect. Lighting tests compare the initial flat chunk and a 31x31x31 disconnect. Lighting tests compare the initial flat chunk and a 31x31x31
glowstone propagation volume against fixtures captured from the official glowstone propagation volume against fixtures captured from the official
vanilla 26.1.2 server. Optional terrain parity diagnostics compare surface vanilla 26.1.2 server. The committed overworld fixture exhaustively compares
heights against `/tmp/vanilla_ground.json` when that capture is present. 393,216 block states, 6,144 biome cells, and three heightmaps across four fixed
chunks; CI guards the current baseline and `make parity` requires exact equality.
## v0.4 scope ## v0.4 scope

View file

@ -20,7 +20,7 @@ import (
"regionio/internal/world" "regionio/internal/world"
) )
const fixtureMagic = "RIOPAR01" const fixtureMagic = "RIOPAR02"
type chunkPos struct{ x, z int32 } type chunkPos struct{ x, z int32 }
@ -63,7 +63,8 @@ func main() {
if err := runServer(*java, jar, work, chunks); err != nil { if err := runServer(*java, jar, work, chunks); err != nil {
fatal(err) fatal(err)
} }
if err := writeFixture(filepath.Join(work, "world"), *output, *seed, chunks); err != nil { overworld := filepath.Join(work, "world", "dimensions", "minecraft", "overworld")
if err := writeFixture(overworld, *output, *seed, chunks); err != nil {
fatal(err) fatal(err)
} }
fmt.Printf("wrote %s: seed %d, %d chunks\n", *output, *seed, len(chunks)) fmt.Printf("wrote %s: seed %d, %d chunks\n", *output, *seed, len(chunks))
@ -233,6 +234,18 @@ func writeFixture(worldDir, output string, seed int64, chunks []chunkPos) error
} }
} }
} }
heightmaps, err := store.LoadVanillaHeightmaps(pos.x, pos.z)
if err != nil {
return fmt.Errorf("load vanilla heightmaps (%d,%d): %w", pos.x, pos.z, err)
}
for _, heightmap := range heightmaps {
for _, y := range heightmap {
binary.BigEndian.PutUint16(value[:2], uint16(y))
if _, err := f.Write(value[:2]); err != nil {
return err
}
}
}
} }
if err := f.Sync(); err != nil { if err := f.Sync(); err != nil {
return err return err

View file

@ -94,6 +94,39 @@ type Chunk struct {
biome uint16 // fallback uniform biome when biomes[si] is nil biome uint16 // fallback uniform biome when biomes[si] is nil
} }
// ParityHeightmaps returns the three client heightmaps as absolute top-block Y
// values. It is used by the vanilla capture harness; wire encoding stores the
// same predicates in packed relative form.
func (c *Chunk) ParityHeightmaps() [3][256]int16 {
c.mu.RLock()
defer c.mu.RUnlock()
var maps [3][256]int16
for z := 0; z < 16; z++ {
for x := 0; x < 16; x++ {
idx := z*16 + x
for kind := range maps {
maps[kind][idx] = int16(MinY - 1)
}
for y := MinY + WorldHeight - 1; y >= MinY; y-- {
state := c.getBlock(x, y, z)
if maps[0][idx] == int16(MinY-1) && state != StateAir {
maps[0][idx] = int16(y)
}
if maps[1][idx] == int16(MinY-1) && blocksMotionOrFluid(state) {
maps[1][idx] = int16(y)
}
if maps[2][idx] == int16(MinY-1) && blocksMotionNoLeaves(state) {
maps[2][idx] = int16(y)
}
if maps[0][idx] != int16(MinY-1) && maps[1][idx] != int16(MinY-1) && maps[2][idx] != int16(MinY-1) {
break
}
}
}
}
return maps
}
// NewChunk returns an empty (all-air) chunk at (x, z) with the given biome. // NewChunk returns an empty (all-air) chunk at (x, z) with the given biome.
func NewChunk(x, z int32, biome uint16) *Chunk { func NewChunk(x, z int32, biome uint16) *Chunk {
return &Chunk{X: x, Z: z, biome: biome} return &Chunk{X: x, Z: z, biome: biome}

View file

@ -72,9 +72,8 @@ func resolveOreTargets(set *worldgen.FeatureSet, config worldgen.OreFeatureConfi
return nil, false return nil, false
} }
replaceables := make(map[uint16]bool) replaceables := make(map[uint16]bool)
for _, name := range set.BlockTags[target.Target.Tag] { for _, name := range flattenBlockTag(set, target.Target.Tag, nil) {
id, ok := nameToStateID(name, nil) if id, ok := nameToStateID(name, nil); ok {
if ok {
replaceables[id] = true replaceables[id] = true
} }
} }
@ -86,6 +85,26 @@ func resolveOreTargets(set *worldgen.FeatureSet, config worldgen.OreFeatureConfi
return targets, true return targets, true
} }
func flattenBlockTag(set *worldgen.FeatureSet, tag string, visiting map[string]bool) []string {
if visiting == nil {
visiting = make(map[string]bool)
}
if visiting[tag] {
return nil
}
visiting[tag] = true
defer delete(visiting, tag)
var names []string
for _, value := range set.BlockTags[tag] {
if len(value) > 0 && value[0] == '#' {
names = append(names, flattenBlockTag(set, value[1:], visiting)...)
} else {
names = append(names, value)
}
}
return names
}
func placeOreEllipsoid(c *Chunk, random worldgen.RandomSource, originX, originY, originZ, size int, discard float64, targets []resolvedOreTarget) { func placeOreEllipsoid(c *Chunk, random worldgen.RandomSource, originX, originY, originZ, size int, discard float64, targets []resolvedOreTarget) {
angle := float64(random.NextFloat()) * math.Pi angle := float64(random.NextFloat()) * math.Pi
extent := float64(size) / 8.0 extent := float64(size) / 8.0

View file

@ -244,6 +244,58 @@ func (s *Store) LoadVanillaChunk(cx, cz int32) (*Chunk, error) {
return s.loadChunk(cx, cz, false) return s.loadChunk(cx, cz, false)
} }
// LoadVanillaHeightmaps reads the three packed heightmaps directly from an
// official chunk. Unlike Chunk.ParityHeightmaps this does not recompute them
// with RegionIO predicates, so parity reports remain independent.
func (s *Store) LoadVanillaHeightmaps(cx, cz int32) ([3][256]int16, error) {
var out [3][256]int16
rx, rz, lx, lz := regionIndex(cx, cz)
rf, err := s.regionFor(cx, cz)
if err != nil {
return out, err
}
raw, err := rf.ReadChunk(lx, lz)
if err != nil {
return out, err
}
_, tag, err := nbt.UnmarshalNamed(raw)
if err != nil {
return out, err
}
root, ok := tag.(*nbt.Compound)
if !ok {
return out, errors.New("world: vanilla chunk root is not a compound")
}
if int32(nbtAsInt(root, "xPos")) != int32(rx*32+lx) || int32(nbtAsInt(root, "zPos")) != int32(rz*32+lz) {
return out, errors.New("world: vanilla heightmap chunk coordinates mismatch")
}
heightmaps, ok := root.Get("Heightmaps")
if !ok {
return out, errors.New("world: vanilla chunk missing Heightmaps")
}
compound, ok := heightmaps.(*nbt.Compound)
if !ok {
return out, errors.New("world: vanilla Heightmaps is not a compound")
}
for kind, name := range []string{"WORLD_SURFACE", "MOTION_BLOCKING", "MOTION_BLOCKING_NO_LEAVES"} {
tag, ok := compound.Get(name)
if !ok {
return out, fmt.Errorf("world: vanilla Heightmaps missing %s", name)
}
longs, ok := tag.(nbt.LongArray)
if !ok || len(longs) != 37 {
return out, fmt.Errorf("world: vanilla heightmap %s has invalid length", name)
}
for index := 0; index < 256; index++ {
longIndex := index / 7
bitOffset := uint((index % 7) * 9)
value := (uint64(longs[longIndex]) >> bitOffset) & 0x1ff
out[kind][index] = int16(MinY + int(value) - 1)
}
}
return out, nil
}
func (s *Store) loadChunk(cx, cz int32, requireGeneratorVersion bool) (*Chunk, error) { func (s *Store) loadChunk(cx, cz int32, requireGeneratorVersion bool) (*Chunk, error) {
rx, rz, lx, lz := regionIndex(cx, cz) rx, rz, lx, lz := regionIndex(cx, cz)
rf, err := s.regionFor(cx, cz) rf, err := s.regionFor(cx, cz)

Binary file not shown.

View file

@ -6,6 +6,7 @@ import (
"io" "io"
"math" "math"
"os" "os"
"sort"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -27,7 +28,7 @@ func TestVanillaBlockParity(t *testing.T) {
if _, err := io.ReadFull(f, header[:]); err != nil { if _, err := io.ReadFull(f, header[:]); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if string(header[:8]) != "RIOPAR01" { if string(header[:8]) != "RIOPAR02" {
t.Fatalf("bad parity fixture magic %q", header[:8]) t.Fatalf("bad parity fixture magic %q", header[:8])
} }
seed := int64(binary.BigEndian.Uint64(header[8:16])) seed := int64(binary.BigEndian.Uint64(header[8:16]))
@ -36,6 +37,10 @@ func TestVanillaBlockParity(t *testing.T) {
t.Fatalf("fixture seed=%d chunks=%d", seed, count) t.Fatalf("fixture seed=%d chunks=%d", seed, count)
} }
gen := NewVanillaGenerator(seed) gen := NewVanillaGenerator(seed)
type statePair struct{ got, want uint16 }
pairs := make(map[statePair]int)
var blockTotal, blockExact, biomeTotal, biomeExact, heightTotal, heightExact int
var fluidMismatch, oreMismatch int
for chunkIndex := 0; chunkIndex < count; chunkIndex++ { for chunkIndex := 0; chunkIndex < count; chunkIndex++ {
var coords [8]byte var coords [8]byte
if _, err := io.ReadFull(f, coords[:]); err != nil { if _, err := io.ReadFull(f, coords[:]); err != nil {
@ -52,8 +57,18 @@ func TestVanillaBlockParity(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
want := binary.BigEndian.Uint16(state[:]) want := binary.BigEndian.Uint16(state[:])
if got := chunk.GetBlock(x, y, z); got != want { got := chunk.GetBlock(x, y, z)
t.Fatalf("chunk (%d,%d) block (%d,%d,%d): got state %d want %d", cx, cz, x, y, z, got, want) blockTotal++
if got == want {
blockExact++
} else {
pairs[statePair{got, want}]++
if isFluidState(got) || isFluidState(want) {
fluidMismatch++
}
if isOreState(got) || isOreState(want) {
oreMismatch++
}
} }
} }
} }
@ -65,17 +80,81 @@ func TestVanillaBlockParity(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
want := binary.BigEndian.Uint16(state[:]) want := binary.BigEndian.Uint16(state[:])
if got := chunk.GetBiome(x, y, z); got != want { biomeTotal++
t.Fatalf("chunk (%d,%d) biome (%d,%d,%d): got %d want %d", cx, cz, x, y, z, got, want) if got := chunk.GetBiome(x, y, z); got == want {
biomeExact++
} }
} }
} }
} }
heightmaps := chunk.ParityHeightmaps()
for kind := range heightmaps {
for idx, got := range heightmaps[kind] {
if _, err := io.ReadFull(f, state[:]); err != nil {
t.Fatal(err)
}
want := int16(binary.BigEndian.Uint16(state[:]))
heightTotal++
if got == want {
heightExact++
} else if os.Getenv("REGIONIO_REQUIRE_PARITY") == "1" && heightTotal < 4 {
t.Logf("heightmap %d chunk (%d,%d) column %d: got %d want %d", kind, cx, cz, idx, got, want)
}
}
}
} }
var trailing [1]byte var trailing [1]byte
if n, err := f.Read(trailing[:]); n != 0 || err != io.EOF { if n, err := f.Read(trailing[:]); n != 0 || err != io.EOF {
t.Fatalf("fixture has trailing data or read error: n=%d err=%v", n, err) t.Fatalf("fixture has trailing data or read error: n=%d err=%v", n, err)
} }
type pairCount struct {
pair statePair
count int
}
top := make([]pairCount, 0, len(pairs))
for pair, n := range pairs {
top = append(top, pairCount{pair, n})
}
sort.Slice(top, func(i, j int) bool { return top[i].count > top[j].count })
if len(top) > 12 {
top = top[:12]
}
for _, mismatch := range top {
t.Logf("block mismatch %d: %s (%d) -> %s (%d)", mismatch.count,
stateLabel(mismatch.pair.got), mismatch.pair.got, stateLabel(mismatch.pair.want), mismatch.pair.want)
}
t.Logf("block exact %d/%d (%.3f%%), biome exact %d/%d (%.3f%%), heightmaps exact %d/%d (%.3f%%), fluid mismatches %d, ore mismatches %d",
blockExact, blockTotal, percent(blockExact, blockTotal), biomeExact, biomeTotal, percent(biomeExact, biomeTotal),
heightExact, heightTotal, percent(heightExact, heightTotal), fluidMismatch, oreMismatch)
// The ordinary CI profile is a regression floor while the port is still
// incomplete. REGIONIO_REQUIRE_PARITY upgrades the same exhaustive audit to
// exact equality; there is no sampled or summary-only comparison path.
if percent(blockExact, blockTotal) < 90 || biomeExact != biomeTotal || heightExact != heightTotal {
t.Fatalf("vanilla parity regressed below the committed baseline")
}
if os.Getenv("REGIONIO_REQUIRE_PARITY") == "1" && (blockExact != blockTotal || biomeExact != biomeTotal || heightExact != heightTotal) {
t.Fatalf("vanilla parity failed: %d block, %d biome, %d heightmap mismatches",
blockTotal-blockExact, biomeTotal-biomeExact, heightTotal-heightExact)
}
}
func percent(exact, total int) float64 {
if total == 0 {
return 100
}
return 100 * float64(exact) / float64(total)
}
func stateLabel(id uint16) string {
if state, ok := stateByID(id); ok {
return state.Name
}
return "unknown"
}
func isOreState(id uint16) bool {
state, ok := stateByID(id)
return ok && (strings.HasSuffix(state.Name, "_ore") || strings.HasPrefix(state.Name, "minecraft:raw_"))
} }
// TestVanillaParity compares our generated surface heights against heights // TestVanillaParity compares our generated surface heights against heights