dh.block
Extension: .dh-block
Type ID: dh.block
A block definition says how one block of a voxel container is drawn: the cuboid-element models it can show, and a rule table that picks among them from the block’s state. A platform pallet holds one per block ID. blocks/oak_stairs.dh-block in the pallet a container maps the minecraft namespace to is minecraft:oak_stairs.
The rules stay rules. They are never expanded into one entry per state, so a fence’s 32 states are still five rules. One schema covers Minecraft’s variants and multipart blockstates, and leaves room for the models of other block platforms.
Block definitions are usually generated, not written by hand: dh import minecraft writes one per Minecraft block. Most of them are a few lines over a base that holds a shape many blocks share.
Properties
Section titled “Properties”| Property | Type | Required | Description |
|---|---|---|---|
$type | "dh.block" | no | Type identifier |
name | string | no | Display name |
description | string | no | Description |
inherits | string | no | A base block this one is built on. It states only what differs |
units | number | no | Model units per block edge. Coordinates and UVs are in these units. Default 16, Minecraft’s |
match | string | no | "first" (default): the first rule whose condition holds applies. "all": every rule whose condition holds applies |
rules | array | yes, or inherited | The rule table, in order |
models | { string: model } | yes, or inherited | The models the rules name, by a key local to this block |
materials | { string: string } | no | Material slots every model reads through |
tints | { int: tint } | no | What colors each tint index. An index with no entry draws untinted |
special | bool | no | The source platform draws this block in code. Its models are placeholders |
fluid | fluid | no | The block is a fluid, drawn from its level rather than its models |
lightEmission | array | no | The light the block gives off, by state. Absent means none |
lightOpacity | int | no | How much the block cuts light passing through it, 0 to 15. Absent: derived from its shape |
lightShape | bool | no | Whether the sides its model covers stop light crossing them. Default false |
The field set is closed at every depth: a member the schema does not define is a compile error naming it, in a rule, a model, an element, a face or a tint as much as at the top.
Bases and inherits
Section titled “Bases and inherits”A block may inherits another, the way objects and materials do: the base is read first, a field the block states wins, and a field it leaves out reads through. A base holds a shape and its state rules, with named material slots left open. Each block built on it names its materials, and anything else that differs.
| Field | How it folds |
|---|---|
name, description, units, special, lightOpacity, lightShape | The block’s own wins |
fluid, lightEmission | Whole |
rules | Whole. A block that states any rule states the table, and its match with it, since a rule table’s order is its meaning |
models | Per key. A model folds per field: its materials per slot, its elements whole when the block states any, its collision whole when the block states it (an empty list included) |
materials | Per slot |
tints | Per index |
BlockMerge in Core is the one implementation. The compiler validates each file’s own fields, and checks the folded block’s rules against its models and each face against its slots. A chain that comes back to itself is an error. The engine folds the chain again where it reads the block.
{ "name": "Stairs", "rules": [ { "when": { "facing": "east", "half": "bottom", "shape": "straight" }, "apply": [ { "model": "stairs" } ] }, { "when": { "facing": "east", "half": "top", "shape": "inner_left" }, "apply": [ { "model": "inner_stairs", "x": 180, "uvlock": true } ] } // ... 38 more ], "materials": { "bottom": "", "side": "", "top": "" }, "models": { "stairs": { "elements": [ { "from": [0, 0, 0], "to": [16, 8, 16], "faces": { "down": { "uv": [0, 0, 16, 16], "material": "#bottom", "cullFace": "down" }, "up": { "uv": [0, 0, 16, 16], "material": "#top" }, "north": { "uv": [0, 8, 16, 16], "material": "#side", "cullFace": "north" } // south, west, east } }, { "from": [8, 8, 0], "to": [16, 16, 16], "faces": { /* ... */ } } ] }, "inner_stairs": { /* ... */ }, "outer_stairs": { /* ... */ } }}{ "name": "Oak Stairs", "inherits": "/blocks/base/stairs.dh-block", "materials": { "bottom": "/materials/block/oak_planks.dh-mat", "side": "/materials/block/oak_planks.dh-mat", "top": "/materials/block/oak_planks.dh-mat" }}A platform pallet keeps its bases under blocks/base/. No block ID has a / in it, so a base is never mistaken for a block a container names.
Each rule is a condition and the models it applies:
| Field | Type | Description |
|---|---|---|
when | condition | The condition on the block’s state. Absent means always |
apply | array | One or more choices. One is drawn, picked by weight |
A choice places one model:
| Field | Type | Description |
|---|---|---|
model | string | Required. A key in models |
x, y, z | number | The whole model’s rotation in degrees: X first, then Y, then Z. Minecraft uses 90-degree steps |
uvlock | bool | Textures stay aligned to the world instead of turning with the model |
weight | int | The choice’s relative chance among its rule’s. Default 1 |
Conditions
Section titled “Conditions”A condition is one object. Every property key must match. The reserved keys OR and AND hold lists of nested conditions. A value is one or more alternatives joined by |, and a leading ! negates the whole list. A property the state does not have matches only a negated test.
{ "facing": "east|west", "waterlogged": "!true" }{ "OR": [ { "north": "side|up", "east": "side|up" }, { "AND": [ { "west": "up" }, { "south": "!none" } ] } ] }The reserved keys are uppercase because block property names are lowercase on every platform DH reads, so neither can be mistaken for a property. Minecraft’s blockstates use the same keys.
BlockDefinition.Applied(state) in Core is the one implementation of this: it returns the rules that apply to a state under the block’s match.
Models
Section titled “Models”A model is flat: no parent, and its elements written out.
| Field | Type | Description |
|---|---|---|
materials | { string: string } | Material slots this model binds itself, ahead of the block’s. A face names its slot as #slot |
elements | array | The model’s boxes |
ambientOcclusion | bool | Whether ambient occlusion darkens the model. Default true |
collision | array | The boxes a body collides with where the model is placed. Absent: the elements’ boxes. Empty: not solid |
placeholder | bool | The importer made this model up. See special blocks |
Material slots
Section titled “Material slots”A face’s #slot is looked up in its model’s materials first, then in the block’s. A slot bound to "" is declared and open: a base leaves it for the blocks built on it, and a face on a slot still open when the block is drawn shows the unresolved checker. A face naming a slot neither declares is a compile error. The importer names each slot after the texture variable that finally binds the sprite, so a cube_all block’s six faces all read #all.
Material references follow the usual rule: a bare path is read from the block’s own folder, a leading / reads from the pallet root, and <palletId>:<path> names another pallet, which must be a declared dependency.
Elements
Section titled “Elements”| Field | Type | Description |
|---|---|---|
from, to | [x, y, z] | Required. The box’s corners in model units. The block spans 0 to units on each axis. A flat element has one axis equal |
rotation | object | The element’s own rotation: origin ([x, y, z]), x, y, z in degrees, and rescale |
shade | bool | Whether directional shading darkens the faces. Default true. The engine lights blocks with the scene’s own light, so false lights the faces as if they faced up: a cross plant’s two planes read alike |
lightEmission | int | The least light level the element’s faces are drawn at, 0 to 15, over the block’s own light. Default 0. Minecraft’s open eyeblossom glows this way |
faces | { direction: face } | Faces by down, up, north, south, west, east. A missing face is not drawn |
Axes: +Y is up, north is -Z and east is +X, as in Minecraft.
A choice’s x, y and z turn the whole model about the block’s center, clockwise looking down each axis toward the center, as Minecraft does: "y": 90 turns east to south. The cull face turns with it. Under uvlock each corner reads the texture point under it on the side the face now looks toward, so planks keep running the same way on a turned stair.
An element’s rotation turns about origin, X first, then Y, then Z, each about the fixed block axes. Minecraft’s older single-axis form (axis and angle) becomes the one matching field. Minecraft from 25w46a writes all three. rescale stretches the faces across the rotated axes by 1 / cos(angle), so a 45-degree cross still spans the block.
| Field | Type | Description |
|---|---|---|
material | string | Required. The model’s slot, written #slot |
uv | [u1, v1, u2, v2] | The texture area in model units. Absent means derived from the element’s position, as Minecraft does |
cullFace | string | The direction whose neighbor hides this face when it is solid |
rotation | int | Texture rotation: 0, 90, 180 or 270. Default 0 |
tint | int | The tint index, looked up in tints. Absent means untinted |
A derived uv follows Minecraft: down is [x1, 16 − z2, x2, 16 − z1], up is [x1, z1, x2, z2], north is [16 − x2, 16 − y2, 16 − x1, 16 − y1], south is [x1, 16 − y2, x2, 16 − y1], west is [z1, 16 − y2, z2, 16 − y1] and east is [16 − z2, 16 − y2, 16 − z1, 16 − y1], with 16 read as units.
A tint is exactly one of:
| Field | Description |
|---|---|
colormap | A colormap texture in the pallet, sampled by the biome’s temperature and downfall |
color | A fixed #RRGGBB |
computed | A color the platform computes with no data to carry: water (the biome’s water color), redstonePower, stemAge |
"tints": { "0": { "colormap": "/textures/colormap/grass.png" } }Collision
Section titled “Collision”A model’s collision is the boxes a body collides with, in model units, as from and to corners. The boxes turn with the model: a choice’s x, y and z turn them exactly as they turn its elements.
collision | What collides |
|---|---|
| Absent | The model’s elements, each as its box (a turned element as the box around it; a flat one not at all) |
[] | Nothing: the model is not solid |
| Boxes | Those boxes, which may reach past the block: a fence post stands a block and a half |
A model with neither elements nor collision (a barrier, which draws nothing) collides as a whole block. It folds through inherits per model, so a base states it once and every block built on it collides alike:
"models": { "fence_post": { "elements": [ { "from": [6, 0, 6], "to": [10, 16, 10], "faces": { /* ... */ } } ], "collision": [ { "from": [6, 0, 6], "to": [10, 24, 10] } ] }, "fence_side": { "elements": [ /* the two bars */ ], "collision": [ { "from": [6, 0, 0], "to": [10, 24, 6] } ] }}The engine reads it where a voxel volume is made solid. Whether a block is solid at all is still the block table’s call; a block’s collision can only narrow it, as an empty list does.
Special blocks
Section titled “Special blocks”Some blocks are drawn by their platform’s code rather than from a model: chests, banners, skulls, shulker boxes, liquids, the end portal. Their model has no elements. For most of them the Minecraft importer writes real models from DH’s own recipes: a chest is a base block with a body, a lid and a latch, and each chest a thin block over it naming its texture. The rest the importer marks "special": true, giving each empty model a placeholder: one full cube of its particle material, flagged "placeholder": true.
Water and lava are fluids, not special: what their code reads is data.
Blocks that draw nothing at all (air, barriers, light blocks, structure voids, and a bubble column, which is drawn as the water it holds) keep their empty models and are not special, so they never become a cube.
Fluids
Section titled “Fluids”A fluid makes the block a fluid. A renderer that draws fluids shapes it from its level and its neighbors’ (the engine’s way is on the dh.voxel page) and ignores its models; a reader that does not draw fluids draws the models, which for Minecraft’s are a stand-in: the still texture on top and bottom and the flowing texture on the sides, topped at a source block’s height (8/9 of a block).
| Field | Type | Required | Description |
|---|---|---|---|
still | string | yes | The block’s material slot, #slot, a level surface wears |
flow | string | yes | The slot a sloped surface and the sides wear, turned along the flow |
tint | int | no | A tint index both are drawn in. Absent means untinted |
level | string | no | The state property holding the level, on Minecraft’s scale: 0 is a source, 1 to 7 flow farther from it, 8 and up fall. Default level |
logged | string | no | A boolean property that puts a source of this fluid into any block of the namespace whose state sets it true |
holders | [string] | no | Blocks, by ID in this pallet, that always stand in a source of it |
medium | string | no | The slot whose material’s water block says what the fluid is to a body inside it: how a swimmer moves, what floats and the fog an eye inside sees. Absent means a body passes through it as through air |
The slots are the block’s own materials, which its stand-in model reads through too. The compiler checks that every one the fluid names is declared there. The medium is a pointer like a material’s preset: the block names a material and states no numbers of its own, so retuning the material retunes every fluid that names it. How the engine swims in it is on the dh.voxel page.
{ "name": "Water", "materials": { "still": "/materials/block/water_still.dh-mat", "flow": "/materials/block/water_flow.dh-mat", "medium": "/materials/fluid/water.dh-mat" }, "fluid": { "still": "#still", "flow": "#flow", "medium": "#medium", "tint": 0, "level": "level", "logged": "waterlogged", "holders": ["bubble_column", "kelp", "kelp_plant", "seagrass", "tall_seagrass"] }, "tints": { "0": { "computed": "water" } }, "rules": [ { "apply": [ { "model": "fluid" } ] } ], "models": { "fluid": { /* the stand-in */ } }}Lava is the same with no tint, no logged and no holders, its own medium, and gives off full light, so it glows. Which IDs of a platform are fluids is the engine’s voxel platform table (water and lava for Minecraft), read before a volume’s palette so a waterlogged block holds water even where no water block is in it.
lightEmission says how much light the block gives off, by state: a list read in order, the first entry whose when holds giving the level, and a state no entry matches giving none. It folds whole, and it is a fact of the block, not of its textures: a lit furnace and a cold one wear the same side texture.
| Field | Type | Description |
|---|---|---|
when | condition | The condition on the block’s state. Absent means always |
level | int | The light level, 0 to 15, Minecraft’s scale |
{ "inherits": "/blocks/base/orientable.dh-block", "lightEmission": [ { "when": { "lit": "true" }, "level": 13 } ]}"lightEmission": [ { "when": { "waterlogged": "false" }, "level": 0 }, { "when": { "pickles": "1" }, "level": 6 }, { "when": { "pickles": "2" }, "level": 9 } // ...]A renderer draws the block’s faces at least that bright, whatever lights them: the engine makes them glow by the level. An element’s own lightEmission can raise its faces further. BlockDefinition.LightFor(state) in Core is the one evaluator. The level is also what the engine’s light field spreads from the block.
How a block cuts light
Section titled “How a block cuts light”lightOpacity is how much a block cuts light passing through it, 0 to 15, Minecraft’s light block. A step into a cell always loses at least 1, so a lightOpacity of 1 differs from 0 only for sky light, which falls straight down through a 0 without loss: leaves, ice, slime, honey, cobwebs and spawners are 1, glass is 0, and stone is 15. Left out, it is derived from the block’s shape: 15 when its model covers all six sides opaquely, 0 otherwise, and at least 1 for a block standing in a fluid (water, a waterlogged stair, kelp). A block with no platform pallet behind it reads its table row the same way: an opaque cube is 15, a canopy block 1, anything see-through 0.
lightShape makes the sides the block’s model covers opaquely stop light crossing them, both ways, whatever its opacity: a bottom slab passes light up and sideways but not down, a dirt path stops light from below. It is a flag rather than a rule from the shape alone because Minecraft applies it only to some blocks: a door’s full side and a closed trapdoor pass light.
"lightOpacity": 1"lightShape": trueBoth are facts of the block, written on each block rather than on a base, and fold per field. The compiler rejects a lightOpacity outside 0 to 15.
Example: a fence
Section titled “Example: a fence”A multipart blockstate is an all-match table: the post always, and a side for each connection. The base holds it with one open slot, and each wooden fence binds it.
{ "name": "Fence Post", "match": "all", "rules": [ { "apply": [ { "model": "fence_post" } ] }, { "when": { "north": "true" }, "apply": [ { "model": "fence_side", "uvlock": true } ] }, { "when": { "east": "true" }, "apply": [ { "model": "fence_side", "y": 90, "uvlock": true } ] }, { "when": { "south": "true" }, "apply": [ { "model": "fence_side", "y": 180, "uvlock": true } ] }, { "when": { "west": "true" }, "apply": [ { "model": "fence_side", "y": 270, "uvlock": true } ] } ], "materials": { "texture": "" }, "models": { "fence_post": { "elements": [ { "from": [6, 0, 6], "to": [10, 16, 10], "faces": { /* ... */ } } ] }, "fence_side": { /* ... */ } }}{ "name": "Oak Fence", "inherits": "/blocks/base/fence_post.dh-block", "materials": { "texture": "/materials/block/oak_planks.dh-mat" } }How the importer finds bases
Section titled “How the importer finds bases”Nothing is a hand list of shapes. A model that only binds textures (oak_stairs naming its planks) fills in its parent, the template (stairs); a model with elements of its own is its own template. Two blocks share a base when their rule tables are the same once every model is replaced by the template it fills in, and when each binds every open slot of the template to one material. The base is named for the template of the model its block is named for, less Minecraft’s template_ prefix, and numbered when two different tables use the same one (cube_all, cube_all_2). A block whose table nothing shares stays whole.
On the 26.2 client jar that is 90 bases (13 of them recipes) with 1065 blocks built on them and 133 blocks whole, and 1.7 MB of block definitions where writing every block whole takes 6.2 MB.
Other platforms
Section titled “Other platforms”The schema was checked against the public model formats of the next block platforms, so it does not stop at Minecraft:
- Vintage Story shapes are boxes with
from/to, per-face UVs and textures, and a rotation about an origin on all three axes, whichrotationalready carries. Its shapes also nest elements as children, and its block types pick shapes by wildcard variant codes. Neither is here yet; both fit as additions to a closed field set. - Hytale models are node trees of boxes and quads with a quaternion orientation and per-face texture layouts. Its block size in model units is unverified;
unitsis the field it would set.
Platform support
Section titled “Platform support”| Rules and models | Bases (inherits) | Tints | collision | Special blocks | fluid | lightEmission | lightOpacity, lightShape | |
|---|---|---|---|---|---|---|---|---|
Compiler (dh build) | ✅ Validated: closed fields, models named, slots and materials found | ✅ Folded and validated | ✅ Validated | ✅ Validated: three-value corners, each box with volume | ✅ | ✅ Validated: slots declared by the block | ✅ Validated: closed fields, levels 0 to 15 | ✅ Validated: opacity 0 to 15 |
dh import minecraft | ✅ Writes them | ✅ Derived from the parent chains | ✅ From a hand-kept table | ✅ Fences, walls and gates from a data file; [] on every block the block table calls passable | ✅ Recipes for most; placeholders for the deferred | ✅ Water and lava, with stand-ins and DH media | ✅ From a data file, by state | ✅ From a data file, only where the shape says otherwise |
| Engine, rendering | ✅ Voxel volumes | ✅ | ✅ Colormaps by biome, fixed and computed | — | ✅ Recipes; 🟡 placeholders for the rest | ✅ Levels, flow, waterlogging | ✅ Glows; 🟡 spreads through the light field, which shading does not read yet | 🟡 Cut the light field |
| Engine, collision | ✅ Block shapes | ✅ | — | ✅ | ✅ | ✅ Passable; ✅ swum in through its medium | — | — |
| Studio | 🟡 Listed with its icon, no preview | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
| Unity, BONELAB, Garry’s Mod and the other game platforms | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ |