diff --git a/apps/web/.claude/ARCADE_ROUTING_ARCHITECTURE.md b/apps/web/.claude/ARCADE_ROUTING_ARCHITECTURE.md
new file mode 100644
index 00000000..7cd5e810
--- /dev/null
+++ b/apps/web/.claude/ARCADE_ROUTING_ARCHITECTURE.md
@@ -0,0 +1,440 @@
+# Arcade Routing Architecture - Complete Overview
+
+## 1. Current /arcade Page
+
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/app/arcade/page.tsx` (lines 1-129)
+
+**Purpose:** The main arcade landing page - displays the "Champion Arena"
+
+**Key Components:**
+- `ArcadeContent()` - Renders the main arcade interface
+ - Uses `EnhancedChampionArena` component which contains `GameSelector`
+ - The `GameSelector` displays all available games as cards
+ - `GameSelector` includes both legacy games and registry games
+
+**Current Flow:**
+1. User navigates to `/arcade`
+2. Page renders `FullscreenProvider` wrapper
+3. Displays `PageWithNav` with title "🏟️ Champion Arena"
+4. Content area shows `EnhancedChampionArena` → `GameSelector`
+5. `GameSelector` renders `GameCard` components for each game
+6. When user clicks a game card, `GameCard` calls `router.push(config.url)`
+7. For registry games, `config.url` is `/arcade/room?game={gameName}`
+8. For legacy games, URL would be direct to their page
+
+**State Management:**
+- `GameModeContext` provides player selection (emoji, name, color)
+- `PageWithNav` wraps content and provides mini-nav with:
+ - Active player list
+ - Add player button
+ - Game mode indicator (single/battle/tournament)
+ - Exit session handler
+
+## 2. Current /arcade/room Page
+
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/app/arcade/room/page.tsx` (lines 1-359)
+
+**Purpose:** "Magical place" that shows either a game OR the game chooser, driven by room state
+
+**Three States:**
+
+### State 1: Loading
+- Shows "Loading room..." message
+- Waits for `useRoomData()` hook to resolve
+
+### State 2: Game Selection UI (when `!roomData.gameName`)
+- Shows large game selection buttons
+- User clicks to select a game
+- Calls `setRoomGame()` mutation to save selection to room
+- Invokes `handleGameSelect()` which:
+ 1. Checks if game exists in registry via `hasGame(gameType)`
+ 2. If registry game: calls `setRoomGame({roomId, gameName: gameType})`
+ 3. If legacy game: maps to internal name via `GAME_TYPE_TO_NAME`, then calls `setRoomGame()`
+ 4. Game selection is persisted to the room database
+
+### State 3: Game Display (when `roomData.gameName` is set)
+- Checks game registry first via `hasGame(roomData.gameName)`
+- If registry game:
+ - Gets game definition via `getGame(roomData.gameName)`
+ - Renders: ``
+ - Provider and GameComponent come from game registry definition
+- If legacy game:
+ - Switch statement with TODO for individual games
+ - Currently only shows "Game not yet supported"
+
+**Key Hook:**
+- `useRoomData()` - Fetches current room from API and subscribes to socket updates
+ - Returns `roomData` with fields: `id`, `name`, `code`, `gameName`, `gameConfig`, `members`, `memberPlayers`
+ - Also returns `isLoading` boolean
+
+**Navigation Flow:**
+1. User navigates to `/arcade`
+2. `GameCard` onClick calls `router.push('/arcade/room?game={gameName}')`
+3. User arrives at `/arcade/room`
+4. If NOT in a room yet: Shows error with link back to `/arcade`
+5. If in room but no game selected: Shows game selection UI
+6. If game selected: Loads and displays game
+
+## 3. The "Mini App Nav" - GameContextNav Component
+
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/nav/GameContextNav.tsx` (lines 1-372)
+
+**What It Is:**
+The "mini app nav" is actually a sophisticated component within the `PageWithNav` wrapper that intelligently shows different UI based on context:
+
+**Components & Props:**
+- `navTitle` - Current page title (e.g., "Champion Arena", "Choose Game", "Speed Complement Race")
+- `navEmoji` - Icon emoji for current page
+- `gameMode` - Computed from active player count: 'none' | 'single' | 'battle' | 'tournament'
+- `activePlayers` - Array of selected players
+- `inactivePlayers` - Array of available but unselected players
+- `shouldEmphasize` - Boolean to emphasize player selection
+- `showFullscreenSelection` - Boolean to show fullscreen mode for player selection
+- `roomInfo` - Optional arcade room data (roomId, roomName, gameName, playerCount, joinCode)
+- `networkPlayers` - Remote players from room members
+
+**Three Display Modes:**
+
+### Mode 1: Fullscreen Player Selection
+- When `showFullscreenSelection === true`
+- Displays:
+ - Large title with emoji
+ - Game mode indicator
+ - Fullscreen player selection UI
+ - Shows all inactive players for selection
+
+### Mode 2: Solo Mode (NOT in room)
+- When `roomInfo` is undefined
+- Shows:
+ - **Game Title Section** (left side):
+ - `GameTitleMenu` with game title and emoji
+ - Menu options: Setup, New Game, Quit
+ - `GameModeIndicator`
+ - **Player Section** (right side):
+ - `ActivePlayersList` - shows selected players
+ - `AddPlayerButton` - add more players
+
+### Mode 3: Room Mode (IN a room)
+- When `roomInfo` is defined
+- Shows:
+ - **Hidden:** Game title section (display: none)
+ - **Room Info Pane** (left side):
+ - `RoomInfo` component with room details
+ - Game mode indicator with color/emoji
+ - Room name, player count, join code
+ - `NetworkPlayerIndicator` components for remote players
+ - **Player Section** (may be hidden):
+ - Shows local active players
+ - Add player button (for local players only)
+
+**Key Sub-Components:**
+- `GameTitleMenu` - Menu for game options (setup, new game, quit)
+- `GameModeIndicator` - Shows 🎯 Solo, ⚔️ Battle, 🏆 Tournament, 👥 Select
+- `RoomInfo` - Displays room metadata
+- `NetworkPlayerIndicator` - Shows remote players with scores/streaks
+- `ActivePlayersList` - List of selected players
+- `AddPlayerButton` - Button to add more players with popover
+- `FullscreenPlayerSelection` - Large player picker for fullscreen mode
+- `PendingInvitations` - Banner for room invitations
+
+**State Management:**
+- Lifted from `PageWithNav` to preserve state across remounts:
+ - `showPopover` / `setShowPopover` - AddPlayerButton popover state
+ - `activeTab` / `setActiveTab` - 'add' or 'invite' tab selection
+
+## 4. Navigation Flow
+
+### Flow 1: Solo Player → Game Selection → Room Creation → Game Start
+
+```
+/arcade (Champion Arena)
+ ↓ [Select players - updates GameModeContext]
+ ↓ [Click game card - GameCard.onClick → router.push]
+/arcade/room (if not in room, shows game selector)
+ ↓ [Select game - calls setRoomGame mutation]
+ ↓ [Room created, gameName saved to roomData]
+ ↓ [useRoomData refetch updates roomData.gameName]
+/arcade/room (now displays the game)
+ ↓ [Game Provider and Component render]
+```
+
+### Flow 2: Multiplayer - Room Invitation
+
+```
+User A: Creates room via Champion Arena
+User B: Receives invitation
+User B: Joins room via /arcade/room
+User B: Sees same game selection (if set) or game selector (if not set)
+```
+
+### Flow 3: Exit Game
+
+```
+/arcade/room (in-game)
+ ↓ [Click "Quit" or "Exit Session" in GameContextNav]
+ ↓ [onExitSession callback → router.push('/arcade')]
+/arcade (back to champion arena)
+ ↓ Player selection reset by GameModeContext
+```
+
+## 5. Game Chooser / Game Selection System
+
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/GameSelector.tsx` (lines 1-112)
+
+**How It Works:**
+1. `GameSelector` component gets all games from both sources:
+ - Legacy `GAMES_CONFIG` (currently empty)
+ - Registry games via `getAllGames()`
+
+2. For each game, creates `GameCard` component with configuration including `url` field
+
+3. Game Cards rendered in 2-column grid (responsive)
+
+4. When card clicked:
+ - `GameCard` checks `activePlayerCount` against game's `maxPlayers`
+ - If valid: calls `router.push(config.url)` - client-side navigation via Next.js
+ - If invalid: blocks navigation with warning
+
+**Two Game Systems:**
+
+### Registry Games (NEW - Modular)
+- Location: `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/arcade-games/`
+- File: `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/lib/arcade/game-registry.ts`
+- Examples: `complement-race`, `memory-quiz`, `matching`
+- Each game has: `manifest` (metadata), `Provider` (context), `GameComponent` (UI)
+- Games registered globally via `registerGame()` function
+
+### Legacy Games (OLD)
+- Location: Directly in `/app/arcade/` directory
+- Examples: `/app/arcade/complement-race/page.tsx`
+- Currently, only complement-race is partially migrated
+- Direct URL structure: `/arcade/{gameName}/page.tsx`
+
+**Game Config Structure (for display):**
+```javascript
+{
+ name: string, // Display name
+ fullName?: string, // Longer name for detailed view
+ description: string, // Short description
+ longDescription?: string, // Detailed description
+ icon: emoji, // Game icon emoji
+ gradient: css gradient, // Background gradient
+ borderColor: css color, // Border color for availability
+ maxPlayers: number, // Player limit for validation
+ chips?: string[], // Feature labels
+ color?: 'green'|'purple'|'blue', // Color theme
+ difficulty?: string, // Difficulty level
+ available: boolean, // Is game available
+}
+```
+
+## 6. Key Components Summary
+
+### PageWithNav - Main Layout Wrapper
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/PageWithNav.tsx` (lines 1-192)
+
+**Responsibilities:**
+- Wraps all game/arcade pages
+- Manages GameContextNav state (mini-nav)
+- Handles player configuration dialog
+- Shows moderation notifications
+- Renders top navigation bar via `AppNavBar`
+
+**Key Props:**
+- `navTitle` - Passed to GameContextNav
+- `navEmoji` - Passed to GameContextNav
+- `gameName` - Internal game name for API
+- `emphasizePlayerSelection` - Highlight player controls
+- `onExitSession` - Callback when user exits
+- `onSetup`, `onNewGame` - Game-specific callbacks
+- `children` - Page content
+
+### AppNavBar - Top Navigation Bar
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/AppNavBar.tsx` (lines 1-625)
+
+**Variants:**
+- `full` - Standard navigation (default for non-game pages)
+- `minimal` - Game navigation (auto-selected for `/arcade` and `/games`)
+
+**Minimal Nav Features:**
+- Hamburger menu (left) with:
+ - Site navigation (Home, Create, Guide, Games)
+ - Controls (Fullscreen, Exit Arcade)
+ - Abacus style dropdown
+- Centered game context (navSlot)
+- Fullscreen indicator badge
+
+### EnhancedChampionArena - Main Arcade Display
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/EnhancedChampionArena.tsx` (lines 1-40)
+
+**Responsibilities:**
+- Container for game selector
+- Full-height flex layout
+- Passes configuration to `GameSelector`
+
+### GameSelector - Game Grid
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/GameSelector.tsx` (lines 1-112)
+
+**Responsibilities:**
+- Fetches all games from registry
+- Arranges in responsive grid
+- Shows header "🎮 Available Games"
+- Renders GameCard for each game
+
+### GameCard - Individual Game Button
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/GameCard.tsx` (lines 1-241)
+
+**Responsibilities:**
+- Displays game with icon, name, description
+- Shows feature chips and player count indicator
+- Validates player count against game requirements
+- Handles click to navigate to game
+- Two variants: compact and detailed
+
+## 7. State Management
+
+### GameModeContext
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/contexts/GameModeContext.tsx` (lines 1-325)
+
+**Manages:**
+- Local players (Map)
+- Active players (Set)
+- Game mode (computed from active player count)
+- Player CRUD operations (add, update, remove)
+
+**Key Features:**
+- Fetches players from user's local DB via `useUserPlayers()`
+- Creates 4 default players if none exist
+- When in room: merges room members' players (marked as isLocal: false)
+- Syncs to room members via `notifyRoomOfPlayerUpdate()`
+
+**Computed Values:**
+- `activePlayerCount` - Size of activePlayers set
+- `gameMode`:
+ - 1 player → 'single'
+ - 2 players → 'battle'
+ - 3+ players → 'tournament'
+
+### useRoomData Hook
+**File:** `/Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/hooks/useRoomData.ts` (lines 1-450+)
+
+**Manages:**
+- Current room fetching via TanStack Query
+- Socket.io real-time updates
+- Room state (members, players, game name)
+- Moderation events (kicked, banned, invitations)
+
+**Key Operations:**
+- `fetchCurrentRoom()` - GET `/api/arcade/rooms/current`
+- `createRoomApi()` - POST `/api/arcade/rooms`
+- `joinRoomApi()` - POST `/api/arcade/rooms/{id}/join`
+- `leaveRoomApi()` - POST `/api/arcade/rooms/{id}/leave`
+- `setRoomGame()` - Updates room's gameName and gameConfig
+
+**Socket Events:**
+- `join-user-channel` - Personal notifications
+- `join-room` - Subscribe to room updates
+- `room-joined` - Refresh when entering room
+- `member-joined` - When player joins
+- `member-left` - When player leaves
+- `room-players-updated` - When players change
+- Moderation events (kicked, banned, etc.)
+
+## 8. Routing Summary
+
+**Current URL Structure:**
+
+```
+/ → Home page (Soroban Generator)
+/create → Create flashcards
+/guide → Tutorial guide
+/games → Games library (external game pages)
+/arcade → Champion Arena (main landing with game selector)
+/arcade/room → Active game display or game selection UI
+/arcade/room?game={name} → Query param for game selection (optional)
+/arcade/complement-race → OLD: Direct complement-race page (legacy)
+/arcade/complement-race/practice → Complement-race practice mode
+/arcade/complement-race/sprint → Complement-race sprint mode
+/arcade/complement-race/survival → Complement-race survival mode
+/arcade/memory-quiz → Memory quiz game page (legacy structure)
+```
+
+**Query Parameters:**
+- `/arcade/room?game={gameName}` - Optional game selection (parsed by GameCard)
+
+## 9. Key Differences: /arcade vs /arcade/room
+
+| Aspect | /arcade | /arcade/room |
+|--------|---------|--------------|
+| **Purpose** | Game selection hub | Active game display or selection within room |
+| **Displays** | GameSelector with all games | Selected game OR game selector if no game in room |
+| **Room Context** | Optional (can start solo) | Usually in a room (fetches via useRoomData) |
+| **Navigation** | Click game → /arcade/room | Click game → Saves to room → Displays game |
+| **GameContextNav** | Shows player selector | Shows room info when joined |
+| **Player State** | Local only | Local + remote (room members) |
+| **Exit Button** | Usually hidden | Shows "Exit Session" to return to /arcade |
+| **Socket Connection** | Optional | Always connected (in room) |
+| **Page Transition** | User controls | Driven by room state updates |
+
+## 10. Planning the Merge (/arcade/room → /arcade)
+
+**Challenges to Consider:**
+
+1. **URL Consolidation:**
+ - `/arcade/room` would become a sub-path or handled by `/arcade` with state
+ - Query param `?game={name}` could drive game selection
+ - Current: `/arcade/room?game=complement-race`
+ - Could become: `/arcade?game=complement-race&mode=play`
+
+2. **Route Disambiguation:**
+ - `/arcade` needs to handle: game selection display, game display, game loading
+ - Same page different modes based on state
+ - Or: Sub-routes like `/arcade/select`, `/arcade/play`
+
+3. **State Layering:**
+ - Local game mode (solo player, GameModeContext)
+ - Room state (multiplayer, useRoomData)
+ - Both need to coexist
+
+4. **Navigation Preservation:**
+ - Currently: `GameCard` → `router.push('/arcade/room?game=X')`
+ - After merge: Would need new logic
+ - Fullscreen state must persist (uses Next.js router, not reload)
+
+5. **PageWithNav Behavior:**
+ - Mini-nav shows game selection UI vs room info
+ - Currently determined by `roomInfo` presence
+ - After merge: Need same logic but one route
+
+6. **Game Display:**
+ - Currently: `/arcade/room` fetches game from registry
+ - New: `/arcade` would need same game registry lookup
+ - Game Provider/Component rendering must work identically
+
+**Merge Strategy Options:**
+
+### Option A: Single Route with Modes
+```
+/arcade
+├── Mode: browse (default, show GameSelector)
+├── Mode: select (game selected, show GameSelector for confirmation)
+└── Mode: play (in-game, show game display)
+```
+
+### Option B: Sub-routes
+```
+/arcade
+├── /arcade (selector)
+├── /arcade/play (game display)
+└── /arcade/configure (player config)
+```
+
+### Option C: Query-Parameter Driven
+```
+/arcade
+├── /arcade (default - selector)
+├── /arcade?game=X (game loading)
+└── /arcade?game=X&playing=true (in-game)
+```
+
+**Recommendation:** Option C (Query-driven) is closest to current architecture and requires minimal changes to existing logic.
diff --git a/apps/web/src/arcade-games/card-sorting/Provider.tsx b/apps/web/src/arcade-games/card-sorting/Provider.tsx
index 9030c9d6..528f40fc 100644
--- a/apps/web/src/arcade-games/card-sorting/Provider.tsx
+++ b/apps/web/src/arcade-games/card-sorting/Provider.tsx
@@ -1,13 +1,6 @@
'use client'
-import {
- type ReactNode,
- useCallback,
- useMemo,
- createContext,
- useContext,
- useState,
-} from 'react'
+import { type ReactNode, useCallback, useMemo, createContext, useContext, useState } from 'react'
import { useArcadeSession } from '@/hooks/useArcadeSession'
import { useRoomData, useUpdateGameConfig } from '@/hooks/useRoomData'
import { useViewerId } from '@/hooks/useViewerId'
diff --git a/apps/web/src/components/GameSelector.tsx b/apps/web/src/components/GameSelector.tsx
index c3ae8a21..5de09f81 100644
--- a/apps/web/src/components/GameSelector.tsx
+++ b/apps/web/src/components/GameSelector.tsx
@@ -31,7 +31,7 @@ function getAllGameConfigs() {
maxPlayers: gameDef.manifest.maxPlayers,
description: gameDef.manifest.description,
longDescription: gameDef.manifest.longDescription,
- url: `/arcade/room?game=${gameDef.manifest.name}`, // Registry games load in room
+ url: '/arcade/room', // Room page handles game selection through UI
icon: gameDef.manifest.icon,
chips: gameDef.manifest.chips,
color: gameDef.manifest.color,