docs/world-height.md, a 2096-high fixture pack, and the tall parity run

tools/fixtures/tall_datapacks/horizon_tall_test is Horizon's own pack
(MIT): an Overworld dimension type of min_y -64, height 2096 (To The
Max's, the highest top vanilla allows) and noise settings that raise
terrain to about y 1900. VanillaChunkGenerationOracle builds each level
at its dimension type's range instead of a fixed -64..320, so
HORIZON_PARITY_DATAPACKS=tools/fixtures/tall_datapacks
tools/test_datapack_worldgen_parity.py compares Horizon's tall generation
with vanilla's: 16 chunks over two seeds, terrain and decorated, the same.

docs/world-height.md describes the model (one LevelShape per level, from
its dimension type, read everywhere), the network path, the checks and
what is left; world-generation.md and the README say packs' dimension
types now apply.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ThatCakeIsAPieandClaude Opus 5.5 committed 2026-10-28 18:12:58 -05:00
1 parent 148d592567
commit 83b958a824
12 files changed
+200 -5

No files matched your search

+1 -1
View File
@@ -46,7 +46,7 @@ The [documentation](https://playhorizon.dev/docs/) covers each of these.
- **The game:** singleplayer with vanilla's world generation, mobs, villagers, raids, redstone, every command, the HUD and the recipe book, on saves in vanilla's format. Worlds from 1.8 to 26.2 are upgraded as vanilla does. [Status table](#project-status).
- **Horizon's own extras:** new menus (vanilla's are one setting away), graphics presets, distant terrain up to 1024 chunks, controller support, optional zoom, minimap and more, all off by default where they would change the vanilla look. See [Settings](https://playhorizon.dev/docs/settings).
- **Resource packs and data packs:** resource packs work, and data packs load functions, predicates, item modifiers and world generation (vanilla's heights and biomes for now).
- **Resource packs and data packs:** resource packs work, and data packs load functions, predicates, item modifiers and world generation, taller levels included (vanilla's biomes for now).
- **Playing with others:** invite friends from the pause menu's Friends button with a code, Open to LAN, and joining servers, online-mode ones with the Microsoft account the launcher signs in with. See [Multiplayer](https://playhorizon.dev/docs/multiplayer).
- **Mods:** Horizon has its own mod system (Rust compiled to WebAssembly). Java mod jars do not run. See [Mods and data packs](https://playhorizon.dev/docs/mods).
+1
View File
@@ -35,6 +35,7 @@ To play, use [the launcher](../launcher/README.md); the project site is [playhor
## Architecture: world generation
- [World generation](world-generation.md): the pipeline from noise to features.
- [World height](world-height.md): each level's shape from its dimension type, data packs' taller levels.
- [Pregeneration](pregeneration.md): `/pregen`, generating, lighting and saving an area ahead of the players.
- [Noise generation](noise-generation.md), [compiled density functions](density-functions.md), [structure density](structure-density.md), [terrain filling](terrain-filling.md).
- Biomes: [lookup](biome-lookup.md), [temperatures](biome-temperatures.md).
+2 -2
View File
@@ -209,8 +209,8 @@ conditions, carvers, configured and placed features, structures, structure sets,
higher pack's file for an id replacing a lower one's, and its block, fluid and biome tag files to the tag bundles in pack order.
`GeneratorData::load_with` reads the bundles with them; the client caches the result per set of pack entries
(`vanillagen::shared_data_with`). What Horizon leaves out is logged: a pack's new biomes (the game has no id, colours or sounds for them;
their tag entries and climate points go with them), dimension types (every dimension keeps its vanilla build height, and noise settings
taller than it generate only inside it, as `clampToHeightAccessor`), and folders the generator does not read. Sapling growth and bone
their tag entries and climate points go with them) and folders the generator does not read. A pack's dimension types set each level's
height ([world-height.md](world-height.md)); noise settings taller than the level generate only inside it, as `clampToHeightAccessor`. Sapling growth and bone
meal still place vanilla's features.
`tools/test_datapack_worldgen_parity.py` runs the whole-chunk harness with `HORIZON_DATAPACKS=tools/fixtures/datapacks` on both sides
+75
View File
@@ -0,0 +1,75 @@
# World height: each level's shape from its dimension type
Vanilla takes each level's build range from its dimension type: `min_y` and `height`, multiples of 16,
with `height >= 16` and `min_y + height <= 2032` (`DimensionType.MIN_Y = -2032`, `Y_SIZE = 4064`: `BlockPos`
packs Y in 12 bits), plus `logical_height` (how high portals and chorus fruit reach) and `has_skylight`.
`LevelHeightAccessor` derives everything else from them. Data packs that reshape the Overworld make it taller:
JJThunder's To The Max sets `min_y -64, height 2096`, the largest top vanilla allows, which is 131 sections
against vanilla's 24.
## The model
**`chunk::LevelShape`** is one level's shape: `min_y`, `height`, `logical_height`, `sky_light`, checked as the
dimension type codec checks them (`LevelShape::new`). **`LevelShapes`** holds a world's three.
- **Where it comes from.** A local world's shapes are its dimension types, its enabled data packs' over
vanilla's, through the level stems that name them (`worldgen::overrides::WorldgenOverrides::level_height`,
read by `game/world_loading.rs`'s `level_shapes` when the world opens; the game keeps them in
`Game.level_shapes`). A remote client takes a level's shape from the server's `minecraft:dimension_type`
registry (`protocol::registry::DimensionType`, with its logical height). A type the codec refuses keeps the
vanilla shape, with a warning.
- **Where it lives.** Each level's `World` holds its `shape`, `SaveDir::new(root, shape)` takes it, and the
generator reads the same dimension type for the level's build range (`OverworldGenerator::level`).
- **The column.** `LevelShape::column()` is the span of sections the level's chunks hold (`ColumnLayout`):
the level's own range, widened to the vanilla Overworld's `-64..320`. So vanilla's three levels keep the
24 sections they have always had, and a taller Overworld gets its own column while its Nether and End stay
at 24. Every `Chunk` records its own `min_y` and section count, and the hot accessors read the chunk.
- **No global.** The shape travels with the data (`World`, `SaveDir`, `Chunk`, the light and mesh jobs, the
renderer's `clear_sections(layout)`, the LAN encoder's `set_dimension(shape)`, the remote `ChunkFormat`).
Test binaries run tests in parallel in one process, so a process-wide height would leak between them.
| Subsystem | Reads |
| --- | --- |
| Chunk storage (`chunk.rs`) | the chunk's own `min_y` and sections |
| Light (`light.rs`) | the chunk's sections; the relighter the world's column (`LightWorld::layout`) |
| World, streaming, meshing, renderer | `World::layout()`, the chunk's `min_y`, the renderer's layout |
| Saves (`save.rs`) | `SaveDir.shape` (sections, `yPos`, heightmaps, light layers, far tiles) |
| Network (`lan/`, `game/remote_chunks.rs`) | the level's shape on the host, the server's dimension type on the client |
| Section caches, generation glue, far terrain, minimap, flat worlds | the chunk's column, the generator's level range |
| Gameplay (bounds, searches, commands, mobs) | `World::bottom_y`/`top_y` (the column), `World.shape`, `Game.level_shapes` |
Section sets are `SectionMask`s (256 bits: vanilla's tallest level has 254 sections, light data one more
below and above). Uniform sections (one block, one biome, one light value) are one shared allocation per kind
(`chunk::share_uniform`), and the sky-source scan passes over a uniform section light goes through: a tall
column is mostly such air.
## A world of data packs
The game, single player and LAN alike, runs through a host: the integrated server thread of single player
reads the world's shapes, encodes each level at its shape (`HubConfig.levels`) and sends the data of a
dimension type the packs changed even to a client that knows the vanilla pack (`NetConfig.levels`,
`SyncData::registry_packets`), and its client takes the heights from that registry data, as a vanilla client
of a vanilla server does.
## Checks
- Unit tests: a 131-section chunk stores, lights, relights, saves, loads, encodes and random-ticks its whole
height; `tests/tall_column.rs` streams, lights and meshes a 131-section level to its top and remeshes after an
edit at y 1800.
- `HORIZON_PARITY_DATAPACKS=tools/fixtures/tall_datapacks python3 tools/test_datapack_worldgen_parity.py`
generates the fixture's 2096-high Overworld (`horizon_tall_test`, Horizon's own MIT pack, terrain from about
y 475 to 1900) with the vanilla oracle, which takes each level's range from its dimension type, and with
Horizon, and compares every block, biome and block entity.
- Vanilla worlds are unchanged at every step: `chunkgen_bench --snapshot`, `test_light_parity.py` and the save
and LAN tests give the same results as before.
## What is left
- **Nether and End at their own height.** They keep the vanilla column (`-64..320`). Holding exactly their 16
sections would save memory and match vanilla's `LevelChunk`, but Horizon's light only spreads inside a
chunk's column while vanilla's spreads one section past the level, so it needs that border in the flood fill
and the relighter first, checked with `test_light_parity.py`.
- The vanilla constants `MIN_Y`, `HEIGHT` and `SECTIONS` remain as the default column and in tests.
- Far terrain's detail for a camera far above the world uses vanilla's top (`lod.rs`).
- Looking down from far above the terrain shows fog: vanilla's render-distance fog uses `max(xz, |y|)`, so
terrain more than the render distance below the camera is fogged in vanilla too.
@@ -262,7 +262,8 @@ public final class VanillaChunkGenerationOracle {
case "the_end" -> net.minecraft.world.level.dimension.BuiltinDimensionTypes.END;
default -> net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD;
}).value();
LevelHeightAccessor height = dimension.equals("overworld") ? LevelHeightAccessor.create(-64, 384) : LevelHeightAccessor.create(0, 256);
// The level's build range is its dimension type's: a data pack's when one changes it.
LevelHeightAccessor height = LevelHeightAccessor.create(dimensionType.minY(), dimensionType.height());
var factory = PalettedContainerFactory.create(access);
var unsafeField = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
@@ -0,0 +1,5 @@
{
"type": "minecraft:max",
"left": "minecraft:overworld/final_density",
"right": "horizon_tall:peaks"
}
@@ -0,0 +1,17 @@
{
"type": "minecraft:add",
"left": {
"type": "minecraft:noise",
"noise": "horizon_tall:peaks",
"xz_scale": 1.0,
"y_scale": 0.0
},
"right": {
"type": "minecraft:gradient",
"axis": "y",
"from_coordinate": 0,
"from_value": 1.0,
"to_coordinate": 1900,
"to_value": -1.0
}
}
@@ -0,0 +1,5 @@
{
"base_amplitude": 1.0,
"base_octave": -9,
"octave_count": 3
}
@@ -0,0 +1,63 @@
{
"ambient_light": 0.0,
"attributes": {
"minecraft:audio/ambient_sounds": {
"mood": {
"block_search_extent": 8,
"offset": 2.0,
"sound": "minecraft:ambient.cave",
"tick_delay": 6000
}
},
"minecraft:audio/background_music": {
"creative": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.creative"
},
"default": {
"max_delay": 24000,
"min_delay": 12000,
"sound": "minecraft:music.game"
}
},
"minecraft:gameplay/bed_rule": {
"can_set_spawn": "always",
"can_sleep": "when_dark",
"error_message": {
"translate": "block.minecraft.bed.no_sleep"
}
},
"minecraft:gameplay/nether_portal_spawns_piglin": true,
"minecraft:gameplay/respawn_anchor_works": false,
"minecraft:gameplay/straw_bed_rule": {
"can_set_spawn": "never",
"can_sleep": "when_dark",
"destroy_on_leave": true,
"error_message": {
"translate": "block.minecraft.bed.no_sleep"
}
},
"minecraft:visual/ambient_light_color": "#0a0a0a",
"minecraft:visual/cloud_color": "#ccffffff",
"minecraft:visual/cloud_height": 192.33,
"minecraft:visual/fog_color": "#c0d8ff",
"minecraft:visual/sky_color": "#78a7ff"
},
"coordinate_scale": 1.0,
"default_clock": "minecraft:overworld",
"has_ceiling": false,
"has_ender_dragon_fight": false,
"has_skylight": true,
"height": 2096,
"infiniburn": "#minecraft:infiniburn_overworld",
"logical_height": 384,
"min_y": -64,
"monster_spawn_block_light_limit": 0,
"monster_spawn_light_level": {
"type": "minecraft:uniform",
"max_inclusive": 7,
"min_inclusive": 0
},
"timelines": "#minecraft:in_overworld"
}
@@ -0,0 +1,20 @@
{
"noise": {"min_y": -64, "height": 2096},
"default_block": "minecraft:stone",
"default_fluid": "minecraft:water",
"noise_router": {
"chunk_surface_level": "minecraft:overworld/chunk_surface_level",
"continents": "minecraft:overworld/continents",
"depth": "minecraft:overworld/depth",
"erosion": "minecraft:overworld/erosion",
"final_density": "horizon_tall:final_density",
"ridges": "minecraft:overworld/ridges",
"temperature": "minecraft:overworld/temperature",
"vegetation": "minecraft:overworld/vegetation"
},
"material_rule": "minecraft:overworld",
"spawn_target": [],
"sea_level": 63,
"disable_mob_generation": false,
"legacy_random_source": false
}
@@ -0,0 +1,7 @@
{
"pack": {
"description": "Horizon's tall-world fixture: a 2096-high Overworld with peaks past y 1000",
"min_format": 121,
"max_format": 121
}
}
+2 -1
View File
@@ -26,7 +26,8 @@ except ImportError:
import lab
import test_chunk_generation_parity as chunks
# HORIZON_PARITY_DATAPACKS: another folder of packs to compare.
# HORIZON_PARITY_DATAPACKS: another folder of packs (tools/fixtures/tall_datapacks: a 2096-high
# Overworld).
DATAPACKS = Path(os.environ.get('HORIZON_PARITY_DATAPACKS', lab.ROOT / 'tools/fixtures/datapacks'))
SEEDS = [12345, 4242]