Universal Avatar
Cross-runtime avatar loadouts, equipment slots, and placeholder-compatible socket rules for Web and Unreal.
Universal Avatar is the player-owned character layer that travels across HELIX runtimes. The same loadout should be readable by the Web game, the Web SDK, and the Unreal runtime, even when an item ships different renderable assets per runtime.
This page is the shared contract for the first Character Creator implementation.
Loadout model
The platform stores one loadout per player:
type AvatarBaseShape = 'male' | 'female' | 'custom';
type UniversalAvatarBody =
| {
mode: 'preset';
baseMeshId: 'helix:humanoid:male' | 'helix:humanoid:female';
}
| {
mode: 'customMesh';
avatarItemId: string;
inventoryItemId: string;
glbUrl: string | null;
skeleton: string | null;
};
type AvatarLoadout = {
version: 1;
body: UniversalAvatarBody;
baseShape: AvatarBaseShape;
customAvatar: {
source: 'equipped' | 'auto' | 'default';
inventoryItemId: string | null;
itemId: string | null;
glbUrl: string | null;
skeleton: string | null;
};
equippedSlots: Array<{
slot: string;
itemId: string;
inventoryItemId: string;
socket: string | null;
inventoryItem: InventoryItem;
}>;
inventory: InventoryItem[];
updatedAt: string;
};male and female presets reference humanoid meshes shipped with the runtime. They do not require an
internet fetch. custom uses an owned marketplace avatar item and points to its web-compatible GLB
when available. Character body editing is locked while a custom full mesh is active.
The current backend exposes the canonical body schema now. A durable independent male/female preference
requires a user avatar-state record; until that exists, the default preset resolves as
helix:humanoid:male unless a custom avatar mesh is equipped.
Equipment slots
Avatar equipment is instance-based. Equip by inventory instance id, not universal item id, so duplicate owned copies can be selected independently.
| Slot | Typical UI group | Socket |
|---|---|---|
headPreset | Head Presets | head |
faceType | Face Types | head |
eyebrows | Eyes / Eyebrows | head |
eyelashes | Eyes / Eyelashes | head |
irises | Eyes / Irises | head |
hair | Hair | head |
clothingPreset | Clothing Presets | chest |
top | Tops | chest |
outfit | Outfits | chest |
bottom | Bottoms | hips_l |
underwear | Underwear | hips_l |
socks | Socks | foot_l |
shoes | Shoes | foot_l |
bag | Bags | back |
hat | Hats | head |
mask | Masks | head |
eyewear | Eyewear | head |
necklace | Necklaces | neck |
nails | Nails | hand_r.grip |
gloves | Gloves | hand_r.grip |
Items can override the default socket with properties.universalAvatar.socket or inventory metadata,
but the slot names above remain stable for cross-runtime compatibility.
Platform API
Read the current player's loadout:
GET /api/v1/universal-items/avatar/loadout/me
Authorization: Bearer <access token>Patch the current player's loadout:
PATCH /api/v1/universal-items/avatar/loadout/me
Authorization: Bearer <access token>
Content-Type: application/json
{
"baseShape": "male",
"customAvatarInventoryItemId": null,
"equippedSlots": {
"top": "inventory-instance-id",
"shoes": null
}
}null unequips a slot. Unknown slots and non-wearable inventory instances are rejected.
For local QA only, the backend can seed placeholder avatar inventory:
POST /api/v1/universal-items/avatar/loadout/me/placeholder-seed
Authorization: Bearer <access token>Web SDK and shell behavior
The Character Creator is a shell overlay, not a standalone destination. It can be raised from any home or world that allows Universal Avatars. Worlds can request the shell to open the creator and can read or patch loadouts:
await Helix.avatar.openCreator();
const loadout = await Helix.avatar.getLoadout();
await Helix.avatar.updateLoadout({
equippedSlots: {
bag: backpackInventoryInstanceId,
},
});
const off = Helix.avatar.onAvatarChanged((next) => {
applyAvatar(next);
});The shell remains authoritative for authenticated API calls. Direct local world runs may return an
empty/default loadout until they are embedded in the HELIX shell. When avatar.updateLoadout succeeds,
the shell emits avatar-changed and equipment-changed to the active world. Multiplayer replication is
outside this first implementation slice.
Engine runtime
The Web engine uses a humanoid equipment service over the existing socket service:
- Resolve the item slot.
- Resolve the socket from
properties.universalAvatar.socket, item metadata, or the default slot map. - Attach the item's renderable mesh to that socket.
- Use a generated primitive placeholder when the item has no web-compatible mesh yet.
Placeholder meshes must still follow the shared skeleton/socket contract. They are temporary renderables, not a separate ownership or equipment model.
When a world uses the HELIX humanoid as the player character and Universal Avatars are enabled, the
runtime should read Helix.avatar.getLoadout() during player setup and apply the returned loadout to
the humanoid by default. Worlds can opt into additional scripting later, but the baseline humanoid path
should not require custom per-world avatar code.
Unreal compatibility
The Unreal runtime should consume the same loadout fields and slot ids. Runtime-specific mesh assets may differ, but slot names, inventory instance ids, base shape semantics, and custom-avatar lock behavior should not diverge.