nerdware/packages/shared/src/blindtest.ts
AyoubBenziza 98e06b8206 feat(blindtest): YouTube blindtest mode with neutral DJ + synced playback
Server:
- BlindtestRound (GameRound): neutral DJ pick (never the contributor),
  per-mode scoring (title_artist fuzzy / who_added / mixed), reveal exposes
  title/artist/submittedBy; contributor doesn't score on own track
- track submission: blindtest:submitTrack validates via YouTube oEmbed
  (exists + embeddable) and captures title/artist; per-player quota; dedup
- DJ media relay: media:control (DJ only) → media:sync broadcast (engine)
- per-room shuffled track pool; submissions count in room snapshot
- shared: blindtest payloads, gameType/tracksPerPlayer in settings
- tests: DJ neutrality, scoring per mode, fuzzy match, youtube id extraction

Client:
- YouTube IFrame player (hidden behind a spinning disc — audio only, no
  title leak), DJ transport (play/pause/restart), non-DJ follows media:sync
  with latency compensation
- lobby: game-type switch (Quiz/Blindtest), blindtest config + per-player
  track submission UI
- vote forms per mode; reveal card with title/artist/who-added

Roadmap V1 step 6 (highest technical risk). Server flow verified end-to-end
over the wire (submit→start→DJ relay→vote→reveal→score).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:02:28 +02:00

28 lines
917 B
TypeScript

// Types concrets de l'épreuve Blindtest, partagés client/serveur.
// L'audio est joué localement par chaque client mais piloté par le DJ (media:sync).
/** Payload diffusé au lancement d'une manche blindtest (round:start), SANS la réponse. */
export interface BlindtestRoundPayload {
trackId: string
youtubeId: string
}
/** Vérité révélée à tous (round:reveal → truth), inclut qui a soumis le titre. */
export interface BlindtestRevealTruth {
title: string
artist: string
youtubeId: string
submittedBy: string
submittedByName: string
}
/** Résultat d'un joueur sur la manche (détail selon le mode). */
export interface BlindtestPlayerResult {
titleCorrect?: boolean
artistCorrect?: boolean
guessedCorrect?: boolean
points: number
}
/** round:reveal → perPlayerResult : playerId → résultat. */
export type BlindtestPerPlayerResult = Record<string, BlindtestPlayerResult>