mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
feat(multiplayer): explain queue and connection health
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user