We are implementing a new feature named Social Emotes, which will allow multiple players to interact with this new kind of emote.
These new emotes will have a new property named outcomes, which lets authors
define the animation to execute and specify whether it loops or not.
loop and additionalProperties (sound/geometry) while
keeping richer data off-chain. Social Emotes require multiple animation outcomes for better
player interaction, but we want to avoid bloating the on-chain metadata.
To ensure consistency across exported GLB clips, we suggest the use of the following pattern:
<Action>_(Start | Start_Prop | Avatar | Prop | AvatarOther)
HighFive,
WaveHello).
_, followed by one of the
predefined roles (Start, Start_Prop, Avatar,
Prop, AvatarOther).
Start_Prop, AvatarOther).
Examples:
HighFive_StartHighFive_Start_PropHighFive_AvatarHighFive_PropHighFive_AvatarOtherRationale
export type ArmatureId = "Armature" | "Armature_Prop" | "Armature_Other"
export type EmoteClip = {
animation: string // GLB clip name "HighFive_Avatar" (suggested, not enforced)
}
export type StartAnimation = {
loop: boolean
Armature: EmoteClip
Armature_Prop?: EmoteClip
audio?: string
}
export type OutcomeGroup = {
title: string
loop: boolean
// Any subset of armatures; validated at runtime to ensure at least one
clips: Partial<Record<ArmatureId, EmoteClip>>
audio?: string
}
export type EmoteDataADR74 = {
category: EmoteCategory
representations: EmoteRepresentationADR74[]
tags: string[]
loop: boolean
startAnimation?: StartAnimation
randomizeOutcomes?: boolean
outcomes?: OutcomeGroup[]
}
loop if present.
Example (two-armature outcomes):
const emoteWithADR74Data = {
// ...,
emoteDataADR74: {
// ...,
startAnimation: {
loop: true,
Armature: {
animation: "HighFive_Start"
}
},
randomizeOutcomes: false,
outcomes: [
{
title: "High Five",
loop: false,
clips: {
Armature: {
animation: "HighFive_Avatar"
},
Armature_Other: {
animation: "HighFive_AvatarOther"
}
}
}
]
}
}
outcomes.length === 1 always that outcome.outcomes.length > 1 and no
randomizeOutcomes: true deterministic selection policy
(implementation-defined).
outcomes.length > 1 and
randomizeOutcomes: true choose randomly among all outcomes.
ADR-74 extended the metadata to include loop and then
additionalProperties (sound/geometry) by appending fields on the right to avoid
breaking older parsers. We follow the same pattern:
ADR-74 (current)
${version}:${type}:${name}:${description}:${category}:${bodyShapeTypes}:${loop}:${additionalProperties}
ADR-287 (append-only)
${version}:${type}:${name}:${description}:${category}:${bodyShapeTypes}:${loop}:${additionalProperties}:${outcomeType}
Where:
outcomeType ∈ { so | mo | ro }outcomeType is stored on-chain. The full
outcomes[] list remains off-chain in the emote JSON.
outcomeType from the schema
outcomes.length === 1: sooutcomes.length > 1 && randomizeOutcomes === true:
ro
mooutcomes is empty (legacy), treat as so using the default
animation.
armature MUST be a non-empty string.armature MUST be unique across all clips (no
duplicates).
animation MUST be a non-empty string.loop MUST be a boolean.outcomeType MUST be consistent with the off-chain
outcomes[] derivation.
additionalProperties continues to accept
s | g | sg (sound/geometry) as per ADR-74.
additionalProperties MUST continue to function.
Random outcomes (off-chain)
const emoteWithADR74Data = {
// ...,
emoteDataADR74: {
// ...,
startAnimation: {
loop: true,
Armature: {
animation: "Hug_Start"
}
},
randomizeOutcomes: true,
outcomes: [
{
title: "Hug Short",
loop: false,
clips: {
Armature: {
animation: "HugShort_Avatar"
},
Armature_Other: {
animation: "HugShort_AvatarOther"
}
}
},
{
title: "Hug Long",
loop: false,
clips: {
Armature: {
animation: "HugLong_Avatar"
},
Armature_Other: {
animation: "HugLong_AvatarOther"
}
}
}
]
}
}
// outcomeType => "ro"
Resulting on-chain metadata (example)
${version}:${type}:${name}:${description}:${category}:${bodyShapeTypes}:${loop}:${additionalProperties}:ro
Note: The actual list of
outcomes[]is not encoded on-chain.