From 27017043f9cfda7ebb6fc4504216bb648c3bab78 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:25:54 +0100 Subject: [PATCH] feat(multiplayer): explain allocation lifecycle --- Game/scripts/matchmaking.gd | 24 +++++++++++++++++++++++- Game/tests/cases/test_matchmaking_ui.gd | 7 +++++++ multiplayer-next.md | 2 ++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Game/scripts/matchmaking.gd b/Game/scripts/matchmaking.gd index 4b710c6a..31eefa92 100644 --- a/Game/scripts/matchmaking.gd +++ b/Game/scripts/matchmaking.gd @@ -190,7 +190,9 @@ func _render(snapshot: Dictionary) -> void: elif phase == MatchmakingState.PROPOSED: detail_label.text = proposal_countdown_text(int(snapshot.get("expires_at_unix", 0)), int(Time.get_unix_time_from_system())) elif phase == MatchmakingState.ACCEPTED: - detail_label.text = "All players accepted; preparing the match server" + detail_label.text = phase_detail_label(phase) + elif phase in [MatchmakingState.ALLOCATING, MatchmakingState.PROCESS_READY, MatchmakingState.ASSIGNMENT_READY, MatchmakingState.ASSIGNED, MatchmakingState.CONNECTING, MatchmakingState.LIVE]: + detail_label.text = phase_detail_label(phase) elif phase == MatchmakingState.RESULT_PENDING: detail_label.text = "The server is confirming the final result" elif phase == MatchmakingState.COMPLETED: @@ -210,6 +212,26 @@ static func _is_terminal(phase: String) -> bool: return phase in [MatchmakingState.CANCELLED, MatchmakingState.EXPIRED, MatchmakingState.FAILED, MatchmakingState.LIVE, MatchmakingState.COMPLETED] +static func phase_detail_label(phase: String) -> String: + match phase: + MatchmakingState.ACCEPTED: + return "All players accepted; preparing the match server" + MatchmakingState.ALLOCATING: + return "Finding a dedicated match server" + MatchmakingState.PROCESS_READY: + return "Match server started; preparing player assignments" + MatchmakingState.ASSIGNMENT_READY: + return "Player assignments are ready" + MatchmakingState.ASSIGNED: + return "Your match server is ready" + MatchmakingState.CONNECTING: + return "Connecting to the match server" + MatchmakingState.LIVE: + return "Match in progress" + _: + return "" + + static func proposal_countdown_text(expires_at_unix: int, now_unix: int) -> String: if expires_at_unix <= 0: return "Review the proposal before the countdown expires" diff --git a/Game/tests/cases/test_matchmaking_ui.gd b/Game/tests/cases/test_matchmaking_ui.gd index 6233cec4..982b313c 100644 --- a/Game/tests/cases/test_matchmaking_ui.gd +++ b/Game/tests/cases/test_matchmaking_ui.gd @@ -23,3 +23,10 @@ func test_proposal_countdown_uses_authoritative_expiry_and_clamps_after_expiry() assert_eq(Matchmaking.proposal_countdown_text(1100, 1000), "Review proposal · 100s remaining", "proposal countdown uses server expiry") assert_eq(Matchmaking.proposal_countdown_text(1000, 1001), "Review proposal · 0s remaining", "expired proposal countdown clamps to zero") assert_eq(Matchmaking.proposal_countdown_text(0, 1000), "Review the proposal before the countdown expires", "missing expiry retains compatible copy") + + +func test_allocation_lifecycle_phases_have_specific_detail_copy() -> void: + for phase in [MatchmakingState.ACCEPTED, MatchmakingState.ALLOCATING, MatchmakingState.PROCESS_READY, MatchmakingState.ASSIGNMENT_READY, MatchmakingState.ASSIGNED, MatchmakingState.CONNECTING, MatchmakingState.LIVE]: + assert_true(not Matchmaking.phase_detail_label(phase).is_empty(), "phase %s has lifecycle detail copy" % phase) + assert_true(Matchmaking.phase_detail_label(MatchmakingState.ALLOCATING).contains("dedicated"), "allocation explains dedicated server provisioning") + assert_true(Matchmaking.phase_detail_label(MatchmakingState.CONNECTING).contains("Connecting"), "connecting explains the active transport step") diff --git a/multiplayer-next.md b/multiplayer-next.md index 6d5de3a4..2ff9d97f 100644 --- a/multiplayer-next.md +++ b/multiplayer-next.md @@ -1568,3 +1568,5 @@ WebSocket event resource identifiers now enforce the contract’s opaque 16–12 The Go event hub now enforces the same resource-ID allowlist before publication, so malformed identifiers are rejected at the server boundary rather than only discarded by clients. The matchmaking UI now displays the authoritative proposal countdown from the server expiry epoch, clamped at zero and retaining compatible copy when older responses omit expiry metadata. Adversarial countdown tests cover delayed and missing-expiry responses. + +The UI now provides explicit detail copy for every non-terminal allocation and connection phase (`ACCEPTED` through `LIVE`), so server progress remains understandable throughout assignment and transport startup.