Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions godot/src/decentraland_components/avatar/avatar.gd
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,13 @@ func async_play_emote(emote_urn: String):
await emote_controller.async_play_emote(emote_urn)


## Stop a looping emote on network request (rfc4 PlayerEmote.is_stopping /
## Pulse EmoteStopped). Called from Rust (AvatarScene::stop_emote).
func stop_emote_from_network():
if emote_controller:
emote_controller.stop_emote()


## Register scene emote content info for later retrieval.
## Called from Rust before async_play_emote for scene emotes.
func register_scene_emote_content(
Expand Down
27 changes: 27 additions & 0 deletions godot/src/deep_link_router.gd
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,33 @@ func process_deep_link(url: String) -> void:
print("[DEEPLINK] Found rust-log param: ", rust_log_value)
DclGlobal.set_rust_log_filter(rust_log_value)

# Pulse transport params. `pulse-server=<host:port>` joins a specific Pulse
# server (shareable — everyone opening the link lands on the same instance,
# also implies enabling); `pulse=true/false` toggles it with the configured
# endpoint. No-ops on builds without the use_pulse feature.
var pulse_server_value = Global.deep_link_obj.params.get("pulse-server", "")
if not pulse_server_value.is_empty():
print("[DEEPLINK] pulse-server=", pulse_server_value)
Global.comms.set_pulse_server(pulse_server_value)
var pulse_value = Global.deep_link_obj.params.get("pulse", "")
if not pulse_value.is_empty():
print("[DEEPLINK] pulse=", pulse_value)
Global.comms.set_pulse_enabled(pulse_value.to_lower() in ["true", "1", "yes"])
# `dual-channel=true/false` (default true): whether movement keeps going over
# LiveKit while Pulse is established. false = Pulse-only movement while up.
var dual_channel_value = Global.deep_link_obj.params.get("dual-channel", "")
if not dual_channel_value.is_empty():
print("[DEEPLINK] dual-channel=", dual_channel_value)
Global.comms.set_livekit_movement_dual_channel(
dual_channel_value.to_lower() in ["true", "1", "yes"]
)
# `livekit=false`: pulse-only mode — no LiveKit rooms at all (no chat/voice/
# scene messages). Dev/testing switch; `livekit=true` re-enables.
var livekit_value = Global.deep_link_obj.params.get("livekit", "")
if not livekit_value.is_empty():
print("[DEEPLINK] livekit=", livekit_value)
Global.comms.set_livekit_enabled(livekit_value.to_lower() in ["true", "1", "yes"])

Global._apply_optimized_content_base_url(Global.deep_link_obj)

# `skip-gltf` toggle has to be set BEFORE any scene's GLTF_CONTAINER
Expand Down
21 changes: 21 additions & 0 deletions godot/src/global.gd
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,27 @@ func _ready():

_apply_optimized_content_base_url(deep_link_obj)

# Pulse transport params (mirrors deep_link_router.process_deep_link — this
# fake-deeplink path doesn't route through it). pulse-server implies enabling.
var pulse_server_value = deep_link_obj.params.get("pulse-server", "")
if not pulse_server_value.is_empty():
print("[DEEPLINK] pulse-server=", pulse_server_value)
comms.set_pulse_server(pulse_server_value)
var pulse_value = deep_link_obj.params.get("pulse", "")
if not pulse_value.is_empty():
print("[DEEPLINK] pulse=", pulse_value)
comms.set_pulse_enabled(pulse_value.to_lower() in ["true", "1", "yes"])
var dual_channel_value = deep_link_obj.params.get("dual-channel", "")
if not dual_channel_value.is_empty():
print("[DEEPLINK] dual-channel=", dual_channel_value)
comms.set_livekit_movement_dual_channel(
dual_channel_value.to_lower() in ["true", "1", "yes"]
)
var livekit_value = deep_link_obj.params.get("livekit", "")
if not livekit_value.is_empty():
print("[DEEPLINK] livekit=", livekit_value)
comms.set_livekit_enabled(livekit_value.to_lower() in ["true", "1", "yes"])

print("[DEEPLINK] safemargindebug=", deep_link_obj.safe_margin_debug)
if deep_link_obj.safe_margin_debug:
set_safe_margin_debug_enable(true)
Expand Down
9 changes: 7 additions & 2 deletions godot/src/test/validate_all_scripts.gd
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ func find_all_scripts(path: String) -> Array:

while file_name != "":
if dir.current_is_dir():
if not file_name.begins_with("."):
scripts.append_array(find_all_scripts(path + "/" + file_name))
var sub_path = path + "/" + file_name
# Skip hidden dirs and embedded Godot projects (e.g. the Android build
# template's instrumented assets): their scripts resolve against their
# own project, not ours.
var is_nested_project = FileAccess.file_exists(sub_path + "/project.godot")
if not file_name.begins_with(".") and not is_nested_project:
scripts.append_array(find_all_scripts(sub_path))
elif file_name.ends_with(".gd"):
scripts.append(path + "/" + file_name)

Expand Down
2 changes: 1 addition & 1 deletion godot/src/ui/COMPONENT_AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ Layouts ship as scripts only (no `.tscn`); each is a reusable container/wrapper
| `debug_panel.tscn` | `components/organisms/debug_panel/` | `components/debug_panel/` | Dev debug panel |
| `network_inspector_ui.tscn` | `components/organisms/debug_panel/network_inspector/` | `components/debug_panel/network_inspector/` | Network-inspector UI |
| `request_entry.tscn` | `components/organisms/debug_panel/network_inspector/` | `components/debug_panel/network_inspector/` | Network-inspector row |
| `livekit_debug_panel.tscn` | `components/organisms/livekit_debug/` | `components/livekit_debug/` | LiveKit voice debug panel |
| `multiplayer_debug_panel.tscn` | `components/organisms/multiplayer_debug/` | `components/multiplayer_debug/` | Multiplayer comms debug panel |

## Duplication / unification candidates

Expand Down

This file was deleted.

This file was deleted.

6 changes: 3 additions & 3 deletions godot/src/ui/components/organisms/menu/menu.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ signal toggle_fps
signal toggle_ram
signal request_pause_scenes(enabled: bool)
signal request_debug_panel(enabled: bool)
signal request_livekit_debug(enabled: bool)
signal request_multiplayer_debug(enabled: bool)
#signals from advanced settings

var is_in_game: bool = false # when it is playing in the 3D Game or not
Expand Down Expand Up @@ -199,8 +199,8 @@ func async_show_settings():
control_settings.instance.request_debug_panel.connect(
func(enabled): request_debug_panel.emit(enabled)
)
control_settings.instance.request_livekit_debug.connect(
func(enabled): request_livekit_debug.emit(enabled)
control_settings.instance.request_multiplayer_debug.connect(
func(enabled): request_multiplayer_debug.emit(enabled)
)

select_settings_screen()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
extends PanelContainer

const MAX_PEER_ROWS := 10
const TOKEN_SUMMARY_CHARS := 16

var collapsed := false

@onready var rich_text_label: RichTextLabel = %RichTextLabel
@onready var collapse_button: Button = %CollapseButton
@onready var timer: Timer = %Timer


func _ready():
timer.timeout.connect(_on_timer_timeout)
collapse_button.pressed.connect(_on_collapse_button_pressed)
timer.start()
_on_timer_timeout()


func _on_collapse_button_pressed():
collapsed = not collapsed
rich_text_label.visible = not collapsed
collapse_button.text = "▸" if collapsed else "▾"
if not collapsed:
_on_timer_timeout()


func _state_color(state: String) -> String:
match state:
"connected", "established":
return "green"
"disconnected", "dead", "none":
return "red"
"off", "unavailable", "disabled_for_session", "unknown":
return "gray"
_:
# connecting / reconnecting / identifying / signing / idle / ...
return "yellow"


func _colored_state(state: String) -> String:
return "[color=%s]%s[/color]" % [_state_color(state), state]


func _short_address(address: String) -> String:
if address.length() <= 12:
return address
return address.substr(0, 6) + ".." + address.right(4)


# Long credentials (LiveKit JWTs) blow up the adapter line — keep the first
# TOKEN_SUMMARY_CHARS characters of the token value and elide the rest.
func _summarize_access_token(adapter: String) -> String:
var key := "access_token="
var idx := adapter.find(key)
if idx == -1:
return adapter
var start := idx + key.length()
var end := adapter.find("&", start)
if end == -1:
end = adapter.length()
if end - start <= TOKEN_SUMMARY_CHARS:
return adapter
return adapter.substr(0, start + TOKEN_SUMMARY_CHARS) + "..." + adapter.substr(end)


# Peer names come from the wire — escape the bbcode delimiter so a bracket in a
# name can't inject markup into the RichTextLabel.
func _escape_bbcode(text_value: String) -> String:
return text_value.replace("[", "[lb]")


func _on_timer_timeout():
if collapsed:
return
if not is_instance_valid(Global.comms):
return

var info: Dictionary = Global.comms.get_debug_room_info()
var lines := PackedStringArray()

var realm_name := ""
if is_instance_valid(Global.realm):
realm_name = String(Global.realm.realm_name)
if not realm_name.is_empty():
lines.append("Realm: %s" % realm_name)

var adapter: String = _summarize_access_token(info.get("adapter", ""))
var connection_state: String = info.get("connection_state", "unknown")
lines.append("Adapter: %s [%s]" % [adapter, connection_state])

if info.get("comms_on_hold", false):
lines.append("[color=yellow]COMMS ON HOLD[/color]")

if not info.get("livekit_enabled", true):
lines.append("[color=orange]LIVEKIT DISABLED (pulse-only mode)[/color]")

var main_room_type: String = info.get("main_room_type", "none")
var main_room_state: String = info.get("main_room_state", "none")
# In archipelago mode the island room (below) is the effective main room
if main_room_type != "none" or connection_state != "archipelago":
lines.append("Main room (%s): %s" % [main_room_type, _colored_state(main_room_state)])

if connection_state == "archipelago":
var archipelago_state: String = info.get("archipelago_state", "none")
var island_id: String = info.get("island_id", "")
var island_room_state: String = info.get("island_room_state", "none")
var island_label := island_id if not island_id.is_empty() else "-"
lines.append(
(
"Archipelago: %s | island %s (%s)"
% [
_colored_state(archipelago_state),
island_label,
_colored_state(island_room_state)
]
)
)

var scene_room: String = info.get("scene_room", "")
var scene_room_state: String = info.get("scene_room_state", "none")
var scene_label := scene_room if not scene_room.is_empty() else "-"
lines.append("Scene room: %s %s" % [scene_label, _colored_state(scene_room_state)])

lines.append(_build_pulse_line(info))
if info.get("pulse_disabled_for_session", false):
lines.append("[color=red]Pulse disabled for session[/color]")

lines.append_array(_peer_lines())

rich_text_label.text = "\n".join(lines)


func _build_pulse_line(info: Dictionary) -> String:
if not info.get("pulse_available", false):
return "Pulse: [color=gray]not in build[/color]"
if not info.get("pulse_enabled", false):
return "Pulse: [color=gray]OFF[/color]"

var pulse_state: String = info.get("pulse_state", "unavailable")
var pulse_endpoint: String = info.get("pulse_endpoint", "")
var pulse_failures: int = info.get("pulse_failures", 0)
var dual_channel_label: String = "ON" if info.get("dual_channel", true) else "OFF"
return (
"Pulse: %s @ %s | fails %d | dual-ch %s"
% [_colored_state(pulse_state), pulse_endpoint, pulse_failures, dual_channel_label]
)


func _peer_lines() -> PackedStringArray:
var lines := PackedStringArray()
var peers: Array = Global.comms.get_debug_peer_rooms()
lines.append("Peers: %d" % peers.size())
var shown: int = mini(peers.size(), MAX_PEER_ROWS)
for i in range(shown):
var peer: Dictionary = peers[i]
var address: String = peer.get("address", "")
var rooms: String = peer.get("rooms", "")
var peer_name: String = peer.get("name", "")
var who := _short_address(address)
if not peer_name.is_empty():
who = "%s %s" % [_escape_bbcode(peer_name), who]
lines.append(" %s %s" % [who, rooms])
if peers.size() > MAX_PEER_ROWS:
lines.append(" … +%d more" % (peers.size() - MAX_PEER_ROWS))
return lines
Loading