Skip to content

fix: combined fixes first sweep - #9784

Open
alejandro-jimenez-dcl wants to merge 8 commits into
devfrom
bugsweep/combined-fixes-first-sweep
Open

fix: combined fixes first sweep#9784
alejandro-jimenez-dcl wants to merge 8 commits into
devfrom
bugsweep/combined-fixes-first-sweep

Conversation

@alejandro-jimenez-dcl

@alejandro-jimenez-dcl alejandro-jimenez-dcl commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Pull Request Description

What does this PR change?

Combines five bugsweep fixes into a single PR, rebased onto updated dev, one squashed commit per original PR:

Commit Original PR Fixes Summary
96fe15f83 #9609 #9550 The claimed-name dropdown pre-selected an entry whenever its text matched the display name, even for unclaimed names. Re-clicking the pre-selected entry never fires the dropdown event, so Save could never be enabled and the minted NAME was impossible to equip. Now it only pre-selects when the profile actually has a claimed name.
c8d83d30f #9611 #9546 Events-page jump-ins always targeted the raw parcel center. When the event's coordinates are the scene's own base parcel that made them land somewhere different from map/chat teleports. Now that case falls through to the scene's authored spawn point. Sub-parcel jump-ins inside multi-parcel scenes are untouched.
f74257588 #9774 #9511 A single transient GLTF load failure (aborted request, corrupt partial download) was cached forever as irrecoverable, leaving items like fishing rods permanently meshless until restart. Transient failures are no longer cached as permanent.
4ddaace0c #9776 (copy) The gift notification fires seconds after the transfer, but the backpack list sits behind a server-side ownership cache of up to 10 minutes. The notification copy now tells the receiver the item can take a few minutes to show up in the backpack.
918e283e1 #9777 #9517 The teleport-timeout recovery path could race its own teardown, so ground and LODs never re-registered and /reload reported "not in a SDK7 scene". The fallback is now guarded on cancellation.

Follow-up commits on this branch:

