pub enum ServerMessage {
Show 56 variants
Hello {
heartbeat_interval_ms: u32,
server_version: Option<String>,
},
Ready {
session_id: String,
player_id: i64,
lobby: Option<LobbySnapshot>,
game: Option<GameSnapshot>,
},
Resumed {
missed_events: Vec<ServerMessage>,
},
HeartbeatAck {
server_time: u64,
},
InvalidSession {
reason: String,
},
LobbyJoined {
lobby_id: String,
lobby_code: Option<String>,
lobby: LobbySnapshot,
},
LobbySnapshot {
lobby: LobbySnapshot,
},
LobbyDelta {
changes: Vec<LobbyChange>,
},
LobbyLeft,
CustomLobbyCreated {
lobby_id: String,
lobby_code: String,
},
GameStarted {
game_id: String,
game_type: GameType,
grid: Grid,
players: Vec<GamePlayerInfo>,
your_turn_order: u8,
current_turn: i64,
round: u8,
max_rounds: u8,
turn_time_limit: Option<u32>,
custom: Option<CustomMeta>,
time_remaining_ms: Option<u64>,
},
GameSnapshot {
game_id: String,
game: GameSnapshot,
},
GameDelta {
game_id: String,
changes: Vec<GameChange>,
},
GameOver {
game_id: String,
final_scores: Vec<ScoreInfo>,
winner_id: i64,
is_draw: bool,
},
GameCancelled {
game_id: String,
reason: String,
},
AdventureLevelResult {
level: u32,
score: i32,
stars: u8,
personal_best: bool,
unlocked_level: Option<u32>,
duration_ms: Option<u64>,
},
DailyResult {
seed: i64,
score: i32,
personal_best: i32,
rank: u32,
attempts_used: u8,
attempts_max: u8,
},
AdventureEvent {
game_id: String,
kind: AdventureEventKind,
affected_positions: Vec<Position>,
new_grid: Grid,
label: String,
},
PlayerJoined {
player: LobbyPlayerInfo,
},
PlayerLeft {
player_id: i64,
reason: Option<String>,
},
PlayerReconnected {
player_id: i64,
},
PlayerDisconnected {
game_id: Option<String>,
player_id: i64,
grace_period_seconds: u32,
},
WordScored {
player_id: i64,
game_id: String,
word: String,
score: i32,
path: Vec<Position>,
total_score: i32,
gems_earned: i32,
total_gems: i32,
new_grid: Grid,
},
RuneBoxWordAccepted {
game_id: String,
word: String,
letters: i32,
total_score: i32,
},
RuneBoxScoreChanged {
player_id: i64,
game_id: String,
total_score: i32,
},
RuneBoxRoundEnded {
game_id: String,
round: u8,
max_rounds: u8,
round_scores: Vec<ScoreInfo>,
eliminated: Vec<i64>,
standings: Vec<RuneBoxStanding>,
next_round_starts_at: Option<DateTime<Utc>>,
},
TurnChanged {
player_id: i64,
game_id: String,
round: u8,
time_remaining: Option<u32>,
},
TurnPassed {
player_id: i64,
game_id: String,
},
RoundChanged {
game_id: String,
round: u8,
max_rounds: u8,
new_grid: Option<Grid>,
},
BoardShuffled {
player_id: i64,
game_id: String,
new_grid: Grid,
gems_spent: i32,
total_gems: i32,
},
TileSwapped {
player_id: i64,
game_id: String,
row: usize,
col: usize,
old_letter: char,
new_letter: char,
gems_spent: i32,
total_gems: i32,
},
SwapModeEntered {
player_id: i64,
game_id: String,
},
SwapModeExited {
player_id: i64,
game_id: String,
},
SpectatorJoined {
game_id: String,
game: GameSnapshot,
},
SpectatorAdded {
spectator: SpectatorInfo,
game_id: String,
},
SpectatorRemoved {
spectator_id: i64,
game_id: String,
},
SpectatorBecamePlayer {
player_id: i64,
username: String,
name: Option<PlayerName>,
game_id: String,
},
SpectatorLeft,
SelectionUpdate {
player_id: i64,
game_id: String,
positions: Vec<Position>,
},
TimerVoteUpdate {
state: TimerVoteState,
game_id: String,
},
TurnTimerStarted {
target_player_id: i64,
game_id: String,
seconds: u32,
},
TurnTimerExpired {
player_id: i64,
game_id: String,
},
RematchCountdownUpdate {
state: RematchCountdownState,
previous_game_id: String,
},
PlayerLeftRematch {
player_id: i64,
previous_game_id: String,
},
RematchStarting {
triggered_by: Option<i64>,
previous_game_id: String,
},
PlayerPoolChanged {
player_id: i64,
old_pool: Option<GameType>,
new_pool: Option<GameType>,
},
PoolJoined {
position: i32,
total_in_pool: i32,
game_id: String,
},
PoolUpdate {
position: i32,
total_in_pool: i32,
game_id: String,
},
PoolLeft,
AdminGamesList {
games: Vec<AdminGameInfo>,
},
AdminGameDeleted {
game_id: String,
},
GameStateUpdate {
game_id: String,
state: String,
grid: Grid,
players: Vec<PlayerInfo>,
current_turn: i64,
round: i32,
max_rounds: i32,
used_words: Vec<String>,
spectators: Vec<SpectatorInfo>,
timer_vote_state: TimerVoteState,
},
LobbyStateUpdate {
lobby_id: String,
players: Vec<LobbyPlayerInfo>,
games: Vec<LobbyGameInfo>,
},
DebugStateResponse {
timestamp: String,
player: DebugPlayerInfo,
websocket_context: DebugWebsocketContext,
lobby_state: Option<DebugLobbyState>,
backend_game_state: Option<DebugBackendGameState>,
handler_game_state: Option<DebugHandlerGameState>,
},
NewsAnnounced {
item: NewsItemPayload,
},
Error {
code: ErrorCode,
message: String,
details: Option<Value>,
},
}Expand description
Messages sent from server to client.
Variants§
Hello
Initial server greeting after WebSocket connect.
Sent immediately upon connection, before Identify.
Fields
Ready
Successful authentication response.
Contains the full initial state snapshot.
Fields
lobby: Option<LobbySnapshot>Full lobby state (if in a lobby)
game: Option<GameSnapshot>Full game state (if in a game)
Resumed
Session resumed successfully after reconnect.
Contains any events missed during disconnection.
Fields
missed_events: Vec<ServerMessage>Events that occurred while disconnected
HeartbeatAck
Heartbeat response.
Echoes back for latency calculation.
InvalidSession
Session is invalid or expired.
Client should re-authenticate.
LobbyJoined
Sent when successfully joining a lobby.
Fields
lobby: LobbySnapshotFull lobby state
LobbySnapshot
Full lobby state snapshot.
Sent on initial join or when delta sync fails.
Fields
lobby: LobbySnapshotLobbyDelta
Incremental lobby state update.
More efficient than full snapshots for small changes.
Fields
changes: Vec<LobbyChange>LobbyLeft
Confirmation of leaving lobby.
CustomLobbyCreated
Custom lobby was created successfully.
GameStarted
A new game has started.
Contains initial game state for all participants.
Fields
game_type: GameTypeWhich mode this game is — the same field, and the same reasoning, as
GameSnapshot::game_type.
v0.18.0 put the mode on the snapshot and stopped there, which left the JOIN path still inferring it. That is the worse half to leave undone: a snapshot is a reconnect/spectate message, so a player who joins normally never receives one, and every bug that has hidden in this mode so far hid on exactly this path.
players: Vec<GamePlayerInfo>custom: Option<CustomMeta>Some marks this as an unranked custom (sandbox) game so every
participant — not just the host — can render the unranked badge.
Omitted for FFA/Adventure (wire format unchanged).
GameSnapshot
Full game state snapshot.
Sent when joining as spectator or when delta sync fails.
GameDelta
Incremental game state update.
GameOver
Game has ended normally.
Fields
GameCancelled
Game was cancelled (not enough players, host left, etc.).
AdventureLevelResult
Adventure Mode level ended. Sent after GameOver for sessions that
had adventure_level.is_some() in their config. The client uses
this to render the level-complete modal; the server has already
upserted the row in adventure_progress by the time this is sent.
Fields
unlocked_level: Option<u32>If this run unlocked a new level, the newly-unlocked level id.
None if no new level was unlocked (level was already completed
or was the campaign finale).
duration_ms: Option<u64>This run’s duration in milliseconds (from game creation to
game_over). Lets the client render “Time: 2:34” in the result
modal without fetching progress. Unsigned since durations
are non-negative, and consistent with other duration /
interval fields in the protocol (heartbeat_interval_ms,
server_time).
DailyResult
Daily Challenge run finished (deadline reached or player ended early). The server has already recorded the attempt + move log by send time.
Fields
AdventureEvent
Adventure Mode random event (bomb / snake / UFO). Broadcast by
the event scheduler when a roll succeeds at round change.
affected_positions is the set of grid cells the client should
animate; new_grid is the post-effect board so the client can
swap in the updated tiles atomically with the animation.
Fields
kind: AdventureEventKindPlayerJoined
A player joined the lobby.
Fields
player: LobbyPlayerInfoPlayerLeft
A player left the lobby.
PlayerReconnected
A player reconnected after disconnection.
PlayerDisconnected
A player disconnected (may reconnect).
Fields
WordScored
A word was successfully scored.
Fields
RuneBoxWordAccepted
RuneBox: a word was accepted — sent to the submitting player only.
Deliberately not a reuse of ServerMessage::WordScored. That variant
carries gems and a full new_grid, neither of which exists in RuneBox
(the board is static and there are no gems), so both would be dead weight
re-sent on every word — and its word field would leak to opponents.
Fields
RuneBoxScoreChanged
RuneBox: another player’s score moved — broadcast to everyone else.
Carries no word, and must never gain one. RuneBox found-sets are independent, so a word another player found is still worth full value to you; naming it would hand you the answer. Opponents see the score move and nothing more.
Fields
RuneBoxRoundEnded
RuneBox: a round ended and the match continues.
Deliberately distinct from ServerMessage::GameOver, which keeps its
existing meaning: the MATCH is over, no further play. That separation is
what makes this addition non-breaking — every other mode plays exactly
one round, so it only ever sends GameOver and never sees this message.
A client that receives this should present standings and wait, NOT tear the game screen down.
Carries no word lists, and must never gain any: the anti-leak rule that
splits ServerMessage::RuneBoxWordAccepted from
ServerMessage::RuneBoxScoreChanged applies just as much between
rounds, because an eliminated player may still be watching.
Fields
max_rounds: u8How many rounds this match will play at most. N - 1 for an
elimination match of N players; 3 for a two-player best-of-3, which
can still finish in 2.
round_scores: Vec<ScoreInfo>THIS round’s scores alone, highest first. Not cumulative — the
running match total lives on standings.
eliminated: Vec<i64>Players eliminated by this round’s result.
Empty in a two-player best-of-3, where nobody is eliminated until the match itself ends. Normally one id, but the field is a list because the rules do not forbid a round that eliminates more than one player.
standings: Vec<RuneBoxStanding>Every player’s match standing after this round — survivors and eliminated alike, so a client renders the whole picture without accumulating round results itself. A client that missed a round (reconnect) is therefore still correct.
TurnChanged
Turn changed to another player.
Fields
TurnPassed
A player passed their turn.
RoundChanged
Round number changed.
Fields
BoardShuffled
Board was shuffled.
Fields
TileSwapped
A tile was swapped.
Fields
SwapModeEntered
Player entered swap mode (for animation).
SwapModeExited
Player exited swap mode.
SpectatorJoined
Successfully joined as spectator.
SpectatorAdded
A new spectator joined (broadcast to others).
SpectatorRemoved
A spectator left.
SpectatorBecamePlayer
Spectator joined as player.
Fields
username: StringThe player’s raw Discord username: the unique handle. Display
name instead when it is present.
name: Option<PlayerName>What to call this player. Absent from servers older than protocol
0.20; fall back to username.
SpectatorLeft
Confirmation of leaving spectator mode.
SelectionUpdate
Another player’s tile selection (for live preview).
TimerVoteUpdate
Timer vote state changed.
TurnTimerStarted
Turn timer started (vote passed).
TurnTimerExpired
Turn timer expired - player auto-passed.
RematchCountdownUpdate
Rematch countdown state update.
Sent to all players on the results screen after a game ends.
Fields
state: RematchCountdownStateCurrent countdown state
PlayerLeftRematch
A player opted out of rematch pool.
Broadcast to remaining players so they can update the player list.
RematchStarting
Rematch is starting (sent right before GameStarted).
Allows frontend to show “Starting…” before the new game begins.
Fields
PlayerPoolChanged
PoolJoined
Player joined the game pool.
PoolUpdate
Pool position updated.
PoolLeft
Left the pool.
AdminGamesList
Response to admin game list request.
Fields
games: Vec<AdminGameInfo>AdminGameDeleted
Game was deleted by admin.
GameStateUpdate
Generic state update (legacy format).
Used for backward compatibility with existing frontend.
LobbyStateUpdate
Lobby state update (legacy format).
DebugStateResponse
Debug state response with player context diagnostics.
Fields
player: DebugPlayerInfowebsocket_context: DebugWebsocketContextlobby_state: Option<DebugLobbyState>backend_game_state: Option<DebugBackendGameState>handler_game_state: Option<DebugHandlerGameState>NewsAnnounced
A news/notification item was created or activated by an admin.
Pushed to every connected client when an admin posts a new item
via the news admin endpoints, or when an existing item is
updated in a way that should re-surface it (becoming active,
extending expires_at, etc.). Clients should treat it the same
as if they had just fetched /api/news and seen this entry —
in particular, maintenance items with a future expires_at
drive the corner-countdown toast, while announcement/update
items go through the existing full-screen overlay.
Fields
item: NewsItemPayloadError
Error response.
Implementations§
Source§impl ServerMessage
impl ServerMessage
Sourcepub fn error_with_message(code: ErrorCode, message: impl Into<String>) -> Self
pub fn error_with_message(code: ErrorCode, message: impl Into<String>) -> Self
Create an error message with custom message.
Sourcepub fn error_with_details(
code: ErrorCode,
message: impl Into<String>,
details: Value,
) -> Self
pub fn error_with_details( code: ErrorCode, message: impl Into<String>, details: Value, ) -> Self
Create an error message with details.
Sourcepub fn message_type(&self) -> &'static str
pub fn message_type(&self) -> &'static str
Get the message type as a string (for logging/debugging).
Sourcepub fn should_store_for_replay(&self) -> bool
pub fn should_store_for_replay(&self) -> bool
Check if this message should be stored for reconnection replay.
Some messages are transient and don’t need to be replayed.
Trait Implementations§
Source§impl Clone for ServerMessage
impl Clone for ServerMessage
Source§fn clone(&self) -> ServerMessage
fn clone(&self) -> ServerMessage
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for ServerMessage
impl Debug for ServerMessage
Source§impl<'de> Deserialize<'de> for ServerMessage
impl<'de> Deserialize<'de> for ServerMessage
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl From<ServerMessage> for Value
Convert a ServerMessage to a serde_json::Value.
impl From<ServerMessage> for Value
Convert a ServerMessage to a serde_json::Value.