feat(observer): add observer mode and disconnect recovery

This commit is contained in:
2026-09-05 21:19:52 +03:00
parent d042c3ce90
commit ffdaa299bc
23 changed files with 988 additions and 43 deletions
+20 -4
View File
@@ -1,9 +1,8 @@
# Authoritative game core
Milestones 006 and 007 provide the portable movement engine, gameplay object
systems, terminal results, and view projector. The ESP remains the only owner
of mutable game state. Reconnect behavior and browser controls remain out of
scope until their later milestones.
Milestones 006, 007, and 009 provide the portable movement engine, gameplay
object systems, terminal results, view projector, and disconnect recovery. The
ESP remains the only owner of mutable game state.
## Command processing
@@ -87,3 +86,20 @@ compass hint, and own-exit status. An opponent's carried key is included only
while that opponent is currently visible. Observer snapshots contain both
inventories and effect counters, both exit states and authoritative exit
timestamps, every remaining ground object, and the public match result.
## Disconnect recovery
Both players start connected. Disconnecting either player during `active` or
`finish_pending` stores that exact phase, clears queued commands, enters
`paused`, and assigns the disconnected player a monotonic deadline 60,000 ms
after detection. Commands processed during the pause are rejected before any
position, inventory, effect counter, or result can change.
If the disconnect occurs during `finish_pending`, the remaining 200 ms result
window is preserved and continues only after both players reconnect.
Restoring the reserved player session before the deadline reconnects the same
player record. The stored phase resumes only when both player records are
connected, so a second disconnected player cannot be bypassed. Reaching a
disconnected player's deadline ends the match with the authoritative `draw`
result and clears queued commands. Observer connection state never enters this
state machine.
+37
View File
@@ -0,0 +1,37 @@
# Observer browser mode
Milestone 009 adds a password-free observer role to the same local browser
application. The role is still authenticated by its temporary session token,
but joining it does not request a player name or consume either player slot.
At most four observer sessions are connected at once.
## Projection and authorization
An observer renders only the server's `observer` view projection. That
projection contains all maze cells, remaining objects, both player positions,
inventories, effects, exit state, phase, and result. The browser does not
simulate movements or infer updates. Each player status also reflects the
server-owned WebSocket connection state. A complete projection establishes the
server-approved static geometry on join, restoration, and match start.
Subsequent bounded projections replace all dynamic player and object state
while reusing only that authoritative geometry, avoiding an 8,192-cell
retransmission after every move.
The observer screen contains no movement, start, or drop-key control. This is
only presentation-level defense: the gateway also rejects and logs observer
`start`, `move`, and `drop_key` attempts before they reach authoritative
mutation. Disconnecting an observer changes only lobby presence and never
pauses the game.
## Viewport and navigation
At `1024 x 768` CSS pixels and above, the complete `128 x 64` default map is
initially scaled to fit the available stage. The observer can zoom with the
labelled buttons or mouse wheel and pan with pointer dragging. Zoom is bounded
between 0.25 and 6 times the eight-pixel base cell size; the **Вся карта**
control restores the calculated fit and centered position.
When either viewport dimension is below the supported minimum, the map and
controls are hidden and the Russian message **Нужен экран побольше** explains
the `1024 x 768` requirement. All assets remain local and use the shared
retro-arcade visual language.
+3
View File
@@ -57,3 +57,6 @@ Russian; protocol identifiers remain English.
Sound and supported vibration are enabled after the first pointer or keyboard
interaction. Both are progressive enhancements: unsupported devices continue
without an error.
Observer presentation and its read-only interaction boundary are documented in
`OBSERVER_UI.md`.
+19 -6
View File
@@ -33,10 +33,12 @@ is only echoed for correlation; it grants no authority.
- `suggest_name`: `{}`.
- `start`: `{}`. Only connected Player 1 may use it, and both player slots must
be connected.
- `move`: `{"direction":"north|east|south|west"}`. Only a player in an active,
unpaused match may submit movement intent.
- `drop_key`: `{}`. Only a player in an active, unpaused match may submit the
key-drop intent.
- `heartbeat`: `{}` after a session has been joined or restored.
No Milestone 005 maze or movement messages are implemented by this milestone.
## Server messages
- `session`: the private role result containing `role`, `slot`, and temporary
@@ -47,10 +49,21 @@ No Milestone 005 maze or movement messages are implemented by this milestone.
- `countdown`: the single authoritative `[3,2,1]` event with common monotonic
`startedAtMs` and `activationAtMs` values. It is sent to both players.
- `active`: the exact authoritative activation instant.
- `view`: a bounded chunk of a role-specific authoritative game projection.
Observer chunks contain the complete maze and both player records, including
their connection state; player chunks retain the fog-of-war boundary.
- `pause`: identifies the disconnected player slot and its monotonic reconnect
deadline.
- `resume`: reports that both players are attached and authoritative gameplay
has resumed.
- `match_result`: reports the terminal winner or draw.
- `ack`: successful heartbeat correlation.
- `error`: a stable English `code` and Russian player-facing `message`.
An observer session can receive snapshots but cannot issue `start`. Player
tokens reserve their original slot after disconnection so a refresh can restore
it. A disconnected observer token may be restored until its bounded slot is
reused by a new observer.
An observer session can receive snapshots but cannot issue `start`, `move`, or
`drop_key`; attempts are rejected with `read_only` where applicable and logged
by the gateway. Player tokens reserve their original slot after disconnection
so a refresh can restore it. A player disconnect pauses the game for at most 60
seconds; restoration reattaches the same state, while expiry produces a draw.
A disconnected observer token may be restored until its bounded slot is reused
by a new observer, and observer disconnects never pause the match.