feat(session): add explicit leave and immediate slot release

This commit is contained in:
2026-09-13 22:53:18 +03:00
parent 44433aee9c
commit b6d484dbc9
16 changed files with 526 additions and 13 deletions
+203 -3
View File
@@ -3265,7 +3265,7 @@ indicator.
## Milestone 029 — Low-overhead live connection quality
**Status:** `NOT_STARTED`
**Status:** `COMPLETE`
### Objective
@@ -3412,5 +3412,205 @@ resources.
measured ESP handling interval and an honestly labelled communication
remainder, while keeping the displayed headline value equal to the complete
browser-to-ESP application round trip.
- Implementation and verification have not started, as requested by the
project owner.
- On 2026-09-13, implementation began with a single in-flight browser
heartbeat sampled no more often than every five seconds, a five-sample
median, 15-second stale timeout, and explicit unknown, stale, and offline
indicator states. Heartbeat acknowledgements now include bounded ESP-only
`handlingMs`, and the browser derives the non-negative `Связь` remainder.
- On 2026-09-13, `node --test test/web/test_player_app.js
test/web/test_hedge_svg.js` passed all 33 tests, including bounded
heartbeat correlation, malformed-field rejection, smoothing, stale state,
indicator copy, and compact header placement.
- On 2026-09-13, `bash scripts/run_host_tests.sh` passed. `python3
scripts/verify_web_assets.py` passed with a deterministic 32,503 /
1,572,864 byte compressed payload. `/home/sasa/.platformio/penv/bin/pio
run` passed with firmware at 1,029,808 / 2,097,152 bytes and
`/home/sasa/.platformio/penv/bin/pio run --target buildfs` passed with a
2,031,616 / 2,031,616 byte LittleFS image.
- Physical-device latency measurements and mobile and desktop visual
acceptance were pending until the project-owner check below.
- On 2026-09-13, the project owner manually checked the quality indicator and
accepted `ESP: 0 мс` as the correct rounded display for the measured
sub-millisecond heartbeat handling interval. The project owner found no UI
issue and accepted the indicator on the available application screens.
## Milestone 030 — Explicit session exit and slot release
**Status:** `IN_PROGRESS`
### Objective
Let an authenticated player or observer deliberately leave through the
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
prefilled. A player leaving an unfinished match 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.
### Scope
- Do not start implementation until every preceding milestone is `COMPLETE`.
Milestone 029 is `COMPLETE` when this plan is created; this milestone remains
`NOT_STARTED` because implementation was not requested.
- Add one clearly labelled Russian `Выйти из игры` action to the existing
gear-triggered settings menu. Show it only while the browser owns an
authenticated player or observer session; do not show it on the unauthenticated
welcome and name-entry screen.
- Require a modal confirmation before sending an exit command. For a player in
countdown, active, finish-pending, or paused state, clearly warn that the
current match will end for everyone and the player's slot will be freed. For
a player in the lobby or an observer in any phase, explain that only this
browser's place will be released.
- Treat opening or cancelling the confirmation as browser-local UI only. While
it is open, suppress input from that browser without pausing or otherwise
mutating authoritative state; cancelling must restore the previous screen,
focus, controls, and session unchanged.
- Introduce a versioned authenticated explicit-leave protocol command and a
correlated success response. It must be distinct from an unexpected socket
disconnect, page reload, or transient connection loss, which retain the
existing 60-second player reconnection contract.
- On an accepted player leave during countdown, active, finish-pending, or
paused state, stop all match timers, clear the current authoritative match,
return the lobby to its waiting state, retain every other connected session
and role, revoke the leaving session, free its player slot immediately, and
broadcast the cancellation and fresh lobby state to the remaining players
and observers.
- On an accepted player leave while already in the waiting lobby or after a
completed result, revoke only that player's session and free its slot. Do not
reset an unfinished match because none is active in those phases.
- 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
otherwise mutate the match because an observer leaves.
- Preserve the roles and sessions of all participants who did not leave. If
Player 1 leaves, Player 2 remains in its existing slot and a later player may
claim the newly free Player 1 slot and become the lobby owner under the
existing ownership rule.
- Invalidate the departed session token on the ESP before confirming success.
The revoked token must never restore its former role, slot, position,
inventory, or reconnect reservation, and a duplicate or replayed leave must
be harmless.
- After confirmed success, remove the session token from browser-local storage,
clear all session, match, input, timer, heartbeat, animation, and pending
command state, and show the welcome/name-entry screen without requiring a
page reload.
- Store the last successfully used display name separately from the temporary
session token in a bounded browser-local value. Prefill it on return to the
name-entry screen, but still apply normal name validation and duplicate-name
rejection if the user tries to join again.
- Do not claim that the slot was released before the ESP acknowledges the
explicit leave. If the command cannot be delivered or confirmed, show a
concise Russian error and retain honest reconnect behavior rather than
silently turning a network failure into a successful exit.
- Bound command size, correlation state, acknowledgement waiting, and cleanup.
The leave path must not disclose hidden maze state, permit one participant to
revoke another session, bypass observer read-only gameplay rules, or create
a new unbounded queue or retry loop.
- Preserve the compact header, live connection-quality indicator, Wi-Fi and
preference actions, responsive layouts, offline operation, Russian
player-facing copy, and firmware and LittleFS budgets.
### Deliverables
- Updated explicit-leave, match-cancellation, slot-release, session-revocation,
and reconnect contracts in `MVP.md`, `ARCHITECTURE.md`, `docs/PROTOCOL.md`,
`docs/UI_STYLE.md`, and `docs/PLAYER_UI.md`.
- A bounded authenticated leave command and response implemented across the
protocol parser, lobby, authoritative game lifecycle, gateway, and browser.
- A settings-menu exit action with role- and phase-appropriate confirmation,
failure handling, focus restoration, and successful return to the
name-entry screen.
- Separate bounded persistence for the last player display name and complete
cleanup of the revoked temporary session.
- Deterministic protocol, lobby, gateway, game-lifecycle, browser,
accessibility, race, replay, reconnect, responsive-layout, and regression
tests.
- Host, browser, web-asset, firmware, LittleFS, maximum-session, and
physical-device verification evidence.
### Acceptance criteria
1. An authenticated player or observer sees exactly one `Выйти из игры` item
in the gear menu on every applicable screen. An unauthenticated browser sees
no exit item, and primary game controls remain outside the menu.
2. Selecting the item opens a keyboard- and touch-accessible confirmation
modal and sends no protocol command until explicit confirmation. Cancelling,
pressing Escape, or activating the modal's cancel action leaves the session
and authoritative state unchanged and safely restores focus.
3. A player confirmation during countdown, active, finish-pending, or paused
state explicitly says that the match will end for everyone and the slot will
be freed. Confirmation produces exactly one authoritative cancellation,
stops every relevant timer, clears the current 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
the same accepted server transition. Every other player's and observer's
session, role, and connection remain valid, and the resulting lobby snapshot
consistently shows the newly free slot.
5. A player leaving the waiting lobby or a completed result releases only that
player's session and slot. It does not disconnect other participants or
perform an additional match reset.
6. An observer leaving during any phase releases only that observer session and
observer slot. Match phase, players, timers, maze, objects, inventory, and
results remain byte-for-byte unchanged except for the expected lobby
connection revision or participant count.
7. The explicit leave command authenticates the sending session and cannot
name or revoke another token or slot. Duplicate, stale, reordered, malformed,
unauthorized, and replayed requests cause no second cancellation, no extra
slot release, and no unrelated state mutation.
8. An accepted explicit leave never starts the 60-second reconnect reservation.
A normal page reload, accidental socket loss, or unconfirmed exit continues
to follow the existing reconnect and pause rules without behavioral change.
9. Only after the correlated success response, the departing browser deletes
its temporary session token, clears all role and gameplay state, stops all
input, animation, heartbeat, reconnect, and pending-command work, and shows
the welcome/name-entry screen without a full reload or stale map flash.
10. The last successfully used display name survives separately from the
revoked token and is prefilled in the name field after exit and on a later
visit. It is bounded to the existing name limit, contains no token data,
and receives the normal validation and uniqueness checks when reused.
11. If the leave request cannot be sent, times out, loses its socket, or receives
a rejection, the browser does not report a successful release. It presents
concise Russian recovery information, retains bounded state, and neither
retries indefinitely nor corrupts a later reconnect or successful leave.
12. If Player 1 leaves, Player 2 remains in Player 2's slot and a new player can
immediately claim the free Player 1 slot, becoming the lobby owner and
gaining start authority only through the existing server rule. Equivalent
behavior applies when Player 2 leaves.
13. The exit item and confirmation fit supported narrow portrait,
compact-landscape, tablet, and desktop layouts, respect safe areas, avoid
page and map overflow, and remain distinguishable without relying only on
color, sound, vibration, or animation.
14. Automated tests cover every role and phase, confirmation and cancellation,
authoritative match cancellation, retained participants, immediate slot
reuse, token revocation, remembered-name prefill, disconnect races,
idempotence, authorization, cleanup, accessibility, and responsive layout.
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;
and firmware remains within the factory partition.
16. Physical-device testing confirms that a player can cancel an unfinished
match and immediately release its slot, an observer can leave without
affecting play, the remaining browsers receive the correct lobby state,
and the departing browser returns to a prefilled name-entry screen without
stale controls, delayed input, or reconnect side effects.
17. The project owner confirms on representative mobile and desktop browsers
that the exit action is easy to find but difficult to trigger accidentally,
its consequences are clear, and all role-specific outcomes match the
intended behavior, or explicitly approves a documented evidence deviation.
### Evidence
- On 2026-09-13, planning inspection confirmed that unexpected player socket
loss currently pauses an active match and reserves the player session for a
60-second reconnect, while observer disconnect does not affect gameplay.
Therefore immediate voluntary slot release requires an explicit authenticated
lifecycle command rather than client-only token deletion or socket closure.
- On 2026-09-13, the project owner required a settings-menu exit action with a
confirmation modal, complete session-token revocation, immediate slot
release, and return to the name-entry screen with the previous player name
prefilled.
- The project owner also required an unfinished match to be cancelled for all
participants when a player leaves, while a lobby player or observer departure
must affect only the departing session.
- On 2026-09-13, implementation began with an authenticated `leave` command,
immediate server-side session revocation, player-match cancellation, and a
browser confirmation flow that clears the temporary token only after the
correlated acknowledgement.