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 · Compose · Patterns · Read & stream · Regions & transforms · Block entities & NBT · Redstone · Mesh & render · Analyze · Worlds · Block database · Scripting · Storage · Gallery · Languages · Docs
Install
Kotlin/JVM, PHP, C, and C++ ship as archives on Releases; see the quickstarts below.
The basics
A Schematic is a named collection of blocks, plus block entities, entities,
and metadata, held in 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 is 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, like
"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, and 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 or along a bezier, and snaps every color to a palette:
=
The shaded brush lights a base color by surface normal, giving 3D-lit forms out of flat blocks:
= # base color, light direction
And palettes turn colors into blocks. Ask for pure white to pure black in 24 steps and the engine picks the blocks itself: distinct, ordered, with 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
Seven presets ship, each a curated set you can list with block_ids_json:
concrete and wool (16 dyed colors each), terracotta (17), wood (13 plank
tones), grayscale (81 neutrals), and the broad decorative (951) and
structural (316) sets. Between any two colors a palette interpolates directly:
gradient_ids_json samples the line evenly, ramp_ids_json picks distinct steps.
# crimson -> gold: red, red, pink, orange, orange, yellow, yellow, yellow
# distinct steps
# red, pink, magenta, purple, blue, light_blue terracotta
A gradient brush does the same in 3D and paints as it fills. Five pairs, each a
linear_gradient in Oklab over a dithered palette, so every ramp is a smooth
blend rather than a few hard steps:
Sweep a single hue around a closed loop instead of between two endpoints and the ramp closes on itself with no seam. A trefoil knot, one hue dithered red -> blue -> green -> red around its length:
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. Here a temple weathers
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 and rule schemas, and the gradient fill rules live in the SDF terrain guide.
Slice the hero island in half and the material rules show their work: a grass and dirt skin over a stone core that grades from deepslate at the roots up through tuff to andesite, with the lava pool sitting in the crater.
Materials can also key on the surface normal, not just height and depth. A
DistanceField reads it off any build (slope is its upward component), so a
landscaping rule paints by steepness: flat ground takes grass, steepening slopes
take coarse dirt then bare stone, and the flat peaks catch snow.
= # depth + normal of any build
= # 1 flat, ->0 vertical
=
Voxelize 3D models
Real 3D models become schematics: GLB (node transforms, 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, and
normals from the nearest triangle, so lighting brushes work on it directly. The
Utah teapot under one spotlight, through the grayscale ladder:
= # shell closes its thin ceramic walls
=
The same teapot, printed layer by layer: the unbuilt volume stays on as a glass ghost so the frame holds still while solid, height-colored layers sweep up.
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, 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. The same voxelizer call handles it, with a color-matched beach palette:
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, pointed at art: 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. 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.
And a flat image is also a heightmap: read each pixel's brightness as a column height and paint it with its own color, and the same Starry Night lifts off the canvas into rolling, luminous terrain, the moon and stars its highest peaks.
Everything composes
Nothing on this page is a special case. Shapes, SDF booleans, deformation,
texture projection, and the palette engine are pieces you stack. This ring is
five of them in one build: a torus SDF with a lattice of spheres subtracted
for holes, a noise warp to deform it, then Van Gogh's Starry Night wrapped
around the ring and tube and matched through the dithered flat-art palette.
# One SDF: a torus, minus a repeating lattice of spheres, warped by noise.
=
# Wrap the painting on: UV from the torus geometry, color through the palette.
=
, = # angle around the ring, angle around the tube
Swap the torus for a mesh, the painting for a heightmap, the palette for
grayscale, and it is a different build with the same five moves. There's a
runnable version you can paste
and adapt; the full recipe is scene_compose in
tools/readme-media/generate.py.
Fields and patterns
A pattern is a scalar field, and nucleation already speaks fields: the SDF JSON
that builds terrain. The cells node adds Worley / Voronoi noise to that
language, so one field stamps a pattern two ways. Point a field brush at it
to color by the field (each cell a flat color), or feed its value into
geometry (each cell's value drives a column's height):
=
# Texture: color every voxel by which Voronoi cell it falls in.
=
# Geometry: raise each column to its cell's value.
= # 0..1 per cell
cells has f1, f2, and f2MinusF1 (the classic crack field) modes too, and
it composes with every other SDF node: subtract it for a foam, intersect it,
warp it. Voronoi is one field; the same brush and the same node take any of the
others.
Put all three modes to work at once and you get a build, not a demo. This
fractured planet reads f1 to shade each cell light at its center and dark at
its rim, cuts recessed buffer grooves along the f2MinusF1 cracks, and pours a
glow down the surface normal. That last field needs no new tool: the depth into
the sphere is R - length(p), exactly the signed distance an SDF returns, so the
same idea gives a gradient normal to any shape. Each groove wears a couple
layers of orange glass over light-emitting blocks that brighten with depth,
shroomlight fading into glowstone toward the core:
=
=
= - # distance along the surface normal
# glowing core
= # glass shell, then emitters deeper
# recessed buffer groove
= None
# cell crust
= # light center, dark rim
None of that is sphere-specific: it is three fields over (x, y, z) plus a
depth. An SDF shape gets the depth for free (its own value), and for any other
build DistanceField.from_schematic runs the distance transform and hands back
the depth (and a surface normal). So the same material paints over arbitrary
geometry. Here it repaints the pre-existing hero island schematic block for
block, its glowing seams following the Voronoi field across the terrain:
The fractured look is not a built-in, just one rule written over those
primitives. Swap the rule and the same DistanceField naturalises instead: on a
clean stone-brick temple, slope and a patch-noise field settle moss and grass on
the flat tiers, creep mossy brick down the steps, and leave the steep walls bare
and cracked. Same primitives, ancient ruin:
And those are two rules of many. The same handful of inputs, a DistanceField's
depth and normal, block occupancy, and position, plus a field and a palette,
drive a whole range of treatments. Snow settles on up-facing surfaces; copper
greens with exposure; height bands a badlands mesa; ambient occlusion darkens the
recesses of a rock; and corners chip to mossy cobble by how many faces they
expose. None of it is a built-in, each is a short rule:
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:
=
=
=
=
Here's the OSM Financial District doing exactly that: 179 buildings streamed out one 16×16 chunk column at a time, a diagonal wavefront assembling the whole 2.4M-block skyline:
The source is whatever you fill with. Swap the OSM footprints for an SDF and the same generator streams a terrain instead, each chunk the SDF evaluated only inside that chunk, materializing center-outward:
Run the same bridge the other way and it's a processing pipeline: WorldStream
→ to_schematic → transform with any tool → from_schematic → WorldSink. The
OSM city, an SDF, a heightmap, a filter: 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 with rotate_x/y/z (degrees) and 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 min-corner max-corner target exclude
Those two operations compose into symmetry. This mandala is one asymmetric
petal, built once, then flip_x and flip_z mirror four copy_region stamps
into the quadrants of a canvas so the domes meet at the center:
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 and 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 too, 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: the simulator flips one lever per tick and the lamps light in sequence, the wavefront assembling 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. Bind an 8-bit a input to the levers and a y output to the lamps, then
hand execute a number: the redpiler sets the bus and the typed output reads
straight back, no wires toggled by hand.
= # bus = the 8 lever->wire->lamp lines
# flat [x,y,z,...] positions
=
=
# -> 178, straight back through the lamps
The simulator is also a probe: read get_redstone_power anywhere and the
circuit becomes data. A signal leaves a redstone block at strength 15 and drops
one per block down a dust line; plotting the reading as a hot-to-cold staircase
turns that falloff into a field you can see:
Mesh and render
Any schematic exports to 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, shown here as a ghost view, additions in green, removals in red:
= # distance 3; summary JSON with regions
# False (fingerprints are translation-invariant)
Fingerprints are position-blind, and the shape preset is orientation-blind
too: a build moved and turned still reads as a duplicate, while adding one block
makes it unique. Deduplicate a library no matter how each copy was placed:
And nucleation can find the repetition in a build, the lattice of a tiling wall, a repeater bus, or 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.
Those loss reports are per block, so you can see a downgrade before you commit it. Here a sampler of blocks from many eras is checked against 1.12.2 (before the Flattening) and recolored by the verdict: green survives, red is a loss.
=
= # what 1.12.2 cannot represent
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, and measured colors for all 1,196 Minecraft 26.2 blocks. It updates itself when Mojang ships a new version, and 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 memory, filesystem, S3, Redis, and Postgres backends:
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"]
The gallery
Ten more builds, each a short recipe that leans on the same handful of primitives: a rainbow DNA helix and a trefoil knot, a Menger sponge, a fractal tree, a gyroid, a Mandelbulb, a voxelized fox, a supershape, animated wave interference, and type set in blocks.
Every one is a few dozen lines. Open the gallery for all ten with their code.
One API, seven languages
Every binding is generated from one annotated-Rust source of truth
(src/bridge/) via
Diplomat, then 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.