Skip to content

feat: support scene named spawn points#9369

Open
popuz wants to merge 30 commits into
devfrom
feat/support-scene-named-spawn-points
Open

feat: support scene named spawn points#9369
popuz wants to merge 30 commits into
devfrom
feat/support-scene-named-spawn-points

Conversation

@popuz

@popuz popuz commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Pull Request Description

What does this PR change?

Adds named spawn point selection to the teleport pipeline (part of #6848). Scenes already declare named spawn points in scene.json; until now the explorer always ran the default selection (default-flagged → closest). This PR lets a teleport request a spawn point by name, end-to-end.

Core mechanism (single chokepoint, applies to every entry point below):

  • TeleportUtils.PickSpawnPoint gains string? spawnPointName. When set and matched (case-insensitive), that point is used directly, bypassing the default/closest logic.
  • Unmatched name → soft fallback to the default selection + ReportHub warning (Spawn point '<name>' not found in scene, falling back to default spawn point selection). The teleport itself never fails because of a bad name.
  • Duplicate names → first in declaration order wins (+ warning).
  • Everything after selection is unchanged: coordinate-range points ("x": [min, max]) still scatter arriving players randomly within the range; cameraTarget still applies.

Entry points wired in this PR:

Entry point Syntax
/goto (Genesis) /goto x,y/spawnPoint
/goto (worlds) /goto world/spawnPoint, /goto world/x,y/spawnPoint
/goto-local /goto-local x,y/spawnPoint, /goto-local spawnPoint (current scene)
Deep link (cold start + runtime) decentraland://?realm=...&spawnpoint=spawnPoint
Launch parameter (CLI / CI) Explorer.exe --realm ... --spawnpoint spawnPoint

Also included:

  • /goto parsing extracted into pure ChatParamUtils.ParseGotoTarget with full grammar-table unit tests (ChatParamUtilsShould); GoToChatCommand is now a thin dispatcher.
  • The chat command handler now trims trailing whitespace, so /goto 0,0 (trailing space) no longer fails validation — for all commands.
  • Already-in-realm named teleport: /goto world/name (and the runtime deep link with spawnpoint but no position) used while already inside the target realm now teleports to the named spawn point of the scene the player stands in, instead of stopping at 🟡 You are already in .... Without a name the old message is kept.
  • Unit tests: TeleportUtilsShould (named selection core), ChatParamUtilsShould (grammar), RealmLaunchSettingsShould (deep link / CLI arg parsing), ChatTeleporterShould (already-in-realm branching).

Out of scope: SDK entry points (teleportTo / changeRealm with spawnPoint) — they require protocol + js-sdk-toolchain changes first; nothing for them in this repo yet.

Test Instructions

Steps (standard run):

metaforge explorer run 9369

Prerequisites

  • A world with named spawn points. Test world: flutterecho.dcl.eth (4x4 parcels, spotlight cubes in each corner, physics playground near the origin corner), deployed with:
Name Position What it's for
spawn (default) 2, 0, 2 default landing (regression)
center 32, 0, 32 scene center
physics 15, 0, 12 inside the physics playground
far-light 60, 0, 60 far corner; has cameraTarget looking back at scene center
mid-corner 4, 0, 60 another corner
scatter x∈[26,38], z∈[26,38] coordinate-range scatter
twin (declared twice) 10, 0, 50 and 50, 0, 10 duplicate-name resolution
  • The "not found" / "duplicate" warnings go to logs, not chat:
metaforge explorer logs tail --filter "spawn point"

Test Cases

1. /goto — world forms

  1. From Genesis: /goto flutterecho/center → realm change, land at scene center (32, 32). No coordinates given: the world manifest picks the parcel, the name picks the point.
  2. From Genesis: /goto flutterecho.dcl.eth/0,0/far-light → land at the far corner and the camera faces back toward scene center (cameraTarget).
  3. Already inside the world: /goto flutterecho/0,0/physics → teleport within the world to the physics playground.
  4. Already inside the world: /goto flutterecho/centerin-world teleport to the scene center, no realm change (the name targets a spawn point of the scene you are standing in).
  5. Already inside the world: /goto flutterecho (no name) → 🟡 You are already in..., no teleport (pre-existing behavior, unchanged).
  6. Regression: /goto flutterecho from Genesis → land at the default spawn (2, 2), exactly as before.

2. /goto — Genesis form

  1. /goto x,y/name against any Genesis scene that declares named spawn points → lands at the named point.
  2. Fallback path works on any parcel: /goto 0,0/nosuch → normal teleport to 0,0 + warning in logs.
  3. Regression: /goto 0,0, /goto random, /goto crowd behave exactly as before.
  4. From inside a world: /goto 0,0/name → realm change to Genesis carries the name (it is not dropped).

3. /goto-local (inside the world / any realm)

  1. /goto-local 0,0/center → parcel + name form.
  2. /goto-local physicsbare-name form: teleports to the named point of the scene you are currently standing in.
  3. /goto-local far-light → also verify camera orientation.
  4. Reserved words are not names: /goto-local random / /goto-local crowd🔴 Invalid parameters + usage.
  5. /goto-local nosuch → teleport to your current parcel, default spawn selection + warning in logs (soft fallback).
  6. Regression: /goto-local 0,0 unchanged.

4. Deep link — cold start (client closed)

decentraland://?realm=flutterecho.dcl.eth&dclenv=org&spawnpoint=physics
  1. Client starts and the initial landing is the physics playground (not the default spawn).
  2. With a position: decentraland://?realm=flutterecho.dcl.eth&dclenv=org&position=0,0&spawnpoint=far-light.
  3. Bad name: ...&spawnpoint=nosuch → lands at default spawn + warning in logs.

5. Deep link — runtime (client already running)

  1. While standing in another realm (e.g. Genesis), open the same deep link from the OS → realm change, land at the named point.
  2. Already inside the world, deep link with position + spawnpoint → in-world teleport to the named point.
  3. Already inside the world, deep link with spawnpoint but without position (decentraland://?realm=flutterecho.dcl.eth&dclenv=org&spawnpoint=far-light) → in-world teleport to the named spawn point of the current scene (previously this silently did nothing).
  4. Already inside the world, deep link with realm only (no position, no spawnpoint) → nothing happens (pre-existing "already in realm" behavior, unchanged).

6. Launch parameters (cmd / CI)

Explorer.exe --realm flutterecho.dcl.eth --dclenv org --spawnpoint physics
  1. Same expectations as the cold-start deep link (CLI args and deep link query params share one parser).
  2. Genesis variant: --realm <genesis peer> --position 0,0 --spawnpoint <name>.

7. Cross-cutting edge cases (any entry point)

  1. Case-insensitivity: /goto-local PHYSICS, /goto flutterecho/Far-Light, ...&spawnpoint=CENTER → all match.
  2. Range scatter: /goto-local scatter several times in a row → each landing is a different spot within the 26..38 square (players must not stack on one point).
  3. Duplicate names: /goto-local twin repeatedly → always lands at (10, 50) (first declared), never (50, 10); warning about duplicates in logs.
  4. Unknown name: never breaks the teleport — always default selection + Spawn point '<name>' not found... in logs. Nothing red in chat.
  5. Trailing whitespace: /goto flutterecho/center (trailing spaces) → works (previously any trailing space failed validation of any command).
  6. Grammar junk stays world-names (unchanged behavior): /goto myworld//lobby, /goto myworld/0,0/ → treated as a (non-existent) world → 🔴 ... doesn't exist or not reachable.
  7. /goto flutterecho/randomrandom here is a spawn point name (soft fallback if the scene has none), not the random-parcel flag.
  8. Initial world entry without a name (jump-in, navmap, plain deep link) → default spawn, no behavior change.

Quality Checklist

  • Changes have been tested locally
  • Documentation has been updated (if required)
  • Performance impact has been considered (name matching runs only during teleport resolution; no per-frame cost, no allocations added to hot paths)
  • For SDK features: Test scene is included

Code Review Reference

Please review our Branch & PR Standards before submitting. It explains the automated review flow, QA/DEV approval requirements, and what each label does — especially useful for first-time contributors.

popuz added 6 commits July 13, 2026 18:33
…rget

  No behavior change. All /goto argument parsing moves out of GoToChatCommand
  into a pure static ChatParamUtils.ParseGotoTarget returning a GotoTarget
  value (world, parcel, spawn point, random/crowd flags). The command becomes
  a thin dispatcher. GotoTarget.SpawnPoint is always null in this slice — the
  field exists so the named-spawn-point grammar (#6848) lands additively.

  Covers the existing grammar with unit tests: x,y, random, crowd, world,
  world/x,y, plus junk/edge inputs.
  PickSpawnPoint selects a case-insensitively matched named point directly,
  bypassing the default/closest logic (unmatched name warns and falls back).
  The name is threaded as string? spawnPointName through the same-realm
  teleport chain, mirroring landOnParcel. ParseGotoTarget accepts x,y/name.
…x,y/name

    The spawn point name is threaded through the realm-change chain
    (TryChangeRealmAsync → TeleportParams → MoveToParcelInNewRealmTeleportOperation
    → TeleportToSpawnPointOperationBase) and through TryChangeToGenesisAsync,
    so /goto x,y/name also works from a world. For the coordinate-less form the
    manifest still picks the scene parcel; the name selects the point within it.
    ParseGotoTarget accepts world/name and world/x,y/name.
@popuz popuz self-assigned this Jul 14, 2026
@popuz popuz added the force-build Used to trigger a build on draft PR label Jul 14, 2026
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown
Contributor

badge

New build in progress, come back later!

@popuz popuz changed the title Feat/support scene named spawn points feat: support scene named spawn points Jul 14, 2026
@popuz
popuz marked this pull request as ready for review July 15, 2026 10:59
@popuz
popuz requested review from a team as code owners July 15, 2026 10:59
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Warnings count reduced: 19087 => 14680

Warnings/errors in files changed by this PR (49)
File Line Rule Message
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 106 CSharpWarnings::CS8600 Converting null literal or possible null value into non-nullable type
Assets/DCL/Chat/Commands/ChatTeleporter.cs 122 CSharpWarnings::CS8601 Possible null reference assignment
Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs 154 CSharpWarnings::CS8604 Possible null reference argument for parameter 'assetsProvisioner' in 'Global.Dynamic.DynamicWorldDependencies.DynamicWorldDependencies'
Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs 115 CSharpWarnings::CS8604 Possible null reference argument for parameter 'assetsProvisioner' in 'Global.StaticContainer.CreateAsync'
Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs 122 CSharpWarnings::CS8604 Possible null reference argument for parameter 'ethereumApi' in 'Global.StaticContainer.CreateAsync'
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 107 CSharpWarnings::CS8604 Possible null reference argument for parameter 'localSceneParameter' in 'Global.Dynamic.RealmLaunchSettings.ParseLocalSceneParameter'
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 93 CSharpWarnings::CS8604 Possible null reference argument for parameter 'realmParamValue' in 'Global.Dynamic.RealmLaunchSettings.ParseRealmAppParameter'
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 115 CSharpWarnings::CS8604 Possible null reference argument for parameter 'serverValue' in 'Global.Dynamic.RealmLaunchSettings.ParseLSDRemoteABServer'
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 96 CSharpWarnings::CS8604 Possible null reference argument for parameter 'targetPositionParam' in 'Global.Dynamic.RealmLaunchSettings.ParsePositionAppParameter'
Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs 159 CSharpWarnings::CS8604 Possible null reference argument for parameter 'web3IdentityCache' in 'Global.Dynamic.DynamicWorldDependencies.DynamicWorldDependencies'
Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs 253 CSharpWarnings::CS8604 Possible null reference argument for parameter 'web3IdentityCache' in 'Global.SceneSharedContainer.Create'
Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs 121 CSharpWarnings::CS8604 Possible null reference argument for parameter 'web3IdentityProvider' in 'Global.StaticContainer.CreateAsync'
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 42 CSharpWarnings::CS8618 Non-nullable field 'portableExperiencesEnsToLoadAtGameStart' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/RealmNavigation/RealmNavigator.cs 141 CSharpWarnings::CS8629 Nullable value type can be null
Assets/DCL/Infrastructure/SceneLifeCycle/Realm/IRealmNavigator.cs 13 InconsistentNaming Name 'ChangeCancelled' does not match rule 'Enum members'. Suggested name is 'CHANGE_CANCELLED'.
Assets/DCL/Infrastructure/SceneLifeCycle/Tests/ControlSceneUpdateLoopSystemShould.cs 145 InconsistentNaming Name 'ChangeSceneFPS' does not match rule 'members_should_be_pascal_case'. Suggested name is 'ChangeSceneFps'.
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 31 InconsistentNaming Name 'EditorSceneStartPosition' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'editorSceneStartPosition'.
Assets/DCL/Infrastructure/Global/Tests/EditMode/RealmLaunchSettingsShould.cs 189 InconsistentNaming Name 'IgnoreMacOSRealmInvalidation' does not match rule 'members_should_be_pascal_case'. Suggested name is 'IgnoreMacOsRealmInvalidation'.
Assets/DCL/Infrastructure/SceneLifeCycle/Realm/IRealmNavigator.cs 15 InconsistentNaming Name 'LocalSceneDevelopmentBlocked' does not match rule 'Enum members'. Suggested name is 'LOCAL_SCENE_DEVELOPMENT_BLOCKED'.
Assets/DCL/Infrastructure/SceneLifeCycle/Realm/IRealmNavigator.cs 12 InconsistentNaming Name 'MessageError' does not match rule 'Enum members'. Suggested name is 'MESSAGE_ERROR'.
Assets/DCL/Infrastructure/SceneLifeCycle/Realm/IRealmNavigator.cs 14 InconsistentNaming Name 'NotReachable' does not match rule 'Enum members'. Suggested name is 'NOT_REACHABLE'.
Assets/DCL/RealmNavigation/ITeleportController.cs 67 InconsistentNaming Name 'Ok' does not match rule 'Enum members'. Suggested name is 'OK'.
Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs 342 InconsistentNaming Name 'OpenDefaultUI' does not match rule 'members_should_be_pascal_case'. Suggested name is 'OpenDefaultUi'.
Assets/DCL/RealmNavigation/ITeleportController.cs 68 InconsistentNaming Name 'ParcelAlreadyConsumed' does not match rule 'Enum members'. Suggested name is 'PARCEL_ALREADY_CONSUMED'.
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 149 InconsistentNaming Name 'ParseLSDRemoteABServer' does not match rule 'members_should_be_pascal_case'. Suggested name is 'ParseLsdRemoteABServer'.
Assets/DCL/Infrastructure/SceneLifeCycle/Realm/IRealmNavigator.cs 27 InconsistentNaming Name 'PasswordCancelled' does not match rule 'Enum members'. Suggested name is 'PASSWORD_CANCELLED'.
Assets/DCL/Infrastructure/SceneLifeCycle/Realm/IRealmNavigator.cs 22 InconsistentNaming Name 'PasswordRequired' does not match rule 'Enum members'. Suggested name is 'PASSWORD_REQUIRED'.
Assets/DCL/Infrastructure/SceneLifeCycle/Tests/ControlSceneUpdateLoopSystemShould.cs 75 InconsistentNaming Name 'StartSceneWithCorrectFPS' does not match rule 'members_should_be_pascal_case'. Suggested name is 'StartSceneWithCorrectFps'.
Assets/DCL/Infrastructure/SceneLifeCycle/Realm/IRealmNavigator.cs 17 InconsistentNaming Name 'Timeout' does not match rule 'Enum members'. Suggested name is 'TIMEOUT'.
Assets/DCL/Infrastructure/SceneLifeCycle/Realm/IRealmNavigator.cs 16 InconsistentNaming Name 'UnauthorizedWorldAccess' does not match rule 'Enum members'. Suggested name is 'UNAUTHORIZED_WORLD_ACCESS'.
Assets/DCL/Infrastructure/SceneLifeCycle/Realm/IRealmNavigator.cs 32 InconsistentNaming Name 'WhitelistAccessDenied' does not match rule 'Enum members'. Suggested name is 'WHITELIST_ACCESS_DENIED'.
Assets/DCL/Chat/Commands/GoToChatCommand.cs 85 InconsistentNaming Name 'baseCoords' does not match rule 'members_should_be_pascal_case'. Suggested name is 'BaseCoords'.
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 25 InconsistentNaming Name 'enabled' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Enabled'.
Assets/DCL/Chat/Commands/GoToChatCommand.cs 84 InconsistentNaming Name 'name' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Name'.
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 26 InconsistentNaming Name 'parcels' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Parcels'.
Assets/DCL/Chat/Commands/ChatTeleporter.cs 48 InconsistentNaming Name 'realmURL' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'realmUrl'.
Assets/DCL/Chat/Commands/ChatTeleporter.cs 88 InconsistentNaming Name 'realmURL' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'realmUrl'.
Assets/DCL/Chat/Commands/ChatTeleporter.cs 115 InconsistentNaming Name 'realmURL' does not match rule 'parameters_should_be_camel_case'. Suggested name is 'realmUrl'.
Assets/DCL/Chat/Commands/ChatTeleporter.cs 129 InconsistentNaming Name 'realmURL' does not match rule 'parameters_should_be_camel_case'. Suggested name is 'realmUrl'.
Assets/DCL/RealmNavigation/ITeleportController.cs 17 InvalidXmlDocComment Parameter 'ct' has no matching param tag in the XML comment for DCL.RealmNavigation.ITeleportController.TeleportToSceneSpawnPointAsync (but other parameters do)
Assets/DCL/RealmNavigation/ITeleportController.cs 17 InvalidXmlDocComment Parameter 'loadReport' has no matching param tag in the XML comment for DCL.RealmNavigation.ITeleportController.TeleportToSceneSpawnPointAsync (but other parameters do)
Assets/DCL/RealmNavigation/ITeleportController.cs 17 InvalidXmlDocComment Parameter 'parcel' has no matching param tag in the XML comment for DCL.RealmNavigation.ITeleportController.TeleportToSceneSpawnPointAsync (but other parameters do)
Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs 108 ParameterHidesMember Parameter 'appArgs' hides field 'Global.Dynamic.Bootstrap.appArgs'
Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs 145 ParameterHidesMember Parameter 'appArgs' hides field 'Global.Dynamic.Bootstrap.appArgs'
Assets/DCL/Chat/MessageBus/CommandsHandleChatMessageBus.cs 40 ParameterHidesMember Parameter 'origin' hides field 'DCL.Chat.MessageBus.CommandsHandleChatMessageBus.origin'
Assets/DCL/Chat/Commands/ChatTeleporter.cs 96 RedundantArgumentDefaultValue The parameter 'allowsSpawnPointerOverride' has the same default value
Assets/DCL/Chat/MessageBus/CommandsHandleChatMessageBus.cs 1 RedundantNullableDirective Redundant nullable directive
Assets/DCL/Infrastructure/Global/Dynamic/ChatContainer.cs 20 RedundantUsingDirective Using directive is not required by the code and can be safely removed
Assets/DCL/Infrastructure/Global/Dynamic/RealmLaunchSettings.cs 11 RedundantUsingDirective Using directive is not required by the code and can be safely removed

@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: support scene named spawn points

STEP 2 — Root-cause check ✅

This PR adds a new feature (named spawn point selection) to the teleport pipeline. The diff addresses a genuine gap: scene manifests already declare named spawn points, but the explorer had no way to target them. The implementation is a feature addition, not a symptom fix. PASS.

STEP 3 — Design & integration ✅

Threading approach: The spawn point name is threaded as string? spawnPointName = null through the existing teleport pipeline methods (ChatTeleporter → RealmNavigator → TeleportController → TeleportOperations → PlayerTeleportIntent → TeleportPositionCalculationSystem → TeleportUtils.PickSpawnPoint). This is the minimally invasive approach — it extends existing method signatures with an optional parameter rather than requiring a teleport-request-object refactoring. The parameter is never stored long-term; it flows through a single teleport invocation.

New units introduced:

  • GotoTarget — transient readonly struct for parsed /goto command results. Not a long-lived unit; no lifecycle management. Justified: it replaces ad-hoc parsing logic that was spread across GoToChatCommand, and the grammar-table test suite (ChatParamUtilsShould) validates every form.
  • DeepLinkExtensions — pure static extension methods extracted from DeepLinkHandleImplementation. The original methods were private static; the extraction is justified because the same deep-link parsing logic is now shared with the spawn point parameter, and the extension method pattern matches the DeepLink API surface cleanly.

MANDATORY OWNER SEARCH: No new systems, plugins, managers, controllers, or long-lived units that manage entity/scene/connection lifecycles are introduced. The spawn point name is a transient parameter flowing through a single teleport invocation. No persistent state, no per-frame reconciliation, no collections outside ECS. PASS — no owner-search violations.

TEARDOWN / CONSUMPTION TRACE: No new subscriptions, event hookups, connections, rooms, buffers, or measurements are added. The only new state is StartParcel.SpawnPointName (consumed once at startup alongside the parcel). PASS.

STEP 4 — Member audit ✅

Member Consumers Assessment
GotoTarget.{World,Parcel,SpawnPoint,IsRandom,IsCrowd} GoToChatCommand, GoToLocalChatCommand, tests Multi-consumer; well-scoped struct fields
ChatParamUtils.ParseGotoTarget() GoToChatCommand, GoToLocalChatCommand, tests Multi-consumer; replaces scattered parsing
ChatParamUtils.IsSpawnPointName() ParseGotoTarget, GoToLocalChatCommand.ValidateParameters Dual-use; validates spawn point segment
DeepLinkExtensions.{Realm,Position,SpawnPoint,Community}() DeepLinkHandleImplementation Single consumer, but justified extraction (shared parsing surface, replaces private static methods)
StartParcel.SpawnPointName TeleportStartupOperation, DeepLinkHandleImplementation.Assign Dual-use; consumed at startup
TeleportParams.SpawnPointName MoveToParcelIn{New,Same}RealmTeleportOperation Dual-consumer
PlayerTeleportIntent.SpawnPointName TeleportPositionCalculationSystem Single consumer; ECS component field used by the system that calculates teleport position — correct placement

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

STEP 5 — Line-level review

[P2] GoToLocalChatCommand.ExecuteCommandAsync — semantic gap in bare-name form (see inline comment)

In the bare-name form (/goto-local physics), ParseGotoTarget returns the token in target.World (since it can't distinguish world names from spawn-point names at parse time). ExecuteCommandAsync then passes target.World as the spawnPointName parameter, which reads as passing a world name where a spawn point name is expected. A clarifying comment at the call site would make this self-documenting.

STEP 5 — Security review ✅

  • Input validation: Spawn point names are validated by IsSpawnPointName (non-empty, no commas, no slashes). The name is used only for case-insensitive string comparison against scene metadata — never in SQL, file I/O, URL construction, or any injection-prone context.
  • Deep link parameters: Extracted via DeepLink.ValueOf() which handles URL decoding. Validated with string.IsNullOrEmpty.
  • CLI arguments: Parsed via ApplicationParametersParser. Validated with !string.IsNullOrEmpty.
  • Log messages: Spawn point names appear in ReportHub.LogWarning (not user-facing chat). No injection risk.
  • No secrets, credentials, or sensitive data involved. No security issues found.

Positive observations

  • Core mechanism is clean: single chokepoint in TeleportUtils.PickSpawnPoint with soft fallback — teleports never fail because of a bad name.
  • Named lookup uses manual iteration (not LINQ), consistent with CLAUDE.md §4.
  • Excellent test coverage: TeleportUtilsShould (named selection core), ChatParamUtilsShould (grammar table — 13 test cases covering every form), RealmLaunchSettingsShould (deep link / CLI), ChatTeleporterShould (already-in-realm branching).
  • TrimEnd() in CommandsHandleChatMessageBus is a good cross-cutting fix — trailing whitespace previously broke all command validation, not just /goto.
  • DeepLinkExtensions extraction is a clean refactoring that eliminates duplicated parsing logic.

STEP 6 — Complexity

COMPLEX — Modifies shared interfaces (IRealmNavigator, ITeleportController) and the teleport pipeline across 6+ assemblies, introduces new parsing grammar, and changes cold-start and runtime teleport flow.

STEP 7 — QA assessment

QA_REQUIRED: YES — Changes affect user-facing teleport behavior across all entry points (chat commands, deep links, CLI arguments, cold start, runtime realm changes). The PR description includes a thorough test matrix.

STEP 8 — Non-blocking warnings

None. Main.unity is not modified.

CI Status

  • ✅ Playmode tests pass (23,542 passed)
  • ❌ Editmode tests: 1 failure — ControlSceneUpdateLoopSystemShould.StartWorldSceneWithoutReadinessReportRegardlessOfSceneRoom. This test is in ControlSceneUpdateLoopSystemShould.cs, a file not touched by this PR, and tests scene update loop behavior unrelated to spawn points. Likely a pre-existing flaky test.
  • ⏳ Builds (Windows, macOS) and Lint still in progress
  • ✅ PR title follows semantic commit format (feat: ...)

REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Modifies shared interfaces (IRealmNavigator, ITeleportController) and the teleport pipeline across 6+ assemblies, introduces new parsing grammar, and changes cold-start and runtime teleport flow
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by mikhail-dcl via GitHub

Comment thread Explorer/Assets/DCL/Chat/Commands/GoToLocalChatCommand.cs
@popuz popuz added clean-build Used to trigger clean build on PR and removed force-build Used to trigger a build on draft PR labels Jul 15, 2026
@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

This comment has been minimized.

@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Claude finished @popuz's task in 1m 48s —— View job


Security-focused review (re-run on sync) — dependency / workflow / CI-script changes

  • Diff workflow files (test.yml, pr-comment-warnings.yml) vs origin/dev
  • Diff CI lint-script changes (scripts/lint/*.sh) vs origin/dev
  • Verify provenance of downloaded artifacts & job trust split
  • Classify risk & post verdict

What changed since the last security review: only the --no-swea parameter was removed (commit b57099b3f) — solution-wide analysis is now left ON on the 2023.1 CLI. That is a lint-coverage change, not a trust change. The two downloaded artifacts (ReSharper CLI 2023.1.2, JetBrains.Unity extension 2023.1.0.150) and the workflow diffs are otherwise unchanged from the last pass. No new runtime dependency is introduced.


CI-script changes — scripts/lint/*.sh

Artifact Version Source Type Scope Risk Confidence
ReSharper CommandLineTools 2023.1.2 download.jetbrains.com managed binary (CLI) CI + local dev lint only LOW HIGH
JetBrains.Unity extension (nupkg) 2023.1.0.150 plugins.jetbrains.com managed binary (ReSharper ext) CI + local dev lint only LOW HIGH

Provenance [PR]: both URLs point at official JetBrains domains and are pinned to exact, immutable version strings embedded in the URL — no floating tags, no third-party mirror. In-file comments explain why 2023.1 (later Unity plugins schedule a deferred-cache flush that crashes headless on Linux — RIDER-122490 / resharper-unity#2491) and the extension does exactly what the diff says (makes InspectCode Unity-aware so Assets//Packages/ stop firing false CheckNamespace). Purpose matches capability.

Shipping impact [PR/Inference]: these scripts run only during linting — the test.yml pull_request job (read-only token) and the local Stop hook. Nothing here ships in the end-user desktop client, so there is no new runtime attack surface for users.

Minor notes (non-blocking, LOW): no checksum/signature verification on the two wget downloads (download-resharper.sh:35,41). Pinned URLs over TLS on official domains mitigate this and it matches the pre-existing pattern; a SHA-256 check would harden against a compromised-mirror/MITM scenario.


Workflow files — the "PR-file warnings" feature

Trust split verified [PR]: test.yml triggers on pull_request (not pull_request_target) with top-level permissions: contents: read, packages: write, checks: write, actions: readno pull-requests: write. The new Collect PR-file warnings step runs there and only reads git data (git merge-base / git diff --name-only / jq / sed) — no PR content is executed or eval'd, and interpolated values (pull_request.number, steps.pr-findings.outputs.total) are integers, not injectable. It only produces warning-result.json.

The write-capable side (pr-comment-warnings.yml) runs on workflow_run in the trusted base context with explicit minimal contents: read + pull-requests: write. No LLM, no PR code execution, no pull_request_target. Correct.

Carried-over finding (still open):

  1. [MEDIUM] pr-comment-warnings.yml:89 renders the findings table with .file and .rule unsanitized, and .message stripped only of |/\r/\n (gsub("[|\r\n]"; " ")) — not </> — into a pull-requests: write bot comment. These fields originate from linting PR-controlled source (file paths, rule ids, message text) and cross a trust boundary. Real-world impact is limited (GitHub strips <script>, camo-proxies images, token is only pull-requests: write) — worst case is a cosmetically defaced bot comment. Recommend wrapping file/rule in code spans and HTML-escaping </> in message. (This file is under .github/workflows/, which the GitHub App cannot edit — @popuz would need to apply it.)

    Heredoc note: the static EOF delimiter (pr-comment-warnings.yml:73) is not exploitable via these fields — each finding is emitted as a single line and .message newlines are stripped, so a standalone EOF line can't be forged. A random delimiter (DELIM="EOF_$(uuidgen)") is good hygiene but non-blocking.

[LOW, optional] SHA-pin the third-party actions the comment job uses (actions/download-artifact@v4, peter-evans/find-comment@v2, peter-evans/create-or-update-comment@v3) — they run in the pull-requests: write job, so a compromised upstream tag would inherit that scope (W.7a). All are well-known and tag-pinned today.


ℹ️ Label note (non-blocking): the PR carries new-dependency, but no external runtime dependency is added — Packages/manifest.json, packages-lock.json, Assets/Plugins/, and all .asmdef files are untouched. The only additions are the test-only DCL.Chat.Commands.Tests.asmref (bare in-repo GUID reference) and the CI-only lint tooling above. Consider dropping the label to keep the supply-chain signal accurate.


Verdict

No HIGH findings. The CI lint-script changes are LOW risk — official, exact-pinned, dev/CI-only, non-shipping; removing --no-swea only widens lint coverage and does not change the trust posture. One pre-existing MEDIUM (unescaped cross-boundary file/rule/message content in the bot comment) remains open.

DEPENDENCY_REVIEW: NEEDS_ATTENTION

Scope: security review of the workflow / dependency / CI-script files only. The feature-code review (teleport pipeline) is covered separately by Jarvis (REVIEW_RESULT: PASS).

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 new-dependency

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants