# Issue #312 — FupOgFakta extraction map for logic currently living in `lobby/` Parent: #311 Issue: #312 ## Purpose This artifact documents the concrete FupOgFakta-specific logic that still lives in `lobby/`, separates it from true platform/session concerns, and names the intended destination ownership before any larger code move happens. It is intentionally an inventory + extraction plan only. It does **not** perform the full move. ## Architectural boundary this map is enforcing The target boundary is already described in: - `docs/plans/2026-03-09-fupogfakta-game-engine-design.md` - `docs/plans/2026-03-09-fupogfakta-implementation-plan.md` - `docs/ARCHITECTURE.md` Those docs consistently describe: - `lobby/` as the **platform layer** for session lifecycle, player presence, host ownership, generic game-run orchestration, and transport-facing platform concerns. - `fupogfakta/` as the **game cartridge** that owns question selection rules, round config semantics, lie/guess/reveal/scoreboard flow, answer mixing, scoring, and game-specific response/event payloads. In other words: - **Platform (`lobby/`)** should know that a session exists and that a game can be started/observed. - **Cartridge (`fupogfakta/`)** should know what a lie is, what a guess is, how answers are mixed, when phases advance, and what payload shape those game phases expose. ## Summary split ### Generic platform/session concerns that belong in `lobby/` These are not FupOgFakta-specific and should remain platform-owned: - Session code parsing/generation: - `lobby/views.py::_generate_session_code` - `lobby/views.py::_normalize_session_code` - `lobby/views.py::_create_unique_session_code` - Generic request parsing: - `lobby/views.py::_json_body` - Session lifecycle and player presence endpoints: - `lobby/views.py::create_session` - `lobby/views.py::join_session` - `lobby/views.py::session_detail` **only for the generic session/player shell part** - Generic ownership / host authorization checks - Generic session detail payload fields: - `session.code` - `session.status` - `session.host_id` - `session.current_round` - `session.players_count` - `players[].id|nickname|score|is_connected` - Generic i18n/error transport helper usage: - `lobby/i18n.py` - `api_error(...)` - Route mounting / namespace ownership in `lobby/urls.py` for platform routes only ### FupOgFakta-specific logic currently misplaced in `lobby/` These items are game-cartridge logic and should move behind `fupogfakta/` ownership: - Round question selection by category and previously-used questions - Lie-phase payload construction and lie timer semantics - Mixed-answer preparation for bluff gameplay - Guess correctness / fooled-player detection - Bluff/correct-answer score resolution - Reveal payload construction - Reveal → scoreboard promotion rules - Start round / mix answers / submit lie / submit guess / calculate scores / reveal scoreboard / next round / finish game gameplay endpoints - Phase view-model booleans that encode FupOgFakta rules rather than generic platform readiness ## Extraction map | Source file | Current function / concern | Why it is FupOgFakta-specific | Intended destination / owner | | --- | --- | --- | --- | | `lobby/views.py` | `_build_player_ref(player)` | Helper is only used to shape FupOgFakta reveal payloads; not a generic platform concern today. | `fupogfakta/serializers.py` or `fupogfakta/payloads.py` owned by cartridge. | | `lobby/views.py` | `_build_reveal_payload(round_question)` | Encodes FupOgFakta reveal contract: lies, guesses, fooled-player refs, correct answer, prompt. | `fupogfakta/payloads.py::build_reveal_payload` or equivalent cartridge response builder. | | `lobby/views.py` | `_build_leaderboard(session)` | Current implementation is generic-ish, but used exclusively by FupOgFakta scoreboard/finish flow and coupled to that response shape. | Short term: keep shared helper if multiple games will consume same contract; otherwise move to `fupogfakta/payloads.py` until a true shared scoreboard contract exists. | | `lobby/views.py` | `_get_current_round_question(session)` | Depends on FupOgFakta `RoundQuestion` model and current-round semantics. | `fupogfakta/services/rounds.py` or `fupogfakta/queries.py`. | | `lobby/views.py` | `_select_round_question(session, round_config)` | Implements FupOgFakta question selection rules by category, active questions, and not-yet-used question set. | `fupogfakta/services/rounds.py::select_round_question`. | | `lobby/views.py` | `_build_lie_started_payload(session, round_config, round_question)` | Builds a FupOgFakta event/response contract for lie phase, including category, prompt, lie deadline, round question id. | `fupogfakta/payloads.py::build_lie_started_payload`. | | `lobby/views.py` | `_prepare_mixed_answers(round_question)` | Bluff-answer dedupe and shuffle is core FupOgFakta gameplay logic. | `fupogfakta/services/answers.py::prepare_mixed_answers`. | | `lobby/views.py` | `_resolve_scores(session, round_question, round_config)` | Applies FupOgFakta scoring rules for correct guesses and successful bluffs; depends on `Guess`, `LieAnswer`, `ScoreEvent`, `points_correct`, `points_bluff`. | `fupogfakta/services/scoring.py::resolve_scores`. | | `lobby/views.py` | `_maybe_promote_reveal_to_scoreboard(session)` | Encodes FupOgFakta reveal completion semantics and scoreboard transition trigger. | `fupogfakta/services/phases.py::maybe_promote_reveal_to_scoreboard`. | | `lobby/views.py` | `_build_phase_view_model(session, players_count, has_round_question)` | Most booleans are not platform-generic; they encode FupOgFakta phase names (`lie`, `guess`, `scoreboard`) and MVP constraints (`3-5 players`, round-question readiness, next-round/finish gating). | Split: keep platform-shell fields in `lobby/`; move game-specific readiness/action flags to `fupogfakta/payloads.py::build_phase_view_model` or cartridge driver payload builder. | | `lobby/views.py` | `start_round(request, code)` | Starts FupOgFakta round, binds category, creates `RoundConfig`, selects `RoundQuestion`, transitions to `LIE`, broadcasts `phase.lie_started`. | `fupogfakta/views.py` or cartridge command handler behind a future `GameDriver.on_game_start` / round bootstrap service. | | `lobby/views.py` | `show_question(request, code)` | Emits lie-phase question payload using FupOgFakta `RoundQuestion` and `RoundConfig`. | `fupogfakta/views.py` or remove entirely once canonical driver flow owns the transition. | | `lobby/views.py` | `submit_lie(request, code, round_question_id)` | Pure FupOgFakta gameplay endpoint: lie validation, deadline semantics, auto-advance to guess phase, `phase.guess_started` payload. | `fupogfakta/views.py::submit_lie` (or cartridge intent handler). | | `lobby/views.py` | `mix_answers(request, code, round_question_id)` | Manual FupOgFakta host action for lie→guess transition and answer mixing. | `fupogfakta/views.py` short term; long term likely deleted in favor of cartridge-driven automatic transition. | | `lobby/views.py` | `submit_guess(request, code, round_question_id)` | Pure FupOgFakta gameplay endpoint: validates answer choice, resolves correctness/bluff source, auto-calculates scores, transitions to reveal. | `fupogfakta/views.py::submit_guess` plus `fupogfakta/services/scoring.py` and `fupogfakta/services/phases.py`. | | `lobby/views.py` | `reveal_scoreboard(request, code)` | FupOgFakta reveal/scoreboard progression, not a generic platform capability. | `fupogfakta/views.py::reveal_scoreboard` or cartridge phase service. | | `lobby/views.py` | `start_next_round(request, code)` | FupOgFakta next-round bootstrap: copies prior `RoundConfig`, increments round, picks next question, re-enters lie phase. | `fupogfakta/services/rounds.py::start_next_round` plus cartridge-owned endpoint/driver integration. | | `lobby/views.py` | `finish_game(request, code)` | Current finish path is tied to FupOgFakta scoreboard semantics and winner payload. | `fupogfakta/views.py::finish_game` until a truly generic platform finish contract exists. | | `lobby/views.py` | `calculate_scores(request, code, round_question_id)` | Explicit FupOgFakta score resolution endpoint. | `fupogfakta/services/scoring.py` and/or remove when fully absorbed by cartridge phase driver. | | `lobby/urls.py` | Gameplay routes for rounds, lies, guesses, scoreboard, finish | These route names expose FupOgFakta-specific phase/actions from the platform namespace. | Re-home under `fupogfakta/urls.py` or leave mounted under `/lobby/sessions/...` only as a temporary façade delegating to cartridge-owned code. | | `lobby/tests.py` | `StartRoundTests`, `LieSubmissionTests`, `MixAnswersTests`, `GuessSubmissionTests`, `CanonicalRoundFlowTests`, `ScoreCalculationTests`, `RevealRoundFlowTests`, `SessionDetailRoundQuestionTests`, `SessionDetailPhaseViewModelTests`, `SmokeStagingCommandTests` | These test classes verify FupOgFakta game flow rather than platform mechanics. | Move/split into `fupogfakta/tests/` with only session creation/join/platform transport tests left in `lobby/tests.py`. | | `lobby/management/commands/smoke_staging.py` | End-to-end gameplay smoke through lies/guesses/finish | Script executes one concrete game flow and should be cartridge-aware, not platform-owned. | `fupogfakta/management/commands/` or a shared smoke harness that delegates into cartridge-specific scenario runners. | ## Recommended ownership split by module ### Keep in `lobby/` - Session creation/join and session-code lifecycle - Generic player membership/presence reads - Generic auth/host checks helpers (if extracted from views) - Generic API error/i18n plumbing - Future `GameRun` / driver orchestration, timers, and cartridge dispatch - A slim generic `session_detail` envelope that can embed cartridge payloads under a dedicated game key ### Move to `fupogfakta/` - Round state queries - Question selection - Lie/guess/reveal/scoreboard/finish transition rules - Score calculation - Answer mixing - Gameplay payload/response builders - Gameplay endpoints and tests - Gameplay smoke command ## Explicit boundary for `session_detail` `session_detail` is currently mixed. ### Generic part that should remain platform-owned - Session identity/status metadata - Player list / presence list - Generic host/player capability envelope if it is game-agnostic ### FupOgFakta part that should move or be delegated - `round_question` payload - `reveal` payload - `scoreboard` payload - `phase_view_model` fields keyed to `lie`, `guess`, `scoreboard`, `finished`, `question_ready`, and 3–5-player MVP rules A clean future shape would be: ```json { "session": {"code": "ABC123", "status": "active", "game_type": "fupogfakta"}, "players": [...], "game": { "phase": "lie", "payload": {"round_question": {...}, "reveal": null, "scoreboard": null} } } ``` That makes `lobby/` the shell and `fupogfakta/` the authority for game-state payloads. ## Concrete extraction sequence 1. **Move pure helpers first** - `_get_current_round_question` - `_select_round_question` - `_prepare_mixed_answers` - `_resolve_scores` - `_build_lie_started_payload` - `_build_reveal_payload` 2. **Move gameplay endpoints behind cartridge-owned service functions** - `submit_lie` - `submit_guess` - `start_round` - `start_next_round` - `finish_game` - `reveal_scoreboard` - `calculate_scores` 3. **Slim `session_detail` into platform envelope + delegated cartridge payload** 4. **Move gameplay tests out of `lobby/tests.py`** 5. **Optionally leave compatibility routes in `lobby/urls.py` as a façade** until clients are rewired ## Risks this map is explicitly preventing - Moving only models but leaving hidden phase-transition rules in `lobby/views.py` - Treating `session_detail` as platform-generic while it still leaks cartridge payload semantics - Leaving scoreboard/reveal transition logic behind as an undocumented coupling - Splitting tests incorrectly so regressions stay "green" in `lobby/` while FupOgFakta behavior silently drifts ## Decision For #311 / #312, the repository should treat the following as **game-specific and extraction candidates**: - round-question selection - lie/guess/reveal/scoreboard/finish transitions - answer mixing - score resolution - reveal/scoreboard payload builders - FupOgFakta-specific session-detail subpayloads - gameplay flow tests and smoke command And it should treat the following as **platform-generic**: - session identity/lifecycle - player presence/membership - host authorization shell - generic error transport - future game-driver dispatch/orchestration That is the explicit `lobby` vs `fupogfakta` boundary this issue needs before code extraction proceeds.