Skip to content

hotfix: revert avatar shape and admin filtering#8385

Merged
m3taphysics merged 3 commits into
mainfrom
fix/rollback-avatar-masks
Apr 24, 2026
Merged

hotfix: revert avatar shape and admin filtering#8385
m3taphysics merged 3 commits into
mainfrom
fix/rollback-avatar-masks

Conversation

@Maurizio-dcl

@Maurizio-dcl Maurizio-dcl commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Pull Request Description

What does this PR change?

Hotfix aimed at stabilizing local scene development:

  • Revert feat: avatar masks (#7462) commit 755408034. Scene emotes go back to pre-feat: avatar masks #7462 behavior (full-body only, with the known emote flicker, which is addressed separately in this PR and not part of this PR).
  • Revert fix: admin tools filtering (#8154) commit 4b04f9edf. Rolls back the admin-tools filtering changes.
  • Partial rollback of fix: multiple LSD and asset load issues (#8293) commit 65a7f71b7.

This PR should expect these outcomes:

  1. the FIRST reload of a local scene should not freeze the avatar in place indefinitely and eventually load the scene (although with some assets missing and general degradation in the case of Genesis Plaza, this is a separate issue being investigated)
  2. Scene emotes should play with a flicker (existing issue) when running the scene locally in 88,-13-avatar-masks (or not play at all in central-plaza running locally), these are pre-existent issues.
  3. Scene emotes should play correctly both in live Genesis Plaza and in nebi.dcl.eth

Test Instructions

1. Local Scene Development (Genesis-Plaza-2025)

Test scene: https://github.com/decentraland-scenes/Genesis-Plaza-2025/tree/main/central-plaza

Run the scene locally and verify with this PR's build that:

  • The scene loads correctly, right now this scene is very demanding on memory (a separated issue about this is being investigated) so depending on how much RAM you have some assets might be missing.
  • Manual reload via the minimap reload button works: on THE FIRST reload, the avatar is briefly frozen during the reload and unfreezes on completion, and the chat shows 🟢 Current scene has been reloaded. No error message should appear in chat, from subsequent reloads onwards there might be assets missing and the scene might not load correctly, this is expected and it is heavily dependent on hardware right now.
  • Manual reload via the /reload chat command behaves identically.
  • Triggering scene emotes should behave like stated by Pravus in this thread (second box he plays) or not play at all, this is an existing issue with GP and with local scene development in general, this is expected.

2. Local Scene Development (88,-13-avatar-masks)

Test scene: https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/88%2C-13-avatar-masks

Run the scene locally and verify with this PR's build that:

  • Yellow cube emote and green cube emote both play the emotes correctly, they must behave the same as before the avatar-masks feature (flicker is known behavior, addressed separately as stated before) again as in this thread by Pravus (second box he plays).
  • Manual reload via the minimap reload button works: on THE FIRST reload, the avatar is briefly frozen during the reload and unfreezes on completion, and the chat shows 🟢 Current scene has been reloaded. No error message should appear in chat, and subsequent reloads must also succeed.
  • Manual reload via the /reload chat command behaves identically.
  • Hot reloads (editing the scene while connected) behaves identically (this flow does not show the 🟢 Current scene has been reloaded message in chat).

3. nebi.dcl.eth

  • Teleport to nebi.dcl.eth.
  • Verify the yellow cube emote and green cube emote both play correctly (no flicker here).
  • Trigger a manual scene reload and confirm it completes cleanly and chat shows 🟢 Current scene has been reloaded.
  • Subsequent reloads work as well

4. Genesis Plaza

  • Enter Genesis Plaza on the live realm.
  • Verify:
    • The scene loads correctly
    • Scene emotes play correctly e.g. watering plants, pigeons.
    • A manual scene reload reloads GP correctly, and 🟢 Current scene has been reloaded message appears in chat.
    • Subsequent reloads work as well

Quality Checklist

  • Changes have been tested locally (Genesis Plaza scene)
  • Changes have been tested locally (LSD test scene)
  • Tested on nebi.dcl.eth
  • Tested on Genesis Plaza on the live realm

Code Review Reference

Please review our Branch & PR Standards before submitting.

@Maurizio-dcl Maurizio-dcl self-assigned this Apr 22, 2026
@Maurizio-dcl Maurizio-dcl added the force-build Used to trigger a build on draft PR label Apr 22, 2026
@github-actions

github-actions Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown
Contributor

badge

New build in progress, come back later!

@davidejensen
davidejensen marked this pull request as ready for review April 22, 2026 20:21
@davidejensen
davidejensen requested review from a team as code owners April 22, 2026 20:21
@davidejensen davidejensen changed the title Revert "feat: avatar masks (#7462)" hotfix: revert avatar shape and admin filtering Apr 22, 2026
@Maurizio-dcl

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Maurizio-dcl's task in 7m 13s —— View job


PR Review

  • Gather context (CLAUDE.md, docs/README.md, PR diff)
  • Review avatar rendering / emote system changes
  • Review CRDT bridge / restricted actions changes
  • Review scene runner / ECS changes
  • Review GLTF / asset loading changes
  • Compile findings and post final review

Root-cause assessment

Problem: LSD (Local Scene Development) scene reloads permanently freeze the avatar. Root cause: The UnloadSceneSystem partial-rollback (65a7f71b) was removing DeleteEntityIntention unconditionally, which allowed cleanup queries to re-run on the scene entity during LSD reload, corrupting state. The fix correctly restricts DeleteEntityIntention removal to !localSceneDevelopment only — addressing the actual cause, not a symptom. The avatar-masks and admin-filtering reverts correctly remove the features that introduced cascading instability. PASS on root-cause logic.


Blocking Issues

1. Non-looping legacy emotes never clean up → Mecanim Animator permanently disabled (LSD mode)

Location: CharacterEmoteSystem.csCancelEmotes query, line 134
Problem: The if (!emoteReference.legacy) block completely skips completion detection for legacy animation clips. When a non-looping legacy emote finishes naturally (animationComp.isPlaying == false), no code path clears CurrentEmoteReference. Consequently:

  • IsPlayingLegacyEmote stays true (non-null reference with legacy=true)
  • DisableAnimatorWhenPlayingLegacyAnimations fires every frame, keeping AvatarAnimator.enabled = false
  • AvatarShapeVisibilitySystem.UpdateVisibilityState early-returns because LegacyAnimation.enabled == true appears consistent with shouldPlayFootstepFX

Net effect: after a non-looping scene emote plays to completion in LSD mode, the avatar's Mecanim Animator stays permanently disabled. Idle, walk, and run animations stop working. The avatar appears frozen until a teleport or scene reload.

Scope: LSD mode only, non-looping emotes only. Looping emotes are correctly cancelled by movement input (since IsPlayingEmote = animationComp.isPlaying is true while looping). The primary hotfix test cases (yellow/green cube emotes, which loop) are not affected by this bug, but it is a new regression introduced by this PR.

Fix: In CancelEmotes, add a completion branch for legacy emotes (see inline comment on CharacterEmoteSystem.cs:134). Alternatively, have StopEmote unconditionally re-enable AvatarAnimator after clearing the reference. Fix this →


Non-Blocking Warnings

⚠️ Security trade-off (intentional revert): Reverting #8154 removes all incoming CRDT filtering from remote peers in CommunicationsControllerAPIImplementation.OnMessageReceived. Previously, VideoPlayer components from non-admin/non-self sources were dropped. These messages now pass through without any filtering. This restores pre-#8154 behavior and is an explicit stated goal of this hotfix, but it is worth noting as a known regression in the security posture.

⚠️ LEGACY_ANIMATION_CLIPS static shared buffer (EmotePlayer.cs:14): A static readonly AnimationClip[2] is used as a scratch buffer in CreateNewEmoteReference. Safe today (main-thread only), but fragile if the factory is ever called off the main thread. See inline comment.

⚠️ propCLip parameter naming typo (EmoteReferences.cs:16): Capital 'L' in propCLip violates camelCase. See inline comment for a one-line fix.

⚠️ DisposableTexture.Dispose() is a no-op: Texture is never destroyed on disposal — a potential memory leak. The TODO comment acknowledges an open investigation. Pre-existing behavior, but now surfaced in a new class.

⚠️ Double blank line in GetSceneEmoteFromRealmIntention.cs after TryParseFromURN closing brace (line ~97). See inline comment.


Summary

The PR correctly reverts the two features and makes the targeted UnloadSceneSystem fix to address the LSD reload freeze. The logic is sound and the root cause is properly addressed. One new bug is introduced: non-looping legacy emotes in LSD mode leave the Mecanim Animator permanently disabled after natural completion. Given this is a hotfix and the primary test cases use looping emotes, the impact is narrow, but it is a real regression that should be addressed before or shortly after merge.


REVIEW_RESULT: FAIL ❌
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches avatar emote system, ECS systems and queries, CRDT bridge filtering, scene runner wiring, async UniTask patterns, plugin registration, and GLTF asset loading pipeline.
QA_REQUIRED: YES

@Maurizio-dcl Maurizio-dcl added force-build Used to trigger a build on draft PR and removed force-build Used to trigger a build on draft PR labels Apr 24, 2026
public AudioSource? audioSource;

public void Initialize(AnimationClip? animationClip, AnimationClip? propClip, Animator? animatorComp, int propClipHash)
public void Initialize(AnimationClip? animationClip, AnimationClip? propCLip, Animator? animatorComp, Animation? animationComp, int propClipHash, bool legacy)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter propCLip has an accidental capital 'L'. Per CLAUDE.md code standards, parameters must follow camelCase.

Suggested change
public void Initialize(AnimationClip? animationClip, AnimationClip? propCLip, Animator? animatorComp, Animation? animationComp, int propClipHash, bool legacy)
public void Initialize(AnimationClip? animationClip, AnimationClip? propClip, Animator? animatorComp, Animation? animationComp, int propClipHash, bool legacy)

}

if (!emoteComponent.IsPlayingEmote)
if (!emoteReference.legacy)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: non-looping legacy emotes never clean up — The if (!emoteReference.legacy) guard completely skips the completion-detection logic for legacy animations. When a non-looping legacy emote plays to its end naturally, animationComp.isPlaying becomes false (IsPlayingEmote returns false), but CurrentEmoteReference is never cleared: nothing sets emoteComponent.StopEmote = true, CancelEmotesByMovementInput short-circuits on !IsPlayingEmote, and DisableAnimatorWhenPlayingLegacyAnimations keeps firing because CurrentEmoteReference.legacy is still true. The net result is that the Mecanim AvatarAnimator stays permanently disabled until an explicit cancellation event (teleport, scene reload) occurs.

Fix — add a completion branch for legacy:

Suggested change
if (!emoteReference.legacy)
if (!emoteReference.legacy)
{
if (!emoteComponent.IsPlayingEmote)
{
avatarView.ResetAnimatorTrigger(AnimationHashes.EMOTE_STOP);
return;
}
int animatorCurrentStateTag = avatarView.GetAnimatorCurrentStateTag();
bool isOnAnotherTag = animatorCurrentStateTag != AnimationHashes.EMOTE && animatorCurrentStateTag != AnimationHashes.EMOTE_LOOP;
if (isOnAnotherTag)
StopEmote(ref emoteComponent, avatarView);
}
else if (!emoteReference.avatarClip!.isLooping && !emoteComponent.IsPlayingEmote)
{
// Non-looping legacy emote finished naturally — clean up so the Mecanim Animator is not left disabled.
StopEmote(ref emoteComponent, avatarView);
}

using DCL.AvatarRendering.AvatarShape.UnityInterface;
using DCL.ECSComponents;
using DCL.Optimization.Pools;
using System;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code smell: static shared mutable arrayLEGACY_ANIMATION_CLIPS is a static array used as a scratch buffer inside CreateNewEmoteReference. If a future refactor moves this off the main thread (or the pool factory is called from a background thread), two simultaneous calls would corrupt each other. Consider allocating a local AnimationClip[2] inside the method instead; it is tiny and short-lived. Fix this →


return !string.IsNullOrEmpty(sceneId) && !string.IsNullOrEmpty(emoteHash);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double blank line — should be a single blank line per code style. Fix this →

@DafGreco DafGreco left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️ PR reviewed and approved by QA on both platforms following instructions playing both happy and un-happy path

Regressions for this ticket had been performed in order to verify that the normal flow is working as expected:

  • [✔️ ] Backpack and wearables in world
  • [✔️ ] Emotes in world and in backpack
  • [✔️ ] Teleport with map/coordinates/Jump In
  • [✔️ ] Chat and multiplayer
  • [✔️ ] Profile card
  • [✔️ ] Camera
  • [✔️ ] Skybox
  • [ ✔️] Settings

This PR has been tested with @Maurizio-dcl in both platforms and we are ready to ship this one 🚀
Evidence on the following thread

@anicalbano
anicalbano enabled auto-merge April 24, 2026 11:01
@m3taphysics
m3taphysics disabled auto-merge April 24, 2026 11:59
@m3taphysics
m3taphysics merged commit 555b273 into main Apr 24, 2026
46 of 47 checks passed
@m3taphysics
m3taphysics deleted the fix/rollback-avatar-masks branch April 24, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

force-build Used to trigger a build on draft PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants