Commit graph

8 commits

Author SHA1 Message Date
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
AyoubBenziza
9fe30feec5 feat(server): Drizzle + PostgreSQL schema + Open Trivia DB seed
- Drizzle (postgres-js) setup: schema (quiz_category, quiz_question,
  quiz_played, game_history), client (nullable — no DATABASE_URL → null),
  drizzle.config, initial migration, migrate script
- Open Trivia DB seed: curated geek categories, mcq + truefalse, base64
  decode (incl. type/difficulty), 1 req/5s rate limit, idempotent
  (prompt unique); also seeds the FR in-code bank as source 'manual'
- quiz wired to the DB: per-room pool preloaded at game start (loadQuizPool
  excludes already-played), records quiz_played for cross-session
  anti-repeat; falls back to the in-code bank when no DB
- docker-compose for local Postgres; db:* scripts; DATABASE_URL in env

Roadmap V1 step 5. Verified against a real Postgres: migrate + seed (270
OpenTDB + 10 manual), DB-backed game serves OpenTDB questions and fills
quiz_played; no-DB fallback still serves the in-code bank.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 12:24:17 +02:00
AyoubBenziza
7dda00c65c feat: lead-in timer compensation + GIF support for round transitions
Server (option 2 — don't eat answer time):
- engine: leadMs prep delay (default 1600ms) before the clock starts; the
  round timer now fires at lead + duration, and scoring uses the post-lead
  start so the speed bonus spans the full answer window
- shared: round:start carries startsAt (answers begin) alongside endsAt
- engine test passes leadMs:0

Client:
- Countdown shows the full duration (no ticking) until startsAt, then counts
  down — so the WarioWare transition plays in full without stealing time
- store/ActiveRound carry startsAt

Custom transition media:
- RoundTransition auto-loads any gif/webp/apng/png/avif dropped in
  src/assets/transitions/ (import.meta.glob), plays one at random per round,
  falls back to the Framer animation when none are present
- README documenting how to add animations

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 11:25:17 +02:00
AyoubBenziza
2fce63530b feat(quiz): show per-player answer status during the round
- voteAck now carries `voted` (playerIds who validated) — never their answer,
  so no leak; the count/total stay for the progress text
- PlayerCards show a green "Prêt" check for players who answered and a
  pulsing "en attente" dot for those who haven't, during in_round only
- score stays visible on the cards alongside the status
- engine test updated for the new voteAck shape

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:30:03 +02:00
AyoubBenziza
5cc9113b8c feat(quiz): quiz game mode end-to-end (server mode + playable client UI)
Server:
- quiz mode (GameRound): in-memory FR question bank (mcq + truefalse),
  per-room anti-repeat, first-vote lock, score = base + speed bonus ∝ time
  left; registered via registerRound, loaded at startup
- shared: typed quiz payloads (QuizQuestionPayload / reveal truth / result)
- tests: quiz scoring, vote lock, bounds, reveal (bun test)

Client:
- room store handles round:start / voteAck / reveal / game:end
- RoomPage dispatches by status: lobby (host start controls) → quiz view
  (question, countdown, vote, reveal + scoreboard) → game-end view
- replaces standalone lobby page

Roadmap V1 step 4. Verified end-to-end over the wire (start→vote→reveal→
score→next round→game:end, no answer leaked, anti-repeat works).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:43:17 +02:00
AyoubBenziza
9ed3b8a4cb feat(server): generic game engine — GameRound interface + orchestration loop
- game/round.ts: GameRound contract (start/submitAnswer/reveal/score) +
  RoundContext + RoomGameController (decouples room from engine)
- game/registry.ts: register/createRound — adding a mode = one registerRound
- game/engine.ts: server-authoritative orchestration. Per round:
  start → broadcast round:start → wait (timer OR all eligible voted) →
  reveal → apply score deltas → score:update; then game:end. DJ counts as
  eligible voter; round ends on first condition met.
- handlers: game:start (host-only, validates rounds/modes) + round:vote
  wired to the engine
- shared: add game:start client event
- engine.test.ts: covers both end conditions + voteAck (bun test)
- turbo/root: add test task

Roadmap V1 step 3. No UI yet — concrete quiz mode + client come in step 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:29:49 +02:00
AyoubBenziza
29faa18731 feat(server,web): first real-time cycle — room create/join + lobby
Server (@nerdware/server):
- RoomManager: in-memory rooms (Map), code generation, create/join,
  disconnect handling, public snapshot projection (no secrets)
- Socket.IO handlers: room:create / room:join with typed acks,
  room:state broadcast on every change, lobby:updateSettings (host-only),
  player:left on disconnect

Web (web):
- typed Socket.IO client + Zustand store mirroring room:state
- wouter routing: home (create/join) + minimal lobby (live player list)
- @nerdware/shared wired into the client

Roadmap V1 step 2. Verified end-to-end (create/join/broadcast/disconnect).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:16:26 +02:00
AyoubBenziza
56115b5a9f feat(shared,server): scaffold @nerdware/shared types + Fastify/Socket.IO server
- packages/shared (@nerdware/shared): domain model (Room, Player, Settings,
  Round, votes, scores) + fully typed Socket.IO events, consumed as source
- apps/server (@nerdware/server): Fastify boot + /health, typed Socket.IO
  attached to the HTTP server, env config, graceful shutdown
- gitignore: allow .env.example to be committed

Roadmap V1 step 1. Room logic (room:create/join) comes next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:55:52 +02:00