Skip to content

Resonite

Platform ID com.yellowdogman.resonite

Live link Push and update in place work against a live session, including on an avatar you have equipped and are wearing.

Resonite has no file format a tool can write. Its official external interface, ResoniteLink, edits a running session: slots, components and fields over a localhost WebSocket, plus imports for meshes and textures. So DigitalHeaven doesn’t export a Resonite file. DigitalHeaven.Platforms.Resonite pushes an avatar into the world you are hosting, as a new world object, and you equip it and save it to your inventory with Resonite’s own tools. Push the same avatar again later and the bridge finds it by its barcode and rewrites it in place.

The design and what a live probe settled are in the Resonite design note. The library references only Core and Content, and reads ResoniteLink’s JSON through a source-generated context, so it is NativeAOT-compatible like the Source bridge.

  1. Host a session in Resonite (your own world or a local one).
  2. Turn ResoniteLink on: Dashboard → Session → Settings → ResoniteLink. It stays on until the world closes.
  3. Compile the avatar’s pallet into your workspace as usual.
Terminal window
dh export resonite # list the workspace's avatars and each one's content hash
dh export resonite <barcode> # push, finding the session on its own
dh export resonite <barcode> --port 30431
FlagValuesDoes
--porta port numberThe session’s ResoniteLink port. Without it, the bridge listens for the announcements every ResoniteLink session broadcasts on UDP 12512 for one announcement interval (about 10 seconds) and uses the one session it hears; with several, it names them and asks for a port
--slota slot idWhich avatar to update when the world holds several pushes of the same barcode
--newonPushes a new world object even when the world already holds this avatar, so a test copy never touches the one you wear
--litpbs, toonOverrides litMaterial
--spring-boneson, offOverrides springBones
--hide-headon, offOverrides hideHead
--rebuildRewrites the avatar even when its content hash already matches
--verbose, -vPrints every step’s detail and every note

A push reports six steps:

  1. Find the session. Connect, and refuse a ResoniteLink version the bridge wasn’t built for (it was built for 0.13), naming both.
  2. Find the avatar in the world. Walk the world tree for a root slot tagged dh:<barcode>. If it carries the same content hash, the push stops here with nothing to do.
  3. Read the avatar from the workspace’s compiled pallets, as every DH host does.
  4. Convert it (see Conversion).
  5. Write into the world. Meshes and textures are imported first. Each provider slot is tagged with its asset’s hash, so an unchanged mesh or texture is never uploaded twice. Then one data-model batch creates or updates every slot and component. A new avatar then gets one call to Resonite’s own avatar creator (AvatarCreator.CreateBipedAvatar), which sets up IK, eyes, face tracking, protection and the proxies. DH never rebuilds those by hand.
  6. Done. The content hash is written last, so a push that stops halfway never looks finished. The next push completes it, and runs the avatar creator if it never ran.

A new avatar lands 1.5 m in front of the first user in the world, facing them.

The content hash is a 16-character hex hash of the avatar, the applied settings, every pallet the avatar reads, and the bridge’s own build (a hash of its sources and the shared export code in Content).

Resonite re-mints every id when a world or an item is saved, so the bridge never remembers ids. It marks what it owns instead:

  • The avatar root’s tag is dh:<barcode>, and the root carries a DigitalHeaven dynamic variable space with DigitalHeaven/barcode, DigitalHeaven/palletVersion, DigitalHeaven/contentHash and DigitalHeaven/exporter.
  • Every slot DH creates below it is tagged dh:<barcode>#<node>. The node is the bone’s model path (Armature/Hips/Spine). DH’s own slots use names that start with a colon: :model (an unnamed model root), :headHide, :assets, :asset/<hash>, :anchors and :anchor/<role>.
  • A slot without a dh: tag is yours, and DH never touches it. A DH slot the avatar no longer has is removed on the next push, unless something of yours is below it; then it is left in place and reported.

A slot Resonite adds below a tagged slot takes the parent’s tag. The avatar creator’s CenteredRoot, proxies, eye pivots and eye manager all do. So only the outermost slot with the root tag counts as the avatar, and where two slots share a node’s tag, the one with the name DH gave it wins.

An update rewrites each DH slot’s transform and every DH component on it (renderers, dynamic bone chains and the head hide), matched by type, under the component’s existing id. Whatever Resonite wired to those components, such as visemes, eyes and VRIK, stays wired. Only the types DH writes on a slot are matched there, so the avatar creator’s own RenderTransformOverride on the head is never taken for DH’s.

