Skip to content

fix: discard duplicate scene facade for already-cached parcels and serialize realm changes - #9807

Closed
alejandro-jimenez-dcl wants to merge 1 commit into
mainfrom
bugsweep/scenescache-duplicate-parcel-add
Closed

fix: discard duplicate scene facade for already-cached parcels and serialize realm changes#9807
alejandro-jimenez-dcl wants to merge 1 commit into
mainfrom
bugsweep/scenescache-duplicate-parcel-add

Conversation

@alejandro-jimenez-dcl

Copy link
Copy Markdown
Contributor

Problem

ArgumentException: An item with the same key has already been added. Key: (2, -4) from
ScenesCache.Add (Sentry UNITY-EXPLORER-PA9, 75 events / 8 users, ongoing since v0.150).
The crash is the visible tip: session breadcrumbs show every scene loading twice from
session start, zombie facades ripping the live scene's cache mappings on unload, and a
~1.5 s dispose/re-create churn loop under the player.

Root cause

Two composing defects. (1) RealmController.SetRealmAsync has no re-entrancy guard: two
overlapping calls both pass the unload phase and both create a realm entity → two
independent scene-pointer pipelines mint every scene-definition entity twice for the whole
session. (2) The consume path assumes the one-entity-per-parcel invariant instead of
enforcing it: the duplicate consume's Dictionary.Add throw is swallowed, but
World.Add(entity, scene) still runs - the duplicate keeps a zombie facade that never
started, and its eventual unload removes whatever facade owns the parcel, i.e. the live
scene's cache entries.

Fix (~30 LOC, two loci)

  • A (invariant at the single cache writer): ControlSceneUpdateLoopSystem.HandleNotCreatedScenes
    detects a parcel collision after a successful consume (plain for-loop, non-PX only), logs
    a warning, disposes the duplicate facade, and returns without attaching it - the duplicate
    entity stays inert and its unload takes the promise-only path, which cannot touch the live
    scene's mappings. Keep-first is correct (in-session duplicates are always the same scene id).
  • B (close the mint): SetRealmAsync serialized behind a DCLSemaphoreSlim(1,1) (the
    repo's WebGL-safe wrapper; raw SemaphoreSlim is lint-forbidden) - overlapping realm
    changes now execute sequentially, restoring the at-most-one-realm-entity invariant all
    pointer dedup relies on.

Test

Two new EditMode tests in the existing ControlSceneUpdateLoopSystemShould harness:
DiscardDuplicateSceneForSameParcelsKeepingLiveFacade (duplicate disposed exactly once,
never started, exactly one facade attached, cache holds the kept facade, no error log) and
KeepLiveSceneCacheMappingWhenDuplicateEntityUnloads (cascade guard through a real
UnloadSceneSystem). Fix B validated by review (mocking the /about + WorldManifest stack
is out of proportion).

Validation

Windows Unity 6000.4 EditMode lane at the pin: RED FAIL 2/2 as intended (both failed via
the unhandled same-key ArgumentException surfacing through LogAssert) / GREEN PASS 2/2.

Fixes #8911
Related: #8883, #8720, #8492/#8441/#8183 (closed same-key family in sibling subsystems,
context for the defect class)

Includes inspection-warning cleanup in all touched files.

Fixes #8720

…rialize realm changes

## Problem

`ArgumentException: An item with the same key has already been added. Key: (2, -4)` from
`ScenesCache.Add` (Sentry UNITY-EXPLORER-PA9, 75 events / 8 users, ongoing since v0.150).
The crash is the visible tip: session breadcrumbs show every scene loading twice from
session start, zombie facades ripping the live scene's cache mappings on unload, and a
~1.5 s dispose/re-create churn loop under the player.

## Root cause

Two composing defects. (1) `RealmController.SetRealmAsync` has no re-entrancy guard: two
overlapping calls both pass the unload phase and both create a realm entity → two
independent scene-pointer pipelines mint every scene-definition entity twice for the whole
session. (2) The consume path assumes the one-entity-per-parcel invariant instead of
enforcing it: the duplicate consume's `Dictionary.Add` throw is swallowed, but
`World.Add(entity, scene)` still runs — the duplicate keeps a zombie facade that never
started, and its eventual unload removes whatever facade owns the parcel, i.e. the live
scene's cache entries.

## Fix (~30 LOC, two loci)

- A (invariant at the single cache writer): `ControlSceneUpdateLoopSystem.HandleNotCreatedScenes`
  detects a parcel collision after a successful consume (plain for-loop, non-PX only), logs
  a warning, disposes the duplicate facade, and returns without attaching it — the duplicate
  entity stays inert and its unload takes the promise-only path, which cannot touch the live
  scene's mappings. Keep-first is correct (in-session duplicates are always the same scene id).
- B (close the mint): `SetRealmAsync` serialized behind a `DCLSemaphoreSlim(1,1)` (the
  repo's WebGL-safe wrapper; raw SemaphoreSlim is lint-forbidden) — overlapping realm
  changes now execute sequentially, restoring the at-most-one-realm-entity invariant all
  pointer dedup relies on.

## Test

Two new EditMode tests in the existing `ControlSceneUpdateLoopSystemShould` harness:
`DiscardDuplicateSceneForSameParcelsKeepingLiveFacade` (duplicate disposed exactly once,
never started, exactly one facade attached, cache holds the kept facade, no error log) and
`KeepLiveSceneCacheMappingWhenDuplicateEntityUnloads` (cascade guard through a real
`UnloadSceneSystem`). Fix B validated by review (mocking the /about + WorldManifest stack
is out of proportion).

## Validation

Windows Unity 6000.4 EditMode lane at the pin: RED FAIL 2/2 as intended (both failed via
the unhandled same-key ArgumentException surfacing through LogAssert) / GREEN PASS 2/2.

Fixes #8911
Related: #8883, #8720, #8492/#8441/#8183 (closed same-key family in sibling subsystems,
context for the defect class)

Includes inspection-warning cleanup in all touched files.
@alejandro-jimenez-dcl
alejandro-jimenez-dcl requested review from a team as code owners August 19, 2026 12:20
@alejandro-jimenez-dcl alejandro-jimenez-dcl self-assigned this Aug 19, 2026
@github-actions
github-actions Bot requested a review from anicalbano August 19, 2026 12:32
@decentraland-bot
decentraland-bot self-requested a review August 19, 2026 12:32
@alejandro-jimenez-dcl
alejandro-jimenez-dcl marked this pull request as draft August 19, 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.

PR Review: fix: discard duplicate scene facade for already-cached parcels and serialize realm changes


STEP 2 — Root-cause check: PASS ✅

Problem: ArgumentException from ScenesCache.Add when duplicate scene-definition entities mint two facades for the same parcel. Zombie facades rip live-scene cache mappings on unload, causing a churn loop.

Root cause: Two composing defects: (1) SetRealmAsync has no re-entrancy guard — overlapping calls each create a realm entity and an independent scene-pointer pipeline, minting every scene-definition entity twice. (2) The consume path assumes but does not enforce the one-entity-per-parcel invariant — the duplicate Dictionary.Add throws, but World.Add(entity, scene) still runs, leaving a zombie facade whose eventual unload removes the live scene's cache entries.

Does the diff fix the cause? Yes, on two levels:

  • Fix B (semaphore) eliminates the root cause: serializing realm changes behind DCLSemaphoreSlim(1,1) restores the at-most-one-realm-entity invariant.
  • Fix A (parcel collision guard) provides defense-in-depth at the consume path: if a duplicate entity somehow arrives, it is detected and discarded before attachment.

Neither fix is a symptom workaround — Fix B prevents the over-mint, Fix A enforces the invariant the cache writer relies on.


STEP 3 — Design & integration: PASS ✅

No new long-lived units are introduced. The semaphore is a field on the existing RealmController; the guard is a conditional inside the existing HandleNotCreatedScenes query method.

