nucleation 0.3.18

A high-performance Minecraft schematic parser and utility library
Documentation

Nucleation

A Minecraft schematic engine in Rust: load, build, simulate, mesh, and render schematics from seven languages.

Crates.io npm PyPI CI

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

cargo add nucleation        # Rust
npm  install nucleation     # JavaScript / TypeScript (Node ≥ 18 or a bundler)
pip  install nucleation     # Python (CPython 3.12+)

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:

from nucleation import Schematic

cube = Schematic.load_from_file("simple_cube.litematic")   # any format, auto-detected
cube.dimensions()                                          # (3, 3, 3)

cube.set_block(1, 3, 1, "minecraft:glowstone")             # y=3: the region grows to fit
cube.get_block_name(1, 3, 1)                               # "minecraft:glowstone"

cube.save_to_file("cube.schem")                            # format from the extension

The same loop in JavaScript. The WASM build has no filesystem, so it is bytes in, bytes out:

import { Schematic } from "nucleation";
import { readFileSync, writeFileSync } from "node:fs";

const cube = Schematic.fromData(readFileSync("simple_cube.litematic"));
cube.setBlock(1, 3, 1, "minecraft:glowstone");
writeFileSync("simple_cube.schem", Buffer.from(cube.toSchematicB64(), "base64"));

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:

brush = Brush.curve_gradient(stops, rainbow_colors, InterpolationSpace.Oklab)
brush.set_palette(Palette.wool())
BuildingTool.fill(s, Shape.torus(0, 0, 0, 16, 6, 0, 1, 0), brush)

The shaded brush lights a base color by surface normal, giving 3D-lit forms out of flat blocks:

brush = Brush.shaded(224, 130, 84,  -1.0, 0.7, -0.3)   # base color, light direction
brush.set_palette(Palette.terracotta())
BuildingTool.fill(s, Shape.sphere(0, 0, 0, 16), brush)

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

Palette.grayscale().ramp_ids_json(255, 255, 255,  0, 0, 0,  24)
# 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.

Palette.concrete().gradient_ids_json(200, 30, 70,  245, 205, 55,  8)
# crimson -> gold: red, red, pink, orange, orange, yellow, yellow, yellow
Palette.terracotta().ramp_ids_json(220, 40, 44,  40, 80, 220,  6)   # 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:

b = PaletteBuilder.create()
b.chroma_below(0.022)               # near-neutral only
b.lightness_between(0.35, 0.75)     # mid-grays
b.full_blocks_only()
mid_grays = b.build()               # 40+ blocks, picked by math

Blocks.by_color_json(120, 200, 60, 0.10)
# 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:

blob = Shape.sdf('{"type": "smoothUnion", "k": 6.0, "a": {"type": "sphere", "radius": 10}, '
                 '"b": {"type": "translate", "offset": [11, 3, 0], "child": {"type": "sphere", "radius": 7}}}')
BuildingTool.fill(s, blob, shaded_brush)      # 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:

BuildingTool.fill_replacing(temple, decay_sphere, weathered_brush,
                            '["minecraft:stone_bricks"]')

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.

from nucleation import Sdf

island = '''{"type": "displace", "amplitude": 3, "frequency": 0.1, "seed": 7,
             "child": {"type": "ellipsoid", "radii": [14, 8, 14]}}'''
rules = '''{"fill": [
  {"when": {"depthBelowSurface": {"min": 0, "max": 0}}, "block": "minecraft:grass_block"},
  {"when": {"depthBelowSurface": {"min": 1, "max": 3}}, "block": "minecraft:dirt"},
  {"block": "minecraft:stone"}]}'''

terrain = Sdf.schematic_from_sdf_auto(island, rules)
# → 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.

field = DistanceField.from_schematic(terrain)     # depth + normal of any build
ny = field.slope(x, top, z)                        # 1 flat, ->0 vertical
surf = "grass_block" if ny > 0.86 else "coarse_dirt" if ny > 0.72 else "stone"

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:

teapot = Voxelizer.shape_from_obj(teapot_obj, 56.0, 0.75)   # shell closes its thin ceramic walls
spot = Brush.spotlight(-38, 55, -52,  0.48, -0.54, 0.66,  46.0,  245, 242, 235)
spot.set_palette(gray_ramp)
BuildingTool.fill(s, teapot, spot)

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:

