diff --git a/.claude/agents/dcl-sdk-feature-implementation/dcl-sdk-specialist.md b/.claude/agents/dcl-sdk-feature-implementation/dcl-sdk-specialist.md index 9964dca7967..96bf463d9fb 100644 --- a/.claude/agents/dcl-sdk-feature-implementation/dcl-sdk-specialist.md +++ b/.claude/agents/dcl-sdk-feature-implementation/dcl-sdk-specialist.md @@ -203,6 +203,17 @@ Create a test file at a location matching the system's purpose: Look at `test/ecs/events/videoEventsSystem.spec.ts` for a reference test structure. +## ColliderLayer mask semantics + +When generating helpers or tests for components with a `collision_mask` / `collisionMask` field of type `ColliderLayer`, keep these rules in mind (full table + examples in `unity-explorer/docs/how-to-implement-new-sdk-components.md` under "ColliderLayer mask semantics"): + +- **Additive avatar semantics:** main player is tagged with both `CL_PLAYER` and `CL_MAIN_PLAYER`; remote avatars only with `CL_PLAYER`. +- **Unified main-player qualification** (Raycast + TriggerArea): the main player qualifies only when the mask contains `CL_PLAYER` or `CL_MAIN_PLAYER` (`PLAYER_QUALIFYING_BITS`). `CL_PHYSICS`, `CL_POINTER`, `CL_CUSTOM*`, and `CL_NONE` do NOT qualify the main player. +- **`CL_PHYSICS` targets scene-mesh walls / floors**, not the character. Scenes that need to detect the player must opt in via `CL_PLAYER` or `CL_MAIN_PLAYER`. +- **Remote avatars qualify only on `CL_PLAYER`.** +- **`CL_MAIN_PLAYER`-only TriggerArea** is fast-pathed via `targetOnlyMainPlayer` / `TargetTransform` early-out. Any other mask must NOT enable this. +- **Scene-mesh routing:** for `MeshCollider` / `GltfContainer`, avatar-only masks (`CL_PLAYER` / `CL_MAIN_PLAYER`, no other bits) route to the `SDKAvatarHit` Unity layer — pass-through for the player capsule, raycast- and trigger-detectable via the matrix. Mixed masks containing `CL_PHYSICS` route to `CharacterOnly` and remain solid. + ## GROWN_ONLY_COMPONENTS (GOVS) For grow-only result components (not LWW), add the component name to: diff --git a/.claude/agents/dcl-sdk-feature-implementation/dcl-test-scene-specialist.md b/.claude/agents/dcl-sdk-feature-implementation/dcl-test-scene-specialist.md index b6c1030a719..dbdce2c21b1 100644 --- a/.claude/agents/dcl-sdk-feature-implementation/dcl-test-scene-specialist.md +++ b/.claude/agents/dcl-sdk-feature-implementation/dcl-test-scene-specialist.md @@ -32,16 +32,41 @@ sdk7-test-scenes/ Each scene folder follows the naming convention: `{x},{y}-{scene-name}` +## MANDATORY: Folder Naming Convention + +**Every new scene folder MUST be named `{x},{y}-{scene-name}` where `{x},{y}` matches the parcel coordinates declared in `scene.json` (`scene.parcels[0]` / the base parcel).** + +Examples: +- ✅ `scenes/5,5-collider-layer-main-player` (parcel `5,5`) +- ✅ `scenes/0,7-particle-system` (parcel `0,7`) +- ✅ `scenes/100,100-mannakia-test-scene` (parcel `100,100`) +- ❌ `scenes/collider-layer-main-player` — missing coordinate prefix +- ❌ `scenes/my-feature-test` — missing coordinate prefix +- ❌ `scenes/3,3-feature` while `scene.json` says base `5,5` — folder/parcel mismatch + +Tooling and humans alike rely on this prefix: +- `dcl-workspace.json` lists scenes alphabetically by folder name — coordinate-prefixed entries sort coherently. +- `npm run check-parcels` validates parcel collisions; a missing prefix slips past spatial review. +- Operators locate scenes by parcel when bug-bashing; an unprefixed folder is invisible to that workflow. + +If you ever find yourself about to create a folder without `{x},{y}-` at the start, stop and pick the parcel first. + ## Scene Creation Workflow ### Step 1: Duplicate an existing scene -Choose a scene close to your use case and copy it: +Choose a scene close to your use case and copy it. **The destination folder name MUST be `{x},{y}-{scene-name}`** — pick the parcel before creating the folder: + ```bash cd ../sdk7-test-scenes cp -r scenes/0,0-cube-spawner scenes/,- ``` +Example for a new scene at parcel `5,5`: +```bash +cp -r scenes/0,0-cube-spawner scenes/5,5-my-feature-test +``` + ### Step 2: Update scene metadata **`package.json`** — Update the `name` field: @@ -170,14 +195,23 @@ npm run check-parcels # Validates all scene parcels, update ## Completion Gate -**Do not report success until `npm run build` passes with zero errors.** Always run before finishing: - -```bash -cd ../sdk7-test-scenes/scenes/,- -npm run build # TypeScript compilation must succeed with no errors -``` - -If the build fails, diagnose and fix before reporting done. Do not hand off a scene that does not compile. +Before reporting success, ALL of the following must hold: + +1. **Folder name matches `{x},{y}-{scene-name}`** and `{x},{y}` equals the parcel coordinates declared in `scene.json`. Verify with: + ```bash + cd ../sdk7-test-scenes + ls -d scenes/,- # folder exists with coordinate prefix + grep -E '"base"|"parcels"' scenes/,-/scene.json # matches folder prefix + ``` + If the folder is missing the coordinate prefix (or the prefix does not match the parcel in `scene.json`), rename it now via `mv` and update `dcl-workspace.json` accordingly — do NOT defer this to the user. +2. `npm run check-parcels` (from repo root) reports `✅ No collisions found`. +3. `npm run build` (from inside the scene folder) passes with zero TypeScript errors: + ```bash + cd ../sdk7-test-scenes/scenes/,- + npm run build + ``` + +If any gate fails, diagnose and fix before reporting done. Do not hand off a scene that does not compile, lacks the coordinate prefix, or collides with another parcel. ## Git Rules diff --git a/Explorer/Assets/DCL/Character/CharacterCamera/Character Camera.prefab b/Explorer/Assets/DCL/Character/CharacterCamera/Character Camera.prefab index 401bb876c11..71e95226390 100644 --- a/Explorer/Assets/DCL/Character/CharacterCamera/Character Camera.prefab +++ b/Explorer/Assets/DCL/Character/CharacterCamera/Character Camera.prefab @@ -2105,7 +2105,7 @@ Camera: m_Depth: -1 m_CullingMask: serializedVersion: 2 - m_Bits: 1677918839 + m_Bits: 1678967415 m_RenderingPath: -1 m_TargetTexture: {fileID: 0} m_TargetDisplay: 0 diff --git a/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/PhysicsLayers.cs b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/PhysicsLayers.cs index f98f6112ec6..d88daf4730d 100644 --- a/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/PhysicsLayers.cs +++ b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/PhysicsLayers.cs @@ -8,12 +8,20 @@ public static class PhysicsLayers private const ColliderLayer LAYER_PHYSICS = ColliderLayer.ClPhysics; private const ColliderLayer LAYER_POINTER = ColliderLayer.ClPointer; private const ColliderLayer LAYER_PHYSICS_POINTER = LAYER_PHYSICS | LAYER_POINTER; + private const ColliderLayer LAYER_PLAYER = ColliderLayer.ClPlayer; + private const ColliderLayer LAYER_MAIN_PLAYER = ColliderLayer.ClMainPlayer; + + /// + /// Bits that qualify the main player on Raycast and TriggerArea queries. + /// Remote avatars only match on . + /// + public const ColliderLayer PLAYER_QUALIFYING_BITS = ColliderLayer.ClPlayer | ColliderLayer.ClMainPlayer; private const ColliderLayer NON_CUSTOM_LAYERS = ColliderLayer.ClPhysics | ColliderLayer.ClPointer | ColliderLayer.ClNone | ColliderLayer.ClPlayer - | ColliderLayer.ClReserved2 + | ColliderLayer.ClMainPlayer | ColliderLayer.ClReserved3 | ColliderLayer.ClReserved4 | ColliderLayer.ClReserved5 @@ -31,7 +39,8 @@ public static class PhysicsLayers public static readonly int SDK_CUSTOM_LAYER = LayerMask.NameToLayer("SDKCustomLayer"); public static readonly int OTHER_AVATARS_LAYER = LayerMask.NameToLayer("OtherAvatars"); public static readonly int SDK_ENTITY_TRIGGER_AREA = LayerMask.NameToLayer("SDKEntityTriggerArea"); - public static readonly int ALL_AVATARS = LayerMask.NameToLayer("AllAvatars"); + public static readonly int SDK_AVATAR_TRIGGER_AREA = LayerMask.NameToLayer("SDKAvatarTriggerArea"); + public static readonly int SDK_AVATAR_HIT_LAYER = LayerMask.NameToLayer("SDKAvatarHit"); public static readonly LayerMask PLAYER_ORIGIN_RAYCAST_MASK = (1 << ON_POINTER_EVENT_LAYER) | (1 << DEFAULT_LAYER) | (1 << OTHER_AVATARS_LAYER); public static readonly LayerMask CHARACTER_ONLY_MASK = (1 << DEFAULT_LAYER) | (1 << FLOOR_LAYER) | (1 << CHARACTER_ONLY_LAYER); @@ -40,6 +49,15 @@ public static class PhysicsLayers public static bool LayerMaskHasAnySDKCustomLayer(ColliderLayer layerMask) => (layerMask & ~NON_CUSTOM_LAYERS) != 0; + /// + /// True when the mask is exclusively avatar bits (CL_PLAYER / CL_MAIN_PLAYER, no other bits). + /// Used to route SDK colliders to the SDKAvatarHit Unity layer. + /// + public static bool IsAvatarOnlyMask(ColliderLayer sdkMask) => + sdkMask != ColliderLayer.ClNone + && (sdkMask & PLAYER_QUALIFYING_BITS) != 0 + && (sdkMask & ~PLAYER_QUALIFYING_BITS) == 0; + public static bool LayerMaskContainsTargetLayer(uint layerMask, uint targetLayer) => (layerMask & targetLayer) != 0; @@ -51,7 +69,16 @@ public static bool LayerMaskContainsTargetLayer(ColliderLayer layerMask, Collide public static int CreateUnityLayerMaskFromSDKMask(ColliderLayer sdkMask) { - int unityLayerMask = (1 << CHARACTER_LAYER) | (1 << DEFAULT_LAYER); + // Default keeps catching SDK meshes on the Default layer regardless of which SDK bits are set. + int unityLayerMask = 1 << DEFAULT_LAYER; + + // Player-qualifying bits include the main player capsule (CHARACTER_LAYER) and the SDK avatar-hit + // layer (SDKAvatarHit) where SDK MeshCollider/GltfContainer colliders tagged with avatar bits live. + if ((sdkMask & PLAYER_QUALIFYING_BITS) != 0) + { + unityLayerMask |= 1 << CHARACTER_LAYER; + unityLayerMask |= 1 << SDK_AVATAR_HIT_LAYER; + } unityLayerMask |= sdkMask switch { @@ -60,6 +87,10 @@ public static int CreateUnityLayerMaskFromSDKMask(ColliderLayer sdkMask) _ => (1 << CHARACTER_ONLY_LAYER) | (1 << ON_POINTER_EVENT_LAYER), }; + // CL_PLAYER targets any avatar: include OTHER_AVATARS_LAYER. CL_MAIN_PLAYER alone is local-only. + if ((sdkMask & LAYER_PLAYER) == LAYER_PLAYER) + unityLayerMask |= 1 << OTHER_AVATARS_LAYER; + // 8 Custom SDK Layers are projected onto a single Unity layer if (LayerMaskHasAnySDKCustomLayer(sdkMask)) unityLayerMask |= 1 << SDK_CUSTOM_LAYER; @@ -87,6 +118,14 @@ public static bool TryGetUnityLayerFromSDKLayer(ColliderLayer sdkMask, out int u return true; } + // Avatar-only masks route to SDKAvatarHit. Player capsule passes through (matrix-disabled); + // trigger areas and raycasts targeting avatar bits still detect them. + if (IsAvatarOnlyMask(sdkMask)) + { + unityLayer = SDK_AVATAR_HIT_LAYER; + return true; + } + if (LayerMaskHasAnySDKCustomLayer(sdkMask)) { unityLayer = SDK_CUSTOM_LAYER; diff --git a/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests.meta b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests.meta new file mode 100644 index 00000000000..7106e48ed9e --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4e72de20c693460c910feefaab9d8295 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayers.Tests.asmref b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayers.Tests.asmref new file mode 100644 index 00000000000..5aa9fc5db6f --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayers.Tests.asmref @@ -0,0 +1,3 @@ +{ + "reference": "GUID:da80994a355e49d5b84f91c0a84a721f" +} diff --git a/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayers.Tests.asmref.meta b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayers.Tests.asmref.meta new file mode 100644 index 00000000000..c125095ab05 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayers.Tests.asmref.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4decc6300f104ca6a9a1e2556a518ed5 +AssemblyDefinitionReferenceImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayersShould.cs b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayersShould.cs new file mode 100644 index 00000000000..2dc476428df --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayersShould.cs @@ -0,0 +1,214 @@ +using CrdtEcsBridge.Physics; +using DCL.ECSComponents; +using NUnit.Framework; + +namespace CrdtEcsBridge.Physics.Tests +{ + [TestFixture] + public class PhysicsLayersShould + { + [Test] + public void TryGetUnityLayerFromSDKLayer_ReturnsAvatarHit_ForPlayerOnly() + { + ColliderLayer mask = ColliderLayer.ClPlayer; + + bool result = PhysicsLayers.TryGetUnityLayerFromSDKLayer(mask, out int unityLayer); + + // Avatar-only masks land on SDKAvatarHit (player capsule passes through; trigger areas + raycasts still detect). + Assert.IsTrue(result); + Assert.AreEqual(PhysicsLayers.SDK_AVATAR_HIT_LAYER, unityLayer); + } + + [Test] + public void TryGetUnityLayerFromSDKLayer_ReturnsAvatarHit_ForMainPlayerOnly() + { + ColliderLayer mask = ColliderLayer.ClMainPlayer; + + bool result = PhysicsLayers.TryGetUnityLayerFromSDKLayer(mask, out int unityLayer); + + Assert.IsTrue(result); + Assert.AreEqual(PhysicsLayers.SDK_AVATAR_HIT_LAYER, unityLayer); + } + + [Test] + public void TryGetUnityLayerFromSDKLayer_ReturnsAvatarHit_ForPlayerAndMainPlayer() + { + ColliderLayer mask = ColliderLayer.ClPlayer | ColliderLayer.ClMainPlayer; + + bool result = PhysicsLayers.TryGetUnityLayerFromSDKLayer(mask, out int unityLayer); + + Assert.IsTrue(result); + Assert.AreEqual(PhysicsLayers.SDK_AVATAR_HIT_LAYER, unityLayer); + } + + [Test] + public void TryGetUnityLayerFromSDKLayer_ReturnsCharacterOnly_ForPlayerWithPhysics() + { + // Mixed mask: CL_PHYSICS wins — mesh must stay solid against the player capsule. + ColliderLayer mask = ColliderLayer.ClPlayer | ColliderLayer.ClPhysics; + + bool result = PhysicsLayers.TryGetUnityLayerFromSDKLayer(mask, out int unityLayer); + + Assert.IsTrue(result); + Assert.AreEqual(PhysicsLayers.CHARACTER_ONLY_LAYER, unityLayer); + } + + [Test] + public void TryGetUnityLayerFromSDKLayer_PreservesPhysicsBranch() + { + // Arrange + ColliderLayer mask = ColliderLayer.ClPhysics; + + // Act + bool result = PhysicsLayers.TryGetUnityLayerFromSDKLayer(mask, out int unityLayer); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(PhysicsLayers.CHARACTER_ONLY_LAYER, unityLayer); + } + + [Test] + public void TryGetUnityLayerFromSDKLayer_PreservesPointerBranch() + { + // Arrange + ColliderLayer mask = ColliderLayer.ClPointer; + + // Act + bool result = PhysicsLayers.TryGetUnityLayerFromSDKLayer(mask, out int unityLayer); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(PhysicsLayers.ON_POINTER_EVENT_LAYER, unityLayer); + } + + [Test] + public void TryGetUnityLayerFromSDKLayer_PhysicsAndPointer_StillReturnsDefault() + { + // Arrange + ColliderLayer mask = ColliderLayer.ClPhysics | ColliderLayer.ClPointer; + + // Act + bool result = PhysicsLayers.TryGetUnityLayerFromSDKLayer(mask, out int unityLayer); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(PhysicsLayers.DEFAULT_LAYER, unityLayer); + } + + [Test] + public void TryGetUnityLayerFromSDKLayer_FallsBack_WhenMaskHasNoMappableLayer() + { + // Arrange + ColliderLayer mask = ColliderLayer.ClNone; + + // Act + bool result = PhysicsLayers.TryGetUnityLayerFromSDKLayer(mask, out int unityLayer); + + // Assert + Assert.IsFalse(result); + Assert.AreEqual(0, unityLayer); + } + + [Test] + public void CreateUnityLayerMaskFromSDKMask_IncludesOtherAvatarsAndAvatarHit_WhenMaskHasPlayer() + { + ColliderLayer mask = ColliderLayer.ClPlayer; + + int unityMask = PhysicsLayers.CreateUnityLayerMaskFromSDKMask(mask); + + Assert.AreNotEqual(0, unityMask & (1 << PhysicsLayers.OTHER_AVATARS_LAYER), + "Expected OTHER_AVATARS_LAYER bit to be set when SDK mask contains CL_PLAYER."); + Assert.AreNotEqual(0, unityMask & (1 << PhysicsLayers.CHARACTER_LAYER), + "Expected CHARACTER_LAYER to be included when SDK mask contains CL_PLAYER."); + Assert.AreNotEqual(0, unityMask & (1 << PhysicsLayers.SDK_AVATAR_HIT_LAYER), + "Expected SDK_AVATAR_HIT_LAYER to be included when SDK mask contains CL_PLAYER."); + } + + [Test] + public void CreateUnityLayerMaskFromSDKMask_IncludesAvatarHitButNotOtherAvatars_WhenMaskOnlyHasMainPlayer() + { + ColliderLayer mask = ColliderLayer.ClMainPlayer; + + int unityMask = PhysicsLayers.CreateUnityLayerMaskFromSDKMask(mask); + + Assert.AreEqual(0, unityMask & (1 << PhysicsLayers.OTHER_AVATARS_LAYER), + "Expected OTHER_AVATARS_LAYER bit NOT to be set when only CL_MAIN_PLAYER is in the SDK mask."); + Assert.AreNotEqual(0, unityMask & (1 << PhysicsLayers.CHARACTER_LAYER), + "Expected CHARACTER_LAYER to be included when SDK mask contains CL_MAIN_PLAYER."); + Assert.AreNotEqual(0, unityMask & (1 << PhysicsLayers.SDK_AVATAR_HIT_LAYER), + "Expected SDK_AVATAR_HIT_LAYER to be included when SDK mask contains CL_MAIN_PLAYER."); + } + + [Test] + public void CreateUnityLayerMaskFromSDKMask_ExcludesCharacter_ForPhysicsOnlyMask() + { + // CL_PHYSICS is not in PLAYER_QUALIFYING_BITS — CHARACTER_LAYER must NOT be reached. + ColliderLayer mask = ColliderLayer.ClPhysics; + + // Act + int unityMask = PhysicsLayers.CreateUnityLayerMaskFromSDKMask(mask); + + // Assert + Assert.AreEqual(0, unityMask & (1 << PhysicsLayers.CHARACTER_LAYER), + "Expected CHARACTER_LAYER NOT to be set for a CL_PHYSICS-only mask."); + Assert.AreEqual(0, unityMask & (1 << PhysicsLayers.OTHER_AVATARS_LAYER), + "Expected OTHER_AVATARS_LAYER NOT to be set for a CL_PHYSICS-only mask."); + Assert.AreEqual(0, unityMask & (1 << PhysicsLayers.SDK_AVATAR_HIT_LAYER), + "Expected SDK_AVATAR_HIT_LAYER NOT to be set for a CL_PHYSICS-only mask."); + } + + [Test] + public void CreateUnityLayerMaskFromSDKMask_ExcludesCharacter_ForPointerOnlyMask() + { + // Arrange: CL_POINTER alone is NOT in PLAYER_QUALIFYING_BITS — pointer raycasts must + // not unconditionally hit the local player capsule. + ColliderLayer mask = ColliderLayer.ClPointer; + + // Act + int unityMask = PhysicsLayers.CreateUnityLayerMaskFromSDKMask(mask); + + // Assert + Assert.AreEqual(0, unityMask & (1 << PhysicsLayers.CHARACTER_LAYER), + "Expected CHARACTER_LAYER NOT to be set for a CL_POINTER-only mask."); + Assert.AreEqual(0, unityMask & (1 << PhysicsLayers.OTHER_AVATARS_LAYER), + "Expected OTHER_AVATARS_LAYER NOT to be set for a CL_POINTER-only mask."); + } + + [Test] + public void CreateUnityLayerMaskFromSDKMask_ExcludesCharacter_ForCustomOnlyMask() + { + // Arrange: CL_CUSTOM* bits are NOT in PLAYER_QUALIFYING_BITS — custom-layer raycasts + // must not unconditionally hit the local player capsule. + ColliderLayer mask = ColliderLayer.ClCustom1; + + // Act + int unityMask = PhysicsLayers.CreateUnityLayerMaskFromSDKMask(mask); + + // Assert + Assert.AreEqual(0, unityMask & (1 << PhysicsLayers.CHARACTER_LAYER), + "Expected CHARACTER_LAYER NOT to be set for a CL_CUSTOM1-only mask."); + Assert.AreEqual(0, unityMask & (1 << PhysicsLayers.OTHER_AVATARS_LAYER), + "Expected OTHER_AVATARS_LAYER NOT to be set for a CL_CUSTOM1-only mask."); + } + + [Test] + public void IsAvatarOnlyMask_TrueForAvatarBitsOnly() + { + Assert.IsTrue(PhysicsLayers.IsAvatarOnlyMask(ColliderLayer.ClPlayer)); + Assert.IsTrue(PhysicsLayers.IsAvatarOnlyMask(ColliderLayer.ClMainPlayer)); + Assert.IsTrue(PhysicsLayers.IsAvatarOnlyMask(ColliderLayer.ClPlayer | ColliderLayer.ClMainPlayer)); + } + + [Test] + public void IsAvatarOnlyMask_FalseForNonAvatarOrMixedMasks() + { + Assert.IsFalse(PhysicsLayers.IsAvatarOnlyMask(ColliderLayer.ClNone)); + Assert.IsFalse(PhysicsLayers.IsAvatarOnlyMask(ColliderLayer.ClPhysics)); + Assert.IsFalse(PhysicsLayers.IsAvatarOnlyMask(ColliderLayer.ClPointer)); + Assert.IsFalse(PhysicsLayers.IsAvatarOnlyMask(ColliderLayer.ClCustom1)); + // Mixed: avatar bit + non-avatar bit must NOT be avatar-only. + Assert.IsFalse(PhysicsLayers.IsAvatarOnlyMask(ColliderLayer.ClPlayer | ColliderLayer.ClPhysics)); + Assert.IsFalse(PhysicsLayers.IsAvatarOnlyMask(ColliderLayer.ClMainPlayer | ColliderLayer.ClPointer)); + } + } +} diff --git a/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayersShould.cs.meta b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayersShould.cs.meta new file mode 100644 index 00000000000..6f5bfa54649 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/Physics/Tests/PhysicsLayersShould.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4c19c35786eb445abf0af4d02c852360 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/SceneAdmins.cs.meta b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/SceneAdmins.cs.meta new file mode 100644 index 00000000000..c8a3abb179d --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/SceneAdmins.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 951dd35092514bf4b93af8e5c47d2ab1 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Interaction/Raycast/Systems/ExecuteRaycastSystem.cs b/Explorer/Assets/DCL/Interaction/Raycast/Systems/ExecuteRaycastSystem.cs index 8168cdb6239..0d0a42c8dc6 100644 --- a/Explorer/Assets/DCL/Interaction/Raycast/Systems/ExecuteRaycastSystem.cs +++ b/Explorer/Assets/DCL/Interaction/Raycast/Systems/ExecuteRaycastSystem.cs @@ -257,14 +257,27 @@ private bool DoesHitColliderQualify(Collider collider, ColliderLayer collisionMa { foundEntity = null; - // 1. Player is always qualified + // 1. Main player: qualify when the SDK mask has CL_PLAYER or CL_MAIN_PLAYER. if (RaycastUtils.IsPlayer(collider)) { + if ((collisionMask & PhysicsLayers.PLAYER_QUALIFYING_BITS) == 0) + return false; + foundEntity = SpecialEntitiesID.PLAYER_ENTITY; return true; } - // 2. If the collider is not a character, we need to check if it's in the collision mask + // 2. Remote avatar: qualify when the SDK mask has CL_PLAYER. + if (collider.gameObject.layer == PhysicsLayers.OTHER_AVATARS_LAYER) + { + if ((collisionMask & ColliderLayer.ClPlayer) == 0) + return false; + + // No CRDT entity ID to report for a remote avatar in this scene. + return true; + } + + // 3. If the collider is not a character, we need to check if it's in the collision mask if (!sceneData.IsPortableExperience()) { if (collidersSceneCache.TryGetEntity(collider, out ColliderSceneEntityInfo entityInfo)) diff --git a/Explorer/Assets/DCL/Interaction/Raycast/Tests/ExecuteRaycastSystemShould.cs b/Explorer/Assets/DCL/Interaction/Raycast/Tests/ExecuteRaycastSystemShould.cs index 155a6e0e15b..13639d248b9 100644 --- a/Explorer/Assets/DCL/Interaction/Raycast/Tests/ExecuteRaycastSystemShould.cs +++ b/Explorer/Assets/DCL/Interaction/Raycast/Tests/ExecuteRaycastSystemShould.cs @@ -1,5 +1,6 @@ using Arch.Core; using CRDT; +using CrdtEcsBridge.Components; using CrdtEcsBridge.Components.ResetExtensions; using CrdtEcsBridge.ECSToCRDTWriter; using CrdtEcsBridge.Physics; @@ -424,6 +425,155 @@ public void IgnoreCrossSceneColliderNotInCollisionMask_PortableExperience() Assert.That(raycastResult.Hits.Count, Is.EqualTo(0)); // Should not find any hits } + [Test] + public void MainPlayer_QualifiesForPlayerMask() + { + // Arrange: a single CHARACTER_LAYER collider + CreateAvatarLayerCollider(PhysicsLayers.CHARACTER_LAYER); + + pbRaycast.QueryType = RaycastQueryType.RqtHitFirst; + pbRaycast.CollisionMask = (uint)ColliderLayer.ClPlayer; + + // Act + system.Update(0); + + // Assert + Assert.That(raycastResult.Hits.Count, Is.EqualTo(1)); + Assert.That(raycastResult.Hits[0].EntityId, Is.EqualTo((uint)SpecialEntitiesID.PLAYER_ENTITY)); + } + + [Test] + public void MainPlayer_QualifiesForMainPlayerMask() + { + // Arrange + CreateAvatarLayerCollider(PhysicsLayers.CHARACTER_LAYER); + + pbRaycast.QueryType = RaycastQueryType.RqtHitFirst; + pbRaycast.CollisionMask = (uint)ColliderLayer.ClMainPlayer; + + // Act + system.Update(0); + + // Assert + Assert.That(raycastResult.Hits.Count, Is.EqualTo(1)); + Assert.That(raycastResult.Hits[0].EntityId, Is.EqualTo((uint)SpecialEntitiesID.PLAYER_ENTITY)); + } + + [Test] + public void MainPlayer_DoesNotQualifyForPhysicsOnlyMask() + { + // CL_PHYSICS is not in PLAYER_QUALIFYING_BITS — main player must not be hit. + CreateAvatarLayerCollider(PhysicsLayers.CHARACTER_LAYER); + + pbRaycast.QueryType = RaycastQueryType.RqtHitFirst; + pbRaycast.CollisionMask = (uint)ColliderLayer.ClPhysics; + + // Act + system.Update(0); + + // Assert + Assert.That(raycastResult.Hits.Count, Is.EqualTo(0)); + } + + [Test] + public void RemoteAvatar_QualifiesForPlayerMask() + { + // Arrange + CreateAvatarLayerCollider(PhysicsLayers.OTHER_AVATARS_LAYER); + + pbRaycast.QueryType = RaycastQueryType.RqtHitFirst; + pbRaycast.CollisionMask = (uint)ColliderLayer.ClPlayer; + + // Act + system.Update(0); + + // Assert + Assert.That(raycastResult.Hits.Count, Is.EqualTo(1)); + // Remote avatars must not report a scene-local entity id (foundEntity stays null). + Assert.That(raycastResult.Hits[0].EntityId, Is.EqualTo(0u)); + } + + [Test] + public void RemoteAvatar_DoesNotQualifyForMainPlayerOnlyMask() + { + // Arrange + CreateAvatarLayerCollider(PhysicsLayers.OTHER_AVATARS_LAYER); + + pbRaycast.QueryType = RaycastQueryType.RqtHitFirst; + pbRaycast.CollisionMask = (uint)ColliderLayer.ClMainPlayer; + + // Act + system.Update(0); + + // Assert + Assert.That(raycastResult.Hits.Count, Is.EqualTo(0)); + } + + [Test] + public void MainPlayer_DoesNotQualifyForPointerOnlyMask() + { + // CL_POINTER is not in PLAYER_QUALIFYING_BITS — main player must not be hit. + CreateAvatarLayerCollider(PhysicsLayers.CHARACTER_LAYER); + + pbRaycast.QueryType = RaycastQueryType.RqtHitFirst; + pbRaycast.CollisionMask = (uint)ColliderLayer.ClPointer; + + // Act + system.Update(0); + + // Assert + Assert.That(raycastResult.Hits.Count, Is.EqualTo(0)); + } + + [Test] + public void MainPlayer_DoesNotQualifyForCustomOnlyMask() + { + // Custom layers are not in PLAYER_QUALIFYING_BITS — main player must not be hit. + CreateAvatarLayerCollider(PhysicsLayers.CHARACTER_LAYER); + + pbRaycast.QueryType = RaycastQueryType.RqtHitFirst; + pbRaycast.CollisionMask = (uint)ColliderLayer.ClCustom1; + + // Act + system.Update(0); + + // Assert + Assert.That(raycastResult.Hits.Count, Is.EqualTo(0)); + } + + [Test] + public void MainPlayer_DoesNotQualifyForNoneMask() + { + // CL_NONE has no bits set — main player must not be hit. + CreateAvatarLayerCollider(PhysicsLayers.CHARACTER_LAYER); + + pbRaycast.QueryType = RaycastQueryType.RqtHitFirst; + pbRaycast.CollisionMask = (uint)ColliderLayer.ClNone; + + // Act + system.Update(0); + + // Assert + Assert.That(raycastResult.Hits.Count, Is.EqualTo(0)); + } + + private BoxCollider CreateAvatarLayerCollider(int unityLayer) + { + Transform colliderTransform = new GameObject(nameof(ExecuteRaycastSystemShould) + "_AvatarLayer_" + unityLayer).transform; + // Place forward of the raycast origin so the global +Z ray hits it. + colliderTransform.position = new UnityEngine.Vector3(0, 0, 5f); + colliderTransform.gameObject.layer = unityLayer; + BoxCollider collider = colliderTransform.gameObject.AddComponent(); + collider.size = UnityEngine.Vector3.one; + collider.isTrigger = false; + + instantiatedTemp.Add(collider); + // Intentionally not registered in either the scene cache or the global + // cache: avatar branches in DoesHitColliderQualify must decide based on + // the GameObject layer alone. + return collider; + } + private void AssertHit(int index, int colliderIndex) { var sorted = raycastResult.Hits.OrderBy(x => x.Length).ToList(); diff --git a/Explorer/Assets/DCL/SDKComponents/TriggerArea/Systems/TriggerAreaHandlerSystem.cs b/Explorer/Assets/DCL/SDKComponents/TriggerArea/Systems/TriggerAreaHandlerSystem.cs index da42795f36f..72725586e4b 100644 --- a/Explorer/Assets/DCL/SDKComponents/TriggerArea/Systems/TriggerAreaHandlerSystem.cs +++ b/Explorer/Assets/DCL/SDKComponents/TriggerArea/Systems/TriggerAreaHandlerSystem.cs @@ -97,13 +97,19 @@ protected override void Update(float t) [All(typeof(TransformComponent))] private void SetupTriggerArea(Entity entity, in PBTriggerArea pbTriggerArea) { + ColliderLayer mask = pbTriggerArea.GetColliderLayer(); + + // Fast-path: mask is EXACTLY CL_MAIN_PLAYER — SDKEntityTriggerArea.OnTriggerEnter + // early-outs on any collider that isn't the local player. + bool targetOnlyMainPlayer = mask == ColliderLayer.ClMainPlayer; + World.Add( entity, new SDKEntityTriggerAreaComponent( areaSize: Vector3.zero, - targetOnlyMainPlayer: false, + targetOnlyMainPlayer: targetOnlyMainPlayer, meshType: (SDKEntityTriggerAreaMeshType)pbTriggerArea.GetMeshType(), - layerMask: pbTriggerArea.GetColliderLayer()), + layerMask: mask), new TriggerAreaComponent() ); } @@ -151,10 +157,16 @@ private void PropagateResultComponent(in Entity triggerAreaEntity, in CRDTEntity { Entity avatarEntity = Entity.Null; ColliderSceneEntityInfo entityInfo = default; - if (triggerEntityCollider.gameObject.layer == PhysicsLayers.CHARACTER_LAYER - || triggerEntityCollider.gameObject.layer == PhysicsLayers.OTHER_AVATARS_LAYER) + int colliderLayer = triggerEntityCollider.gameObject.layer; + bool isMainAvatar = colliderLayer == PhysicsLayers.CHARACTER_LAYER; + bool isRemoteAvatar = colliderLayer == PhysicsLayers.OTHER_AVATARS_LAYER; + if (isMainAvatar || isRemoteAvatar) { - if (!PhysicsLayers.LayerMaskContainsTargetLayer(areaLayerMask, ColliderLayer.ClPlayer) + // Additive: main player matches (CL_PLAYER | CL_MAIN_PLAYER); remote avatars match CL_PLAYER only. + ColliderLayer expected = isMainAvatar + ? (ColliderLayer.ClPlayer | ColliderLayer.ClMainPlayer) + : ColliderLayer.ClPlayer; + if ((areaLayerMask & expected) == 0 || !TryGetAvatarEntity(triggerEntityCollider.transform, out avatarEntity)) return; } @@ -182,7 +194,14 @@ private void PropagateResultComponent(in Entity triggerAreaEntity, in CRDTEntity triggerEntityScale = characterTransform.Transform.localScale.ToProtoVector(); } - uint triggerLayers = avatarEntity == Entity.Null ? (uint)entityInfo.SDKLayer : (uint)ColliderLayer.ClPlayer; + // Report the intersection of the area mask with the bits the avatar carries. + uint triggerLayers; + if (avatarEntity == Entity.Null) + triggerLayers = (uint)entityInfo.SDKLayer; + else if (isMainAvatar) + triggerLayers = (uint)((ColliderLayer.ClPlayer | ColliderLayer.ClMainPlayer) & areaLayerMask); + else + triggerLayers = (uint)(ColliderLayer.ClPlayer & areaLayerMask); uint triggerEntity; if (avatarEntity != Entity.Null) diff --git a/Explorer/Assets/DCL/SDKComponents/TriggerArea/Tests/TriggerAreaHandlerSystemShould.cs b/Explorer/Assets/DCL/SDKComponents/TriggerArea/Tests/TriggerAreaHandlerSystemShould.cs index 442ce8fd657..9505373f019 100644 --- a/Explorer/Assets/DCL/SDKComponents/TriggerArea/Tests/TriggerAreaHandlerSystemShould.cs +++ b/Explorer/Assets/DCL/SDKComponents/TriggerArea/Tests/TriggerAreaHandlerSystemShould.cs @@ -201,6 +201,248 @@ public void PropagateOnTriggerExit() Object.DestroyImmediate(colliderGO); } + [Test] + public void MainPlayerMask_FiresAgainstCharacterLayer() + { + // Arrange: trigger area with CL_MAIN_PLAYER only + SetupAreaWithSDKLayer(ColliderLayer.ClMainPlayer); + + var colliderGO = new GameObject("MainPlayerCollider"); + colliderGO.layer = PhysicsLayers.CHARACTER_LAYER; + BoxCollider box = colliderGO.AddComponent(); + + ClearCapturedResult(); + SetupCRDTWriterCapture(); + + // No global avatar entity in the fixture, so FindAvatarUtils returns false and no + // result is emitted — verifies the mask passed and the avatar lookup short-circuited. + world.Get(entity).SetMonoBehaviour(CreateAndAttachAreaMonoBehaviour(entity)); + var comp = world.Get(entity); + comp.TryClear(); + comp.monoBehaviour!.OnTriggerEnter(box); + + system.Update(0); + + // collidersSceneCache must not be consulted for an avatar-layer collider — + // proves the mask passed the avatar gate and reached TryGetAvatarEntity. + collidersSceneCache.DidNotReceive().TryGetEntity(box, out Arg.Any()); + + Object.DestroyImmediate(colliderGO); + } + + [Test] + public void MainPlayerMask_DoesNotFireAgainstOtherAvatarsLayer() + { + // Arrange: trigger area with CL_MAIN_PLAYER only + SetupAreaWithSDKLayer(ColliderLayer.ClMainPlayer); + + var colliderGO = new GameObject("RemoteAvatarCollider"); + colliderGO.layer = PhysicsLayers.OTHER_AVATARS_LAYER; + BoxCollider box = colliderGO.AddComponent(); + + ClearCapturedResult(); + SetupCRDTWriterCapture(); + + world.Get(entity).SetMonoBehaviour(CreateAndAttachAreaMonoBehaviour(entity)); + var comp = world.Get(entity); + comp.TryClear(); + comp.monoBehaviour!.OnTriggerEnter(box); + + system.Update(0); + + // CL_MAIN_PLAYER must not match remote avatars: the handler must return early + // before TryGetAvatarEntity is called, so the scene-cache lookup is skipped. + collidersSceneCache.DidNotReceive().TryGetEntity(box, out Arg.Any()); + Assert.IsNull(capturedResult); + + Object.DestroyImmediate(colliderGO); + } + + [Test] + public void MainPlayer_DoesNotFireOnPhysicsMask() + { + // CL_PHYSICS is not a player-qualifying bit — main player must not fire the trigger. + SetupAreaWithSDKLayer(ColliderLayer.ClPhysics); + + var colliderGO = new GameObject("MainPlayerCollider_Physics"); + colliderGO.layer = PhysicsLayers.CHARACTER_LAYER; + BoxCollider box = colliderGO.AddComponent(); + + ClearCapturedResult(); + SetupCRDTWriterCapture(); + + world.Get(entity).SetMonoBehaviour(CreateAndAttachAreaMonoBehaviour(entity)); + var comp = world.Get(entity); + comp.TryClear(); + comp.monoBehaviour!.OnTriggerEnter(box); + + system.Update(0); + + // The handler must return early in the avatar-overlap branch before the scene-cache + // lookup, so TryGetEntity is not consulted and no result is dispatched. + collidersSceneCache.DidNotReceive().TryGetEntity(box, out Arg.Any()); + Assert.IsNull(capturedResult); + + Object.DestroyImmediate(colliderGO); + } + + [Test] + public void RemoteAvatar_DoesNotFireOnPhysicsMask() + { + // Arrange: trigger area with CL_PHYSICS only. Remote avatars (OTHER_AVATARS_LAYER) have + // no client-side physics body, so CL_PHYSICS must NOT match them. + SetupAreaWithSDKLayer(ColliderLayer.ClPhysics); + + var colliderGO = new GameObject("RemoteAvatarCollider_Physics"); + colliderGO.layer = PhysicsLayers.OTHER_AVATARS_LAYER; + BoxCollider box = colliderGO.AddComponent(); + + ClearCapturedResult(); + SetupCRDTWriterCapture(); + + world.Get(entity).SetMonoBehaviour(CreateAndAttachAreaMonoBehaviour(entity)); + var comp = world.Get(entity); + comp.TryClear(); + comp.monoBehaviour!.OnTriggerEnter(box); + + system.Update(0); + + // CL_PHYSICS must not match remote avatars: the handler must return early + // before TryGetAvatarEntity is called, so the scene-cache lookup is skipped. + collidersSceneCache.DidNotReceive().TryGetEntity(box, out Arg.Any()); + Assert.IsNull(capturedResult); + + Object.DestroyImmediate(colliderGO); + } + + [Test] + public void MainPlayerOnlyMask_SetsTargetOnlyMainPlayer() + { + // Arrange: a CL_MAIN_PLAYER-only mask should set targetOnlyMainPlayer = true. + var pbTriggerArea = new PBTriggerArea + { + Mesh = TriggerAreaMeshType.TamtBox, + CollisionMask = (uint)ColliderLayer.ClMainPlayer, + }; + + world.Add(entity, pbTriggerArea); + system.Update(0); + + // Run TryAssignArea: monoBehaviour.TargetTransform should be the main player transform. + var pool = Substitute.For>(); + var area = CreateAndAttachAreaMonoBehaviour(entity); + pool.Get().Returns(area); + + var mainPlayerGO = new GameObject("MainPlayerProxy"); + try + { + var comp = world.Get(entity); + var transformComp = world.Get(entity); + comp.TryAssignArea(pool, mainPlayerGO.transform, transformComp); + + Assert.AreSame(mainPlayerGO.transform, area.TargetTransform, + "Expected TargetTransform to be assigned for a CL_MAIN_PLAYER-only mask."); + } + finally + { + Object.DestroyImmediate(mainPlayerGO); + } + } + + [Test] + public void PlayerMask_DoesNotSetTargetOnlyMainPlayer() + { + // CL_PLAYER alone must NOT enable targetOnlyMainPlayer — would reject remote avatars. + var pbTriggerArea = new PBTriggerArea + { + Mesh = TriggerAreaMeshType.TamtBox, + CollisionMask = (uint)ColliderLayer.ClPlayer, + }; + + world.Add(entity, pbTriggerArea); + system.Update(0); + + var pool = Substitute.For>(); + var area = CreateAndAttachAreaMonoBehaviour(entity); + pool.Get().Returns(area); + + var mainPlayerGO = new GameObject("MainPlayerProxy_PlayerMask"); + try + { + var comp = world.Get(entity); + var transformComp = world.Get(entity); + comp.TryAssignArea(pool, mainPlayerGO.transform, transformComp); + + Assert.IsNull(area.TargetTransform, + "Expected TargetTransform to remain null for a CL_PLAYER mask."); + } + finally + { + Object.DestroyImmediate(mainPlayerGO); + } + } + + [Test] + public void PlayerAndMainPlayerMask_DoesNotSetTargetOnlyMainPlayer() + { + // Arrange: CL_PLAYER | CL_MAIN_PLAYER must NOT route through targetOnlyMainPlayer — + // it must accept remote avatars (via CL_PLAYER) as well. + var pbTriggerArea = new PBTriggerArea + { + Mesh = TriggerAreaMeshType.TamtBox, + CollisionMask = (uint)(ColliderLayer.ClPlayer | ColliderLayer.ClMainPlayer), + }; + + world.Add(entity, pbTriggerArea); + system.Update(0); + + var pool = Substitute.For>(); + var area = CreateAndAttachAreaMonoBehaviour(entity); + pool.Get().Returns(area); + + var mainPlayerGO = new GameObject("MainPlayerProxy_CombinedMask"); + try + { + var comp = world.Get(entity); + var transformComp = world.Get(entity); + comp.TryAssignArea(pool, mainPlayerGO.transform, transformComp); + + Assert.IsNull(area.TargetTransform, + "Expected TargetTransform to remain null for a CL_PLAYER|CL_MAIN_PLAYER mask."); + } + finally + { + Object.DestroyImmediate(mainPlayerGO); + } + } + + [Test] + public void PlayerMask_DoesNotFireAgainstCharacterLayerAvatarLookupFails() + { + // Arrange: trigger area with CL_PLAYER -- should accept main + remote + SetupAreaWithSDKLayer(ColliderLayer.ClPlayer); + + var colliderGO = new GameObject("MainAvatarColliderForPlayerMask"); + colliderGO.layer = PhysicsLayers.OTHER_AVATARS_LAYER; + BoxCollider box = colliderGO.AddComponent(); + + ClearCapturedResult(); + SetupCRDTWriterCapture(); + + world.Get(entity).SetMonoBehaviour(CreateAndAttachAreaMonoBehaviour(entity)); + var comp = world.Get(entity); + comp.TryClear(); + comp.monoBehaviour!.OnTriggerEnter(box); + + system.Update(0); + + // CL_PLAYER must accept remote avatars: it reaches TryGetAvatarEntity and + // returns without consulting the scene cache (avatar branch). + collidersSceneCache.DidNotReceive().TryGetEntity(box, out Arg.Any()); + + Object.DestroyImmediate(colliderGO); + } + private void SetupAreaWithSDKLayer(ColliderLayer layer) { var pbTriggerArea = new PBTriggerArea diff --git a/Explorer/Assets/DCL/SDKEntityTriggerArea/Components/SDKEntityTriggerAreaComponent.cs b/Explorer/Assets/DCL/SDKEntityTriggerArea/Components/SDKEntityTriggerAreaComponent.cs index 1587b5894ab..1f8891a1f14 100644 --- a/Explorer/Assets/DCL/SDKEntityTriggerArea/Components/SDKEntityTriggerAreaComponent.cs +++ b/Explorer/Assets/DCL/SDKEntityTriggerArea/Components/SDKEntityTriggerAreaComponent.cs @@ -92,8 +92,13 @@ public void TryAssignArea(IComponentPool pool, Transform m break; } - // Optimization to use an avatars specific physics layer if the trigger only cares about avatars - monoBehaviour!.gameObject.layer = LayerMask == ColliderLayer.ClPlayer ? PhysicsLayers.ALL_AVATARS : PhysicsLayers.SDK_ENTITY_TRIGGER_AREA; + // Route to SDK_AVATAR_TRIGGER_AREA when the mask has CL_PLAYER or CL_MAIN_PLAYER; otherwise + // fall back to SDK_ENTITY_TRIGGER_AREA. Handler-side filter narrows from there. + const ColliderLayer PLAYER_TOUCHING_BITS = ColliderLayer.ClPlayer | ColliderLayer.ClMainPlayer; + bool touchesPlayer = LayerMask != ColliderLayer.ClNone && (LayerMask & PLAYER_TOUCHING_BITS) != 0; + monoBehaviour!.gameObject.layer = touchesPlayer + ? PhysicsLayers.SDK_AVATAR_TRIGGER_AREA + : PhysicsLayers.SDK_ENTITY_TRIGGER_AREA; } public void UpdateAreaSize(Vector3 size) diff --git a/Explorer/Assets/DCL/SDKEntityTriggerArea/Tests/PlayMode/SDKEntityTriggerAreaHandlerSystemShould.cs b/Explorer/Assets/DCL/SDKEntityTriggerArea/Tests/PlayMode/SDKEntityTriggerAreaHandlerSystemShould.cs index ed01de1a745..e1628997091 100644 --- a/Explorer/Assets/DCL/SDKEntityTriggerArea/Tests/PlayMode/SDKEntityTriggerAreaHandlerSystemShould.cs +++ b/Explorer/Assets/DCL/SDKEntityTriggerArea/Tests/PlayMode/SDKEntityTriggerAreaHandlerSystemShould.cs @@ -397,7 +397,7 @@ public async Task UsePlayerLayerByDefault() } [Test] - public async Task UseAllAvatarsPhysicsLayerWhenOnlyTargetingPlayers() + public async Task UseSDKAvatarTriggerAreaLayerWhenOnlyTargetingPlayers() { // Workaround for Unity bug not awaiting async Setup correctly await UniTask.WaitUntil(() => system != null); @@ -407,7 +407,7 @@ public async Task UseAllAvatarsPhysicsLayerWhenOnlyTargetingPlayers() system.Update(0); - Assert.AreEqual(PhysicsLayers.ALL_AVATARS, sdkEntityTriggerArea.gameObject.layer); + Assert.AreEqual(PhysicsLayers.SDK_AVATAR_TRIGGER_AREA, sdkEntityTriggerArea.gameObject.layer); component = new SDKEntityTriggerAreaComponent(areaSize: Vector3.one * 4, layerMask: ColliderLayer.ClPlayer | ColliderLayer.ClCustom6); world.Set(entity, component); diff --git a/Explorer/Assets/Protocol/DecentralandProtocol/MeshCollider.gen.cs b/Explorer/Assets/Protocol/DecentralandProtocol/MeshCollider.gen.cs index e4da4821054..31a9d435e1d 100644 --- a/Explorer/Assets/Protocol/DecentralandProtocol/MeshCollider.gen.cs +++ b/Explorer/Assets/Protocol/DecentralandProtocol/MeshCollider.gen.cs @@ -37,14 +37,14 @@ static MeshColliderReflection() { "YWRpdXNfdG9wGAEgASgCSACIAQESGgoNcmFkaXVzX2JvdHRvbRgCIAEoAkgB", "iAEBQg0KC19yYWRpdXNfdG9wQhAKDl9yYWRpdXNfYm90dG9tGgsKCVBsYW5l", "TWVzaBoMCgpTcGhlcmVNZXNoQgYKBG1lc2hCEQoPX2NvbGxpc2lvbl9tYXNr", - "KrACCg1Db2xsaWRlckxheWVyEgsKB0NMX05PTkUQABIOCgpDTF9QT0lOVEVS", - "EAESDgoKQ0xfUEhZU0lDUxACEg0KCUNMX1BMQVlFUhAEEhAKDENMX1JFU0VS", - "VkVEMhAIEhAKDENMX1JFU0VSVkVEMxAQEhAKDENMX1JFU0VSVkVENBAgEhAK", - "DENMX1JFU0VSVkVENRBAEhEKDENMX1JFU0VSVkVENhCAARIPCgpDTF9DVVNU", - "T00xEIACEg8KCkNMX0NVU1RPTTIQgAQSDwoKQ0xfQ1VTVE9NMxCACBIPCgpD", - "TF9DVVNUT000EIAQEg8KCkNMX0NVU1RPTTUQgCASDwoKQ0xfQ1VTVE9NNhCA", - "QBIQCgpDTF9DVVNUT003EICAARIQCgpDTF9DVVNUT004EICAAkIUqgIRRENM", - "LkVDU0NvbXBvbmVudHNiBnByb3RvMw==")); + "KrICCg1Db2xsaWRlckxheWVyEgsKB0NMX05PTkUQABIOCgpDTF9QT0lOVEVS", + "EAESDgoKQ0xfUEhZU0lDUxACEg0KCUNMX1BMQVlFUhAEEhIKDkNMX01BSU5f", + "UExBWUVSEAgSEAoMQ0xfUkVTRVJWRUQzEBASEAoMQ0xfUkVTRVJWRUQ0ECAS", + "EAoMQ0xfUkVTRVJWRUQ1EEASEQoMQ0xfUkVTRVJWRUQ2EIABEg8KCkNMX0NV", + "U1RPTTEQgAISDwoKQ0xfQ1VTVE9NMhCABBIPCgpDTF9DVVNUT00zEIAIEg8K", + "CkNMX0NVU1RPTTQQgBASDwoKQ0xfQ1VTVE9NNRCAIBIPCgpDTF9DVVNUT002", + "EIBAEhAKCkNMX0NVU1RPTTcQgIABEhAKCkNMX0NVU1RPTTgQgIACQhSqAhFE", + "Q0wuRUNTQ29tcG9uZW50c2IGcHJvdG8z")); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { }, new pbr::GeneratedClrTypeInfo(new[] {typeof(global::DCL.ECSComponents.ColliderLayer), }, null, new pbr::GeneratedClrTypeInfo[] { @@ -75,10 +75,13 @@ public enum ColliderLayer { /// [pbr::OriginalName("CL_PHYSICS")] ClPhysics = 2, /// - /// layer corresponding to any player avatar + /// layer corresponding to any player avatar (main + remote) /// [pbr::OriginalName("CL_PLAYER")] ClPlayer = 4, - [pbr::OriginalName("CL_RESERVED2")] ClReserved2 = 8, + /// + /// layer corresponding to the local (main) player avatar + /// + [pbr::OriginalName("CL_MAIN_PLAYER")] ClMainPlayer = 8, [pbr::OriginalName("CL_RESERVED3")] ClReserved3 = 16, [pbr::OriginalName("CL_RESERVED4")] ClReserved4 = 32, [pbr::OriginalName("CL_RESERVED5")] ClReserved5 = 64, diff --git a/Explorer/ProjectSettings/DynamicsManager.asset b/Explorer/ProjectSettings/DynamicsManager.asset index 5eed4b0e9f3..20267c885fd 100644 --- a/Explorer/ProjectSettings/DynamicsManager.asset +++ b/Explorer/ProjectSettings/DynamicsManager.asset @@ -3,7 +3,7 @@ --- !u!55 &1 PhysicsManager: m_ObjectHideFlags: 0 - serializedVersion: 18 + serializedVersion: 23 m_Gravity: {x: 0, y: -9.81, z: 0} m_DefaultMaterial: {fileID: 0} m_BounceThreshold: 2 @@ -17,7 +17,7 @@ PhysicsManager: m_EnableAdaptiveForce: 0 m_ClothInterCollisionDistance: 0 m_ClothInterCollisionStiffness: 0 - m_LayerCollisionMatrix: 04000904040000000700000000000000000000000000000000000100000001000000000000000800000000000000000000000000000000000000000000000000c1000e000000090000000140010203440000000000000000000000000000000000000000000000000100080000000000000000000000000000000c0000000000 + m_LayerCollisionMatrix: 04000904040000000700000000000000000000000000000000000100000001000000000000000800000000000000000000000000000000000000000000000000c1000e0000000900000011400102134400000c0000000000000000000000000000000000000000000100080000000000000000000000000000000c0000000000 m_SimulationMode: 2 m_AutoSyncTransforms: 0 m_ReuseCollisionCallbacks: 1 @@ -26,11 +26,20 @@ PhysicsManager: m_ClothGravity: {x: 0, y: -9.81, z: 0} m_ContactPairsMode: 0 m_BroadphaseType: 0 + m_WorldBounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 256, y: 256, z: 256} + m_WorldSubdivisions: 8 m_FrictionType: 0 m_EnableEnhancedDeterminism: 0 m_ImprovedPatchFriction: 0 + m_GenerateOnTriggerStayEvents: 1 m_SolverType: 0 m_DefaultMaxAngularSpeed: 7 m_ScratchBufferChunkCount: 4 m_CurrentBackendId: 4072204805 m_FastMotionThreshold: 3.4028235e+38 + m_SceneBuffersReleaseInterval: 0 + m_ReleaseSceneBuffers: 0 + m_LogVerbosity: 3 + m_IncrementalStaticBroadphase: 1 diff --git a/Explorer/ProjectSettings/TagManager.asset b/Explorer/ProjectSettings/TagManager.asset index 6800afa0e5f..945649aad6c 100644 --- a/Explorer/ProjectSettings/TagManager.asset +++ b/Explorer/ProjectSettings/TagManager.asset @@ -24,9 +24,9 @@ TagManager: - - CharacterController - CharacterOnly - - AllAvatars + - SDKAvatarTriggerArea - SDKEntityTriggerArea - - + - SDKAvatarHit - - - @@ -56,3 +56,4 @@ TagManager: locked: 0 m_RenderingLayers: - Default + m_MigratedRenderPipelines: [] diff --git a/docs/how-to-implement-new-sdk-components.md b/docs/how-to-implement-new-sdk-components.md index 365083860c0..80035c194a8 100644 --- a/docs/how-to-implement-new-sdk-components.md +++ b/docs/how-to-implement-new-sdk-components.md @@ -167,6 +167,64 @@ The following must be implemented: - Usage of `ISceneStateProvider.IsCurrent` to escape the `Update()` of the system. - (more complex and powerful) Usage of `ISceneIsCurrentListener` to listen to the moments the user enters/exits the scene. +#### ColliderLayer mask semantics + +Several SDK components carry a `collision_mask` / `collisionMask` field of type `ColliderLayer` (defined in `decentraland/sdk/components/common/mesh_collider.proto`). The full enum is: + +| Name | Value | Meaning | +|---|---|---| +| `CL_NONE` | 0 | No collisions. | +| `CL_POINTER` | 1 | Pointer-ray collisions (mouse hover, click). | +| `CL_PHYSICS` | 2 | Scene-mesh walls / floors / platforms that affect the player's physics. **Does NOT target the character itself** — see "Unified main-player qualification rule" below. | +| `CL_PLAYER` | 4 | Any player avatar — main player AND remote avatars. | +| `CL_MAIN_PLAYER` | 8 | The local (main) player avatar only. | +| `CL_RESERVED3..6` | 16/32/64/128 | Reserved — do not use. | +| `CL_CUSTOM1..8` | 256..32768 | Scene-defined custom layers (8 of them). | + +**Additive avatar semantics.** The main player capsule is "tagged" with both `CL_PLAYER` and `CL_MAIN_PLAYER`: a mask containing either bit matches it. Remote avatars are tagged only with `CL_PLAYER`. Consequently: + +| Mask | Main player matches? | Remote avatar matches? | +|---|---|---| +| `CL_PLAYER` | yes | yes | +| `CL_MAIN_PLAYER` | yes | no | +| `CL_PLAYER \| CL_MAIN_PLAYER` | yes | yes | + +**Unified main-player qualification rule.** Both **Raycast** (`ExecuteRaycastSystem.DoesHitColliderQualify`) and **TriggerArea** (`TriggerAreaHandlerSystem.PropagateResultComponent`) qualify the main player using the same constant: + +```csharp +PhysicsLayers.PLAYER_QUALIFYING_BITS = CL_PLAYER | CL_MAIN_PLAYER +``` + +A scene mask that contains either of those two bits qualifies the local player. Any other mask (`CL_PHYSICS`, `CL_POINTER`, `CL_CUSTOM*`, `CL_NONE`) does NOT qualify the main player on either system. + +`CL_PHYSICS` is **deliberately excluded** from this set. Per the proto definition, `CL_PHYSICS` targets *scene-mesh walls and floors* that affect the player's physics — not the character itself. This matches the convention of every major game engine: + +- **Unreal**: `WorldStatic` / `WorldDynamic` vs the separate `Pawn` channel. +- **Unity (idiomatic)**: project layers like `Environment` vs `Player`/`Character`. +- **Godot**: world geometry on its own layer; characters on a separate `Player` layer. +- **Source / Half-Life 2**: `MASK_NPCWORLDSTATIC` (world without entities) vs masks that opt entities in. + +Scenes that need to detect the local player on a raycast or trigger area must opt in via `CL_PLAYER` or `CL_MAIN_PLAYER` explicitly. + +Remote avatars qualify only on `CL_PLAYER`. They have no client-side physics body either way. + +**TriggerArea `targetOnlyMainPlayer` optimisation.** When the scene mask is **exactly** `CL_MAIN_PLAYER` (no other bits), `TriggerAreaHandlerSystem.SetupTriggerArea` sets `targetOnlyMainPlayer = true`. The underlying `SDKEntityTriggerArea` MonoBehaviour then short-circuits any `OnTriggerEnter`/`OnTriggerExit` whose collider transform isn't the main player's, dropping remote-avatar overlaps before they reach the per-frame query in `PropagateResultComponent`. Any other mask (including `CL_PLAYER | CL_MAIN_PLAYER`) must NOT enable this optimisation, because it would also reject the colliders the mask is meant to match. + +**Scene-mesh routing (`MeshCollider` / `GltfContainer`).** `PhysicsLayers.TryGetUnityLayerFromSDKLayer` picks the destination Unity layer per SDK mask: + +| SDK mask | Unity layer | Effect on player capsule | +|---|---|---| +| `CL_PHYSICS \| CL_POINTER` (Default mask) | `Default` | Solid (player blocks) | +| `CL_PHYSICS` alone | `CharacterOnly` | Solid | +| `CL_POINTER` alone | `OnPointerEvent` | Pass-through | +| `CL_PLAYER` and/or `CL_MAIN_PLAYER` (no other bits) | `SDKAvatarHit` | Pass-through; only trigger areas / raycasts targeting avatar bits detect it | +| Any mask containing `CL_PHYSICS` (mixed) | `CharacterOnly` (physics wins) | Solid | +| Custom layers | `SDKCustomLayer` | Pass-through | + +`SDKAvatarHit` is matrix-configured to only overlap with `SDKEntityTriggerArea` and `SDKAvatarTriggerArea`, so the player capsule walks straight through avatar-tagged scene meshes while trigger areas and avatar-targeting raycasts still pick them up. + +**Migration note for scenes pre-dating this rule.** A scene that previously used `CL_PHYSICS` raycasts to detect the local player (e.g. line-of-sight checks) must now OR `CL_MAIN_PLAYER` (or `CL_PLAYER`) into the mask. Walls-and-floors checks that don't want to detect the player keep working unchanged. + #### "Perfect implementation" checks before shipping Especially for big implementations: diff --git a/scripts/package-lock.json b/scripts/package-lock.json index 5cec8d93c8c..fe9c4cb13ad 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@dcl/protocol": "^1.0.0-24513643863.commit-4851fd6", + "@dcl/protocol": "https://sdk-team-cdn.decentraland.org/@dcl/protocol/branch//dcl-protocol-1.0.0-26316313514.commit-8ee7c54.tgz", "@protobuf-ts/protoc": "^2.8.2", "@types/fs-extra": "^11.0.1", "@types/glob": "^8.0.1", @@ -37,9 +37,9 @@ } }, "node_modules/@dcl/protocol": { - "version": "1.0.0-24513643863.commit-4851fd6", - "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-24513643863.commit-4851fd6.tgz", - "integrity": "sha512-AbQ1exc37Uve3N7egafyBnJbIfH7Ppyq6iGMYVZgGnNNuWXhV0RIv3bNV9CMEYWE/E0Ryjq4Ngzst2MWfIVtog==", + "version": "1.0.0-26316313514.commit-8ee7c54", + "resolved": "https://sdk-team-cdn.decentraland.org/@dcl/protocol/branch//dcl-protocol-1.0.0-26316313514.commit-8ee7c54.tgz", + "integrity": "sha512-lp7F9ZlSjBFtbB9YfcBg07ycsCYkhzcZ+FHkhiuRv7twWxOtRvXuICT6LD761U13wEFpOuB/UcrJNXPDYxwAyw==", "license": "Apache-2.0", "dependencies": { "@dcl/ts-proto": "1.154.0", @@ -110,25 +110,24 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", "license": "BSD-3-Clause", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@protobufjs/aspromise": "^1.1.1" } }, "node_modules/@protobufjs/float": { @@ -138,9 +137,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { @@ -156,9 +155,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, "node_modules/@tsconfig/node10": { @@ -603,9 +602,8 @@ } }, "@dcl/protocol": { - "version": "1.0.0-24513643863.commit-4851fd6", - "resolved": "https://registry.npmjs.org/@dcl/protocol/-/protocol-1.0.0-24513643863.commit-4851fd6.tgz", - "integrity": "sha512-AbQ1exc37Uve3N7egafyBnJbIfH7Ppyq6iGMYVZgGnNNuWXhV0RIv3bNV9CMEYWE/E0Ryjq4Ngzst2MWfIVtog==", + "version": "https://sdk-team-cdn.decentraland.org/@dcl/protocol/branch//dcl-protocol-1.0.0-26316313514.commit-8ee7c54.tgz", + "integrity": "sha512-lp7F9ZlSjBFtbB9YfcBg07ycsCYkhzcZ+FHkhiuRv7twWxOtRvXuICT6LD761U13wEFpOuB/UcrJNXPDYxwAyw==", "requires": { "@dcl/ts-proto": "1.154.0", "protobufjs": "7.2.4" @@ -663,22 +661,21 @@ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==" }, "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==" }, "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@protobufjs/aspromise": "^1.1.1" } }, "@protobufjs/float": { @@ -687,9 +684,9 @@ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" }, "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==" }, "@protobufjs/path": { "version": "1.1.2", @@ -702,9 +699,9 @@ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" }, "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==" }, "@tsconfig/node10": { "version": "1.0.9", diff --git a/scripts/package.json b/scripts/package.json index 64bcb41177f..c5bc648735d 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -16,7 +16,7 @@ "typescript": "^4.2.3" }, "dependencies": { - "@dcl/protocol": "^1.0.0-24513643863.commit-4851fd6", + "@dcl/protocol": "https://sdk-team-cdn.decentraland.org/@dcl/protocol/branch//dcl-protocol-1.0.0-26316313514.commit-8ee7c54.tgz", "@protobuf-ts/protoc": "^2.8.2", "@types/fs-extra": "^11.0.1", "@types/glob": "^8.0.1",