Update der Dokumentation

This commit is contained in:
Tim Krampitz
2026-08-08 12:50:15 +02:00
parent bbc6ad6080
commit 118a62a804
15 changed files with 460 additions and 139 deletions

View File

@@ -1,121 +1,175 @@
---
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]
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 SignalR events, backend service operations, database updates, and client state notifications interact.
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`, [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.
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
```
[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
[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. **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)`.
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()` fetches active phrases, randomly selects 5 phrases per player, creates `PlayerCard` entries, and saves them to PostgreSQL.
- SignalR broadcasts `GameStarted` to the group.
- `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 Workflow
## 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. **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.
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 another player suspects that a submitted phrase or recent conversation statement was an invalid slip, they can challenge the slipper.
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. 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`.
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
Player A (the accused) responds by admitting or denying the false slip:
The accused player responds via `ChallengeNotificationOverlay`:
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
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 Player A (Accused)
actor Challenger as Player B (Challenger)
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->>Hub: SubmitSlip(gameId, playerAId, cardId)
Hub->>Service: SubmitSlipAsync(gameId, playerAId, cardId)
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->>Hub: ChallengeSlip(gameId, playerBId, targetCardId)
Challenger->>GameBoardVM: ChallengeCardAsync(card)
GameBoardVM->>Hub: ChallengeSlip(gameId, playerBId, cardId)
Hub->>Service: CreateChallengeAsync()
Service->>DB: Insert SlipChallenge (Status = Pending)
Hub-->>Challenger: Broadcast "SlipChallenged" (SlipChallengeDto)
Hub-->>Slipper: Unicast "ChallengeReceived" to ConnectionId
Hub-->>Challenger: Broadcast "SlipChallenged"
Hub-->>Slipper: Unicast "ChallengeReceived"
Note over Slipper: Accused resolves accusation
Slipper->>Hub: ResolveChallenge(challengeId, approved)
Note over Slipper: Notification Overlay Displays
Slipper->>GameBoardVM: ResolveChallengeAsync(challengeId, approved)
GameBoardVM->>Hub: ResolveChallenge(challengeId, approved)
Hub->>Hub: ValidateChallengeTargetAccessAsync()
Hub->>Service: ResolveChallengeAsync(challengeId, approved)
Hub->>Service: ResolveChallengeAsync()
alt approved == false (False Accusation)
Service->>DB: Update targetCard.PlayerId = PlayerBId (Penalty)
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. SignalR Hub Event Reference
## 4. Offline Action Queueing & Reconnection Resync Workflow
Summary of server-to-client events emitted by `GameHub.cs`:
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()` |
| `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()` |
@@ -123,3 +177,21 @@ Summary of server-to-client events emitted by `GameHub.cs`:
| `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
```