diff --git a/apps/web/.claude/settings.local.json b/apps/web/.claude/settings.local.json index 58061b7f..75923598 100644 --- a/apps/web/.claude/settings.local.json +++ b/apps/web/.claude/settings.local.json @@ -57,7 +57,8 @@ "Bash(pnpm remove:*)", "Bash(gh run view:*)", "Bash(pnpm install:*)", - "Bash(git checkout:*)" + "Bash(git checkout:*)", + "Bash(node server.js:*)" ], "deny": [], "ask": [] diff --git a/apps/web/src/app/arcade/know-your-world/page.tsx b/apps/web/src/app/arcade/know-your-world/page.tsx index 288e029c..2e064e45 100644 --- a/apps/web/src/app/arcade/know-your-world/page.tsx +++ b/apps/web/src/app/arcade/know-your-world/page.tsx @@ -4,6 +4,9 @@ import { knowYourWorldGame } from '@/arcade-games/know-your-world' const { Provider, GameComponent } = knowYourWorldGame +// Opt-out of static generation due to ES module dependencies in map data +export const dynamic = 'force-dynamic' + export default function KnowYourWorldPage() { return ( diff --git a/apps/web/src/arcade-games/know-your-world/Validator.ts b/apps/web/src/arcade-games/know-your-world/Validator.ts index 50f2f476..f1cf6f34 100644 --- a/apps/web/src/arcade-games/know-your-world/Validator.ts +++ b/apps/web/src/arcade-games/know-your-world/Validator.ts @@ -5,19 +5,27 @@ import type { KnowYourWorldState, GuessRecord, } from './types' -import { getMapData, getFilteredMapData } from './maps' + +/** + * Lazy-load map functions to avoid importing ES modules at module init time + * This is critical for server-side usage where ES modules can't be required + */ +async function getFilteredMapDataLazy(...args: Parameters) { + const { getFilteredMapData } = await import('./maps') + return getFilteredMapData(...args) +} export class KnowYourWorldValidator implements GameValidator { - validateMove(state: KnowYourWorldState, move: KnowYourWorldMove): ValidationResult { + async validateMove(state: KnowYourWorldState, move: KnowYourWorldMove): Promise { switch (move.type) { case 'START_GAME': - return this.validateStartGame(state, move.data) + return await this.validateStartGame(state, move.data) case 'CLICK_REGION': return this.validateClickRegion(state, move.playerId, move.data) case 'NEXT_ROUND': - return this.validateNextRound(state) + return await this.validateNextRound(state) case 'END_GAME': return this.validateEndGame(state) case 'END_STUDY': @@ -39,42 +47,21 @@ export class KnowYourWorldValidator } } - private validateStartGame(state: KnowYourWorldState, data: any): ValidationResult { + private async validateStartGame(state: KnowYourWorldState, data: any): Promise { if (state.gamePhase !== 'setup') { return { valid: false, error: 'Can only start from setup phase' } } const { activePlayers, playerMetadata, selectedMap, gameMode, difficulty } = data - console.log('[KnowYourWorld Validator] Starting game with:', { - selectedMap, - gameMode, - difficulty, - studyDuration: state.studyDuration, - activePlayers: activePlayers?.length, - }) - if (!activePlayers || activePlayers.length === 0) { return { valid: false, error: 'Need at least 1 player' } } // Get map data and shuffle regions (with continent and difficulty filters) - const mapData = getFilteredMapData(selectedMap, state.selectedContinent, difficulty) - console.log('[KnowYourWorld Validator] Map data loaded:', { - map: mapData.id, - continent: state.selectedContinent, - difficulty, - regionsCount: mapData.regions.length, - regionIds: mapData.regions.map((r) => r.id).slice(0, 10), - regionNames: mapData.regions.map((r) => r.name).slice(0, 10), - }) + const mapData = await getFilteredMapDataLazy(selectedMap, state.selectedContinent, difficulty) const regionIds = mapData.regions.map((r) => r.id) const shuffledRegions = this.shuffleArray([...regionIds]) - console.log('[KnowYourWorld Validator] First region to find:', { - id: shuffledRegions[0], - name: mapData.regions.find((r) => r.id === shuffledRegions[0])?.name, - totalShuffled: shuffledRegions.length, - }) // Initialize scores and attempts const scores: Record = {} @@ -225,13 +212,13 @@ export class KnowYourWorldValidator } } - private validateNextRound(state: KnowYourWorldState): ValidationResult { + private async validateNextRound(state: KnowYourWorldState): Promise { if (state.gamePhase !== 'results') { return { valid: false, error: 'Can only start next round from results' } } // Get map data and shuffle regions (with continent and difficulty filters) - const mapData = getFilteredMapData(state.selectedMap, state.selectedContinent, state.difficulty) + const mapData = await getFilteredMapDataLazy(state.selectedMap, state.selectedContinent, state.difficulty) const regionIds = mapData.regions.map((r) => r.id) const shuffledRegions = this.shuffleArray([...regionIds]) diff --git a/apps/web/src/lib/arcade/session-manager.ts b/apps/web/src/lib/arcade/session-manager.ts index 6819ff47..fb44f25f 100644 --- a/apps/web/src/lib/arcade/session-manager.ts +++ b/apps/web/src/lib/arcade/session-manager.ts @@ -227,7 +227,7 @@ export async function applyGameMove( } // Validate the move with authorization context (use internal userId, not guestId) - const validationResult = validator.validateMove(session.gameState, move, { + const validationResult = await validator.validateMove(session.gameState, move, { userId: internalUserId || userId, // Use internal userId for room-based games playerOwnership, }) diff --git a/apps/web/src/lib/arcade/validation/types.ts b/apps/web/src/lib/arcade/validation/types.ts index 00b53342..d3059330 100644 --- a/apps/web/src/lib/arcade/validation/types.ts +++ b/apps/web/src/lib/arcade/validation/types.ts @@ -68,11 +68,12 @@ export interface ValidationContext { export interface GameValidator { /** * Validate a game move and return the new state if valid + * Can be async to support lazy-loaded dependencies (e.g., ES modules) * @param state Current game state * @param move The move to validate * @param context Optional validation context for authorization checks */ - validateMove(state: TState, move: TMove, context?: ValidationContext): ValidationResult + validateMove(state: TState, move: TMove, context?: ValidationContext): ValidationResult | Promise /** * Check if the game is in a terminal state (completed)