Owner search for Fix A (parcel collision guard):

  • Lifecycle managed: scene facade creation (consume) and destruction (unload)
  • Creation owner: ControlSceneUpdateLoopSystem.HandleNotCreatedScenes — this is the single writer that consumes an AssetPromise<ISceneFacade> and attaches the facade via World.Add(entity, scene). The guard is placed directly in this method
  • Destruction owner: UnloadSceneSystem.UnloadLoadedScene — this query requires ISceneFacade in its signature. A facade-less discarded entity will NOT match this query, so its unload follows the AbortLoadingScenes path (which only calls ForgetLoading on the consumed promise — no cache mutation). ✓
  • Conclusion: The guard is placed at the correct lifecycle moment (creation) and the unload path correctly bypasses cache mutations for facade-less entities.

Owner search for Fix B (realm serialization):

  • Lifecycle managed: realm entity creation/destruction
  • Owner: RealmController.SetRealmAsync — the public entry point that unloads the current realm and creates a new one. The semaphore guards this entry point directly. ✓
  • Callers: ChangeRealmTeleportOperation, Bootstraper, RealUserInAppInitializationFlow — overlapping calls from teleport + initialization flows are now correctly serialized.

The guard is NOT per-frame reconciliation. AnyParcelHasLiveScene runs only when an AssetPromise is successfully consumed (an explicit lifecycle moment), not on every Update tick. The query filter [None(typeof(ISceneFacade))] ensures it only runs for entities that haven't yet been processed.


STEP 4 — Member audit: PASS ✅

Member Visibility Consumers Verdict
AnyParcelHasLiveScene(IReadOnlyList<Vector2Int>) private 1 (HandleNotCreatedScenes) Loop extraction into named method — acceptable for readability. Not a derived predicate; it's a simple existence check against the cache.
SetRealmExclusiveAsync(URLDomain, CancellationToken) private 1 (SetRealmAsync) Standard semaphore-wrapper pattern: public method handles acquisition/release, private method holds the logic.
realmChangeSemaphore private readonly field SetRealmAsync only Application-lifetime scoped; no IDisposable on RealmController is consistent with existing design.

STEP 5 — Line-level review: PASS ✅

Pass A — Blocking-issue categories: No P0 or P1 issues found.

  1. Semaphore pattern (RealmController.cs:130-133): WaitAsync(ct) is correctly placed OUTSIDE the try block. If cancellation throws during wait, Release() is not called — this is correct because the semaphore was never acquired. Release in finally guarantees release after successful acquisition, even on exception.

  2. Parcel collision guard (ControlSceneUpdateLoopSystem.cs:84-89): After TryConsume succeeds, the guard checks AnyParcelHasLiveScene. If true, it disposes the facade and returns BEFORE World.Add(entity, scene) — the entity remains facade-less. This is verified correct: UnloadLoadedScene requires ISceneFacade in its query signature, so the facade-less entity takes the safe AbortLoadingScenes path.

  3. scene.DisposeAsync().Forget() (line 87): Fire-and-forget disposal of a never-started facade. This follows the established codebase pattern (cf. UnloadSceneSystem.UnloadLoadedPortableExperienceScene line 118, AbortSucceededScenesPromises line 137). Per CLAUDE.md §9, detached async is acceptable for non-essential cleanup, and the duplicate facade is never started.

  4. WaitForFixedScenePromisesAsync refactor (RealmController.cs:211-217): FixedScenePointers is confirmed as a struct. The old code captured it via out parameter in a closure (potentially stale copy). The new code uses inline out var in the lambda and re-reads with Get<> after WaitUntil — this is a correctness improvement. No practical TOCTOU risk: the semaphore serializes realm changes, and within Unity's single-threaded execution model, no system can mutate the component between WaitUntil returning and the Get<> call.

  5. AnyParcelHasLiveScene (lines 96-103): Allocation-free for-loop with dictionary lookup — compliant with CLAUDE.md §4 (no LINQ, allocation-free in Update path).

  6. ChangeSceneFPSChangeSceneFps rename: Follows .NET naming guidelines for 3+ character acronyms. ✓

  7. Parameter rename realmEntitytargetRealmEntity (lines 324, 329): Avoids shadowing the instance field realmEntity. ✓

