feat(multiplayer): explain queue and connection health

This commit is contained in:
Josh Creek
2026-09-01 23:11:37 +01:00
parent 9b14109727
commit b15d19eec2
3 changed files with 41 additions and 2 deletions
+25 -2
View File
@@ -186,13 +186,15 @@ func _render(snapshot: Dictionary) -> void:
var waited := _elapsed_seconds
if int(snapshot.get("enqueued_at_unix", 0)) > 0:
waited = float(ControlPlaneClient.state.waited_seconds(int(Time.get_unix_time_from_system())))
detail_label.text = "Waiting %.0fs · revision %d" % [waited, int(snapshot.get("revision", 0))]
detail_label.text = queue_wait_detail_text(int(waited), int(snapshot.get("revision", 0)))
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 = phase_detail_label(phase)
elif phase in [MatchmakingState.ALLOCATING, MatchmakingState.PROCESS_READY, MatchmakingState.ASSIGNMENT_READY, MatchmakingState.ASSIGNED, MatchmakingState.CONNECTING, MatchmakingState.LIVE]:
elif phase in [MatchmakingState.ALLOCATING, MatchmakingState.PROCESS_READY, MatchmakingState.ASSIGNMENT_READY, MatchmakingState.ASSIGNED]:
detail_label.text = phase_detail_label(phase)
elif phase in [MatchmakingState.CONNECTING, MatchmakingState.LIVE]:
detail_label.text = "%s · %s" % [phase_detail_label(phase), latency_detail_text(NetworkManager.rtt_ms)]
elif phase == MatchmakingState.RESULT_PENDING:
detail_label.text = "The server is confirming the final result"
elif phase == MatchmakingState.COMPLETED:
@@ -238,5 +240,26 @@ static func proposal_countdown_text(expires_at_unix: int, now_unix: int) -> Stri
return "Review proposal · %ds remaining" % maxi(0, expires_at_unix - now_unix)
static func queue_wait_detail_text(waited_seconds: int, revision: int) -> String:
var waited := maxi(0, waited_seconds)
var suffix := "looking for compatible players"
if waited >= 30:
suffix = "widening skill range while keeping latency limits"
elif waited >= 10:
suffix = "matching nearby skill and latency"
return "Waiting %ds · %s · revision %d" % [waited, suffix, maxi(0, revision)]
static func latency_detail_text(rtt_ms: float) -> String:
if not is_finite(rtt_ms) or rtt_ms < 0.0:
return "Latency: measuring"
var rounded := int(round(rtt_ms))
if rtt_ms <= 50.0:
return "Latency: %dms · excellent" % rounded
if rtt_ms <= 100.0:
return "Latency: %dms · good" % rounded
return "Latency: %dms · high" % rounded
static func _can_start_new_search(phase: String) -> bool:
return phase == MatchmakingState.IDLE or phase in [MatchmakingState.CANCELLED, MatchmakingState.EXPIRED, MatchmakingState.FAILED, MatchmakingState.COMPLETED]
+14
View File
@@ -30,3 +30,17 @@ func test_allocation_lifecycle_phases_have_specific_detail_copy() -> void:
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")
func test_queue_wait_copy_explains_progress_without_trusting_negative_input() -> void:
assert_eq(Matchmaking.queue_wait_detail_text(-4, -2), "Waiting 0s · looking for compatible players · revision 0", "negative metadata is clamped")
assert_true(Matchmaking.queue_wait_detail_text(10, 3).contains("skill and latency"), "mid-wait explains the compatibility search")
assert_true(Matchmaking.queue_wait_detail_text(30, 4).contains("keeping latency limits"), "long waits explain bounded widening")
func test_latency_copy_fails_closed_and_explains_quality_boundaries() -> void:
assert_eq(Matchmaking.latency_detail_text(-1.0), "Latency: measuring", "missing latency remains honest")
assert_eq(Matchmaking.latency_detail_text(INF), "Latency: measuring", "infinite latency fails closed")
assert_eq(Matchmaking.latency_detail_text(50.0), "Latency: 50ms · excellent", "excellent boundary is inclusive")
assert_eq(Matchmaking.latency_detail_text(100.0), "Latency: 100ms · good", "good boundary is inclusive")
assert_eq(Matchmaking.latency_detail_text(100.1), "Latency: 100ms · high", "high latency is surfaced")
+2
View File
@@ -1621,6 +1621,8 @@ The public queue adapter also rejects an explicitly supplied short or unsafe `ti
RFC3339 validation now checks both wire syntax and calendar parseability, rejecting impossible dates before they can become epoch metadata.
Matchmaking now explains queue progress (including bounded skill widening while preserving latency limits) and exposes live connection latency quality during connect/live phases; adversarial UI tests cover missing, infinite, negative, and threshold RTT values.
Signed MatchNet claims now also require exact JSON string/integer types for every identity, protocol, expiry, slot, team, and generation field; string-number coercion is rejected before canonical signature verification.
Presentation progress: a shared `Game/themes/cosmic_clash_theme.tres` now gives the menu, lobby, matchmaking, and settings surfaces consistent button, input, option, and label styling. The custom-font portion of `TODO.md` remains open until a distributable font asset is selected.