references/avatar-apis.md
# Avatar APIs Reference
## AvatarShape — Full Fields
Create NPC avatars in your scene:
```typescript
import { AvatarShape } from '@dcl/sdk/ecs'
AvatarShape.create(entity, {
id: 'npc-unique-id', // Unique identifier (required)
name: 'NPC Name', // Display name
bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale', // or BaseFemale
wearables: [
// Array of wearable URNs
'urn:decentraland:off-chain:base-avatars:eyebrows_00',
'urn:decentraland:off-chain:base-avatars:mouth_00',
'urn:decentraland:off-chain:base-avatars:eyes_00',
'urn:decentraland:off-chain:base-avatars:blue_tshirt',
'urn:decentraland:off-chain:base-avatars:brown_pants',
'urn:decentraland:off-chain:base-avatars:classic_shoes',
'urn:decentraland:off-chain:base-avatars:short_hair',
],
hairColor: { r: 0.92, g: 0.76, b: 0.62 }, // RGB 0-1
skinColor: { r: 0.94, g: 0.85, b: 0.6 }, // RGB 0-1
eyeColor: { r: 0.2, g: 0.4, b: 0.7 }, // RGB 0-1
expressionTriggerId: '', // built-in emote name OR a scene-emote _emote.glb path
expressionTriggerTimestamp: 0, // Lamport timestamp; bump to replay the SAME id
talking: false, // Mouth animation
emotes: [], // Custom emote URNs
showOnlyWearables: false, // Mannequin mode (show wearables without body)
})
```
`expressionTriggerId` on an AvatarShape plays either a built-in emote (`'robot'`) or a custom scene emote by `.glb` path (`'animations/Snowball_Throw_emote.glb'`, same `_emote.glb` files as `triggerSceneEmote`). Confirmed in test scenes 4,21 and 4,22.
### Body Shape URNs
- `urn:decentraland:off-chain:base-avatars:BaseMale`
- `urn:decentraland:off-chain:base-avatars:BaseFemale`
### Common Base Wearable URNs
**Required minimums (avatar won't render without face features):**
- `urn:decentraland:off-chain:base-avatars:eyebrows_00` through `eyebrows_07`
- `urn:decentraland:off-chain:base-avatars:mouth_00` through `mouth_04`
- `urn:decentraland:off-chain:base-avatars:eyes_00` through `eyes_11`
**Hair:**
- `short_hair`, `long_hair`, `curly_hair`, `bald`, `mohawk`, `ponytail`, `cornrows`, `cool_hair`
**Upper body:**
- `blue_tshirt`, `red_tshirt`, `green_tshirt`, `black_tshirt`, `white_shirt`, `striped_shirt`, `elegant_sweater`
**Lower body:**
- `brown_pants`, `blue_jeans`, `cargo_pants`, `shorts`, `formal_pants`
**Shoes:**
- `classic_shoes`, `sport_shoes`, `elegant_shoes`, `sneakers`
All base wearable URNs follow the pattern: `urn:decentraland:off-chain:base-avatars:<name>`
### Mannequin Mode
Display wearables without a full avatar body — useful for storefronts:
```typescript
AvatarShape.create(entity, {
id: 'mannequin-1',
name: 'Display',
wearables: ['urn:decentraland:matic:collections-v2:0x...:0'],
showOnlyWearables: true,
})
```
## All Anchor Points
```typescript
import { AvatarAnchorPointType } from '@dcl/sdk/ecs'
AvatarAnchorPointType.AAPT_NAME_TAG // Above the head
AvatarAnchorPointType.AAPT_RIGHT_HAND // Right hand
AvatarAnchorPointType.AAPT_LEFT_HAND // Left hand
AvatarAnchorPointType.AAPT_POSITION // Avatar root position
AvatarAnchorPointType.AAPT_HEAD
AvatarAnchorPointType.AAPT_NECK
AvatarAnchorPointType.AAPT_SPINE
AvatarAnchorPointType.AAPT_SPINE1
AvatarAnchorPointType.AAPT_SPINE2
AvatarAnchorPointType.AAPT_HIP
AvatarAnchorPointType.AAPT_LEFT_SHOULDER
AvatarAnchorPointType.AAPT_LEFT_ARM
AvatarAnchorPointType.AAPT_LEFT_FOREARM
AvatarAnchorPointType.AAPT_LEFT_HAND_INDEX
AvatarAnchorPointType.AAPT_RIGHT_SHOULDER
AvatarAnchorPointType.AAPT_RIGHT_ARM
AvatarAnchorPointType.AAPT_RIGHT_FOREARM
AvatarAnchorPointType.AAPT_RIGHT_HAND_INDEX
AvatarAnchorPointType.AAPT_LEFT_UP_LEG
AvatarAnchorPointType.AAPT_LEFT_LEG
AvatarAnchorPointType.AAPT_LEFT_FOOT
AvatarAnchorPointType.AAPT_LEFT_TOE_BASE
AvatarAnchorPointType.AAPT_RIGHT_UP_LEG
AvatarAnchorPointType.AAPT_RIGHT_LEG
AvatarAnchorPointType.AAPT_RIGHT_FOOT
AvatarAnchorPointType.AAPT_RIGHT_TOE_BASE
AvatarAnchorPointType.AAPT_NAME_TAG
```
## Built-in Emote Names
For `triggerEmote({ predefinedEmote: '...' })`:
**Interactions:**
- `buttonDown` — press button downward
- `buttonFront` — press button forward
- `getHit` — react to being hit
- `knockOut` — fall knocked out
- `lever` — pull a lever
- `openChest` — open a chest
- `openDoor` — open a door
- `punch` — punch
- `push` — push something
- `swingWeaponOneHand` — one-handed weapon swing
- `swingWeaponTwoHands` — two-handed weapon swing
- `throw` — throw an object
**Sitting:**
- `sittingChair1` — sit in chair (variant 1)
- `sittingChair2` — sit in chair (variant 2)
- `sittingGround1` — sit on ground (variant 1)
- `sittingGround2` — sit on ground (variant 2)
**Social:**
- `wave` — wave hello
- `fistpump` — fist pump
- `robot` — robot dance
- `raiseHand` — raise hand
- `clap` — clap hands
- `money` — money gesture
- `kiss` — blow a kiss
- `tik` — tik dance
- `hammer` — hammer dance
- `tektonik` — tektonik dance
- `dontsee` — cover eyes
- `handsair` — hands in the air
- `shrug` — shrug shoulders
- `disco` — disco dance
- `dab` — dab
- `headexplode` — head explode
## Player Event Callbacks
### Scene Entry/Exit
```typescript
import { onEnterScene, onLeaveScene } from '@dcl/sdk/src/players'
onEnterScene((player) => {
console.log('Player entered:', player.userId)
})
onLeaveScene((userId) => {
console.log('Player left:', userId)
})
```
### Avatar Change Listeners
```typescript
import {
AvatarEmoteCommand,
AvatarBase,
AvatarEquippedData,
EmoteState,
} from '@dcl/sdk/ecs'
// Emote lifecycle: each entry carries state ES_STARTED / ES_FINISHED / ES_INTERRUPTED.
// Absent state (older clients) means STARTED — always apply the ?? default.
// See SKILL.md "Detecting when an emote finishes".
AvatarEmoteCommand.onChange(engine.PlayerEntity, (cmd) => {
if (cmd) console.log('Emote:', cmd.emoteUrn, 'state:', cmd.state ?? EmoteState.ES_STARTED)
})
// Appearance changed
AvatarBase.onChange(engine.PlayerEntity, (base) => {
if (base) console.log('Name:', base.name, 'Body:', base.bodyShapeUrn)
})
// Equipment changed
AvatarEquippedData.onChange(engine.PlayerEntity, (equipped) => {
if (equipped) console.log('Wearables:', equipped.wearableUrns)
})
```
## AvatarModifierType — All Values
```typescript
AvatarModifierType.AMT_HIDE_AVATARS // Hide all avatars in area
AvatarModifierType.AMT_DISABLE_PASSPORTS // Disable clicking avatars for profiles
AvatarModifierType.AMT_HIDE_NAMETAGS // Hide name tags above avatars in area
```
To disable jumping in an area, use the `InputModifier` component's `disableJump` flag (covered in the advanced-input skill), not an `AvatarModifierType`.
## AvatarLocomotionSettings
```typescript
// Values shown are the CLIENT DEFAULTS — set only the fields you want to change.
AvatarLocomotionSettings.createOrReplace(engine.PlayerEntity, {
walkSpeed: 1.5, // Control key on desktop
jogSpeed: 8, // the default movement speed
runSpeed: 10, // Shift key on desktop
jumpHeight: 1,
runJumpHeight: 1.5,
doubleJumpHeight: 2,
glidingSpeed: 6, // horizontal speed while gliding
glidingFallingSpeed: 1, // MAX descent speed while gliding — caps falling only, does not limit upward motion (e.g. lift from a continuous force)
hardLandingCooldown: 0.75, // seconds before moving again after a high fall
})
```
All fields are `float`; each is optional (omit to keep the client default). Default values (verified against unity-explorer `origin/main` `Explorer/Assets/DCL/Character/CharacterMotion/Settings/CharacterControllerSettings.asset`):
| Field | Default | Source field in .asset |
| --------------------- | ------- | ---------------------- |
| `walkSpeed` | `1.5` | `WalkSpeed` |
| `jogSpeed` | `8` | `JogSpeed` |
| `runSpeed` | `10` | `RunSpeed` |
| `jumpHeight` | `1` | `JogJumpHeight` |
| `runJumpHeight` | `1.5` | `RunJumpHeight` |
| `doubleJumpHeight` | `2` | `AirJumpHeight` |
| `glidingSpeed` | `6` | `GlideSpeed` |
| `glidingFallingSpeed` | `1` | `GlideMaxGravity` |
| `hardLandingCooldown` | `0.75` | `LongFallStunTime` |
references/catalyst-profile-fetch.md
# Fetching Avatar Profiles from the Catalyst
For users **not currently in the scene**, `getPlayer(userId)` returns `null`. To dress an `AvatarShape` for an arbitrary wallet address (parcel owner, NFT holder, leaderboard entry, off-scene claimant, etc.), fetch the profile from the catalyst's `/lambdas/profile/` endpoint.
## Endpoint
```
GET https://peer.decentraland.org/lambdas/profile/<wallet-address>
```
- `peer.decentraland.org` is the canonical catalyst. Use it regardless of which realm or world the scene is deployed to.
- **Do NOT read `realmInfo.baseUrl`** for this — Worlds servers do not expose `/lambdas`.
- Address is case-insensitive; lower-case is conventional.
## Response shape (verified live)
```json
{
"timestamp": 1776880856372,
"avatars": [
{
"name": "NicoE",
"userId": "0xe2b6...",
"ethAddress": "0xe2b6...",
"hasConnectedWeb3": true,
"hasClaimedName": true,
"avatar": {
"bodyShape": "urn:decentraland:off-chain:base-avatars:BaseMale",
"wearables": ["urn:decentraland:off-chain:base-avatars:thug_life", "..."],
"emotes": [],
"eyes": { "color": { "r": 0.23, "g": 0.62, "b": 0.31, "a": 1 } },
"hair": { "color": { "r": 0.45, "g": 0.01, "b": 0.99, "a": 1 } },
"skin": { "color": { "r": 0.86, "g": 0.69, "b": 0.56, "a": 1 } },
"snapshots": {}
}
}
]
}
```
- The wearable list lives at `json.avatars[0].avatar.wearables` — **NOT** `json[0].metadata.avatars[0].avatar.wearables` (that older shape appears in some docs and is wrong for this endpoint).
- Colors come as `{ r, g, b, a }` floats in `[0,1]`.
- For **unknown / never-seen addresses** the response is `{ "avatars": [], "timestamp": 0 }` — code MUST handle the empty array.
## Helper: `fetchAvatarFromCatalyst`
Returns fields ready to spread into `AvatarShape.create(...)`. Returns `null` on network error, non-2xx, or unknown address.
```typescript
import { Color3 } from '@dcl/sdk/math'
const CATALYST_PROFILE_BASE = 'https://peer.decentraland.org/lambdas/profile/'
export interface CatalystAvatar {
bodyShape?: string
wearables?: string[]
skinColor?: Color3
hairColor?: Color3
eyeColor?: Color3
}
export async function fetchAvatarFromCatalyst(
address: string
): Promise<CatalystAvatar | null> {
try {
const res = await fetch(`${CATALYST_PROFILE_BASE}${address}`)
if (!res.ok) return null
const json: any = await res.json()
// Empty for unknown addresses: { avatars: [], timestamp: 0 }
const avatar = json?.avatars?.[0]?.avatar
if (!avatar) return null
return {
bodyShape: avatar.bodyShape,
wearables: Array.isArray(avatar.wearables) ? avatar.wearables : [],
skinColor: parseColor(avatar.skin?.color),
hairColor: parseColor(avatar.hair?.color),
eyeColor: parseColor(avatar.eyes?.color)
}
} catch (err) {
console.error('[profile] Failed to fetch profile for', address, err)
return null
}
}
function parseColor(input: any): Color3 | undefined {
if (!input || typeof input.r !== 'number') return undefined
return Color3.create(input.r, input.g, input.b)
}
```
## Color shape gotcha — `Color3`, NOT `{ color: Color3 }`
The catalyst returns `eyes/hair/skin` as `{ color: { r, g, b, a } }`. The SDK's `AvatarShape` expects the **raw `Color3`** for `skinColor` / `hairColor` / `eyeColor` — do not re-wrap in `{ color: ... }`. Wrapping produces a TS2322 error.
```typescript
// CORRECT
AvatarShape.create(entity, {
id: address,
skinColor: appearance.skinColor, // Color3
hairColor: appearance.hairColor,
eyeColor: appearance.eyeColor
})
// WRONG — TS2322
AvatarShape.create(entity, {
id: address,
skinColor: { color: appearance.skinColor }
})
```
## Full usage example
Spawn an entity for an off-scene user, fetch the profile, dress the `AvatarShape`. For the **local** player, prefer `getPlayer()` — it already exposes `wearables` and `emotes` synchronously.
```typescript
import { engine, Transform, AvatarShape } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
import { getPlayer } from '@dcl/sdk/players'
import { fetchAvatarFromCatalyst } from './profile'
async function spawnRemoteAvatar(address: string, displayName: string) {
const local = getPlayer()
const isLocal =
!!local && local.userId.toLowerCase() === address.toLowerCase()
// Local player → use getPlayer (already loaded). Otherwise → catalyst.
const appearance = isLocal ? null : await fetchAvatarFromCatalyst(address)
const entity = engine.addEntity()
Transform.create(entity, {
position: Vector3.create(8, 0, 8),
scale: Vector3.create(7, 7, 7) // giant "RESERVED BY" avatar
})
AvatarShape.create(entity, {
id: address,
name: displayName,
bodyShape: appearance?.bodyShape,
wearables: isLocal ? local!.wearables ?? [] : appearance?.wearables ?? [],
emotes: isLocal ? local!.emotes ?? [] : [],
skinColor: appearance?.skinColor,
hairColor: appearance?.hairColor,
eyeColor: appearance?.eyeColor
})
return entity
}
```
If `fetchAvatarFromCatalyst` returns `null`, the `AvatarShape` falls back to its built-in defaults (an undressed base avatar) — handle this case if it matters visually.
## When to use which API
| Target user | API | Notes |
|---|---|---|
| Local player | `getPlayer()` from `@dcl/sdk/players` | Sync, already loaded, includes `wearables` and `emotes`. |
| Any other player **currently in the scene** | `getPlayer(userId)` from `@dcl/sdk/players` | Returns `null` if they leave. |
| Any address NOT in the scene (parcel owner, NFT holder, historical claimant, leaderboard) | `fetchAvatarFromCatalyst(address)` | Async HTTP. Use for off-scene users only. |
`AvatarShape.create({ id: address })` with **only** an `id` does NOT auto-fetch wearables — the avatar will render as an undressed base body. The `id` is just the avatar's identifier inside the scene; appearance fields (`wearables`, `bodyShape`, colors) must be supplied explicitly.
## Sources
- Endpoint and response shape verified live against `https://peer.decentraland.org/lambdas/profile/<address>` on 2026-04-29.
- Color3 (not `{ color: Color3 }`) for `AvatarShape.skinColor` / `hairColor` / `eyeColor` confirmed against the SDK type-checker — see `references/avatar-apis.md` in this skill for the full `AvatarShape` schema.
- Reference docs: https://docs.decentraland.org/creator/scenes-sdk7/interactivity/user-data#data-from-any-player (note: the snippet on that page shows `json[0].metadata.avatars[0].avatar.wearables`, which is the format from a **different** endpoint and does NOT match the live `/lambdas/profile/<address>` response).
SKILL.md
---
name: player-avatar
description: The live player in a Decentraland scene. Use when the user wants to read player position or profile, fetch avatar appearance for off-scene addresses (parcel owners, NFT holders), trigger emotes, read equipped wearables, attach items to players/avatar (cosmetic vs held gameplay items), hide avatars or disable passports in zones (AvatarModifierArea), adjust locomotion speed, teleport the player (movePlayerTo), or listen for scene entry/exit. Do NOT use for NPC characters (see npcs), wallet/blockchain checks (see nft-blockchain), freezing player movement (see advanced-input for InputModifier), or camera mode (see camera-control).
---
# Player and Avatar System in Decentraland
## CRITICAL: The player Transform is READ-ONLY from scene code
`Transform` on `engine.PlayerEntity` is engine-controlled. **Mutations from scene code are silently ignored** — your code compiles, runs, no error is thrown, and nothing moves in-world. This is the most common bug when trying to lift, push, knock back, float, or teleport the player.
```typescript
// WRONG — compiles cleanly, runs, does NOTHING in-world
const t = Transform.getMutable(engine.PlayerEntity)
t.position.y += 0.1 // ignored
t.position = Vector3.create(8, 0, 8) // ignored
Transform.createOrReplace(engine.PlayerEntity, { ... }) // ignored
```
**Symptom to recognize:** TypeScript accepts the code, the system ticks, no console error, but the avatar never moves. If you wrote `Transform...PlayerEntity` and expected motion, this is your bug.
**Correct API by intent:**
| Goal | Use | Skill |
|------|-----|-------|
| Instant teleport / smooth slide to a point | `movePlayerTo` from `~system/RestrictedActions` | this skill, see below |
| Lift / float / launch / jump pad / knockback / push / wind / repulsion | `Physics.*` from `@dcl/sdk/ecs` | `player-physics` |
| Restrict / freeze movement | `InputModifier` on `engine.PlayerEntity` | `advanced-input` |
| Change run speed / jump height | `AvatarLocomotionSettings` on `engine.PlayerEntity` | this skill, see below |
`Transform.get(engine.PlayerEntity)` is valid for **reading** position and rotation only.
## Player Position and Movement (Reading)
Access the player's position via the reserved `engine.PlayerEntity`:
```typescript
import { engine, Transform } from '@dcl/sdk/ecs'
function trackPlayer() {
if (!Transform.has(engine.PlayerEntity)) return
const playerTransform = Transform.get(engine.PlayerEntity)
console.log('Player position:', playerTransform.position)
console.log('Player rotation:', playerTransform.rotation)
}
engine.addSystem(trackPlayer)
```
Always check `Transform.has(engine.PlayerEntity)` before reading player data — it may not be ready on the first frame.
### Distance-Based Logic
```typescript
import { Vector3 } from '@dcl/sdk/math'
function proximityCheck() {
const playerPos = Transform.get(engine.PlayerEntity).position
const npcPos = Transform.get(npcEntity).position
const distance = Vector3.distance(playerPos, npcPos)
if (distance < 5) {
console.log('Player is near the NPC')
}
}
engine.addSystem(proximityCheck)
```
## Player Profile Data
Get the player's name, wallet address, and guest status:
```typescript
import { getPlayer } from '@dcl/sdk/src/players'
function main() {
const player = getPlayer()
if (player) {
console.log('Name:', player.name)
console.log('User ID:', player.userId)
console.log('Is guest:', player.isGuest)
}
}
```
- `userId` — the player's Ethereum wallet address (or guest ID)
- `isGuest` — `true` if the player hasn't connected a wallet
Check `isGuest` before attempting any wallet-dependent feature (guests have no on-chain identity).
## Profile Data for Off-Scene Users (Catalyst)
`getPlayer(userId)` only returns data for users **currently connected to this scene**. For any other address (parcel owner, NFT holder, leaderboard entry, off-scene claimant), fetch from the catalyst:
```
GET https://peer.decentraland.org/lambdas/profile/<wallet-address>
```
- Always use `peer.decentraland.org` — it is the canonical catalyst regardless of realm/world. Worlds servers do NOT expose `/lambdas`, so do not blindly read `realmInfo.baseUrl`.
- Response shape: `json.avatars[0].avatar.{ bodyShape, wearables, eyes:{color}, hair:{color}, skin:{color} }` (NOT the `json[0].metadata.avatars...` shape from older docs).
- Unknown address returns `{ avatars: [], timestamp: 0 }` — handle the empty array.
- Colors come as `{ r, g, b, a }` floats in `[0,1]`. Build a `Color3` and pass it directly to `AvatarShape.skinColor` / `hairColor` / `eyeColor` — these fields take a raw `Color3`, NOT `{ color: Color3 }` (wrapping causes TS2322).
`AvatarShape.create({ id: address })` with only an `id` does NOT auto-fetch wearables — the avatar renders undressed unless you supply `bodyShape`, `wearables`, and the color fields explicitly.
**Which API to use:**
- Local or in-scene player → `getPlayer(userId)` (sync, includes wearables/emotes).
- Off-scene address → `fetchAvatarFromCatalyst(address)` (async HTTP).
For the full helper (`fetchAvatarFromCatalyst`), end-to-end usage example, and gotchas, see `{baseDir}/references/catalyst-profile-fetch.md`.
## Avatar Attachments
Attach 3D objects to a player's avatar:
```typescript
import {
engine,
Transform,
GltfContainer,
AvatarAttach,
AvatarAnchorPointType,
} from '@dcl/sdk/ecs'
const hat = engine.addEntity()
GltfContainer.create(hat, { src: 'models/hat.glb' })
Transform.create(hat, {})
// Attach to the local player's avatar
AvatarAttach.create(hat, {
anchorPointId: AvatarAnchorPointType.AAPT_NAME_TAG,
})
```
`AvatarAttach` requires the target player to be in the same scene — attachments disappear when the player leaves.
> **Before picking `AvatarAttach`, decide whether the item is cosmetic or aim-critical.** Bone anchors inherit avatar skeleton animation (idle bob, walk cycle, gesture) — great for hats/backpacks/halos, **bad** for held weapons, aiming reticles, or anything where relative position must stay stable. See **Held items vs cosmetic items** below.
### Held items vs cosmetic items — `AvatarAttach` vs parenting to `engine.CameraEntity` / `engine.PlayerEntity`
SDK7 gives you three distinct mechanisms for "an entity that follows the player". They are not interchangeable — picking the wrong one is the single most common mistake when porting "held item" patterns from SDK6, and the most common subtle failure is parenting an aim-sensitive item (gun, reticle, flashlight) to `engine.PlayerEntity` and discovering the item does not track camera pitch when the player looks up or down.
**Default for any aim-sensitive held item: `Transform.parent = engine.CameraEntity`.** Use `engine.PlayerEntity` only when you specifically want yaw-only / no-pitch behavior (a body-fixed item the player carries but never aims with).
| Goal | Use | Tracks | Reason |
|------|-----|--------|--------|
| **Aim-sensitive held item** — gun, aiming reticle, flashlight, anything pointed by looking around. **Recommended default for held gameplay items.** | `Transform.parent = engine.CameraEntity` (plus local position offset for "in front of and below" the camera) | Camera yaw **+ pitch** | Follows the camera's full transform, so the item points where the player is looking — including up/down. This is the SDK7 analogue of SDK6's `Attachable.FIRST_PERSON_CAMERA`. Aim stable (no animation jitter). |
| **Yaw-only / body-fixed item** — a held shield the player doesn't aim, a static torch, a fixed-position carry item that should stay level regardless of where the player looks. | `Transform.parent = engine.PlayerEntity` (plus local offset for hand-height / forward distance) | Player root: feet position + body **yaw only** (no pitch) | Follows the player's root transform. Stable (no animation), but stays level when the player looks up/down — wrong default for guns/aim items, correct for items meant to ride the body orientation only. |
| **Cosmetic item** — hat, halo, backpack, name plate, glow effect, torch visible to other players riding the avatar. | `AvatarAttach` with an `anchorPointId` (e.g. `AAPT_HEAD`, `AAPT_SPINE`, `AAPT_LEFT_HAND`) | The actual animated bone | Item moves naturally with idle bob, walk cycle, and gestures — visually correct for cosmetics attached to the body. **Not for aim** — animation jitter makes aim-sensitive items unusable. |
**Why `engine.CameraEntity` is the right default for aim-sensitive items:** `engine.PlayerEntity` only tracks the player's root (foot position + body yaw). Body yaw is NOT camera pitch — when the player tilts the camera up to aim at a flying target, the player root rotation does not change, so a gun parented to `PlayerEntity` stays flat and the muzzle doesn't track the look direction. Parenting to `engine.CameraEntity` inherits both yaw and pitch, so the gun aims where the camera looks. This matches the SDK6 `Attachable.FIRST_PERSON_CAMERA` behavior creators expect when porting.
**Why bone anchors break aim:** anchor points like `AAPT_RIGHT_HAND`, `AAPT_SPINE`, `AAPT_HEAD` are positions on the animated avatar skeleton. Every frame the engine pulls the bone's current world transform — which includes the procedural idle bob and any active animation clip. An entity parented there inherits all of that motion. For a weapon, this reads as jitter and makes aiming feel uncontrollable.
#### Example — gun held in first person (aim follows camera pitch)
```typescript
import { engine, Transform, GltfContainer, CameraModeArea, CameraType } from '@dcl/sdk/ecs'
import { Vector3, Quaternion } from '@dcl/sdk/math'
const gun = engine.addEntity()
GltfContainer.create(gun, { src: 'assets/Models/blaster.glb' })
Transform.create(gun, {
parent: engine.CameraEntity, // gun follows camera (yaw + pitch) — aim tracks where you look
position: Vector3.create(0.25, -0.2, 0.5), // right, down, forward of camera
rotation: Quaternion.fromEulerDegrees(0, 0, 0),
scale: Vector3.One(),
})
```
If the user does NOT want the item to track pitch (e.g. a held torch that should stay level, not point up when looking up), swap `engine.CameraEntity` for `engine.PlayerEntity`. **Do not pick `PlayerEntity` for a gun** — the result is a flat-pointing weapon that ignores look direction.
**Pair with `CameraModeArea` or a forced camera mode when equipping a held gun**, so the player is in first-person while aiming. See [[camera-control]].
#### Anti-pattern (what NOT to do for a held weapon)
```typescript
// WRONG — gun jitters with every idle/walk/gesture animation frame
AvatarAttach.create(gun, {
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND,
})
```
This **looks** like the right SDK7 way to "put a gun in the avatar's hand" because the API name reads that way — but the hand bone is animated, so the gun is unaimable in practice. Use parenting instead.
```typescript
// SUBTLY WRONG for a gun — looks correct in hip-fire, fails the moment the player aims up
Transform.create(gun, { parent: engine.PlayerEntity, position: ... })
```
`engine.PlayerEntity` inherits body yaw but NOT camera pitch. The gun stays flat when the player looks up to aim at a high target. Use `engine.CameraEntity` instead for any aim-sensitive item. `PlayerEntity` is correct only for body-fixed items that should stay level regardless of where the camera points (e.g. a carried lantern, a non-aimed shield).
#### SDK6 porting note
In SDK7 you have a choice that didn't exist in SDK6. SDK6's `Attachable.FIRST_PERSON_CAMERA` / `Attachable.AVATAR` mapped to coarse follow modes only. If you are porting a held item from an SDK6 scene that used `Attachable.FIRST_PERSON_CAMERA`, the SDK7 equivalent is **parenting to `engine.CameraEntity`** (NOT `AvatarAttach` to a hand anchor, and NOT `engine.PlayerEntity` — `PlayerEntity` loses camera pitch). See [[migrate-sdk6-to-sdk7]].
### Anchor Points
```typescript
AvatarAnchorPointType.AAPT_NAME_TAG // Above the head
AvatarAnchorPointType.AAPT_RIGHT_HAND // Right hand
AvatarAnchorPointType.AAPT_LEFT_HAND // Left hand
AvatarAnchorPointType.AAPT_POSITION // [DEPRECATED] Avatar root position — protocol recommends parenting to `engine.PlayerEntity` (body-fixed) or `engine.CameraEntity` (aim-sensitive) instead
AvatarAnchorPointType.AAPT_HEAD
AvatarAnchorPointType.AAPT_NECK
AvatarAnchorPointType.AAPT_SPINE
AvatarAnchorPointType.AAPT_SPINE1
AvatarAnchorPointType.AAPT_SPINE2
AvatarAnchorPointType.AAPT_HIP
AvatarAnchorPointType.AAPT_LEFT_SHOULDER
AvatarAnchorPointType.AAPT_LEFT_ARM
AvatarAnchorPointType.AAPT_LEFT_FOREARM
AvatarAnchorPointType.AAPT_LEFT_HAND_INDEX
AvatarAnchorPointType.AAPT_RIGHT_SHOULDER
AvatarAnchorPointType.AAPT_RIGHT_ARM
AvatarAnchorPointType.AAPT_RIGHT_FOREARM
AvatarAnchorPointType.AAPT_RIGHT_HAND_INDEX
AvatarAnchorPointType.AAPT_LEFT_UP_LEG
AvatarAnchorPointType.AAPT_LEFT_LEG
AvatarAnchorPointType.AAPT_LEFT_FOOT
AvatarAnchorPointType.AAPT_LEFT_TOE_BASE
AvatarAnchorPointType.AAPT_RIGHT_UP_LEG
AvatarAnchorPointType.AAPT_RIGHT_LEG
AvatarAnchorPointType.AAPT_RIGHT_FOOT
AvatarAnchorPointType.AAPT_RIGHT_TOE_BASE
AvatarAnchorPointType.AAPT_NAME_TAG
```
**Anchor points inherit bone animation.** Bone-targeted anchors (`AAPT_RIGHT_HAND`, `AAPT_SPINE`, `AAPT_HEAD`, etc.) follow the **animated** skeleton — idle bob, walk cycle, and gesture animations all propagate to the attached entity. This is correct for cosmetic items (hats, halos, backpacks) and **wrong** for gameplay items where aim stability matters (guns, reticles). For aim-sensitive items parent to `engine.CameraEntity` (yaw + pitch — the default for guns/reticles/flashlights); for yaw-only body-fixed items parent to `engine.PlayerEntity`. See "Held items vs cosmetic items" above.
### Attach to a Specific Player
```typescript
AvatarAttach.create(hat, {
avatarId: '0x123...abc', // Target player's wallet address
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND,
})
```
`avatarId` is the target player's wallet address. To attach to every player in the scene (including remote ones), iterate `engine.getEntitiesWith(PlayerIdentityData)` and read `player.address` for each — guard with a marker component so you attach only once per player:
```typescript
import { PlayerIdentityData } from '@dcl/sdk/ecs'
engine.addSystem(() => {
for (const [entity, player] of engine.getEntitiesWith(PlayerIdentityData)) {
// player.address is the wallet address to pass as avatarId
}
})
```
To attach the **local** player's own held item, get the address from `getPlayer()` / `await getPlayer()` (`.userId`). Omitting `avatarId` attaches to the local player. For multiplayer visibility of a held/attached item, sync the anchor entity's `AvatarAttach` component (see [[multiplayer-sync]]).
## Triggering Emotes
### Default Emotes
```typescript
import { triggerEmote } from '~system/RestrictedActions'
// Play a built-in emote
triggerEmote({ predefinedEmote: 'robot' })
triggerEmote({ predefinedEmote: 'wave' })
triggerEmote({ predefinedEmote: 'clap' })
```
### Custom Scene Emotes
> ⚠️ **CRITICAL FILE NAMING REQUIREMENT:** The emote `.glb` file **MUST** end with `_emote.glb` (case-insensitive). This is **not** optional and **not** just a convention — the runtime rejects files that don't match this suffix.
>
> **Why this matters:** Scenes with incorrectly named emote files often **work fine in `npm run start` preview** but **silently fail in production** once deployed. Preview is more permissive; the deployed runtime is strict. Always rename the file on disk (e.g. `SnowballThrow.glb` → `SnowballThrow_emote.glb`) before deploying.
>
> Valid: `wave_emote.glb`, `Snowball_Throw_emote.glb`, `dance_EMOTE.GLB`
> Invalid: `wave.glb`, `emote_wave.glb`, `wave_emote_v2.glb`
```typescript
import { triggerSceneEmote } from '~system/RestrictedActions'
// File MUST end with _emote.glb — rename it on disk if it doesn't
triggerSceneEmote({
src: 'animations/Snowball_Throw_emote.glb',
loop: false,
})
```
**Notes:**
- Emotes play only while the player is standing still — walking or jumping interrupts them
- If you don't want a player to interrupt an emote, use the `InputModifier` component to freeze the player for the duration of the emote
- Both `triggerEmote` and `triggerSceneEmote` require the scene to declare the `ALLOW_TO_TRIGGER_AVATAR_EMOTE` permission in `scene.json` `requiredPermissions`.
- Both accept an optional `mask` (upper-body-only animation) — see "Emote masks" below.
### Stopping an emote
`stopEmote({})` from `~system/RestrictedActions` stops the local player's currently playing emote (built-in or scene emote). Useful to end a looping scene emote (`triggerSceneEmote({ src, loop: true })`) on demand — e.g. a "pick up / put down" toggle.
```typescript
import { stopEmote } from '~system/RestrictedActions'
stopEmote({})
```
### Detecting when an emote finishes
Every emote lifecycle event is appended to the `AvatarEmoteCommand` grow-only set on the player entity, with a `state` field (`EmoteState` enum). Works for scene-triggered emotes (`triggerEmote`/`triggerSceneEmote`), emotes the player plays via the emote wheel, AND other players' emotes (pass their entity instead of `engine.PlayerEntity`).
```typescript
import { AvatarEmoteCommand, EmoteState } from '@dcl/sdk/ecs'
AvatarEmoteCommand.onChange(engine.PlayerEntity, (cmd) => {
if (!cmd) return
switch (cmd.state ?? EmoteState.ES_STARTED) {
case EmoteState.ES_STARTED: // emote started (also the value when `state` is absent — older clients)
break
case EmoteState.ES_FINISHED: // non-looping emote played to its natural end
break
case EmoteState.ES_INTERRUPTED: // cut short: movement/jump, teleport, another emote, stopEmote(), or scene exit
break
}
})
```
- Always default absent `state` to `ES_STARTED` (`cmd.state ?? EmoteState.ES_STARTED`) — entries from older clients omit the field, and older clients never send FINISHED/INTERRUPTED at all, so don't hard-block gameplay on a finish signal without a fallback.
- **Masked (partial-body) emotes on the local player report no lifecycle events** — known limitation.
- Requires a DCL 2.0 desktop client with playback-completion support.
- Verified against protocol `avatar_emote_command.proto` (commit `215d09c`, field 5, optional) and js-sdk-toolchain (commit `f858f905`).
### Emote masks (upper-body only)
Full-body emotes are interrupted when the player walks or jumps — the default locomotion animations take over. `AvatarMask.AM_UPPER_BODY` limits the animation to the waist up, leaving the legs controlled by locomotion. This means the player can keep walking, running, and jumping while the upper body plays your animation. Use cases: carrying a crate, holding a torch, juggling, cheering while running.
`triggerEmote` and `triggerSceneEmote` both accept an optional `mask` (enum `AvatarMask`, imported from `@dcl/sdk/ecs`) that limits which bones the animation drives.
```typescript
import { AvatarMask } from '@dcl/sdk/ecs'
import { triggerSceneEmote } from '~system/RestrictedActions'
triggerSceneEmote({ src: 'animations/Carry_emote.glb', loop: true, mask: AvatarMask.AM_UPPER_BODY })
```
- Only value: `AvatarMask.AM_UPPER_BODY` (= 0). Omitting `mask` plays the full-body animation (the default) — there is no `AM_FULL_BODY` value in the enum.
- `mask` applies to `triggerEmote` and `triggerSceneEmote` only. `stopEmote({})` takes no arguments (`StopEmoteRequest` is empty).
- **Loop + mask interaction:** `loop: false` with `mask: AM_UPPER_BODY` plays the upper-body animation exactly once, then returns the upper body to locomotion. `loop: true` with the mask repeats until `stopEmote({})` is called. The loop flag is respected regardless of the mask. Verified against sdk7-test-scenes `88,-13-avatar-masks` and `80,-1-scene-emotes` (commit `1c0f394`).
- **Mobile support:** Avatar Masks (upper-body-only emotes) ship on mobile in **v1.13.0 (September 2026)**. Until then the mobile renderer plays masked emotes as full-body. Verified against docs commit `09c5818`.
- Verified against protocol `restricted_actions.proto` / `common/avatar_mask.proto` (pinned in `@dcl/sdk` via protocol `0010e70`) and sdk7-test-scenes `88,-13-avatar-masks` (2026-07-16). Earlier speculative names `AvatarEmoteMask` / `AEM_UPPER_BODY` / `AEM_FULL_BODY` were never released — do not use them.
## NPC Avatars
For creating NPCs (characters, shopkeepers, guards, etc.), see the **npcs** skill. It covers both the NPC Toolkit library (GLB-based, with dialogue and movement) and `AvatarShape`-based avatar NPCs.
## Avatar Modifier Areas
Modify how avatars appear or behave in a region.
```typescript
import {
engine,
Transform,
AvatarModifierArea,
AvatarModifierType,
} from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const modifierArea = engine.addEntity()
Transform.create(modifierArea, {
position: Vector3.create(8, 1.5, 8),
scale: Vector3.create(4, 3, 4),
})
AvatarModifierArea.create(modifierArea, {
area: Vector3.create(4, 3, 4),
modifiers: [AvatarModifierType.AMT_HIDE_AVATARS],
excludeIds: ['0x123...abc'], // Optional: exclude specific players
})
```
### Available Modifiers
```typescript
AvatarModifierType.AMT_HIDE_AVATARS // Hide all avatars in the area
AvatarModifierType.AMT_DISABLE_PASSPORTS // Disable clicking on avatars to see profiles
AvatarModifierType.AMT_HIDE_NAMETAGS // Hide the name tag above avatars in the area
```
`modifiers` is an array — combine several, e.g. `[AMT_HIDE_NAMETAGS, AMT_DISABLE_PASSPORTS]`. The `AvatarModifierArea` component takes both an `area: Vector3` field (the region size) AND the entity's `Transform.scale`; set both to the same size. `excludeIds` is an array of wallet addresses that stay unaffected (e.g. keep the scene owner visible); mutate it at runtime via `AvatarModifierArea.getMutable(entity).excludeIds = [...]`.
`AMT_HIDE_AVATARS` hides both avatars AND nametags — do not combine it with `AMT_HIDE_NAMETAGS` (redundant). Use `AMT_HIDE_NAMETAGS` only when you want nametags hidden while keeping avatars visible (e.g. stages, presentations, clean visual experiences). `AMT_HIDE_NAMETAGS` is combinable with `AMT_DISABLE_PASSPORTS`.
**Nametag hiding is head/torso based:** the nametag is hidden only while the player's head or torso is inside the area. If the area is too short, a player who double-jumps above it will have their nametag briefly reappear. Make the area tall enough to cover the expected range of movement.
**Creator Hub / Inspector support:** the Creator Hub now has a dedicated inspector panel for `AvatarModifierArea` with a multi-select dropdown for modifiers (`Hide Avatars`, `Disable Passports`) and a wallet-address list editor for `excludeIds`. A "Avatar Modifier Area" smart item (utils category, translucent placeholder cube) is available in the asset catalog. The editor keeps the `area` field invisibly in sync with the entity's `Transform.scale` (the runtime reads `area`, not `scale`, for the region size), so resizing the entity via the gizmo automatically updates the modifier region. Note: the inspector panel exposes only `AMT_HIDE_AVATARS` and `AMT_DISABLE_PASSPORTS` in its dropdown; `AMT_HIDE_NAMETAGS` is SDK-only for now. Verified against creator-hub commit `a843390a`.
## Avatar Locomotion Settings
Adjust the player's movement speed and jump height:
```typescript
import { engine, AvatarLocomotionSettings } from '@dcl/sdk/ecs'
// Modify run speed and jump height (set only the fields you want to change)
AvatarLocomotionSettings.createOrReplace(engine.PlayerEntity, {
runSpeed: 14, // default is 10
jumpHeight: 3, // default is 1
})
```
Fields (all `float`, optional) with client defaults — verified against unity-explorer `origin/main` `CharacterControllerSettings.asset`: `walkSpeed` (1.5), `jogSpeed` (8, the default movement speed), `runSpeed` (10), `jumpHeight` (1), `runJumpHeight` (1.5), `doubleJumpHeight` (2), `glidingSpeed` (6), `glidingFallingSpeed` (1), `hardLandingCooldown` (0.75s). See `references/avatar-apis.md`.
`glidingFallingSpeed` is a **max descent cap** — it limits how fast the player falls while gliding, but does not limit upward motion. While gliding, continuous scene forces are 1.5× stronger and can lift the player; see the `player-physics` skill ("Forces while gliding").
## Restrict Locomotion (InputModifier)
Use `InputModifier` on `engine.PlayerEntity` to freeze or selectively restrict the player's movement — useful for cutscenes, locked interactions, or controlled game mechanics.
```typescript
import { InputModifier, engine } from '@dcl/sdk/ecs'
// Freeze all movement
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true }),
})
// Remove restrictions
InputModifier.deleteFrom(engine.PlayerEntity)
```
**Behavior when frozen:** gravity and external forces still apply, camera rotation stays available, global input events are still detectable, restrictions lift automatically when the player leaves scene bounds.
**Standard-mode flags** (all boolean, on `InputModifier.Mode.Standard({...})`): `disableAll`, `disableWalk`, `disableJog`, `disableRun`, `disableJump`, `disableEmote`. Protocol also defines `disableDoubleJump` and `disableGliding`. Note `disableJog` is separate from `disableWalk`/`disableRun` — jog is the default movement speed, so disabling only walk+run still lets the player jog.
The `mode` can be built two equivalent ways — the `InputModifier.Mode.Standard({...})` helper, or the raw discriminated union `{ $case: 'standard', standard: {...} }`.
**Tip:** Combine with `triggerSceneEmote` — freeze the player during an animation, then remove InputModifier when it ends.
For the cutscene pattern, see the **advanced-input** skill.
## Teleporting the Player
**`movePlayerTo` from `~system/RestrictedActions` is the only way to relocate the player to a position.** Setting `Transform.getMutable(engine.PlayerEntity).position` does NOT work (see the read-only warning at the top of this file). For sustained forces (lift, knockback, push, wind), use the `player-physics` skill instead — `movePlayerTo` is for explicit teleports/slides, not for forces.
`movePlayerTo` accepts:
- `newRelativePosition` — where to move the player (scene-relative `Vector3`)
- `cameraTarget` _(optional)_ — a point in space for the camera to face after moving
- `avatarTarget` _(optional)_ — a point in space for the avatar to face after moving
- `duration` _(optional)_ — transition time in seconds; if provided, movement can be awaited
**Constraints:**
- The player must already be inside the scene's bounds for this to work
- The target position must also be within the scene's bounds
- During the transition the avatar passes through colliders (verified: a `CL_PHYSICS` obstacle placed in the path is passed through)
- Requires the `ALLOW_TO_MOVE_PLAYER_INSIDE_SCENE` permission in `scene.json` `requiredPermissions`
- All fields except `newRelativePosition` are optional — `cameraTarget` and `avatarTarget` may each be omitted or used independently
- The target Y may be elevated (e.g. `y: 12`) to place the player on a raised platform, not just ground level
### Instant teleport
```typescript
import { movePlayerTo } from '~system/RestrictedActions'
void movePlayerTo({
newRelativePosition: Vector3.create(8, 0, 8),
cameraTarget: Vector3.create(8, 1, 12),
avatarTarget: Vector3.create(8, 1, 12),
})
```
### Smooth transition with duration
When `duration` is set, `movePlayerTo` is awaitable. The resolved value has a `success` boolean — `false` if the player interrupted the movement with input.
```typescript
import { movePlayerTo } from '~system/RestrictedActions'
async function teleport() {
const result = await movePlayerTo({
newRelativePosition: Vector3.create(1, 0, 1),
cameraTarget: Vector3.create(8, 1, 8),
duration: 2,
})
if (!result.success) {
console.log('Movement was interrupted by the player')
}
}
```
### Prevent the player from interrupting a transition
Combine `InputModifier` with `movePlayerTo` to lock movement for the duration:
```typescript
import { movePlayerTo } from '~system/RestrictedActions'
import { InputModifier, engine } from '@dcl/sdk/ecs'
async function lockedTeleport() {
InputModifier.create(engine.PlayerEntity, {
mode: InputModifier.Mode.Standard({ disableAll: true }),
})
await movePlayerTo({
newRelativePosition: Vector3.create(1, 0, 1),
cameraTarget: Vector3.create(8, 1, 8),
duration: 2,
})
InputModifier.deleteFrom(engine.PlayerEntity)
}
```
### Avatar Change Listeners
React to avatar changes in real-time:
```typescript
import {
AvatarEmoteCommand,
EmoteState,
AvatarBase,
AvatarEquippedData,
} from '@dcl/sdk/ecs'
// Detect when the Explorer reports an emote playing on a player.
// AvatarEmoteCommand is written BY THE EXPLORER to report emote playback
// TO the scene -- it is NOT a signal from scene to renderer. It is appended
// to every player entity (local and remote alike).
// Each entry carries an optional `state` field (EmoteState enum) — see
// "Detecting when an emote finishes" above.
AvatarEmoteCommand.onChange(engine.PlayerEntity, (cmd) => {
if (cmd) console.log('Emote:', cmd.emoteUrn, 'state:', cmd.state ?? EmoteState.ES_STARTED)
})
// Detect avatar appearance changes (wearables, skin color, etc.)
AvatarBase.onChange(engine.PlayerEntity, (base) => {
if (base) console.log('Avatar name:', base.name)
})
// Detect equipment changes
AvatarEquippedData.onChange(engine.PlayerEntity, (equipped) => {
if (equipped) console.log('Wearables changed:', equipped.wearableUrns)
})
```
### Additional Anchor Points
Beyond the commonly used anchor points, the full list includes:
- `AvatarAnchorPointType.AAPT_POSITION` — avatar feet position
- `AvatarAnchorPointType.AAPT_NAME_TAG` — above the name tag
- `AvatarAnchorPointType.AAPT_LEFT_HAND` / `AAPT_RIGHT_HAND`
- `AvatarAnchorPointType.AAPT_HEAD` — head bone
- `AvatarAnchorPointType.AAPT_NECK` — neck bone
> **Need to check the player's wallet before showing avatar items?** See the **nft-blockchain** skill for wallet checks with `getPlayer()` and `isGuest`.
## Example scenes
Engine-team test scenes (exercised against the real engine):
- [100,102-avatar-attach-test](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/100,102-avatar-attach-test) — `AvatarAttach` on multiple anchor points; enumerates every player via `PlayerIdentityData` and attaches to `player.address`; a follower entity reconstructs the attached world position from `PlayerEntity` + attached Transform.
- [80,-1-scene-emotes](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/80,-1-scene-emotes)
- [4,23-emote-finish](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/4,23-emote-finish) — emote **completion** detection: `AvatarEmoteCommand.onChange(engine.PlayerEntity, ...)` logging every appended entry as `STARTED` / `FINISHED` / `INTERRUPTED`, with absent `state` defaulting to `ES_STARTED` for older explorers. Play an emote out fully to see `FINISHED`; walk away mid-playback to see `INTERRUPTED`. Covers both `triggerEmote` (predefined) and a non-looping `triggerSceneEmote`. — `triggerEmote`, `triggerSceneEmote` (with a deliberately mis-named non-`_emote.glb` file shown NOT playing), `stopEmote`, `mask: AvatarMask.AM_UPPER_BODY`, plus `loop: false` + mask (plays once, returns to locomotion) and `loop: true` + mask (repeats until stopped).
- [11,0-move-player-to-duration](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/11,0-move-player-to-duration) — `movePlayerTo` with `duration`, reading `result.success` via `.then()`, `InputModifier` locking input during the slide, and a `CL_PHYSICS` obstacle the avatar passes through mid-transition.
- [9,99-modifier-areas](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/9,99-modifier-areas) — `AvatarModifierArea` (`AMT_HIDE_AVATARS`) with runtime-mutated `excludeIds`, alongside `CameraModeArea`.
- [10,99-avatar-modifier-hide-nametags](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/10,99-avatar-modifier-hide-nametags) — `AvatarModifierArea` with `AMT_HIDE_NAMETAGS`: hides nametags while keeping avatars visible.
- [0,1-input-modifier](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,1-input-modifier) — `InputModifier` toggling every Standard flag (`disableAll/Walk/Jog/Run/Jump/Emote`), both via the helper and the raw `$case` form.
- [80,-4-restricted-actions](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/80,-4-restricted-actions) — `movePlayerTo` (incl. elevated `y`, `avatarTarget`-only turns), `triggerEmote`, `triggerSceneEmote`, `teleportTo`, `openExternalUrl`.
- [88,-13-avatar-masks](https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/88,-13-avatar-masks) — emote masks: looping `AvatarMask.AM_UPPER_BODY` scene emote + `AvatarAttach` anchor to hold a synced crate, `stopEmote` to release. Also includes `loop: false` + mask pair (plays once then returns upper body to locomotion) and `loop: true` + mask pair (repeats until stopped) for verifying the masked-emote loop flag is respected.
For component field details, see `{baseDir}/../sdk-scenes/references/components-reference.md`.
For anchor points, emote names, and event callbacks, see `{baseDir}/references/avatar-apis.md`.