Pass B — Design, encapsulation & resource smells: No issues found. No new persistent state, no construction smells, no naming issues, no magic values, no encapsulation leaks.

Teardown trace: The DCLSemaphoreSlim is application-lifetime scoped (same as RealmController). The disposed facade's DisposeAsync() handles its own internal cleanup. No new subscriptions, event hookups, or resource acquisitions are introduced.


Security Review: PASS ✅

  • No secrets or credentials in the diff
  • No injection vulnerabilities
  • No auth/authz changes
  • Semaphore cannot deadlock: CancellationToken propagation removes cancelled waiters; finally guarantees release
  • No external input can cause semaphore starvation

Tests: Well-structured ✅

Two new tests in ControlSceneUpdateLoopSystemShould:

  1. DiscardDuplicateSceneForSameParcelsKeepingLiveFacade — Verifies: duplicate facade disposed exactly once, never started, exactly one facade attached to entity, cache holds kept facade, no error log. Correctly handles non-deterministic entity consumption order via firstEntityKept check.

  2. KeepLiveSceneCacheMappingWhenDuplicateEntityUnloads — Verifies the cascade guard: after discarding a duplicate, adding DeleteEntityIntention and running UnloadSceneSystem does NOT remove the live scene's cache mappings.

Both tests use real ScenesCache (not mock) for integration coverage, NSubstitute for facades, and follow AAA pattern. ✓


STEP 8 — Non-blocking warnings

None. Main.unity is not modified.


REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches ECS scene lifecycle systems, async/UniTask patterns (DCLSemaphoreSlim, .Forget()), entity structural changes (World.Add), and the scene cache — core scene-loading infrastructure.
QA_REQUIRED: YES


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

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Jarvis reviewed this PR and found no blocking issues, but assessed it as complex — human DEV review is still required before merging.

@github-actions

github-actions Bot commented Aug 19, 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 c6c7c72
Logs https://github.com/decentraland/unity-explorer/actions/runs/32252172441
Download Windows https://github.com/decentraland/unity-explorer/suites/87428427631/artifacts/9376658094
Download Windows S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/bugsweep/scenescache-duplicate-parcel-add/pr-25321-c6c7c72/Decentraland_windows64.zip
Download Mac https://github.com/decentraland/unity-explorer/suites/87428427631/artifacts/
Download Mac S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/bugsweep/scenescache-duplicate-parcel-add/pr-25321-c6c7c72/Decentraland_macos.zip
Built on 2026-08-19T17:46:20Z

Lint

Warnings not reduced: 12724 => 13120 — remove at least 397 warnings to merge.

