feat: scene/UI/avatar/app-UI inspection WebSocket server#2181
Merged
Conversation
Contributor
📦 Build Report🤖 Android
Build Status: ✅ Success 🍏 iOS
🔗 Workflow Run: View logs 🔄 Updated: 2026-05-29 16:31:00 UTC |
kuruk-mm
approved these changes
May 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A developer-only WebSocket inspection server that lets you query the live state of any DCL scene, the per-scene SDK UI tree, every tracked avatar (including yourself), and the Explorer's own UI tree — from
outside the client over a localhost WS. Off by default; toggled from Settings → Developer; bound to
127.0.0.1only.Pure GDScript on the wire (
TCPServer+WebSocketPeer.accept_stream, no multiplayer-peer framing), with thin#[func]hooks for the data only the Rust side can reach.Example response when asked to describe currently loaded scene:

Closes #2182
Why
Most "why is this rendering wrong / why is this entity missing / why is the wrong avatar emote playing?" sessions are bottlenecked on getting a clear read of the live state. The existing scene inspector
covers CRDT but not the rendered Godot side;
print_tree()covers Godot but not the SDK side; nothing covers avatars or app UI. This unifies them with one cmd-style protocol per tree.Five trees, one protocol
scene/entity(scene_id, entity_id)DclSceneNode→DclNodeEntity3d(type-checked)ui_scene/ui_entity(scene_id, entity_id)debug_get_entity_ui_control_id→UiNode.base_controlavatars/avatarAvatarSceneby∈ {address,alias,entity,local}SceneManager.player_avatar_nodeapp_ui/root/explorer/UIor/root/Menu)All four data cmds (
scene,ui_scene,avatar,app_ui) accept a sharedfiltersdict:component: ["TextShape", "MeshRenderer", …]— OR-match on SDK component names; uses a cheap names-list path that doesn't decode proto payloadsproperty_is: {component, field, contains}— generic substring filter on any (SDK component, field) paircollect_nodes: {<child_node_name>: [<property>, …]}— per-child-node property dump viaObject.get(); works for any node classinclude_parents,include_children,limit,offset,depth,class_filter,name_contains— pagination and traversal knobsEvery Godot-side value runs through a
_variant_to_jsonhelper, soVector3/Color/AABB/Transform3D/ dictionaries / packed arrays all serialize cleanly.Cross-tree safety
Each lookup path is type-isolated:
ischecks; the resolved node can't accidentally be from another tree.app_uiis path-based and could in principle traverse into the scene UI subtree, so by default we skip<root>/SceneUIContainer/scenes_ui. Lift the skip withinclude_scene_ui: trueif you wanteverything.
Drive-by fix
GrowOnlySet::to_binary(lib/src/dcl/crdt/grow_only_set.rs:88) underflowed when probed past the end. Existing call sites inmessage.rsnever trigger it (they iterate0..elements_count), but the WSserver's component sweep does. Replaced the raw subtraction with
checked_subreturning the sameErr("Index out of range")other paths already return. No production behavior change.Files
godot/src/tool/debug_server/debug_ws_server.gd— WebSocket dispatchgodot/src/tool/debug_server/debug_collector.gd— collectors for all four treesgodot/project.godot—DebugWsautoload registrationgodot/src/ui/pages/settings/settings.{gd,tscn}— Developer-section togglelib/src/scene_runner/scene_manager.rs—debug_*#[func]s for CRDT and UI accesslib/src/avatars/avatar_scene.rs—debug_list_avatars+ by-address/alias/entity/local lookupslib/src/dcl/crdt/grow_only_set.rs— GOS underflow guardSecurity / shipping posture
127.0.0.1only — no remote attack surface.!Global.is_production()Developer-section guard._process(false)outside that window.How to test
Prereqs: a non-production build, Claude Code available in this repo.
This branch ships a Claude Code skill at
.claude/skills/debug-ws-inspector/that documents the WS protocol and bundles ascripts/debug-ws.shwrapper (websocat with the right buffer size baked in). The skill auto-triggers on debug-WS keywords (port 9230,DebugWs, websocat against the client), so you can drive the whole test plan by talking to Claude in plain English — it picks the right cmd, runs the wrapper, and interprets the reply.1. Toggle on
DebugWsServer: listening on ws://127.0.0.1:9230.2. Basic dispatch
3. 3D scene inspection
-99,103is a known good one).<id>and show their text." Verify the reply has matched entities with both SDK payload and a Godot dump.property_issubstring filter onTextShape.text— confirm the result set shrinks accordingly.4. UI inspection (
ui_scene/ui_entity)<id>'s UI." Verify thegodotblock reports renderedControlproperties (position,size,anchors,modulate).textnode properties — should return the liveDclUiTextchild'stext/horizontal_alignment.5. Avatar inspection
is_local: true(you) and at least one remote player.<address>playing?" — should resolve viaby: addressand returnAnimationPlayer.current_animation.by: localand returnAnimationTree.active.6. Explorer UI (
app_ui)resolved_fromis/root/Menu.resolved_fromshould switch to/root/explorer/UIand the per-scene SDK UI subtree should be pruned. Ask Claude to lift the prune withinclude_scene_ui: trueand confirm the subtree reappears.7. Error paths
9999), an invalidbyvalue,by: localwithoutvalue(should succeed forlocalspecifically), and an unknown address. The first, second, and fourth should come back as readableok:falseerrors.8. Production gate
cargo run -- export --target <platform> --release, or use the Production toggle in CI builds) and confirm the Developer section of Settings — including the Debug WS toggle — is not visible.9. Cleanup