--- type: Workflow title: Slip & Challenge Workflows description: Real-time game loops covering authentication, lobby creation, card dealing, phrase slipping, slip challenging, challenge resolution, and offline action queuing. tags: [workflow, game-loop, signalr, slip-mechanic, real-time, offline-resync] openwiki: roles: [workflow] change_kinds: [public-api, lifecycle] source_paths: [SlipItIn/ViewModels/GameBoardViewModel.cs, SlipItIn/Services/GameStateService.cs, SlipItIn/Services/SignalRService.cs, SlipItIn.Server/Hubs/GameHub.cs] symbols: [LobbyViewModel, GameBoardViewModel, SignalRService, GameStateService, GameHub] validation_commands: ["dotnet build SlipItIn.slnx"] --- # Slip & Challenge Workflows This document outlines the core real-time game workflows in SlipItIn, detailing how client MVVM ViewModels, `WeakReferenceMessenger`, backend SignalR hubs, database services, and offline action queues 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` and client `SignalRService`, [are implemented across backend and client 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 ``` [Client App] LoginVM.LoginAsync() ──> REST Auth ──> Save Token in SecureStorage ──> SignalR.ConnectAsync() │ [Host] LobbyVM.CreateLobbyAsync() ──> SignalR.CreateLobbyAsync() ──> Server Generates Code ──┤ │ [Player] LobbyVM.JoinLobbyAsync() ──> SignalR.JoinLobbyAsync(code) ──────────────────────────┤ │ [Player] LobbyVM.SetReadyAsync() ──> SignalR.PlayerReadyAsync() ─────────────────────────────┼──> Broadcast GameStateUpdated │ [Host] LobbyVM.StartGameAsync() ──> SignalR.StartGameAsync() ──> Deal 5 Cards / Player ──────┴──> Unicast PlayerHandUpdated ``` 1. **Authentication & Session Setup**: Player logs in via `LoginPage` (`LoginViewModel.LoginAsync()`). `ApiService` posts credentials to `/api/auth/login`. `AuthSessionService` persists the token to `SecureStorage`. `SignalRService.ConnectAsync(token)` opens a WebSocket connection to `/hubs/game`. 2. **Lobby Creation**: Host invokes `LobbyViewModel.CreateLobbyAsync()`. `SignalRService.CreateLobbyAsync()` triggers `GameHub.CreateLobby()`. `GameService.CreateGameAsync()` creates a `Game` record (`Status = Lobby`), generates a random 6-character `LobbyCode`, adds host to `game.Players`, and registers host's `ConnectionId`. Caller receives `LobbyCreated` message via `WeakReferenceMessenger`. 3. **Joining Lobby**: Opponents call `LobbyViewModel.JoinLobbyAsync()`. `SignalRService.JoinLobbyAsync(code)` validates game status and capacity. `GameService.JoinGameAsync()` assigns player to game, registers `ConnectionId`, adds player to SignalR group `lobbyCode`, and broadcasts `PlayerJoined` (`GameStateDto`) to all participants. 4. **Player Ready**: Players invoke `LobbyViewModel.SetReadyAsync()`. `GameHub.PlayerReady()` verifies access and broadcasts `GameStateUpdated`. 5. **Game Start & Card Dealing**: The host invokes `LobbyViewModel.StartGameAsync()`. - `GameHub.StartGame()` verifies host identity (`ValidateHostAccessAsync`). - `GameService.StartGameAsync()` transitions `GameStatus` to `InProgress` and creates `GameRound` 1 (`RoundStatus.Active`). - `GameService.DealCardsAsync()` selects 5 random phrases per player, creates `PlayerCard` records, and saves them to PostgreSQL. - SignalR broadcasts `GameStarted` to the group (triggering client navigation to `GameBoardPage`). - SignalR sends private `PlayerHandUpdated` notifications (`PlayerHandDto`) individually to each player's `ConnectionId`. --- ## 2. Phrase Slipping & Offline Queueing 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. **User Action**: Player clicks **Submit Slip** on `GameBoardPage` (`GameBoardViewModel.SubmitSlipAsync(card)`). 2. **Online / Offline Branching**: - **If Connected (`_signalR.IsConnected`)**: Calls `SignalRService.SubmitSlipAsync(gameId, playerId, cardId)` directly. `GameHub.SubmitSlip()` validates ownership and updates `PlayerCard.IsUsed = true`. `GameHub` broadcasts `SlipSubmitted` to the lobby group. - **If Offline (`!_signalR.IsConnected`)**: Calls `GameStateService.EnqueueActionAsync("SubmitSlip", gameId, playerId, cardId)`. Action is serialized into `QueuedGameAction` and saved in `Preferences`. UI updates status to `"Offline: Aktion wurde zwischengespeichert."` --- ## 3. Slip Challenge & Resolution Workflow If an opponent suspects that a submitted phrase or recent conversation statement was an invalid slip, they can challenge the slipper. ### Step 1: Challenge Initiation 1. Challenger clicks **Challenge** on `GameBoardPage` (`GameBoardViewModel.ChallengeCardAsync(card)`). 2. If online, calls `SignalRService.ChallengeSlipAsync(gameId, playerId, cardId)`. 3. `GameHub.ChallengeSlip()` verifies challenger identity (`ValidatePlayerAccessAsync`). 4. `GameService.CreateChallengeAsync()` creates a `SlipChallenge` entity with `ChallengeStatus.Pending`. 5. `GameHub` broadcasts `SlipChallenged` (`SlipChallengeDto`) to the group and unicasts `ChallengeReceived` directly to target player's `ConnectionId`. 6. Client target player sees `ChallengeNotificationOverlay` on `GameBoardPage`. ### Step 2: Challenge Resolution The accused player responds via `ChallengeNotificationOverlay`: 1. Accused player calls `GameBoardViewModel.ResolveChallengeAsync(challengeId, approved)`. 2. `SignalRService.ResolveChallengeAsync(challengeId, approved)` calls `GameHub.ResolveChallenge()`. 3. `GameHub` executes `ValidateChallengeTargetAccessAsync(challengeId, authUserId)` to guarantee *only* the accused player can resolve the challenge. 4. `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 accused player. - **`approved = false` (False Accusation / Wrong Penalty)**: The accused denies the charge. `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`. 5. `GameHub` broadcasts `ChallengeResolved` to the group. ```mermaid sequenceDiagram autonumber actor Slipper as Accused Player A actor Challenger as Challenger Player B participant GameBoardVM as GameBoardViewModel participant Hub as GameHub participant Service as GameService participant DB as SlipItInDbContext Note over Slipper, Challenger: Active Gameplay Slipper->>GameBoardVM: SubmitSlipAsync(card) GameBoardVM->>Hub: SubmitSlip(gameId, playerAId, cardId) Hub->>Service: SubmitSlipAsync() Service->>DB: Set PlayerCard.IsUsed = true Hub-->>Challenger: Broadcast "SlipSubmitted" Note over Challenger: Suspects invalid phrase Challenger->>GameBoardVM: ChallengeCardAsync(card) GameBoardVM->>Hub: ChallengeSlip(gameId, playerBId, cardId) Hub->>Service: CreateChallengeAsync() Service->>DB: Insert SlipChallenge (Status = Pending) Hub-->>Challenger: Broadcast "SlipChallenged" Hub-->>Slipper: Unicast "ChallengeReceived" Note over Slipper: Notification Overlay Displays Slipper->>GameBoardVM: ResolveChallengeAsync(challengeId, approved) GameBoardVM->>Hub: ResolveChallenge(challengeId, approved) Hub->>Hub: ValidateChallengeTargetAccessAsync() Hub->>Service: ResolveChallengeAsync() alt approved == false (False Accusation) Service->>DB: Reassign card.PlayerId = PlayerBId Service->>DB: Set Challenge.Status = Rejected else approved == true (Justified Catch) Service->>DB: Set Challenge.Status = Approved end Hub-->>Challenger: Broadcast "ChallengeResolved" ``` *Sequence diagram showing phrase submission, challenge initiation, target notification, and penalty resolution.* --- ## 4. Offline Action Queueing & Reconnection Resync Workflow When network connection is temporarily interrupted during gameplay, SlipItIn ensures action durability and smooth state synchronization: ```mermaid sequenceDiagram autonumber actor User as Player participant GameBoardVM as GameBoardViewModel participant GameStateSvc as GameStateService participant LocalStorage as LocalStorageService participant SignalRSvc as SignalRService participant Hub as GameHub Note over User, SignalRSvc: Network Disconnected (Offline) User->>GameBoardVM: SubmitSlipAsync(card) GameBoardVM->>GameStateSvc: EnqueueActionAsync("SubmitSlip", args) GameStateSvc->>LocalStorage: SaveQueuedActionsAsync(queuedList) LocalStorage-->>GameBoardVM: Saved to Preferences Note over SignalRSvc: Network Restored SignalRSvc->>Hub: Automatic Reconnect (Exponential Backoff) SignalRSvc->>GameStateSvc: ConnectionStateChanged(true) GameStateSvc->>GameStateSvc: ResyncAsync() GameStateSvc->>SignalRSvc: RequestGameStateAsync(gameId) SignalRSvc->>Hub: RequestGameState(gameId) Hub-->>GameStateSvc: GameStateUpdated (Fresh DTO) GameStateSvc->>GameStateSvc: FlushQueuedActionsAsync() loop For each QueuedGameAction GameStateSvc->>SignalRSvc: SendQueuedActionAsync(action) SignalRSvc->>Hub: InvokeCoreAsync(action.Method, action.Args) end GameStateSvc->>LocalStorage: ClearQueuedActionsAsync() ``` *Sequence diagram showing offline action queueing in local preferences, automatic reconnection, state request, and queued action replay.* --- ## 5. SignalR Hub Event Reference Summary of server-to-client events emitted by `GameHub.cs` and handled by `SignalRService`: | Event Name | Scope | Payload | Trigger | |---|---|---|---| | `LobbyCreated` | Caller | `{ GameId, LobbyCode }` | Host calls `CreateLobby()` | | `PlayerJoined` | Group | `GameStateDto` | Player calls `JoinLobby()` | | `GameStateUpdated` | Group | `GameStateDto` | Player calls `PlayerReady()` or `RequestGameState()` | | `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 | --- ## 6. Guidance for Future Agents & Developers - **When to Consult**: Refer to this document when adding new game interactions, modifying UI challenge flows, changing reconnection strategies, or replaying offline actions. - **Invariants**: - `FlushQueuedActionsAsync()` MUST execute in strictly sequential order based on `CreatedAtUtc`. - `ResolveChallenge` MUST throw `UnauthorizedAccessException` if invoked by anyone other than the target player. - **Primary Source Files**: - `SlipItIn/ViewModels/GameBoardViewModel.cs` - `SlipItIn/Services/GameStateService.cs` - `SlipItIn/Services/SignalRService.cs` - `SlipItIn.Server/Hubs/GameHub.cs` - **Minimal Validation Command**: ```bash dotnet build SlipItIn.slnx ```