Skip to content

Pallets & Assets

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

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"
}
}

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.set-material",
"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.