Skip to content

fix: maintain LocalPlayer-related data on all scenes - #8837

Merged
mikhail-dcl merged 10 commits into
devfrom
feat/seed-local-player-crdt-on-scene-init
Jun 10, 2026
Merged

fix: maintain LocalPlayer-related data on all scenes#8837
mikhail-dcl merged 10 commits into
devfrom
feat/seed-local-player-crdt-on-scene-init

Conversation

@mikhail-dcl

@mikhail-dcl mikhail-dcl commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Fixes decentraland/creator-hub#264

Problem

SDKProfile is propagated to the scene with 1 frame delay, making the following code fail onStart:

      const playerData = PlayerIdentityData.getOrNull(userEntity)
      const avatarData = AvatarBase.getOrNull(userEntity)
      const wearablesData = AvatarEquippedData.getOrNull(userEntity)

Solution

Make the whole set of Avatar-related components available on the systems' Initialize which is called before the scen starts. It's right the same flow seen in WriteMainPlayerTransformSystem

Sensitive changes

  • User-related data now will be available for the scene even if it's not current
  • To make it symmetric if the user profile is updated the corresponding data will be pushed to the scene as well (even if it's not current)
  • The grey zone existed before: TransformComponent was available for the player entity right away whereas PlayerIdentityData, AvatarBase and AvatarEquippedData were not available until the player steps on the current scene

P.S. Behavior for remote peers has not changed.

How to QA

import { getPlayer } from '@dcl/sdk/src/players'

export function main() {

	let myPlayer = getPlayer()

	if (myPlayer) {
		console.log('Name : ', myPlayer.name)
		console.log('UserId : ', myPlayer.userId)
	}
}
  • Execute the scene with the given script, you should see data properly logged.
  • Perform regression on the scenes which are relying on the players' data

…e lifecycle

- Local Player is propagated on Initialize from the scene world so it's available on `onStart`
- Local Player data updated in every scene even if it's not current
@mikhail-dcl
mikhail-dcl requested review from a team as code owners May 20, 2026 17:55
@github-actions
github-actions Bot requested a review from anicalbano May 20, 2026 17:55
@github-actions

github-actions Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

@claude

This comment has been minimized.

@m3taphysics

Copy link
Copy Markdown
Contributor

PR #8837, run #26182654059

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2700 2700
CPU average 33.3 ms 33.3 ms -0.0 ms 0.0%
CPU 1% worst 33.9 ms 34.0 ms -0.1 ms 0.2%
CPU 0.1% worst 38.2 ms 37.2 ms 1.0 ms -2.6%
GPU average 7.8 ms 7.9 ms -0.1 ms 1.4%
GPU 1% worst 19.2 ms 18.6 ms 0.6 ms -3.4% 🔴
GPU 0.1% worst 25.4 ms 23.5 ms 1.9 ms -8.1% 🔴

@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

This comment has been minimized.

@mikhail-dcl mikhail-dcl changed the title Fix: Maintain LocalPlayer-related data on all scenes fix: Maintain LocalPlayer-related data on all scenes May 21, 2026
mikhail-dcl and others added 2 commits May 21, 2026 12:43
Update tests to expect player assignment during SceneState.Starting

The system now assigns players to scenes in the Starting state,
so tests asserting the opposite are updated to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@
…cene-init' into feat/seed-local-player-crdt-on-scene-init
@claude

This comment has been minimized.

@m3taphysics

Copy link
Copy Markdown
Contributor

PR #8837, run #26220267637

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2699 2700
CPU average 33.3 ms 33.3 ms 0.0 ms -0.0%
CPU 1% worst 34.0 ms 33.7 ms 0.3 ms -0.8%
CPU 0.1% worst 36.9 ms 36.0 ms 0.9 ms -2.6%
GPU average 7.9 ms 7.7 ms 0.1 ms -1.8%
GPU 1% worst 19.2 ms 20.3 ms -1.2 ms 5.8% 🟢
GPU 0.1% worst 25.2 ms 24.5 ms 0.7 ms -3.0% 🔴

@pravusjif pravusjif moved this from Todo to With QA / Awaiting Review in Explorer Alpha May 21, 2026
@pravusjif pravusjif added the enhancement Enhancement of an existing feature label May 21, 2026
@claude

This comment has been minimized.

@github-project-automation github-project-automation Bot moved this from With QA / Awaiting Review to In Progress in Explorer Alpha May 21, 2026
@m3taphysics

Copy link
Copy Markdown
Contributor

PR #8837, run #26226730238

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2700 2699
CPU average 33.3 ms 33.3 ms -0.0 ms 0.0%
CPU 1% worst 33.5 ms 33.9 ms -0.4 ms 1.3%
CPU 0.1% worst 33.8 ms 37.6 ms -3.8 ms 10.2% 🟢
GPU average 7.6 ms 7.9 ms -0.3 ms 3.8% 🟢
GPU 1% worst 19.2 ms 19.6 ms -0.4 ms 1.9%
GPU 0.1% worst 25.3 ms 25.1 ms 0.2 ms -0.9%

@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
  • [ ✔️] Skybox
  • [ ✔️] Settings

In order to re-create this one :

  1. Open the index of a scene folder
  2. Copy the text and erase the other one inside index
  3. Save the new text
  4. npm i on the src code
  5. npm run start
  6. check that when enteering to the scene the console mentions the name and wallet of the user

Evidence:

20260522-0824-01.1221605.mp4

Fix test assertions for local player PlayerSceneCRDTEntity persistence

Local player PlayerSceneCRDTEntity now persists on the persistent
entity after leaving a scene, matching the RemovePlayerFromScene
early-return behavior from ea34e55.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@
@claude

This comment has been minimized.

@m3taphysics

Copy link
Copy Markdown
Contributor

PR #8837, run #26280529239

Builds: Windows change, Windows baseline, macOS change, macOS baseline

Framework 13 i7

Metric Change Baseline Delta Improvement
Samples 2700 2700
CPU average 33.3 ms 33.3 ms 0.0 ms -0.0%
CPU 1% worst 33.8 ms 33.7 ms 0.1 ms -0.2%
CPU 0.1% worst 37.1 ms 37.3 ms -0.3 ms 0.7%
GPU average 8.0 ms 8.0 ms -0.0 ms 0.5%
GPU 1% worst 19.7 ms 18.6 ms 1.1 ms -5.8% 🔴
GPU 0.1% worst 29.2 ms 25.0 ms 4.2 ms -16.8% 🔴

@mikhail-dcl mikhail-dcl changed the title fix: Maintain LocalPlayer-related data on all scenes fix: maintain LocalPlayer-related data on all scenes Jun 9, 2026
# Conflicts:
#	Explorer/Assets/DCL/Infrastructure/Global/StaticContainer.cs
@mikhail-dcl
mikhail-dcl enabled auto-merge (squash) June 9, 2026 12:58
@claude

This comment has been minimized.

@mikhail-dcl

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

This comment has been minimized.

@m3taphysics

This comment has been minimized.

@claude

This comment has been minimized.

@m3taphysics

This comment has been minimized.

@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Claude finished @mikhail-dcl's task in 3m 40s —— View job


PR Review (Re-review)

  • Gather context (read CLAUDE.md, docs/README.md)
  • Read changed files and full diff
  • Check for blocking issues
  • Post final review summary

Root-cause check

PASS. getPlayer() returned null in scene main() because the propagation systems only ran during Update(), one frame after the JS runtime started. The fix is structural: LocalPlayerCRDTEntityHandlerSystem.Initialize() seeds PlayerSceneCRDTEntity and SDKProfile onto PersistentEntities.Player before the runtime starts; all three writer systems have matching Initialize() overrides that force-flush CRDT messages; and PlayerCRDTEntitiesHandlerSystem now accepts SceneState.Starting so the global entity tracks the scene before it transitions to Running. This fixes the root cause, not a symptom.


No blocking issues

The latest synchronize trigger added only dev-merge commits (e8dd6f9f, 7173eeae) — the feature code itself is unchanged since the last review. All prior blocking issues were resolved:

  • All three writer systems have Initialize() overrides calling their queries with force=true
  • LocalPlayerCRDTEntityHandlerSystem registered first in MultiplayerPlugin.InjectToWorld, [UpdateBefore] on all three writer systems guarantees seeding order ✅
  • PlayerCRDTEntitiesHandlerSystem accepts SceneState.Starting and reuses PersistentEntities.Player for the local player ✅
  • RemovePlayerFromScene early-returns for local player (crdtEntity.Id == SpecialEntitiesID.PLAYER_ENTITY), gates remote-player cleanup on Running or Starting
  • PlayerProfileDataPropagationSystem gains [None(typeof(PlayerComponent))] excluding the local player from the global propagation path ✅
  • Tests updated correctly: AssignPlayerWhenSceneIsStarting, KeepPlayerAssignedWhenSceneTransitionsFromStartingToRunning, RemovePlayerCRDTEntityForPlayersLeavingScene, ChangeSceneOnPlayerMove — all branched on isMainPlayer
  • SeedPlayerCRDTEntityAndProfile and NotSeedWhenPlayerHasNoProfile cover both Initialize() paths ✅

Non-blocking observations (unchanged from previous review)

CharacterDataPropagationUtility.PropagateGlobalPlayerToScenePlayer (CharacterDataPropagationUtility.cs:27) is dead production code — its only caller is the test SeedLocalPlayerCRDTSystemShould. It contains an unguarded World.Add:

sceneFacade.EcsExecutor.World.Add(targetEntity, new PlayerSceneCRDTEntity(SpecialEntitiesID.PLAYER_ENTITY));

If a production caller is ever added, this will throw because LocalPlayerCRDTEntityHandlerSystem.Initialize() already seeded the component. Should be deleted or converted to AddOrSet. No immediate risk.

SeedLocalPlayerCRDTSystemShould.cs contains class LocalPlayerCRDTEntityHandlerSystemShould — file name and class name are inconsistent, which hinders discoverability.


REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches cross-world ECS access (global ↔ scene propagation via PersistentEntities), CRDT bridge seeding order, plugin/DI wiring, scene lifecycle state machine, and system execution ordering across both global and scene worlds.
QA_REQUIRED: YES

@mikhail-dcl
mikhail-dcl merged commit 49aafc6 into dev Jun 10, 2026
16 checks passed
@mikhail-dcl
mikhail-dcl deleted the feat/seed-local-player-crdt-on-scene-init branch June 10, 2026 10:30
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Explorer Alpha Jun 10, 2026
@m3taphysics

Copy link
Copy Markdown
Contributor

PR #8837, run #27268519909

Builds: Windows change, Windows baseline, macOS change, macOS baseline

How to read this table
  • Each build is measured 3 times. The values are the median, and (min–max) is the lowest and highest of those runs — a wide range means the metric is noisy and small differences are not trustworthy.
  • Δ is Change minus Baseline (a negative Δ means Change is faster).
  • 🟢 faster / 🔴 slower — a real difference: larger than both 3% and the run-to-run range.
  • ⚪ within noise — the difference is smaller than how much the build varies between its own runs, so it cannot be told apart from random variation. Treat it as no change.
  • Exceptions logged — exceptions found in the run logs; more than the baseline is flagged 🔴 even when frame times look fine.

Framework 13 i7

Metric Baseline Change Δ Result
Samples 2701 (×3) 2701 (×3)
CPU average 33.3 ms 33.3 ms -0.0 ms ⚪ within noise
CPU 1% worst 33.8 ms (33.5–34.5) 33.6 ms (33.5–34.0) -0.2 ms ⚪ within noise
CPU 0.1% worst 38.5 ms (34.3–47.1) 35.7 ms (35.1–39.8) -2.8 ms ⚪ within noise
GPU average 7.7 ms (7.6–7.8) 7.5 ms (7.5–8.1) -0.1 ms ⚪ within noise
GPU 1% worst 20.1 ms (18.6–21.3) 19.5 ms (18.8–20.7) -0.5 ms ⚪ within noise
GPU 0.1% worst 27.0 ms (25.3–28.8) 25.3 ms (24.2–26.1) -1.7 ms ⚪ within noise
Exceptions logged 44 12 -32 🟢 fewer errors

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

Labels

enhancement Enhancement of an existing feature

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

getPlayer() function not available on first tick

4 participants