Nucleation
A Minecraft schematic engine in Rust — load, build, simulate, mesh, and render schematics from seven languages.
This volcano island is a JSON description — signed distance fields plus material rules. Every image on this page was built and rendered by nucleation itself, and every snippet ran for real (images · snippets + outputs).
Contents · Install · The basics · Build · Masked edits · Terrain · Voxelize · Real world · Paintings · Read & stream · Regions & transforms · Block entities & NBT · Redstone · Mesh & render · Analyze · Worlds · Block database · Scripting · Storage · Languages · Docs
Install
Kotlin/JVM, PHP, C, and C++ ship as archives on Releases — quickstarts below.
The basics
A Schematic is a named collection of blocks (plus block entities, entities,
and metadata) — one or many named regions. Load one from any supported format,
edit it with plain coordinates and block strings, save it in any other:
= # any format, auto-detected
# (3, 3, 3)
# y=3: the region grows to fit
# "minecraft:glowstone"
# format from the extension
The same loop in JavaScript — the WASM build has no filesystem, so it's bytes in, bytes out:
import from "nucleation";
import from "node:fs";
const cube = ;
cube.;
;
Block-state strings with properties work anywhere a block is named —
"minecraft:lever[face=floor,facing=east]" — and every block string a
schematic can contain round-trips. Later Python snippets assume
from nucleation import * and an existing schematic s; each has a fully
runnable version with captured output in
docs/readme-snippets/.
Build: shapes, brushes, palettes
Spheres, tori, cones, pyramids, bezier ribbons — plus boolean combinators — filled by brushes that pick each block:
A gradient brush follows a shape's own parameter — around the ring of a torus, along a bezier — and snaps every color to a palette:
=
The shaded brush lights a base color by surface normal — 3D-lit forms out of flat blocks:
= # base color, light direction
And palettes turn colors into blocks. Ask for pure white → pure black in 24 steps and the engine picks the blocks itself — distinct, ordered, off-hue candidates penalized (bottom row; above it, the lightness-sorted wool, concrete, terracotta, and planks presets):
# 24 distinct blocks: white_wool ... iron_block ... deepslate_tiles ... black_concrete
And when a ramp still bands, dither it: Palette.…().dithered() makes
every brush alternate between the two nearest blocks per voxel (ordered
Bayer, deterministic) — hard bands on the left, dissolved on the right:
Or build palettes from pure color logic over the block database — no names, just measured color values and block facts:
=
# near-neutral only
# mid-grays
= # 40+ blocks, picked by math
# everything lime-ish, nearest first: lime_concrete_powder (0.053), ...
And shapes aren't limited to the primitives — any SDF tree is a Shape,
so smooth-blended distance fields fill with every brush. Field-gradient
normals mean the shaded brush shades a blend continuously across the seam:
=
# masked fills work too
More in the guides: shapes & brushes · palettes, ramps, and pixel art.
Edit without collateral damage
Masked fills touch only what you allow: fill_only_air builds around
existing work; fill_replacing swaps listed blocks inside a shape — a
temple weathering into moss and cracks within a sphere of decay:
Terrain from a JSON description
The same SDF trees that work as shapes scale up to whole terrains: sampled through declarative material rules (surface shells, depth bands, gradients, scatter) instead of a single brush. Deterministic: same JSON, same terrain, every language.
=
=
=
# → 29×18×29, 6,927 blocks
That's the minimal version; the volcano up top adds smooth-blended cones, a cylinder-cored lava crater, and noise-gated snow. Smooth booleans even animate into metaballs — recipes, node/rule schemas, and the gradient fill rules live in the SDF terrain guide.
Voxelize 3D models
Real 3D models become schematics: GLB (with node transforms and embedded
textures) and OBJ load into a MeshModel, and a voxelized mesh is — like
everything else here — just a Shape. Inside/outside comes from
triangle-parity ray casting; normals come from the nearest triangle, so
lighting brushes simply work. The Utah teapot under one spotlight, through
the grayscale ladder:
= # shell closes its thin ceramic walls
=
And textures project onto the voxels: each block takes the palette-closest color of its nearest surface point (barycentric UVs, bilinear sampling) — the classic COLLADA duck, beak and eye catchlights intact:
=
# 25,641 blocks: yellow_wool body, orange beak, black eyes with snow-block catchlights
And it scales: a full Mario Kart 64 Rainbow Road, voxelized to a road eight blocks wide — 515 blocks long, 51,000 blocks, solved in 1.5 seconds by the scanline voxelizer:
A ribbon in the void is the easy case. Koopa Troopa Beach is the hard one — an open island of sand, dirt track, cliffs, palms and a central lagoon, with the sea faked in as a floor plane so the parity solver has a closed volume to fill. Same call, a color-matched beach palette, and the shore reads at a glance:
The real world, in blocks
Texture mapping and the color math, animated: a voxel Earth spinning under a fixed sun — every frame, every surface block is re-picked by its luminosity through the dithered palette, so continents sweep through a true day/night terminator:
And real geodata voxelizes straight from public sources. The geo entry points
take data, not URLs — you fetch and project, they build the blocks. The
Matterhorn is an AWS elevation grid through Geo.heightmap_terrain (300×300
columns, ~53 m/block, then snow/scree/meadow bands by elevation and slope):
…and Wall Street is OpenStreetMap footprints through Geo.extrude_footprints —
179 buildings, each a Shape.polygon_prism extruded to its tagged height at
1 block = 2 m, stacked tallest-wins and banded by height:
# Each footprint is [x, z] block coords + a height; Geo rasterizes and extrudes.
=
=
That whole 2.4-million-block district is one schematic — and it streams straight out to a playable Minecraft world, region files and all, chunk by chunk in constant memory (see Read, iterate, and stream):
# or stream chunk-by-chunk with WorldSink
All four are reproducible recipes in
tools/readme-media/generate.py
(globe, mountains, city), and the geo API has a
runnable snippet.
Paintings, in blocks
Everything above composes: flat-texture palettes built by color-logic filters, chroma-boosted matching (so muted pigments land on saturated blocks, not gray clays), and per-voxel ordered dithering — pointed at art. Van Gogh's Starry Night, 128 blocks wide:
= # PaletteBuilder + map-art excludes
, , = # chroma exaggeration pre-match
The full recipe — including the flat-palette filter chain — is
scene_paintings in tools/readme-media/generate.py.
Read, iterate, and stream
Everything above writes blocks. This is how you read them back and process
builds too big to hold in memory. Any schematic splits into fixed chunks in a
traversal order you choose — bottom_up, top_down, center_outward,
distance_to_camera, or random. Freeze a center-outward walk 60% of the
way through and the iterator's wavefront reads straight off the terrain:
plasma-tinted columns have been visited, green ones haven't yet.
# Walk a build in 16×16×16 chunks, center-outward from a point:
The same idea scales past memory: stream a real world folder chunk-by-chunk and write a transformed copy, with only one chunk resident at a time — RAM stays flat whether the world is 10 MB or 10 GB.
= # or .from_zip(bytes), or *_bounded(...)
=
= # a WorldChunkView
break # end of stream is signalled by raising
# inspect or edit here: chunk.set_block(...), chunk.to_schematic(), ...
And the chunk is a two-way bridge to the building tools. to_schematic()
reads a chunk out; WorldChunkView.from_schematic(schematic, cx, cz) writes one
back — so anything that fills a schematic becomes a custom world generator, one
chunk at a time. Fill an SDF (or OSM footprints, a heightmap, noise) clipped to
each chunk and stream it straight to a playable world; intersecting with the
chunk means the field is only evaluated inside the chunk being written, so it
never materializes:
=
=
=
=
Run the same bridge the other way and it's a processing pipeline: WorldStream
in → to_schematic → transform with any tool → from_schematic → WorldSink
out. The OSM city, an SDF, a heightmap, a filter — all the same three moves
(generator + filter snippet).
Regions, transforms, and stamping
A schematic is multi-region in the Litematica sense — many named sub-volumes,
each with its own palette and bounds — and both whole builds and single
regions transform in place. Here a keep and two wings are three separate
named regions; rotate_region_y turns the copper wing 90° and leaves the
keep and the prismarine wing exactly where they were:
# Address independent named regions in one schematic:
# ["Main", "keep", "gate"]
# turn one region, leave the rest
# Transform the whole build — rotate_x/y/z (degrees), flip_x/y/z:
# a bar's +x tip at (9,0,0) lands at (0,0,0)
# Stamp a sub-volume of one schematic into another:
# source ── from box ── ── to ── exclude
Block entities, entities, and NBT
Blocks carry NBT, and the schematic holds full block entities and entities, round-tripped as SNBT — so a chest keeps its loot table and a spawner its mob. A vault of them — chests, barrels, dyed shulker boxes, a caged spawner, and brewing/enchanting furniture, every one an NBT carrier:
# A chest with contents, set straight from SNBT:
# → {Items:[{...diamond, Slot:0B, Count:3B}, {...emerald, Slot:1B, Count:5B}]} (SNBT)
# Entities parse from SNBT too:
# 1
Simulate redstone
Headless circuit simulation via MCHPRS's redpiler — and it runs in the browser, since simulation ships in the WASM build. Flip the lever, tick the world, and the lamp (and wire) light up:
import from "nucleation";
const world = ; // lever → wire → lamp
world.; // flip the lever
world.;
world.;
world.; // → true
Eight of those lines make a display — levers flipped through the simulator, lamps showing the byte:
Beyond poking blocks: a typed executor drives circuits through named, typed
inputs and outputs (booleans, integers, floats, ASCII) with layout builders
for buses. Build an IoLayout, wrap the world in a TypedCircuitExecutor,
and set an 8-bit input by value instead of toggling wires by hand — see the
docs.
Mesh and render
Any schematic → GLB/glTF or USDZ using any vanilla-format resource pack, plus the headless GPU renderer that drew this page. The sphere-fit camera holds a rotation-stable frame — this turntable is 40 renders of the hero island:
=
= # magic b'glTF'
=
# rotation-stable framing
Analyze: diff, fingerprint, auto-stack
Structural diffs know what was added, removed, changed, and swapped — here as a ghost view, additions in green, removals in red:
= # distance 3; summary JSON with regions
# False (fingerprints are translation-invariant)
And nucleation can find the repetition in a build — the lattice of a tiling wall, a repeater bus, a pixel grid — and restamp it to a new size:
# {"mode": "1d", "vectors": [[4,0,0]], "coverage": 1.0}
= # 2 units → 6: (8,4,1) → (24,4,1)
Worlds and versions
Schematics round-trip through playable worlds — export a real world folder
(level.dat + region files), import any world back, bounded to a box or
streamed chunk-by-chunk in constant memory:
=
The built-in DataConverter port migrates blocks, items, and entities across Minecraft data versions (loss reports on downgrades), and Java ↔ Bedrock translation runs on GeyserMC's mappings at full 26.2 parity.
The block database
Under it all sits a block database extracted from Mojang's own data generator and the vanilla jars — kinds, variant families, resolved tags, geometry, measured colors for all 1,196 Minecraft 26.2 blocks — which updates itself when Mojang ships a new version. It's what lets palettes reason about color and brushes about block facts:
# {"kind": "minecraft:stair", "base_block": "minecraft:oak_planks",
# "tags": ["minecraft:mineable/axe", ...], "full_cube": false, ...}
# [oak_planks, oak_button, oak_fence, oak_fence_gate, oak_pressure_plate, oak_slab, ...]
Scripting
Embedded Lua and JS engines run build scripts against the full API — this
sine wall is a 12-line Lua script run through Scripting.run_lua_script
(scripting guide):
Pluggable storage
A library of builds — any schematic saves and loads through one URI, across backends — memory, filesystem, S3, Redis, Postgres:
Two layers: StoreIo moves whole schematics, Store is a raw key-value store
over the same backends.
# Whole schematics, by URI (format inferred from the path — or defaulted):
=
# Or raw key-value over any backend:
= # also file:// · s3:// · redis:// · postgres://
# "Mw=="
# ["meta/version"]
One API, seven languages
Every binding is generated from one annotated-Rust source of truth
(src/bridge/) via
Diplomat — committed,
regenerated, and diffed in CI so they can never drift:
| Language | Package | Errors | Naming |
|---|---|---|---|
| Rust | nucleation crate (native API) |
Result |
snake_case |
| JavaScript | npm install nucleation |
exceptions | setBlock |
| Python | pip install nucleation |
exceptions | set_block |
| Kotlin/JVM | Release JAR (JNA, 5 platforms bundled) | kotlin.Result |
setBlock |
| PHP | Release archive (php/ + FFI) |
DiplomatError |
setBlock |
| C | Release archive (include/ + library) |
result structs | Schematic_set_block |
| C++ | Header-only over the C ABI | diplomat::result |
set_block |
What ships where:
| Channel | Surface |
|---|---|
| npm | full surface; WASM includes simulation + meshing (no GPU rendering) |
| PyPI | full surface, including simulation, meshing, rendering, scripting |
| Release archives + JAR | full surface, native, 5 platforms |
| crates.io | full surface except simulation* |
* MCHPRS isn't on crates.io — for simulation in Rust, use
nucleation = { git = "https://github.com/Schem-at/Nucleation", features = ["simulation"] }.
import at.schem.nucleation.*
val schematic = Schematic.create("demo")
schematic.setBlock(1, 2, 3, "minecraft:stone").getOrThrow()
println(schematic.getBlockName(1, 2, 3).getOrThrow()) // "minecraft:stone"
int
Documentation & development
- Documentation index — per-language references and all feature guides (shapes & brushes, palettes, SDF terrain, scripting, block database)
docs/readme-snippets/— every snippet above with its verified output- Release notes
Also in the box: layer-art templates (schematics from ASCII art).
CI regenerates bindings and fails on drift, exercises every built wheel and the assembled JAR before release, and smoke-tests all seven language bindings on every push.
License
MIT. See LICENSE.