# Ralph Loop: Implement WebSocket push for Weirsøe Party Protocol ## Context - Project: /home/agw/projects/weirsoe-party-protocol - Backend: Django 6.0.2 + Django Channels + Redis - The full game REST flow is already implemented in lobby/views.py (create_session, join_session, start_round, show_question, submit_lie, mix_answers, submit_guess, calculate_scores, reveal_scoreboard, finish_game) - realtime/ app exists but is a stub (no consumers.py, no routing) - partyhub/settings.py has channels in INSTALLED_APPS but no CHANNEL_LAYERS or routing - PO hard requirement: WebSocket push is mandatory in MVP (no polling) ## What to build ### 1. realtime/consumers.py — GameConsumer - AsyncJsonWebsocketConsumer - Connects to group game_{session_code} on connect (session_code from URL) - Player auth: session_token query param validated against Player model - Host auth: query param role=host, no token required for MVP - On disconnect: clean leave from group - Handles incoming message type "ping" -> replies with {"type": "pong"} - Forwards broadcast group events to WebSocket client ### 2. partyhub/settings.py — CHANNEL_LAYERS Add CHANNEL_LAYERS using channels_redis.core.RedisChannelLayer. Read CHANNEL_REDIS_HOST (default 127.0.0.1) and CHANNEL_REDIS_PORT (default 6379) from env. ### 3. partyhub/asgi.py — ASGI routing Wire URLRouter so ws/game// routes to GameConsumer. Keep existing HTTP routing intact. ### 4. realtime/routing.py Define websocket_urlpatterns list. ### 5. realtime/broadcast.py — broadcast helper - async def broadcast_phase_event(session_code, event_type, payload) Sends to group game_{session_code} via channel layer. - def sync_broadcast_phase_event(session_code, event_type, payload) Sync wrapper using async_to_sync for calling from sync REST views. ### 6. lobby/views.py — hook broadcasts into phase transitions After each phase transition, call sync_broadcast_phase_event: - start_round -> phase.lie_started (question prompt + time limit) - show_question -> phase.question_shown (question text) - mix_answers -> phase.guess_started (shuffled answers + time limit) - calculate_scores -> phase.scores_calculated (per-player score delta) - reveal_scoreboard -> phase.scoreboard (ranked player list) - finish_game -> phase.game_over (final rankings) ### 7. realtime/tests.py — basic tests - Connect/disconnect test using channels.testing.WebsocketCommunicator - Verify a broadcast reaches a connected client ## Constraints - Keep auth simple: session_token query param for players, unauthenticated host in MVP - Use async_to_sync wrapper for sync REST views calling async broadcast - Do not break existing REST tests (python manage.py test lobby must still pass) - After each file written, run: python manage.py check - Follow existing code style in lobby/views.py ## Completion criteria Output the exact text: WEBSOCKET COMPLETE ...when ALL of the following are true: - realtime/consumers.py exists and handles connect/disconnect/ping - realtime/broadcast.py exists with sync_broadcast_phase_event - partyhub/settings.py has CHANNEL_LAYERS configured - partyhub/asgi.py routes ws/game// to GameConsumer - All 6 phase transitions in lobby/views.py call sync_broadcast_phase_event - python manage.py check passes with no errors - python manage.py test lobby passes (existing tests not broken)