Skip to content

fix: masked emote looping#9081

Open
nicoecheza wants to merge 2 commits into
devfrom
fix/masked-emote-loop
Open

fix: masked emote looping#9081
nicoecheza wants to merge 2 commits into
devfrom
fix/masked-emote-loop

Conversation

@nicoecheza

Copy link
Copy Markdown
Member

No description provided.

@nicoecheza
nicoecheza requested review from a team as code owners June 26, 2026 18:57
@github-actions
github-actions Bot requested a review from DafGreco June 26, 2026 18:57
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@claude

This comment has been minimized.

Comment on lines +205 to +209
if (shouldPlay && masked.CurrentEmoteReference == null)
ReplayMaskedEmote(ref masked, ec);
{
if (emoteStorage.TryGetElement(masked.EmoteUrn.Shorten(), out IEmote emote) && emote.IsLooping())
ReplayMaskedEmote(ref masked, ec);
}

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.

The IsLooping() guard here triggers a second emoteStorage.TryGetElement call — ReplayMaskedEmote already does its own TryGetElement on line 216. Moving the looping check inside ReplayMaskedEmote would eliminate the redundant lookup and co-locate all replay preconditions in one place (alongside IsLoading, body-shape checks, etc.).

Suggested change
if (shouldPlay && masked.CurrentEmoteReference == null)
ReplayMaskedEmote(ref masked, ec);
{
if (emoteStorage.TryGetElement(masked.EmoteUrn.Shorten(), out IEmote emote) && emote.IsLooping())
ReplayMaskedEmote(ref masked, ec);
}
if (shouldPlay && masked.CurrentEmoteReference == null)
ReplayMaskedEmote(ref masked, ec);

And in ReplayMaskedEmote, add the looping guard right after the storage lookup:

private void ReplayMaskedEmote(ref CharacterMaskedEmoteComponent masked, CharacterEmoteComponent emoteComponent)
{
    if (!emoteStorage.TryGetElement(masked.EmoteUrn.Shorten(), out IEmote emote)) return;
    if (!emote.IsLooping()) return;   // <-- add here
    if (emote.IsLoading) return;
    ...

This keeps UpdateMaskedEmoteVisibility simple and ReplayMaskedEmote self-contained — consistent with how ConsumeMaskedEmoteIntent is structured.

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Claude reviewed this PR and found no blocking issues, but assessed it as complex — human DEV review is still required before merging.

@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Claude finished @nicoecheza's task in 1m 32s —— View job


PR Review: fix/masked-emote-loop

  • Load context and diff
  • Root-cause check
  • Design & integration review
  • Line-level review
  • Verdict

Step 2 — Root-cause check

Problem: Non-looping scene-triggered masked emotes were being replayed when the player re-entered the scene or when a full-body emote / glide ended. ReplayMaskedEmote had no guard against one-shot emotes.

Fix: This update (the update: review commit) moves the !emote.IsLooping() guard inside ReplayMaskedEmote (line 216), co-locating it with all other preconditions (IsLoading, bodyShape null-checks). This correctly implements the inline suggestion from the previous review pass. ✅


Step 3 — Design & integration

The guard placement is now correct. ReplayMaskedEmote is self-contained: it cannot be called and wrongly replay a one-shot emote regardless of how it is invoked. The call site (UpdateMaskedEmoteVisibility) remains clean with no guard at the call site. ✅


Step 5 — Line-level review

Non-blocking — isLooping is a constant inside ReplayMaskedEmote

SceneMaskedEmoteSystem.cs line 236: bool isLooping = emote.IsLooping();

Because the guard at line 216 already returned if !emote.IsLooping(), execution can only reach line 236 when IsLooping() is true. The variable isLooping is therefore always true, and both its uses (line 238 and line 245) reduce to constants. The variable is not wrong, just redundant.

Simplest fix — replace the redundant call with a const:

const bool isLooping = true;

Or inline true directly at lines 238 and 245 and remove the variable. Either eliminates the third IsLooping() call and makes the invariant explicit. This is non-blocking.


Step 6 — Complexity

COMPLEX — modifies a query method within an ECS BaseUnityLoopSystem in the avatar rendering pipeline.


Step 7 — QA assessment

QA_REQUIRED: YES — directly affects user-visible emote playback (whether a one-shot masked emote incorrectly replays on scene re-entry or after a full-body emote ends).


REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Modifies a query method in SceneMaskedEmoteSystem, an ECS BaseUnityLoopSystem in the avatar rendering pipeline.
QA_REQUIRED: YES


💰 Review cost: $0.10 · claude-sonnet-4-6


💰 Review cost: $0.26 · claude-sonnet-4-6 · 8 turns

if (!emoteStorage.TryGetElement(masked.EmoteUrn.Shorten(), out IEmote emote)) return;
if (emote.IsLoading) return;

if (!emote.IsLooping()) return;

@lorux0 lorux0 Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The masked emote system is supposed to keep playing it even if the avatar plays another full body emote. The loop state its already considered here:

if (!emotePlayer.PlayMasked(mainAsset, audioClip, isLooping, true, in avatarBase, ref masked))

So with this change, i guess that if the masked emote is not a loop emote, it will not end playing the animation if another action is made, either emote or glide

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If the attempt is to fix the non-stopping masked emotes from this scene, then i think the problem is in how it tries to stop the emote. Its not using stopEmote({}) as its used here: https://github.com/decentraland/sdk7-test-scenes/pull/59/changes#diff-8a277014b5464a07850d78daffc446f7e7bacbf56390348825a59f6901e7b39eR4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants