Files
horizon-airdef/docs/world-generation.md
T
Ludovic Marie cb19660fbe Build Horizon Rust parity foundations and differential validation
Add version-bound core, world data, geometry, generation and feature implementations with native client/server comparison tools and scoped evidence reports. Apply Horizon project branding.

Workspace tests, Clippy and 147 Python tests passed. Full 51-family survival and expanded provider comparisons are still running; complete gameplay parity and a playable Rust client remain unfinished. Private game artifacts and the isolated simple-block placement work are excluded.
2026-10-06 11:24:51 +02:00

12 KiB

World generation

Horizon targets the repository-pinned Minecraft Java Edition 26.3. horizon_world::worldgen::decode_noise implements its direct normal-noise JSON codec. DensityRegistry decodes named density definitions and NoiseRouter loads the eight router roots against the same graph. AquiferConfig binds the six aquifer density roots, and NoiseBasedAquifer implements underground fluid and pressure selection. Complete terrain generation remains pending.

The decoder reads numeric tokens directly from serde_json::value::RawValue. This preserves decimal precision and the sign of integer -0, avoiding a double-precision intermediate before float density decoding. Integer fields retain Gson's truncation toward zero and wrapping to the low 32 bits, including decimal and exponent forms.

Noise definitions require a base octave in [-32,32]. Defaults are one octave, unit base amplitude, enabled normalization and an empty amplitude-modifier list. Octave counts range from 1 to 32. Nonempty modifier lists must contain exactly that many values. Amplitude bounds retain vanilla's floating-point comparator ordering: negative zero is below the modifier lower bound of positive zero. Normalization accepts booleans or the string "legacy". Null optional fields use their defaults; unknown keys are ignored and duplicate keys retain the last value.

The API accepts strict JSON. Gson's additional permissive syntax is outside this decoder's scope. Construction failures return typed errors; comparison checks classification and values rather than Java exception messages.

After preparing the pinned artifacts, run:

python3 tools/test_worldgen_parity.py
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings

The original Java transport uses vanilla's actual NormalNoise.DIRECT_CODEC, then its normal and legacy Nether initializers. The suite compares every bundled noise resource, six world seeds, both random streams and both initialization paths. Additional cases check codec defaults, normalization modes, field bounds, invalid types, nulls, duplicate/unknown keys, decimal integer conversion, signed zero and synthetic parameters. Comparisons include octave metadata, normalization/range bits, scalar noise and parent random state after success or failure. Raw registry contents and outputs remain private under .local.

The density loader resolves forward references into distinct immutable named holders. Numeric and object roots retain direct-value semantics. Its graph is loaded atomically, with typed errors for missing references and cycles. It implements the bundled function kinds, including nested splines, shifted noise, interval selection, rounding, surface searches and context-bound fields. Graph nesting and interval branches are bounded at 256; inline noise holders remain pending. These bounds are explicit limits of the Rust API.

The registry comparison decodes all 55 bundled density definitions using vanilla's actual registry-aware codecs and the 64 bundled noise resources. It compares ranges, axis dependencies, compilation failures, cache allocation, seeded scalar values and repeated strided volumes in raw and optimized modes. Density roots are checked through one compiler per seed and mode. Nonempty blending and structure fields are outside these fixtures.

The local run produced 961,693 matching output lines per reference JAR. The metadata report records source hashes, reference identities and resource digests. The runner authenticates both artifacts, verifies that their registry resources agree, removes stale success reports and rejects source or fixture changes during validation. CI includes the suite on its configured platforms; the recorded local run does not establish remote results or gameplay parity.

The router loader requires all eight fields in vanilla declaration order. It retains named holder identity and compiles every field through one shared cache namespace. Failed router decoding leaves the registry graph intact. All seven bundled preset routers now match the pinned JARs across six seeds, both random algorithms and raw/optimized compilation. Single-layer surface volumes and invalid multilayer surface requests are both checked. This API decodes the router object. The vertical NoiseSettings and optional aquifers members are also decoded; complete generator settings and material configuration remain pending.

NoiseSettings in 26.3 contains only minimum Y and height. Its JSON fields are bounded to [-2032,2031] and [0,4064]. The guard checks the wrapped upper limit first, then height alignment and minimum alignment to 16. The runtime create helper applies those guards without the JSON field bounds. Height-accessor clamping retains wrapped arithmetic, unaligned results and negative heights for disjoint ranges. The suite compares bundled noise members, runtime and codec construction, integer conversion and extreme accessor bounds.

Climate sampling converts quart positions to block coordinates with wrapped 32-bit shifts, evaluates all six fields in order with the same context, and quantizes using float multiplication followed by Java's saturating long cast. The suite compares vanilla's actual climate sampler binding and 4,116 quantization fixtures covering random float bits, subnormals, NaNs, infinities and saturation. The climate parameter decoder accepts scalar intervals, two-element lists and objects with min/max fields. Endpoints use directly parsed floats in [-2,2], with Java float comparator ordering before quantization. Parameter points require all six coordinates and an offset in [0,1]; negative-zero offsets fail that lower bound. Codec comparisons include precision-sensitive decimals, missing fields, invalid types and duplicate keys.

