mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
fix(multiplayer): preserve proposal requeues
This commit is contained in:
@@ -124,17 +124,20 @@ func apply_proposal_update(update: Dictionary) -> bool:
|
||||
elif incoming_proposal_state == "DECLINED":
|
||||
if not _is_legal_proposal_transition(proposal_state, incoming_proposal_state):
|
||||
return _request_resync(proposal_id)
|
||||
phase = FAILED
|
||||
if phase == PROPOSED:
|
||||
phase = QUEUED
|
||||
message = "A player declined the match proposal"
|
||||
elif incoming_proposal_state == "EXPIRED":
|
||||
if not _is_legal_proposal_transition(proposal_state, incoming_proposal_state):
|
||||
return _request_resync(proposal_id)
|
||||
phase = EXPIRED
|
||||
if phase == PROPOSED:
|
||||
phase = QUEUED
|
||||
message = "The match proposal expired"
|
||||
elif incoming_proposal_state == "CANCELLED":
|
||||
if not _is_legal_proposal_transition(proposal_state, incoming_proposal_state):
|
||||
return _request_resync(proposal_id)
|
||||
phase = CANCELLED
|
||||
if phase == PROPOSED:
|
||||
phase = QUEUED
|
||||
message = "The match proposal was cancelled"
|
||||
else:
|
||||
return _request_resync(proposal_id)
|
||||
|
||||
@@ -96,15 +96,22 @@ func test_proposal_terminal_states_are_visible_and_not_cancellable() -> void:
|
||||
assert_true(state.apply_proposal_update({"proposal_id": "proposal-1", "revision": 1, "state": "OPEN"}), "open proposal applies")
|
||||
assert_eq(state.phase, MatchmakingState.PROPOSED, "open proposal is visible")
|
||||
assert_true(state.apply_proposal_update({"proposal_id": "proposal-1", "revision": 2, "state": "DECLINED"}), "declined proposal applies")
|
||||
assert_eq(state.phase, MatchmakingState.FAILED, "decline is terminal and visible")
|
||||
assert_true(not state.can_cancel(), "terminal proposal cannot issue queue cancel")
|
||||
assert_eq(state.phase, MatchmakingState.QUEUED, "decline returns the requeued ticket to search")
|
||||
assert_true(state.can_cancel(), "requeued ticket can be cancelled")
|
||||
|
||||
var expired := MatchmakingState.new()
|
||||
expired.begin_queue("ticket-2", "casual")
|
||||
assert_true(expired.apply_proposal_update({"proposal_id": "proposal-2", "revision": 1, "state": "OPEN"}), "second proposal opens")
|
||||
assert_true(expired.apply_proposal_update({"proposal_id": "proposal-2", "revision": 2, "state": "EXPIRED"}), "expired proposal applies")
|
||||
assert_eq(expired.phase, MatchmakingState.EXPIRED, "expiry is visible")
|
||||
assert_true(not expired.can_cancel(), "expired proposal cannot be cancelled")
|
||||
assert_eq(expired.phase, MatchmakingState.QUEUED, "expiry returns the requeued ticket to search")
|
||||
assert_true(expired.can_cancel(), "requeued ticket can be cancelled")
|
||||
|
||||
var cancelled := MatchmakingState.new()
|
||||
cancelled.begin_queue("ticket-3", "casual")
|
||||
assert_true(cancelled.apply_proposal_update({"proposal_id": "proposal-3", "revision": 1, "state": "OPEN"}), "third proposal opens")
|
||||
cancelled.phase = MatchmakingState.CANCELLED
|
||||
assert_true(cancelled.apply_proposal_update({"proposal_id": "proposal-3", "revision": 2, "state": "DECLINED"}), "decline after ticket cancellation is accepted")
|
||||
assert_eq(cancelled.phase, MatchmakingState.CANCELLED, "proposal decline cannot resurrect a cancelled ticket")
|
||||
|
||||
|
||||
func test_proposal_projection_rejects_illegal_higher_revision_transitions() -> void:
|
||||
|
||||
@@ -1547,4 +1547,6 @@ Ticket, proposal, and WebSocket revisions now fail closed unless they are finite
|
||||
|
||||
Proposal updates now enforce the documented `OPEN → ACCEPTED/DECLINED/EXPIRED/CANCELLED` graph, including rejecting higher-revision reopen/accept attempts after terminal decisions while preserving same-state duplicates. Adversarial proposal-transition tests cover accepted and declined terminal paths.
|
||||
|
||||
Proposal decline/expiry/cancellation now leaves a still-proposed ticket in `QUEUED`, matching the durable server requeue transaction; the proposal’s terminal message remains visible without making the ticket terminal. A cancelled ticket is never resurrected by a later proposal event, covered by adversarial cross-aggregate tests.
|
||||
|
||||
Ticket projections now validate playlist metadata on every update, rejecting unknown values before either phase or playlist state can mutate. An adversarial higher-revision update test covers this boundary.
|
||||
|
||||
Reference in New Issue
Block a user