No warnings in files changed by this PR — showing general ones you can remove to unblock (50 of 13120)
Assets/DCL/AvatarRendering/AvatarShape/Tests/PerformanceTests/AvatarOutlineFrustumHoistPerformanceTest.cs:126  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/AvatarRendering/AvatarShape/Tests/PlayMode/BoneMatrixCalculationJobPerformanceTest.cs:58  ArrangeRedundantParentheses  Redundant parentheses
Assets/DCL/Rendering/GPUInstanceBatcher/ComputeShaders/DrawArgsInstanceCountTransfer.compute:1  CppUnusedIncludeDirective  Possibly unused #include directive
Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/ClientWebSocketApiImplementation.cs:275  RedundantArgumentDefaultValue  The parameter 'initialCount' has the same default value
Assets/DCL/VoiceChat/Microphone/MicrophoneTrackPublisher.cs:43  RedundantArgumentDefaultValue  The parameter 'initialCount' has the same default value
Assets/DCL/Web3/Authenticators/Implementations/ThirdWeb/ThirdWebLoginService.cs:29  RedundantArgumentDefaultValue  The parameter 'initialCount' has the same default value
Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/ClientWebSocketApiImplementation.cs:275  RedundantArgumentDefaultValue  The parameter 'maxCount' has the same default value
Assets/DCL/VoiceChat/Microphone/MicrophoneTrackPublisher.cs:43  RedundantArgumentDefaultValue  The parameter 'maxCount' has the same default value
Assets/DCL/Web3/Authenticators/Implementations/ThirdWeb/ThirdWebLoginService.cs:29  RedundantArgumentDefaultValue  The parameter 'maxCount' has the same default value
Assets/DCL/PluginSystem/Global/DefaultTexturesContainer.cs:75  RedundantArgumentDefaultValue  The parameter 'optionValue' has the same default value
Assets/DCL/PluginSystem/Global/DefaultTexturesContainer.cs:76  RedundantArgumentDefaultValue  The parameter 'optionValue' has the same default value
Assets/DCL/Communities/CommunitiesCard/Events/EventListController.cs:156  RedundantArgumentDefaultValue  The parameter 'parcelToTeleport' has the same default value
Assets/DCL/Events/EventCardActionsController.cs:93  RedundantArgumentDefaultValue  The parameter 'parcelToTeleport' has the same default value
Assets/DCL/Friends/UI/FriendPanel/Sections/Friends/FriendListSectionUtilities.cs:60  RedundantArgumentDefaultValue  The parameter 'parcelToTeleport' has the same default value
Assets/DCL/Places/PlacesCardSocialActionsController.cs:183  RedundantArgumentDefaultValue  The parameter 'parcelToTeleport' has the same default value
Assets/DCL/Tests/PlayMode/PerformanceTests/EventsStateServiceLookupPerformanceTest.cs:140  RedundantArgumentDefaultValue  The parameter 'unit' has the same default value
Assets/DCL/Tests/PlayMode/PerformanceTests/EventsStateServiceLookupPerformanceTest.cs:141  RedundantArgumentDefaultValue  The parameter 'unit' has the same default value
Assets/DCL/Tests/PlayMode/PerformanceTests/PlacesStateServiceLookupPerformanceTest.cs:161  RedundantArgumentDefaultValue  The parameter 'unit' has the same default value
Assets/DCL/Tests/PlayMode/PerformanceTests/PlacesStateServiceLookupPerformanceTest.cs:162  RedundantArgumentDefaultValue  The parameter 'unit' has the same default value
Assets/DCL/Chat/_Refactor/ChatReactions/Tests/LocalPlayerWorldReactorShould.cs:141  RedundantArgumentDefaultValue  The parameter 'walletId' has the same default value
Assets/DCL/Chat/_Refactor/ChatReactions/Tests/LocalPlayerWorldReactorShould.cs:156  RedundantArgumentDefaultValue  The parameter 'walletId' has the same default value
Assets/DCL/Chat/_Refactor/ChatReactions/Tests/LocalPlayerWorldReactorShould.cs:171  RedundantArgumentDefaultValue  The parameter 'walletId' has the same default value
Assets/DCL/MapRenderer/MapLayers/HomeMarker/HomeMarkerController.cs:135  RedundantArgumentDefaultValue  The parameter 'worldName' has the same default value
Assets/DCL/Infrastructure/ECS/Unity/Materials/Tests/CreateBasicMaterialSystemShould.cs:76  RedundantAssignment  The value passed to the method is never used because it is overwritten in the method body before being read
Assets/DCL/Infrastructure/ECS/Unity/Materials/Tests/CreatePBRMaterialSystemShould.cs:85  RedundantAssignment  The value passed to the method is never used because it is overwritten in the method body before being read
Assets/DCL/RealmNavigation/RetrieveSceneFromFixedRealm.cs:46  RedundantAssignment  The value passed to the method is never used because it is overwritten in the method body before being read
Assets/DCL/AvatarRendering/Emotes/Editor/EmbeddedEmotesEditor.cs:37  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/AvatarRendering/Emotes/Editor/EmbeddedEmotesEditor.cs:38  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Backpack/AvatarSection/Outfits/OutfitsPresenter.cs:193  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Chat/History/ChatHistoryEncryptor.cs:25  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/Tests/CommunicationControllerAPIImplementationShould.cs:177  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/Tests/CommunicationControllerAPIImplementationShould.cs:257  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/ECS/Unity/Materials/Systems/StartMaterialsLoadingSystem.cs:167  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/ECS/Unity/StreamableLoading/GLTF/DownloadProvider/GltFastDownloadProviderBase.cs:67  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Global/Editor/DebugSettingsDrawer.cs:73  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Global/Editor/RealmLaunchSettingsDrawer.cs:248  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:64  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:100  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:136  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:172  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/BoxFactory.cs:219  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Infrastructure/Utility/Primitives/CylinderVariantsFactory.cs:137  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Landscape/Jobs/NoiseJob.cs:68  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/PerformanceAndDiagnostics/Diagnostics/ReportsHandling/Sentry/DclAnrIntegration.cs:411  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/PluginSystem/Global/VoiceChatDebugContainer.cs:164  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/SDKComponents/AudioAnalysis/AudioAnalysisSystem.cs:78  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/SDKComponents/AvatarAttach/Systems/AvatarAttachHandlerSystem.cs:87  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Translation/Processors/AngleBracketSegmentationRule.cs:29  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Translation/Processors/AngleBracketSegmentationRule.cs:38  RedundantAssignment  Value assigned is not used in any execution path
Assets/DCL/Multiplayer/Connections/Archipelago/Rooms/ArchipelagoIslandRoom.cs:52  RedundantBaseConstructorCall  Redundant base constructor call

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 25056 0 13
PlayMode ✅ Passed 236 0 37

