Bind the surface rule tree to the world seed
The tree was parsed once, globally, and shared by every world -- so every condition that needs the seed simply did not work. Compiling it per RandomState fixes four of them at once. noise_threshold sampled a per-column random draw and pretended it was "minecraft:surface"; the other six noises it names were unsupported and returned false. Each condition now holds its own seeded noise, sampled once per column into a small cache the way vanilla's LazyXZCondition does. Powder snow, packed ice and ice appear in the dump for the first time; calcite, swamp water windows and gravel patches have their conditions back too. vertical_gradient tapered through a per-column RNG shared with the other rules. Vanilla rolls a positional random at the exact block, from a factory named by the rule. More importantly the anchor decoder read only above_bottom and discarded which kind of anchor it was, so the deepslate rule's absolute 0..8 collapsed onto y=-64 and **no deepslate existed anywhere in the world**. Anchors now carry their kind and resolve against the real height bounds -- which also retires a hardcoded 384 in y_above. Two more stubs land with them: hole is surfaceDepth <= 0 rather than a constant false, and steep reads the neighbouring column heights. steep needs the whole chunk's heightmap, so the column pass is now two passes -- terrain and fluids for all 256 columns, then surface rules -- which is the order vanilla uses anyway (doFill, then buildSurface). Deepslate was also missing from the block-ID table, and an unknown name resolved to 0, which the caller read as "no block" and skipped. So even a correct rule would have placed nothing. Unknown names are now a parse error, deepslate and mud are in the table, and a rule that resolves to air genuinely places air -- the frozen-ocean surface asks for exactly that. Below y=0 is now entirely deepslate, y=1..7 a scatter, above y=8 none.
This commit is contained in:
parent
1083e47211
commit
c19e5f0e4f
9 changed files with 478 additions and 255 deletions
|
|
@ -5,16 +5,30 @@ import (
|
|||
"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()
|
||||
// loadTestRules compiles the overworld surface rule set at a fixed seed.
|
||||
func loadTestRules(t *testing.T) *SurfaceRuleSet {
|
||||
t.Helper()
|
||||
od, err := LoadOverworldFinalDensity(12345)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadOverworldSurfaceRule: %v", err)
|
||||
t.Fatalf("load overworld density: %v", err)
|
||||
}
|
||||
if rule == nil {
|
||||
t.Fatal("nil surface rule")
|
||||
rules, err := od.SurfaceRule()
|
||||
if err != nil {
|
||||
t.Fatalf("compile surface rule: %v", err)
|
||||
}
|
||||
if rules == nil {
|
||||
t.Fatal("nil surface rule set")
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
// TestLoadSurfaceRule confirms the embedded overworld surface_rule parses into
|
||||
// a rule tree without error, and that every noise its noise_threshold
|
||||
// conditions name resolved. Six of the seven used to fall through as false.
|
||||
func TestLoadSurfaceRule(t *testing.T) {
|
||||
rules := loadTestRules(t)
|
||||
if len(rules.noises) != 7 {
|
||||
t.Errorf("rule set references %d noises, want 7", len(rules.noises))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -22,25 +36,24 @@ func TestLoadSurfaceRule(t *testing.T) {
|
|||
// 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)
|
||||
}
|
||||
rules := loadTestRules(t)
|
||||
biomes := []string{
|
||||
"minecraft:plains", "minecraft:desert", "minecraft:forest",
|
||||
"minecraft:badlands", "minecraft:snowy_plains", "minecraft:ocean",
|
||||
"minecraft:mushroom_fields", "minecraft:wooded_badlands",
|
||||
}
|
||||
ctx := rules.NewContext()
|
||||
rules.BeginColumn(ctx, 100, 100)
|
||||
ctx.SeaLevel, ctx.MinY = 63, -64
|
||||
ctx.MinSurfaceLevel, ctx.WaterHeight = 80, NoWaterAbove
|
||||
ctx.SurfaceDepth = 3
|
||||
ctx.Rng = rand.New(rand.NewSource(1))
|
||||
for _, b := range biomes {
|
||||
ctx.BiomeName = b
|
||||
for y := 0; y < 100; y++ {
|
||||
ctx := &SurfaceContext{
|
||||
X: 100, Y: y, Z: 100,
|
||||
StoneDepthAbove: 100 - y, StoneDepthBelow: y + 1,
|
||||
SeaLevel: 63, BiomeName: b, MinY: -64,
|
||||
MinSurfaceLevel: 80, WaterHeight: NoWaterAbove,
|
||||
Rng: rand.New(rand.NewSource(1)),
|
||||
}
|
||||
rule.Apply(ctx) // must not panic
|
||||
ctx.Y = y
|
||||
ctx.StoneDepthAbove, ctx.StoneDepthBelow = 100-y, y+1
|
||||
rules.Apply(ctx) // must not panic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,17 +61,17 @@ func TestSurfaceRuleNoPanic(t *testing.T) {
|
|||
// 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: 1, StoneDepthBelow: 1,
|
||||
SeaLevel: 63, BiomeName: "minecraft:plains", MinY: -64,
|
||||
MinSurfaceLevel: 62, WaterHeight: NoWaterAbove,
|
||||
Rng: rand.New(rand.NewSource(1)),
|
||||
}
|
||||
state, ok := rule.Apply(ctx)
|
||||
rules := loadTestRules(t)
|
||||
ctx := rules.NewContext()
|
||||
rules.BeginColumn(ctx, 0, 0)
|
||||
ctx.Y = -64
|
||||
ctx.StoneDepthAbove, ctx.StoneDepthBelow = 1, 1
|
||||
ctx.SeaLevel, ctx.MinY = 63, -64
|
||||
ctx.BiomeName = "minecraft:plains"
|
||||
ctx.MinSurfaceLevel, ctx.WaterHeight = 62, NoWaterAbove
|
||||
ctx.SurfaceDepth = 3
|
||||
ctx.Rng = rand.New(rand.NewSource(1))
|
||||
state, ok := rules.Apply(ctx)
|
||||
if !ok {
|
||||
t.Fatal("no rule matched at bedrock floor")
|
||||
}
|
||||
|
|
@ -84,12 +97,23 @@ func TestSurfaceBlockIDResolution(t *testing.T) {
|
|||
{"minecraft:red_sand", nil, 123},
|
||||
{"minecraft:coarse_dirt", nil, 11},
|
||||
{"minecraft:calcite", nil, 24687},
|
||||
{"minecraft:deepslate", map[string]string{"axis": "y"}, 27924},
|
||||
{"minecraft:mud", nil, 27922},
|
||||
{"minecraft:air", nil, 0},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := surfaceBlockID(c.name, c.props); got != c.want {
|
||||
got, ok := surfaceBlockID(c.name, c.props)
|
||||
if !ok {
|
||||
t.Errorf("surfaceBlockID(%q,%v) not in the table", c.name, c.props)
|
||||
continue
|
||||
}
|
||||
if got != c.want {
|
||||
t.Errorf("surfaceBlockID(%q,%v) = %d, want %d", c.name, c.props, got, c.want)
|
||||
}
|
||||
}
|
||||
if _, ok := surfaceBlockID("minecraft:not_a_block", nil); ok {
|
||||
t.Error("surfaceBlockID accepted an unknown name")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsColdBiome confirms the snow-cover predicate recognises cold biomes so
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue