Replaces the biome-blind fillVanillaColumn heuristics with a full interpreter for the overworld surface_rule tree (already embedded in overworld.json): block/sequence/condition/bandlands rules plus all 11 condition tests (biome, steep, hole, water, temperature, y_above, stone_depth, noise_threshold, not, vertical_gradient, above_preliminary_surface). - worldgen/blockids.go: name(+Properties)→network-ID table for surface blocks (grass/sand/terracotta/mycelium/podzol/coarse_dirt/sandstone/ calcite/snow/ice/...), with snowy property variants. - worldgen/surface.go: rule-tree parser + interpreter + SurfaceContext; LoadOverworldSurfaceRule caches the seed-independent tree. - loader.go: OverworldDensity.SurfaceRule() exposes the parsed tree. - biome_lookup.go: BiomeNameAt returns the biome name for biome tests. - vanilla.go: samples the 2D climate + biome before column fill, threads the rule tree and biome name into fillVanillaColumn, and applies it top-down with stone as the default for non-matching (deeper) blocks. The above_preliminary_surface gate uses an inclusive bound so the top solid block reaches the biome dispatch. - Performance: one per-column RNG and a reused SurfaceContext keep the overhead to ~+13ms/chunk (71ms vs 58ms baseline), within the gate.
107 lines
3.3 KiB
Go
107 lines
3.3 KiB
Go
package worldgen
|
|
|
|
import (
|
|
"math/rand"
|
|
"testing"
|
|
)
|
|
|
|
// TestLoadSurfaceRule confirms the embedded overworld surface_rule parses into
|
|
// a rule tree without error. This guards the parser against any rule/condition
|
|
// type the overworld uses.
|
|
func TestLoadSurfaceRule(t *testing.T) {
|
|
rule, err := LoadOverworldSurfaceRule()
|
|
if err != nil {
|
|
t.Fatalf("LoadOverworldSurfaceRule: %v", err)
|
|
}
|
|
if rule == nil {
|
|
t.Fatal("nil surface rule")
|
|
}
|
|
}
|
|
|
|
// TestSurfaceRuleNoPanic runs the full rule tree across a range of Y values and
|
|
// several biomes to confirm Apply never panics on real-world inputs. A panic
|
|
// during generation would crash the server.
|
|
func TestSurfaceRuleNoPanic(t *testing.T) {
|
|
rule, err := LoadOverworldSurfaceRule()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
biomes := []string{
|
|
"minecraft:plains", "minecraft:desert", "minecraft:forest",
|
|
"minecraft:badlands", "minecraft:snowy_plains", "minecraft:ocean",
|
|
"minecraft:mushroom_fields", "minecraft:wooded_badlands",
|
|
}
|
|
for _, b := range biomes {
|
|
for y := 0; y < 100; y++ {
|
|
ctx := &SurfaceContext{
|
|
X: 100, Y: y, Z: 100, StoneDepthAbove: 100 - y,
|
|
SeaLevel: 63, BiomeName: b, MinY: -64,
|
|
PreliminarySurface: 100, Rng: rand.New(rand.NewSource(1)),
|
|
}
|
|
rule.Apply(ctx) // must not panic
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSurfaceBedrockFloor confirms the bottom of the world resolves to bedrock
|
|
// (the vertical_gradient bedrock_floor rule is the first rule in the tree).
|
|
func TestSurfaceBedrockFloor(t *testing.T) {
|
|
rule, err := LoadOverworldSurfaceRule()
|
|
if err != nil {
|
|
t.Fatalf("load: %v", err)
|
|
}
|
|
ctx := &SurfaceContext{
|
|
X: 0, Y: -64, Z: 0, StoneDepthAbove: 0,
|
|
SeaLevel: 63, BiomeName: "minecraft:plains", MinY: -64,
|
|
PreliminarySurface: 70, Rng: rand.New(rand.NewSource(1)),
|
|
}
|
|
state, ok := rule.Apply(ctx)
|
|
if !ok {
|
|
t.Fatal("no rule matched at bedrock floor")
|
|
}
|
|
if state != 85 { // bedrock
|
|
t.Errorf("bedrock floor state = %d, want 85", state)
|
|
}
|
|
}
|
|
|
|
// TestSurfaceBlockIDResolution checks the block-ID table covers the blocks the
|
|
// overworld surface_rule references, including snowy property variants.
|
|
func TestSurfaceBlockIDResolution(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
props map[string]string
|
|
want uint16
|
|
}{
|
|
{"minecraft:bedrock", nil, 85},
|
|
{"minecraft:grass_block", nil, 9},
|
|
{"minecraft:grass_block", map[string]string{"snowy": "true"}, 8},
|
|
{"minecraft:mycelium", nil, 8919},
|
|
{"minecraft:podzol", map[string]string{"snowy": "true"}, 12},
|
|
{"minecraft:terracotta", nil, 12912},
|
|
{"minecraft:red_sand", nil, 123},
|
|
{"minecraft:coarse_dirt", nil, 11},
|
|
{"minecraft:calcite", nil, 24687},
|
|
}
|
|
for _, c := range cases {
|
|
if got := surfaceBlockID(c.name, c.props); got != c.want {
|
|
t.Errorf("surfaceBlockID(%q,%v) = %d, want %d", c.name, c.props, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestIsColdBiome confirms the snow-cover predicate recognises cold biomes so
|
|
// the temperature condition routes snowy biomes to snow.
|
|
func TestIsColdBiome(t *testing.T) {
|
|
cold := []string{"minecraft:snowy_plains", "minecraft:frozen_peaks", "minecraft:grove"}
|
|
for _, b := range cold {
|
|
if !isColdBiome(b) {
|
|
t.Errorf("isColdBiome(%q) = false, want true", b)
|
|
}
|
|
}
|
|
warm := []string{"minecraft:desert", "minecraft:plains", "minecraft:badlands"}
|
|
for _, b := range warm {
|
|
if isColdBiome(b) {
|
|
t.Errorf("isColdBiome(%q) = true, want false", b)
|
|
}
|
|
}
|
|
}
|