RMHbox — Wit-War Design Specification¶
Version: 1.0
Last Updated: 2026-02-27
Status: Implemented
Parent Document: info.md
Table of Contents¶
1. Overview¶
Field |
Value |
|---|---|
ID |
|
Display Name |
Wit-War |
Category |
|
Icon |
|
Min Players |
3 |
Max Players |
16 |
Estimated Duration |
240 seconds |
Supports Teams |
No |
Join-in-Progress |
|
Spectator Mode |
|
Tags |
|
Constant Prefix |
|
2. Game Concept¶
Wit-War is a party game where players write funny or clever answers to prompts, then answers face off in head-to-head matchups while other players vote for their favorite. Points are awarded based on vote percentage, and a unanimous vote triggers a special “Wit-Wham!” bonus.
The game emphasizes creativity, humor, and audience appeal. Players who don’t submit answers receive a safety quip (“(no answer submitted)”) that earns no points.
3. Detailed Mechanics¶
3.1 Prompt Assignment¶
At the start of each round:
N prompts are selected from the prompt pool (where N = number of players)
Each player receives exactly
WW_PROMPTS_PER_PLAYER(2) promptsEach prompt is assigned to exactly 2 different players, creating head-to-head matchups
A round-robin pairing algorithm ensures variety in opponent matchups
Previously used prompts are excluded to prevent repetition across rounds
3.2 Writing Phase¶
Players see their assigned prompts and write answers in text areas
Each answer is submitted individually, with a “Lock In All Answers” button for convenience
Maximum answer length:
WW_MAX_ANSWER_LENGTH(200 characters)Answers are trimmed and validated via Zod schema (
SubmitAnswerSchema)A live counter shows how many players have submitted all answers
If a player doesn’t submit before time expires, the safety quip is used
3.3 Voting Phase¶
Matchups are presented one at a time to all players
The prompt is shown with both answers displayed anonymously side by side
Players vote for their preferred answer (the two authors cannot vote on their own matchup)
Vote casting is validated via Zod schema (
CastVoteSchema)A live counter shows voting progress
Authors see a “sit back and watch” spectating message
3.4 Matchup Resolution¶
After voting closes for each matchup:
Vote percentages are calculated for each answer
The winner is the answer with more votes (ties result in no winner)
A “Wit-Wham!” bonus is triggered when one answer gets 100% of the votes (minimum 2 voters)
Points are awarded based on vote percentage (proportional to
WW_MAX_MATCHUP_POINTS)Safety quip answers always receive 0 points
Results are revealed with vote percentage bars and author names
4. Phases¶
Each round follows this sequence:
PROMPT_REVEAL → WRITING → VOTING (per matchup) → MATCHUP_RESULTS (per matchup) → ROUND_RESULTS
After the final round: ROUND_RESULTS → GAME_OVER
4.1 PROMPT_REVEAL¶
Duration:
WW_PROMPT_REVEAL_DURATION(3 seconds)Each player receives their assigned prompts via per-player action (
WW_PROMPT_REVEAL)Animated entrance of prompt cards
Phase change broadcasted to all players
4.2 WRITING¶
Duration:
WW_WRITING_DURATION(60 seconds, configurable)Phase timer displayed in header
Players write and submit answers individually
Submit count broadcasted in real-time (
WW_SUBMIT_COUNT)Per-player acceptance confirmation (
WW_ANSWER_ACCEPTED)Auto-submit on timer expiry: When the timer expires, the server broadcasts
WW_WRITING_ENDand the client auto-submits all typed but unsent answers. The server allows a 1-second grace period for these late submissions before filling in safety quips for any still-missing answers.
4.3 VOTING¶
Duration:
WW_VOTING_DURATION(15 seconds per matchup, configurable)Phase timer displayed per matchup
One matchup at a time, cycled through sequentially
Live vote count broadcasted (
WW_VOTE_COUNT)Early end: When all eligible voters (total players minus the 2 matchup authors) have voted, the voting phase ends immediately without waiting for the timer.
4.4 MATCHUP_RESULTS¶
Duration:
WW_MATCHUP_RESULTS_DURATION(5 seconds)Vote percentages revealed with animated bar
Author names revealed
Winner highlighted; “Wit-Wham!” banner for unanimous wins
Updated cumulative scores broadcasted
4.5 ROUND_RESULTS¶
Duration:
WW_ROUND_RESULTS_DURATION(8 seconds)Phase timer displayed
Leaderboard with cumulative scores
Summary of all matchups from the round
Automatic transition to next round or game end
4.6 GAME_OVER¶
Final results computed (rankings, awards)
Game completion signaled via
onComplete()
5. Scoring¶
Event |
Points |
|---|---|
Answer vote share |
|
Wit-Wham! bonus (100% of votes, ≥2 voters) |
|
Safety quip (no answer) |
0 (always) |
Points are cumulative across rounds. Ties in voting result in a 50/50 split (500 points each if non-safety).
6. Constants¶
All constants are defined in lib/rmhbox/constants.ts with the WW_ prefix:
Constant |
Value |
Description |
|---|---|---|
|
2 |
Default number of rounds |
|
2 |
Prompts assigned to each player per round |
|
60 |
Writing phase duration (seconds) |
|
15 |
Voting duration per matchup (seconds) |
|
5 |
Matchup results display duration (seconds) |
|
8 |
Round results display duration (seconds) |
|
3 |
Prompt reveal animation duration (seconds) |
|
200 |
Maximum characters per answer |
|
1000 |
Maximum points per matchup |
|
500 |
Bonus for unanimous victory |
|
|
Placeholder for missing answers |
7. Server Handler¶
7.1 Class: WitWarMinigame¶
Extends
BaseMinigameFile:
server/rmhbox/minigames/wit-war/handler.tsRegistered in
MINIGAME_SERVER_REGISTRYas['wit-war', WitWarMinigame]
7.2 Abstract Method Implementations¶
Method |
Implementation |
|---|---|
|
Initializes state, starts first round |
|
Routes |
|
Returns phase-specific scoped state |
|
Returns omniscient view with all prompts, answers, and votes |
|
Computes rankings, awards, and game log |
|
Returns |
7.3 Action Types (Server → Client)¶
Action Type |
Description |
|---|---|
|
Per-player prompt assignments |
|
Phase transition notification |
|
Writing phase begins |
|
Writing phase ends |
|
Updated submission count |
|
Per-player answer confirmation |
|
Voting matchup begins |
|
Updated vote count |
|
Matchup results with scores |
|
Round results with leaderboard |
7.4 Input Actions (Client → Server)¶
Action |
Schema |
Description |
|---|---|---|
|
|
Submit answer for a prompt |
|
|
Vote on a matchup |
7.5 Phase Enum: WitWarPhase¶
enum WitWarPhase {
PROMPT_REVEAL = 'PROMPT_REVEAL',
WRITING = 'WRITING',
VOTING = 'VOTING',
MATCHUP_RESULTS = 'MATCHUP_RESULTS',
ROUND_RESULTS = 'ROUND_RESULTS',
GAME_OVER = 'GAME_OVER',
}
7.6 Reconnection¶
Reconnection is handled by the base class buildReconnectionSnapshot(), which dispatches to getStateForPlayer() for players and getStateForSpectator() for spectators. The handler overrides handlePlayerDisconnect() for logging purposes only.
8. Client Components¶
8.1 WitWarGame.tsx (Main Component)¶
Phase router that renders sub-components based on current phase
Uses
useGameSocket()hook for socket event subscription and state hydrationUses
emitGameInput()fromminigame-client.tsfor sending inputUses
extractTimerTick()for TIMER_TICK handlingUses
playSound()fromaudio.tsfor sound effectsAccepts
MinigameProps(playerId,playerName)
8.2 Sub-Components¶
Component |
Phase |
Description |
|---|---|---|
|
PROMPT_REVEAL |
Animated prompt card display |
|
WRITING |
Text inputs with character counters |
|
VOTING |
Head-to-head anonymous voting |
|
MATCHUP_RESULTS |
Vote bar reveal with author names |
|
ROUND_RESULTS / GAME_OVER |
Leaderboard and matchup summary |
|
History |
Per-round matchup replay |
9. Spectator Mode¶
Wit-War uses shared-privileged spectator mode. Spectators see the same omniscient view including:
Current phase, round, and score information
All prompt assignments and answers for all players
Current matchup details with answers from both players
Vote counts and percentages
Full matchup results
Spectator state is sent during prompt reveals and voting phase starts, and is available via getStateForSpectator() for reconnection. The client’s handleStateSnapshot reconstructs matchup data from the spectator snapshot (using the currentMatchup field) to support mid-game spectator joins and reconnections.
10. Sound Effects¶
The client plays sound effects at key moments using playSound() from lib/rmhbox/audio.ts:
Moment |
Sound |
|---|---|
Prompt reveal (new round) |
|
Writing phase starts |
|
Answer accepted |
|
New matchup starts |
|
Vote cast |
|
Matchup result revealed |
|
Wit-Wham! unanimous win |
|
Round results |
|
Timer countdown (≤5 seconds) |
|
11. Game Settings¶
Host-configurable settings (defined as WIT_WAR_SETTINGS in lib/rmhbox/minigame-registry.ts):
Setting |
Type |
Default |
Range |
Description |
|---|---|---|---|---|
|
integer |
2 |
1–3 |
Number of rounds |
|
integer |
60 |
30–120 |
Writing phase duration (seconds) |
|
integer |
15 |
10–30 |
Voting duration per matchup (seconds) |
Settings are read via this.getSetting(key, fallback) in the handler.
12. History & Awards¶
12.1 Game Log¶
The game log stored in match history has type 'wit-war' and includes:
initialState—totalRounds,playerCountactions— Sequenced log ofprompt_reveal,writing_end,answer_submitted,matchup_resolvedevents. Eachmatchup_resolvedaction includes aroundfield for proper round-grouping in the history viewer.
12.2 History Display¶
Registered in lib/rmhbox/history-display-registrations.ts with:
DetailComponent:
WitWarHistoryDetail— Per-round matchup replay viewerSearchable fields: Prompts, Answers
Filterable fields: Had Wit-Wham! (boolean), Matchup Wins (range)
getSummary: “X matchups — Y Wit-Whams”
12.3 Awards¶
Award |
Criteria |
Icon |
|---|---|---|
Crowd Pleaser |
Highest average vote percentage per matchup |
|
Wit-Wham! |
Most unanimous matchup wins |
|
Dark Horse |
Won a single matchup with >75% of the vote |
|
13. Data Files¶
Location:
data/rmhbox/wit-war/prompts.jsonFormat: JSON array of prompt strings
Loading:
lib/rmhbox/wit-war/data-loader.tswith filesystem cachingSelection: Fisher-Yates shuffle with used-index exclusion
14. File Structure¶
lib/rmhbox/wit-war/
├── schemas.ts # SubmitAnswerSchema, CastVoteSchema
└── data-loader.ts # loadPrompts(), selectRoundPrompts(), assignPromptsToPlayers()
server/rmhbox/minigames/wit-war/
├── index.ts # Barrel export
├── handler.ts # WitWarMinigame (extends BaseMinigame)
└── types.ts # WitWarPhase, WWMatchup, WWPromptAssignment, WitWarState
components/rmhbox/minigames/wit-war/
├── WitWarGame.tsx # Main phase router
├── PromptReveal.tsx # Animated prompt display
├── WritingPhase.tsx # Answer text inputs
├── VotingPhase.tsx # Head-to-head voting
├── MatchupResult.tsx # Vote result reveal
├── WitWarResults.tsx # Leaderboard + matchup summary
└── WitWarHistoryDetail.tsx # History detail view
data/rmhbox/wit-war/
└── prompts.json # Static prompt data
Registration Points¶
lib/rmhbox/constants.ts—WW_*constantslib/rmhbox/minigame-registry.ts—MINIGAME_REGISTRY['wit-war']+WIT_WAR_SETTINGSserver/rmhbox/game-coordinator.ts—MINIGAME_SERVER_REGISTRY['wit-war']components/rmhbox/minigames/MinigameRenderer.tsx—MINIGAME_COMPONENTS['wit-war']lib/rmhbox/history-display-registrations.ts— History display config