// Contrat générique d'une épreuve. Le moteur ne connaît QUE cette interface : // brancher une nouvelle épreuve = écrire un module, pas retoucher la plomberie. import type { Answer, MediaControlPayload, RevealPayload, RoundRecap, RoundType, ScoreDelta, } from "@nerdware/shared" import type { ServerRoom } from "../rooms" /** Partie du récap fournie par l'épreuve (le moteur complète index/type/scorers). */ export type RoundRecapInfo = Omit /** Ce que `start()` renvoie : la partie publique + les données privées de la manche. */ export interface RoundStart { /** Joueur DJ (blindtest) ; null pour les épreuves sans DJ (quiz). */ djId?: string | null /** Durée de la manche en secondes ; défaut = room.settings.roundDuration. */ durationSec?: number /** Payload broadcasté aux clients, SANS la réponse. */ payload: unknown /** * Joueur recevant un round:start privé avec `mine: true` et exclu des votants * (blindtest : le contributeur du titre). Jamais révélé aux autres. */ secretPlayerId?: string /** Données privées (ex: la bonne réponse), conservées jusqu'au reveal. Jamais diffusées. */ data?: unknown } /** Contexte runtime d'une manche, fourni aux callbacks de l'épreuve. */ export interface RoundContext { room: ServerRoom djId: string | null /** Votes reçus : playerId → réponse. Muté par submitAnswer. */ votes: Map startedAt: number endsAt: number /** Données privées renvoyées par start(). */ data: unknown } /** * Une épreuve. Implémente le cycle start → submitAnswer → reveal → score. * Le serveur est autoritaire : start/reveal/score ne s'exécutent que côté serveur. */ export interface GameRound { type: RoundType /** Prépare la manche (tire le DJ, choisit le contenu) et renvoie le payload public. */ start(room: ServerRoom): RoundStart /** Enregistre/valide le vote d'un joueur dans ctx.votes. Doit être idempotent. */ submitAnswer(ctx: RoundContext, playerId: string, answer: Answer): void /** Calcule la vérité + le détail révélé à tous. */ reveal(ctx: RoundContext): RevealPayload /** Renvoie les variations de score à appliquer (le moteur les applique). */ score(ctx: RoundContext): ScoreDelta[] /** Résumé de la manche pour le récap de fin de partie (intitulé + réponse + média). */ recap(ctx: RoundContext): RoundRecapInfo } /** Vue minimale du moteur exposée à la room (évite un cycle d'import). */ export interface RoomGameController { run(): Promise handleVote(playerId: string, answer: Answer): void /** Relais média du DJ (blindtest) : rebroadcast media:sync à toute la room. */ handleMediaControl(playerId: string, payload: MediaControlPayload): void /** Reconnexion : re-pousse la manche en cours au socket d'un joueur. */ resync(socketId: string, playerId: string): void }