RMHbox โ Codebase Reference for Coding Agentsยถ
Maintenance Requirement: This document must be kept up-to-date whenever changes are made to the core design, structure, patterns, or architecture of the RMHbox codebase. Any agent working on RMHbox should verify that this document still accurately reflects the codebase after their changes, and update it if anything has changed.
Table of Contentsยถ
Overviewยถ
RMHbox is a real-time multiplayer party game platform built on Next.js (frontend) and a standalone Socket.io WebSocket server (backend). Players create or join lobbies via room codes, vote on or select minigames, and play together in real time. The platform supports 7 implemented minigames, spectator modes, host controls, match history, leaderboards, and reconnection.
The seven currently implemented minigames are:
Rhyme Time โ Speed-based vocabulary rhyming game
Undercover Agent โ Team-based word-association deduction (like Codenames)
Category Crash โ Brainstorming showdown with peer review. Phases: REVEAL โ INPUT โ PEER_REVIEW โ ROUND_RESULTS. Voting is crash/safe; points come only from unique safe answers (no voting bonuses).
Wiki-Race โ Navigate Wikipedia from a start article to a target
Minimalist Masterpiece โ Draw with limited strokes, then art auction. Drawing phase supports auto-save (
SAVE_DRAWING) and explicit submit (SUBMIT_DRAWING). Auto-save preserves drawings for reconnection and spectator live updates without locking. Explicit submit locks the drawing and ends the phase early if all players submit.Emoji Cinema โ Describe movies with emojis, others guess
Wit-War โ Battle of wits with head-to-head voting
Architecture Summaryยถ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Next.js Frontend โ
โ app/rmhbox/ โ Pages (landing, lobby, minigames) โ
โ components/rmhbox/ โ React components โ
โ lib/rmhbox/ โ Shared types, store, socket, utils โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ WebSocket (Socket.io) โ
โ ws://host/rmhbox-ws (port 7676) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Standalone RMHbox Server โ
โ server/rmhbox/ โ Server logic, lobby, game, auth โ
โ server/rmhbox/minigames/ โ Server-side minigame handlers โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ PostgreSQL (Prisma) โ
โ RMHboxProfile, RMHboxMatch, RMHboxMatchPlayer โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The client and server are separate processes. The RMHbox WebSocket server runs independently on port 7676 (configurable via RMHBOX_PORT), separate from the main Next.js server. Communication is exclusively via Socket.io WebSocket events.
Directory Structureยถ
Core Platform Filesยถ
lib/rmhbox/
โโโ types.ts # Shared type definitions (client + server)
โโโ events.ts # WebSocket event name constants (C2S, S2C)
โโโ constants.ts # Shared tuning constants (timers, scoring, limits)
โโโ schemas.ts # Shared Zod validation schemas for event payloads
โโโ store.ts # Client-side Zustand store
โโโ socket.ts # Client-side Socket.io connection manager
โโโ minigame-client.ts # Shared hooks/utils for minigame components
โโโ minigame-registry.ts # Minigame metadata definitions + settings schemas
โโโ game-settings.ts # Game settings validation & defaults
โโโ audio.ts # Sound effect manager (Howler.js)
โโโ toast-store.ts # Toast notification store
โโโ utils.ts # Shared utilities (sanitize, room code gen)
โโโ history-display-registry.ts # History detail component registry
โโโ history-display-registrations.ts # All minigame history registrations
โโโ <minigame-id>/ # Per-minigame shared code (schemas, data loaders)
โโโ schemas.ts
โโโ data-loader.ts
server/rmhbox/
โโโ index.ts # Server entry point โ bootstraps all services
โโโ config.ts # Server configuration (env vars, defaults)
โโโ auth.ts # WebSocket auth middleware (Better Auth tokens)
โโโ lobby-manager.ts # Lobby CRUD, joins, leaves, host controls, GC
โโโ game-coordinator.ts # Game lifecycle state machine orchestration
โโโ vote-manager.ts # Minigame voting system
โโโ state-sync.ts # Heartbeat, phase sync, timer broadcasting
โโโ reconnection.ts # Player reconnection & duplicate session handling
โโโ chat.ts # Chat message handling
โโโ leaderboard.ts # Match persistence & leaderboard queries
โโโ rate-limit.ts # Per-socket rate limiting
โโโ schemas.ts # Re-exports shared schemas + `validated()` wrapper
โโโ logger.ts # Structured JSON logger
โโโ prisma-client.ts # Prisma client singleton
โโโ types.ts # Server-only type definitions
โโโ minigames/
โโโ base-minigame.ts # Abstract base class for all minigame handlers
โโโ <minigame-id>/
โโโ index.ts # Barrel export
โโโ handler.ts # Minigame server handler (extends BaseMinigame)
โโโ types.ts # Minigame-specific server types
components/rmhbox/
โโโ RMHboxShell.tsx # Theme wrapper + toast container
โโโ RMHboxHeader.tsx # Shared header with timer ring, settings, controls
โโโ GameShell.tsx # Game layout wrapper (footer with score/round/players)
โโโ LobbyView.tsx # WAITING state lobby view
โโโ GameVoting.tsx # VOTING state minigame selection
โโโ GameSettingsPhase.tsx # GAME_SETTINGS state host settings
โโโ InstructionsScreen.tsx # INSTRUCTIONS state game instructions
โโโ PreloadScreen.tsx # PRELOADING state asset preload progress
โโโ ResultsScreen.tsx # ROUND_RESULTS state results display
โโโ PlayerList.tsx # Player list sidebar
โโโ ChatOverlay.tsx # Chat overlay
โโโ SpectatorBanner.tsx # Spectator mode banner with player selector
โโโ HostControls.tsx / HostControlModal.tsx # Host management UI
โโโ GameSettingsForm.tsx / GameSettingsModal.tsx # Settings configuration UI
โโโ GamePickerModal.tsx # Minigame selection modal
โโโ LeaderboardPanel.tsx # Leaderboard display
โโโ MinigameLeaderboardModal.tsx # Per-minigame leaderboard modal
โโโ ReadyButton.tsx # Ready-up button
โโโ RoomCodeDisplay.tsx # Room code display with copy
โโโ ToastContainer.tsx # Toast notification renderer
โโโ SettingsMenu.tsx # User settings (volume, theme)
โโโ minigames/
โโโ MinigameRenderer.tsx # Dynamic lazy-loading of minigame components
โโโ <minigame-id>/
โโโ <MinigameName>Game.tsx # Main minigame component
โโโ <MinigameName>HistoryDetail.tsx # History detail view component
โโโ <SubComponent>.tsx # Game-specific sub-components
app/rmhbox/
โโโ layout.tsx # Auth gate + RMHboxShell wrapper
โโโ page.tsx # Landing page (create/join/browse/leaderboard)
โโโ rmhbox.css # Theme stylesheet (CSS custom properties)
โโโ [lobbyId]/page.tsx # Lobby page (state machine view renderer)
โโโ minigames/
โโโ page.tsx # Minigames catalog page
โโโ [minigameId]/history/page.tsx # Per-minigame match history
app/api/rmhbox/
โโโ history/route.ts # Match history REST API
โโโ stats/route.ts # Player stats REST API
โโโ leaderboard/route.ts # Leaderboard REST API
data/rmhbox/<minigame-id>/ # Static JSON data files for minigames
testing/rmhbox/ # Test suites organized by implementation phase
public/music/rmhbox/sfx/ # Sound effect files
Technology Stackยถ
Layer |
Technology |
|---|---|
Frontend Framework |
Next.js (App Router, React 18+) |
State Management |
Zustand (with |
Real-time Communication |
Socket.io (client + server) |
Validation |
Zod (shared schemas, both client and server) |
Database |
PostgreSQL via Prisma ORM |
Authentication |
Better Auth (session tokens validated against DB) |
Styling |
Tailwind CSS + CSS custom properties (design tokens) |
Sound |
Howler.js |
Testing |
Vitest |
Package Manager |
pnpm |
Language |
TypeScript (strict mode) |
WebSocket Protocolยถ
Event Naming Conventionยถ
All events use the rmhbox: prefix for namespacing. Events are defined in lib/rmhbox/events.ts as two constant objects:
C2Sโ Client-to-Server events (e.g.,rmhbox:lobby:create,rmhbox:game:input)S2Cโ Server-to-Client events (e.g.,rmhbox:lobby:state_snapshot,rmhbox:game:action)
Key Eventsยถ
Lobby Management (C2S):
LOBBY_CREATE,LOBBY_JOIN,LOBBY_LEAVE,LOBBY_KICK,LOBBY_TRANSFER_HOSTLOBBY_UPDATE_SETTINGS,LOBBY_TOGGLE_READY,LOBBY_CHAT,LOBBY_BROWSELOBBY_REQUEST_PROMOTION,LOBBY_PROMOTE_SPECTATOR,LOBBY_END_SESSION
Game Flow (C2S):
GAME_PICKโ Host selects a game (or__vote__for vote mode)GAME_SELECTโ Host selects a game directlyGAME_START_VOTEโ Host initiates votingGAME_CAST_VOTEโ Player votes for a gameGAME_FORCE_SKIPโ Host skips current phaseGAME_FORCE_ENDโ Host force-ends the gameGAME_PAUSE_TIMERโ Host pauses/resumes timerGAME_READY_TO_RENDERโ Client signals preload completeGAME_INPUTโ Player sends game-specific input
State Sync (S2C):
LOBBY_STATE_SNAPSHOTโ Full client state (sent on join, reconnect, heartbeat)GAME_ACTIONโ Sequenced incremental action (reducer-applied on client)GAME_STATE_SNAPSHOTโ Full game-specific state snapshotGAME_INSTRUCTIONS,GAME_PRELOAD_START,GAME_COUNTDOWN,GAME_STARTEDGAME_ROUND_RESULTS,GAME_SESSION_RESULTSGAME_VOTE_STARTED,GAME_VOTE_UPDATE,GAME_VOTE_RESULTGAME_SETTINGS_OPENED,GAME_SETTINGS_UPDATEDSPECTATOR_TARGET_STATEโ Spectator target player infoERRORโ Structured error with code and message
Payload Validationยถ
All C2S event payloads are validated using Zod schemas defined in lib/rmhbox/schemas.ts. The server uses the validated() wrapper from server/rmhbox/schemas.ts which combines:
Rate limiting (per-socket, per-event)
Zod schema validation
Error emission on failure
Try-catch wrapping for handler execution
Usage pattern:
socket.on(C2S.SOME_EVENT, validated(socket, C2S.SOME_EVENT, SomeSchema, (s, data) => {
this.handleSomeEvent(s, data);
}));
State Synchronization Modelยถ
The client receives state via two mechanisms:
Full snapshots (
LOBBY_STATE_SNAPSHOT) โ CompleteClientLobbyStatesent on join, reconnect, and periodic heartbeat during gameplayIncremental actions (
GAME_ACTION) โ SequencedGameActionobjects applied by reducer functions in the Zustand store
Actions have a monotonically increasing seq number. The clientโs applyAction() skips any action with seq <= lastSeq to prevent out-of-order duplicates.
The store has two reducer functions:
applyLobbyAction()โ Handles lobby-level actions (PLAYER_JOINED, STATE_CHANGED, TIMER_TICK, etc.)applyGameAction()โ Stores game-specific actions as raw payloads keyed by action type undergameState
Server Architectureยถ
Entry Point (server/rmhbox/index.ts)ยถ
The server creates an HTTP server + Socket.io instance, applies auth middleware, and bootstraps these services:
LobbyManagerโ Lobby CRUD, player management, GCStateSyncServiceโ Heartbeat broadcasting, phase sync, timer broadcastsLeaderboardServiceโ Match persistence, leaderboard queriesGameCoordinatorโ Game lifecycle orchestrationVoteManagerโ Voting systemChatHandlerโ Chat message handlingReconnectionHandlerโ Reconnection protocol
Each service implements a handleConnection(socket) method that registers its Socket.io event listeners on the socket.
Lobby Managerยถ
Manages all lobbies in memory using Map<string, RMHboxLobby>. Key features:
Room code generation โ 6-character codes using a custom alphabet (excludes ambiguous chars)
Player/spectator tracking โ
Map<string, RMHboxPlayer>andMap<string, RMHboxSpectator>UserโLobby index โ
Map<string, string>for O(1) userIdโlobbyId lookupSequence counters โ Per-lobby incrementing
seqfor GameActionsGrace timers โ For handling temporary disconnects
Garbage collection โ Periodic cleanup of idle/empty lobbies
Join-in-progress โ Pending join queue for
join_next_subroundpolicybuildClientState()โ The ONLY function that constructsClientLobbyStatefrom internal state, ensuring data masking/scopingbroadcastAction()โ Emits sequencedGameActionto all lobby members
Game Coordinatorยถ
Orchestrates the game lifecycle state machine:
WAITING โ VOTING โ GAME_SETTINGS โ INSTRUCTIONS โ PRELOADING โ COUNTDOWN โ PLAYING โ ROUND_RESULTS โ WAITING
Key responsibilities:
Phase transitions โ Drives state transitions with timed phases
Minigame instantiation โ Uses
MINIGAME_SERVER_REGISTRYto create handler instancesInput routing โ Routes
GAME_INPUTto the active handlerโshandleInput()Timer management โ Delegates to
StateSyncService.startTimerBroadcast()for timed phases, or the minigame handler manages its own timers viaBaseMinigameDisconnect handling โ Grace timers for in-game disconnects
Spectator target tracking โ Manages which player each spectator is following
Results processing โ Calls
computeResults()and persists via LeaderboardService
MINIGAME_SERVER_REGISTRY is a Map<string, Constructor> in game-coordinator.ts that maps minigame IDs to their server handler class constructors. New minigames must be registered here.
State Sync Serviceยถ
Heartbeat โ Periodic full state snapshots to all in-game lobbies (every 10s by default)
Phase transition sync โ Immediate full sync on state changes
Timer broadcasting โ
startTimerBroadcast()creates a 1-second countdown that emitsTIMER_STARTthenTIMER_TICKactions; returns aTimerHandlewith cancel/pause/resume
Authenticationยถ
The authMiddleware validates Better Auth session tokens against PostgreSQL on every new connection. It attaches userId, userName, and avatarUrl to socket.data.
Reconnectionยถ
The ReconnectionHandler identifies returning users by userId (not socketId), re-associates their socket with the existing lobby slot, sends a full state snapshot to resync, and handles duplicate sessions (same user, different tab).
Client Architectureยถ
Zustand Store (lib/rmhbox/store.ts)ยถ
The useRMHboxStore is the central state for all RMHbox client-side data:
interface RMHboxStore {
connectionStatus: 'disconnected' | 'connecting' | 'connected' | 'error';
lobby: ClientLobbyState | null;
gameState: Record<string, unknown>;
lastSeq: number;
settings: RMHboxUserSettings; // Persisted to localStorage
timerInfo: TimerInfo | null; // Header timer ring state
minigameRound: MinigameRoundInfo | null; // Footer round counter
gameSettingsState: GameSettingsState | null;
spectatorTarget: SpectatorTargetInfo | null;
// ... actions
}
Key patterns:
Only
settingsis persisted to localStorage via ZustandโspersistmiddlewareapplyAction()enforces sequence ordering vialastSeqapplyFullSync()replaces the entire lobby stateleaveLobby()clears all lobby/game stateTimer and round state is written directly by store actions (not via reducers) for immediate reactivity
Socket Client (lib/rmhbox/socket.ts)ยถ
Module-level singleton socket with these exports:
connectToRMHbox()โ Creates connection with auth token, sets up global event listeners, returns socketgetSocket()โ Returns current socket instance (may be null)disconnectFromRMHbox()โ Disconnects and resets storeemit()โ Null-safe emit helper
The socket uses dynamic auth callbacks to refresh the session token on every reconnection attempt.
Minigame Client Utilities (lib/rmhbox/minigame-client.ts)ยถ
Shared hooks and utilities used by all minigame components:
emitGameInput(action, data)โ Sends game input with lobbyId from storeuseGameSocket(handlers)โ Subscribes toGAME_ACTION+GAME_STATE_SNAPSHOTevents, hydrates from store on mountextractTimerTick(data)โ Extracts timeRemaining from TIMER_TICK action payload
Page Structureยถ
Landing page (
app/rmhbox/page.tsx) โ Connect, create/join lobby, browse public lobbies, view leaderboardLobby page (
app/rmhbox/[lobbyId]/page.tsx) โ State machine renderer that shows the appropriate component based onlobby.stateLayout (
app/rmhbox/layout.tsx) โ Auth gate (redirects unauthenticated users) +RMHboxShelltheme wrapper
The lobby page renders different components based on lobby.state:
State |
Component |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
Countdown overlay |
|
|
|
|
|
Session results view |
Minigame Systemยถ
Server-Side Handler Patternยถ
Every minigame server handler extends BaseMinigame (in server/rmhbox/minigames/base-minigame.ts).
MinigameContext โ Provided to Every Handlerยถ
interface MinigameContext {
lobbyId: string;
players: Map<string, RMHboxPlayer>;
settings: LobbySettings;
gameSettings: GameSettingValues; // Host-configured settings
getHostId: () => string;
broadcastToLobby: (event, data) => void; // All members
broadcastToPlayers: (event, data) => void; // Players only
broadcastAction: (action) => void; // Sequenced GameAction
sendToPlayer: (userId, event, data) => void;
sendToSpectators: (event, data) => void;
sendToSpectatorFollowers: (targetPlayerId, event, data) => void;
onComplete: (results) => void; // Signal game end
onError: (error) => void;
}
Abstract Methods to Implementยถ
Method |
Description |
|---|---|
|
Called when PLAYING begins. Initialize game state and start game logic. |
|
Called when a player sends game input. Route by action string. |
|
Return scoped state for a specific player (reconnection, heartbeat). |
|
Return omniscient/privileged state for spectators. |
|
Compute final |
|
Return |
Built-in Helpers from BaseMinigameยถ
Timer management:
setTimeout(fn, ms)โ Tracked timeout, auto-cleaned, pausableclearTrackedTimeout(handle)โ Cancel a specific tracked timeoutsetInterval(fn, ms)โ Tracked interval, auto-cleanedstartPhaseTimer(durationSeconds)โ Broadcasts TIMER_START + TIMER_TICK actions every secondclearPhaseTimer()โ Stops the phase timerstartInfinitePhaseTimer(showSkip?)โ Broadcasts infinite timer (no countdown, full ring + โ icon)pausePhaseTimer()/resumePhaseTimer()โ Pause/resume all tracked timers + broadcast
State management:
getSetting<T>(key, fallback)โ Read host-configured game setting with type-safe fallbackbroadcastRound(current, total)โ Broadcast minigame sub-round info to clientsbroadcastInitialState(snapshotEvent)โ Send per-player scoped initial state to all playersbroadcastGameAction(data)โ Convenience wrapper for broadcasting game actionsbuildReconnectionSnapshot(userId, isSpectator, targetPlayerId?)โ Unified reconnection state
Lifecycle:
forceEnd(reason)โ Force-end with cleanup + partial resultscleanup()โ Clears all timers/intervalshandlePlayerJoin(userId)โ Override for join-in-progress supporthandlePlayerDisconnect(userId)โ Override for disconnect handlinghandlePlayerReconnect(userId)โ Override for reconnection side effects
Spectator:
getSpectatorSnapshot(targetPlayerId?)โ Dispatches to correct spectator state methodgetStateForSpectatorViewingPlayer(targetPlayerId)โ Overridable per-player spectator viewgetViewablePlayers()โ List of players available for spectator selection
Minigame Handler File Structureยถ
Each minigame lives in server/rmhbox/minigames/<minigame-id>/:
index.tsโ Barrel export (exports the handler class)handler.tsโ The handler class extendingBaseMinigametypes.tsโ Minigame-specific types (phase enums, state interfaces, submission types)
Client-Side Component Patternยถ
Each minigame has components in components/rmhbox/minigames/<minigame-id>/:
Main Game Component (<MinigameName>Game.tsx)ยถ
This is the root component loaded by MinigameRenderer. It receives MinigameProps:
interface MinigameProps {
playerId: string;
playerName: string;
}
Standard pattern used by all minigame components:
Define local state for the gameโs current phase, data, etc.
Use
useGameSocket()hook to subscribe toGAME_ACTIONandGAME_STATE_SNAPSHOTeventsIn the
onGameActioncallback, dispatch based ondata.typeto update local stateIn the
onStateSnapshotcallback, hydrate full local state from the snapshotUse
emitGameInput(action, data)to send player input to the serverRender different views based on the current phase
MinigameRenderer (components/rmhbox/minigames/MinigameRenderer.tsx)ยถ
Dynamically lazy-loads minigame components using React.lazy(). The MINIGAME_COMPONENTS map associates minigame IDs to their lazy-loaded component imports. New minigames must be added here.
Also exports two hooks for minigame components:
useHeaderTimer()โ Control the header timer ring (startTimer, tickTimer, clearTimer)useMinigameRound()โ Control the footer round counter (setRound, clearRound)
History Detail Component (<MinigameName>HistoryDetail.tsx)ยถ
Renders the expanded view of a past matchโs game log. Receives HistoryDetailProps:
interface HistoryDetailProps {
gameLog: GameLog;
currentUserId: string;
players: Array<{ userId: string; userName: string; rank: number; score: number }>;
}
Adding a New Minigame โ Checklistยถ
When adding a new minigame, the following files/registrations are required:
2. Minigame Registryยถ
Add the
MinigameDefinitionentry toMINIGAME_REGISTRYinlib/rmhbox/minigame-registry.tsDefine id, displayName, description, category, icon (Lucide icon name), minPlayers, maxPlayers, estimatedDurationSeconds, supportsTeams, instructionDurationSeconds, preloadAssets, joinInProgressPolicy, tags
If the minigame has host-configurable settings, define a
GameSettingsSchemaarray and assign it tosettingsSchema
3. Server Handlerยถ
Create
server/rmhbox/minigames/<minigame-id>/types.tsโ Phase enum, state interfaces, submission typesCreate
server/rmhbox/minigames/<minigame-id>/handler.tsโ Handler class extendingBaseMinigameImplement all abstract methods:
start(),handleInput(),getStateForPlayer(),getStateForSpectator(),computeResults(),get spectatorModeUse
this.getSetting(key, FALLBACK_CONSTANT)for host-configurable settingsUse
this.setTimeout()/this.setInterval()for tracked timersUse
this.startPhaseTimer()/this.clearPhaseTimer()for countdown phasesUse
this.broadcastRound()for sub-round displayUse
this.context.broadcastAction()for sequenced state updatesUse
this.context.sendToPlayer()for per-player private stateUse
this.context.onComplete()to signal game end with results
Create
server/rmhbox/minigames/<minigame-id>/index.tsโ Barrel exportRegister the handler class in
MINIGAME_SERVER_REGISTRYinserver/rmhbox/game-coordinator.tsAdd import and
Mapentry:['<minigame-id>', MinigameHandlerClass]
4. Client Componentsยถ
Create
components/rmhbox/minigames/<minigame-id>/<MinigameName>Game.tsxโ Main game componentAccept
MinigameProps(playerId,playerName)Use
useGameSocket()for event subscriptionUse
emitGameInput()for sending inputExport as default for lazy loading
Create game-specific sub-components as needed in the same directory
Register the lazy import in
MINIGAME_COMPONENTSincomponents/rmhbox/minigames/MinigameRenderer.tsx
5. History Displayยถ
Create
components/rmhbox/minigames/<minigame-id>/<MinigameName>HistoryDetail.tsxRegister in
lib/rmhbox/history-display-registrations.tsusingregisterHistoryDisplay()Define
DetailComponent,searchableFields,filterableFields, andgetSummary
6. Game Settings (if applicable)ยถ
Define a
GameSettingsSchemaarray inlib/rmhbox/minigame-registry.ts(alongside the existing ones)Assign it to the
settingsSchemafield in the minigameโsMinigameDefinitionUse
this.getSetting(key, fallback)in the handler to read settings
7. Testingยถ
Add tests following the existing patterns in
testing/rmhbox/Test the handler in isolation (start, input, results, edge cases)
8. Verify Integrationยถ
The game appears in the voting candidate pool (based on
MINIGAME_REGISTRY)The host can pick the game directly
Host-configured settings are passed to the handler
The game lifecycle (instructions โ preloading โ countdown โ playing โ results) works
Spectator mode works correctly
Reconnection provides correct state
Results and rankings display properly
Match history and leaderboard persist correctly
Game Settings Systemยถ
The game settings system (ยง12A) allows hosts to configure minigame parameters before starting.
Schema Definitionยถ
Settings are defined as GameSettingsSchema arrays (in lib/rmhbox/minigame-registry.ts):
const EXAMPLE_SETTINGS: GameSettingsSchema = [
{ key: 'totalRounds', type: 'integer', label: 'Number of Rounds', description: '...', default: 3, min: 1, max: 5, step: 1 },
{ key: 'enableFeature', type: 'boolean', label: 'Feature Toggle', description: '...', default: true },
{ key: 'difficulty', type: 'select', label: 'Difficulty', description: '...', default: 'medium', options: ['easy', 'medium', 'hard'] },
];
Flowยถ
Host picks a game โ
GAME_PICKEDaction โ client initializesgameSettingsStatein storeHost opens settings modal โ modifies values โ emits
GAME_UPDATE_SETTINGSServer validates with
validateGameSettings()โ broadcastsGAME_SETTINGS_UPDATEDHost confirms โ emits
GAME_CONFIRM_SETTINGSโ server resolves settings and starts gameHandler reads settings via
this.getSetting(key, fallback)
Validationยถ
lib/rmhbox/game-settings.ts provides:
getDefaultSettings(schema)โ Build defaults object from schemavalidateGameSettings(schema, values)โ Validate and sanitize all valuesmergeGameSettings(schema, current, updates)โ Merge partial updates
Spectator Systemยถ
RMHbox supports two spectator viewing modes:
competitive-individualยถ
All players compete equally; spectators select a player to โfollowโ and see that playerโs individual state. Used for games like Rhyme Time and Wiki-Race where showing everything would be unfair.
Key API:
SPECTATOR_SELECT_PLAYER(C2S) โ Spectator chooses a player to followSPECTATOR_TARGET_STATE(S2C) โ Server sends target player infosendToSpectatorFollowers(targetPlayerId, event, data)โ Forward per-player events to spectators following that playergetSpectatorSnapshot(targetPlayerId?)โ Dispatches to the correct state method based on modegetPhaseTimerSnapshot()โ Returns current phase timer state; sent to the spectator on target switch so the timer immediately reflects the correct remaining time
History & Leaderboard Systemยถ
Match Persistenceยถ
After each game completes, the LeaderboardService persists:
RMHboxMatchโ Match metadata (minigameId, lobbyId, duration, results, gameLog)RMHboxMatchPlayerโ Per-player results (rank, score, wasWinner)RMHboxProfileโ Updated aggregate stats (totalGamesPlayed, totalWins, totalScore, minigameStats)
History Display Registryยถ
lib/rmhbox/history-display-registry.ts defines the HistoryDisplayConfig interface:
DetailComponentโ React component for rendering game log detailssearchableFieldsโ Fields extractable from game logs for searchfilterableFieldsโ Fields for filtering (select, range, boolean)getSummary()โ One-line summary from a game log
All minigames register their history display in lib/rmhbox/history-display-registrations.ts.
History Page UI (app/rmhbox/minigames/[minigameId]/history/page.tsx)ยถ
The history page consumes the registered HistoryDisplayConfig to provide:
Search bar โ Searches player names, dates, and all
searchableFieldsextracted from each matchโsgameLog. Placeholder text lists available searchable fields.Filter controls โ Renders
select-typefilterableFieldsas dropdowns, with options aggregated from all loaded match game logs.Summary display โ Each match entry row shows the
getSummary()result from the game log for quick recall.Detail expansion โ Clicking a match loads the full
gameLogand renders the minigameโsDetailComponent.
The list API (GET /api/rmhbox/history) includes gameLog in list responses to support client-side search, filtering, and summary computation.
REST APIsยถ
GET /api/rmhbox/historyโ Paginated match history with optional match detailGET /api/rmhbox/statsโ Player statisticsGET /api/rmhbox/leaderboardโ Leaderboard data
Database Modelsยถ
Defined in prisma/schema.prisma:
model RMHboxProfile {
id, userId (unique), totalGamesPlayed, totalWins, totalScore,
minigameStats (Json), currentWinStreak, bestWinStreak, createdAt, updatedAt
โ matches: RMHboxMatchPlayer[]
}
model RMHboxMatch {
id, minigameId, lobbyId, startedAt, endedAt, durationMs,
winnerUserId, playerCount, gameLog (Json), results (Json)
โ players: RMHboxMatchPlayer[]
}
model RMHboxMatchPlayer {
id, matchId, profileId, userId, userName, rank, score, wasWinner
}
Theming & Stylingยถ
RMHbox uses CSS custom properties (design tokens) for a fully isolated visual identity. The theme stylesheet is app/rmhbox/rmhbox.css.
Design Token Patternยถ
All RMHbox-specific colors, fonts, and spacing use the --rmhbox- prefix:
.rmhbox-theme {
--rmhbox-bg: #1a1b1e;
--rmhbox-surface: #27282c;
--rmhbox-text: #e8e8ec;
--rmhbox-accent: #6ea8d9;
--rmhbox-success: #7bc88a;
--rmhbox-danger: #d98a8a;
--rmhbox-warning: #d9c36e;
/* ... */
}
Dark/Light Themeยถ
Default is dark theme (
.rmhbox-theme)Light theme activated by adding
.rmhbox-lightclass (.rmhbox-theme.rmhbox-light)Theme toggle is in user settings, persisted via Zustand
Usage in Componentsยถ
Reference tokens via Tailwind arbitrary values:
<div className="bg-(--rmhbox-surface) text-(--rmhbox-text) border-(--rmhbox-border)">
Iconsยถ
The codebase uses Lucide React icons. Minigame icons in MinigameDefinition reference Lucide icon names (e.g., 'mic-vocal', 'shield-check', 'globe', 'brush', 'clapperboard').
Sound Systemยถ
lib/rmhbox/audio.ts provides a simple API using Howler.js:
playSound(name: SoundName)โ Play a sound effect at current volume settingspreloadSounds()โ Preload all sound files
Available sounds: chime, click, countdownBeep, goFanfare, scoreDing, buzzer, victoryFanfare, swoosh
Sound files are stored in public/music/rmhbox/sfx/. Volume respects the userโs masterVolume ร sfxVolume settings from the Zustand store.
Testing Conventionsยถ
Tests are organized in testing/rmhbox/ by implementation phase:
phase-1/โ Core types, schemas, constants, utils, auth, rate limiting, registryphase-2/โ Lobby management (creation, join, leave, host controls, chat, ready-up)phase-3/โ Game coordination (voting, state sync, reconnection, spectators)phase-4/โ Client store, UI components, database, API routes, sound systemphase-5/โ Minigame implementations (Rhyme Time, Undercover Agent, Category Crash, Wiki-Race)phase-6/โ Minigame implementations (Minimalist Masterpiece, Emoji Cinema)
Each phase has a setup.ts file for shared test utilities and mocking. Tests use Vitest as the test runner.
Security state masking tests (security-state-masking.test.ts) exist in each phase directory, verifying that sensitive server state is never leaked to clients.
Code Standards & Maintainability Guidelinesยถ
General Principlesยถ
Modularity โ Every file should have a single, clear responsibility. The RMHbox codebase is organized by domain (lobby management, game coordination, per-minigame logic) with clean boundaries.
Separation of Concerns
Shared code (client + server) lives in
lib/rmhbox/Server-only code lives in
server/rmhbox/UI components live in
components/rmhbox/Page routes live in
app/rmhbox/Each minigame is self-contained in its own subdirectory at each layer
No Cross-Minigame Dependencies โ Minigame implementations must never import from other minigames. All shared functionality lives in the base class or shared utilities.
File Organizationยถ
Barrel exports โ Each minigame server handler directory must have an
index.tsthat exports the handler classConsistent naming โ Files use kebab-case; React components use PascalCase; types/interfaces use PascalCase; constants use UPPER_SNAKE_CASE
File headers โ Every file begins with a JSDoc comment describing its purpose, responsibilities, and relevant reference sections
Section separators โ Use
// โโโ Section Name โโโโcomment dividers within files to organize logical sections
TypeScript Standardsยถ
Strict mode โ TypeScript strict mode is enabled
Explicit typing โ Use explicit types for function parameters, return types (on exported functions), and interface fields
unknownoveranyโ Always preferunknownand narrow with type guards; never useanyunless absolutely unavoidableReadonly where possible โ Mark readonly fields and use
as constfor immutable constantsImport types with
typekeyword โ Useimport type { ... }for type-only imports
Validationยถ
Zod everywhere โ All external inputs (WebSocket payloads, API request bodies, data files) must be validated with Zod schemas before use
Schema co-location โ Shared schemas go in
lib/rmhbox/schemas.ts; minigame-specific schemas go inlib/rmhbox/<minigame-id>/schemas.tsTransform in schemas โ Use Zod
.transform()for normalization (trimming, lowercasing) at the schema level
Error Handlingยถ
Structured errors โ All errors emitted to clients use
RMHboxErrorwith a typedRMHboxErrorCodeTry-catch isolation โ All handler callbacks and timer callbacks are wrapped in try-catch. The
validated()wrapper handles this for event handlersBaseMinigame error isolation โ All
setTimeoutandsetIntervalcallbacks in BaseMinigame are wrapped to catch errors and route them tocontext.onError()Fire-and-forget persistence โ Database writes (match persistence) are async and must not affect game flow on failure
State Managementยถ
Single source of truth โ Server holds authoritative game state; client state is derived from server events
Sequence ordering โ All client state updates respect monotonic
seqnumbersState masking โ
buildClientState()in LobbyManager is the ONLY exit point for state data, ensuring internal fields are never leakedPer-player scoping โ Game state sent to players is scoped via
getStateForPlayer(userId)โ never send a player another playerโs private state
React Component Patternsยถ
'use client'โ All interactive components must have the'use client'directiveLazy loading โ Minigame components are loaded via
React.lazy()in MinigameRendererHooks for shared behavior โ Use custom hooks (
useGameSocket,useHeaderTimer,useMinigameRound) for common patternsZustand selectors โ Use specific selectors like
useRMHboxStore((s) => s.lobby)to minimize re-rendersCleanup on unmount โ Hooks must clean up subscriptions and timers in their cleanup functions
No inline styles except for CSS variables โ Use Tailwind classes with CSS variable references; avoid hardcoded colors
Constantsยถ
Centralized โ All tuning constants (timers, scoring, limits) live in
lib/rmhbox/constants.tsPer-minigame prefix โ Minigame constants use a 2-letter prefix (e.g.,
RT_for Rhyme Time,UA_for Undercover Agent,CC_for Category Crash,WR_for Wiki-Race,MM_for Minimalist Masterpiece,EC_for Emoji Cinema,WW_for Wit-War)Server config โ Server-specific runtime configuration (env vars, ports) lives in
server/rmhbox/config.ts
Adding New Featuresยถ
When adding new features to the RMHbox platform:
Define types in
lib/rmhbox/types.ts(shared) orserver/rmhbox/types.ts(server-only)Add event names to
C2SandS2Cinlib/rmhbox/events.tsCreate Zod schemas in
lib/rmhbox/schemas.tsImplement server logic in the appropriate service
Update the Zustand store reducers if new actions are needed
Add socket event listeners in
lib/rmhbox/socket.tsif new S2C events existCreate/update UI components
Write tests
Update this document if the change affects core architecture or patterns