Skip to content

Asking the overworld

OverworldLookup answers a question about the overworld from anywhere — including from a worldgen thread of another dimension.

if (OverworldLookup.is(x, z, BiomeTags.IS_OCEAN)) { ... }

double howMuchSea = OverworldLookup.coverage(x, z, BiomeTags.IS_OCEAN);  // 0..1, smoothed
long seed = OverworldLookup.seed();                                      // every dimension shares it

A mod whose dimension is shaped by what the overworld looks like underneath it — sky islands only over the sea, a mirror world that follows the coastlines — needs this, and the naive version loads overworld chunks from a worldgen thread, which deadlocks.

Why it is safe

BiomeSource#getNoiseBiome is a pure computation over the overworld's noise. Given its Climate.Sampler it answers "what biome would be here" without reading, generating or loading a single chunk.

That is also what makes it compatible with Terralith, TerraBlender and Tectonic by construction: it goes through whatever biome source the overworld actually has, not through a copy of vanilla's rules. Any of them whose oceans carry minecraft:is_ocean is supported without a line of compatibility code.

What it returns

Method Answer
overworld() the ServerLevel, or null when no server is running
seed() the world seed, or 0 with no server — which in practice means datagen
biomeAt(x, z) the biome at the overworld's sea level, or null
is(x, z, tag) whether that biome carries the tag
coverage(x, z, tag) how much of the 3×3 neighbourhood of quart cells does, from 0 to 1

coverage is the one to multiply by. A yes-or-no answer used as a mask gives a vertical wall at every coastline; a smoothed one gives a slope a few blocks wide. It costs nine samples, which is affordable once per column and ruinous once per block — cache it per column at the call site.

The three traps it exists to hold

⚠️ Resolved lazily, never in a constructor and never in a codec. Registries load long before any level exists, and a density function's codec runs while the datapack is being read. A field initialised at construction is null for the whole life of the game.

⚠️ Cleared when the server stops. A single-player client can open one world, quit to the title and open another inside the same process. Without the reset the second world is asked about the first one's terrain.

⚠️ The cache is per thread, and stamped with the world it was filled for. Several worldgen threads call this at once, so a shared map would need locking on a path that runs for every column of every chunk. And clearing only the stopping thread's cache would leave every other worldgen thread holding the old world's answers — those threads outlive a world, so the next world would be shaped by the last one's coastlines, in a way that looks like ordinary worldgen and is never traced back. Each cache checks a generation counter and empties itself the first time it is used in a new world.

Resolution

The quart: one answer per 4×4 blocks, which is the resolution biomes are stored at anyway. Sampled at the overworld's sea level, y = 64.