@decentraland-bot

Copy link
Copy Markdown
Contributor

PR #9807, run #32283579001

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) 2364 (×3)
CPU average 38.6 ms (33.4–38.8) 37.8 ms (37.7–38.0) -0.8 ms ⚪ within noise
CPU 1% worst 322.6 ms (57.0–343.5) 295.2 ms (289.7–309.3) -27.3 ms ⚪ within noise
CPU 0.1% worst 344.1 ms (341.1–360.3) 309.4 ms (303.2–350.9) -34.7 ms ⚪ within noise
GPU average 9.5 ms (9.2–9.6) 9.6 ms (9.4–9.6) 0.0 ms ⚪ within noise
GPU 1% worst 35.6 ms (23.5–37.7) 31.9 ms (31.5–33.9) -3.7 ms ⚪ within noise
GPU 0.1% worst 44.4 ms (39.8–45.0) 38.0 ms (37.7–38.9) -6.4 ms 🟢 14% faster
Exceptions per run 66 66 0 ⚪ none new
Exception breakdown
Exception Baseline (3 runs) Change (3 runs)
[UI] DllNotFoundException 192 192
[ENGINE] NullReferenceException 3 3
[ENGINE] ObjectDisposedException 3 3

Apple M1

Metric Baseline Change Δ Result
Samples 4105 (×3) 3961 (×3)
CPU average 21.8 ms (21.8–22.9) 22.6 ms (22.1–23.6) 0.8 ms ⚪ within noise
CPU 1% worst 215.9 ms (215.7–217.7) 229.0 ms (229.0–231.3) 13.1 ms 🔴 6% slower
CPU 0.1% worst 226.3 ms (222.9–228.8) 235.2 ms (231.1–243.4) 8.9 ms ⚪ within noise
GPU average 2.5 ms (2.0–3.2) 2.4 ms (2.4–4.1) -0.0 ms ⚪ within noise
GPU 1% worst 34.3 ms (34.2–36.2) 35.4 ms (34.5–37.6) 1.1 ms ⚪ within noise
GPU 0.1% worst 36.3 ms (34.8–37.5) 37.0 ms (37.0–40.8) 0.7 ms ⚪ within noise
Exceptions per run 0 0 0 ⚪ none new

lorenzo-ranciaffi added a commit that referenced this pull request Aug 21, 2026
…rialize realm changes (#9807)

Squashed changes from PR #9807.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alejandro-jimenez-dcl

Copy link
Copy Markdown
Contributor Author

Superseded by #9828, which compounds this fix together with the rest of the bugsweep batch. Closing in favor of that combined PR.

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.

4 participants