feat: open Explorer UI from SDK scene - #9472
Conversation
SDK7 scenes open a native Explorer fullscreen panel via ~system/RestrictedActions and receive a typed OpenExplorerUiResult verdict (Opened / RejectedNotCurrentScene / RejectedAlreadyOpen / RejectedFeatureDisabled / RejectedNoUserGesture). - Protocol bindings regenerated (OpenExplorerUi RPC, ExplorerUi & OpenExplorerUiResult enums); also picks up experimental's additive AvatarEmoteCommand.Mask field. - Impl gates: current-scene, feature-flag, user-gesture window, already-open. - Gesture signal: LastUserInputTick on ISceneStateProvider, stamped by WritePointerEventResultsSystem, consumed by TryOpenExplorerUi. - IExplorerUiActions returns OpenSectionResult and tracks panel open-state via MVC events; disposal wired through the restricted-actions API. - Host RestrictedActions.js exposes the runtime enums; tests cover the gesture and already-open rejections. CONTEXT.md documents the open-verdict domain language. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Windows and Mac build successful in Unity Cloud! You can find a link to the downloadable artifact below. |
SDK7 scenes open a native Explorer fullscreen panel via ~system/RestrictedActions and receive a typed OpenExplorerUiResult verdict (Opened / RejectedNotCurrentScene / RejectedAlreadyOpen / RejectedFeatureDisabled / RejectedNoUserGesture). - Protocol bindings regenerated (OpenExplorerUi RPC, ExplorerUi & OpenExplorerUiResult enums); also picks up experimental's additive AvatarEmoteCommand.Mask field. - Impl gates: current-scene, feature-flag, user-gesture window, already-open. - Gesture signal: LastUserInputTick on ISceneStateProvider, stamped by WritePointerEventResultsSystem, consumed by TryOpenExplorerUi. - IExplorerUiActions returns OpenSectionResult and tracks panel open-state via MVC events; disposal wired through the restricted-actions API. - Host RestrictedActions.js exposes the runtime enums; tests cover the gesture and already-open rejections. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…' into feat/scene-triggered-explorer-ui
Regenerate RestrictedActions.gen.cs from protocol commit 0e7de00: ExplorerUi loses EU_UNSPECIFIED and renumbers contiguously with EU_SETTINGS as the zero value, and OpenExplorerUiResult renames REJECTED_ALREADY_OPEN to WAS_ALREADY_OPEN (value 2, no longer a rejection; rejected values shift to 3-5). Mirror both enums in the host JS module. Delete the OpenSectionResult enum and return the protobuf OpenExplorerUiResult from IExplorerUiActions.OpenSection, collapsing the TryOpenExplorerUi tail to a direct cast of the OpenSection verdict. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FeatureId.COMMUNITIES is never populated in FeaturesRegistry (its state is identity-dependent), so the synchronous registry gate in TryOpenExplorerUi rejected the section unconditionally, while the sidebar and ExplorePanel opened it fine via CommunitiesFeatureAccess. Move the Communities gate into ExplorerUiActions, backed by a new synchronous CommunitiesFeatureAccess.IsUserAllowedCached() snapshot of the async allowlist check (warmed at login; true in the editor, mirroring the async path). CAMERA_REEL and DISCOVER stay on the registry gate, which is populated correctly for them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
decentraland-bot
left a comment
There was a problem hiding this comment.
STEP 2 — Root-cause check
Problem: SDK7 scenes have no way to open native Explorer panels (Settings, Map, Backpack, etc.) from user gestures.
Fix vs symptom: The diff adds a new openExplorerUi restricted action — this is the right approach. It extends the existing restricted-actions infrastructure with proper gating (current-scene, gesture window, feature flags, communities identity check). This addresses the cause directly.
Verdict: PASS ✅
STEP 3 — Design & integration
Owner search
New unit: ExplorerUiActions — manages whether the explore panel is open (via MVC events) and opens sections.
- Entity/resource managed: The explore panel's open/close state and the act of opening a section.
- Existing owners searched:
ExplorePanelController— the MVC controller that owns the panel lifecycle. Lives inDCL.Social.SidebarController— opens explore panel sections from the sidebar. Lives inDCL.Social.ExplorePanelPlugin— constructs theExplorePanelControllerand wires its dependencies.
- Can the logic live at those points? No. The restricted-actions API lives in
SceneRuntime, which cannot referenceDCL.Social(the dependency runs the other way). The asmref that compilesExplorerUiActionsintoDCL.Socialwhile keeping the file in theRestrictedActions/folder is the correct cross-assembly seam.
Teardown trace
| Opener | Mirror | Location |
|---|---|---|
mvcManager.OnViewShowed += OnViewShowed |
mvcManager.OnViewShowed -= OnViewShowed |
ExplorerUiActions ctor → Dispose() |
mvcManager.OnViewClosed += OnViewClosed |
mvcManager.OnViewClosed -= OnViewClosed |
ExplorerUiActions ctor → Dispose() |
Disposal chain: SceneRuntimeImpl.Dispose() → RestrictedActionsAPIWrapper.Dispose() (line 118) → RestrictedActionsAPIImplementation.Dispose() (line 242) → explorerUiActions.Dispose() (line 244) → unsubscribes MVC events. ✅
Verdict: PASS ✅
STEP 4 — Member audit
| New member | Consumers | Assessment |
|---|---|---|
CommunitiesFeatureAccess.IsUserAllowedCached() |
ExplorerUiActions.OpenSection (1) |
Legitimate accessor centralizing storedResult with editor override. Not a single-use derivation. |
ISceneStateProvider.LastUserInputTick |
WritePointerEventResultsSystem (writer), RestrictedActionsAPIImplementation.TryOpenExplorerUi (reader) |
Simple stamp property, 2 consumers. |
IExplorerUiActions.OpenSection() |
RestrictedActionsAPIImplementation.TryOpenExplorerUi (1) |
Interface boundary for cross-assembly seam. Mocked in tests. Justified. |
IRestrictedActionsAPI.TryOpenExplorerUi() |
RestrictedActionsAPIWrapper.OpenExplorerUi (1) |
Standard interface method following existing restricted-actions pattern. |
STEP 5 — Line-level review
3 findings (all P2). See inline comments.
Additional P2 — Unused constructor parameter (not on a changed line):
ExplorePanelController.cs:89 — The creditsPanelController field was removed but the constructor still accepts the parameter. It is no longer stored or used. Consider removing the parameter from the signature and its call site in ExplorePanelPlugin.cs (~line 620).
STEP 6 — Complexity assessment
COMPLEX — Adds a new restricted action with protocol bindings, cross-assembly asmref, MVC event subscriptions, gesture-window logic, and async patterns (UniTask + .Forget()).
STEP 7 — QA assessment
QA_REQUIRED: YES — Modifies runtime code that affects user-facing behavior: opening fullscreen panels from scene interactions, gesture-based gating, and feature-flag rejection.
STEP 8 — Non-blocking warnings
ExplorePanelController (creditsPanelController field, communitiesBrowserController private property, CanBeClosedByEscape override). The CanBeClosedByEscape removal is a behavioral change: previously, the panel could not be closed by Escape during the ViewShowing transition state; now the IController default applies (always closable for FULLSCREEN layer). This is likely fine (other fullscreen panels use the default), but confirm this is intentional — TC4 should validate no regression.
STEP 9 — Verdict
REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: New restricted action with protocol bindings, cross-assembly asmref, MVC event subscriptions, gesture-window logic, and async patterns.
QA_REQUIRED: YES
Security review
No security issues found. The gesture gate prevents scenes from opening panels without user interaction (1-tick window, underflow-safe). LastUserInputTick is only written by internal C# systems — scene JS cannot spoof it. The TryMapExplorerUi switch rejects unknown enum values. IsUserAllowedCached() defaults to false (fail-closed). uint overflow wraps to 0, which also fails closed.
Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub
|
Warnings count reduced: 14087 => 14078 Warnings/errors in files changed by this PR (91) |
|
Tests: 24410 passed, 0 failed ✅ |
|
PR reviewed and approved by QA on both platforms following the PR test instructions. ✅ Build: Test results:
Unrelated errors noted (do not affect verdict):
Verdict: PASS ✅ All 7 test cases (TC1–TC7) pass on both Windows and Mac, across standard, fresh, flag-disabled, and gesture-edge-case runs, with no regressions or related errors found. Full click→verdict chain traced end-to-end from scene console + Player.log evidence. Scene console + Player.logs for windows and mac TC 1 24.07.2026_15.10.14_REC.9472.new.user.mp424.07.2026_15.03.48_REC.9472.old.user.mp4TC 3 24.07.2026_15.51.15_REC.TC.3-1.mp424.07.2026_15.31.06_REC.TC3.step.1.Mac.camera.reel.mp4TC 4 24.07.2026_15.53.10_REC.TC.4.Mac.OK.mp4TC 5-6-7 24.07.2026_15.56.19_REC.TC.5.mp424.07.2026_15.58.05_REC.TC.6.mp4 |
…xplorer-ui # Conflicts: # Explorer/Assets/DCL/ExplorePanel/ExplorePanelController.cs
dalkia
left a comment
There was a problem hiding this comment.
Approved! Some minor comments for consideration
Review follow-up: IsUserAllowedCached() stayed false until another feature flow had run the allowlist check at least once (e.g. the user opening Communities from the sidebar), so openExplorerUi(COMMUNITIES) was rejected for allowed users on a fresh session. - CommunitiesFeatureAccess resolves the allowlist-aware check in the background from its constructor, so the cached verdict no longer depends on another flow having run the check first; the cache still resets on identity change - split the two predicates crammed into IsUserAllowedToUseTheFeatureAsync(ct, ignoreAllowedList, cacheResult): a sync flag-only IsFeatureEnabled() for pre-login bootstrap paths and the allowlist-aware IsUserAllowedToUseTheFeatureAsync(ct) that always caches - flag-only callers can no longer wipe or poison the cache - CommunitiesContainer.CreateAsync became sync Create (its only await is gone) - consolidated the triplicated editor force-enable hack into a single ForceEnabledInEditor property (one #if, aggressively inlined) - removed dead storedMembersCounterResult/storedAnnouncementsResult fields and the unused UnityEngine using Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pravusjif
left a comment
There was a problem hiding this comment.
Looks good! just make sure to update the protocol befor merging (needs protocol change in main -> synched into experimental).
And ideally it would be good to have a test scene for this feature at sdk7testscenes, can you create a new one there with the same test code you have in flutterecho.dcl.eth? so that QA can still test the feature there while doing the RC candidate testing and you can keep re-deploying on your world safely.
…iResult) (#451) ## What Adds an `OpenExplorerUi` RPC to the existing `RestrictedActionsService` so SDK7 scenes can open native explorer UI panels (Map, Settings, Backpack, Camera Reel, Communities, Places, Events) through `~system/RestrictedActions`. - New enum `ExplorerUi` — 7 fullscreen sections; `EU_SETTINGS` holds the zero value so an unset `ui` field defaults to the least-intrusive panel. - New enum `OpenExplorerUiResult` — the open verdict: `UNSPECIFIED`, `OPENED`, `WAS_ALREADY_OPEN`, `REJECTED_NOT_CURRENT_SCENE`, `REJECTED_FEATURE_DISABLED`, `REJECTED_NO_USER_GESTURE`. - New messages `OpenExplorerUiRequest { ExplorerUi ui }` / `OpenExplorerUiResponse { OpenExplorerUiResult open_result }` (dedicated response, not the shared `SuccessResponse`; the request documents a `oneof` extension point for future per-panel params). - `rpc OpenExplorerUi(OpenExplorerUiRequest) returns (OpenExplorerUiResponse)` on `RestrictedActionsService`. Additive and non-breaking. Only `proto/decentraland/kernel/apis/restricted_actions.proto` changed. ## Context Iteration 1 of the Scene-triggered Explorer UI feature. This is the **review PR into `main`** per the protocol contribution flow; #446 carries the exact same change on `experimental` for Unity testing and will be closed once the main→experimental sync lands. ## Testing Consumed downstream by js-sdk-toolchain (decentraland/js-sdk-toolchain#1485, `~system/RestrictedActions` codegen) and unity-explorer (decentraland/unity-explorer#9472, C# implementation with QA test scene) on matching `feat/scene-triggered-explorer-ui` branches.
## What Bumps `@dcl/protocol` to the **main** build `1.0.0-30373157983.commit-2c475cb` ([decentraland/protocol#451](decentraland/protocol#451), merged) so `~system/RestrictedActions` regenerates with: - `openExplorerUi(body: OpenExplorerUiRequest): Promise<OpenExplorerUiResponse>` - `ExplorerUi` enum (7 sections: `EU_MAP`, `EU_SETTINGS`, `EU_BACKPACK`, `EU_CAMERA_REEL`, `EU_COMMUNITIES`, `EU_PLACES`, `EU_EVENTS`) - `OpenExplorerUiResult` enum + `OpenExplorerUiResponse` Main-based counterpart of #1485 (experimental, pinned a protocol branch build): once this lands and main syncs into experimental, #1485 will be closed. ## Why Lets SDK7 scenes open native Explorer fullscreen panels from a user gesture and receive a typed verdict. Client implementation: [decentraland/unity-explorer#9472](decentraland/unity-explorer#9472). Test world: `flutterecho.dcl.eth` + `sdk7-test-scenes` parcel `80,-5`.
decentraland-bot
left a comment
There was a problem hiding this comment.
Review: feat: open Explorer UI from SDK scene
STEP 1 — Context & Scope
This PR adds a new openExplorerUi restricted action that lets SDK7 scenes request native Explorer panels from a user gesture. It touches:
- Protocol bindings (
RestrictedActions.gen.cs,AvatarEmoteCommand.gen.cs) — generated code, not reviewed for style. - Scene runtime API (
IRestrictedActionsAPI,RestrictedActionsAPIWrapper,RestrictedActions.js). - New
IExplorerUiActions/ExplorerUiActionsbridge. CommunitiesFeatureAccessrefactoring (sync methods, background warm-up).- Gesture stamping in
WritePointerEventResultsSystem. ExplorePanelControllerdead-code cleanup.- Test coverage for both
CommunitiesFeatureAccessandRestrictedActionsAPIImplementation.
Subsystem docs loaded: CLAUDE.md.
STEP 2 — Root-cause check
Problem: SDK7 scenes had no way to open native Explorer panels. Additionally, Communities access was gated through FeaturesRegistry where FeatureId.COMMUNITIES is never populated, causing false rejections.
Fix: A new TryOpenExplorerUi method in RestrictedActionsAPIImplementation with a proper gate chain. Communities is now gated through CommunitiesFeatureAccess (identity-dependent flag + wallets allowlist), matching what the sidebar uses.
Verdict: ✅ This addresses the root cause, not a symptom.
STEP 3 — Design & integration
Owner search — ExplorerUiActions:
- Entity managed: Explore panel open/close state for the SDK bridge.
- Existing owners searched:
ExplorePanelControllerowns the panel lifecycle;MVCManagerowns show/close;SceneInstanceDepsowns per-scene runtime dependencies. ExplorerUiActionsbridges two assembly layers: the restricted-actions API (SceneRuntime) needs to openExplorePanelController(DCL.Social). The asmref (RestrictedActions.ExplorerUi.asmref→ DCL.Social) is the established pattern for cross-assembly bridges in this codebase. The interfaceIExplorerUiActionsstays in SceneRuntime and is mocked in tests — it's a genuine test seam, not a single-impl anti-pattern.- The class does NOT duplicate an existing lifecycle: no existing code provides "open a specific explore-panel section and return a typed verdict" from the scene runtime layer.
Owner search — LastUserInputTick:
- Stamped by
WritePointerEventResultsSystem(the existing system that processes pointer events for the scene). This is the natural owner — the tick stamp is a one-uint-write side-effect of the existing non-hover filter. No new system introduced. - Read by
TryOpenExplorerUiinRestrictedActionsAPIImplementation— the consumer is in the right place (the restricted-actions gate chain).
Teardown / subscription trace:
- ✅
ExplorerUiActionssubscribesmvcManager.OnViewShowed += OnViewShowedandmvcManager.OnViewClosed += OnViewClosed— both unsubscribed inDispose()(lines 33–34). - ✅ Disposal chain verified:
RestrictedActionsAPIWrapper.Dispose()→api.Dispose()→RestrictedActionsAPIImplementation.Dispose()→explorerUiActions.Dispose(). - ✅
CommunitiesFeatureAccess.OnIdentityChangedsubscription (pre-existing, not introduced by this PR) —[Singleton]lifetime, acceptable.
CommunitiesFeatureAccess.CreateAsync → Create: The async path was awaiting IsUserAllowedToUseTheFeatureAsync which blocks container creation until the identity is available. The new design fires the allowlist check in the background via .SuppressToResultAsync().Forget() and provides synchronous access via IsFeatureEnabled() and IsUserAllowedCached(). This is correct — the container no longer blocks startup, and the cached result is available before any scene can call the restricted action (the warm-up runs in the constructor, well before scene load).
Verdict: ✅ PASS — no duplicate lifecycle, no per-frame reconciliation, proper teardown.
STEP 4 — Member audit
| Member | Consumers | Verdict |
|---|---|---|
IExplorerUiActions.OpenSection |
RestrictedActionsAPIImplementation.TryOpenExplorerUi, test mock |
✅ 2 consumers (impl + mock), justified interface |
CommunitiesFeatureAccess.IsFeatureEnabled() |
CommunitiesContainer.Create, ExplorePanelPlugin.InitializeAsync |
✅ 2 consumers — flag-only sync check for bootstrap paths |
CommunitiesFeatureAccess.IsUserAllowedCached() |
ExplorerUiActions.OpenSection, tests |
✅ Sync counterpart of async check, returns cached result or safe default (false) |
ISceneStateProvider.LastUserInputTick |
WritePointerEventResultsSystem (write), RestrictedActionsAPIImplementation.TryOpenExplorerUi (read), SceneStateProvider.Start (reset) |
✅ Clear write/read/reset lifecycle |
ExplorerUiActions.isExplorePanelOpen |
OpenSection (read), OnViewShowed/OnViewClosed (write) |
✅ Private field, 3 internal consumers |
RestrictedActionsAPIImplementation.TryMapExplorerUi |
TryOpenExplorerUi |
✅ Private static, 1 consumer — extracted for readability |
No single-use derived predicates, no absent-≠-false conflation, no re-derivation of existing properties.
STEP 5 — Line-level review
Pass A — Blocking issues: None found.
Pass B — Design/encapsulation smells: None found.
Security review: Completed via sub-agent. No vulnerabilities. The gate chain (current-scene → gesture-window → enum-validation → feature-flag → communities-allowlist → already-open) is defense-in-depth. The JS bridge passes only an int in and returns only an int — no injection surface. IsUserAllowedCached() defaults to false (deny) when the warm-up hasn't resolved — safe by default.
STEP 6 — Complexity
COMPLEX — touches restricted actions API, scene runtime, gesture stamping in an ECS system, feature flags, MVC integration, assembly definitions, protocol bindings, and the Communities feature-access singleton.
STEP 7 — QA assessment
QA_REQUIRED: YES — runtime code changes affecting user-facing UI opening behavior; new SDK action that scenes can invoke; comprehensive test plan provided with world flutterecho.dcl.eth and 7 test cases.
STEP 8 — Non-blocking warnings
None. Main.unity not modified.
STEP 9 — Verdict
REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches restricted-actions API, scene runtime, gesture stamping in an ECS system, MVC integration, feature flags, assembly definitions, and protocol bindings.
QA_REQUIRED: YES
Reviewed by Jarvis 🤖 · Requested by popuz via GitHub
SDK7 scenes open a native Explorer fullscreen panel via
~system/RestrictedActionsand receive a typedOpenExplorerUiResultverdict (OPENED / WAS_ALREADY_OPEN / REJECTED_NOT_CURRENT_SCENE / REJECTED_FEATURE_DISABLED / REJECTED_NO_USER_GESTURE).OpenExplorerUiRPC,ExplorerUi&OpenExplorerUiResultenums); also picks up experimental's additiveAvatarEmoteCommand.Maskfield.LastUserInputTickonISceneStateProvider, stamped byWritePointerEventResultsSystem, consumed byTryOpenExplorerUi.IExplorerUiActionsreturns the open verdict and tracks panel open-state via MVC events; disposal wired through the restricted-actions API.CommunitiesFeatureAccess(identity-dependent flag + wallets allowlist — the same source the sidebar uses), not throughFeaturesRegistry, whereFeatureId.COMMUNITIESis never populated.RestrictedActions.jsexposes the runtime enums; unit tests cover the gesture, already-open, unknown-value, feature-disabled and communities-rejection-propagation paths.Pull Request Description
What does this PR change?
Adds the
openExplorerUirestricted action so SDK7 scenes can open native Explorer panels from a user gesture:ExplorerUivalueEU_SETTINGS(0)EU_MAP(1)EU_BACKPACK(2)EU_CAMERA_REEL(3)camera-reelfeature flagEU_COMMUNITIES(4)EU_PLACES(5)discoverfeature flagEU_EVENTS(6)discoverfeature flagEvery call returns a verdict to the scene:
OPENED(1),WAS_ALREADY_OPEN(2),REJECTED_NOT_CURRENT_SCENE(3),REJECTED_FEATURE_DISABLED(4),REJECTED_NO_USER_GESTURE(5).Test Instructions
The world
flutterecho.dcl.ethhosts a test scene with two rows of labeled clickable cubes in front of spawn:openExplorerUifor that panel.DELAYED 5s(calls after a countdown, outside the user-gesture window),DOUBLE CALL(two calls from one click),INVALID (99)(unknownuivalue). On scene load the scene also fires one automaticON-START(MAP)call with no gesture at all.The player spawns right in front of the cube rows. Every verdict is shown in an on-screen HUD (top-right under the scene-name badge, newest first with a scene-side timestamp: green
OPENED, yellowWAS_ALREADY_OPEN, red rejections) — log tailing is optional. The scene console additionally logs each step with the[openExplorerUi]prefix and a timestamp:click <NAME>,request <NAME> (ui=N)andverdict <NAME> -> <RESULT> (<code>), so the click→verdict chain is fully traceable from Explorer logs.Steps (standard run):
(Alternatively join any realm and use the chat command
/goto flutterecho.dcl.eth.)Expected result: see the test cases below — every cube opens its panel with
OPENED (1)in the logs.Steps (fresh account):
Expected result: same as the standard run — in particular the COMMUNITIES cube must behave exactly like the sidebar does for that account.
Prerequisites
metaforge explorer run 9472)Test Steps
TC1 — happy path, all 7 panels
MAP).OPENED (1).TC2 — Communities parity (bug fixed in this PR)
COMMUNITIEScube.OPENED (1). Before the fix this always returnedREJECTED_FEATURE_DISABLED (4)even for allowed users.TC3 — feature-flag rejection
metaforge explorer run 9472 -- --realm flutterecho.dcl.eth --debug --camera-reel falseCAMERA_REELcube: no panel opens, log showsREJECTED_FEATURE_DISABLED (4).--debug --discover false: thePLACESandEVENTScubes are rejected the same way;MAP/SETTINGS/BACKPACKstill open.TC4 — manual UI regression
dev(the gating source did not change, only the restricted action now consults it).TC5 — user-gesture gate
ON-START(MAP) -> REJECTED_NO_USER_GESTURE (5)— a call with no gesture ever recorded is rejected.DELAYED 5scube (row 2) and don't click anything else during the countdown shown above it: no panel opens, the HUD showsDELAYED(MAP) -> REJECTED_NO_USER_GESTURE (5).OPENED— that is not a failure, just retry without clicking.)TC6 — rapid clicks / double call
DOUBLE CALLcube (firesopenExplorerUitwice from one click): exactly one Map panel opens —MVCManagerignores a show command for a controller that is already showing.DOUBLE#1(MAP) -> OPENED (1)and, forDOUBLE#2(MAP), eitherOPENED (1)orWAS_ALREADY_OPEN (2)depending on whether the panel registered as shown between the two calls — both are acceptable; the failure mode would be a second panel or a stuck UI.TC7 — unknown ui value
INVALID (99)cube: no panel opens, the HUD showsINVALID(99) -> REJECTED_FEATURE_DISABLED (4).Additional Testing Notes
REJECTED_NOT_CURRENT_SCENEis the only verdict not drivable in the world (a world confines the player to its own scene). It is unit-tested; in a Genesis City deployment of the same scene it can be reproduced by clickingDELAYED 5sand walking to a neighbouring parcel during the countdown.ExplorerUinumbering and open the wrong (off-by-one) panel. The scene deployed toflutterecho.dcl.ethis up to date; if cubes open mismatched panels, suspect a stale scene deployment, not the client.--scene-console).sdk7-test-scenesat parcel80,-5(scenes/80,-5-open-explorer-ui) for local-scene-development runs.Quality Checklist
flutterecho.dcl.ethworld +sdk7-test-scenes/scenes/80,-5-open-explorer-ui🤖 Generated with Claude Code