Skip to main content

ServerMessage

Enum ServerMessage 

Source
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

§heartbeat_interval_ms: u32

Recommended heartbeat interval in milliseconds

§server_version: Option<String>

Server version for compatibility checks

§

Ready

Successful authentication response.

Contains the full initial state snapshot.

Fields

§session_id: String

Unique session ID for this connection

§player_id: i64

The authenticated player’s user ID

§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.

Fields

§server_time: u64

Server timestamp when heartbeat was received

§

InvalidSession

Session is invalid or expired.

Client should re-authenticate.

Fields

§reason: String
§

LobbyJoined

Sent when successfully joining a lobby.

Fields

§lobby_id: String
§lobby_code: Option<String>

6-char code for custom lobbies

§lobby: LobbySnapshot

Full lobby state

§

LobbySnapshot

Full lobby state snapshot.

Sent on initial join or when delta sync fails.

Fields

§

LobbyDelta

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.

Fields

§lobby_id: String
§lobby_code: String
§

GameStarted

A new game has started.

Contains initial game state for all participants.

Fields

§game_id: String
§game_type: GameType

Which 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.

§grid: Grid
§your_turn_order: u8

Your turn order (0-indexed)

§current_turn: i64

Who goes first

§round: u8
§max_rounds: u8
§turn_time_limit: Option<u32>

Turn time limit in seconds (if configured)

§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).

§time_remaining_ms: Option<u64>

Server-authoritative time left in the run, in milliseconds. Set for Daily Challenge runs (incl. resumes); omitted otherwise.

§

GameSnapshot

Full game state snapshot.

Sent when joining as spectator or when delta sync fails.

Fields

§game_id: String
§

GameDelta

Incremental game state update.

Fields

§game_id: String
§changes: Vec<GameChange>
§

GameOver

Game has ended normally.

Fields

§game_id: String
§final_scores: Vec<ScoreInfo>

Final scores, sorted by rank

§winner_id: i64

Winner’s user ID

§is_draw: bool

Whether it was a draw

§

GameCancelled

Game was cancelled (not enough players, host left, etc.).

Fields

§game_id: String
§reason: String
§

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

§level: u32

Which level was played (1..=50).

§score: i32

Human player’s final score for this run.

§stars: u8

Stars awarded (0..=3). 0 = target not hit; 1+ = hit one_star/two_star/three_star.

§personal_best: bool

True iff score exceeded the prior high_score for this user + level.

§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

§seed: i64

The day’s seed (deterministic from the UTC date).

§score: i32

This run’s final score.

§personal_best: i32

The player’s best score across today’s attempts.

§rank: u32

1-based rank on today’s shared leaderboard.

§attempts_used: u8

Attempts used so far today (incl. this one).

§attempts_max: u8

Max attempts today for this user (1 free, 3 premium).

§

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

§game_id: String
§affected_positions: Vec<Position>
§new_grid: Grid

Full post-effect grid. Clients apply this as an authoritative snapshot — no delta needed.

§label: String

Short human-readable label for the event, already localized server-side. Rendered in a brief toast alongside the animation.

§

PlayerJoined

A player joined the lobby.

Fields

§

PlayerLeft

A player left the lobby.

Fields

§player_id: i64
§reason: Option<String>
§

PlayerReconnected

A player reconnected after disconnection.

Fields

§player_id: i64
§

PlayerDisconnected

A player disconnected (may reconnect).

Fields

§game_id: Option<String>
§player_id: i64
§grace_period_seconds: u32

Grace period in seconds before they’re removed

§

WordScored

A word was successfully scored.

Fields

§player_id: i64
§game_id: String
§word: String
§score: i32
§path: Vec<Position>

Positions that formed the word

§total_score: i32

New total score

§gems_earned: i32

Gems earned from this word

§total_gems: i32

New gem total

§new_grid: Grid

Updated grid (letters replaced)

§

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

§game_id: String

Game this word was played in.

§word: String

The accepted word, echoed back so the client can list it.

§letters: i32

Letters gained — equal to the word’s length. Sent explicitly so the client never re-derives scoring rules locally.

§total_score: i32

The submitting player’s new running total.

§

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

§player_id: i64

Whose score changed.

§game_id: String

Game the score belongs to.

§total_score: i32

That player’s new running total.

§

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

§game_id: String

Match this round belongs to.

§round: u8

Which round just finished, 1-based.

§max_rounds: u8

How 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.

§next_round_starts_at: Option<DateTime<Utc>>

When the next round’s board goes live, for the client’s countdown.

None means the server has not scheduled it yet; it does NOT mean the match is over — that is GameOver.

§

TurnChanged

Turn changed to another player.

Fields

§player_id: i64
§game_id: String
§round: u8
§time_remaining: Option<u32>

Time remaining for this turn (if timer active)

§

TurnPassed

A player passed their turn.

Fields

§player_id: i64
§game_id: String
§

RoundChanged

Round number changed.

Fields

§game_id: String
§round: u8
§max_rounds: u8
§new_grid: Option<Grid>

New grid if board was regenerated at the start of this round. Only present when the game is configured with regenerate_board_each_round: true.

§

BoardShuffled

Board was shuffled.

Fields

§player_id: i64
§game_id: String
§new_grid: Grid
§gems_spent: i32
§total_gems: i32

Player’s remaining gems after shuffle

§

TileSwapped

A tile was swapped.

Fields

§player_id: i64
§game_id: String
§row: usize
§col: usize
§old_letter: char
§new_letter: char
§gems_spent: i32
§total_gems: i32

Player’s remaining gems after swap

§

SwapModeEntered

Player entered swap mode (for animation).

Fields

§player_id: i64
§game_id: String
§

SwapModeExited

Player exited swap mode.

Fields

§player_id: i64
§game_id: String
§

SpectatorJoined

Successfully joined as spectator.

Fields

§game_id: String
§game: GameSnapshot

Full game state

§

SpectatorAdded

A new spectator joined (broadcast to others).

Fields

§spectator: SpectatorInfo
§game_id: String
§

SpectatorRemoved

A spectator left.

Fields

§spectator_id: i64
§game_id: String
§

SpectatorBecamePlayer

Spectator joined as player.

Fields

§player_id: i64
§username: String

The 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.

§game_id: String
§

SpectatorLeft

Confirmation of leaving spectator mode.

§

SelectionUpdate

Another player’s tile selection (for live preview).

Fields

§player_id: i64
§game_id: String
§positions: Vec<Position>
§

TimerVoteUpdate

Timer vote state changed.

Fields

§game_id: String
§

TurnTimerStarted

Turn timer started (vote passed).

Fields

§target_player_id: i64
§game_id: String
§seconds: u32
§

TurnTimerExpired

Turn timer expired - player auto-passed.

Fields

§player_id: i64
§game_id: String
§

RematchCountdownUpdate

Rematch countdown state update.

Sent to all players on the results screen after a game ends.

Fields

§state: RematchCountdownState

Current countdown state

§previous_game_id: String

The game that just ended

§

PlayerLeftRematch

A player opted out of rematch pool.

Broadcast to remaining players so they can update the player list.

Fields

§player_id: i64
§previous_game_id: String

The game they left from

§

RematchStarting

Rematch is starting (sent right before GameStarted).

Allows frontend to show “Starting…” before the new game begins.

Fields

§triggered_by: Option<i64>

Who triggered the early start (None if countdown expired naturally)

§previous_game_id: String

The previous game that ended

§

PlayerPoolChanged

Fields

§player_id: i64
§old_pool: Option<GameType>
§new_pool: Option<GameType>
§

PoolJoined

Player joined the game pool.

Fields

§position: i32
§total_in_pool: i32
§game_id: String
§

PoolUpdate

Pool position updated.

Fields

§position: i32
§total_in_pool: i32
§game_id: String
§

PoolLeft

Left the pool.

§

AdminGamesList

Response to admin game list request.

Fields

§

AdminGameDeleted

Game was deleted by admin.

Fields

§game_id: String
§

GameStateUpdate

Generic state update (legacy format).

Used for backward compatibility with existing frontend.

Fields

§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 state update (legacy format).

Fields

§lobby_id: String
§

DebugStateResponse

Debug state response with player context diagnostics.

Fields

§timestamp: String
§websocket_context: DebugWebsocketContext
§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

§

Error

Error response.

Fields

§message: String
§details: Option<Value>

Additional context (e.g., which field was invalid)

Implementations§

Source§

impl ServerMessage

Source

pub fn error(code: ErrorCode) -> Self

Create an error message from an error code.

Source

pub fn error_with_message(code: ErrorCode, message: impl Into<String>) -> Self

Create an error message with custom message.

Source

pub fn error_with_details( code: ErrorCode, message: impl Into<String>, details: Value, ) -> Self

Create an error message with details.

Source

pub fn message_type(&self) -> &'static str

Get the message type as a string (for logging/debugging).

Source

pub fn is_error(&self) -> bool

Check if this is an error message.

Source

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

Source§

fn clone(&self) -> ServerMessage

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ServerMessage

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for ServerMessage

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl From<ServerMessage> for Value

Convert a ServerMessage to a serde_json::Value.

Source§

fn from(msg: ServerMessage) -> Self

Converts to this type from the input type.
Source§

impl Serialize for ServerMessage

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl TryFrom<Value> for ServerMessage

Convert a serde_json::Value to a ServerMessage.

Source§

type Error = Error

The type returned in the event of a conversion error.
Source§

fn try_from( value: Value, ) -> Result<Self, <ServerMessage as TryFrom<Value>>::Error>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.