RegionIO/internal/worldgen/surface.go
Master290 1083e47211 Restore the subsurface layers: real above_preliminary_surface and surface depth
Every land column was one block of grass sitting straight on stone. No dirt
under grass, no sandstone under sand, nothing. Two stubs did it together:

above_preliminary_surface compared blockY against the column's actual top block,
so of every position in the column exactly one passed -- and the entire
biome-specific half of the surface rule tree hangs under that condition.
Vanilla compares against a minimum surface level: the preliminary surface level
sampled at the four corners of the 16-block cell, bilinearly interpolated, plus
the surface depth less 8. That is about twenty blocks of reach on ordinary
terrain, which is what the biome subtree is written against.

Surface depth was hardcoded to 0. Vanilla is surfaceNoise*2.75 + 3 with a
per-column jitter, so it comes out around three; it sets how thick the band is
and feeds every add_surface_depth term in the tree. Zero collapsed them all.

Also samples surface_secondary, so stone_depth's secondary_depth_range widens
its band instead of being parsed and dropped.

Grass columns now read grass, two to four dirt, stone -- the histogram over 256
columns is {2: 223, 3: 33}, against vanilla's 2..4. gendump prints it and fails
if the band collapses again; TestGrassColumnsHaveDirt guards it in the suite.
2026-07-27 02:15:14 +03:00

553 lines
17 KiB
Go

package worldgen
import (
"encoding/json"
"fmt"
"math"
"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.
// NoWaterAbove is the "dry column" sentinel for SurfaceContext.WaterHeight,
// matching the Integer.MIN_VALUE vanilla uses. A caller building a context by
// hand must set it explicitly; the zero value would read as water at y=0.
const NoWaterAbove = math.MinInt
// 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 NoWaterAbove 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 how thick the biome's surface layers are at this column
// (SurfaceSystem.getSurfaceDepth): usually 3, sometimes 0 or less, which is
// what "hole" tests for. It widens the stone_depth bands.
SurfaceDepth int
// SurfaceSecondary is the "minecraft:surface_secondary" noise at this
// column, which widens a stone_depth band further when the rule sets
// secondary_depth_range.
SurfaceSecondary float64
// MinSurfaceLevel is the lowest Y the biome surface subtree may reach:
// the interpolated preliminary surface level plus SurfaceDepth less 8.
// above_preliminary_surface tests Y against it.
MinSurfaceLevel 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 clear of the water above it — either there
// is none, or it sits far enough below the water's underside
// (SurfaceRules.WaterConditionSource).
//
// The height compared against is the column's own water surface, not sea level.
// Those differ wherever the aquifer put a pool at its own level: an underground
// lake, a mountain tarn or a flooded cave sit nowhere near y=63, and measuring
// them against sea level dressed dry stone as lakebed and lakebed as dry stone.
type waterTest struct {
offset int
surfaceDepthMul int
addStoneDepth bool
}
func (t waterTest) Test(ctx *SurfaceContext) bool {
if ctx.WaterHeight == NoWaterAbove {
return true
}
y := ctx.Y
if t.addStoneDepth {
y += ctx.StoneDepthAbove
}
return y >= ctx.WaterHeight+t.offset+ctx.SurfaceDepth*t.surfaceDepthMul
}
// 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
}
secondary := 0
if t.secondaryRange != 0 {
secondary = int(mapRange(ctx.SurfaceSecondary, -1.0, 1.0, 0.0, float64(t.secondaryRange)))
}
return depth <= 1+t.offset+surfaceDepth+secondary
}
// 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 gates the whole biome surface subtree: below the
// column's minimum surface level nothing is dressed and the stone stays stone.
type abovePreliminarySurfaceTest struct{}
func (abovePreliminarySurfaceTest) Test(ctx *SurfaceContext) bool {
return ctx.Y >= ctx.MinSurfaceLevel
}
// ---- 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
}