extends Node # Manual two-process smoke test for NetworkManager (task 1.2 acceptance: # "two peers connect and disconnect cleanly"; also exercises task 1.3's # manual-poll-only regime — NetworkManager disables automatic multiplayer # polling, so this script's own _process() polling is what makes the # connection progress at all). Deliberately not part of the pure-function # suite in tests/test_runner.tscn — an ENet handshake needs two real # processes. Run: # # godot --headless --path Game res://tests/net_smoke.tscn -- --role=host # godot --headless --path Game res://tests/net_smoke.tscn -- --role=client # # (start the host first). Each process prints one "SMOKE PASS/FAIL: ..." # line and exits 0/1. # # Adversarial-review regression: this test used to only confirm each # process exits cleanly on its own initiative — it never confirmed the # OTHER peer actually observes the disconnect. The host role now waits for # BOTH client_connected and client_disconnected before passing; the client # explicitly disconnects mid-test (rather than only on process exit) and # gives it a beat before quitting, same reasoning as §9 gotcha 26 for # connects: a clean disconnect notice still needs a few poll() cycles to # reach the wire, or the other side falls back to its ~5s peer timeout # (§9 gotcha 11) instead of a prompt, clean disconnect. const DEFAULT_PORT := 7799 const TIMEOUT_SECONDS := 8.0 var _role := "" var _port := DEFAULT_PORT var _finished := false func _ready() -> void: for arg in OS.get_cmdline_user_args(): if arg.begins_with("--role="): _role = arg.substr("--role=".length()) elif arg.begins_with("--port="): _port = int(arg.substr("--port=".length())) if _role == "host": NetworkManager.client_connected.connect(_on_host_client_connected) NetworkManager.client_disconnected.connect(_on_host_client_disconnected) var err := NetworkManager.host(_port) if err != OK: _finish(false, "host() failed: %s" % error_string(err)) return print("SMOKE: hosting on port %d, waiting for a client..." % _port) elif _role == "client": NetworkManager.connected_to_server.connect(_on_client_connected) NetworkManager.connection_failed.connect(_on_client_connection_failed) var err := NetworkManager.join("127.0.0.1", _port) if err != OK: _finish(false, "join() failed: %s" % error_string(err)) return print("SMOKE: joining 127.0.0.1:%d ..." % _port) else: _finish(false, "missing or unrecognised --role= (expected host|client)") return get_tree().create_timer(TIMEOUT_SECONDS).timeout.connect(_on_timeout) func _process(_delta: float) -> void: NetworkManager.poll() func _physics_process(_delta: float) -> void: NetworkManager.poll() func _on_host_client_connected(peer_id: int) -> void: print("SMOKE INFO: host saw client_connected (peer_id=%d), waiting for client_disconnected too..." % peer_id) func _on_host_client_disconnected(peer_id: int) -> void: _finish(true, "host saw client_connected AND client_disconnected (peer_id=%d)" % peer_id) func _on_client_connected() -> void: print("SMOKE INFO: client connected to host") # §9 gotcha 26: give the host a beat to fully settle the connect # handshake before we turn around and disconnect again. await get_tree().create_timer(0.5).timeout NetworkManager.shutdown() # Same class of issue as gotcha 26, the disconnect leg: closing the # peer queues ENet's own disconnect notice, which still needs a few # more poll() cycles to actually reach the wire before this process # exits — quit immediately and the host would fall back to its ~5s # peer timeout (§9 gotcha 11) instead of a prompt, clean disconnect. await get_tree().create_timer(1.0).timeout _finish(true, "client connected then disconnected cleanly") func _on_client_connection_failed() -> void: _finish(false, "client connection_failed") func _on_timeout() -> void: if not _finished: _finish(false, "timed out waiting for connect+disconnect confirmation") func _finish(success: bool, message: String) -> void: if _finished: return _finished = true print("SMOKE %s: %s" % ["PASS" if success else "FAIL", message]) await get_tree().create_timer(0.3).timeout NetworkManager.shutdown() get_tree().quit(0 if success else 1)