Quiz mode - Developer documentation =================================== 1) Scope -------- This document describes the current Quiz mode implementation in Earth Pulse, across backend and frontend. Quiz mode is a real-time single-player experience with a virtual opponent (Desty): * one human player plus one bot * 6 rounds by default (8 when max players is 4 in room settings) * multiple-choice answer flow with score and time bonus 2) Main components ------------------ Backend ^^^^^^^ * ``apiserver/apiserver/routers/quiz_game.py`` * ``apiserver/apiserver/controllers/quiz_game_controller.py`` * ``apiserver/apiserver/utils/quiz_turn.py`` * ``apiserver/apiserver/services/quiz_game_service.py`` * ``apiserver/apiserver/models/quiz_room.py`` * ``apiserver/apiserver/models/quiz_player.py`` Frontend ^^^^^^^^ * ``frontend/engines/QuizEngine.ts`` * ``frontend/class/QuizGame.ts`` * ``frontend/types/QuizGame.ts`` * ``frontend/composables/useGameWebSocket.ts`` * ``frontend/pages/quiz-game-board.vue`` 3) Transport and endpoints -------------------------- REST ^^^^ * ``POST /quiz/create_room`` * body: ``mode``\ , ``max_players``\ , ``deck_id``\ , ``difficulty`` * returns ``room_id`` and ``difficulty`` * ``GET /quiz/rooms`` * ``GET /quiz/rooms/{room_id}`` WebSocket ^^^^^^^^^ * ``GET WS /quiz/ws/{room_id}/{pseudo}`` * ``GET WS /quiz/ws/{room_id}/{pseudo}/{token}`` Core events: * server to client: ``game_context``\ , ``player_join``\ , ``game_start``\ , ``round_start``\ , ``turn_result``\ , ``game_finish``\ , ``error`` * client to server (answer): payload with ``card_id``\ , ``player_id``\ , ``rest_time`` 4) Room and round model ----------------------- ``QuizRoom`` keeps in-memory state: * room metadata (\ ``room_id``\ , ``mode``\ , ``state``\ , ``created_at``\ ) * players list and current player pointer * round counters (\ ``turn_played``\ , ``max_turn``\ , ``round_index``\ ) * round card state (\ ``turn_card``\ , ``turn_cards``\ , ``played_cards_data``\ , ``used_card_ids``\ ) * difficulty-dependent timer (\ ``beginner=25s``\ , ``intermediate=12s``\ , ``expert=6s``\ ) Round construction in ``QuizRoom.pick_all_round_cards``\ : * choose a random card that can still form a 4-card set by type * choose 3 other cards of same type * fetch and serialize card + indicator + photo data for UI * prevent card reuse via ``used_card_ids`` 5) Game flow ------------ Start ^^^^^ #. Client creates room. #. Player joins via WebSocket. #. Controller validates pseudo, room state, and capacity. #. Bot player ``Desty`` is appended. #. Backend sends ``game_context`` and ``player_join``. #. ``start_game`` persists a ``Game`` row and broadcasts ``game_start``. Round loop ^^^^^^^^^^ #. ``new_turn`` emits ``round_start`` with 4 candidate cards and deadline. #. Player sends selected ``card_id``. #. Backend checks if selected card equals the hidden winning card. #. ``turn_result`` is broadcast. #. Next round starts or game ends. End ^^^ ``end_game`` emits ``game_finish``\ , updates game status, and writes leaderboard entries for active non-bot players. 6) Scoring logic ---------------- Scoring is difficulty-based: * correct answer: +100 / +200 / +400 * wrong answer: -25 / -50 / -100 * speed bonus: ``+10 * seconds_left`` when answer is correct Only non-bot and non-eliminated players are scored/persisted. 7) Persistence and leaderboard ------------------------------ * ``add_game(room)`` creates game record at start (\ ``type=quiz``\ ). * ``update_game(room, "finish")`` updates terminal state. * ``add_score_to_leaderboard(..., game_type=QUIZ)`` writes final score. 8) Frontend runtime behavior ---------------------------- ``QuizEngine`` handles event choreography: * ``round_start``\ : prepares answer proposals and winner card metadata * ``turn_result``\ : modal sequence (\ ``round_result`` -> ``card_reveal`` -> score bar) * ``game_finish``\ : final score modal UI deadline compensation is applied after result animations so timers stay aligned with server rounds. 9) Operational notes -------------------- * Pseudo toxicity checks are enforced before room join. * Room join is rejected when game already started or room is full. * Room expiration is handled in model (\ ``is_expired``\ , 2h threshold). * Existing backend tests for round card constraints: ``apiserver/tests/test_quiz_room.py``.