Files
SlipItIn/openwiki/workflows/slip-and-challenge.md
2026-08-08 18:09:52 +00:00

12 KiB

type, title, description, tags, openwiki
type title description tags openwiki
Workflow Slip & Challenge Workflows Real-time game loops covering authentication, lobby creation, card dealing, phrase slipping, slip challenging, challenge resolution, and offline action queuing.
workflow
game-loop
signalr
slip-mechanic
real-time
offline-resync
roles change_kinds source_paths symbols validation_commands
workflow
public-api
lifecycle
SlipItIn/ViewModels/GameBoardViewModel.cs
SlipItIn/Services/GameStateService.cs
SlipItIn/Services/SignalRService.cs
SlipItIn.Server/Hubs/GameHub.cs
LobbyViewModel
GameBoardViewModel
SignalRService
GameStateService
GameHub
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 domain entities, invoke real-time methods in the ASP.NET Core GameHub and client SignalRService, are implemented across backend and client services mapped in the source map, and are verified using tests described in 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.
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:

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:
    dotnet build SlipItIn.slnx