extends Node # Manual two-process smoke test for lobby.tscn (task 1.5). BOTH roles load # lobby.tscn as their actual current_scene via change_scene_to_file — # matching how main_menu.gd's Host/Join flow (task 1.7) really gets a # player there — rather than instantiating it as a child of this driver. # That distinction matters: change_scene_to_file() operates on # get_tree().current_scene, and calling it from a node that ISN'T an # ancestor-chain match for current_scene (as an earlier draft of this test # did, by add_child()-ing lobby.tscn under this driver) hung completely # on disconnect — see multiplayer-todo.md §9 gotcha 27. # # The host role loading lobby.tscn is deliberate, not an oversight: a # *dedicated* server (server_boot.tscn) never loads it, but a self-hosting # player clicking main_menu.gd's Host button does — NetworkManager.host() # then _leave_to_lobby(), landing them on lobby.gd's is_server branch (a # read-only view of the roster, no team/ready controls). An earlier # version of this test skipped that branch entirely on the mistaken # assumption that "host" here meant "dedicated server"; adversarial review # caught that it left a real, production-reachable code path untested. # # Not part of tests/test_runner.tscn — needs real ENet peers. Run: # # godot --headless --path Game res://tests/lobby_smoke.tscn -- --role=host # godot --headless --path Game res://tests/lobby_smoke.tscn -- --role=client const PORT := 7806 var _role := "" func _ready() -> void: for arg in OS.get_cmdline_user_args(): if arg.begins_with("--role="): _role = arg.substr("--role=".length()) match _role: "host": var err := NetworkManager.host(PORT) if err != OK: print("SMOKE FAIL: host() failed: %s" % error_string(err)) get_tree().quit(1) return print("SMOKE: hosting on port %d" % PORT) get_tree().change_scene_to_file.call_deferred("res://scenes/lobby.tscn") var host_hooks := preload("res://tests/lobby_test_hooks.gd").new() get_tree().root.add_child.call_deferred(host_hooks) host_hooks.run_host_test.call_deferred() "client": MatchNet.local_player_name = "Carol" var err := NetworkManager.join("127.0.0.1", PORT) if err != OK: print("SMOKE FAIL: join() failed: %s" % error_string(err)) get_tree().quit(1) return # Real usage (task 1.7): main_menu.gd will call this same # change_scene_to_file after NetworkManager.join() succeeds — but # not from this test's own _ready(), which the tree is still in # the middle of processing (Godot rejects a synchronous # change_scene_to_file mid node-add with "Parent node is busy"). # call_deferred sidesteps that; main_menu.gd's real button-press # handler won't have this problem since it isn't called from # inside _ready(). get_tree().change_scene_to_file.call_deferred("res://scenes/lobby.tscn") var hooks := preload("res://tests/lobby_test_hooks.gd").new() get_tree().root.add_child.call_deferred(hooks) # sibling of current_scene, not a child of it -- survives the swap above hooks.run_client_test.call_deferred() _: print("SMOKE FAIL: missing or unrecognised --role=") get_tree().quit(1) return func _process(_delta: float) -> void: NetworkManager.poll() func _physics_process(_delta: float) -> void: NetworkManager.poll()