The immutable climate search tree now reproduces vanilla's stable construction order, 19-child default, wrapped interval distance and squared fitness, pruning, and previous-result tie handling. A searcher owns its previous leaf and borrows one index, preventing cache reuse across unrelated indices. Indexed and brute lookup intentionally retain their respective tie behavior. The suite compares all supported branching values 2..32, sizes around branch powers, duplicate entries, a 65,536-entry tree and extreme signed-long ranges/targets. The entry bound and typed failures are explicit Rust API limits.

MultiNoiseBiomeSource resolves biome identifiers through scalar climate sampling and prepared quart volumes. Bulk preparation evaluates six fields in declaration order and retains their float buffers; each query quantizes those stored values. Scalar and bulk queries share one previous-result state, so their query order retains vanilla's tie behavior. The bounded API rejects coordinates outside a prepared volume.

The runner exports built-in parameter tables independently from both authenticated JARs, requires them to agree and keeps them private. These contain 7,594 Overworld entries covering 56 biome identifiers and five Nether entries. Rust loads the quantized tables using a Horizon import format. This is an import of built-in data; the preset builders and vanilla biome codecs remain pending. The reference uses actual MultiNoiseBiomeSource resolvers with keyed registration holders. These checks resolve identities without evaluating biome properties or gameplay. Both tables are exercised against every router, including cross-preset fixtures; this does not establish End biome-source behavior.

The report records table and fixture digests. Source or private fixture changes during a comparison invalidate its success report.

The global fluid picker and disabled-aquifer path are now implemented. Lava selection uses Y below min(-54, sea_level); other positions use the default fluid status. Fluid status lookup returns its state only below its level. Disabled aquifers use the default solid block for positive density; nonpositive or NaN density calls the picker, and this path never schedules fluid updates. The reference comparison uses the actual global picker lambda and Aquifer.createDisabled, counting picker calls and checking default state identities. Regular-mode fixtures cover sea-level cutoffs, extreme coordinates, subnormal density, signed zeros, NaNs and infinities. Developer fluid-disable behavior is outside this corpus.

The noise-based aquifer implementation retains the six-field configuration's binding order, seeded positional cell generation, nearest-cell ties, pressure barriers, surface sampling, fluid levels and update scheduling. One context is shared across all density roots. Cell locations and statuses belong to each chunk aquifer; surface values are snapped to quart coordinates and cached. Global fluid statuses use exact state identity where vanilla does, while lava, water and air predicates classify blocks, including nondefault liquid states, cave air and void air. Dry cells use the version's WAY_BELOW_MIN_Y value -32512; this differs from the global developer-disable status's -4064.

The Rust API limits grids to 16,777,216 cells and constructor surface volumes to 2,097,152 samples, checks array bounds and returns typed errors. It preserves wrapped grid indexing within that domain, including vanilla's final-index aliasing. Allocation failures and invalid oversized grids are outside the comparison corpus. Read-only cache inspection does not populate caches.

The noise-based comparison uses actual vanilla configuration decoding and aquifer construction. It checks fluid states, scheduling flags, sampler call order and values, global picker calls and final location/status/surface caches. Fixtures include all bundled aquifer configurations, both seeded random factories, strided volume extents, repeated/reversed queries, nonfinite density/sampler values, malformed roots and injected failures followed by retries. The runner requires every injected failure to occur. Developer fluid-disable behavior is outside these regular-mode checks. This does not validate complete generated chunks or the registry-backed block classification adapter independently.

TagDefinitions decodes tag entries and files, applies resource-stack replacement, and resolves required and optional dependencies before building ordered, deduplicated memberships. Missing required references omit the affected tag while unrelated tags remain available. Lookup order and required flags retain the pinned runtime's hash bucket and dependency traversal behavior. A valid JSON resource that fails its tag codec can introduce an empty tag; invalid replacement leaves earlier entries intact.

The comparison checks all 300 bundled block-tag files against 1,286 block identifiers exported independently from both JARs. Synthetic fixtures cover replacement, malformed files, missing references, cycles, duplicate members and lookup order. The API is bounded to 8,192 definitions and dependency depth 256; Java hash-tree buckets and pathological self-recursive dependency searches remain unsupported. These checks cover tag loading, not the gameplay behavior of members.

The separate heightmap implementation and comparison now checks all six types, packed data, updates and priming against actual vanilla chunks.

The noise-fill stage now integrates final density, aquifers, registry-backed sections, world-generation heightmaps and fluid postprocessing. Complete terrain still requires material and surface rules, nonempty world fields, biome/feature/structure placement and complete generated-chunk validation. No rendering, frame-rate or complete gameplay claim follows from these decoder checks.

Subsequent biome lookup and chunk suites now validate fuzzy block-to-quart selection, section-atomic biome fills and multi-noise chunk assignment through actual ChunkGenerator.doCreateBiomes. The original report above remains a record of its narrower tested source snapshot.