Skip to content

Features

Complete overview of Signal Fish Server capabilities.

Room Management

Automatic Room Codes

Rooms are identified by auto-generated 6-character codes (configurable length).

Create a room by joining without a room code:

JSON
{
  "type": "JoinRoom",
  "data": {
    "game_name": "my-game",
    "player_name": "Player1",
    "max_players": 8
  }
}

Response:

JSON
{
  "type": "RoomJoined",
  "data": {
    "room_code": "ABC123",
    "room_id": "uuid-string",
    "player_id": "your-player-id",
    "game_name": "my-game",
    "max_players": 8,
    "supports_authority": true,
    "current_players": [
      {
        "id": "your-player-id",
        "name": "Player1",
        "is_authority": false,
        "is_ready": false,
        "connected_at": "2025-01-15T10:30:00Z"
      }
    ],
    "is_authority": false,
    "lobby_state": "waiting",
    "ready_players": [],
    "relay_type": "WebRTC",
    "current_spectators": []
  }
}

Players join using the room code:

JSON
{
  "type": "JoinRoom",
  "data": {
    "game_name": "my-game",
    "room_code": "ABC123",
    "player_name": "Player2"
  }
}

Room Limits

Configure per-game room limits:

JSON
{
  "server": {
    "max_rooms_per_game": 1000
  }
}

When auth is enabled, per-app limits apply:

JSON
{
  "security": {
    "authorized_apps": [
      {
        "app_id": "my-game",
        "max_rooms": 100,
        "max_players_per_room": 16
      }
    ]
  }
}

Lobby State Machine

Rooms transition through three states based on player ready status:

Waiting

Initial state. Waiting for players to join until the room reaches max_players. No ready-state toggles happen in this state.

Lobby

Room is full and players are coordinating readiness.

Finalized

All players are ready and the game is starting.

State Transitions

Text Only
Waiting --> Lobby (room full)
Lobby --> Waiting (player leaves)
Lobby --> Finalized (all players ready)
Finalized --> [*] (game started, room cleanup)

Clients are notified of state changes:

JSON
{
  "type": "LobbyStateChanged",
  "data": {
    "lobby_state": "finalized",
    "ready_players": ["player-id-1", "player-id-2"],
    "all_ready": true
  }
}

Player Ready State

Players toggle their own ready state by sending PlayerReady:

JSON
{
  "type": "PlayerReady"
}

Each PlayerReady send flips that player's ready/unready status and broadcasts LobbyStateChanged. When all players are ready, the server sends GameStarting.

Authority Management

Players can request game authority (e.g., for server-authoritative gameplay):

JSON
{
  "type": "AuthorityRequest",
  "data": {
    "become_authority": true
  }
}

When granted, all players are notified:

JSON
{
  "type": "AuthorityChanged",
  "data": {
    "authority_player": "player-id",
    "you_are_authority": false
  }
}

Only one player can hold authority at a time.

Spectator Mode

Join rooms as a spectator without participating in gameplay:

JSON
{
  "type": "JoinAsSpectator",
  "data": {
    "game_name": "my-game",
    "room_code": "ABC123",
    "spectator_name": "Observer1"
  }
}

Spectators:

  • Don't count toward max_players
  • Can't send PlayerReady (cannot toggle readiness)
  • Receive all game data
  • Don't participate in authority decisions

Reconnection

Token-based reconnection with event replay.

Disconnect and Token Generation

When a player disconnects, the server generates a reconnection token bound to their player ID and room ID. The server buffers room events during the disconnection window so they can be replayed on reconnect.

Reconnecting

If the connection is lost, reconnect using stored credentials:

JSON
{
  "type": "Reconnect",
  "data": {
    "player_id": "your-player-id",
    "room_id": "your-room-id",
    "auth_token": "stored-token"
  }
}

Event Replay

On successful reconnection, the server sends a Reconnected message with the current room state and replays any events that occurred during the disconnection window.

Configuration

JSON
{
  "server": {
    "enable_reconnection": true,
    "reconnection_window": 300,
    "event_buffer_size": 100
  }
}

