--- type: Workflow title: Slip & Challenge Workflows description: Real-time game loops covering lobby creation, card dealing, phrase slipping, slip challenging, and challenge resolution. tags: [workflow, game-loop, signalr, slip-mechanic, real-time] --- # Slip & Challenge Workflows This document outlines the core real-time game workflows in SlipItIn, detailing how SignalR events, backend service operations, database updates, and client state notifications interact. These workflows [execute state transitions on](/openwiki/domain/game-mechanics.md) domain entities, [invoke real-time methods in](/openwiki/architecture/overview.md) the ASP.NET Core `GameHub`, [are implemented across backend services mapped in](/openwiki/source-map.md) the source map, and [are verified using tests described in](/openwiki/operations/runbook.md) the operations runbook. --- ## 1. Game Setup & Lobby Workflow ``` [Host] CreateLobby() ──> Generate 6-Char LobbyCode ──> Add to SignalR Group │ [Player] JoinLobby() ──> Verify MaxPlayers & Status ───────────┤ │ [Player] PlayerReady() ──> Set IsReady = true ─────────────────┼──> Broadcast GameStateUpdated │ [Host] StartGame() ──> Deal 5 Random Cards Per Player ─────────┴──> Unicast PlayerHandUpdated ``` 1. **Lobby Creation**: Host calls `GameHub.CreateLobby()`. `GameService.CreateGameAsync()` creates a `Game` record (`Status = Lobby`), generates a random 6-character `LobbyCode`, adds the host as the first `Player`, and registers the host's WebSocket `ConnectionId`. Caller receives `LobbyCreated`. 2. **Joining Lobby**: Opponents invoke `GameHub.JoinLobby(lobbyCode)`. `GameService.JoinGameAsync()` validates that the game exists, has space (`Players.Count < MaxPlayers`), and is in `Lobby` status. The player is assigned a `Player` entry, added to the SignalR group `lobbyCode`, and `PlayerJoined` (`GameStateDto`) is broadcast to all participants. 3. **Player Ready**: Players invoke `GameHub.PlayerReady(gameId, playerId)`. `GameHub` checks `ValidatePlayerAccessAsync` and broadcasts `GameStateUpdated`. 4. **Game Start & Card Dealing**: The host invokes `GameHub.StartGame(gameId)`. `GameHub` verifies host authorization via `ValidateHostAccessAsync(gameId, authUserId)`. - `GameService.StartGameAsync()` transitions `GameStatus` to `InProgress` and creates `GameRound` 1 (`RoundStatus.Active`). - `GameService.DealCardsAsync()` fetches active phrases, randomly selects 5 phrases per player, creates `PlayerCard` entries, and saves them to PostgreSQL. - SignalR broadcasts `GameStarted` to the group. - SignalR sends private `PlayerHandUpdated` notifications (`PlayerHandDto`) individually to each player's `ConnectionId`. --- ## 2. Phrase Slipping Workflow During real-life conversation or chat, a player speaks or types one of their secret phrases and marks it as used in the app: 1. **Submission**: Player A calls `GameHub.SubmitSlip(gameId, playerId, cardId)`. 2. **Access Control**: `GameHub` executes `ValidatePlayerAccessAsync(playerId, userId)` to verify that Player A owns the specified `Player` account. 3. **Engine Execution**: `GameService.SubmitSlipAsync()` loads the `PlayerCard`, validates card ownership (`card.PlayerId == playerId`), and sets `card.IsUsed = true`. 4. **Notification**: `GameHub` broadcasts `SlipSubmitted` (`{ PlayerId, CardId, Success }`) to the SignalR lobby group. --- ## 3. Slip Challenge & Resolution Workflow If another player suspects that a submitted phrase or recent conversation statement was an invalid slip, they can challenge the slipper. ### Step 1: Challenge Initiation 1. Player B (Challenger) calls `GameHub.ChallengeSlip(gameId, challengingPlayerId, targetCardId)`. 2. `GameHub` verifies Player B's identity (`ValidatePlayerAccessAsync`). 3. `GameService.CreateChallengeAsync()` creates a `SlipChallenge` entity with `ChallengeStatus.Pending`. 4. `GameHub` broadcasts `SlipChallenged` (`SlipChallengeDto`) to the lobby group. 5. `GameHub` sends a targeted `ChallengeReceived` message directly to Player A's `ConnectionId`. ### Step 2: Challenge Resolution Player A (the accused) responds by admitting or denying the false slip: 1. Player A calls `GameHub.ResolveChallenge(challengeId, approved)`. 2. `GameHub` executes `ValidateChallengeTargetAccessAsync(challengeId, authUserId)` to guarantee that *only* the accused player can resolve the challenge. 3. `GameService.ResolveChallengeAsync()` executes penalty logic: - **`approved = true` (Justified Accusation / Legitimate Catch)**: The accused admits the phrase was a fake slip. `SlipChallenge.Status` is set to `Approved`. The card remains with the accused player. - **`approved = false` (False Accusation / Wrong Penalty)**: The accused denies the charge (the slip was valid). `SlipChallenge.Status` is set to `Rejected`. As a penalty for making a false accusation, the phrase card is reassigned to the challenger: `targetCard.PlayerId = challenge.ChallengingPlayerId`. The challenger now holds >5 cards in their hand. 4. `GameHub` broadcasts `ChallengeResolved` to the lobby group. ### Real-time Sequence Diagram ```mermaid sequenceDiagram autonumber actor Slipper as Player A (Accused) actor Challenger as Player B (Challenger) participant Hub as GameHub participant Service as GameService participant DB as SlipItInDbContext Note over Slipper, Challenger: Active Gameplay Slipper->>Hub: SubmitSlip(gameId, playerAId, cardId) Hub->>Service: SubmitSlipAsync(gameId, playerAId, cardId) Service->>DB: Set PlayerCard.IsUsed = true Hub-->>Challenger: Broadcast "SlipSubmitted" Note over Challenger: Suspects invalid phrase Challenger->>Hub: ChallengeSlip(gameId, playerBId, targetCardId) Hub->>Service: CreateChallengeAsync() Service->>DB: Insert SlipChallenge (Status = Pending) Hub-->>Challenger: Broadcast "SlipChallenged" (SlipChallengeDto) Hub-->>Slipper: Unicast "ChallengeReceived" to ConnectionId Note over Slipper: Accused resolves accusation Slipper->>Hub: ResolveChallenge(challengeId, approved) Hub->>Hub: ValidateChallengeTargetAccessAsync() Hub->>Service: ResolveChallengeAsync(challengeId, approved) alt approved == false (False Accusation) Service->>DB: Update targetCard.PlayerId = PlayerBId (Penalty) Service->>DB: Set Challenge.Status = Rejected else approved == true (Justified Catch) Service->>DB: Set Challenge.Status = Approved end Hub-->>Challenger: Broadcast "ChallengeResolved" ``` --- ## 4. SignalR Hub Event Reference Summary of server-to-client events emitted by `GameHub.cs`: | Event Name | Scope | Payload | Trigger | |---|---|---|---| | `LobbyCreated` | Caller | `{ GameId, LobbyCode }` | Host calls `CreateLobby()` | | `PlayerJoined` | Group | `GameStateDto` | Player calls `JoinLobby()` | | `GameStateUpdated` | Group | `GameStateDto` | Player calls `PlayerReady()` | | `GameStarted` | Group | `{ GameId }` | Host calls `StartGame()` | | `PlayerHandUpdated` | Unicast | `PlayerHandDto` | Hand dealt or updated | | `SlipSubmitted` | Group | `{ PlayerId, CardId, Success }` | Player calls `SubmitSlip()` | | `SlipChallenged` | Group | `SlipChallengeDto` | Opponent calls `ChallengeSlip()` | | `ChallengeReceived` | Unicast | `SlipChallengeDto` | Direct alert to target player | | `ChallengeResolved` | Group | `SlipChallengeDto` | Target calls `ResolveChallenge()` | | `Error` | Caller | `{ Message }` | Any authorization or engine exception |