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
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