Message Batching

Batch outbound messages for improved throughput.

JSON
{
  "websocket": {
    "enable_batching": true,
    "batch_size": 10,
    "batch_interval_ms": 16
  }
}
  • batch_size - Max messages per batch
  • batch_interval_ms - Max time to wait before flushing

Batching is transparent to clients.

Rate Limiting

In-memory rate limiting for room creation and join attempts.

JSON
{
  "rate_limit": {
    "max_room_creations": 5,
    "time_window": 60,
    "max_join_attempts": 20
  }
}
  • max_room_creations - Max rooms per IP per time window
  • time_window - Window duration in seconds
  • max_join_attempts - Max join attempts per IP per window

When auth is enabled, per-app rate limits apply:

JSON
{
  "security": {
    "authorized_apps": [
      {
        "app_id": "my-game",
        "rate_limit_per_minute": 60
      }
    ]
  }
}

Metrics

JSON Metrics

Bash
curl http://localhost:3536/metrics

Returns:

JSON
{
  "active_rooms": 42,
  "active_players": 156,
  "total_rooms_created": 1024,
  "total_messages_sent": 50000,
  "uptime_seconds": 3600
}

Prometheus Metrics

Bash
curl http://localhost:3536/metrics/prom

Returns Prometheus text format for scraping.

Metrics Authentication

Protect metrics endpoints:

JSON
{
  "security": {
    "require_metrics_auth": true
  }
}

Access with:

Bash
curl -H "Authorization: Bearer app_id:app_secret" \
  http://localhost:3536/metrics

Authentication

Optional app-based authentication with per-app limits.

JSON
{
  "security": {
    "require_websocket_auth": true,
    "authorized_apps": [
      {
        "app_id": "my-game",
        "app_secret": "secret-key",
        "max_rooms": 100,
        "max_players_per_room": 16,
        "rate_limit_per_minute": 60
      }
    ]
  }
}

See Authentication for full details.

MessagePack Support

Enable MessagePack encoding for game data:

JSON
{
  "protocol": {
    "enable_message_pack_game_data": true
  }
}

Game data messages can be sent in MessagePack format for reduced bandwidth.

CORS Support

Configure allowed origins:

JSON
{
  "security": {
    "cors_origins": "https://yourgame.com"
  }
}

Multiple origins (comma-separated):

JSON
{
  "security": {
    "cors_origins": "https://game.com,https://beta.game.com"
  }
}

Allow all (development only):

JSON
{
  "security": {
    "cors_origins": "*"
  }
}

Connection Limits

Limit concurrent connections per IP:

JSON
{
  "security": {
    "max_connections_per_ip": 10
  }
}

Message Size Limits

Limit maximum WebSocket message size:

JSON
{
  "security": {
    "max_message_size": 65536
  }
}

Messages exceeding this size are rejected.

Room Cleanup

Automatic cleanup of empty and inactive rooms:

JSON
{
  "server": {
    "room_cleanup_interval": 60,
    "empty_room_timeout": 300,
    "inactive_room_timeout": 3600
  }
}
  • room_cleanup_interval - Seconds between cleanup sweeps
  • empty_room_timeout - Seconds before empty room removal
  • inactive_room_timeout - Seconds before inactive room removal

Ping/Pong

Keep-alive mechanism to detect dead connections:

JSON
{
  "server": {
    "ping_timeout": 30
  }
}

Clients should send periodic Ping messages. Server disconnects clients that are silent for longer than ping_timeout.

Structured Logging

JSON-formatted structured logs for production observability:

JSON
{
  "logging": {
    "enable_file_logging": true,
    "dir": "logs",
    "filename": "server.log",
    "rotation": "daily",
    "format": "Json"
  }
}

Zero External Dependencies

Everything runs in-memory:

  • No database required
  • No message broker
  • No cloud services
  • No external runtime dependencies

Perfect for:

  • Local development
  • LAN games
  • Self-hosted deployments
  • Embedded systems

Next Steps