duck = Voxelizer.schematic_from_glb_textured(duck_glb, 44.0, 0.7, Palette.solid(), "duck")
# 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.
buildings = [{"polygon": [[0,0],[12,0],[12,8],[0,8]], "height": 40, "block": "minecraft:white_concrete"}, ...]
city = Geo.extrude_footprints(json.dumps(buildings), "minecraft:gray_concrete", "fidi")

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

city.save_world("fidi-world/", "")     # 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:

palette = flat_art_palette().dithered()          # PaletteBuilder + map-art excludes
r, g, b = boost(*pixel, sat=1.35)                # chroma exaggeration pre-match
s.set_block(x, 0, y, palette.closest_block_dithered(r, g, b, x, 0, y))

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.
torus = Shape.sdf('''{"type": "warp", "amplitude": 3, "frequency": 0.045, "seed": 11, "child":
  {"type": "smoothSubtract", "k": 1.5,
   "a": {"type": "torus", "majorRadius": 26, "minorRadius": 9},
   "b": {"type": "repeat", "spacing": [11,11,11], "child": {"type": "sphere", "radius": 3.5}}}}''')
BuildingTool.fill(s, torus, Brush.solid("minecraft:stone"))

# Wrap the painting on: UV from the torus geometry, color through the palette.
pal = flat_art_palette().dithered()
for x, y, z in solid_voxels(s):
    u, v = torus_uv(x, y, z)               # angle around the ring, angle around the tube
    s.set_block(x, y, z, pal.closest_block_dithered(*starry_night(u, v), x, y, z))

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

field = '{"type": "cells", "frequency": 0.11, "seed": 7, "mode": "value"}'

# Texture: color every voxel by which Voronoi cell it falls in.
brush = Brush.field(field, stops, colors, 0.0, 1.0, InterpolationSpace.Oklab)
BuildingTool.fill(s, Shape.sphere(0, 0, 0, 28), brush)

# Geometry: raise each column to its cell's value.
for x, z in grid:
    h = Sdf.eval(field, x, 0, z)                    # 0..1 per cell
    s.fill_cuboid(x, 0, z, x, round(1 + h * 20), z, block_for(h))

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:

f1    = '{"type": "cells", "frequency": 0.09, "seed": 4, "mode": "f1"}'
crack = '{"type": "cells", "frequency": 0.09, "seed": 4, "mode": "f2MinusF1"}'
for x, y, z in inside_sphere(R):
    depth = R - length(x, y, z)                     # distance along the surface normal
    if depth > crust:                               # glowing core
        block = glow.snap(depth)                    # glass shell, then emitters deeper
    elif Sdf.eval(crack, x, y, z) < crack_w:        # recessed buffer groove
        block = None if depth < inset else glow.snap(depth)
    else:                                            # cell crust
        block = cells.snap(shade(Sdf.eval(f1, x, y, z)))   # 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.

import json
# Walk a build in 16×16×16 chunks, center-outward from a point:
for chunk in json.loads(s.get_chunks_with_strategy_json(16, 16, 16, "center_outward", 0, 0, 0)):
    handle(chunk["chunk_x"], chunk["chunk_z"], chunk["blocks"])

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.

from nucleation import WorldStream, WorldSink

stream = WorldStream.open_dir("world/")     # or .from_zip(bytes), or *_bounded(...)
sink   = WorldSink.create("world-out/", "")
while True:
    try:
        chunk = stream.next()               # a WorldChunkView
    except Exception:
        break                               # end of stream is signalled by raising
    # inspect or edit here: chunk.set_block(...), chunk.to_schematic(), ...
    sink.write_chunk(chunk)
sink.finish()

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:

from nucleation import Schematic, Shape, Brush, BuildingTool, WorldChunkView, WorldSink

sdf  = Shape.sdf(island_json)
sink = WorldSink.create("world/", "")
for cx in range(-8, 8):
    for cz in range(-8, 8):
        chunk = Schematic.create("c")
        box = Shape.cuboid(cx*16, -16, cz*16, cx*16 + 15, 48, cz*16 + 15)
        BuildingTool.fill(chunk, sdf.intersection_with(box), Brush.solid("minecraft:stone"))
        sink.write_chunk(WorldChunkView.from_schematic(chunk, cx, cz))
sink.finish()

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: WorldStreamto_schematic → transform with any tool → from_schematicWorldSink. 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:
s.set_block_in_region("keep",  0, 0, 0, "minecraft:quartz_block")
s.set_block_in_region("gate", 10, 0, 0, "minecraft:blackstone")
s.region_names_json()                 # ["Main", "keep", "gate"]
s.rotate_region_y("gate", 90)         # turn one region, leave the rest

