- Rust 75.9%
- Java 22.7%
- Python 0.7%
- Shell 0.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
bit-identity / bit-identity (b1.7.3) (push) Successful in 1m23s
bit-identity / bit-identity (1.12.2) (push) Successful in 3m30s
ci / release (macos-arm64) (push) Successful in 2m51s
bit-identity / bit-identity (1.21.4) (push) Successful in 7m20s
ci / release (linux-arm64) (push) Successful in 3m25s
bit-identity / bit-identity (26.2) (push) Successful in 9m4s
ci / release (macos-x86_64) (push) Successful in 3m4s
setup-versions / setup (1.12.2) (push) Successful in 1m20s
ci / release (linux-x86_64) (push) Successful in 3m45s
setup-versions / setup (1.21.4) (push) Successful in 2m0s
ci / release (windows-x86_64) (push) Successful in 3m35s
setup-versions / setup (26.2) (push) Successful in 1m19s
bit-identity / bit-identity (b1.8.1) (push) Successful in 1m15s
|
||
| .cargo | ||
| .forgejo/workflows | ||
| .github/workflows | ||
| .vscode | ||
| crates | ||
| java | ||
| Scripts | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
worldclanker
From-scratch Rust reimplementations of Minecraft overworld world generation, built to be bit-identical to the real Java server for the same seed — same blocks, same states, same RNG stream, down to the last block. Every layer is validated against the real server bytecode:
- Beta 1.7.3 (the classic pre-release engine) — bit-identical to the real CraftBukkit Beta
1.7.3 server across the full pipeline: terrain, surface, caves, ravines, and the complete population
pass including the synchronous fluid-flow engine (
WorldGenLiquidssprings). Validated live in CI over an n×n region (the Beta oracle jars aren't on Mojang's manifest, so the run downloads a community archive on demand); far-out chunks (~180k blocks out) match too. Ground truth is the server's controlled chunk generation, the same stance the 1.12.2 oracle takes. - Beta 1.8.1 (the Adventure Update engine — a completely different biome/terrain system than 1.7.3)
— COMPLETE. Rebuilt from scratch against the real CraftBukkit Beta 1.8.1 server and bit-identical
(block id + metadata) across the full pipeline: GenLayer biomes, density terrain, surface, caves,
ravines, the complete population pass (ores, disks, trees incl. big/swamp/taiga, springs with the
synchronous fluid-flow engine, lakes, dungeons), and all three structure families — mineshafts,
strongholds, villages (placement, layout, every piece type, chest loot, spawners, the village
lake-suppression gate). Verified 0-diff on six oracle regions for the 2b2t seed — swamp, spawn,
a 64-chunk mineshaft cluster, a 100-chunk stronghold, a 169-chunk village, and a taiga — and
validated live in CI alongside 1.7.3. Getting here required reproducing some deep engine
behaviour: Java's aliased
BlockFlowingdirection-array field, exact material/opacity/lighting tables,World.randomdraw accounting (torch pops, spawn passes, village reseeds), and Beta's chunk-local lighting-bug quirk. - 1.12.2 (legacy engine) — COMPLETE. A seed-42 region generated end-to-end (terrain + populate) matches the real jar with 0 differences across 1024 chunks (32×32).
- 1.21.4 (the post-1.18 "Caves & Cliffs" density-function engine) — COMPLETE. RNG, noise, density router, terrain/aquifers/ore veins, biomes, surface, carvers, a 129-entry oracle-validated PlacedFeature registry (including decorated trees), Beardifier terrain adaptation, and every overworld structure family (village, mansion, monument, stronghold, mineshaft, shipwreck, igloo, ocean ruin, ruined portal, desert/jungle temple, swamp hut, buried treasure, ancient city, trail ruins, trial chambers, pillager outpost) wired into full cross-chunk region decoration: a seed-42 64×64-chunk region with structures enabled (terrain + surface + carvers + structures + decoration) matches the real 1.21.4 server with 0 differences across all 4096 chunks (402,653,184 blocks).
Installation
Install the mc-worldgencli binary straight from the repository with Cargo (needs a stable Rust
toolchain — nothing else):
cargo install --git https://git.eplg.services/obvtiger/worldgen mc-worldgencli
This builds and drops mc-worldgencli into ~/.cargo/bin (make sure it's on your PATH). To update
later, re-run the same command with --force.
Generating a world
mc-worldgencli generates a region and writes the Anvil region files + level.dat directly — a real,
loadable save, no Java step and no oracle jar. It's resumable: re-running the same command skips
batches already on disk.
# 1.21.4, the 2b2t seed, 64×64 chunks (radius 32), into ./world
mc-worldgencli --version 1.21.4 --seed -4172144997902289642 --radius 32 --out ./world
# 1.12.2 (legacy engine)
mc-worldgencli --version 1.12.2 --seed 42 --radius 32 --out ./world-1122
# Beta 1.7.3 and Beta 1.8.1
mc-worldgencli --version b1.7.3 --seed 42 --radius 16 --out ./world-b173
mc-worldgencli --version b1.8.1 --seed 42 --radius 16 --out ./world-b181
Copy the output folder into your Minecraft saves/ directory and open it in the matching game
version (don't let a newer client convert it). You spawn at the centre of the region.
Chunks are generated in [-radius, radius) on both axes — a 2*radius-chunk square. Beta and 1.21.4
generate the whole square in one pass (bit-identity needs it), so memory scales with the square:
radius 32 (64×64 chunks) peaks around 0.6 GB for beta and ~1 GB for 1.21.4 (also 1.21.4's radius cap
for now). Add --terrain-only for raw terrain + carvers with no ores/trees/structures.
Full flag reference:
$ mc-worldgencli --help
Usage: mc-worldgencli [OPTIONS] --out <OUT> --seed <SEED>
Options:
--out <OUT> Output world directory (created if missing)
--seed <SEED> World seed (accepts negatives)
--radius <RADIUS> Chunks generated in [-radius, radius) on both axes (a 2*radius side square)
--version <VERSION> "1.12.2", "1.21.4", "b1.7.3" or "b1.8.1" [default: 1.12.2]
--batch <BATCH> 1.12.2 only (default 32): resume/halo granularity in chunks per side
--terrain-only Skip population: raw terrain + carvers only
-h, --help Print help
Compressing an existing world
The generators are deterministic, so a stored world is mostly redundant: given the seed and the era
that made it, the terrain can be regenerated instead of stored. mc-worldgencli uses that as a
compression dictionary — it keeps only the residual between what is on disk and what the generator
reproduces, and rebuilds the world on the way out.
# How small would this world get? Writes nothing.
mc-worldgencli analyze --world ./world
# Compress it. Restores to a byte-for-byte equal *decoded* world (see below).
mc-worldgencli compress --world ./world --out ./world.wclk
# And back.
mc-worldgencli decompress --in ./world.wclk --out ./world-restored
analyze runs the identical pipeline to compress with the writing switched off, so the ratio it
prints is the ratio you actually get — not an estimate of it.
Both accept a world directory, a bare region/ folder, or a single .mca/.mcr file, and both
search a candidate set:
--world <WORLD> World dir, region/ folder, or a single .mca/.mcr
--seeds <SEEDS> Comma-separated candidate seeds [default: the four 2b2t-era seeds]
--eras <ERAS> Comma-separated: b1.7.3, b1.8.1, 1.12.2, 1.21.4, 26.2 [default: all]
--max-regions <N> Stop after N region files
--threads <N> Worker threads [default: parallelism - 2]
--no-verify Skip the per-delta round-trip check (faster, less certain)
--json One JSON object instead of the human report
The seed is the dictionary, so --seeds is what makes this work at all; --eras narrows the
search. Cost is eras × seeds region regenerations per region file, and a modern-engine window is
minutes and ~1 GB — so pass both whenever you know the answer. Legacy-storage regions skip the modern
engines automatically, since a post-1.18 engine cannot predict a pre-1.13 chunk.
Every candidate is encoded and measured, and the smallest wins — including storing the chunk verbatim. A chunk is therefore never made bigger by being compressed, and a world nothing predicts costs about what it costs today. Correctness does not depend on the prediction being right either: a wrong prediction simply fails to match and the voxel is stored as-is. Only the ratio suffers.
All five eras are supported for real, across all three storage generations — Beta McRegion (.mcr),
pre-1.13 Anvil, and modern palette-based Anvil including the Y -64..320 range. Every delta is
round-tripped in memory before being accepted (turn it off with --no-verify).
Everything in the world folder that isn't an overworld region file — level.dat, other dimensions,
entities/, poi/, data/ — is carried along compressed but undelta'd, because nothing predicts it.
It's tiny next to the terrain, and it's the difference between an archive and a restorable world.
One caveat worth stating plainly: restoring is content-lossless, not byte-lossless. The decoded
chunk NBT comes back exactly, but the rebuilt .mca files differ in bytes, because zlib level, sector
packing and the region timestamp table are all writer choices. That is the same bar the generator's own
resumability test holds itself to.
Repository layout
worldclanker/
├── crates/
│ ├── mc-core/ # engine primitives shared across versions (RNG, noise, density, …)
│ ├── mc-worldgen-beta/ # the Beta 1.7.3 generator + its CraftBukkit-oracle fixture suite
│ ├── mc-worldgen-b1_8/ # the Beta 1.8.1 generator + its CraftBukkit-oracle fixture suite
│ ├── mc-worldgen-v1_12_2/ # the 1.12.2 generator + its bit-parity fixture suite
│ ├── mc-worldgen-v1_21_4/ # the 1.21.4 generator + its bit-parity fixture suite
│ ├── mc-version-detector/ # classify stored chunks by worldgen epoch (incl. 1.18 blended); .mca read/write,
│ │ # lossless chunk-grid codec + per-era prediction for the compressor
│ ├── mc-world-delta-compressor/# the delta transform + its inverse, and a DEFLATE size model
│ └── mc-worldgencli/ # end-to-end resumable CLI → a real, loadable world; also analyze/compress/decompress
├── java/
│ ├── mappings/ # MCP stable_39 mappings for 1.12.2 (community, redistributable)
│ ├── beta_fixtures/ # committed CraftBukkit golden chunks for the Beta 1.7.3 / 1.8.1 suites
│ ├── reference/ # 1.12.2 oracle dumpers + WorldExport (the save packager)
│ └── reference-1.21.4/ # Ref1214.java — the 1.21.4 golden-value dumper (Java 21)
├── Scripts/
│ ├── setup.sh # download + deobfuscate a Mojang jar → the validation oracle
│ ├── dump_reference_1214.sh # run a Ref1214 dump mode (fixtures / regions)
│ ├── generate_java_world.sh # REAL-server region dump (1.12.2 or 1.21.4)
│ └── generate_rust_world.sh # Rust region dump in the same byte format
├── .forgejo/workflows/bit-identity.yml # CI: 64×64 live-oracle compare per version, every push
└── README.md
Mojang's jar is not redistributed. The validation oracle (
java/server-srg.jar) and the decompiled source are produced locally bysetup.sh(see below); they're git-ignored, never committed. The Rust crate + its fixtures are self-contained and need none of this to build or test.
Prerequisites
- Rust (stable, 2021 edition) — for the generators and tests. This is all you need for the crates.
- For the validation oracles / world export only: Java 8 (1.12.2 oracle) and/or Java 21
(1.21.4 oracle), plus
python3,curl/wget, andunzip(used bysetup.sh).
Setting up the validation oracle (setup.sh)
The oracle is the real Minecraft server, deobfuscated. Since the jar can't be shipped, fetch and build it on demand (under Mojang's EULA):
./setup.sh --list 1.12 # list matching release versions
./setup.sh 1.12.2 # download + deobfuscate → java/server-srg.jar (the oracle)
./setup.sh 1.12.2 --decompile # also produce readable source (see below)
setup.sh lists any release, downloads its server jar (handling modern "bundler" jars), pulls the
tools it needs, and deobfuscates: 1.12.2 via the bundled MCP stable_39 SRG mappings; 1.14.4
through 1.21.x via Mojang's own official mappings (e.g. ./setup.sh 1.20.1 → java/1.20.1-mojang.jar).
26.2 and newer already ship readable net/minecraft names and publish no mappings, so the jar is
used as-is (no remap). The worldclanker oracle itself targets 1.12.2.
--decompile decompiles the jar to readable .java (with CFR, which runs on the same Java 8 the
1.12.2 oracle needs) into java/decompiled-<version>/. For 1.12.2 it then auto-substitutes the MCP
stable_39 human-readable names (func_76304_a → generateNoiseOctaves) into java/named-<version>/.
All tools (JAVA env to override) and outputs are git-ignored.
Build & test
cargo test --release --workspace # every bit-parity suite (all versions, committed fixtures only)
End-to-end region compare (either version)
The Scripts/ directory holds the oracle-comparison harnesses used for validation — e.g.
generate_java_world.sh / generate_rust_world.sh (1.12.2 & 1.21.4 region dumps in a common byte
format) and compare_beta.sh (live CraftBukkit oracle vs Rust). The same compares run in CI
(.forgejo/workflows/bit-identity.yml, self-hosted) at full scale on every push: a 64×64-chunk
live-oracle compare for each version — 1.12.2, 1.21.4 (structures on), Beta 1.7.3, and Beta 1.8.1.
(.github/workflows/bit-identity.yml is a GitHub-runner variant with small PR regions and a nightly
schedule, sized for GitHub's 6h job limit.)
Using the library (1.12.2)
use mc_worldgen::WorldGen;
let mut world = WorldGen::new(42); // construct once per seed
let chunk = world.terrain_chunk(0, 0); // generate chunk (0, 0)
let (id, meta) = chunk.block(8, 64, 8); // (block_id, metadata) at local x,y,z
let air = chunk.is_air(8, 100, 8);
let raw = chunk.state(8, 64, 8); // packed (id << 4) | meta
let all = chunk.states(); // &[u16; 65536], index (x*16 + z)*256 + y
let biome = world.biome_at(8, 8); // biome id at world block (x, z)
terrain_chunk returns the per-chunk terrain (stone/water shape + surface builder + cave/ravine
carving). Decoration (ores, trees, lakes, snow) and structures are a separate multi-chunk populate
pass that writes across chunk borders — that wiring lives in the full-world driver
(tests/worldgen_parity.rs). For a ready-to-play save, use mc-worldgencli (see
Generating a world) rather than driving the library directly.
Detecting the worldgen version of an existing world
mc-version-detector classifies the worldgen epoch of already-generated chunks straight from their
.mca region files — including the mixed/blended chunks 1.18 left behind where an older surface sits
over a regenerated modern underground. It ships its own zero-dependency DEFLATE inflate, .mca
reader/writer, and NBT parser, plus several diagnostic binaries (classify, census, betadetect, …).
# per-region epoch map + summary, or point it at a whole world/region dir for an aggregate distribution
cargo run --release -p mc-version-detector --bin classify -- path/to/world/region