Skip to content

Pallets & Assets

DigitalHeaven organizes everything into pallets: packages of related assets like avatars, materials, textures, and models.

One rule covers every identifier DigitalHeaven mints: dots separate namespaces, and every segment is camelCase. That applies uniformly — patch $types (dh.setMaterial), entity types (spawnPoint), input/output names (onPressed, open), and preference keys (world.jumpHeight, client.camera.roll.dampingRatio) all follow it. JSON property keys are camelCase too (baseMesh, eyeRotationLimits).

Acronyms are treated as ordinary words, never as all-caps runs: glbPath, setHdrSky, enableCsg — not GLBPath or enableCSG.

Hyphens still show up, but only where they’re structural rather than identifier style: file extensions (.dh-obj, .dh-mat), pallet IDs (io.mltn.avatars.mltn-mayu), and file paths. None of those are camelCase identifiers, so the hyphen stays.

Definition files (.dh-obj, .dh-mat, pallet.dh, etc.) are JSONC in practice: comments and trailing commas are accepted.

A pallet is a folder with a pallet.dh manifest and whatever assets it needs.

Pallets use reverse-DNS identifiers:

  • io.mltn.avatars.mltn-mayu
  • tech.azuki.avatars.mayu
  • com.example.my-avatar

This prevents naming conflicts and shows ownership. The ID also determines the folder path: io.mltn.avatars.mltn-mayu lives at Source/io.mltn/avatars/mltn-mayu/.

Every pallet needs a manifest:

pallet.dh
{
"id": "io.mltn.avatars.mltn-mayu",
"name": "mltn Mayu",
"version": "0.0.1",
"author": "mltn",
"dependencies": {
"tech.azuki.avatars.mayu": "^3.0.0"
}
}

The manifest also accepts an optional look — a built-in slug or a .dh-look reference — which becomes the pallet’s house art direction. Every map in the pallet picks it up, and it is the weakest authored layer, so a map that names its own look or authors its own render fields still wins:

pallet.dh
{
"id": "io.mltn.maps.nightcity",
"name": "Night City",
"version": "0.0.1",
"look": "looks/night.dh-look"
}

An asset is any file inside a pallet: definition files (.dh-avatar, .dh-mat, etc.) or binary resources (.png, .glb, .wav). The exception is sidecar files (e.g. sprite.png.dh-tex-settings), which carry import settings for a neighboring file and are consumed during compilation rather than included as assets. See dh.texture — Sidecar Files for details.

Within the same pallet, just use the relative path:

skin.dh-mat
{
"texture": "textures/body-diffuse.png"
}

Across pallets, prefix with the pallet ID and a : separator:

avatar.dh-avatar
{
"baseMesh": "tech.azuki.avatars.mayu:models/body.glb"
}

This is called a barcode: pallet.id:path/to/asset.

  1. No : in the path → local reference within the current pallet
  2. Has : → everything before it is the pallet ID, everything after is the path
  3. Paths are relative to the pallet root

Assets can inherit from other assets, even across pallets, and override specific properties.

avatar.dh-avatar
{
"inherits": "io.mltn.avatars.mltn-mayu:avatar.dh-avatar",
"patches": [
// Only the patches that differ
{
"$type": "dh.setMaterial",
"target": "Body",
"slot": 0,
"path": "materials/custom-skin.dh-mat"
}
]
}

Inheritance works at every level: avatars, objects, materials, rigs. You only define what’s different.

The compiler resolves all inheritance chains at build time. The compiled output has everything flattened, no unresolved references.

Platform-specific data lives in a platforms/ subdirectory alongside your universal definitions:

  • Directorymy-avatar/
    • avatar.dh-avatar Universal
    • Directorymaterials/
      • skin.dh-mat Universal material
    • Directoryplatforms/
      • Directoryvrchat/
        • avatar-descriptor.jsonc
      • Directoryunity/
        • Directorymaterials/
          • skin.dh-mat Unity-specific shader overrides
      • Directoryresonite/
        • avatar-metadata.jsonc

When building for a platform, the compiler loads the universal definition first, then applies any matching overrides from platforms/{platform}/.

Shared assets go in type-based folders at the pallet root. Self-contained features (accessories, outfits) get their own subdirectory with everything co-located:

  • Directorymy-pallet/
    • pallet.dh
    • avatar.dh-avatar
    • Directorytextures/ Shared textures
      • body-diffuse.png
      • body-normal.png
      • body-orm.dh-tex
    • Directorymodels/ Shared models
      • body.glb
    • Directoryrigs/ Shared rigs
      • humanoid.dh-rig
    • Directorymaterials/ Shared materials
      • skin.dh-mat
      • eyes.dh-mat
    • Directoryaccessories/ Self-contained features
      • Directoryglasses/
        • glasses.dh-obj
        • model.glb
        • material.dh-mat
        • lens.png
      • Directorypiercing/
        • piercing.dh-obj
        • model.glb
        • material.dh-mat

The rule: shared/base assets in type folders at root; self-contained features get their own directory with everything they need inside.

Designed for Git:

  • Commit: Source/ (your source files), Scripts/ (your custom scripts)
  • Ignore: Pallets/ (compiled output), .dh/ (cache)

The workspace init generates .gitignore and .gitattributes automatically. The .gitattributes file sets up Git LFS for binary assets (.glb, .fbx, .png, .jpg, etc.), so make sure you have Git LFS installed if you’re using those.