# Transform the whole build with rotate_x/y/z (degrees) and flip_x/y/z:
s.rotate_y(90)                        # a bar's +x tip at (9,0,0) lands at (0,0,0)

# Stamp a sub-volume of one schematic into another:
dst.copy_region(src, 0, 0, 0,  9, 0, 0,   100, 0, 0,  "[]")
#               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:
s.set_block_entity(0, 0, 0, "minecraft:chest",
    '{Items:[{Slot:0b,id:"minecraft:diamond",Count:3b},'
            '{Slot:1b,id:"minecraft:emerald",Count:5b}]}')
s.get_block_entity_snbt(0, 0, 0)
# → {Items:[{...diamond, Slot:0B, Count:3B}, {...emerald, Slot:1B, Count:5B}]}  (SNBT)

# Entities parse from SNBT too:
s.add_entity_from_snbt('{id:"minecraft:armor_stand",Pos:[0.5d,1.0d,0.5d],Rotation:[0f,0f]}')
s.entity_count()                      # 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 { Schematic, MchprsWorld } from "nucleation";

const world = MchprsWorld.create(circuit);   // lever → wire → lamp
world.onUseBlock(0, 1, 0);                   // flip the lever
world.tick(2);
world.flush();
world.isLit(2, 1, 0);                        // → 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.

cb = CircuitBuilder.create(bus)                       # bus = the 8 lever->wire->lamp lines
cb.with_input_auto("a", IoType.unsigned_int(8), levers)   # flat [x,y,z,...] positions
cb.with_output_auto("y", IoType.unsigned_int(8), lamps)
ex = cb.build()

res = json.loads(ex.execute('{"a": 178}', ExecutionMode.until_stable(2, 100)))
res["outputs"]["y"]["value"]                           # -> 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:

mesh = MeshResult.create(schem, ResourcePack.from_bytes(pack_zip), MeshConfig.create())
glb = base64.b64decode(mesh.glb_data_b64())     # magic b'glTF'

cfg = RenderConfig.create(1200, 760)
cfg.set_isometric()
cfg.set_sphere_fit(True)                        # rotation-stable framing
Renderer.render_to_file(schem, pack_zip, cfg, "island.png")

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:

diff = Diff.compute(before, after, "exact")     # distance 3; summary JSON with regions
Fingerprint.is_duplicate(before, after, "exact")   # 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:

Autostack.detect_structures(wall)        # {"mode": "1d", "vectors": [[4,0,0]], "coverage": 1.0}
longer = Autostack.resize_1d(wall, 4, 0, 0, 6)   # 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:

plaza.save_world(world_dir, "")
back = Schematic.from_world_directory_bounded(world_dir, 0, 0, 0, 39, 4, 39)

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.

report = json.loads(build.convert_to_data_version(1343, build.canonical_data_version()))
lost = [e["path"] for e in report if e["severity"] == "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:

json.loads(Blocks.get_json("minecraft:oak_stairs"))
# {"kind": "minecraft:stair", "base_block": "minecraft:oak_planks",
#  "tags": ["minecraft:mineable/axe", ...], "full_cube": false, ...}

json.loads(Blocks.variants_of_json("minecraft:oak_planks"))
# [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):
StoreIo.save(castle, "file:///data/castle.schem", "")
castle = StoreIo.open("file:///data/castle.schem")

# Or raw key-value over any backend:
store = Store.open("mem://")           # also file:// · s3:// · redis:// · postgres://
store.put("meta/version", b"3")
store.get_b64("meta/version")          # "Mw=="
store.list("meta/")                    # ["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"
<?php
require "php/index.php";
use Stencil\Lib;
use Stencil\Schematic;

Lib::init("/path/to/libnucleation.so");
$schematic = Schematic::create("demo");
$schematic->setBlock(1, 2, 3, "minecraft:stone");
echo $schematic->getBlockName(1, 2, 3); // "minecraft:stone"
#include "Schematic.h"

int main(void) {
    DiplomatStringView name = {"demo", 4};
    Schematic *s = Schematic_create(name);
    DiplomatStringView stone = {"minecraft:stone", 15};
    Schematic_set_block(s, 1, 2, 3, stone);
    Schematic_destroy(s);
    return 0;
}

Documentation & development

Also in the box: layer-art templates (schematics from ASCII art).

cargo test                          # core suite (784 tests)
./tools/gen-bindings.sh             # regenerate bindings (diplomat-tool fork)
./examples/bridge_smoke/js/run.sh   # end-to-end smoke per language

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.