Skip to content

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.

PropertyTypeRequiredDescription
$type"dh.block"noType identifier
namestringnoDisplay name
descriptionstringnoDescription
inheritsstringnoA base block this one is built on. It states only what differs
unitsnumbernoModel units per block edge. Coordinates and UVs are in these units. Default 16, Minecraft’s
matchstringno"first" (default): the first rule whose condition holds applies. "all": every rule whose condition holds applies
rulesarrayyes, or inheritedThe rule table, in order
models{ string: model }yes, or inheritedThe models the rules name, by a key local to this block
materials{ string: string }noMaterial slots every model reads through
tints{ int: tint }noWhat colors each tint index. An index with no entry draws untinted
specialboolnoThe source platform draws this block in code. Its models are placeholders
fluidfluidnoThe block is a fluid, drawn from its level rather than its models
lightEmissionarraynoThe light the block gives off, by state. Absent means none
lightOpacityintnoHow much the block cuts light passing through it, 0 to 15. Absent: derived from its shape
lightShapeboolnoWhether 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.

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.

FieldHow it folds
name, description, units, special, lightOpacity, lightShapeThe block’s own wins
fluid, lightEmissionWhole
rulesWhole. A block that states any rule states the table, and its match with it, since a rule table’s order is its meaning
modelsPer 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)
materialsPer slot
tintsPer 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.

blocks/base/stairs.dh-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": { /* ... */ }
}
}
blocks/oak_stairs.dh-block
{
"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:

FieldTypeDescription
whenconditionThe condition on the block’s state. Absent means always
applyarrayOne or more choices. One is drawn, picked by weight

A choice places one model:

FieldTypeDescription
modelstringRequired. A key in models
x, y, znumberThe whole model’s rotation in degrees: X first, then Y, then Z. Minecraft uses 90-degree steps
uvlockboolTextures stay aligned to the world instead of turning with the model
weightintThe choice’s relative chance among its rule’s. Default 1

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.

A model is flat: no parent, and its elements written out.

FieldTypeDescription
materials{ string: string }Material slots this model binds itself, ahead of the block’s. A face names its slot as #slot
elementsarrayThe model’s boxes
ambientOcclusionboolWhether ambient occlusion darkens the model. Default true
collisionarrayThe boxes a body collides with where the model is placed. Absent: the elements’ boxes. Empty: not solid
placeholderboolThe importer made this model up. See special blocks

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.

FieldTypeDescription
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
rotationobjectThe element’s own rotation: origin ([x, y, z]), x, y, z in degrees, and rescale
shadeboolWhether 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
lightEmissionintThe 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.

FieldTypeDescription
materialstringRequired. 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
cullFacestringThe direction whose neighbor hides this face when it is solid
rotationintTexture rotation: 0, 90, 180 or 270. Default 0
tintintThe 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:

FieldDescription
colormapA colormap texture in the pallet, sampled by the biome’s temperature and downfall
colorA fixed #RRGGBB
computedA color the platform computes with no data to carry: water (the biome’s water color), redstonePower, stemAge
"tints": { "0": { "colormap": "/textures/colormap/grass.png" } }

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.

collisionWhat collides
AbsentThe 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
BoxesThose 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:

blocks/base/fence_post.dh-block
"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.

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.

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

FieldTypeRequiredDescription
stillstringyesThe block’s material slot, #slot, a level surface wears
flowstringyesThe slot a sloped surface and the sides wear, turned along the flow
tintintnoA tint index both are drawn in. Absent means untinted
levelstringnoThe 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
loggedstringnoA boolean property that puts a source of this fluid into any block of the namespace whose state sets it true
holders[string]noBlocks, by ID in this pallet, that always stand in a source of it
mediumstringnoThe 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.

blocks/water.dh-block
{
"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.

FieldTypeDescription
whenconditionThe condition on the block’s state. Absent means always
levelintThe light level, 0 to 15, Minecraft’s scale
blocks/furnace.dh-block
{
"inherits": "/blocks/base/orientable.dh-block",
"lightEmission": [ { "when": { "lit": "true" }, "level": 13 } ]
}
blocks/sea_pickle.dh-block (excerpt)
"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.

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.

blocks/oak_leaves.dh-block (excerpt)
"lightOpacity": 1
blocks/oak_slab.dh-block (excerpt)
"lightShape": true

Both 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.

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.

blocks/base/fence_post.dh-block
{
"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": { /* ... */ }
}
}
blocks/oak_fence.dh-block
{ "name": "Oak Fence", "inherits": "/blocks/base/fence_post.dh-block", "materials": { "texture": "/materials/block/oak_planks.dh-mat" } }

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.

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, which rotation already 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; units is the field it would set.
Rules and modelsBases (inherits)TintscollisionSpecial blocksfluidlightEmissionlightOpacity, 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❔❔❔❔❔❔❔❔