fix(session): reset lobby when player leaves finished match
This commit is contained in:
@@ -195,6 +195,11 @@ stateDiagram-v2
|
|||||||
During `Paused`, movement commands are rejected and move-counted effects do not
|
During `Paused`, movement commands are rejected and move-counted effects do not
|
||||||
advance. During `FinishPending`, the first exit is retained while the server
|
advance. During `FinishPending`, the first exit is retained while the server
|
||||||
waits long enough to determine whether the second exit creates a draw.
|
waits long enough to determine whether the second exit creates a draw.
|
||||||
|
An authenticated explicit player leave from any non-waiting phase, including
|
||||||
|
`Finished`, stops the current lifecycle and returns remaining sessions to the
|
||||||
|
waiting lobby. This is distinct from an unexpected disconnect, which preserves
|
||||||
|
the player's reconnect reservation for 60 seconds; observer leave and player
|
||||||
|
leave from `Lobby` revoke only the departing session.
|
||||||
|
|
||||||
## 6. Authoritative command processing
|
## 6. Authoritative command processing
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,10 @@ Browser controls must support:
|
|||||||
state.
|
state.
|
||||||
- If the player does not reconnect within 60 seconds, the match ends in a draw.
|
- If the player does not reconnect within 60 seconds, the match ends in a draw.
|
||||||
- Reloading a page is treated as a reconnect, not as leaving the match.
|
- Reloading a page is treated as a reconnect, not as leaving the match.
|
||||||
|
- A confirmed explicit player exit during any non-waiting phase, including a
|
||||||
|
finished result, revokes only that session and authoritatively returns all
|
||||||
|
remaining participants to a fresh waiting lobby. Observer exits and player
|
||||||
|
exits from an already waiting lobby affect only the departing session.
|
||||||
|
|
||||||
## 11. Browser presentation
|
## 11. Browser presentation
|
||||||
|
|
||||||
|
|||||||
@@ -3443,8 +3443,9 @@ resources.
|
|||||||
Let an authenticated player or observer deliberately leave through the
|
Let an authenticated player or observer deliberately leave through the
|
||||||
top-right settings menu, revoke only that browser's session, immediately free
|
top-right settings menu, revoke only that browser's session, immediately free
|
||||||
its slot, and return it to the name-entry screen with its previous display name
|
its slot, and return it to the name-entry screen with its previous display name
|
||||||
prefilled. A player leaving an unfinished match must authoritatively cancel
|
prefilled. A player leaving any non-waiting match phase, including a finished
|
||||||
that match and return every remaining participant to the lobby, while an
|
result, must authoritatively cancel that match and return every remaining
|
||||||
|
participant to the lobby, while an
|
||||||
observer departure or a lobby departure must not reset gameplay.
|
observer departure or a lobby departure must not reset gameplay.
|
||||||
|
|
||||||
### Scope
|
### Scope
|
||||||
@@ -3457,10 +3458,10 @@ observer departure or a lobby departure must not reset gameplay.
|
|||||||
authenticated player or observer session; do not show it on the unauthenticated
|
authenticated player or observer session; do not show it on the unauthenticated
|
||||||
welcome and name-entry screen.
|
welcome and name-entry screen.
|
||||||
- Require a modal confirmation before sending an exit command. For a player in
|
- Require a modal confirmation before sending an exit command. For a player in
|
||||||
countdown, active, finish-pending, or paused state, clearly warn that the
|
countdown, active, finish-pending, paused, or finished state, clearly warn
|
||||||
current match will end for everyone and the player's slot will be freed. For
|
that the current match will return everyone to the waiting lobby and the
|
||||||
a player in the lobby or an observer in any phase, explain that only this
|
player's slot will be freed. For a player in the lobby or an observer in any
|
||||||
browser's place will be released.
|
phase, explain that only this browser's place will be released.
|
||||||
- Treat opening or cancelling the confirmation as browser-local UI only. While
|
- Treat opening or cancelling the confirmation as browser-local UI only. While
|
||||||
it is open, suppress input from that browser without pausing or otherwise
|
it is open, suppress input from that browser without pausing or otherwise
|
||||||
mutating authoritative state; cancelling must restore the previous screen,
|
mutating authoritative state; cancelling must restore the previous screen,
|
||||||
@@ -3469,15 +3470,14 @@ observer departure or a lobby departure must not reset gameplay.
|
|||||||
correlated success response. It must be distinct from an unexpected socket
|
correlated success response. It must be distinct from an unexpected socket
|
||||||
disconnect, page reload, or transient connection loss, which retain the
|
disconnect, page reload, or transient connection loss, which retain the
|
||||||
existing 60-second player reconnection contract.
|
existing 60-second player reconnection contract.
|
||||||
- On an accepted player leave during countdown, active, finish-pending, or
|
- On an accepted player leave during countdown, active, finish-pending, paused,
|
||||||
paused state, stop all match timers, clear the current authoritative match,
|
or finished state, stop all match timers, clear the current authoritative
|
||||||
return the lobby to its waiting state, retain every other connected session
|
match, return the lobby to its waiting state, retain every other connected
|
||||||
and role, revoke the leaving session, free its player slot immediately, and
|
session and role, revoke the leaving session, free its player slot
|
||||||
broadcast the cancellation and fresh lobby state to the remaining players
|
immediately, and broadcast the cancellation and fresh lobby state to the
|
||||||
and observers.
|
remaining players and observers.
|
||||||
- On an accepted player leave while already in the waiting lobby or after a
|
- On an accepted player leave while already in the waiting lobby, revoke only
|
||||||
completed result, revoke only that player's session and free its slot. Do not
|
that player's session and free its slot.
|
||||||
reset an unfinished match because none is active in those phases.
|
|
||||||
- On an accepted observer leave in any phase, revoke only that observer's
|
- On an accepted observer leave in any phase, revoke only that observer's
|
||||||
session and free its observer slot. Never pause, reset, finish, restart, or
|
session and free its observer slot. Never pause, reset, finish, restart, or
|
||||||
otherwise mutate the match because an observer leaves.
|
otherwise mutate the match because an observer leaves.
|
||||||
@@ -3536,18 +3536,20 @@ observer departure or a lobby departure must not reset gameplay.
|
|||||||
modal and sends no protocol command until explicit confirmation. Cancelling,
|
modal and sends no protocol command until explicit confirmation. Cancelling,
|
||||||
pressing Escape, or activating the modal's cancel action leaves the session
|
pressing Escape, or activating the modal's cancel action leaves the session
|
||||||
and authoritative state unchanged and safely restores focus.
|
and authoritative state unchanged and safely restores focus.
|
||||||
3. A player confirmation during countdown, active, finish-pending, or paused
|
3. A player confirmation during countdown, active, finish-pending, paused, or
|
||||||
state explicitly says that the match will end for everyone and the slot will
|
finished state explicitly says that the match will return everyone to the
|
||||||
be freed. Confirmation produces exactly one authoritative cancellation,
|
waiting lobby and the slot will be freed. Confirmation produces exactly one
|
||||||
stops every relevant timer, clears the current match, and returns all
|
authoritative cancellation, stops every relevant timer, clears the current
|
||||||
remaining connected participants to a fresh waiting lobby.
|
match, and returns all remaining connected participants to a fresh waiting
|
||||||
|
lobby.
|
||||||
4. The leaving player's session is removed and its slot becomes available in
|
4. The leaving player's session is removed and its slot becomes available in
|
||||||
the same accepted server transition. Every other player's and observer's
|
the same accepted server transition. Every other player's and observer's
|
||||||
session, role, and connection remain valid, and the resulting lobby snapshot
|
session, role, and connection remain valid, and the resulting lobby snapshot
|
||||||
consistently shows the newly free slot.
|
consistently shows the newly free slot.
|
||||||
5. A player leaving the waiting lobby or a completed result releases only that
|
5. A player leaving the waiting lobby releases only that player's session and
|
||||||
player's session and slot. It does not disconnect other participants or
|
slot. A player leaving a completed result performs the same authoritative
|
||||||
perform an additional match reset.
|
return-to-lobby transition as another non-waiting player exit, without
|
||||||
|
disconnecting any remaining participant.
|
||||||
6. An observer leaving during any phase releases only that observer session and
|
6. An observer leaving during any phase releases only that observer session and
|
||||||
observer slot. Match phase, players, timers, maze, objects, inventory, and
|
observer slot. Match phase, players, timers, maze, objects, inventory, and
|
||||||
results remain byte-for-byte unchanged except for the expected lobby
|
results remain byte-for-byte unchanged except for the expected lobby
|
||||||
@@ -3586,11 +3588,12 @@ observer departure or a lobby departure must not reset gameplay.
|
|||||||
15. All host and browser tests pass; compressed-web-asset validation passes;
|
15. All host and browser tests pass; compressed-web-asset validation passes;
|
||||||
the payload remains within the 1.5 MiB web budget and LittleFS partition;
|
the payload remains within the 1.5 MiB web budget and LittleFS partition;
|
||||||
and firmware remains within the factory partition.
|
and firmware remains within the factory partition.
|
||||||
16. Physical-device testing confirms that a player can cancel an unfinished
|
16. Physical-device testing confirms that a player can cancel a non-waiting
|
||||||
match and immediately release its slot, an observer can leave without
|
match, including a finished result, and immediately release its slot, an
|
||||||
affecting play, the remaining browsers receive the correct lobby state,
|
observer can leave without affecting play, the remaining browsers receive
|
||||||
and the departing browser returns to a prefilled name-entry screen without
|
the correct lobby state, and the departing browser returns to a prefilled
|
||||||
stale controls, delayed input, or reconnect side effects.
|
name-entry screen without stale controls, delayed input, or reconnect side
|
||||||
|
effects.
|
||||||
17. The project owner confirms on representative mobile and desktop browsers
|
17. The project owner confirms on representative mobile and desktop browsers
|
||||||
that the exit action is easy to find but difficult to trigger accidentally,
|
that the exit action is easy to find but difficult to trigger accidentally,
|
||||||
its consequences are clear, and all role-specific outcomes match the
|
its consequences are clear, and all role-specific outcomes match the
|
||||||
@@ -3614,3 +3617,30 @@ observer departure or a lobby departure must not reset gameplay.
|
|||||||
immediate server-side session revocation, player-match cancellation, and a
|
immediate server-side session revocation, player-match cancellation, and a
|
||||||
browser confirmation flow that clears the temporary token only after the
|
browser confirmation flow that clears the temporary token only after the
|
||||||
correlated acknowledgement.
|
correlated acknowledgement.
|
||||||
|
- On 2026-09-13, a corrective lifecycle inspection found that the explicit
|
||||||
|
player-leave handler excluded `MAZE32_GAME_PHASE_FINISHED` from its match
|
||||||
|
reset condition. It therefore revoked the departing result-screen session
|
||||||
|
but did not broadcast `reset`, which could leave remaining clients behind an
|
||||||
|
unusable result modal. The handler now resets for a player leaving every
|
||||||
|
non-waiting lobby phase, including a completed result; it keeps waiting-lobby
|
||||||
|
player exits and observer exits session-scoped. The reset is broadcast before
|
||||||
|
the fresh lobby snapshot, and browser reset handling hides the modal and
|
||||||
|
renders the waiting lobby.
|
||||||
|
- On 2026-09-13, `node --test test/web/test_player_app.js` passed 31 tests,
|
||||||
|
including the result-phase leave regression that verifies the confirmation
|
||||||
|
warning, server reset condition, correlated token cleanup, modal dismissal,
|
||||||
|
and lobby rendering. `bash scripts/run_host_tests.sh` passed all suites;
|
||||||
|
its lobby/protocol suite reported explicit leave session revocation and slot
|
||||||
|
reuse as passing, including invalid-token restoration after either Player 1
|
||||||
|
or Player 2 leaves, retained player and observer sessions, observer-only
|
||||||
|
leave, duplicate leave idempotence, and immediate replacement of both player
|
||||||
|
slots. `python3
|
||||||
|
scripts/verify_web_assets.py` passed with deterministic payload
|
||||||
|
`3fb28e1ae55deba0ac592bcd99d81b7e95271604a5b35d7f293c4b8bffef6143` at
|
||||||
|
33,183 / 1,572,864 bytes. `/home/sasa/.platformio/penv/bin/pio run` passed
|
||||||
|
at 1,030,256 / 2,097,152 factory bytes, and `/home/sasa/.platformio/penv/bin/pio
|
||||||
|
run --target buildfs` passed at 2,031,616 / 2,031,616 LittleFS bytes.
|
||||||
|
- On 2026-09-13, the project owner checked the corrected result-screen exit
|
||||||
|
flow in the browser and confirmed that the issue was fixed: a remaining
|
||||||
|
participant is returned from the result modal to a usable waiting lobby when
|
||||||
|
the other player explicitly leaves.
|
||||||
|
|||||||
@@ -950,9 +950,8 @@ static void handle_message(maze32_gateway_t* gateway, maze32_gateway_connection_
|
|||||||
}
|
}
|
||||||
if (message->type == MAZE32_MESSAGE_LEAVE) {
|
if (message->type == MAZE32_MESSAGE_LEAVE) {
|
||||||
maze32_role_t leaving_role = session->role;
|
maze32_role_t leaving_role = session->role;
|
||||||
int cancel_match = leaving_role == MAZE32_ROLE_PLAYER &&
|
int cancel_match =
|
||||||
gateway->lobby.phase != MAZE32_LOBBY_WAITING &&
|
leaving_role == MAZE32_ROLE_PLAYER && gateway->lobby.phase != MAZE32_LOBBY_WAITING;
|
||||||
gateway->game.phase != MAZE32_GAME_PHASE_FINISHED;
|
|
||||||
char acknowledgement[192] = "";
|
char acknowledgement[192] = "";
|
||||||
size_t length = 0U;
|
size_t length = 0U;
|
||||||
char prefix[96];
|
char prefix[96];
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ closing this presentation-only menu never sends a command or changes server
|
|||||||
state. Escape and outside activation dismiss it; player actions also close it
|
state. Escape and outside activation dismiss it; player actions also close it
|
||||||
before opening their existing destination.
|
before opening their existing destination.
|
||||||
|
|
||||||
|
Authenticated sessions also see `Выйти из игры`. Its confirmation states that
|
||||||
|
a player exit from any non-waiting phase, including the result screen, returns
|
||||||
|
the remaining participants to the waiting lobby; observer exits and waiting
|
||||||
|
lobby player exits release only the departing browser's place. The browser
|
||||||
|
removes its token only after the correlated server acknowledgement.
|
||||||
|
|
||||||
The header response indicator takes at most one heartbeat sample every five
|
The header response indicator takes at most one heartbeat sample every five
|
||||||
seconds, holds one in-flight request, and uses a five-sample median. Good is
|
seconds, holds one in-flight request, and uses a five-sample median. Good is
|
||||||
at most 150 ms, delayed is 151--500 ms, and slow is above 500 ms. Hysteresis
|
at most 150 ms, delayed is 151--500 ms, and slow is above 500 ms. Hysteresis
|
||||||
|
|||||||
+2
-1
@@ -40,7 +40,8 @@ is only echoed for correlation; it grants no authority.
|
|||||||
players must be connected. Sessions are preserved while a new authoritative
|
players must be connected. Sessions are preserved while a new authoritative
|
||||||
maze and game state are created.
|
maze and game state are created.
|
||||||
- `leave`: `{}`. Revokes only the authenticated sending session. A player
|
- `leave`: `{}`. Revokes only the authenticated sending session. A player
|
||||||
leaving an unfinished match cancels it for all participants; observer and
|
leaving any non-waiting match phase, including a finished result, cancels
|
||||||
|
that match and returns remaining sessions to the waiting lobby; observer and
|
||||||
waiting-lobby departures release only that session.
|
waiting-lobby departures release only that session.
|
||||||
- `heartbeat`: `{}` after a session has been joined or restored.
|
- `heartbeat`: `{}` after a session has been joined or restored.
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -107,9 +107,9 @@ are not MVP requirements.
|
|||||||
safe areas, closes on outside activation or Escape, and never contains
|
safe areas, closes on outside activation or Escape, and never contains
|
||||||
primary game, lobby, observer, or result actions.
|
primary game, lobby, observer, or result actions.
|
||||||
- The settings popover shows `Выйти из игры` only for an authenticated session.
|
- The settings popover shows `Выйти из игры` only for an authenticated session.
|
||||||
It opens a confirmation modal; an unfinished player match warns that it ends
|
It opens a confirmation modal; a player match, including a finished result,
|
||||||
for everyone, while observer and lobby departures explain that only this
|
warns that it returns everyone to the waiting lobby, while observer and
|
||||||
browser's place is released.
|
waiting-lobby departures explain that only this browser's place is released.
|
||||||
- A compact response indicator between the connection state and gear shows a
|
- A compact response indicator between the connection state and gear shows a
|
||||||
rounded total response time or an explicit unknown, stale, or offline state.
|
rounded total response time or an explicit unknown, stale, or offline state.
|
||||||
Its accessible description and settings details expose `Отклик`, `Связь`,
|
Its accessible description and settings details expose `Отклик`, `Связь`,
|
||||||
|
|||||||
@@ -39,8 +39,11 @@ static void test_protocol_schema(void) {
|
|||||||
const char* restart = "{\"v\":1,\"type\":\"restart\",\"seq\":5,\"payload\":{}}";
|
const char* restart = "{\"v\":1,\"type\":\"restart\",\"seq\":5,\"payload\":{}}";
|
||||||
assert(maze32_protocol_parse(restart, strlen(restart), &message) == MAZE32_PROTOCOL_OK);
|
assert(maze32_protocol_parse(restart, strlen(restart), &message) == MAZE32_PROTOCOL_OK);
|
||||||
assert(message.type == MAZE32_MESSAGE_RESTART);
|
assert(message.type == MAZE32_MESSAGE_RESTART);
|
||||||
|
const char* leave = "{\"v\":1,\"type\":\"leave\",\"seq\":6,\"payload\":{}}";
|
||||||
|
assert(maze32_protocol_parse(leave, strlen(leave), &message) == MAZE32_PROTOCOL_OK);
|
||||||
|
assert(message.type == MAZE32_MESSAGE_LEAVE);
|
||||||
const char* diagonal =
|
const char* diagonal =
|
||||||
"{\"v\":1,\"type\":\"move\",\"seq\":6,\"payload\":{\"direction\":\"northwest\"}}";
|
"{\"v\":1,\"type\":\"move\",\"seq\":7,\"payload\":{\"direction\":\"northwest\"}}";
|
||||||
assert(maze32_protocol_parse(diagonal, strlen(diagonal), &message) ==
|
assert(maze32_protocol_parse(diagonal, strlen(diagonal), &message) ==
|
||||||
MAZE32_PROTOCOL_INVALID_PAYLOAD);
|
MAZE32_PROTOCOL_INVALID_PAYLOAD);
|
||||||
|
|
||||||
@@ -129,9 +132,52 @@ static void test_lobby_limits_restoration_and_authority(void) {
|
|||||||
puts("lobby repeated-match reset: PASS (128 cycles)");
|
puts("lobby repeated-match reset: PASS (128 cycles)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_explicit_leave_releases_only_departing_session(void) {
|
||||||
|
maze32_lobby_t lobby;
|
||||||
|
maze32_lobby_init(&lobby);
|
||||||
|
maze32_session_t* player_1 = NULL;
|
||||||
|
maze32_session_t* player_2 = NULL;
|
||||||
|
maze32_session_t* observer = NULL;
|
||||||
|
assert(maze32_lobby_join_player(&lobby, 10, "Первый", TOKEN_1, &player_1) ==
|
||||||
|
MAZE32_LOBBY_OK);
|
||||||
|
assert(maze32_lobby_join_player(&lobby, 11, "Второй", TOKEN_2, &player_2) ==
|
||||||
|
MAZE32_LOBBY_OK);
|
||||||
|
assert(maze32_lobby_join_observer(&lobby, 20, OBSERVER_TOKENS[0], &observer) ==
|
||||||
|
MAZE32_LOBBY_OK);
|
||||||
|
|
||||||
|
maze32_role_t role = MAZE32_ROLE_NONE;
|
||||||
|
unsigned int player_slot = 0U;
|
||||||
|
assert(maze32_lobby_leave(&lobby, 10, &role, &player_slot));
|
||||||
|
assert(role == MAZE32_ROLE_PLAYER && player_slot == 1U);
|
||||||
|
assert(maze32_lobby_session_for_connection(&lobby, 10) == NULL);
|
||||||
|
assert(maze32_lobby_session_for_connection(&lobby, 11) == player_2);
|
||||||
|
assert(maze32_lobby_session_for_connection(&lobby, 20) == observer);
|
||||||
|
assert(maze32_lobby_restore(&lobby, 30, TOKEN_1, &player_1) == MAZE32_LOBBY_INVALID_TOKEN);
|
||||||
|
assert(maze32_lobby_join_player(&lobby, 30, "Новый первый", OBSERVER_TOKENS[1], &player_1) ==
|
||||||
|
MAZE32_LOBBY_OK);
|
||||||
|
assert(player_1->player_slot == 1U);
|
||||||
|
assert(!maze32_lobby_leave(&lobby, 10, NULL, NULL));
|
||||||
|
|
||||||
|
assert(maze32_lobby_leave(&lobby, 11, &role, &player_slot));
|
||||||
|
assert(role == MAZE32_ROLE_PLAYER && player_slot == 2U);
|
||||||
|
assert(maze32_lobby_session_for_connection(&lobby, 30) == player_1);
|
||||||
|
assert(maze32_lobby_session_for_connection(&lobby, 20) == observer);
|
||||||
|
assert(maze32_lobby_restore(&lobby, 31, TOKEN_2, &player_2) == MAZE32_LOBBY_INVALID_TOKEN);
|
||||||
|
assert(maze32_lobby_join_player(&lobby, 31, "Новый второй", OBSERVER_TOKENS[2], &player_2) ==
|
||||||
|
MAZE32_LOBBY_OK);
|
||||||
|
assert(player_2->player_slot == 2U);
|
||||||
|
|
||||||
|
assert(maze32_lobby_leave(&lobby, 20, &role, &player_slot));
|
||||||
|
assert(role == MAZE32_ROLE_OBSERVER && player_slot == 0U);
|
||||||
|
assert(maze32_lobby_session_for_connection(&lobby, 30) == player_1);
|
||||||
|
assert(maze32_lobby_session_for_connection(&lobby, 31) == player_2);
|
||||||
|
puts("explicit leave session revocation and slot reuse: PASS");
|
||||||
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
test_protocol_schema();
|
test_protocol_schema();
|
||||||
test_lobby_limits_restoration_and_authority();
|
test_lobby_limits_restoration_and_authority();
|
||||||
|
test_explicit_leave_releases_only_departing_session();
|
||||||
puts("lobby and protocol tests: PASS");
|
puts("lobby and protocol tests: PASS");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -490,3 +490,20 @@ test("finished matches expose only an owner restart intention", () => {
|
|||||||
assert.match(script, /message\.type === "reset"/);
|
assert.match(script, /message\.type === "reset"/);
|
||||||
assert.doesNotMatch(script, /restart[^\n]*(position|inventory|result):/);
|
assert.doesNotMatch(script, /restart[^\n]*(position|inventory|result):/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("finished-player exit returns remaining browsers to the waiting lobby", () => {
|
||||||
|
const html = fs.readFileSync(path.join(project, "web/index.html"), "utf8");
|
||||||
|
const script = fs.readFileSync(path.join(project, "web/player.js"), "utf8");
|
||||||
|
const gateway = fs.readFileSync(path.join(project, "components/maze32_web/maze32_gateway.c"), "utf8");
|
||||||
|
const leaveHandler = gateway.match(/if \(message->type == MAZE32_MESSAGE_LEAVE\) \{[\s\S]*?if \(message->type == MAZE32_MESSAGE_START\)/)[0];
|
||||||
|
|
||||||
|
assert.match(html, /id="settings-leave"/);
|
||||||
|
assert.match(html, /id="leave-confirmation"/);
|
||||||
|
assert.match(script, /\["countdown", "active", "finish_pending", "paused", "finished"\]\.includes\(phase\)/);
|
||||||
|
assert.match(script, /message\.type === "reset"[\s\S]*?hideModal\(\); setScreen\("lobby"\)/);
|
||||||
|
assert.match(script, /message\.payload\.command === "leave" && message\.requestId === state\.leaveRequest/);
|
||||||
|
assert.match(script, /windowObject\.localStorage\.removeItem\(TOKEN_KEY\)/);
|
||||||
|
assert.match(script, /setScreen\("welcome"\)/);
|
||||||
|
assert.match(leaveHandler, /leaving_role == MAZE32_ROLE_PLAYER &&\s*gateway->lobby\.phase != MAZE32_LOBBY_WAITING;/);
|
||||||
|
assert.doesNotMatch(leaveHandler, /gateway->game\.phase != MAZE32_GAME_PHASE_FINISHED/);
|
||||||
|
});
|
||||||
|
|||||||
+2
-2
@@ -497,9 +497,9 @@
|
|||||||
closeSettings(false);
|
closeSettings(false);
|
||||||
const phase = state.snapshot ? state.snapshot.phase : state.lobby && state.lobby.phase;
|
const phase = state.snapshot ? state.snapshot.phase : state.lobby && state.lobby.phase;
|
||||||
const endsMatch = state.session && state.session.role === "player" &&
|
const endsMatch = state.session && state.session.role === "player" &&
|
||||||
["countdown", "active", "finish_pending", "paused"].includes(phase);
|
["countdown", "active", "finish_pending", "paused", "finished"].includes(phase);
|
||||||
byId("leave-text").textContent = endsMatch
|
byId("leave-text").textContent = endsMatch
|
||||||
? "Текущая гонка закончится для всех, а ваш слот сразу освободится."
|
? "Все вернутся в лобби, а ваш слот сразу освободится."
|
||||||
: "Освободим только ваше место в игре.";
|
: "Освободим только ваше место в игре.";
|
||||||
byId("leave-confirmation").hidden = false;
|
byId("leave-confirmation").hidden = false;
|
||||||
byId("leave-accept").focus();
|
byId("leave-accept").focus();
|
||||||
|
|||||||
Reference in New Issue
Block a user