mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 17:53:44 +00:00
39a41c016c
Implements tasks 2.1-2.7: NetworkedMatch spawns a deterministic slot layout from the lobby roster, the server drives each connected peer's ship via RLShipController fed by decoded client input and broadcasts 60Hz snapshots, and the client renders everything (including its own ship) from a per-body NetInterpolator with no local prediction yet. Dual-time remote entities split collider updates (present-time, for correct contacts) from $Visual updates (interp-delayed, for smoothness). Camera/HUD wiring and remote engine-flame VFX fell out of the existing Ship API for free once snapshots were flowing. Three real bugs found and fixed while getting a two-process test green: an RPC method named _input collided with Node's built-in _input virtual and broke the whole MatchSim autoload from loading; networked_match.gd never called NetworkManager.poll(), so nothing sent via RPC in this scene reached the wire despite Phase 1's manual polling being wired up everywhere else; and a match_config request/response fallback (added to close a startup race) could double-deliver once polling was fixed, requiring an idempotency guard. Verified with tests/networked_match_smoke: a real headless two-process host+client run shows the client rendering 31m of server-authoritative movement from a held forward-thrust input, with thrust_z=1.0 confirmed on the interpolated snapshot mid-drive and camera/HUD both wired. Full Phase 1 regression suite re-run clean alongside it. Task 2.8 (net_sim.gd latency/jitter/loss decorator) is not yet done; Phase 2's own gate needs it before it's fully met.
72 lines
2.5 KiB
GDScript
72 lines
2.5 KiB
GDScript
extends Node
|
|
|
|
# Manual two-process smoke test for Phase 2 (tasks 2.1-2.5): match_config,
|
|
# server-authoritative simulation, snapshot broadcast, client interpolation.
|
|
# Not part of tests/test_runner.tscn — needs real ENet peers and a real
|
|
# physics-driven ship. Run:
|
|
#
|
|
# godot --headless --path Game res://tests/networked_match_smoke.tscn -- --role=host
|
|
# godot --headless --path Game res://tests/networked_match_smoke.tscn -- --role=client
|
|
|
|
const PORT := 7812
|
|
const SETTLE_SECONDS := 2.0 # time to let match_config + a few snapshots land before checking spawn state
|
|
const DRIVE_SECONDS := 2.0 # time to hold forward thrust and let the ship actually move
|
|
const HOST_LIFETIME_SECONDS := 10.0
|
|
|
|
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, waiting for a client to join the roster..." % PORT)
|
|
MatchNet.player_joined.connect(_on_host_player_joined)
|
|
"client":
|
|
MatchNet.local_player_name = "NetTest"
|
|
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
|
|
print("SMOKE: joining ...")
|
|
MatchNet.welcomed.connect(_on_client_welcomed)
|
|
_:
|
|
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()
|
|
|
|
|
|
func _on_host_player_joined(_peer_id: int, _name: String) -> void:
|
|
MatchNet.player_joined.disconnect(_on_host_player_joined)
|
|
print("SMOKE: host loading networked_match.tscn ...")
|
|
get_tree().change_scene_to_file.call_deferred("res://scenes/networked_match.tscn")
|
|
var hooks := preload("res://tests/networked_match_test_hooks.gd").new()
|
|
get_tree().root.add_child.call_deferred(hooks)
|
|
hooks.run_host_check.call_deferred(HOST_LIFETIME_SECONDS)
|
|
|
|
|
|
func _on_client_welcomed() -> void:
|
|
MatchNet.welcomed.disconnect(_on_client_welcomed)
|
|
print("SMOKE: client loading networked_match.tscn ...")
|
|
get_tree().change_scene_to_file.call_deferred("res://scenes/networked_match.tscn")
|
|
var hooks := preload("res://tests/networked_match_test_hooks.gd").new()
|
|
get_tree().root.add_child.call_deferred(hooks)
|
|
hooks.run_client_check.call_deferred(SETTLE_SECONDS, DRIVE_SECONDS)
|