Scene-driven movement animations#723
Merged
Merged
Conversation
Route avatar movement animations through the movement scene via a nested MovementAnimation block on PbAvatarMovement, resolved through the existing emote loader pipeline. Adds an IpfsType::SceneContent variant so scene-emote URNs resolve relative to the scene's baseUrl rather than the realm content URL, fixing asset 404s for portables where scene content lives outside the realm. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Spawn an invisible wrapper entity between the avatar and the prop scene, so instantiated scene entities inherit Hidden the moment they appear. Flip the wrapper to Inherited on the same pass that marks name-matched props Hidden explicitly, avoiding the one-frame flash of _reference / _basemesh / m_mask_* children that should stay hidden. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When a triggered emote is the current active emote, a new non-idle MovementAnimation would be ignored because branch 1 of the animation select always wins while a matching EmoteCommand is still present. Add a scene_cancels arm in the same-URN cancel block so a non-idle scene-driven request clears requested_emote, allowing the movement scene to seize control. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Collapse the primary-player resource and foreign-player component into a single SceneDrivenAnim component used for both. Latch single-frame seeks in broadcast_position so they aren't dropped between 10Hz/1Hz send gates. Defer remote SceneDrivenAnim insertion until the interpolated position catches up with the target, so animation transitions line up with the visible motion rather than the packet arrival. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Precompute the velocity-based ActiveEmote in animate and stash it as `fallback` on scene-driven selections. When play_current_emote hits a permanent resolution failure (URN parse, missing gltf animation, Failed / Missing / NoRepresentation, or no _Avatar clip), swap to the fallback and retry once. Loading still skips the frame as before. In update_foreign_user_target_position, flush any unapplied scene_anim before overwriting PlayerTargetPosition so bursts of events (stalls or multi-event frames) don't silently drop one-shot seeks or intermediate transitions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When a foreign player's velocity becomes zero (e.g. landing from a jump) the STATIC_FREQ skip held back the next broadcast for up to a second, so remotes kept the outgoing jump clip visible until the keepalive fired. Bypass the skip when the active URN changed so transitions reach remotes immediately. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Avoids resending the ~50-char `urn:decentraland:off-chain:scene-emote:…-false` preamble on every broadcast. Receivers reconstruct the URN from the hash pair.
Route scene-published content-hash sound triggers through the broadcast into a per-frame avatar-bus play system. Adds a custom `.audio` AssetLoader that wraps kira's symphonia-backed static sound loader so audio content can be fetched by content hash without knowing the container format up front. Includes a retry-filter fix for the wasm audio path so AudioEmitters whose buffer isn't decoded on the spawn frame get a chance to start once the buffer lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bevy_animation::AnimationNodeActive::replay() resets `seek_time` to 0.0 (alongside clearing `completions` / `just_completed`), so calling it after `seek_to` wiped the requested position. The visible symptom was that any scene-driven MovementAnimation with a non-None `playbackTime` snapped the clip back to frame 0 every tick. Swap the order so replay clears the completion state first, then seek_to positions the cursor. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Upstream rfc4/comms.proto already assigns Movement tags 19–28 (head-sync, glide, jump_count, point-at) and MovementCompressed tags 3–4 (head_sync_data, point_at_data). Our local anim_* extensions were colliding with those, so a Movement from an upstream-compliant sender would have its head_ik_yaw_enabled bool decoded as our anim_scene_hash string (wire-type mismatch, garbage value). Renumber the local additions to Movement.29–35 and MovementCompressed.5–11, both well beyond any current upstream usage, and note the reservation in a comment so future additions skip the reserved range. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Routes avatar movement animation selection through the movement scene via a nested
MovementAnimationblock onPbAvatarMovement, resolved through the existing emote loader pipeline. The engine still picks from velocity heuristics when the scene hasn't supplied one.Protocol dependency: this PR depends on decentraland/protocol#392. Once that lands and the protocol-squad artifact is cut, we'll bump
dcl-protocolhere.Pipeline
MovementAnimationnested onPbAvatarMovement(scene → engine):src,loop,speed,idle, optionaltransition_seconds, optionalplayback_timeseek.AvatarAnimationStateonPbAvatarMovementInfo(engine → scenes): mirrors the currently-playing scene-driven clip plusplayback_time,duration,loop_count. Any scene can read it.SceneDrivenAnimcomponent covers both primary and foreign players.baseUrlvia a newIpfsType::SceneContent, fixing asset 404s for portables whose content lives outside the realm.Interaction with triggered emotes
idle: trueletstriggerSceneEmotetake over;idle: falsesuppresses emotes while the movement animation plays.AvatarAnimationState.playback_timefreezes while a triggered emote is overriding the clip.Remote sync
playback_timeseeks inbroadcast_positionso they aren't dropped between 10Hz/1Hz send gates.SceneDrivenAniminsertion on foreign avatars until the interpolated position catches up to the target, so the clip change visibly lines up with the motion rather than with the packet arrival.scene_animbefore overwriting the target position so bursts of events don't silently drop one-shot seeks or intermediate transitions.Resilience
ActiveEmoteand stash it as a fallback; on permanent URN resolution failure (parse/missing clip/Failed/Missing/NoRepresentation/no_Avatar) the engine swaps to the fallback and retries.Unrelated (bundled)
Hiddenthe moment they appear. Avoids a one-frame flash of_reference/_basemesh/m_mask_*children.🤖 Generated with Claude Code