An update writes a slot’s transform only while the slot still hangs where DH put it. The avatar creator re-parents some of DH’s slots and places them itself: each eye bone goes under an eye pivot of its own, at the pivot’s origin, and the model goes under a CenteredRoot. A slot whose parent is a slot DH doesn’t own is left exactly where Resonite put it. An earlier build wrote the eye’s head-relative rest position into the pivot’s space and lifted both eyes 14 cm out of their sockets. A slot under another slot DH owns is one DH moved itself, such as the head going in or out of the head hide, so DH moves it to the planned parent and writes its transform.

AreaWhat happens
AxesglTF is right-handed and Y up; Resonite is left-handed and Y up, with +X to the avatar’s right. The avatar is turned to face +Z from its own arms, and its left lands on −X. That change is a mirror, so every triangle’s winding is turned back and every tangent’s handedness flips. It is applied once, at the bridge’s edge, through Content’s shared BasisChange
UVsV flips (glTF’s texture origin is the top left, Resonite’s the bottom left). Textures go up as the pallet stores them, top row first, which is what ResoniteLink takes
MeshesimportMeshRawData with the real bone names and bind poses, all four bone weights, every authored UV channel at its own dimension, and every morph target as a one-frame blendshape. Normal deltas are sent only when a shape has them
SkeletonEvery model node is a slot with its rest transform. A skinned renderer’s bone list is ordered as its mesh’s
Rigdh.rig’s humanoid bones fill a BipedRig. A thumb named Unity’s way (proximal, intermediate, distal) moves down one joint, since Resonite’s thumb is metacarpal, proximal, distal
HeadThe head is hidden from its wearer the way a hand-built Resonite avatar does it: a DH-owned Head Hide slot between the head bone and its parent, at rest, whose RenderTransformOverride scales everything below it to zero in the wearer’s own view (UserView) only. Every skinned renderer with a bone under the head is listed on it, so meshes skinned to an accessory’s copy of the head (whiskers, glasses) collapse too, and a rigid mesh below the head shrinks with the hierarchy. Mirrors, cameras and other users still see the head. The avatar creator’s own override on the head only moves the head with the headset; it hides nothing
AnchorsThe viewpoint, hands, feet and hips are placed from the rig’s own bones and DH’s view position, with each hand’s tooltip, grabber and shelf placed as Resonite’s Unity SDK places them
TexturesimportTexture2DRawData, uncompressed, at most 2048 px a side. Color textures are sRGB; normal maps and masks are linear
Blendshape weightsThe avatar’s rest weights are copied onto the renderer, so the face matches DH
DH materialResonite material
unlitUnlitMaterial
lit, with a matcapXiexeToonMaterial, with the matcap in its matcap slot
lit, litMaterial: toonXiexeToonMaterial
lit, both faces drawnPBS_DualSidedMetallic
litPBS_Metallic
water, glassPBS_Metallic blended, with a note

The tint, base color, normal, occlusion and emission maps carry over. DH’s metallic-roughness texture is repacked into Resonite’s metallic map (metallic in red, smoothness in alpha), with the material’s metallic and roughness folded in. Detail maps, decals and parallax are not pushed, and a matcap mask or a matcap blend other than add has no Xiexe slot; each is reported as a note. A material that can’t be read is pushed flat gray and reported. It is never dropped.

Each DH spring chain becomes a DynamicBoneChain on its anchor’s parent slot. A branching chain becomes one component per branch. The bridge links each chain to its bones itself, since a chain written over ResoniteLink isn’t linked by Resonite.

The motion is DH’s resolved rates as a continuous spring-damper, the same equivalence the Source export’s jigglebones use: its stiffness becomes Elasticity and its damping Damping, the chain’s immobile becomes Inertia, its radius BaseBoneRadius, and its gravity Gravity. This mapping has not been calibrated against Resonite’s solver yet. DynamicBoneChain has no angle limits, so a limited chain (ears, whiskers) swings freer than in DH. This is reported as one note per armature. The avatar’s springs overrides can set Resonite’s own fields per chain.

KeyValuesDefaultDoes
springBonestrue, falsetrueWhether spring chains become DynamicBoneChains
litMaterialpbs, toonpbsWhich material a lit DH material becomes; toon is XiexeToonMaterial for all of them
setupEyestrue, falsetrueThe avatar creator’s eye setup
setupProtectiontrue, falsetrueThe avatar creator’s avatar protection
setupVolumeMetertrue, falsefalseThe avatar creator’s voice volume meter
setupFaceTrackingtrue, falsetrueThe avatar creator’s face tracking
hideHeadtrue, falsetrueWhether the head is hidden from its wearer’s own view
maxTextureSizepixels2048The largest texture side pushed
materialsan object of material to overridePer material: component (a Resonite material, by short name or full type) and fields. A material is named by its path, its file name, or its file name without extension
springsan object of chain to overridePer spring chain: fields of DynamicBoneChain. A chain is named by its target path or its last segment

An override’s fields are set directly. A bare number is a float and a bare boolean a bool. Anything else is written as ResoniteLink member JSON, as in the example below. A field Resonite can’t read is skipped and reported.

Settings are layered. Each layer states only what it changes, and a higher layer wins:

  1. The built-in default.
  2. The workspace, in dh-config.jsonc under platforms.resonite.
  3. The avatar’s own pallet, at the avatar’s path under platforms/resonite/ (or the ID com.yellowdogman.resonite), with .jsonc in place of .dh-avatar. Like every platform override, it is packed into the pallet as a plain file.
  4. The user’s force: dh export resonite flags.
platforms/resonite/my-avatar.jsonc (for my-avatar.dh-avatar)
{
"litMaterial": "toon",
"setupVolumeMeter": true,
"materials": {
"materials/main-fur.dh-mat": {
"component": "XiexeToonMaterial",
"fields": {
"RimIntensity": 0.4,
"Outline": { "$type": "enum", "value": "Lit", "enumType": "XiexeToonMaterial+OutlineStyle" }
}
}
},
"springs": {
"EarBase L": { "fields": { "Stiffness": 0.6, "Inertia": 0.3 } }
}
}

An unknown key or value is skipped with a warning. It never fails a push.

FeatureResonite
OverlayNo
Avatar replacementPartial: pushed as a world object you equip and save yourself
Update in placeYes
Spring bonesPartial: DynamicBoneChain, no limits, uncalibrated
Spring collidersNo
Eye look and blinkPartial: Resonite’s own eye setup from the avatar creator
Live mirror from the DH editorNo: stage 2
  • No round trip. ResoniteLink refuses to read assets back out.
  • No Resonite file. The output is a live scene you save.
  • No inventory save. You save the avatar yourself.
  • Host only. You must host the session and turn ResoniteLink on.

A failed push never throws. It returns a stable code: workspace-missing, avatar-not-found, avatar-unreadable, not-humanoid, missing-bones (the avatar creator needs the head, both hands and both upper arms), no-session, several-sessions, connection-failed, version-mismatch, several-matches, refused (Resonite refused a request; the message carries its reason), canceled or unexpected.

  • The library is Platforms/DigitalHeaven.Platforms.Resonite: ResoniteExporter is the public face (ListAvatars, PushAsync, StateOfAsync, DiscoverSessionsAsync), Conversion/ builds a plan with no session involved, Push/ writes it, Marker/ tags and finds, and Link/ holds the client.
  • The client is DH’s own. It speaks the official YellowDogMan.ResoniteLink package’s message models, but not its LinkInterface transport. That transport takes no lock on sends, never fails in-flight calls when the socket drops, returns null instead of the error for a refused request, and stops receiving on an unsolicited message. None of that can be fixed from outside it.
  • Ids are client-allocated, for slots, components and the slot transform fields a dynamic bone chain drives. So the whole avatar goes up in one batch with its references already resolved.
  • Tests (DigitalHeaven.Platforms.Resonite.Tests) need no Resonite. They cover a chiral avatar (a horn on one side only, since a symmetric body can’t show a mirror), the raw mesh layout byte by byte, tags and markers, settings layering, and find-by-tag over a world tree shaped like a live one. The suite runs with reflection-based JSON off.
  • Pictures of a pushed avatar can be taken without touching the Resonite window. Add an InteractiveCamera with PositioningMode: Manual on a slot under a posed parent, and call its Capture() sync method. Resonite writes the photo (PNG with Format: PNG) into its local asset cache, AppData/LocalLow/Yellow Dog Man Studios/Resonite/Assets/ in your user folder.