Conflict resolutions (only #9611 conflicted)

  • TeleportController.cs: dev gained a named-spawn-point-in-parcel block (fix: events JumpIn lands in the middle of the scene #9567) overlapping the PR's change. Both are kept: dev's named-spawn-point check runs first, then this PR's base-parcel fallback.
  • The PR's RealmNavigation.Tests.asmref duplicated the asmref dev already ships (same assembly GUID), so it was dropped in favor of dev's existing one.

Each fix includes a regression test that fails without it.

Replaces #9609, #9611, #9774, #9776 and #9777.

Test Instructions

Steps (standard run):

metaforge explorer run 9784

Overall smoke test (required)

This PR touches teleporting, scene asset loading, terrain and LODs, notifications, and the profile name editor, so a general pass is required on top of the specific checks below: log in, walk around Genesis City, teleport between a few scenes using the map, chat and the Events page, open the backpack and the profile, and confirm nothing looks or behaves worse than before.

Specific checks

  1. Name equip popup (Equipping minted Name is silently blocked when it matches the current non-unique display name #9550): use an account that owns a minted NAME spelled exactly the same as its current display name, with the name not yet claimed. Open the profile name editor and select the owned name in the dropdown. The entry must be selectable and the Save button must become enabled (before this fix, Save stayed disabled forever).
  2. Events jump-in landing (Unify landing position between map/chat teleport and Events page teleport #9546): from Discover > Events, jump in to an event placed at a scene's own coordinates (single-parcel scenes are the easy case). You should land at the scene's spawn point, in the same spot as teleporting to that scene from the map or from chat. Also verify that jumping in to an event on a specific parcel inside a large multi-parcel scene still lands on that parcel.
  3. Invisible items after load hiccups (Fishing rod model not visible during fishing — only line renders (GLB disappears under load) #9511): visit a scene with many copies of the same item (the fishing scene with its rods is the known repro). All rods must be visible. Then hop through a few heavy scenes to build memory pressure and come back: the rods must still appear, with no permanently invisible items until restart.
  4. Gifting popup copy: send a wearable or emote as a gift to a second account. On the receiving account, check the gift toast and the notification panel entry: the copy must now mention that the item can take a few minutes to appear in the backpack. Opening the backpack right away and not seeing the item yet is expected behavior, not a bug.
  5. Ground and LODs after teleport timeout ([QA] LOD | Ground (LODs) missing after teleport timeout #9517): teleport to a very heavy scene so the loading screen times out and closes early. After the timeout, the ground/terrain and distant scenes (LODs) must still load in around you, and /reload must work in the scene you landed in.

Quality Checklist

  • Changes have been tested locally (each original PR was CI-green; regression tests included)
  • Documentation has been updated (not required)
  • Performance impact has been considered

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Windows and Mac build successful in Unity Cloud! You can find a link to the downloadable artifact below.

Name Link
Commit dbaee58
Logs https://github.com/decentraland/unity-explorer/actions/runs/32190289897
Download Windows https://github.com/decentraland/unity-explorer/suites/87266285252/artifacts/9344895670
Download Windows S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/bugsweep/combined-fixes-first-sweep/pr-25292-dbaee58/Decentraland_windows64.zip
Download Mac https://github.com/decentraland/unity-explorer/suites/87266285252/artifacts/9345494308
Download Mac S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/bugsweep/combined-fixes-first-sweep/pr-25292-dbaee58/Decentraland_macos.zip
Built on 2026-08-18T22:56:41Z

Lint

Warnings not reduced: 13116 => 13129 — remove at least 14 warnings to merge.

Warnings/errors in files changed by this PR (86)
Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Systems/CleanUpGltfContainerSystem.cs:69  CSharpWarnings::CS8604  Possible null reference argument for parameter 'gltfContainerAsset' in 'DCL.Interaction.Utility.EntityCollidersCacheExtensions.Remove'
Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Systems/ResetGltfContainerSystem.cs:71  CSharpWarnings::CS8604  Possible null reference argument for parameter 'gltfContainerAsset' in 'DCL.Interaction.Utility.EntityCollidersCacheExtensions.Remove'
Assets/DCL/Notifications/NotificationEntry/GiftToastView.cs:15  CSharpWarnings::CS8618  Non-nullable event 'NotificationClicked' is uninitialized. Consider adding the 'required' modifier or declaring the event as nullable.
Assets/DCL/RealmNavigation/Tests/TeleportControllerShould.cs:21  CSharpWarnings::CS8618  Non-nullable field 'controller' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/UI/Profiles/Names/Tests/ProfileNameEditorControllerShould.cs:27  CSharpWarnings::CS8618  Non-nullable field 'controller' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/UI/Profiles/Names/Tests/ProfileNameEditorControllerShould.cs:28  CSharpWarnings::CS8618  Non-nullable field 'profile' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/RealmNavigation/Tests/TeleportControllerShould.cs:22  CSharpWarnings::CS8618  Non-nullable field 'retrieveScene' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/UI/Profiles/Names/Tests/ProfileNameEditorControllerShould.cs:26  CSharpWarnings::CS8618  Non-nullable field 'view' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/UI/Profiles/Names/Tests/ProfileNameEditorControllerShould.cs:25  CSharpWarnings::CS8618  Non-nullable field 'viewGameObject' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/RealmNavigation/Tests/TeleportControllerShould.cs:19  CSharpWarnings::CS8618  Non-nullable field 'world' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Landscape/TerrainGenerator.cs:66  CSharpWarnings::CS8618  Non-nullable members 'terrainGenData', 'boundariesGenerator', 'factory', 'TerrainRoot', 'Ocean', 'Wind', 'Cliffs', 'landscapeData', 'TerrainModel', 'OccupancyMap' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the members as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:31  CSharpWarnings::CS8618  Non-nullable property 'AcceptedNotificationAudio' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:20  CSharpWarnings::CS8618  Non-nullable property 'Background' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftToastView.cs:19  CSharpWarnings::CS8618  Non-nullable property 'Background' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:26  CSharpWarnings::CS8618  Non-nullable property 'CloseButton' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:24  CSharpWarnings::CS8618  Non-nullable property 'GiftNameText' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:22  CSharpWarnings::CS8618  Non-nullable property 'HeaderText' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:21  CSharpWarnings::CS8618  Non-nullable property 'MainButton' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftToastView.cs:20  CSharpWarnings::CS8618  Non-nullable property 'MainButton' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:17  CSharpWarnings::CS8618  Non-nullable property 'Notification' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftToastView.cs:17  CSharpWarnings::CS8618  Non-nullable property 'Notification' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftToastView.cs:23  CSharpWarnings::CS8618  Non-nullable property 'NotificationAudio' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:29  CSharpWarnings::CS8618  Non-nullable property 'NotificationImage' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftToastView.cs:22  CSharpWarnings::CS8618  Non-nullable property 'NotificationImage' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:30  CSharpWarnings::CS8618  Non-nullable property 'NotificationImageBackground' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:28  CSharpWarnings::CS8618  Non-nullable property 'NotificationTypeImage' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:25  CSharpWarnings::CS8618  Non-nullable property 'TimeText' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:23  CSharpWarnings::CS8618  Non-nullable property 'TitleText' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftToastView.cs:21  CSharpWarnings::CS8618  Non-nullable property 'TitleText' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs:27  CSharpWarnings::CS8618  Non-nullable property 'UnreadImage' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Notifications/NotificationEntry/GiftToastView.cs:31  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/UI/Profiles/Names/Tests/ProfileNameEditorControllerShould.cs:54  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Infrastructure/ECS/Unity/StreamableLoading/Tests/LoadSystemBaseIrrecoverableFailureShould.cs:175  EmptyGeneralCatchClause  Empty general catch clause suppresses any errors
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:15  InconsistentNaming  Name 'ColoredTextFormat' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'COLORED_TEXT_FORMAT'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:8  InconsistentNaming  Name 'DefaultInfoMessage' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'DEFAULT_INFO_MESSAGE'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:28  InconsistentNaming  Name 'DefaultStatusMessage' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'DEFAULT_STATUS_MESSAGE'.
Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Systems/CleanUpGltfContainerSystem.cs:65  InconsistentNaming  Name 'DestroyGLTFContainer' does not match rule 'members_should_be_pascal_case'. Suggested name is 'DestroyGltfContainer'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:52  InconsistentNaming  Name 'ErrorDialogCancelText' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'ERROR_DIALOG_CANCEL_TEXT'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:55  InconsistentNaming  Name 'ErrorDialogConfirmText' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'ERROR_DIALOG_CONFIRM_TEXT'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:58  InconsistentNaming  Name 'ErrorDialogDescription' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'ERROR_DIALOG_DESCRIPTION'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:61  InconsistentNaming  Name 'ErrorDialogSupportLinkFormat' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'ERROR_DIALOG_SUPPORT_LINK_FORMAT'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:49  InconsistentNaming  Name 'ErrorDialogTitle' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'ERROR_DIALOG_TITLE'.
Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Systems/CleanUpGltfContainerSystem.cs:48  InconsistentNaming  Name 'FinalizeGLTFContainer' does not match rule 'members_should_be_pascal_case'. Suggested name is 'FinalizeGltfContainer'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:73  InconsistentNaming  Name 'GiftLoading' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'GIFT_LOADING'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:36  InconsistentNaming  Name 'GiftOnItsWayMessage' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'GIFT_ON_ITS_WAY_MESSAGE'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:70  InconsistentNaming  Name 'GiftOpenedTitle' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'GIFT_OPENED_TITLE'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:45  InconsistentNaming  Name 'GiftReceivedFromFormat' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'GIFT_RECEIVED_FROM_FORMAT'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:42  InconsistentNaming  Name 'GiftReceivedSenderTitleFormat' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'GIFT_RECEIVED_SENDER_TITLE_FORMAT'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:39  InconsistentNaming  Name 'GiftReceivedTitleFormat' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'GIFT_RECEIVED_TITLE_FORMAT'.
Assets/DCL/Backpack/Gifting/GiftingTextIds.cs:31  InconsistentNaming  Name 'GiftSentTextFormat' does not match rule 'constants_should_be_capital_snake_case'. Suggested name is 'GIFT_SENT_TEXT_FORMAT'.

…and 36 more (see the csharp-lint-reports artifact).

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 25197 0 13
PlayMode ✅ Passed 240 0 37

…ile constructor

Profile.Create was removed on dev; use new Profile(UserId.New(...).Unwrap(), ...) like the rest of the test suite.

@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 — bugsweep: combined fixes first sweep


STEP 2 — Root-cause check

This PR bundles several independent bug fixes, each targeting a distinct root cause:

  1. Gift notification copy — Gift-received notifications implied the item was already in the backpack. The copy is updated to say the gift is "on its way." Fixes the cause (misleading text).
  2. GLTF container double-dereference — After releasing a GLTF asset, the promise still held the resolved result, allowing a repeated cleanup path to dereference and dispose the asset a second time. Nulling the promise after release fixes the cause (stale cached result).
  3. Irrecoverable failure cache poisoning — Any failed load (including transient exceptions) was cached as irrecoverable, causing session-permanent asset loading failures. The fix restricts caching to definitive 4xx HTTP errors. Fixes the cause (over-broad caching predicate).
  4. Realm recovery after failed change — A cancelled realm change could leave the client with no configured realm. The fix adds a recovery path to fall back to the previous realm. Fixes the cause (missing recovery after partial teardown).
  5. Terrain partial cleanup — A cancelled terrain generation left IsTerrainGenerated = true despite the terrain being destroyed, and swallowed OperationCanceledException instead of propagating it. The fix moves the flag after success and re-throws cancellation. Fixes the cause (incorrect state after partial generation).
  6. TeleportController landOnParcel — When the target parcel equals the scene's base parcel, landOnParcel is cleared so spawn-point resolution runs. Fixes the cause (flag staying set when there's no sub-parcel to target).
  7. Profile name dropdown — An unclaimed display name matching an owned NFT name pre-selected the dropdown, making Save permanently disabled. The fix checks HasClaimedName before pre-selecting. Fixes the cause (incorrect pre-selection logic).

Verdict: PASS — All fixes target root causes, not symptoms.


STEP 3 — Design & integration

GLTF promise-nulling (CleanUpGltfContainerSystem, ResetGltfContainerSystem): The AssetPromise.NULL sentinel is already used elsewhere in the codebase (e.g. InvalidatePromise in ResetGltfContainerSystem already sets it). The fix adds the same pattern to the two remaining release paths that were missing it. The lifecycle owner (the GLTF container systems) is the correct place for this. No new unit introduced. PASS.

IsIrrecoverableFailure (LoadSystemBase): The new gate lives inside the existing RepeatLoopAsync method on the system that owns the irrecoverable-failure cache. No new unit, no design change — just a tighter predicate. PASS.

RecoverUnconfiguredRealmAsync (RealmNavigator): This is a private recovery method on the existing RealmNavigator, which already owns the realm-change lifecycle. It reuses DoChangeRealmAsync (the same operation factory used for normal realm changes) and runs it through loadingScreen.ShowWhileExecuteTaskAsync (which has its own built-in timeout). Uses CancellationToken.None intentionally — the recovery must not be externally cancelled (the original token is already cancelled), and the loading-screen timeout is the safety net. No new unit. PASS.

DestroyPartialTerrain (TerrainGenerator): This is a private helper on the class that owns the terrain lifecycle. Ocean and Cliffs are children of TerrainRoot and are destroyed when it is. Wind is unparented and correctly destroyed separately. Trees is a data object (not a GameObject) and doesn't need destruction here. PASS.

TeleportController landOnParcel fix: The fix is placed immediately after the existing TryPickSpawnPointNameInParcel check, before the !landOnParcel branch that snaps to the base parcel. This is the correct location — it extends the existing spawn-point resolution logic. PASS.


STEP 4 — Member audit

GiftingTextIds.GiftOnItsWayMessage (new public const): Used by GiftNotificationView.Configure, GiftToastView.Configure, GiftToastView.UpdateSenderName, and GiftReceivedTitleFormat (4 consumers). Appropriate as a shared constant. However, GiftReceivedNotification.NOTIFICATION_TITLE inlines the same sentence — see finding below.

TerrainGenerator.DestroyPartialTerrain (new private method): Called from two catch blocks within the same class. Correctly extracted to avoid duplication.

RealmNavigator.RecoverUnconfiguredRealmAsync (new private method): Single consumer (TryChangeRealmAsync). It encapsulates the recovery logic cleanly; merging it inline would harm readability. Acceptable.

LoadSystemBase.IsIrrecoverableFailure (new private static method): Single consumer (RepeatLoopAsync). Extracted for clarity of the predicate — acceptable given the logic complexity.


STEP 5 — Line-level review

See inline comments for specific findings.


STEP 6 — Complexity assessment

COMPLEX — touches ECS systems (GLTF container cleanup), asset loading pipeline (LoadSystemBase irrecoverable failure caching), realm navigation lifecycle (RealmNavigator, RealmController), landscape generation (TerrainGenerator), and teleport controller logic across 26 files with 900+ lines of changes.


STEP 7 — QA assessment

QA_REQUIRED: YES — changes affect runtime behavior: gift notification UI text, GLTF asset lifecycle, asset loading retry behavior, realm navigation and recovery, terrain generation, teleport positioning, and profile name editor UI.


STEP 8 — Non-blocking warnings

No main scene file modified. No warnings.


REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches ECS GLTF container systems, StreamableLoading pipeline, realm navigation lifecycle, landscape generation, and teleport controller
QA_REQUIRED: YES


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

Comment thread Explorer/Assets/DCL/Notifications/NotificationEntry/GiftNotificationView.cs Outdated
Comment thread Explorer/Assets/DCL/RealmNavigation/RealmNavigator.cs
Comment thread Explorer/Assets/DCL/Landscape/TerrainGenerator.cs
…st test

- Centralize the received-gift title copy in GiftingTextIds (new GiftReceivedSenderTitleFormat) and reference GiftOnItsWayMessage from GiftReceivedNotification instead of duplicating the sentence
- Replace the fragile backing-field reflection in GiftReceivedCopyShould with a settable GiftToastView.TitleText, matching GiftNotificationView.HeaderText
- Document why RealmNavigator's recovery uses CancellationToken.None

@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 — bugsweep: combined fixes first sweep


STEP 2 — Root-cause check

All five fixes address genuine root causes rather than symptoms:

Fix Root cause Verdict
#9550 — Name equip FindIndex matched on display-name text alone, ignoring whether the name was actually claimed. The fix guards with profile.HasClaimedName. ✅ Cause fixed
#9546 — Events jump-in landOnParcel: true survived even when the target parcel equals the scene's base parcel, forcing the raw-parcel-center branch instead of spawn-point resolution. The fix clears the flag for base-parcel targets. ✅ Cause fixed
#9511 — GLTF cache poisoning RepeatLoopAsync cached any concluded failure as irrecoverable — including transient exceptions with no HTTP status. IsIrrecoverableFailure now restricts caching to genuine 4xx client errors (excluding 408/425/429) via WebRequestUtils.IsIrrecoverableError(). ✅ Cause fixed
#9776 — Gift copy The notification copy implied immediate availability; the server-side ownership cache can lag by minutes. Copy now says the item is on its way. ✅ Cause fixed
#9517 — Teleport-timeout race Multiple interacting bugs: (a) TerrainGenerator set IsTerrainGenerated = true in finally even on failure, (b) OperationCanceledException was swallowed in both TerrainGenerator and RealmController.SetRealmAsync, (c) LoadLandscapeTeleportOperation ignored the landscape result, (d) the in-chain fallback ran on a cancelled token producing teardown side-effects. Fixed at each layer. ✅ Causes fixed

STEP 3 — Design & integration

Owner search for new units:

No new long-lived systems, plugins, managers, services, or controllers are introduced. All changes modify existing lifecycle paths:

  • DestroyPartialTerrain() — private helper in TerrainGenerator, called from existing catch blocks. TerrainGenerator already owns the terrain lifecycle (create in GenerateGenesisTerrainAndShowAsync, destroy in Dispose). The new method centralizes cleanup for interrupted generation. Home is correct.
  • RecoverUnconfiguredRealmAsync() — private method in RealmNavigator, which already owns the realm-change orchestration (including the existing in-chain fallback in DoChangeRealmAsync). The recovery fires only when the in-chain fallback couldn't run (cancelled token). Placing it here avoids duplicating the ShowWhileExecuteTaskAsync call chain. Home is correct. Checked RealmController — it owns realm configuration but not the loading-screen / fallback orchestration, so recovery belongs in RealmNavigator.
  • IsIrrecoverableFailure() — static method in LoadSystemBase, which already owns the SetIrrecoverableFailure call and the RepeatLoopAsync return path. The decision to restrict caching is a loading-system concern (the cache itself is agnostic). Home is correct.
  • GLTF promise nulling (component.Promise = ...NULL) — added at the two existing release paths in CleanUpGltfContainerSystem.DestroyGLTFContainer and ResetGltfContainerSystem.HandleComponentRemoval. These are the lifecycle owners for GLTF component teardown. Home is correct.

Teardown / consumption trace:

No new subscriptions, event hookups, callbacks, connections, rooms, buffers, or measurements are introduced. The changes modify existing control flow and add defensive cleanup.

  • RecoverUnconfiguredRealmAsync uses CancellationToken.None — the loading screen's own timeout bounds the operation. No unbounded work.
  • RealmController.SetRealmAsync now rethrows OperationCanceledException instead of swallowing it — this is critical because the previous realm is already unloaded at that point; swallowing would leave a half-configured realm appearing successful.

Design verdict: PASS — no new lifecycle units, no polling patterns, no persistent state outside ECS.


STEP 4 — Member audit

Member Consumers Audit
GiftingTextIds.GiftOnItsWayMessage (new const) 3 — GiftReceivedTitleFormat, GiftReceivedSenderTitleFormat, GiftReceivedNotification.NOTIFICATION_TITLE Shared message string factored into a const. No single-use concern. ✓
GiftingTextIds.GiftReceivedSenderTitleFormat (new const) 3 — GiftToastView.Configure, GiftToastView.UpdateSenderName, GiftNotificationView.Configure Format string for the address/plain-name variant ({0} only). Parallel to the existing GiftReceivedTitleFormat (color+name, {0}+{1}). ✓
GiftToastView.TitleText setter (widened from private set to set) Test (GiftReceivedCopyShould) Aligns with GiftNotificationView.HeaderText which already has a public setter. [field: SerializeField] backing field was already inspector-writable. ✓
TerrainGenerator.DestroyPartialTerrain() (new private) 2 — cancellation catch and error catch Centralizes cleanup. Not single-use (called from two paths). ✓
RealmNavigator.RecoverUnconfiguredRealmAsync() (new private) 1 — TryChangeRealmAsync Single consumer, but the extracted method is non-trivial (7 lines of orchestration). Extraction is warranted for readability and the explanatory comment. ✓
LoadSystemBase.IsIrrecoverableFailure() (new private static) 1 — RepeatLoopAsync Single consumer, but encapsulates a specific policy decision with a clear name. ✓

STEP 5 — Line-level review

Pass A — Blocking issues: None found.

Pass B — Design, encapsulation & resource smells: None found.

Security review: No secrets, credentials, injection vectors, auth/authz issues, unsafe input handling, or sensitive data exposure introduced. The unsafe blocks in TerrainGenerator are existing NativeArray operations unrelated to this PR.

Detailed per-fix notes:

  1. IsIrrecoverableFailure logic — The inner-exception loop correctly handles wrapped exceptions (e.g., AggregateException wrapping UnityWebRequestException). The conjunction ResponseCode is >= 400 and < 500 && IsIrrecoverableError() double-filters: the range check excludes 5xx and aborted (code 0) requests; IsIrrecoverableError() further excludes 408/425/429 and SSL errors. Sound.

  2. TerrainGenerator exception handling — Moving IsTerrainGenerated = true into the try block after success, and adding throw; in the cancellation catch, are both critical. The old finally block's memory log (afterCleaning - beforeCleaning) was measuring the cost of setting a boolean — effectively a no-op. Removing it is a cleanup.

  3. RealmNavigator recovery guard!realmController.RealmData.Configured && !ct.IsCancellationRequested correctly distinguishes loading-screen timeout (internal token cancelled, external ct intact → recover) from user/caller cancellation (external ct cancelled → skip recovery). The recovery uses CancellationToken.None with the loading screen providing its own timeout bound.

  4. DoChangeRealmAsync cancellation guardif (ct.IsCancellationRequested) return opResult; before the fallback prevents a second round of teardown side-effects when the operation's token is already cancelled.

  5. GLTF promise nulling — Setting component.Promise = AssetPromise<...>.NULL after ForgetLoading and cache.Dereference prevents double-release when multiple cleanup paths run on the same entity (e.g., per-frame DeleteEntityIntention followed by scene teardown). TryGetResult on a NULL promise returns false, so subsequent release paths safely no-op.

  6. LoadLandscapeTeleportOperation result checkct.ThrowIfCancellationRequested() before throw new Exception(...) ensures cancellation propagates as OperationCanceledException rather than a generic exception. Used in an awaited async method with exception handling — acceptable per CLAUDE.md §9.

  7. GiftNotificationView.Configure — The change from $"{shortAddr} sent you a something!" to string.Format(GiftingTextIds.GiftReceivedSenderTitleFormat, shortAddr) also fixes the existing typo ("a something").


STEP 6 — Complexity assessment

COMPLEX — The teleport-timeout fix (#9517) modifies async/cancellation flows across the realm navigation subsystem (RealmNavigator, RealmController, LoadLandscapeTeleportOperation), touches the terrain generation lifecycle (TerrainGenerator), and interacts with the loading-screen timeout mechanism. The GLTF cache-poisoning fix (#9511) modifies the asset loading pipeline's failure-caching policy.


STEP 7 — QA assessment

QA_REQUIRED: YES — All five fixes affect runtime behavior: teleporting, scene asset loading, terrain/LODs, UI notifications, and the profile name editor.


STEP 8 — Non-blocking warnings

⚠️ PR title does not match semantic commit format. CI check semantic / title-matches-convention is failing. bugsweep: is not a standard type. Consider fix: combined bugsweep fixes (first sweep) or similar.

⚠️ ProfileNameEditorControllerShould uses reflection to invoke the compiler-generated method for the SetUpClaimed local function. This is fragile against compiler implementation changes (method name mangling). A comment documents the rationale. Acceptable for now, but worth noting for future maintainers.


Test coverage

All five fixes include regression tests that fail without the fix:

  • ProfileNameEditorControllerShould — verifies dropdown stays unselected for unclaimed names
  • TeleportControllerShould — verifies landOnParcel is cleared for base-parcel targets
  • LoadSystemBaseIrrecoverableFailureShould — verifies transient exceptions aren't cached; verifies 404 IS cached
  • GiftReceivedCopyShould — verifies all gift surfaces include the "on its way" copy
  • LoadLandscapeTeleportOperationShould — verifies cancelled terrain load fails the teleport

REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Modifies async/cancellation flows across realm navigation, terrain generation lifecycle, and the asset loading pipeline's failure-caching policy.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by alejandro-jimenez-dcl via GitHub

Comment thread Explorer/Assets/DCL/Landscape/TerrainGenerator.cs
Comment thread Explorer/Assets/DCL/RealmNavigation/RealmNavigator.cs
GiftReceivedNotification (DCL.SharedAPI) referenced GiftingTextIds (DCL.Social), but DCL.Social already depends on DCL.SharedAPI, so the reference could never compile. Move the shared 'on its way' copy into GiftReceivedNotification and alias it from GiftingTextIds, keeping a single definition without the cycle.
@alejandro-jimenez-dcl alejandro-jimenez-dcl changed the title bugsweep: combined fixes first sweep fix: combined fixes first sweep Aug 18, 2026
@decentraland-bot

Copy link
Copy Markdown
Contributor

PR #9784, run #32195050170

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

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

Intel Core i5

Metric Baseline Change Δ Result
Samples 2313 (×3) 2371 (×3)
CPU average 38.6 ms (33.4–38.8) 37.6 ms (33.2–37.8) -1.0 ms ⚪ within noise
CPU 1% worst 322.6 ms (57.0–343.5) 283.1 ms (33.4–300.4) -39.4 ms ⚪ within noise
CPU 0.1% worst 344.1 ms (341.1–360.3) 309.8 ms (34.1–330.5) -34.3 ms ⚪ within noise
GPU average 9.5 ms (9.2–9.6) 9.5 ms (9.4–9.5) -0.0 ms ⚪ within noise
GPU 1% worst 35.6 ms (23.5–37.7) 30.6 ms (19.7–33.2) -5.0 ms ⚪ within noise
GPU 0.1% worst 44.4 ms (39.8–45.0) 37.0 ms (31.3–42.4) -7.4 ms ⚪ within noise
Exceptions per run 66 65 -1 🟢 fewer errors
Exception breakdown
Exception Baseline (3 runs) Change (3 runs)
[UI] DllNotFoundException 192 192
[ENGINE] ObjectDisposedException 3 3
[ENGINE] NullReferenceException 3 0

Apple M1

Metric Baseline Change Δ Result
Samples 4105 (×3) 4004 (×3)
CPU average 21.8 ms (21.8–22.9) 22.4 ms (21.4–23.4) 0.5 ms ⚪ within noise
CPU 1% worst 215.9 ms (215.7–217.7) 233.5 ms (34.7–233.7) 17.6 ms ⚪ within noise
CPU 0.1% worst 226.3 ms (222.9–228.8) 236.8 ms (35.1–239.1) 10.6 ms ⚪ within noise
GPU average 2.5 ms (2.0–3.2) 2.5 ms (0.2–3.9) 0.0 ms ⚪ within noise
GPU 1% worst 34.3 ms (34.2–36.2) 34.6 ms (19.5–36.5) 0.3 ms ⚪ within noise
GPU 0.1% worst 36.3 ms (34.8–37.5) 37.7 ms (35.2–37.8) 1.4 ms ⚪ within noise
Exceptions per run 0 0 0 ⚪ none new

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants