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
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.
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
=
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.
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.
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
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: 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 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)
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.
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.