mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
feat(multiplayer): show authoritative proposal countdown
This commit is contained in:
@@ -188,7 +188,7 @@ func _render(snapshot: Dictionary) -> void:
|
||||
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))]
|
||||
elif phase == MatchmakingState.PROPOSED:
|
||||
detail_label.text = "Review the proposal before the countdown expires"
|
||||
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"
|
||||
elif phase == MatchmakingState.RESULT_PENDING:
|
||||
@@ -210,5 +210,11 @@ static func _is_terminal(phase: String) -> bool:
|
||||
return phase in [MatchmakingState.CANCELLED, MatchmakingState.EXPIRED, MatchmakingState.FAILED, MatchmakingState.LIVE, MatchmakingState.COMPLETED]
|
||||
|
||||
|
||||
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"
|
||||
return "Review proposal · %ds remaining" % maxi(0, expires_at_unix - now_unix)
|
||||
|
||||
|
||||
static func _can_start_new_search(phase: String) -> bool:
|
||||
return phase == MatchmakingState.IDLE or phase in [MatchmakingState.CANCELLED, MatchmakingState.EXPIRED, MatchmakingState.FAILED, MatchmakingState.COMPLETED]
|
||||
|
||||
@@ -17,3 +17,9 @@ func test_terminal_state_policy_does_not_leave_cancel_or_proposal_actions_enable
|
||||
assert_true(Matchmaking._can_start_new_search(MatchmakingState.FAILED), "failed search can be retried")
|
||||
assert_true(not Matchmaking._can_start_new_search(MatchmakingState.LIVE), "live match cannot start a second search")
|
||||
assert_true(Matchmaking._can_start_new_search(MatchmakingState.COMPLETED), "completed match can start a new search")
|
||||
|
||||
|
||||
func test_proposal_countdown_uses_authoritative_expiry_and_clamps_after_expiry() -> void:
|
||||
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")
|
||||
|
||||
@@ -1564,3 +1564,5 @@ Queue heartbeat and cancellation revision conflicts now schedule the same author
|
||||
WebSocket event envelopes now require RFC3339 timestamps rather than merely non-empty text, matching the versioned contract; session-expiry format checks use the same boundary validator. Malformed-format adversarial coverage is included.
|
||||
|
||||
WebSocket event resource identifiers now enforce the contract’s opaque 16–128 character allowlist, preventing path/separator text or undersized identifiers from entering the client projection.
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user