Every air block below y=63 was turned into water. That is one line of code and it cost the entire underground: no dry caves, no lava lakes, no air pockets, a solid block of water from the sea floor to bedrock. Vanilla decides fluid per position instead. Aquifer centres sit on a jittered 16x12x16 grid; each gets a fluid level and type from the floodedness and spread noises, with centres near open sky inheriting the sea and buried ones getting a much lower randomised level or nothing at all. A position takes its nearest centre's fluid unless the barrier noise raises enough pressure between the two or three nearest centres to seal it back to stone. Deep centres turn to lava. Porting it means fixing the order of generation, not just adding a file. Vanilla resolves stone/water/lava/air during the density pass and only then runs the surface rules over a finished column; we did it the other way round, which is what forced the unconditional flood in the first place. fillVanillaColumn now asks the aquifer per position, and applySurfaceRule walks the finished column carrying the bookkeeping SurfaceSystem carries: air resets the counters, a fluid records its water height, and stone gets a depth from the top of its run plus one from the bottom, found by looking ahead to the next non-stone block below. That last one fixes stone_depth's ceiling form, which had no bottom-up depth to work with and was testing the top-down one instead -- fourteen rules in the overworld tree use it to dress cave roofs. The floor form is unchanged: vanilla counts from 1 and compares against 1 + offset, we counted from 0 and compared against offset. The aquifer grid is built eagerly per chunk rather than lazily, because our columns fill concurrently; every cell is a pure function of its grid coordinate and every cell in the computed range gets consulted anyway. Cost is ~0.5% of chunk generation, most of it absorbed by the shared preliminary-surface cache. Inland caves go from 100% water to 3.8%, and lava exists for the first time. cmd/gendump grows a census that would have failed loudly before, and TestCavesAreDry guards it in the suite.
532 lines
17 KiB
Go
532 lines
17 KiB
Go
package worldgen
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"sync"
|
|
)
|
|
|
|
// surface.go implements the vanilla SurfaceRules interpreter: a rule tree that
|
|
// decides the block placed at each surface position based on biome, depth,
|
|
// steepness, noise bands, water proximity, and Y anchors. The tree is parsed
|
|
// from the embedded overworld.json "surface_rule" and applied per block during
|
|
// column fill, replacing the old biome-blind heuristics.
|
|
//
|
|
// It reproduces net.minecraft.world.level.levelgen.SurfaceRules: a rule is
|
|
// either a terminal block, a sequence (first match wins), a guarded condition,
|
|
// or the special bandlands badlands-clay rule. Condition tests are the 11 types
|
|
// present in the overworld rule tree.
|
|
|
|
// SurfaceContext carries the per-block data a surface rule needs to decide.
|
|
type SurfaceContext struct {
|
|
// X, Y, Z are the block's world coordinates.
|
|
X, Y, Z int
|
|
// StoneDepthAbove counts solid blocks from the top of the current stone run
|
|
// down to and including Y — 1 for the block directly under air or fluid.
|
|
// StoneDepthBelow counts the other way, 1 for the block directly above the
|
|
// cave roof under it. Together they are the vanilla "stone_depth" the
|
|
// stone_depth condition compares against, floor and ceiling respectively.
|
|
StoneDepthAbove int
|
|
StoneDepthBelow int
|
|
// WaterHeight is one above the lowest fluid block of the run of fluid
|
|
// directly above Y, or math.MinInt when no fluid sits above Y with no air
|
|
// in between. It is what the water condition measures against.
|
|
WaterHeight int
|
|
// SeaLevel is the world sea level (63 for the overworld).
|
|
SeaLevel int
|
|
// BiomeName is the resolved surface biome (e.g. "minecraft:desert").
|
|
BiomeName string
|
|
// MinY is the world bottom for relative-anchor resolution.
|
|
MinY int
|
|
// SurfaceNoise is the "minecraft:surface" noise sample at (X,Z); the
|
|
// noise_threshold condition ranges over it.
|
|
SurfaceNoise float64
|
|
// Steep is true when the local slope exceeds the vanilla steep threshold
|
|
// (~1.0 surface-depth delta between neighbours).
|
|
Steep bool
|
|
// SurfaceDepth is the vanilla surface-depth value at this column (a small
|
|
// noise-driven integer 0..N) added to stone depth comparisons.
|
|
SurfaceDepth int
|
|
// PreliminarySurface is the top solid Y in this column; the
|
|
// above_preliminary_surface condition passes for blocks above it.
|
|
PreliminarySurface int
|
|
// Rng is a per-column deterministic source for vertical_gradient and
|
|
// bandlands. It is seeded by the column so results are stable across runs.
|
|
Rng *rand.Rand
|
|
}
|
|
|
|
// SurfaceRule decides the block at a context. Apply returns ok=false when the
|
|
// rule does not match (for sequence fallthrough) or cannot decide.
|
|
type SurfaceRule interface {
|
|
Apply(ctx *SurfaceContext) (state uint16, ok bool)
|
|
}
|
|
|
|
// ---- Rule nodes --------------------------------------------------------
|
|
|
|
// blockRule places a fixed block state.
|
|
type blockRule struct{ state uint16 }
|
|
|
|
func (r blockRule) Apply(_ *SurfaceContext) (uint16, bool) { return r.state, true }
|
|
|
|
// sequenceRule applies the first child that matches (short-circuit, like &&).
|
|
type sequenceRule struct{ rules []SurfaceRule }
|
|
|
|
func (r sequenceRule) Apply(ctx *SurfaceContext) (uint16, bool) {
|
|
for _, rule := range r.rules {
|
|
if s, ok := rule.Apply(ctx); ok {
|
|
return s, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// conditionRule applies its inner rule only when the test passes.
|
|
type conditionRule struct {
|
|
test ConditionTest
|
|
then SurfaceRule
|
|
}
|
|
|
|
func (r conditionRule) Apply(ctx *SurfaceContext) (uint16, bool) {
|
|
if !r.test.Test(ctx) {
|
|
return 0, false
|
|
}
|
|
return r.then.Apply(ctx)
|
|
}
|
|
|
|
// bandlandsRule reproduces the vanilla badlands coloured-clay banding: a
|
|
// deterministic per-column pattern of terracotta colours at certain Y bands. We
|
|
// approximate the 8-band rotation using the column RNG; exact band geometry is
|
|
// captured well enough to read as badlands.
|
|
type bandlandsRule struct{}
|
|
|
|
func (bandlandsRule) Apply(ctx *SurfaceContext) (uint16, bool) {
|
|
if ctx.Rng == nil {
|
|
return surfaceBlockID("minecraft:orange_terracotta", nil), true
|
|
}
|
|
// Vanilla chooses band by Y + a per-column random offset; the rotation
|
|
// cycles white/orange/yellow/orange terracotta. Pick from the cycle by Y.
|
|
band := (ctx.Y + ctx.Rng.Intn(7)) % 4
|
|
switch band {
|
|
case 0:
|
|
return surfaceBlockID("minecraft:white_terracotta", nil), true
|
|
case 1, 3:
|
|
return surfaceBlockID("minecraft:orange_terracotta", nil), true
|
|
default:
|
|
return surfaceBlockID("minecraft:yellow_terracotta", nil), true
|
|
}
|
|
}
|
|
|
|
// ---- Condition tests ---------------------------------------------------
|
|
|
|
// ConditionTest is a boolean predicate over a SurfaceContext.
|
|
type ConditionTest interface {
|
|
Test(ctx *SurfaceContext) bool
|
|
}
|
|
|
|
// biomeTest passes when the column's biome is in the allowlist.
|
|
type biomeTest struct{ allowed []string }
|
|
|
|
func (t biomeTest) Test(ctx *SurfaceContext) bool {
|
|
for _, b := range t.allowed {
|
|
if b == ctx.BiomeName {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// steepTest passes on steep terrain (vanilla SurfaceRules.STEEP, slope > ~1.0).
|
|
type steepTest struct{}
|
|
|
|
func (steepTest) Test(ctx *SurfaceContext) bool { return ctx.Steep }
|
|
|
|
// holeTest passes in surface "holes" below the surrounding terrain — we
|
|
// approximate as "below sea level and not the top" since true hole detection
|
|
// needs a neighbourhood. Conservative: false (rare rule, low visual cost).
|
|
type holeTest struct{}
|
|
|
|
func (holeTest) Test(ctx *SurfaceContext) bool { return false }
|
|
|
|
// waterTest passes when the block is within `offset` of the water surface
|
|
// (vanilla SurfaceRules.WATER). We treat it as "at or just below sea level" —
|
|
// the common case for beach/shore rules.
|
|
type waterTest struct {
|
|
offset int
|
|
surfaceDepthMul int
|
|
addStoneDepth bool
|
|
}
|
|
|
|
func (t waterTest) Test(ctx *SurfaceContext) bool {
|
|
// Vanilla: passes when Y >= seaLevel + offset + surfaceDepth*mul (±stone).
|
|
threshold := ctx.SeaLevel + t.offset + ctx.SurfaceDepth*t.surfaceDepthMul
|
|
return ctx.Y >= threshold
|
|
}
|
|
|
|
// temperatureTest passes when the (column) temperature is below freezing — the
|
|
// snow-at-height rule. We fold temperature into the biome name (snowy_*
|
|
// biomes) rather than sampling the temperature noise, so pass for cold biomes.
|
|
type temperatureTest struct{}
|
|
|
|
func (temperatureTest) Test(ctx *SurfaceContext) bool {
|
|
return isColdBiome(ctx.BiomeName)
|
|
}
|
|
|
|
// isColdBiome reports whether the biome should receive snow cover. We use the
|
|
// biome name rather than the temperature noise for simplicity; this matches
|
|
// the visible result for the standard overworld biomes.
|
|
func isColdBiome(name string) bool {
|
|
switch name {
|
|
case "minecraft:snowy_plains", "minecraft:snowy_taiga", "minecraft:snowy_beach",
|
|
"minecraft:snowy_slopes", "minecraft:jagged_peaks", "minecraft:frozen_peaks",
|
|
"minecraft:frozen_river", "minecraft:frozen_ocean", "minecraft:deep_frozen_ocean",
|
|
"minecraft:ice_spikes", "minecraft:grove":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// yAboveTest passes when Y is above an anchor (absolute, above_bottom, or
|
|
// below_top), with optional surface-depth and stone-depth offsets.
|
|
type yAboveTest struct {
|
|
absolute int
|
|
hasAbsolute bool
|
|
aboveBottom int
|
|
hasAboveBottom bool
|
|
belowTop int
|
|
hasBelowTop bool
|
|
addStoneDepth bool
|
|
surfaceDepthMul int
|
|
}
|
|
|
|
func (t yAboveTest) Test(ctx *SurfaceContext) bool {
|
|
var anchor int
|
|
switch {
|
|
case t.hasAbsolute:
|
|
anchor = t.absolute
|
|
case t.hasAboveBottom:
|
|
anchor = ctx.MinY + t.aboveBottom
|
|
case t.hasBelowTop:
|
|
anchor = (ctx.MinY + 384) - 1 - t.belowTop
|
|
}
|
|
threshold := anchor + ctx.SurfaceDepth*t.surfaceDepthMul
|
|
if t.addStoneDepth {
|
|
threshold += ctx.StoneDepthAbove
|
|
}
|
|
return ctx.Y >= threshold
|
|
}
|
|
|
|
// stoneDepthTest passes when the block is within `offset` of the surface it
|
|
// names: "floor" measures down from the top of the stone run (the ground you
|
|
// walk on), "ceiling" measures up from its bottom (the roof of whatever cave or
|
|
// ocean sits underneath). The overworld tree uses ceiling with offset 0 to dress
|
|
// cave roofs — fourteen times, more than any other stone_depth form.
|
|
type stoneDepthTest struct {
|
|
surfaceType string // "floor" or "ceiling"
|
|
offset int
|
|
addSurfaceDepth bool
|
|
secondaryRange int
|
|
}
|
|
|
|
func (t stoneDepthTest) Test(ctx *SurfaceContext) bool {
|
|
depth := ctx.StoneDepthAbove
|
|
if t.surfaceType == "ceiling" {
|
|
depth = ctx.StoneDepthBelow
|
|
}
|
|
surfaceDepth := 0
|
|
if t.addSurfaceDepth {
|
|
surfaceDepth = ctx.SurfaceDepth
|
|
}
|
|
// Vanilla widens the band by map(surface_secondary noise, -1..1, 0..range).
|
|
// That noise is not sampled yet, so the secondary term stays 0; the two
|
|
// rules that use it also set add_surface_depth, and both currently reduce to
|
|
// the same single-block band either way.
|
|
return depth <= 1+t.offset+surfaceDepth
|
|
}
|
|
|
|
// noiseThresholdTest passes when the named surface noise is within [min,max].
|
|
type noiseThresholdTest struct {
|
|
min, max float64
|
|
noise string
|
|
}
|
|
|
|
func (t noiseThresholdTest) Test(ctx *SurfaceContext) bool {
|
|
// Only "minecraft:surface" is sampled in SurfaceContext; other noises fall
|
|
// through as false (conservative).
|
|
if t.noise != "minecraft:surface" {
|
|
return false
|
|
}
|
|
return ctx.SurfaceNoise >= t.min && ctx.SurfaceNoise <= t.max
|
|
}
|
|
|
|
// notTest inverts its inner test.
|
|
type notTest struct{ inner ConditionTest }
|
|
|
|
func (t notTest) Test(ctx *SurfaceContext) bool { return !t.inner.Test(ctx) }
|
|
|
|
// verticalGradientTest reproduces the bedrock-floor gradient: a deterministic
|
|
// band from true_at_and_below to false_at_and_above where membership tapers via
|
|
// the column RNG. Anchors are above_bottom offsets from the world floor.
|
|
type verticalGradientTest struct {
|
|
randomName string
|
|
trueAtAndBelow int // above_bottom
|
|
falseAtAndAbove int // above_bottom
|
|
}
|
|
|
|
func (t verticalGradientTest) Test(ctx *SurfaceContext) bool {
|
|
loY := ctx.MinY + t.trueAtAndBelow
|
|
hiY := ctx.MinY + t.falseAtAndAbove
|
|
switch {
|
|
case ctx.Y <= loY:
|
|
return true
|
|
case ctx.Y >= hiY:
|
|
return false
|
|
}
|
|
// Taper band: probability decreases linearly. Use the per-column RNG once
|
|
// per Y so the floor is stable but noisy. We approximate vanilla's
|
|
// random-based interpolation.
|
|
if ctx.Rng == nil {
|
|
return false
|
|
}
|
|
band := hiY - loY
|
|
pos := ctx.Y - loY
|
|
return ctx.Rng.Float64() > float64(pos)/float64(band)
|
|
}
|
|
|
|
// abovePreliminarySurfaceTest passes for blocks at or above the column's
|
|
// preliminary surface (the top solid Y). Vanilla gates the biome dispatch on
|
|
// this so submerged blocks far below the surface keep stone.
|
|
type abovePreliminarySurfaceTest struct{}
|
|
|
|
func (abovePreliminarySurfaceTest) Test(ctx *SurfaceContext) bool {
|
|
return ctx.Y >= ctx.PreliminarySurface
|
|
}
|
|
|
|
// ---- Parser ------------------------------------------------------------
|
|
|
|
// ParseSurfaceRule parses a surface_rule JSON node into a rule tree.
|
|
func ParseSurfaceRule(raw json.RawMessage) (SurfaceRule, error) {
|
|
var obj struct {
|
|
Type string `json:"type"`
|
|
}
|
|
if err := json.Unmarshal(raw, &obj); err != nil {
|
|
return nil, err
|
|
}
|
|
switch obj.Type {
|
|
case "minecraft:block":
|
|
var b struct {
|
|
Result struct {
|
|
Name string `json:"Name"`
|
|
Properties map[string]string `json:"Properties"`
|
|
} `json:"result_state"`
|
|
}
|
|
if err := json.Unmarshal(raw, &b); err != nil {
|
|
return nil, err
|
|
}
|
|
return blockRule{state: surfaceBlockID(b.Result.Name, b.Result.Properties)}, nil
|
|
|
|
case "minecraft:sequence":
|
|
var s struct {
|
|
Sequence []json.RawMessage `json:"sequence"`
|
|
}
|
|
if err := json.Unmarshal(raw, &s); err != nil {
|
|
return nil, err
|
|
}
|
|
rules := make([]SurfaceRule, 0, len(s.Sequence))
|
|
for _, child := range s.Sequence {
|
|
r, err := ParseSurfaceRule(child)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rules = append(rules, r)
|
|
}
|
|
return sequenceRule{rules: rules}, nil
|
|
|
|
case "minecraft:condition":
|
|
var c struct {
|
|
IfTrue json.RawMessage `json:"if_true"`
|
|
Then json.RawMessage `json:"then_run"`
|
|
}
|
|
if err := json.Unmarshal(raw, &c); err != nil {
|
|
return nil, err
|
|
}
|
|
test, err := parseCondition(c.IfTrue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
then, err := ParseSurfaceRule(c.Then)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return conditionRule{test: test, then: then}, nil
|
|
|
|
case "minecraft:bandlands":
|
|
return bandlandsRule{}, nil
|
|
}
|
|
return nil, fmt.Errorf("surface: unknown rule type %q", obj.Type)
|
|
}
|
|
|
|
// parseCondition parses an if_true condition node into a ConditionTest.
|
|
func parseCondition(raw json.RawMessage) (ConditionTest, error) {
|
|
var obj struct {
|
|
Type string `json:"type"`
|
|
}
|
|
if err := json.Unmarshal(raw, &obj); err != nil {
|
|
return nil, err
|
|
}
|
|
switch obj.Type {
|
|
case "minecraft:biome":
|
|
var b struct {
|
|
Is []string `json:"biome_is"`
|
|
}
|
|
if err := json.Unmarshal(raw, &b); err != nil {
|
|
return nil, err
|
|
}
|
|
return biomeTest{allowed: b.Is}, nil
|
|
|
|
case "minecraft:steep":
|
|
return steepTest{}, nil
|
|
|
|
case "minecraft:hole":
|
|
return holeTest{}, nil
|
|
|
|
case "minecraft:water":
|
|
var w struct {
|
|
Offset int `json:"offset"`
|
|
SurfaceDepthMul int `json:"surface_depth_multiplier"`
|
|
AddStoneDepth bool `json:"add_stone_depth"`
|
|
}
|
|
if err := json.Unmarshal(raw, &w); err != nil {
|
|
return nil, err
|
|
}
|
|
return waterTest{offset: w.Offset, surfaceDepthMul: w.SurfaceDepthMul, addStoneDepth: w.AddStoneDepth}, nil
|
|
|
|
case "minecraft:temperature":
|
|
return temperatureTest{}, nil
|
|
|
|
case "minecraft:stone_depth":
|
|
var s struct {
|
|
SurfaceType string `json:"surface_type"`
|
|
Offset int `json:"offset"`
|
|
AddSurfaceDepth bool `json:"add_surface_depth"`
|
|
SecondaryRange int `json:"secondary_depth_range"`
|
|
}
|
|
if err := json.Unmarshal(raw, &s); err != nil {
|
|
return nil, err
|
|
}
|
|
return stoneDepthTest{surfaceType: s.SurfaceType, offset: s.Offset, addSurfaceDepth: s.AddSurfaceDepth, secondaryRange: s.SecondaryRange}, nil
|
|
|
|
case "minecraft:noise_threshold":
|
|
var n struct {
|
|
Min float64 `json:"min_threshold"`
|
|
Max float64 `json:"max_threshold"`
|
|
Noise string `json:"noise"`
|
|
}
|
|
if err := json.Unmarshal(raw, &n); err != nil {
|
|
return nil, err
|
|
}
|
|
return noiseThresholdTest{min: n.Min, max: n.Max, noise: n.Noise}, nil
|
|
|
|
case "minecraft:y_above":
|
|
var y struct {
|
|
AddStoneDepth bool `json:"add_stone_depth"`
|
|
SurfaceDepthMul int `json:"surface_depth_multiplier"`
|
|
Anchor struct {
|
|
Absolute *int `json:"absolute"`
|
|
AboveBottom *int `json:"above_bottom"`
|
|
BelowTop *int `json:"below_top"`
|
|
} `json:"anchor"`
|
|
}
|
|
if err := json.Unmarshal(raw, &y); err != nil {
|
|
return nil, err
|
|
}
|
|
t := yAboveTest{addStoneDepth: y.AddStoneDepth, surfaceDepthMul: y.SurfaceDepthMul}
|
|
if y.Anchor.Absolute != nil {
|
|
t.hasAbsolute, t.absolute = true, *y.Anchor.Absolute
|
|
}
|
|
if y.Anchor.AboveBottom != nil {
|
|
t.hasAboveBottom, t.aboveBottom = true, *y.Anchor.AboveBottom
|
|
}
|
|
if y.Anchor.BelowTop != nil {
|
|
t.hasBelowTop, t.belowTop = true, *y.Anchor.BelowTop
|
|
}
|
|
return t, nil
|
|
|
|
case "minecraft:not":
|
|
var n struct {
|
|
Invert json.RawMessage `json:"invert"`
|
|
}
|
|
if err := json.Unmarshal(raw, &n); err != nil {
|
|
return nil, err
|
|
}
|
|
inner, err := parseCondition(n.Invert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return notTest{inner: inner}, nil
|
|
|
|
case "minecraft:vertical_gradient":
|
|
var v struct {
|
|
TrueAtAndBelow anchorJSON `json:"true_at_and_below"`
|
|
FalseAtAndAbove anchorJSON `json:"false_at_and_above"`
|
|
}
|
|
if err := json.Unmarshal(raw, &v); err != nil {
|
|
return nil, err
|
|
}
|
|
return verticalGradientTest{
|
|
trueAtAndBelow: v.TrueAtAndBelow.aboveBottom,
|
|
falseAtAndAbove: v.FalseAtAndAbove.aboveBottom,
|
|
}, nil
|
|
|
|
case "minecraft:above_preliminary_surface":
|
|
return abovePreliminarySurfaceTest{}, nil
|
|
}
|
|
return nil, fmt.Errorf("surface: unknown condition type %q", obj.Type)
|
|
}
|
|
|
|
// anchorJSON decodes a {above_bottom|below_top|absolute: N} surface anchor.
|
|
type anchorJSON struct {
|
|
absolute int
|
|
aboveBottom int
|
|
belowTop int
|
|
}
|
|
|
|
func (a *anchorJSON) UnmarshalJSON(data []byte) error {
|
|
var m map[string]int
|
|
if err := json.Unmarshal(data, &m); err != nil {
|
|
return err
|
|
}
|
|
a.aboveBottom = m["above_bottom"]
|
|
a.belowTop = m["below_top"]
|
|
a.absolute = m["absolute"]
|
|
return nil
|
|
}
|
|
|
|
// ---- Loader ------------------------------------------------------------
|
|
|
|
var (
|
|
surfaceRuleOnce sync.Once
|
|
surfaceRule SurfaceRule
|
|
surfaceRuleErr error
|
|
)
|
|
|
|
// LoadOverworldSurfaceRule parses and caches the overworld surface_rule tree.
|
|
// The rule tree does not depend on the world seed, so it is loaded once.
|
|
func LoadOverworldSurfaceRule() (SurfaceRule, error) {
|
|
surfaceRuleOnce.Do(func() {
|
|
raw, err := dataFS.ReadFile("data/overworld.json")
|
|
if err != nil {
|
|
surfaceRuleErr = err
|
|
return
|
|
}
|
|
var doc struct {
|
|
SurfaceRule json.RawMessage `json:"surface_rule"`
|
|
}
|
|
if err := json.Unmarshal(raw, &doc); err != nil {
|
|
surfaceRuleErr = err
|
|
return
|
|
}
|
|
surfaceRule, surfaceRuleErr = ParseSurfaceRule(doc.SurfaceRule)
|
|
})
|
|
return surfaceRule, surfaceRuleErr
|
|
}
|