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.
The overworld noise_router ships fourteen keys; we read six. The eight left on
the floor are exactly the ones the aquifer, the ore veins and the preliminary
surface estimate need, so every one of those subsystems has been impossible to
write.
Wire the rest of the router into OverworldDensity: barrier,
fluid_level_floodedness, fluid_level_spread and lava for the aquifer,
vein_toggle/vein_ridged/vein_gap for the veins, and preliminary_surface_level
for both. Two node types were missing and are added with them --
minecraft:invert (the reciprocal, not negation: Mapped.Type ordinal 5 is
1.0/input) and minecraft:find_top_surface, which walks down from an upper bound
in cell_height steps looking for positive density.
PreliminarySurfaceLevelAt wraps that node the way NoiseChunk does: quart-align
the column, then memoise. The cache is per generator rather than per chunk
because the aquifer samples columns up to three chunks away, so neighbours
overlap heavily -- with a shared cache a chunk costs a few dozen evaluations
instead of a few thousand.
Also lifts sea_level, min_y, height and the aquifers/ore-veins flags out of the
settings file, and adds PositionalRandomFactory.At for the aquifer cell centres
(Mth.getSeed hashed into the low half of the factory seed).
No generator output changes yet: nothing reads the new keys.
Replaces the biome-blind fillVanillaColumn heuristics with a full
interpreter for the overworld surface_rule tree (already embedded in
overworld.json): block/sequence/condition/bandlands rules plus all 11
condition tests (biome, steep, hole, water, temperature, y_above,
stone_depth, noise_threshold, not, vertical_gradient,
above_preliminary_surface).
- worldgen/blockids.go: name(+Properties)→network-ID table for surface
blocks (grass/sand/terracotta/mycelium/podzol/coarse_dirt/sandstone/
calcite/snow/ice/...), with snowy property variants.
- worldgen/surface.go: rule-tree parser + interpreter + SurfaceContext;
LoadOverworldSurfaceRule caches the seed-independent tree.
- loader.go: OverworldDensity.SurfaceRule() exposes the parsed tree.
- biome_lookup.go: BiomeNameAt returns the biome name for biome tests.
- vanilla.go: samples the 2D climate + biome before column fill, threads
the rule tree and biome name into fillVanillaColumn, and applies it
top-down with stone as the default for non-matching (deeper) blocks.
The above_preliminary_surface gate uses an inclusive bound so the top
solid block reaches the biome dispatch.
- Performance: one per-column RNG and a reused SurfaceContext keep the
overhead to ~+13ms/chunk (71ms vs 58ms baseline), within the gate.