dba865ae4b
- Session autoload: carries {world_id, host, port} from the world browser
to the game scene.
- world_list.gd "Enter World": POST /api/worlds/{id}/join -> Session.begin
-> load game.tscn. On 503 (backend has no orchestration) falls back to
local single-player; other errors surface on the card.
- api_client.gd: join_world().
- game.gd (new, on game.tscn root): local single-player by default; when
Session.active, connects a NetworkClient to the world's dedicated server
and runs networked -- forwards the local Player's input each physics
frame, snaps to the server position past a generous drift threshold
(the Player keeps predicting locally with real collision), renders other
players from snapshots (labelled capsules, lerped -- avatar appearance
sync is a follow-up, the server doesn't replicate loadouts yet), and
returns to the world list on disconnect.
- player.gd: exposes net_input (the input frame it applied) and
apply_server_correction().
- network_client.gd: send_input() + get_own_server_state() for callers that
do their own collision-aware prediction.
- scripts/mp_game_flow_client.gd + run_game_multiplayer_test.sh: two real
clients each register -> join via the API -> game.tscn -> networked;
A sees B and moves. PASS.
Verified end to end against an orchestration-enabled backend. Non-networked
smokes green (enter_world 9/9, world_list 5/5, player 15/15,
avatar_in_world 10/10, main_menu_full_flow 12/12).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
44 lines
1.8 KiB
Bash
44 lines
1.8 KiB
Bash
#!/usr/bin/env bash
|
|
# Full networked game flow, two real client processes:
|
|
# register -> POST /api/worlds/{id}/join -> Session -> game.tscn -> networked.
|
|
# Client A must see client B rendered as a remote player and its own movement
|
|
# must round-trip. Requires a backend with GAME_SERVERS_ENABLED and the Godot
|
|
# binary reachable from it.
|
|
#
|
|
# Usage: scripts/run_game_multiplayer_test.sh <backend_base_url> <world_id>
|
|
set -uo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
BASE_URL="${1:?usage: run_game_multiplayer_test.sh <backend_url> <world_id>}"
|
|
WORLD_ID="${2:?usage: run_game_multiplayer_test.sh <backend_url> <world_id>}"
|
|
GODOT_BIN="${GODOT_BIN:-godot}"
|
|
command -v "$GODOT_BIN" >/dev/null 2>&1 || GODOT_BIN="$HOME/godot/godot"
|
|
|
|
export AGP_API_BASE_URL="$BASE_URL"
|
|
|
|
cleanup() { kill "${B_PID:-}" 2>/dev/null; wait 2>/dev/null; }
|
|
trap cleanup EXIT
|
|
|
|
echo "=== client B: join world $WORLD_ID and hold ==="
|
|
"$GODOT_BIN" --headless --path . --script res://scripts/mp_game_flow_client.gd -- \
|
|
--role b --world "$WORLD_ID" >/tmp/mpflow_b.log 2>&1 &
|
|
B_PID=$!
|
|
for _ in $(seq 1 40); do grep -q 'RESULT b in_world' /tmp/mpflow_b.log && break; sleep 1; done
|
|
grep -q 'RESULT b in_world' /tmp/mpflow_b.log || { echo "B never entered the world:"; tail -5 /tmp/mpflow_b.log; echo "=== SMOKE RESULT: FAIL ==="; exit 1; }
|
|
|
|
echo "=== client A: join world $WORLD_ID, move, look for B ==="
|
|
"$GODOT_BIN" --headless --path . --script res://scripts/mp_game_flow_client.gd -- \
|
|
--role a --world "$WORLD_ID" 2>&1 | tee /tmp/mpflow_a.log
|
|
A_LINE=$(grep '^RESULT a' /tmp/mpflow_a.log || true)
|
|
|
|
echo
|
|
echo "A: ${A_LINE:-<none>}"
|
|
echo "B: $(grep '^RESULT b' /tmp/mpflow_b.log || echo '<none>')"
|
|
|
|
if echo "$A_LINE" | grep -q 'moved=true' && echo "$A_LINE" | grep -qE 'remotes=[1-9]'; then
|
|
echo "=== SMOKE RESULT: PASS ==="
|
|
exit 0
|
|
fi
|
|
echo "=== SMOKE RESULT: FAIL ==="
|
|
exit 1
|