From b11e6f14cb3e5d5d5504155c2679c662588411f1 Mon Sep 17 00:00:00 2001 From: Daniar Mannanov Date: Mon, 10 Aug 2026 23:59:22 +0300 Subject: [PATCH] Pin biome fitness against vanilla runtime --- internal/world/biome_lookup.go | 2 +- internal/world/store.go | 2 +- internal/worldgen/biome.go | 16 ++++++------- internal/worldgen/biome_test.go | 41 ++++++++++++++++++++++++++++++++ tools/VanillaClimateVectors.java | 35 +++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 tools/VanillaClimateVectors.java diff --git a/internal/world/biome_lookup.go b/internal/world/biome_lookup.go index 0c61627..736f4b3 100644 --- a/internal/world/biome_lookup.go +++ b/internal/world/biome_lookup.go @@ -102,8 +102,8 @@ func makeBiomeParameter(e rawParameter, depth worldgen.ClimateRange) worldgen.Bi qr(e.Param.Humidity), qr(e.Param.Continentalness), qr(e.Param.Erosion), - qr(e.Param.Weirdness), depth, + qr(e.Param.Weirdness), }, Offset: worldgen.Quantize(e.Param.Offset), } diff --git a/internal/world/store.go b/internal/world/store.go index 71d19d8..377865a 100644 --- a/internal/world/store.go +++ b/internal/world/store.go @@ -33,7 +33,7 @@ const dataVersion26 = 4790 // first time it ran: chunkAt prefers the store over the generator, so the // already-explored area around spawn keeps its old terrain and every later fix // looks like it did nothing in exactly the place you are standing. -const generatorVersion = 15 +const generatorVersion = 16 // generatorVersionTag is the NBT key holding generatorVersion. It is namespaced // because it is ours, not part of the vanilla chunk format. diff --git a/internal/worldgen/biome.go b/internal/worldgen/biome.go index 87c4afc..ac410f8 100644 --- a/internal/worldgen/biome.go +++ b/internal/worldgen/biome.go @@ -4,7 +4,7 @@ import "math" // This file reproduces net.minecraft.world.level.biome.Climate, the multi-noise // biome selector. A point in climate space is six quantized coordinates -// (temperature, humidity, continentalness, erosion, weirdness, depth); the +// (temperature, humidity, continentalness, erosion, depth, weirdness); the // finder returns the biome whose parameter range is closest to the point by the // vanilla fitDistance metric. // @@ -23,13 +23,13 @@ func quantize(v float64) int64 { func Quantize(v float64) int64 { return quantize(v) } // AxisCount is the number of climate coordinates (temperature, humidity, -// continentalness, erosion, weirdness, depth). +// continentalness, erosion, depth, weirdness). const AxisCount = 6 // TargetPoint is a fully-specified climate point: the value the biome finder // tries to match against parameter ranges. Fields are pre-quantized longs. type TargetPoint struct { - Temperature, Humidity, Continentalness, Erosion, Weirdness, Depth int64 + Temperature, Humidity, Continentalness, Erosion, Depth, Weirdness int64 } // NewTargetPoint quantizes six float climate coordinates into a TargetPoint. @@ -39,15 +39,15 @@ func NewTargetPoint(temp, humid, cont, ero, weird, depth float64) TargetPoint { Humidity: quantize(humid), Continentalness: quantize(cont), Erosion: quantize(ero), - Weirdness: quantize(weird), Depth: quantize(depth), + Weirdness: quantize(weird), } } // fitDistance is the vanilla distance from a point to a parameter range. A // coordinate inside a range contributes zero; offset is applied separately. func fitDistance(point TargetPoint, ranges [AxisCount]ClimateRange, offset int64) int64 { - values := [AxisCount]int64{point.Temperature, point.Humidity, point.Continentalness, point.Erosion, point.Weirdness, point.Depth} + values := [AxisCount]int64{point.Temperature, point.Humidity, point.Continentalness, point.Erosion, point.Depth, point.Weirdness} var total int64 for i, value := range values { r := ranges[i] @@ -73,7 +73,7 @@ func (r ClimateRange) contains(v int64) bool { return v >= r.Min && v <= r.Max } // BiomeParameter is one biome entry's full climate signature plus its name. type BiomeParameter struct { Name string - // ranges[0..5] = temperature, humidity, continentalness, erosion, weirdness, depth. + // ranges[0..5] = temperature, humidity, continentalness, erosion, depth, weirdness. Ranges [AxisCount]ClimateRange Offset int64 } @@ -118,6 +118,6 @@ func containsAll(ranges [AxisCount]ClimateRange, p TargetPoint) bool { ranges[1].contains(p.Humidity) && ranges[2].contains(p.Continentalness) && ranges[3].contains(p.Erosion) && - ranges[4].contains(p.Weirdness) && - ranges[5].contains(p.Depth) + ranges[4].contains(p.Depth) && + ranges[5].contains(p.Weirdness) } diff --git a/internal/worldgen/biome_test.go b/internal/worldgen/biome_test.go index aa8f21d..a7d12d1 100644 --- a/internal/worldgen/biome_test.go +++ b/internal/worldgen/biome_test.go @@ -48,6 +48,32 @@ func TestParameterTableDistanceOffsetAndTies(t *testing.T) { } } +func TestFitnessVectorsAgainstVanillaRuntime(t *testing.T) { + zero := [AxisCount]ClimateRange{} + temperatureRange := zero + temperatureRange[0] = ClimateRange{Min: 0, Max: 10} + depthWeirdness := zero + depthWeirdness[4] = ClimateRange{Min: 20, Max: 20} + depthWeirdness[5] = ClimateRange{Min: 30, Max: 30} + for _, vector := range []struct { + name string + point TargetPoint + ranges [AxisCount]ClimateRange + offset int64 + want int64 + }{ + {"inside", TargetPoint{Temperature: 5}, temperatureRange, 0, 0}, + {"below", TargetPoint{Temperature: -3}, temperatureRange, 0, 9}, + {"above", TargetPoint{Temperature: 14}, temperatureRange, 0, 16}, + {"offset", TargetPoint{Temperature: 5}, temperatureRange, 7, 49}, + {"depth-weirdness-order", TargetPoint{Depth: 23, Weirdness: 35}, depthWeirdness, 0, 34}, + } { + if got := fitDistance(vector.point, vector.ranges, vector.offset); got != vector.want { + t.Errorf("%s fitness = %d, want vanilla runtime %d", vector.name, got, vector.want) + } + } +} + // TestFitDistanceZero confirms identical points are zero-distance and distinct // points are positive; the exact value is not asserted to stay robust to // representation choices. @@ -78,6 +104,21 @@ func TestRangeContains(t *testing.T) { } } +func TestContainsAllUsesVanillaAxisOrder(t *testing.T) { + var ranges [AxisCount]ClimateRange + for i := range ranges { + ranges[i] = ClimateRange{Min: 0, Max: 0} + } + ranges[4] = ClimateRange{Min: 20, Max: 20} + ranges[5] = ClimateRange{Min: 30, Max: 30} + if !containsAll(ranges, TargetPoint{Depth: 20, Weirdness: 30}) { + t.Fatal("depth/weirdness ranges were not matched in vanilla order") + } + if containsAll(ranges, TargetPoint{Depth: 30, Weirdness: 20}) { + t.Fatal("accepted swapped depth/weirdness axes") + } +} + // TestSampleColumnDeterministic verifies the same seed/coords give the same // biome and a different seed gives (almost certainly) a different one. func TestSampleColumnDeterministic(t *testing.T) { diff --git a/tools/VanillaClimateVectors.java b/tools/VanillaClimateVectors.java new file mode 100644 index 0000000..40aeedc --- /dev/null +++ b/tools/VanillaClimateVectors.java @@ -0,0 +1,35 @@ +import java.lang.reflect.Method; +import net.minecraft.world.level.biome.Climate; + +// Prints fixed Climate.ParameterPoint fitness vectors from the official jar. +public final class VanillaClimateVectors { + public static void main(String[] args) throws Exception { + Method fitness = Climate.ParameterPoint.class.getDeclaredMethod("fitness", Climate.TargetPoint.class); + fitness.setAccessible(true); + vector(fitness, "inside", ranges(0, 10, 0), point(5, 0)); + vector(fitness, "below", ranges(0, 10, 0), point(-3, 0)); + vector(fitness, "above", ranges(0, 10, 0), point(14, 0)); + vector(fitness, "offset", ranges(0, 10, 7), point(5, 0)); + + Climate.Parameter zero = new Climate.Parameter(0, 0); + Climate.Parameter depth = new Climate.Parameter(20, 20); + Climate.Parameter weird = new Climate.Parameter(30, 30); + Climate.ParameterPoint axes = new Climate.ParameterPoint(zero, zero, zero, zero, depth, weird, 0); + Climate.TargetPoint target = new Climate.TargetPoint(0, 0, 0, 0, 23, 35); + vector(fitness, "depth-weirdness-order", axes, target); + } + + private static Climate.ParameterPoint ranges(long min, long max, long offset) { + Climate.Parameter range = new Climate.Parameter(min, max); + Climate.Parameter zero = new Climate.Parameter(0, 0); + return new Climate.ParameterPoint(range, zero, zero, zero, zero, zero, offset); + } + + private static Climate.TargetPoint point(long temperature, long depth) { + return new Climate.TargetPoint(temperature, 0, 0, 0, depth, 0); + } + + private static void vector(Method fitness, String name, Climate.ParameterPoint p, Climate.TargetPoint target) throws Exception { + System.out.println(name + "=" + fitness.invoke(p, target)); + } +}