Real-time Synchronization
Relevant source files
The following files were used as context for generating this wiki page:
Real-time synchronization in Dreamcats is the mechanism that ensures all players in an "online" game session share an identical, authoritative view of the game state. It leverages Convex as a reactive backend to handle data persistence, presence tracking, and state propagation, while the frontend uses Zustand for local state management and a dedicated synchronization component to bridge the two.
The system is designed to handle three primary modes: online, hotseat, and solo. For online play, the server serves as the "single source of truth," performing visibility filtering (e.g., hiding card values in opponents' hands) before pushing updates to connected clients.
Sources: src/state/ConvexSync.tsx:49-55, src/state/useGame.ts:16-20, README.md:3-8
System Architecture
The synchronization architecture consists of three main layers: the Authority Layer (Convex), the Sync Layer (ConvexSync), and the View Layer (Zustand & React).
Authority Layer (Backend)
Convex maintains the authoritative state in a games table. Every player action is sent as a mutation, processed on the server, and stored with a version number to prevent stale writes.
Sync Layer (Client Bridge)
The ConvexSync component acts as a reactive bridge. It uses useQuery hooks to subscribe to remote data. When the server state changes, ConvexSync updates the local Zustand store. It also manages the "Presence Heartbeat," sending a signal every 10 seconds to indicate the player is still active.
View Layer (Frontend)
The local Zustand store (accessed via useAppStore) holds the current view of the game. UI components subscribe to this store for rendering. Actions initiated by the user are sent to the backend via the useGame hook.
The following diagram illustrates the unidirectional data flow for online synchronization:
Data Models and Schema
The synchronization system relies on specific tables defined in the Convex schema to track rooms, players, and the authoritative game state.
| Table | Purpose | Key Fields |
|---|---|---|
rooms | Manages session metadata | roomId, hostId, status, mode |
players | Tracks participants in a room | playerId, name, connected, lastSeenAt |
games | Stores the authoritative game snapshot | state, version, idempotencyKey |
presence | Dedicated real-time connectivity tracking | status (online/away), lastPing |
messages | Real-time chat history | senderId, message, timestamp |
Sources: convex/schema.ts:9-67
Core Synchronization Logic
Reactive State Application
The client subscribes to the game state using api.games.getGameState. When remoteGame data is received, it is applied to the local store via setGame. The server handles visibility filtering, ensuring clients only receive data they are permitted to see.
// src/state/ConvexSync.tsx:64-77
useEffect(() => {
if (!remoteGame || gameMode !== "online" || !roomId) return;
// Server handles visibility filtering; client trusts the state
setGame(remoteGame, { source: "remote" });
setRoomStatus(
remoteGame.gamePhase === "lobby"
? "lobby"
: remoteGame.gamePhase === "game_over"
? "game_over"
: "playing",
);
}, [gameMode, remoteGame, roomId, setGame, setRoomStatus]);
Reconnection and Session Persistence
Dreamcats uses sessionStorage to allow players to rejoin a game after a page refresh. ConvexSync checks for a stored playerId and roomId. If the local store is empty but storage has data, it performs a "silent rejoin" via the joinRoom mutation.
Presence Tracking
Connectivity is tracked through a heartbeat mechanism. ConvexSync initiates an interval that calls updatePlayerPresence every 10,000ms. On the backend, this updates the lastSeenAt timestamp in the players table and the lastPing in the presence table.
// src/state/ConvexSync.tsx:89-97
useEffect(() => {
if (gameMode !== "online" || !roomId || !playerId) return;
const interval = setInterval(() => {
updatePresenceMutation({ roomId, playerId }).catch(() => {
/* swallow */
});
}, 10000);
return () => clearInterval(interval);
}, [gameMode, playerId, roomId, updatePresenceMutation]);
Sources: src/state/ConvexSync.tsx:89-97, convex/rooms.ts:145-175
Network Status and Error Handling
The system monitors network health by comparing the lastSeenAt timestamps of all players in a room. If a player hasn't been seen in over 30 seconds, their status may be updated.
The useGame hook implements a retryMutation utility to handle transient network glitches. This utility uses exponential backoff for retries but explicitly ignores "non-retriable" errors, such as invalid phases or actions taken out of turn, to prevent infinite loops on logic errors.
| Error Type | Behavior | Example Message |
|---|---|---|
| Transient | Exponential Retry | "Network connection lost" |
| Logic | Immediate Failure | "Not your turn", "Invalid phase" |
| Auth | Immediate Failure | "Not authenticated" |
Sources: src/state/useGame.ts:31-60, src/state/ConvexSync.tsx:99-106
Summary
Real-time synchronization in the project is a multi-layered system that prioritizes server authority for online play while maintaining high responsiveness through local state updates. By combining Convex's reactive queries with a heartbeat presence system and session persistence, the project ensures a stable multiplayer experience capable of recovering from network interruptions.