Skip to content

feat: sdk teleportTo realm coords support - #10047

Open
pravusjif wants to merge 6 commits into
devfrom
feat/sdk-teleport-to-realm-coords
Open

feat: sdk teleportTo realm coords support#10047
pravusjif wants to merge 6 commits into
devfrom
feat/sdk-teleport-to-realm-coords

Conversation

@pravusjif

@pravusjif pravusjif commented Sep 9, 2026

Copy link
Copy Markdown
Member
  • Implemented support for new optional use of coordiantes in the TeleportTo restricted action.
  • Test coverage
  • Protocol update

QA TEST STEPS

Use the build from this PR to enter the ZONE/SEPOLIA world sdk7testscenes.dcl.eth at position 1,2 and confirm:

  • You see different portals to other scenes in this realm
  • If you click and accept the teleport from one of them, you get teleported to the correct scene

@pravusjif pravusjif self-assigned this Sep 9, 2026
@pravusjif pravusjif moved this to In Progress in Creators Tools Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Windows and Mac built successfully in Unity Cloud.

Name Links & timing
Build ab11728 · Logs · built 2026-09-10T20:16:41Z
Windows GitHub job · Unity Cloud #9 · Unity log · ⏱ 56m 40s build + 8m 16s queue · Download .zip · .zip via S3
Mac GitHub job · Unity Cloud #9 · Unity log · ⏱ 1h 32m build + 2m 13s queue · Download .zip · .zip via S3

Lint

Warnings not reduced: 12025 => 12025 — remove at least 1 warning to merge.

Warnings/errors in files changed by this PR (1)
Assets/DCL/Infrastructure/SceneRuntime/Apis/Modules/RestrictedActionsApi/RestrictedActionsAPIWrapper.cs:86  RedundantNameQualifier  Qualifier is redundant

Lint run · full InspectCode report · took 25m 34s

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped Tests time Job time
EditMode ✅ Passed 25848 0 13 4m 32s 19m 55s
PlayMode ✅ Passed 248 0 37 44s 16m 20s

Tests time sums the test cases; Job time is the job's wall clock including checkout, licensing and asset import.

Slowest tests
  • [editmode] 16.5s DCL.AuthenticationScreenFlow.Tests.ProfileFetchingAuthStateShould.CancelStalledFetchOnTimeout
  • [editmode] 15.3s DCL.Tests.Editor.ValidationTests.CheckForDebugUsage
  • [editmode] 13.0s DCL.Tests.Editor.ValidationTests.CheckUnityObjectsForMissingReferences
  • [editmode] 10.0s DCL.Notifications.Tests.NotificationsRequestControllerShould.ReuseSingleListInstanceAcrossPollIterations
  • [editmode] 5.7s DCL.Tests.Editor.ValidationTests.SettingsAreValid
  • [editmode] 5.0s DCL.Friends.Tests.FriendsConnectivityStatusTrackerShould.RaiseOnlineEventWhenSameStatusIsRebroadcastAfterReset
  • [editmode] 5.0s CrdtEcsBridge.WorldSynchronizer.Tests.CrdtWorldSynchronizerShould.ThrowIfSyncBufferIsAlreadyRented
  • [editmode] 4.5s DCL.AvatarRendering.AvatarShape.Tests.FinishAvatarMatricesCalculationSystemShould.PlaceTheAvatarBoundsInTheWorldTheSameWayTheReferenceFormDoes
  • [editmode] 4.2s SceneRunner.Tests.SceneFacadeShould.ContinueUpdateLoopOnBackgroundThread(180,4000)
  • [editmode] 4.1s SceneRunner.Tests.SceneFacadeShould.ContinueUpdateLoopOnBackgroundThread(60,4000)
  • [playmode] 4.2s Global.Tests.PlayMode.CubeWaveSceneShould.EmitECSComponents
  • [playmode] 3.0s DCL.SDKComponents.Tween.Tests.TweenUpdaterSystemShould.ContinuousTweensRunIndefinitelyWhenDurationIsZero
  • [playmode] 2.4s DCL.SDKComponents.Tween.Tests.TweenSequenceSystemShould.TextureMoveSequenceUpdatesMaterial
  • [playmode] 2.3s DCL.SDKComponents.Tween.Tests.TweenSequenceSystemShould.TweenSequenceWithoutLoopCompletesOnce
  • [playmode] 2.3s DCL.SDKComponents.Tween.Tests.TweenUpdaterSystemShould.TextureMoveContinuousOffsetCompletesAndUpdatesMaterial
  • [playmode] 2.3s DCL.SDKComponents.Tween.Tests.TweenUpdaterSystemShould.RotateContinuousCompletesAfterDuration
  • [playmode] 2.2s DCL.SDKComponents.Tween.Tests.TweenUpdaterSystemShould.MoveContinuousMovesAndCompletesAfterDuration
  • [playmode] 1.7s DCL.SDKComponents.Tween.Tests.TweenSequenceSystemShould.TweenSequenceWithMoveRotateScaleWithOmittedScale_ResolvesScaleFromCurrentTransform
  • [playmode] 1.6s DCL.SDKComponents.Tween.Tests.TweenUpdaterSystemShould.RotateContinuousAroundXAxisRotatesAroundXNotZ
  • [playmode] 1.5s DCL.SDKComponents.Tween.Tests.TweenSequenceSystemShould.TweenSequenceWithMultipleTweens

Full report: run summary · results + editor logs: editmode · playmode

Performance

🏁 Bare-metal benchmark finished — run #34525474443.

Full report

PR #10047, run #34525474443

Overall: ✅ no significant changes

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

