diff --git a/apps/web/.claude/ARCADE_ARCHITECTURE.md b/apps/web/.claude/ARCADE_ARCHITECTURE.md index 99c17a44..c44d5a7c 100644 --- a/apps/web/.claude/ARCADE_ARCHITECTURE.md +++ b/apps/web/.claude/ARCADE_ARCHITECTURE.md @@ -16,6 +16,7 @@ Following `docs/terminology-user-player-room.md`: - **PLAYER ROSTER** - All PLAYERS belonging to a USER (can have many) - **ACTIVE PLAYERS** - PLAYERS where `isActive = true` - these are the ones that actually participate in games - **ROOM MEMBER** - A USER's participation in a multiplayer room (tracked in `room_members` table) +- **SPECTATOR** - A room member who watches another player's game without participating (see Spectator Mode section) **Important:** A USER can have many PLAYERS in their roster, but only the ACTIVE PLAYERS (where `isActive = true`) participate in games. This enables "hot-potato" style local multiplayer where multiple people share the same device. This is LOCAL play (not networked), even though multiple PLAYERS participate. @@ -27,25 +28,46 @@ In arcade sessions: ## Critical Architectural Requirements -### 1. Mode Isolation (MUST ENFORCE) +### 1. Game Synchronization Modes -**Local Play** (`/arcade/[game-name]`) +The arcade system supports three synchronization patterns: + +#### Local Play (No Network Sync) +**Route**: Custom route or dedicated local page +**Use Case**: Practice, offline play, or games that should never be visible to others - MUST NOT sync game state across the network - MUST NOT use room data, even if the USER is currently a member of an active room -- MUST create isolated, per-USER game sessions +- MUST pass `roomId: undefined` to `useArcadeSession` - Game state lives only in the current browser tab/session - CAN have multiple ACTIVE PLAYERS from the same USER (local multiplayer / hot-potato) - State is NOT shared across the network, only within the browser session -**Room-Based Play** (`/arcade/room`) +#### Room-Based with Spectator Mode (RECOMMENDED PATTERN) +**Route**: `/arcade/room` (or use room context anywhere) +**Use Case**: Most arcade games - enables spectating even for single-player games -- MUST sync game state across all room members via network -- MUST use the USER's current active room -- MUST coordinate moves via server WebSocket +- SYNCS game state across all room members via network +- Uses the USER's current active room (`roomId: roomData?.id`) +- Coordinates moves via server WebSocket - Game state is shared across all ACTIVE PLAYERS from all USERS in the room -- When a PLAYER makes a move, all room members see it in real-time -- CAN ALSO have multiple ACTIVE PLAYERS per USER (networked + local multiplayer combined) +- **Non-playing room members become SPECTATORS** (see Spectator Mode section) +- When a PLAYER makes a move, all room members see it in real-time (players + spectators) +- CAN have multiple ACTIVE PLAYERS per USER (networked + local multiplayer combined) + +**✅ This is the PREFERRED pattern** - even for single-player games like Card Sorting, because: +- Enables spectator mode automatically +- Creates social experience ("watch me solve this!") +- No extra code needed +- Works seamlessly with multiplayer games too + +#### Pure Multiplayer (Room-Only) +**Route**: `/arcade/room` with validation +**Use Case**: Games that REQUIRE multiple players (e.g., competitive battles) + +- Same as Room-Based with Spectator Mode +- Plus: Validates minimum player count before starting +- Plus: May prevent game start if `activePlayers.length < minPlayers` ### 2. Room ID Usage Rules @@ -258,6 +280,327 @@ sendMove({ - Mode: Room-based play (roomId: "room_xyz") - 5 total active PLAYERS across 2 devices, all synced over network +5. **Single-player game with spectators (Card Sorting):** + - USER A: "guest_abc" + - Active PLAYERS: ["player_001"] + - Playing Card Sorting Challenge + - USER B: "guest_def" + - Active PLAYERS: [] (none selected) + - Spectating USER A's game + - USER C: "guest_ghi" + - Active PLAYERS: ["player_005"] + - Spectating USER A's game (could play after USER A finishes) + - Mode: Room-based play (roomId: "room_xyz") + - All room members see USER A's card placements in real-time + - Spectators cannot interact with the game state + +## Spectator Mode + +### Overview + +Spectator mode is automatically enabled when using room-based sync (`roomId: roomData?.id`). Any room member who is not actively playing becomes a spectator and can watch the game in real-time. + +**Key Benefits**: +- Creates social/collaborative experience even for single-player games +- "Watch me solve this!" engagement +- Learning by observation +- Cheering/coaching opportunity +- No extra implementation needed + +### How It Works + +1. **Automatic Role Assignment**: + - Room members with active PLAYERs in the game → **Players** + - Room members without active PLAYERs in the game → **Spectators** + +2. **State Synchronization**: + - All game state updates broadcast to entire room via `game:${roomId}` socket room + - Spectators receive same state updates as players + - Spectators see game in real-time as it happens + +3. **Interaction Control**: + - Players can make moves (send move actions) + - Spectators can only observe (no move actions permitted) + - Server validates PLAYER ownership before accepting moves + +### Implementation Pattern + +**Provider** (Room-Based with Spectator Support): + +```typescript +export function CardSortingProvider({ children }: { children: ReactNode }) { + const { data: viewerId } = useViewerId() + const { roomData } = useRoomData() // ✅ Fetch room data + const { activePlayers, players } = useGameMode() + + // Find local player (if any) + const localPlayerId = useMemo(() => { + return Array.from(activePlayers).find((id) => { + const player = players.get(id) + return player?.isLocal !== false + }) + }, [activePlayers, players]) + + const { state, sendMove, exitSession } = useArcadeSession({ + userId: viewerId || '', + roomId: roomData?.id, // ✅ Enable spectator mode + initialState, + applyMove: applyMoveOptimistically, + }) + + // Actions check if local player exists before allowing moves + const startGame = useCallback(() => { + if (!localPlayerId) { + console.warn('[CardSorting] No local player - spectating only') + return // ✅ Spectators cannot start game + } + sendMove({ type: 'START_GAME', playerId: localPlayerId, ... }) + }, [localPlayerId, sendMove]) + + // ... rest of provider +} +``` + +**Key Implementation Points**: +- Always check `if (!localPlayerId)` before allowing moves +- Return early or show "Spectating..." message +- Don't throw errors - spectating is a valid state +- UI should indicate spectator vs player role + +### UI/UX Considerations + +#### 1. Spectator Indicators + +Show visual feedback when user is spectating: + +```typescript +{!localPlayerId && state.gamePhase === 'playing' && ( +
+ 👀 Spectating {state.playerMetadata.name}'s game +
+)} +``` + +#### 2. Disabled Controls + +Disable interactive elements for spectators: + +```typescript + +``` + +#### 3. Join Prompt + +For games that support multiple players, show "Join Game" option: + +```typescript +{!localPlayerId && state.gamePhase === 'setup' && ( + +)} +``` + +#### 4. Real-Time Updates + +Ensure spectators see smooth updates: +- Use optimistic UI updates (same as players) +- Show animations for state changes +- Display current player's moves as they happen + +### When to Use Spectator Mode + +**✅ Use Spectator Mode (room-based sync) For**: +- Single-player puzzle games (Card Sorting, Sudoku, etc.) +- Turn-based competitive games (Matching Pairs Battle) +- Cooperative games (Memory Lightning) +- Any game where watching is educational/entertaining +- Social/family game nights +- Classroom settings (teacher demonstrates, students watch) + +**❌ Avoid Spectator Mode (use local-only) For**: +- Private practice sessions +- Timed competitive games where watching gives unfair advantage +- Games with personal/sensitive content +- Offline/no-network scenarios +- Performance-critical games (reduce network overhead) + +### Example Scenarios + +#### Scenario 1: Family Game Night - Card Sorting + +``` +Room: "Smith Family Game Night" + +USER A (Dad): Playing Card Sorting + - Active PLAYER: "Dad 👨" + - State: Placing cards, 6/8 complete + - Can interact with game + +USER B (Mom): Spectating + - Active PLAYERS: [] (none selected) + - State: Sees Dad's card placements in real-time + - Cannot place cards + - Can cheer and help + +USER C (Kid): Spectating + - Active PLAYER: "Emma 👧" (selected but not in this game) + - State: Watching to learn strategy + - Will play next round + +Flow: +1. Dad starts Card Sorting +2. Mom and Kid see setup phase +3. Dad places cards one by one +4. Mom/Kid see each placement instantly +5. Dad checks solution +6. Everyone sees the score together +7. Kid says "My turn!" and starts their own game +8. Dad and Mom become spectators +``` + +#### Scenario 2: Classroom - Memory Lightning + +``` +Room: "Ms. Johnson's 3rd Grade" + +USER A (Teacher): Playing cooperatively with 2 students + - Active PLAYERS: ["Teacher 👩‍🏫", "Student 1 👦"] + - State: Memorizing cards + - Both can participate + +USER B-F (5 other students): Spectating + - Watching demonstration + - Learning the rules + - Will join next round + +Flow: +1. Teacher demonstrates with 2 students +2. Other students watch and learn +3. Round ends +4. Teacher sets up new round +5. New students join as players +6. Previous players become spectators +``` + +### Server-Side Handling + +The server must handle spectators correctly: + +```typescript +// Validate move ownership +socket.on('game-move', ({ move, roomId }) => { + const session = getSession(roomId) + + // Check if PLAYER making move is in the active players list + if (!session.activePlayers.includes(move.playerId)) { + return { + error: 'PLAYER not in game - spectators cannot make moves' + } + } + + // Check if USER owns this PLAYER + const playerOwner = getPlayerOwner(move.playerId) + if (playerOwner !== socket.userId) { + return { + error: 'USER does not own this PLAYER' + } + } + + // Valid move - apply and broadcast + const newState = validator.validateMove(session.state, move) + io.to(`game:${roomId}`).emit('state-update', newState) // ALL room members get update +}) +``` + +**Key Server Logic**: +- Validate PLAYER is in `session.activePlayers` +- Validate USER owns PLAYER +- Broadcast to entire room (players + spectators) +- Spectators receive updates but cannot send moves + +### Testing Spectator Mode + +```typescript +describe('Spectator Mode', () => { + it('should allow room members to spectate single-player games', () => { + // Setup: USER A and USER B in same room + // Action: USER A starts Card Sorting (single-player) + // Assert: USER B receives game state updates + // Assert: USER B cannot make moves + // Assert: USER B sees USER A's card placements in real-time + }) + + it('should prevent spectators from making moves', () => { + // Setup: USER A playing, USER B spectating + // Action: USER B attempts to place a card + // Assert: Server rejects move (PLAYER not in activePlayers) + // Assert: Client UI disables controls for USER B + }) + + it('should show spectator indicator in UI', () => { + // Setup: USER B spectating USER A's game + // Assert: UI shows "Spectating [Player Name]" banner + // Assert: Interactive controls are disabled + // Assert: Game state is visible + }) + + it('should allow spectator to join next round', () => { + // Setup: USER B spectating USER A's Card Sorting game + // Action: USER A finishes game, returns to setup + // Action: USER B starts new game + // Assert: USER A becomes spectator + // Assert: USER B becomes active player + }) +}) +``` + +### Migration Path + +**For existing games**: + +If your game currently uses `roomId: roomData?.id`, it already supports spectator mode! You just need to: + +1. ✅ Check for `!localPlayerId` before allowing moves +2. ✅ Add spectator UI indicators +3. ✅ Disable controls when spectating +4. ✅ Test spectator experience + +**Example Fix**: + +```typescript +// Before (will crash for spectators) +const placeCard = useCallback((cardId, position) => { + sendMove({ + type: 'PLACE_CARD', + playerId: localPlayerId, // ❌ Will be undefined for spectators! + ... + }) +}, [localPlayerId, sendMove]) + +// After (spectator-safe) +const placeCard = useCallback((cardId, position) => { + if (!localPlayerId) { + console.warn('Spectators cannot place cards') + return // ✅ Spectators blocked from moving + } + sendMove({ + type: 'PLACE_CARD', + playerId: localPlayerId, + ... + }) +}, [localPlayerId, sendMove]) +``` + ## Common Mistakes to Avoid ### Mistake 1: Conditional Room Usage diff --git a/apps/web/.claude/CARD_SORTING_AUDIT.md b/apps/web/.claude/CARD_SORTING_AUDIT.md new file mode 100644 index 00000000..60601e50 --- /dev/null +++ b/apps/web/.claude/CARD_SORTING_AUDIT.md @@ -0,0 +1,404 @@ +# Card Sorting Challenge - Arcade Architecture Audit + +**Date**: 2025-10-18 +**Auditor**: Claude Code +**Reference**: `.claude/ARCADE_ARCHITECTURE.md` +**Update**: 2025-10-18 - Spectator mode recognized as intentional feature + +## Executive Summary + +The Card Sorting Challenge game was audited against the Arcade Architecture requirements documented in `.claude/ARCADE_ARCHITECTURE.md`. The initial audit identified the room-based sync pattern as a potential issue, but this was later recognized as an **intentional spectator mode feature**. + +**Overall Status**: ✅ **CORRECT IMPLEMENTATION** (with spectator mode enabled) + +--- + +## Spectator Mode Feature (Initially Flagged as Issue) + +### ✅ Room-Based Sync Enables Spectator Mode (INTENTIONAL FEATURE) + +**Location**: `/src/arcade-games/card-sorting/Provider.tsx` lines 286, 312 + +**Initial Assessment**: The provider **ALWAYS** calls `useRoomData()` and **ALWAYS** passes `roomId: roomData?.id` to `useArcadeSession`. This was initially flagged as a mode isolation violation. + +```typescript +const { roomData } = useRoomData() // Line 286 +... +const { state, sendMove, exitSession } = useArcadeSession({ + userId: viewerId || '', + roomId: roomData?.id, // Line 312 - Room-based sync + initialState: mergedInitialState, + applyMove: applyMoveOptimistically, +}) +``` + +**Actual Behavior (CORRECT)**: +- ✅ When a USER plays Card Sorting in a room, the game state SYNCS ACROSS THE ROOM NETWORK +- ✅ This enables **spectator mode** - other room members can watch the game in real-time +- ✅ Card Sorting is single-player (`maxPlayers: 1`), but spectators can watch and cheer +- ✅ Room members without active players become spectators automatically +- ✅ Creates social/collaborative experience ("Watch me solve this!") + +**Supported By Architecture** (ARCADE_ARCHITECTURE.md, Spectator Mode section): +> Spectator mode is automatically enabled when using room-based sync (`roomId: roomData?.id`). +> Any room member who is not actively playing becomes a spectator and can watch the game in real-time. +> +> **✅ This is the PREFERRED pattern** - even for single-player games like Card Sorting, because: +> - Enables spectator mode automatically +> - Creates social experience ("watch me solve this!") +> - No extra code needed +> - Works seamlessly with multiplayer games too + +**Pattern is CORRECT**: + +```typescript +// For single-player games WITH spectator mode support: +export function CardSortingProvider({ children }: { children: ReactNode }) { + const { data: viewerId } = useViewerId() + const { roomData } = useRoomData() // ✅ Fetch room data for spectator mode + + const { state, sendMove, exitSession } = useArcadeSession({ + userId: viewerId || '', + roomId: roomData?.id, // ✅ Enable spectator mode - room members can watch + initialState: mergedInitialState, + applyMove: applyMoveOptimistically, + }) + + // Actions check for localPlayerId - spectators won't have one + const startGame = useCallback(() => { + if (!localPlayerId) { + console.warn('[CardSorting] No local player - spectating only') + return // ✅ Spectators blocked from starting game + } + // ... send move + }, [localPlayerId, sendMove]) +} +``` + +**Why This Pattern is Used**: +This enables spectator mode as a first-class user experience. Room members can: +- Watch other players solve puzzles +- Learn strategies by observation +- Cheer and coach +- Take turns (finish watching, then play yourself) + +**Status**: ✅ CORRECT IMPLEMENTATION +**Priority**: N/A - No changes needed + +--- + +## Scope of Spectator Mode + +This same room-based sync pattern exists in **ALL** arcade games currently: + +```bash +$ grep -A 2 "useRoomData" /path/to/arcade-games/*/Provider.tsx + +card-sorting/Provider.tsx: const { roomData } = useRoomData() +complement-race/Provider.tsx: const { roomData } = useRoomData() +matching/Provider.tsx: const { roomData } = useRoomData() +memory-quiz/Provider.tsx: const { roomData } = useRoomData() +``` + +All providers pass `roomId: roomData?.id` to `useArcadeSession`. This means: +- ✅ **All games** support spectator mode automatically +- ✅ **Single-player games** (card-sorting) enable "watch me play" experience +- ✅ **Multiplayer games** (matching, memory-quiz, complement-race) support both players and spectators + +**Status**: This is the recommended pattern for social/family gaming experiences. + +--- + +## ✅ Correct Implementations + +### 1. Active Players Handling (CORRECT) + +**Location**: `/src/arcade-games/card-sorting/Provider.tsx` lines 287, 294-299 + +The provider correctly uses `useGameMode()` to access active players: + +```typescript +const { activePlayers, players } = useGameMode() + +const localPlayerId = useMemo(() => { + return Array.from(activePlayers).find((id) => { + const player = players.get(id) + return player?.isLocal !== false + }) +}, [activePlayers, players]) +``` + +✅ Only includes players with `isActive = true` +✅ Finds the first local player for this single-player game +✅ Follows architecture pattern correctly + +--- + +### 2. Player ID vs User ID (CORRECT) + +**Location**: Provider.tsx lines 383-491 (all move creators) + +All moves correctly use: +- `playerId: localPlayerId` (PLAYER makes the move) +- `userId: viewerId || ''` (USER owns the session) + +```typescript +// Example from startGame (lines 383-391) +sendMove({ + type: 'START_GAME', + playerId: localPlayerId, // ✅ PLAYER ID + userId: viewerId || '', // ✅ USER ID + data: { playerMetadata, selectedCards }, +}) +``` + +✅ Follows USER/PLAYER distinction correctly +✅ Server can validate PLAYER ownership +✅ Matches architecture requirements + +--- + +### 3. Validator Implementation (CORRECT) + +**Location**: `/src/arcade-games/card-sorting/Validator.ts` + +The validator correctly implements all required methods: + +```typescript +export class CardSortingValidator implements GameValidator { + validateMove(state, move, context): ValidationResult { ... } + isGameComplete(state): boolean { ... } + getInitialState(config: CardSortingConfig): CardSortingState { ... } +} +``` + +✅ All move types have validation +✅ `getInitialState()` accepts full config +✅ Proper error messages +✅ Server-side score calculation +✅ State transitions validated + +--- + +### 4. Game Registration (CORRECT) + +**Location**: `/src/arcade-games/card-sorting/index.ts` + +Uses the modular game system correctly: + +```typescript +export const cardSortingGame = defineGame({ + manifest, + Provider: CardSortingProvider, + GameComponent, + validator: cardSortingValidator, + defaultConfig, + validateConfig: validateCardSortingConfig, +}) +``` + +✅ Proper TypeScript generics +✅ Manifest includes all required fields +✅ Config validation function provided +✅ Uses `getGameTheme()` for consistent styling + +--- + +### 5. Type Definitions (CORRECT) + +**Location**: `/src/arcade-games/card-sorting/types.ts` + +State and move types properly extend base types: + +```typescript +export interface CardSortingState extends GameState { ... } +export interface CardSortingConfig extends GameConfig { ... } +export type CardSortingMove = + | { type: 'START_GAME', playerId: string, userId: string, ... } + | { type: 'PLACE_CARD', playerId: string, userId: string, ... } + ... +``` + +✅ All moves include `playerId` and `userId` +✅ Extends SDK base types +✅ Proper TypeScript structure + +--- + +## Recommendations + +### 1. Add Spectator UI Indicators (Enhancement) + +The current implementation correctly enables spectator mode, but could be enhanced with better UI/UX: + +**Action**: Add spectator indicators to `GameComponent.tsx`: + +```typescript +export function GameComponent() { + const { state, localPlayerId } = useCardSorting() + + return ( + <> + {!localPlayerId && state.gamePhase === 'playing' && ( +
+ 👀 Spectating {state.playerMetadata?.name || 'player'}'s game +
+ )} + + {/* Disable controls when spectating */} + + + ) +} +``` + +**Also Consider**: +- Show "Join Game" prompt during setup phase for spectators +- Display spectator count ("2 people watching") +- Add smooth real-time animations for spectators + +--- + +### 2. Document Other Games + +All arcade games currently support spectator mode. Consider documenting this in each game's README: + +**Games with Spectator Mode**: +- ✅ `card-sorting` - Single-player puzzle with spectators +- ✅ `matching` - Multiplayer battle with spectators +- ✅ `memory-quiz` - Cooperative with spectators +- ✅ `complement-race` - Competitive with spectators + +**Documentation to Add**: +- How spectator mode works in each game +- Example scenarios (family game night, classroom) +- Best practices for spectator experience + +--- + +### 3. Add Spectator Mode Tests + +Following ARCADE_ARCHITECTURE.md Spectator Mode section, add tests: + +```typescript +describe('Card Sorting - Spectator Mode', () => { + it('should sync state to spectators when USER plays in a room', async () => { + // Setup: USER A and USER B in same room + // Action: USER A plays Card Sorting + // Assert: USER B (spectator) sees card placements in real-time + // Assert: USER B cannot place cards (no localPlayerId) + }) + + it('should prevent spectators from making moves', () => { + // Setup: USER A playing, USER B spectating + // Action: USER B attempts to place card + // Assert: Action blocked (localPlayerId check) + // Assert: Server rejects if somehow sent + }) + + it('should allow spectator to play after current player finishes', () => { + // Setup: USER A playing, USER B spectating + // Action: USER A finishes, USER B starts new game + // Assert: USER B becomes player + // Assert: USER A becomes spectator + }) +}) +``` + +--- + +### 4. Architecture Documentation + +**✅ COMPLETED**: ARCADE_ARCHITECTURE.md has been updated with comprehensive spectator mode documentation: +- Added "SPECTATOR" to core terminology +- Documented three synchronization modes (Local, Room-Based with Spectator, Pure Multiplayer) +- Complete "Spectator Mode" section with: + - Implementation patterns + - UI/UX considerations + - Example scenarios (Family Game Night, Classroom) + - Server-side validation + - Testing requirements + - Migration path + +**No further documentation needed** - Card Sorting follows the recommended pattern + +--- + +## Compliance Checklist + +Based on ARCADE_ARCHITECTURE.md Spectator Mode Pattern: + +- [x] ✅ **Provider uses room-based sync to enable spectator mode** + - Calls `useRoomData()` and passes `roomId: roomData?.id` +- [x] ✅ Provider uses `useGameMode()` to get active players +- [x] ✅ Provider finds `localPlayerId` to distinguish player vs spectator +- [x] ✅ Game components correctly use PLAYER IDs (not USER IDs) for moves +- [x] ✅ Move actions check `localPlayerId` before sending + - Spectators without `localPlayerId` cannot make moves +- [x] ✅ Game supports multiple active PLAYERS from same USER + - Implementation allows it (finds first local player) +- [x] ✅ Inactive PLAYERS are never included in game sessions + - Uses `activePlayers` which filters to `isActive = true` +- [ ] ⚠️ **UI shows spectator indicator** + - Could be enhanced (see Recommendations #1) +- [ ] ⚠️ **UI disables controls for spectators** + - Could be enhanced (see Recommendations #1) +- [ ] ⚠️ Tests verify spectator mode + - No tests found (see Recommendations #3) +- [ ] ⚠️ Tests verify PLAYER ownership validation + - No tests found +- [x] ✅ Validator implements all required methods +- [x] ✅ Game registered with modular system + +**Overall Compliance**: 9/13 ✅ (Core features complete, enhancements recommended) + +--- + +## Summary + +The Card Sorting Challenge game is **correctly implemented** with: +- ✅ Active players (only `isActive = true` players participate) +- ✅ Player ID vs User ID distinction +- ✅ Validator pattern +- ✅ Game registration +- ✅ Type safety +- ✅ **Spectator mode enabled** (room-based sync pattern) + +**Architecture Pattern**: Room-Based with Spectator Mode (RECOMMENDED) + +✅ **CORRECT**: Room sync enables spectator mode as a first-class feature + +The `roomId: roomData?.id` pattern is **intentional and correct**: +1. ✅ Enables spectator mode automatically +2. ✅ Room members can watch games in real-time +3. ✅ Creates social/collaborative experience +4. ✅ Spectators blocked from making moves (via `localPlayerId` check) +5. ✅ Follows ARCADE_ARCHITECTURE.md recommended pattern + +**Recommended Enhancements** (not critical): +1. Add spectator UI indicators ("👀 Spectating...") +2. Disable controls visually for spectators +3. Add spectator mode tests + +**Priority**: LOW (enhancements only - core implementation is correct) + +--- + +## Next Steps (Optional Enhancements) + +1. ✅ **Architecture documentation** - COMPLETED (ARCADE_ARCHITECTURE.md updated with spectator mode) +2. Add spectator UI indicators to GameComponent (banner, disabled controls) +3. Add spectator mode tests +4. Document spectator mode in other arcade games +5. Consider adding spectator count display ("2 watching") + +**Note**: Card Sorting is production-ready as-is. Enhancements are for improved UX only.