Api/Models.cs

API models for the game's HTTP and WebSocket messages. Defines JSON-serializable record types for auth responses, leaderboard standings, and a variety of server→client WebSocket frames (hello, pending, armed, round result, game over, etc.).

using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Splitclicker.Api;

// POST /api/v1/auth response: the public tag/username plus a single-use WS ticket
// (mint immediately before connecting; expires after ttl_ms).
public record AuthResponse(
	[property: JsonPropertyName( "tag" )] string Tag,
	[property: JsonPropertyName( "username" )] string Username,
	[property: JsonPropertyName( "ticket" )] string Ticket,
	[property: JsonPropertyName( "ttl_ms" )] long TtlMs
);

// One row of a leaderboard (GET /api/v1/leaderboard/*) and of the standings
// embedded in round_result / game_over. SteamId is the public SteamID64, used to
// open/copy the player's steamcommunity.com profile.
public record Standing(
	[property: JsonPropertyName( "tag" )] string Tag,
	[property: JsonPropertyName( "username" )] string Username,
	[property: JsonPropertyName( "points" )] int Points,
	[property: JsonPropertyName( "steam_id" )] string SteamId
);

// --- WebSocket server→client frames (the "t" field selects the shape) ---

public record HelloYou(
	[property: JsonPropertyName( "tag" )] string Tag,
	[property: JsonPropertyName( "username" )] string Username
);

public record HelloGame(
	[property: JsonPropertyName( "round" )] int Round,
	[property: JsonPropertyName( "of" )] int Of,
	[property: JsonPropertyName( "phase" )] string Phase,
	[property: JsonPropertyName( "players" )] int Players,
	[property: JsonPropertyName( "clicks" )] int Clicks,
	[property: JsonPropertyName( "arm_min" )] int ArmMin,
	[property: JsonPropertyName( "arm_max" )] int ArmMax
);

public record HelloMsg(
	[property: JsonPropertyName( "you" )] HelloYou You,
	[property: JsonPropertyName( "game" )] HelloGame Game
);

public record PendingMsg(
	[property: JsonPropertyName( "round" )] int Round,
	[property: JsonPropertyName( "of" )] int Of,
	[property: JsonPropertyName( "players" )] int Players,
	[property: JsonPropertyName( "clicks" )] int Clicks
);

// nonce is a hex string (an unguessable 64-bit token); echo it back verbatim in
// the click frame — never parse/reformat it. penalty_ms is this connection's own
// arm-delay penalty (0 for honest clients), surfaced so a masher sees the throttle.
public record ArmedMsg(
	[property: JsonPropertyName( "round" )] int Round,
	[property: JsonPropertyName( "seq" )] int Seq,
	[property: JsonPropertyName( "nonce" )] string Nonce,
	[property: JsonPropertyName( "players" )] int Players,
	[property: JsonPropertyName( "clicks" )] int Clicks,
	[property: JsonPropertyName( "penalty_ms" )] int PenaltyMs
);

public record YouResult(
	[property: JsonPropertyName( "points_delta" )] int PointsDelta,
	[property: JsonPropertyName( "round_id" )] string RoundId
);

public record RoundResultMsg(
	[property: JsonPropertyName( "round" )] int Round,
	[property: JsonPropertyName( "of" )] int Of,
	[property: JsonPropertyName( "winners" )] List<Standing> Winners,
	[property: JsonPropertyName( "standings" )] List<Standing> Standings,
	[property: JsonPropertyName( "you" )] YouResult You
);

public record YouGameOver(
	[property: JsonPropertyName( "placement" )] int Placement,
	[property: JsonPropertyName( "won" )] bool Won,
	[property: JsonPropertyName( "game_id" )] string GameId
);

public record GameOverMsg(
	[property: JsonPropertyName( "standings" )] List<Standing> Standings,
	[property: JsonPropertyName( "you" )] YouGameOver You
);