How to read this table
  • Each build is measured 3 times, interleaved with the other build (change, baseline, change, baseline, ...) in the same session, so both see the same world content and machine state. The values are the median, and (min–max) is the lowest and highest of those runs.
  • Δ is Change minus Baseline (a negative Δ means Change is faster).
  • 🟢 faster / 🔴 slower — a difference that passed every check: the runs are fully separated (every run of one build faster than every run of the other), and the median difference is at least 3% and at least 0.5 ms.
  • ⚪ within noise — the builds' runs overlap, or the difference is tiny; it cannot be told apart from random variation. Treat it as no change.
  • — informational — the 0.1% worst metrics average only the few worst frames of a run, so a single OS hiccup swings them by a lot; they are shown for context and never earn a verdict.
  • ⚠️ no verdict — the two builds' sessions were not comparable (very different sample counts, or too few usable runs), so no conclusion is drawn from them.
  • Exceptions per run — the average number of exceptions in a run's log, not counting teardown ones logged while the app quits. Flagged only on a difference of at least 2 per run and 1.5× the other build; exception kinds the baseline never threw are called out under the table. The Exception breakdown groups all of them by the explorer's report category and exception type (as totals across the runs).
  • A run that logged unusually many exceptions (at least 10 and 5× the median of its build's runs — e.g. a service was down during it) is excluded from all numbers and called out under the table.
  • The Overall line at the top only reacts to a metric that moved on two or more machines, or by 10% or more on one — a single modest 🟢/🔴 cell can still be a statistical fluke.

Apple M1

Metric Baseline Change Δ Result
Samples 4046 (×3) 4094 (×3)
CPU average 22.2 ms (21.1–22.7) 21.8 ms (21.6–22.3) -0.4 ms ⚪ within noise
CPU 1% worst 232.0 ms (170.9–233.1) 229.3 ms (225.6–230.6) -2.7 ms ⚪ within noise
CPU 0.1% worst 239.1 ms (234.7–242.8) 236.0 ms (232.7–236.2) -3.1 ms — informational
GPU average 33.9 ms (32.6–35.4) 34.5 ms (33.0–34.6) 0.7 ms ⚪ within noise
GPU 1% worst 44.5 ms (43.6–45.6) 44.5 ms (44.2–46.8) 0.0 ms ⚪ within noise
GPU 0.1% worst 45.4 ms (45.0–46.9) 45.3 ms (45.2–48.1) -0.1 ms — informational
Exceptions per run 0 0 0 ⚪ no significant change

Intel Core i5

Metric Baseline Change Δ Result
Samples 4594 (×3) 4716 (×3)
CPU average 19.5 ms (16.5–20.5) 18.9 ms (15.4–20.4) -0.5 ms ⚪ within noise
CPU 1% worst 360.2 ms (204.2–402.7) 328.5 ms (211.5–384.4) -31.7 ms ⚪ within noise
CPU 0.1% worst 402.7 ms (388.2–433.1) 390.4 ms (279.5–420.3) -12.3 ms — informational
GPU average 11.9 ms (10.1–12.8) 11.4 ms (8.9–12.0) -0.4 ms ⚪ within noise
GPU 1% worst 194.7 ms (111.9–257.8) 152.4 ms (94.2–174.8) -42.3 ms ⚪ within noise
GPU 0.1% worst 400.8 ms (389.1–431.6) 388.2 ms (277.4–413.9) -12.6 ms — informational
Exceptions per run 0 0 0 ⚪ no significant change

Automation

On demand — comment /visual-tests on this PR to run the visual regression suite against its build.

@pravusjif
pravusjif marked this pull request as ready for review September 10, 2026 12:39
@pravusjif
pravusjif requested review from a team as code owners September 10, 2026 12:39
@pravusjif pravusjif moved this from In Progress to QA in Creators Tools Sep 10, 2026
@decentraland-bot
decentraland-bot self-requested a review September 10, 2026 12:39
@github-actions
github-actions Bot requested review from dalkia and popuz September 10, 2026 12:40

@decentraland-bot decentraland-bot left a comment

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.

PR Review — feat: sdk teleportTo realm coords support

Inspected commits: base 2831b06 → head 141ff48


STEP 1 — Scope

The diff touches 9 files (5 hand-written C#, 1 JS bridge, 1 auto-generated protocol C#, 2 package manifest files). Read the implementation, interface, wrappers, tests, JS module, the ChangeRealmPromptController + its Params, the callback wiring in DynamicWorldContainer, and ChatTeleporter.TeleportToRealmAsync. Searched consumers of TryTeleportTo — 3 callers: RestrictedActionsAPIWrapper, UserActionsWrapper, and tests.

STEP 2 — Root-cause check

PASS. This is a protocol-driven feature addition: the SDK's TeleportTo restricted action gains an optional realm field (and makes worldCoordinates optional). The diff implements the new routing — realm-present requests go through the existing change-realm consent prompt; coords-only requests keep the existing teleport prompt. This is a genuine feature, not a symptom fix.

STEP 3 — Design & integration

PASS. No new long-lived units introduced. The change extends the existing TryTeleportTo method signature and reuses the established ChangeRealmAsyncChangeRealmPromptControllerchangeRealmCallbackChatTeleporter.TeleportToRealmAsync pipeline.

  • The ChangeRealmPromptController.Params struct already had the Position field (prior PR).
  • The callback in DynamicWorldContainer (line 719–728) already branches on position.HasValue, routing to ChatTeleporter.TeleportToRealmAsync(realmUrl, position.Value, …) with position or the chat-command path without.
  • The ChangeRealmAsync private method gains an optional Vector2Int? position parameter — backward-compatible with the existing TryChangeRealm caller (which passes no position).

No lifecycle duplication, no per-frame reconciliation, no new persistent state. Clean integration into the existing architecture.

STEP 4 — Member audit

Member Consumers Verdict
IRestrictedActionsAPI.TryTeleportTo(Vector2Int?, string?) RestrictedActionsAPIWrapper.TeleportTo, UserActionsWrapper.RequestTeleport, 4 test methods Appropriate public surface — each combination of optional params is tested
ChangeRealmAsync(…, Vector2Int? position = null) TryTeleportTo, TryChangeRealm Optional parameter preserves backward compatibility

No single-use intermediaries, no redundant predicates.

STEP 5 — Line-level findings

See inline comments below.

STEP 6 — Complexity

COMPLEX — modifies a public interface (IRestrictedActionsAPI) used across assemblies, the JS→C# bridge, and the protocol message layer.

STEP 7 — QA assessment

QA_REQUIRED: YES — changes runtime teleport behavior (user-facing navigation).

STEP 8 — Non-blocking warnings

None. Main scene is not modified.


Security Review

Scope: Traced the attacker-controlled realm string from the SDK scene message through JS bridge → RestrictedActionsAPIWrapperTryTeleportToChangeRealmAsyncChangeRealmPromptController.

  • Consent prompt gate: Every realm-based teleport goes through ChangeRealmPromptController.ShowAsync, which shows a user consent UI before proceeding. No path bypasses it.
  • Realm display sanitization: The controller already disables rich-text parsing on message and realm text fields (SEC-003) and strips userinfo from URL realms to prevent consent-prompt spoofing (SEC-004, DestinationHostFor). These controls cover this new entry point.
  • IsCurrent guard: Present and covers all branches.
  • Dependency bump: @dcl/protocol updated from 1.0.0-33874985952.commit-4f4e0ab to 1.0.0-34381335718.commit-3c838bb — Decentraland-owned package, expected protocol update for the new optional fields. No new dependencies added. No binaries changed. No advisory matches.

Limits: npm audit not run (read-only review). No native binaries affected.

DEPENDENCY_REVIEW: PASS


REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Modifies public IRestrictedActionsAPI interface, JS↔C# bridge layer, and protocol message handling across restricted-actions subsystem
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

…ative assertion, complete XML doc

- Replace multi-line comments that narrate external behavior with concise
  descriptions of what the annotated code itself does
- Add DidNotReceive check for TeleportPromptController in
  TeleportToRealmDefaultSpawnWithoutCoordinates test
- Document the both-absent rejection case in IRestrictedActionsAPI XML doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@decentraland-bot
decentraland-bot self-requested a review September 10, 2026 12:49

@decentraland-bot decentraland-bot left a comment

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.

Review: feat: sdk teleportTo realm coords support

STEP 2 — Root-cause check

PASS. This PR adds a genuine new feature: SDK scenes can now request teleportation to a specific parcel in another realm via the TeleportTo restricted action. The protocol update makes world_coordinates optional and adds an optional realm field. The implementation routes to the correct consent prompt based on which fields are present. This is a new capability, not a symptom fix.

STEP 3 — Design & integration

PASS. The PR makes excellent use of existing infrastructure:

  • ChangeRealmPromptController.Params already had a Vector2Int? Position property (pre-existing, not added by this PR).
  • ChangeRealmAsync is extended with an optional position parameter (default null), keeping backward compatibility with the existing TryChangeRealm caller.
  • The DynamicWorldContainer callback already branched on position.HasValue to route through ChatTeleporter.TeleportToRealmAsync(realmUrl, position.Value, ct) vs the chat-command path.
  • No new lifecycle units, systems, controllers, or managers are introduced. The routing logic is a straightforward addition to the existing TryTeleportTo method.

Owner search: TryTeleportTo already owned the teleport decision; the new realm branch delegates to ChangeRealmAsync, which was already the private helper for TryChangeRealm. No lifecycle duplication.

STEP 4 — Member audit

Member Consumers Status
IRestrictedActionsAPI.TryTeleportTo(Vector2Int?, string?) RestrictedActionsAPIWrapper.TeleportTo, UserActionsWrapper.RequestTeleport, tests Interface method, multiple callers ✓
ChangeRealmAsync(string, string, Vector2Int?) TryTeleportTo (with position), TryChangeRealm (without — uses default null) Private helper, two distinct use cases ✓
RestrictedActionsAPIWrapper.TeleportTo(int?, int?, string?) JS bridge (RestrictedActions.js) Single caller, justified — it's the JS↔C# interop boundary ✓

No single-use-merge, absent≠false, or redundant-guard issues.

STEP 5 — Line-level findings

All findings are P2 (non-blocking). No P0 or P1 issues found.

[P2-1] Pre-existing parameter name mismatch between interface and implementation. The interface declares Vector2Int? newCoords while the implementation uses Vector2Int? coords. This was already present before this PR (base SHA confirmed), but since this PR touches both signatures, it's a good opportunity to align them. coords is the better name — the new prefix was vestigial from when the method only teleported within the current realm. (See inline comment.)

[P2-2] JS bridge comment narrates external behavior (CLAUDE.md §11). The comment on RestrictedActions.js lines 56–57 describes what the C# implementation does with the values, not what the JS bridge function itself does. The interface XML doc already documents the contract. (See inline comment.)

[P2-3] Wrapper silently discards partial coordinates. RestrictedActionsAPIWrapper.TeleportTo treats x.HasValue && y.HasValue as the guard — if only one is provided, coords become null. This is defensible since the JS layer always sends both or neither (they come from the same coords object), so partial coordinates can only arise from a JS-side bug. Low risk.

STEP 5 — Security review

All consent-prompt paths are preserved. Every code path through TryTeleportTo terminates at a user-facing consent prompt (ChangeRealmPromptController or TeleportPromptController). The sceneStateProvider.IsCurrent guard prevents non-focused scenes from invoking the action.

Realm string injection: mitigated. The attacker-controlled realm string passes through the existing SEC-003/SEC-004 defenses: rich-text is disabled on both prompt text fields, and DestinationHostFor() strips URL userinfo to prevent consent-prompt spoofing. The message parameter is hardcoded to string.Empty (line 115), so scenes cannot control the prompt body text via this path — slightly more restrictive than the existing changeRealm action, which is good.

Downstream validation: intact. After consent, the realm string flows through ChatEnvironmentValidator.ValidateTeleport which rejects URLs outside the configured base domain and explicitly rejects URLs containing @ (userinfo).

Supply chain:

Dependency Old version New version Source Integrity
@dcl/protocol 1.0.0-33874985952.commit-4f4e0ab 1.0.0-34381335718.commit-3c838bb npmjs.org (@dcl scope, first-party) sha512 present

The generated C# changes (RestrictedActions.gen.cs) are consistent with the expected protocol update: TeleportToRequest gains an optional realm field (field number 2) and world_coordinates becomes optional. No unexpected fields, services, or code patterns added. No binaries, native plugins, UPM packages, or assembly changes.

DEPENDENCY_REVIEW: PASS

STEP 6 — Complexity

SIMPLE. The PR touches 5 meaningful source files (~80 lines of hand-written changes), plus generated protobuf code and a lockfile bump. Changes are straightforward routing logic in an existing API — no ECS systems, async patterns, containers, or architectural changes.

STEP 7 — QA assessment

QA_REQUIRED: YES. The changes affect runtime teleportation behavior. The PR description includes specific QA test steps: enter sdk7testscenes.dcl.eth at position 1,2, verify portals render, click a portal, confirm teleportation to the correct scene.

STEP 8 — Warnings

No warnings. Main.unity is not modified.


REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Straightforward routing-logic extension to the existing TeleportTo restricted action, reusing pre-existing ChangeRealmPromptController infrastructure.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

Comment thread Explorer/Assets/StreamingAssets/Js/Modules/RestrictedActions.js Outdated
…omment

- Rename interface parameter `newCoords` → `coords` to match implementation
  and update paramref in XML doc
- Replace JS bridge comment that narrated C# behavior with a concise
  description of what the bridge function itself does

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@decentraland-bot
decentraland-bot self-requested a review September 10, 2026 14:01

@decentraland-bot decentraland-bot left a comment

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.

PR Review — feat: sdk teleportTo realm coords support

STEP 1 — Scope

Files inspected (9): RestrictedActionsAPIImplementation.cs, IRestrictedActionsAPI.cs, RestrictedActionsAPIWrapper.cs, UserActionsWrapper.cs, RestrictedActions.js, RestrictedActions.gen.cs, RestrictedActionsAPIImplementationShould.cs, scripts/package.json, scripts/package-lock.json.

Surrounding context loaded: ChangeRealmPromptController.cs, ChangeRealmPromptController.Params.cs, ISceneRuntime.cs (consumer registration), DeepLinkHandleImplementation.cs (existing ChangeRealmAsync + position usage). Repo-wide rg for TryTeleportTo, ChangeRealmPromptController, and IRestrictedActionsAPI consumers to map all call sites.

Conventions loaded: CLAUDE.md, review-instructions.md.


STEP 2 — Root-cause check

This PR implements new protocol support — making worldCoordinates optional and adding an optional realm field to the TeleportTo restricted action. The diff addresses the feature at the right level: the protocol schema gains the field, the JS bridge unpacks the new optional, the C# interface/implementation branch on the new parameter, and existing consent-prompt infrastructure is reused for the realm path. PASS — this is a cause, not a symptom.


STEP 3 — Design & integration

No new units introduced. The diff modifies the existing TryTeleportTo method signature and adds branching logic inside it. No new systems, managers, controllers, or helpers.

Lifecycle reuse confirmed:

  • Realm-based teleport reuses ChangeRealmAsyncChangeRealmPromptController, which already accepted Vector2Int? Position in its Params struct (confirmed in ChangeRealmPromptController.Params.cs). The changeRealmCallback in ChangeRealmPromptController.cs already forwards inputData.Position to the realm-switch callback.
  • Coordinate-only teleport reuses existing TeleportAsyncTeleportPromptController.

No new subscriptions, connections, or resources — nothing to trace for teardown.

PASS — no design issues.


STEP 4 — Member audit

IRestrictedActionsAPI.TryTeleportTo(Vector2Int? coords, string? realm) — signature changed from (Vector2Int newCoords). Consumers found and all updated:

  1. RestrictedActionsAPIWrapper.TeleportTo (JS bridge) — updated ✓
  2. UserActionsWrapper.RequestTeleport — passes null realm ✓
  3. RestrictedActionsAPIImplementation (impl) — updated ✓
  4. Test file — updated with 3 new tests + existing test adapted ✓

ChangeRealmAsync(string, string, Vector2Int?) — added optional third parameter with default null. Backward-compatible; existing callers (TryChangeRealm, MVCManagerMenusAccessFacade) continue to work without change.

No single-use members, no absent-≠-false predicates, no redundant guards. No issues.


STEP 5 — Line-level review

Two passes completed. No blocking issues found. Detailed checks:

A. Blocking-issue pass:

  1. Null safetyTryTeleportTo: string.IsNullOrEmpty(realm) correctly handles both null and "". coords.HasValue correctly gates coords.Value. No null-dereference paths.

  2. Partial-coords defense in wrapper (x.HasValue && y.HasValue) — correct. Per protobuf, worldCoordinates is an optional Vector2 message, so x and y arrive together or not at all. The defensive AND is harmless and follows the pattern used by MovePlayerTo in the same wrapper.

  3. JS bridgecoords != undefined correctly distinguishes set/unset protobuf optional message fields. The loose equality != also catches null. Field-by-field guarding is consistent with the existing movePlayerTo pattern. message.realm != undefined correctly forwards the optional string.

  4. .Forget() on ChangeRealmAsync — consistent with existing .Forget() usage in TryChangeRealm and all other async prompt launches in this class. These are UI-prompt fire-and-forget operations on the main thread — the established pattern. Not detached essential work (CLAUDE.md §9).

  5. Security: realm is scene-controlled (untrusted) — the realm string flows to ChangeRealmPromptController, which already disables richText (SEC-003) and strips userinfo spoofing via DestinationHostFor() (SEC-004). No new attack surface.

  6. System.Text.RegularExpressions import in test file — used by new Regex("TeleportTo") in IgnoreTeleportWithNeitherCoordinatesNorRealm via LogAssert.Expect. Not unused.

  7. Resource/subscription leaks — no new subscriptions, event hookups, or disposables added.

B. Design/encapsulation/naming pass:

  1. Comment quality// Realm present → route through the change-realm consent prompt, carrying the optional parcel. describes what this code does (routes through the consent prompt), not what callers do with the result. Acceptable per CLAUDE.md §11.

  2. XML doc on IRestrictedActionsAPI.TryTeleportTo — accurately describes the three cases (coords-only, realm-only, both-absent rejection). Parameter names match implementation. ✓

  3. Test namingTeleportToParcelInAnotherRealm, TeleportToRealmDefaultSpawnWithoutCoordinates, IgnoreTeleportWithNeitherCoordinatesNorRealm — descriptive, follow existing naming convention in the file. AAA pattern, NUnit + NSubstitute. ✓

  4. No magic values, no naming issues, no encapsulation leaks.


STEP 6 — Complexity

SIMPLE — 9 files changed, but the core logic is ~20 lines of branching in TryTeleportTo. The generated protobuf code is the bulk of the diff. No ECS systems, no async pattern changes, no plugin/container wiring, no assembly changes.


STEP 7 — QA assessment

QA_REQUIRED: YES — changes runtime code affecting teleportation behavior (user-facing consent prompts, realm switching with optional coordinates). The PR description includes specific test steps for a ZONE/SEPOLIA world.


STEP 8 — Non-blocking warnings

None. Main.unity not modified.


Security & Dependency Review

Inventory:

Dependency Version Source Type Scope Risk
@dcl/protocol 1.0.0-34381335718.commit-3c838bb registry.npmjs.org/@dcl/protocol npm build (codegen) Low
  • Provenance: @dcl/protocol is published under the @dcl npm scope (Decentraland's official scope). Resolved from the public npm registry with integrity hash. The package is used only at build time for protobuf code generation; the generated RestrictedActions.gen.cs ships in the player.
  • Generated code: RestrictedActions.gen.cs changes are consistent with adding an optional string realm field to TeleportToRequest. The protobuf descriptor base64, field numbers (1 = worldCoordinates, 2 = realm), wire types, and HasRealm/ClearRealm pattern match standard protoc output for an optional string field.
  • No new binaries, plugins, native payloads, or assembly changes.
  • No new execution hooks, InitializeOnLoad, or build callbacks.
  • Security controls intact: Scene-controlled realm string goes through the existing ChangeRealmPromptController consent prompt with rich-text disabled and userinfo-stripping (DestinationHostFor). No new input vectors.

DEPENDENCY_REVIEW: PASS


REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Signature change to TryTeleportTo with branching logic; reuses existing ChangeRealm/Teleport prompt infrastructure, no new systems or lifecycle units.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

@pravusjif pravusjif added clean-build Used to trigger clean build on PR no-warning-ratchet Prevent CI linting checks labels Sep 10, 2026
@decentraland-bot
decentraland-bot self-requested a review September 10, 2026 16:00

@decentraland-bot decentraland-bot left a comment

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.

PR Review — feat: sdk teleportTo realm coords support

Inspected commits: base 81afe2b → head 0170ec7 (4 commits, including 2 follow-up fixes and a merge from dev)


STEP 1 — Scope

Files changed (9): RestrictedActionsAPIImplementation.cs, IRestrictedActionsAPI.cs, RestrictedActionsAPIWrapper.cs, UserActionsWrapper.cs, RestrictedActions.js, RestrictedActions.gen.cs (auto-generated protobuf), RestrictedActionsAPIImplementationShould.cs, scripts/package.json, scripts/package-lock.json.

Context loaded: Full source of all changed files. ChangeRealmPromptController.cs and its Params.cs partial (confirms Vector2Int? Position pre-exists). DynamicWorldContainer's changeRealmCallback registration (already branches on position.HasValue). Repo-wide rg for all TryTeleportTo consumers: RestrictedActionsAPIWrapper.TeleportTo, UserActionsWrapper.RequestTeleport, and 4 test methods. CLAUDE.md and review-instructions.md loaded.


STEP 2 — Root-cause check

PASS. This is a protocol-driven feature addition: the SDK's TeleportTo restricted action gains an optional realm field and makes worldCoordinates optional. The diff implements the new routing at the correct layers — protocol schema, JS bridge, C# interface/implementation — and reuses existing consent-prompt infrastructure for the realm path. Genuine feature, not a symptom fix.


STEP 3 — Design & integration

PASS. No new long-lived units introduced. The change extends the existing TryTeleportTo method with branching logic.

Lifecycle reuse confirmed:

  • Realm-based teleport reuses ChangeRealmAsyncChangeRealmPromptController, which already accepted Vector2Int? Position in its Params struct (ChangeRealmPromptController.Params.cs, line 13). The controller's OnViewShow (line 49) already forwards inputData.Position to the changeRealmCallback.
  • Coordinate-only teleport reuses existing TeleportAsyncTeleportPromptController.
  • ChangeRealmAsync gains an optional Vector2Int? position = null parameter — backward-compatible with the existing TryChangeRealm caller (line 133, which passes no position).

No lifecycle duplication, no per-frame reconciliation, no new persistent state, no new subscriptions or resources to trace.


STEP 4 — Member audit

Member Consumers Verdict
IRestrictedActionsAPI.TryTeleportTo(Vector2Int?, string?) RestrictedActionsAPIWrapper.TeleportTo, UserActionsWrapper.RequestTeleport, 4 test methods Appropriate public surface — each combination of optional params is tested
ChangeRealmAsync(string, string, Vector2Int?) TryTeleportTo (with position), TryChangeRealm (without — uses default null) Optional parameter preserves backward compatibility
RestrictedActionsAPIWrapper.TeleportTo(int?, int?, string?) JS bridge (RestrictedActions.js) Single caller, justified — JS↔C# interop boundary

No single-use intermediaries, no absent-≠-false predicates, no redundant guards.


STEP 5 — Line-level review

Two passes completed. No issues found.

A. Blocking-issue pass:

  1. Null safetystring.IsNullOrEmpty(realm) correctly handles both null and "" (an SDK scene explicitly setting realm = "" is correctly treated as absent). coords.HasValue correctly gates coords.Value. No null-dereference paths.

  2. Partial-coords defense in wrapper (x.HasValue && y.HasValue) — correct. Per protobuf, worldCoordinates is an optional Vector2 message, so x and y arrive together or not at all. The defensive AND is harmless and follows the pattern used by MovePlayerTo in the same wrapper.

  3. JS bridgecoords != undefined correctly distinguishes set/unset protobuf optional message fields. The loose != also catches null. message.realm != undefined correctly forwards the optional string. Pattern is consistent with movePlayerTo.

  4. .Forget() on ChangeRealmAsync (line 115) — consistent with all other .Forget() calls in this class (lines 73, 125, 133, 223, 264). These are UI consent-prompt fire-and-forget operations on the main thread — the established pattern. Not detached essential work (CLAUDE.md §9).

  5. IsCurrent guard — present at line 109, covers all branches (realm, coords, and neither). ✓

  6. Resource/subscription leaks — no new subscriptions, event hookups, connections, CancellationTokenSources, or disposables added. ✓

  7. System.Text.RegularExpressions import in test file — used by new Regex("TeleportTo") in LogAssert.Expect. Not unused. ✓

B. Design/encapsulation/naming pass:

  1. Comment quality — All comments describe what the annotated code does, not external behavior: "Realm present → route through the change-realm consent prompt, carrying the optional parcel" (line 112), "Unpack optional fields and forward to the C# bridge; both are nullable" (JS line 56). Compliant with CLAUDE.md §11. ✓

  2. XML doc on IRestrictedActionsAPI.TryTeleportTo — accurately describes all three cases including the both-absent rejection. Parameter names (coords, realm) match the implementation. ✓

  3. Naming — PascalCase for methods/properties, camelCase for locals/params. I prefix on interface. Follows conventions. ✓

  4. Test naming and patternsTeleportToParcelInAnotherRealm, TeleportToRealmDefaultSpawnWithoutCoordinates, IgnoreTeleportWithNeitherCoordinatesNorRealm — descriptive, follow existing naming convention. AAA pattern, NUnit + NSubstitute. Negative assertions present on all tests where applicable. ✓

  5. No magic values, no naming issues, no encapsulation leaks, no constants needed.

Prior review findings: All 6 P2 findings from the initial review (comments narrating external behavior, missing negative assertion, incomplete XML doc, parameter name mismatch, JS comment) were addressed by the two follow-up commits (cf54ace, 8af11ed). No unresolved findings remain.


Security Review

Input trace: The attacker-controlled realm string flows: SDK scene message → protobuf TeleportToRequest.realm → JS bridge (message.realm) → RestrictedActionsAPIWrapper.TeleportTo(realm)TryTeleportTo(coords, realm)ChangeRealmAsync(string.Empty, realm, coords)ChangeRealmPromptController.Params(message, realm, position) → consent prompt shown to user.

Controls verified:

  • Consent prompt gate: Every realm-based teleport goes through ChangeRealmPromptController.ShowAsync (line 298), which shows a user consent UI before proceeding. No path bypasses it. ✓
  • Rich-text disabled (SEC-003): viewInstance.MessageText.richText = false and viewInstance.RealmText.richText = false in OnViewInstantiated (controller lines 37–38). Covers this new entry point. ✓
  • Userinfo stripping (SEC-004): DestinationHostFor() (controller lines 72–93) strips URL userinfo to prevent consent-prompt spoofing. Covers this new entry point. ✓
  • IsCurrent guard: Line 109, covers all branches. ✓
  • Message hardcoded: string.Empty passed as message (line 115) — scenes cannot control the prompt body text via this path (more restrictive than TryChangeRealm). ✓

Supply chain:

Dependency Old version New version Source Type
@dcl/protocol 1.0.0-33874985952.commit-4f4e0ab 1.0.0-34381335718.commit-3c838bb registry.npmjs.org (@dcl scope) npm, build-time codegen
  • First-party Decentraland package (@dcl npm scope). sha512 integrity hash present in lockfile. ✓
  • Generated RestrictedActions.gen.cs changes are consistent with adding an optional realm string field (field number 2) and making worldCoordinates optional. Protobuf descriptor, field numbers, wire types, HasRealm/ClearRealm pattern match standard protoc output. ✓
  • No new binaries, native plugins, UPM packages, or assembly definition changes. ✓
  • No new execution hooks, InitializeOnLoad, or build callbacks. ✓

Coverage limits: npm audit not executed (read-only review). No native binaries affected.

DEPENDENCY_REVIEW: PASS


STEP 6 — Complexity

SIMPLE — The core logic change is ~20 lines of branching in TryTeleportTo, plus a 1-parameter addition to ChangeRealmAsync. The bulk of the diff (116 lines) is auto-generated protobuf code. No ECS systems, async pattern changes, plugin/container wiring, or assembly changes.

STEP 7 — QA assessment

QA_REQUIRED: YES — Changes runtime teleportation behavior (user-facing consent prompts, realm switching with optional coordinates). The PR description includes specific test steps for a ZONE/SEPOLIA world.

STEP 8 — Non-blocking warnings

None. Main.unity not modified.


REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Straightforward routing-logic extension to existing TryTeleportTo, reusing pre-existing ChangeRealmPromptController infrastructure; no new systems or lifecycle units.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

@decentraland-bot
decentraland-bot self-requested a review September 10, 2026 18:22

@decentraland-bot decentraland-bot left a comment

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.

PR Review — feat: sdk teleportTo realm coords support

Inspected commits: base 19d6924 → head 28d1273 (5 commits: initial feature, 2 review-feedback fixes, 2 merges from dev)


STEP 1 — Scope

Files inspected (9): RestrictedActionsAPIImplementation.cs, IRestrictedActionsAPI.cs, RestrictedActionsAPIWrapper.cs, UserActionsWrapper.cs, RestrictedActions.js, RestrictedActions.gen.cs (generated), RestrictedActionsAPIImplementationShould.cs, scripts/package.json, scripts/package-lock.json.

Context files read in full: ChangeRealmPromptController.cs, ChangeRealmPromptController.Params.cs, ChangeRealmPromptPlugin.cs, DynamicWorldContainer.cs (composition-root callback at L719–729), DeepLinkHandleImplementation.cs. Searched all callers of TryTeleportTo, ChangeRealmAsync, ChangeRealmPromptController, and IRestrictedActionsAPI across the repo.

STEP 2 — Root-cause check

PASS. This PR adds a genuine new feature: SDK scenes can now request teleportation to a specific parcel in another realm via the TeleportTo restricted action. The diff extends TeleportToRequest (protocol) with an optional realm field, and the implementation routes realm-bearing requests through the pre-existing ChangeRealmPromptController consent flow. No symptom-masking or workaround patterns.

STEP 3 — Design & integration

PASS. No new long-lived units are introduced. The change adds a routing branch inside the existing RestrictedActionsAPIImplementation.TryTeleportTo method, reusing:

  • ChangeRealmAsync helper — already existed in the same class (used by TryChangeRealm); this PR adds the Vector2Int? position = null parameter to thread the optional parcel through.
  • ChangeRealmPromptController.Params.Position — the struct already had this optional field (used by DeepLinkHandleImplementation.ShowRealmChangePromptAsync).
  • Action<string, Vector2Int?> changeRealmCallback — the composition-root callback in DynamicWorldContainer (L719–729) already branches on position.HasValue to call ChatTeleporter.TeleportToRealmAsync with the parcel or fall back to the chat-command route.

The routing logic lives in the implementation class (not the wrapper), consistent with TryChangeRealm and every other Try* method in the same file. RestrictedActionsAPIWrapper.TeleportTo stays a pure JS↔C# type-conversion shim — not a same-layer bridge anti-pattern.

Lifecycle / teardown: ChangeRealmPromptController is a long-lived singleton registered once in ChangeRealmPromptPlugin, reused per ShowAsync call. No new subscriptions, event hookups, or disposable resources are introduced.

Call-site sweep: All three callers of the old TryTeleportTo(Vector2Int) (RestrictedActionsAPIWrapper.TeleportTo, UserActionsWrapper.RequestTeleport, and the interface declaration) were consistently updated to the new (Vector2Int?, string?) signature. No stale callers remain.

STEP 4 — Member audit

Member Consumers Notes
IRestrictedActionsAPI.TryTeleportTo(Vector2Int?, string?) RestrictedActionsAPIWrapper.TeleportTo, UserActionsWrapper.RequestTeleport, tests All updated consistently
RestrictedActionsAPIImplementation.ChangeRealmAsync(string, string, Vector2Int?) TryTeleportTo (with position), TryChangeRealm (without — uses default null) Optional param avoids breaking existing caller

No single-use accessor issues. No absent-≠-false confusion — both coords and realm are genuinely nullable, and the method correctly rejects the both-absent case.

STEP 5 — Line-level review

See inline comments below. Summary:

  • 0 P0 (blockers)
  • 0 P1 (major)
  • 3 P2 (minor, non-blocking)

STEP 6 — Complexity

SIMPLE. Touches 5 hand-written files + 1 generated + 2 package files. Straightforward feature addition that reuses existing consent-prompt infrastructure. Does not modify ECS systems, async patterns, plugins, containers, or assembly structure.

STEP 7 — QA assessment

QA_REQUIRED: YES. Modifies runtime code affecting teleportation behavior — the new realm+coords routing and the existing coords-only path must be manually verified in a live world.

STEP 8 — Non-blocking warnings

None. Main scene not modified.


Security review

Consent bypass: Not found. Both branches of TryTeleportTo route exclusively through user-facing consent prompts (TeleportPromptController or ChangeRealmPromptController). The prompt only fires the realm change after the user clicks Approve.

Realm string sanitization: Adequate, unchanged. ChangeRealmPromptController.OnViewInstantiated disables richText (SEC-003) and DestinationHostFor strips userinfo/path/query to prevent consent-prompt spoofing (SEC-004).

JS bridge input handling: Consistent with existing sibling calls. Number() coercion for coords, direct string pass-through for realm (same pattern as changeRealm handler).

Injection risk: None identified. Coordinates are strictly int/Vector2Int. Realm string display is sanitized; realm string handling for the actual network connection is pre-existing, unchanged code.

@dcl/protocol bump: 1.0.0-33874985952.commit-4f4e0ab1.0.0-34381335718.commit-3c838bb. Build/codegen dependency only (scripts/package.json), not shipped in the Unity client. Integrity hash updated consistently. The transitive protobufjs advisories are pre-existing across all @dcl/protocol versions (not introduced by this PR) and only execute during offline code generation, never in the shipped client.

DEPENDENCY_REVIEW: PASS


REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Adds a routing branch to TryTeleportTo and threads an optional parcel through the pre-existing ChangeRealmAsync / ChangeRealmPromptController consent flow — no new systems, plugins, or async patterns.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

@decentraland-bot
decentraland-bot self-requested a review September 10, 2026 18:31

@decentraland-bot decentraland-bot left a comment

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.

PR Review — feat: sdk teleportTo realm coords support

Inspected commits: base dev → head ab11728e (re-review after follow-up fixes)


STEP 1 — Scope

9 files changed (5 hand-written C#, 1 JS bridge, 1 auto-generated protocol C#, 2 package manifests). Inspected full context of all changed files plus surrounding files: ChangeRealmPromptController + Params, ChangeRealmPromptPlugin, DynamicWorldContainer (callback wiring), DeepLinkHandleImplementation, and ChatTeleporter.TeleportToRealmAsync. Searched consumers of TryTeleportTo — 3 callers: RestrictedActionsAPIWrapper, UserActionsWrapper, and tests.

STEP 2 — Root-cause check

PASS. This is a protocol-driven feature addition: the SDK's TeleportTo restricted action gains an optional realm field (and makes worldCoordinates optional). The diff implements the new routing — realm-present requests go through the existing change-realm consent prompt; coords-only requests keep the existing teleport prompt. This is a genuine feature, not a symptom fix.

STEP 3 — Design & integration

PASS. No new long-lived units introduced. The change extends the existing TryTeleportTo method signature and reuses the established ChangeRealmAsyncChangeRealmPromptControllerchangeRealmCallbackChatTeleporter.TeleportToRealmAsync pipeline.

  • The ChangeRealmPromptController.Params struct already had the Position field (prior PR).
  • The callback in DynamicWorldContainer (lines 719–728) already branches on position.HasValue, routing to ChatTeleporter.TeleportToRealmAsync(realmUrl, position.Value, …) with position or the chat-command path without.
  • The ChangeRealmAsync private method gains an optional Vector2Int? position parameter — backward-compatible with the existing TryChangeRealm caller (which passes no position).

No lifecycle duplication, no per-frame reconciliation, no new persistent state. Clean integration into the existing architecture.

STEP 4 — Member audit

Member Consumers Status
TryTeleportTo(Vector2Int?, string?) RestrictedActionsAPIWrapper, UserActionsWrapper, tests (3) Clean — routes to two existing async paths based on parameters
ChangeRealmAsync(string, string, Vector2Int?) TryTeleportTo (with position), TryChangeRealm (default null) (2) Clean — optional parameter with sane default

No single-use intermediates, no re-derived state, no absent-vs-false conflation.

STEP 5 — Line-level review

No new findings. All P2 issues identified in the prior review (head 141ff48) have been addressed by the follow-up commits:

Prior finding Addressed in
Comment narrating external behavior (RestrictedActionsAPIImplementation.cs) cf54ace — trimmed to describe what the annotated code does
Missing negative assertion in TeleportToRealmDefaultSpawnWithoutCoordinates cf54ace + ab11728 — added DidNotReceive for TeleportPromptController (type param fixed in ab11728)
Incomplete XML doc (both-absent case) cf54ace — appended "both absent is rejected"
Interface param name mismatch newCoordscoords 8af11ed — aligned to match implementation
JS bridge comment narrating C# behavior 8af11ed — trimmed to "Unpack optional fields and forward to the C# bridge"

Partial-coords edge case (RestrictedActionsAPIWrapper.TeleportTo): if only x or only y is non-null, the wrapper maps to null coords rather than surfacing a malformed call. As noted in the prior review, this is academic — protobuf Vector2 delivers both or neither, and the JS bridge mirrors that. The current handling (treat partial as absent) is the safest default. No code change needed.

STEP 6 — Complexity

SIMPLE. Straightforward extension of an existing method signature with routing logic. Does not touch ECS systems/components/queries, async patterns, plugin registration, or any complex subsystem.

STEP 7 — QA assessment

YES. Changes affect runtime teleport behavior — user-facing UI (consent prompts) and navigation.

STEP 8 — Non-blocking warnings

None. No main scene changes.


Security Review

Consent flow: Preserved. All realm-carrying teleport requests route through ChangeRealmAsyncChangeRealmPromptController, which shows a consent prompt. The controller disables richText on both message and realm labels (SEC-003/034/050) and strips userinfo from URL realms via DestinationHostFor (SEC-004). The sceneStateProvider.IsCurrent gate is unchanged.

Input validation: string.IsNullOrEmpty(realm) correctly handles both null and empty-string realm. The JS bridge (RestrictedActions.js:57-61) correctly maps undefined to null before crossing into C#. RestrictedActionsAPIWrapper uses HasValue checks for nullable int? parameters.

Supply chain — @dcl/protocol bump:

Field Value
Package @dcl/protocol
Old version 1.0.0-33874985952.commit-4f4e0ab
New version 1.0.0-34381335718.commit-3c838bb
Publisher decentraland (npm)
Integrity sha512-0QUcs/F1K60GmB9Qnc... (lockfile verified)
Scope Build-time codegen only — not shipped in the player

The transitive protobufjs vulnerability (GHSA-h755-8qp9-cq85 and others) is pre-existing — identical npm audit output on the dev baseline. Not introduced by this PR.

No binary, assembly, plugin, .meta, .asmdef, or .asmref changes.

DEPENDENCY_REVIEW: PASS


REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Extends an existing restricted-action method signature with an optional realm parameter and routing logic; no ECS, async, or subsystem changes
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

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

Labels

clean-build Used to trigger clean build on PR no-warning-ratchet Prevent CI linting checks

Projects

Status: QA

Development

Successfully merging this pull request may close these issues.

2 participants