package worldgen import ( "embed" "encoding/json" "fmt" "strings" ) //go:embed data var dataFS embed.FS // Loader parses the embedded datapack density-function tree into evaluatable // nodes, seeding noises through a RandomState. Shared sub-functions are cached // by name so the DAG is built once. type Loader struct { rs *RandomState dfCache map[string]DensityFunction interpolated []*Interpolated bands *clayBands } // OverworldDensity is the parsed final_density plus the set of Interpolated // nodes that the generator samples on the cell grid. type OverworldDensity struct { Final DensityFunction Interpolated []*Interpolated // Climate parameters sampled by the biome finder. Read from the same // noise_router as final_density. The router keys map to climate axes: // temperature→Temperature, vegetation→Humidity, continents→Continentalness, // erosion→Erosion, ridges→Weirdness, depth→Depth. Temperature, Humidity, Continentalness, Erosion, Weirdness, Depth DensityFunction // Aquifer inputs (NoiseRouter.barrierNoise and friends). Barrier is the // pressure noise that seals an aquifer off from the surrounding stone; // FluidLevelFloodedness and FluidLevelSpread decide whether a cell holds // fluid and at what level; Lava turns deep aquifers into lava. Barrier, FluidLevelFloodedness, FluidLevelSpread, Lava DensityFunction // Ore-vein inputs (unused until the OreVeinifier lands, but parsed here so // the whole router is wired in one place). VeinToggle, VeinRidged, VeinGap DensityFunction // PreliminarySurfaceLevel is the cheap surface estimate used by the aquifer // and by the above_preliminary_surface surface-rule condition. Read it // through PreliminarySurfaceLevelAt, which quart-aligns and memoises. PreliminarySurfaceLevel DensityFunction // Settings read from the same noise settings file. SeaLevel int MinY int Height int AquifersEnabled bool OreVeinsEnabled bool // AquiferRandom places the aquifer cell centres; OreRandom rolls the // per-position ore-vein draws. AquiferRandom PositionalRandomFactory OreRandom PositionalRandomFactory // Surface samples the noises SurfaceSystem reads per column, before the // rule tree runs. Surface *SurfaceSampler surfaceRule *SurfaceRuleSet surfaceRuleErr error prelim *levelCache } // SurfaceRule returns the overworld surface rule set, compiled against this // world's seed. A nil rule set (on error) is non-fatal: the generator falls // back to its biome-blind surface heuristics. func (od *OverworldDensity) SurfaceRule() (*SurfaceRuleSet, error) { return od.surfaceRule, od.surfaceRuleErr } // LoadOverworldFinalDensity builds the overworld final_density function for the // given world seed. func LoadOverworldFinalDensity(seed int64) (*OverworldDensity, error) { l := &Loader{rs: NewRandomState(seed), dfCache: make(map[string]DensityFunction)} var settings struct { NoiseRouter map[string]json.RawMessage `json:"noise_router"` SeaLevel int `json:"sea_level"` Noise struct { MinY int `json:"min_y"` Height int `json:"height"` } `json:"noise"` AquifersEnabled bool `json:"aquifers_enabled"` OreVeinsEnabled bool `json:"ore_veins_enabled"` } if err := l.readJSON("data/overworld.json", &settings); err != nil { return nil, err } var node any if err := json.Unmarshal(settings.NoiseRouter["final_density"], &node); err != nil { return nil, err } final, err := l.parseNode(node) if err != nil { return nil, err } od := &OverworldDensity{ Final: final, SeaLevel: settings.SeaLevel, MinY: settings.Noise.MinY, Height: settings.Noise.Height, AquifersEnabled: settings.AquifersEnabled, OreVeinsEnabled: settings.OreVeinsEnabled, AquiferRandom: l.rs.AquiferRandom(), OreRandom: l.rs.OreRandom(), prelim: newLevelCache(), } // Parse the remaining router keys. Each resolves to a density function via // the same parseNode/loadRef machinery as final_density. A missing key is // not fatal — the field stays nil and its consumer treats it as absent — // but a parse error is. // // The climate keys feed the biome finder (temperature→Temperature, // vegetation→Humidity, continents→Continentalness, erosion→Erosion, // ridges→Weirdness, depth→Depth); the rest feed the aquifer, the ore veins // and the preliminary surface estimate. // // The order is fixed rather than a map range: parsing assigns Interpolated // node indices in encounter order, and those indices address the cell-corner // grids the generator fills. routerKeys := []struct { key string dst *DensityFunction }{ {"temperature", &od.Temperature}, {"vegetation", &od.Humidity}, {"continents", &od.Continentalness}, {"erosion", &od.Erosion}, {"ridges", &od.Weirdness}, {"depth", &od.Depth}, {"barrier", &od.Barrier}, {"fluid_level_floodedness", &od.FluidLevelFloodedness}, {"fluid_level_spread", &od.FluidLevelSpread}, {"lava", &od.Lava}, {"vein_toggle", &od.VeinToggle}, {"vein_ridged", &od.VeinRidged}, {"vein_gap", &od.VeinGap}, {"preliminary_surface_level", &od.PreliminarySurfaceLevel}, } for _, rk := range routerKeys { raw, ok := settings.NoiseRouter[rk.key] if !ok { continue } var cn any if err := json.Unmarshal(raw, &cn); err != nil { return nil, fmt.Errorf("parse router key %q: %w", rk.key, err) } df, err := l.parseNode(cn) if err != nil { return nil, fmt.Errorf("router key %q: %w", rk.key, err) } *rk.dst = df } // Interpolated nodes are collected as the whole router is parsed, so the // list has to be taken after the loop, not just after final_density. od.Interpolated = l.interpolated // SurfaceSystem's own noises. They are not router keys: vanilla pulls them // straight out of the noise registry when it builds the SurfaceSystem. surfaceNoise, err := l.noiseField("minecraft:surface") if err != nil { return nil, fmt.Errorf("surface noise: %w", err) } secondaryNoise, err := l.noiseField("minecraft:surface_secondary") if err != nil { return nil, fmt.Errorf("surface_secondary noise: %w", err) } od.Surface = &SurfaceSampler{ surfaceNoise: surfaceNoise, secondaryNoise: secondaryNoise, positionalRand: l.rs.Positional(), } // The rule tree is seed-bound: its noise_threshold conditions sample seeded // noises and its vertical_gradient rolls against a seeded positional // factory. A failure here is reported but not fatal — the generator keeps // going on the fallback heuristics rather than refusing to start. od.surfaceRule, od.surfaceRuleErr = l.loadSurfaceRuleSet(od.MinY, od.Height) return od, nil } func (l *Loader) readJSON(path string, v any) error { b, err := dataFS.ReadFile(path) if err != nil { return fmt.Errorf("read %s: %w", path, err) } return json.Unmarshal(b, v) } // parseNode builds a density function from a decoded JSON value: a number is a // constant, a string is a reference to another density-function file, and an // object is a typed node. func (l *Loader) parseNode(v any) (DensityFunction, error) { switch t := v.(type) { case float64: return Constant(t), nil case string: return l.loadRef(t) case map[string]any: return l.parseObject(t) default: return nil, fmt.Errorf("unexpected density-function node %T", v) } } // loadRef loads and caches a density function referenced by resource location. func (l *Loader) loadRef(name string) (DensityFunction, error) { if df, ok := l.dfCache[name]; ok { return df, nil } path := "data/density_function/" + strings.TrimPrefix(name, "minecraft:") + ".json" var node any if err := l.readJSON(path, &node); err != nil { return nil, err } df, err := l.parseNode(node) if err != nil { return nil, fmt.Errorf("in %s: %w", name, err) } l.dfCache[name] = df return df, nil } func (l *Loader) parseObject(m map[string]any) (DensityFunction, error) { typ, _ := m["type"].(string) arg := func(k string) (DensityFunction, error) { return l.parseNode(m[k]) } num := func(k string) float64 { f, _ := m[k].(float64); return f } switch strings.TrimPrefix(typ, "minecraft:") { case "add", "mul", "min", "max": a, err := arg("argument1") if err != nil { return nil, err } b, err := arg("argument2") if err != nil { return nil, err } switch typ[10:] { case "add": return Add(a, b), nil case "mul": return Mul(a, b), nil case "min": return Min(a, b), nil default: return Max(a, b), nil } case "abs", "square", "cube", "half_negative", "quarter_negative", "invert", "squeeze": a, err := arg("argument") if err != nil { return nil, err } return unaryByName(strings.TrimPrefix(typ, "minecraft:"), a), nil case "clamp": a, err := arg("input") if err != nil { return nil, err } return Clamp(a, num("min"), num("max")), nil case "range_choice": in, err := arg("input") if err != nil { return nil, err } whenIn, err := arg("when_in_range") if err != nil { return nil, err } whenOut, err := arg("when_out_of_range") if err != nil { return nil, err } return RangeChoice{in, num("min_inclusive"), num("max_exclusive"), whenIn, whenOut}, nil case "y_clamped_gradient": return YClampedGradient{num("from_y"), num("to_y"), num("from_value"), num("to_value")}, nil case "noise": n, err := l.noiseField(m["noise"]) if err != nil { return nil, err } return NoiseDF{Noise: n, XZScale: num("xz_scale"), YScale: num("y_scale")}, nil case "shifted_noise": sx, err := arg("shift_x") if err != nil { return nil, err } sy, err := arg("shift_y") if err != nil { return nil, err } sz, err := arg("shift_z") if err != nil { return nil, err } n, err := l.noiseField(m["noise"]) if err != nil { return nil, err } return ShiftedNoise{sx, sy, sz, num("xz_scale"), num("y_scale"), n}, nil case "shift_a", "shift_b": n, err := l.noiseField(m["argument"]) if err != nil { return nil, err } if typ[10:] == "shift_a" { return ShiftA{n}, nil } return ShiftB{n}, nil case "old_blended_noise": return l.rs.BlendedNoise(num("xz_scale"), num("y_scale"), num("xz_factor"), num("y_factor"), num("smear_scale_multiplier")), nil case "weird_scaled_sampler": in, err := arg("input") if err != nil { return nil, err } n, err := l.noiseField(m["noise"]) if err != nil { return nil, err } rarity := SpaghettiRarity3D if s, _ := m["rarity_value_mapper"].(string); s == "type_2" { rarity = SpaghettiRarity2D } return WeirdScaledSampler{in, n, rarity}, nil case "find_top_surface": density, err := arg("density") if err != nil { return nil, err } upper, err := arg("upper_bound") if err != nil { return nil, err } cellHeight := int(num("cell_height")) if cellHeight <= 0 { return nil, fmt.Errorf("find_top_surface: cell_height must be positive, got %d", cellHeight) } return FindTopSurface{ Density: density, UpperBound: upper, LowerBound: int(num("lower_bound")), CellHeight: cellHeight, }, nil case "spline": return l.parseSpline(m["spline"]) case "blend_alpha": return Constant(1.0), nil // no blending: alpha = 1 case "blend_offset": return Constant(0.0), nil // no blending: offset = 0 case "interpolated": inner, err := arg("argument") if err != nil { return nil, err } n := &Interpolated{Inner: inner, Index: len(l.interpolated)} l.interpolated = append(l.interpolated, n) return n, nil case "blend_density", "flat_cache", "cache_2d", "cache_once", "cache_all_in_cell": // 2D caches and blend wrappers are value-preserving for per-point // evaluation (recomputed rather than cached); only the 3D interpolated // marker changes the result and is handled above. return arg("argument") default: return nil, fmt.Errorf("unsupported density-function type %q", typ) } } func unaryByName(name string, a DensityFunction) DensityFunction { switch name { case "abs": return Abs(a) case "square": return Square(a) case "cube": return Cube(a) case "half_negative": return HalfNegative(a) case "quarter_negative": return QuarterNegative(a) case "invert": return Invert(a) default: // squeeze return Squeeze(a) } } // noiseField resolves a noise reference (a "minecraft:" key, or an object // with a "noise" key) to a seeded NormalNoise. func (l *Loader) noiseField(v any) (*NormalNoise, error) { var key string switch t := v.(type) { case string: key = t case map[string]any: key, _ = t["noise"].(string) } if key == "" { return nil, fmt.Errorf("missing noise reference") } var params struct { FirstOctave int `json:"firstOctave"` Amplitudes []float64 `json:"amplitudes"` } path := "data/noise/" + strings.TrimPrefix(key, "minecraft:") + ".json" if err := l.readJSON(path, ¶ms); err != nil { return nil, err } return l.rs.Noise(key, params.FirstOctave, params.Amplitudes), nil } func (l *Loader) parseSpline(v any) (DensityFunction, error) { m, ok := v.(map[string]any) if !ok { return nil, fmt.Errorf("spline is not an object") } coord, err := l.parseNode(m["coordinate"]) if err != nil { return nil, err } pts, _ := m["points"].([]any) s := &CubicSpline{coordinate: coord} for _, p := range pts { pm := p.(map[string]any) loc, _ := pm["location"].(float64) der, _ := pm["derivative"].(float64) val, err := l.parseSplineValue(pm["value"]) if err != nil { return nil, err } s.locations = append(s.locations, float32(loc)) s.derivatives = append(s.derivatives, float32(der)) s.values = append(s.values, val) } return s, nil } // parseSplineValue handles a spline point's value: a number (constant), a raw // nested spline (object with "coordinate"), or a density-function node. func (l *Loader) parseSplineValue(v any) (DensityFunction, error) { if m, ok := v.(map[string]any); ok { if _, hasCoord := m["coordinate"]; hasCoord { return l.parseSpline(m) } } return l.parseNode(v) } // clayBands builds the world's badlands band table on first use. It is seeded // from the root positional factory hashed by name, as SurfaceSystem does. func (l *Loader) clayBands() (*clayBands, error) { if l.bands != nil { return l.bands, nil } offset, err := l.noiseField("minecraft:clay_bands_offset") if err != nil { return nil, fmt.Errorf("clay_bands_offset noise: %w", err) } l.bands = newClayBands(l.rs.Positional().FromHashOf("minecraft:clay_bands"), offset) return l.bands, nil }