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.pyapiserver/apiserver/controllers/quiz_game_controller.pyapiserver/apiserver/utils/quiz_turn.pyapiserver/apiserver/services/quiz_game_service.pyapiserver/apiserver/models/quiz_room.pyapiserver/apiserver/models/quiz_player.py
Frontend
frontend/engines/QuizEngine.tsfrontend/class/QuizGame.tsfrontend/types/QuizGame.tsfrontend/composables/useGameWebSocket.tsfrontend/pages/quiz-game-board.vue
3) Transport and endpoints
REST
POST /quiz/create_roombody:
mode,max_players,deck_id,difficultyreturns
room_idanddifficulty
GET /quiz/roomsGET /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,errorclient 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
Destyis appended.Backend sends
game_contextandplayer_join.start_gamepersists aGamerow and broadcastsgame_start.
Round loop
new_turnemitsround_startwith 4 candidate cards and deadline.Player sends selected
card_id.Backend checks if selected card equals the hidden winning card.
turn_resultis 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_leftwhen 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 metadataturn_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.