Captures all brainstormed decisions: - Pluggable game cartridge platform (GameDriver interface) - Celery + Redis timer-driven phase transitions - Session owner play/pause/exit controls (no skip) - Escalating scoring per round, incremental reveal scoring - Emoji reactions during guess phase → post-game awards - Relational per-user config presets with game-specific models - Ephemeral game state (no persistence after exit/finish) - Full WebSocket event reference and data lifecycle Also: updated TODO.md (WebSocket done, persisted answers done), created CLAUDE.md, and PROMPT.md for ralph-loop. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.3 KiB
3.3 KiB
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/<session_code>/ 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)