20 Commits

Author SHA1 Message Date
Josh Creek 55b88a3aa5 fix(multiplayer): converge events through REST 2026-09-02 18:54:10 +01:00
Josh Creek 91658fbc13 fix(multiplayer): recover assignment handoff 2026-09-02 18:47:00 +01:00
Josh Creek bda3fc5aff fix(multiplayer): validate persisted matchmaking ids 2026-09-01 22:46:40 +01:00
Josh Creek a10c6d47f9 fix(multiplayer): validate persisted matchmaking snapshots 2026-09-01 22:34:59 +01:00
Josh Creek 1e5825b096 fix(multiplayer): validate ticket timestamps 2026-09-01 22:08:19 +01:00
Josh Creek c08c761af3 fix(multiplayer): recover requeued tickets 2026-09-01 22:06:30 +01:00
Josh Creek 0c4ad6a5aa fix(multiplayer): preserve proposal requeues 2026-09-01 22:04:53 +01:00
Josh Creek 277ad4bf98 fix(multiplayer): validate ticket playlists 2026-09-01 22:02:01 +01:00
Josh Creek 0ce2a49419 fix(multiplayer): enforce proposal transitions 2026-09-01 22:00:37 +01:00
Josh Creek a60c1a097e fix(multiplayer): validate client revisions 2026-09-01 21:59:32 +01:00
Josh Creek 0f1864f8bc fix(multiplayer): enforce client state transitions 2026-09-01 21:57:13 +01:00
Josh Creek 30a5a89164 fix(multiplayer): project complete queue lifecycle 2026-09-01 21:55:19 +01:00
Josh Creek 6ac2d0fbb1 fix(multiplayer): project accepted queue state 2026-09-01 21:54:10 +01:00
Josh Creek 9240cd4b27 feat(multiplayer): preserve authoritative queue wait 2026-09-01 21:45:07 +01:00
Josh Creek 8fa53b778c fix(multiplayer): stop an infinite queue-ticket resync loop at revision 0
Found via a new real end-to-end integration test (next commit), not by
inspection: a client that just called begin_queue() and receives the
server's first confirmation at the same revision (0) always treated
it as a conflict and requested a resync -- forever, since the resync
response is itself a same-revision confirmation hitting the exact same
false mismatch. A real Godot client against a real running server
would loop on GET /v1/queue/{id} without ever settling into QUEUED.

Root cause: apply_ticket_update()'s incoming_revision == revision
branch never adopts fields on acceptance, but _ticket_differs()
compared expires_at_unix -- a field begin_queue() has no way to set in
advance, since it doesn't know the server-assigned expiry yet. Every
first same-revision confirmation therefore looked like a conflict
unconditionally, not just occasionally.

Fix: exclude expires_at_unix from the conflict check (a differing
expiry at the same revision is expected, not a sign of corruption --
real conflicts are still caught via state/playlist), and adopt it on
acceptance so the field doesn't just become permanently stale instead.
The existing "same-revision conflict requests recovery" unit test
still passes unchanged: its fixture differs on `state`, not
expires_at_unix, so it was never actually exercising this bug.
2026-09-01 14:13:36 +01:00
Josh Creek c5678ce877 fix: pass Godot 4.7 multiplayer test suite 2026-09-01 08:24:33 +01:00
Josh Creek 222bbb5b84 feat: classify matchmaking recovery failures 2026-08-31 22:45:20 +01:00
Josh Creek 62c1478913 feat: persist matchmaking recovery state 2026-08-31 22:42:43 +01:00
Josh Creek 550d73f1d7 fix: preserve active matchmaking on transient errors 2026-08-31 22:39:42 +01:00
Josh Creek 39ebfce1bb feat: add revisioned matchmaking client state 2026-08-31 22:33:33 +01:00