Skip to main content
Competitive games attract manipulation. The architecture of SET: 3D Edition’s online mode is designed so that cheating is structurally impossible, not merely detectable after the fact. A cheating client cannot influence Set validation results, cannot back-date a claim timestamp, cannot grant itself cosmetics, and cannot corrupt the leaderboard — because the server owns every one of those outcomes and the client is never asked to self-report them. This page documents the specific mechanisms that enforce this, so that when you are implementing client features you understand which boundaries must never be crossed.
Pre-production — Planned Feature. All anti-cheat mechanisms, rate limiting, IAP validation, and Nakama integration described on this page are planned features for a game currently in pre-production. None of these systems are implemented yet.

The Fundamental Defence

The most important security property of the system is also the simplest: the client sends intent, not outcomes. When a player taps three cards, the client sends their IDs to the server via IMultiplayerService.SendClaim. It does not send “these cards form a valid Set.” It does not pre-validate and skip sending invalid claims. The server runs SetValidator on every claim independently of what the client thinks. The server’s result is the result. A modified client that injects a “valid” claim message for three cards that do not form a Set will receive the same “invalid” response from the server as an honest client would. There is nothing the client can send to change the server’s validation outcome.

What the Client Can and Cannot Do


Rate Limiting

Sending hundreds of claim messages per second does not help an attacker — it actively hurts them. The Nakama match handler applies a per-player, per-tick rate limit on incoming claim messages. The processing model:
  1. The server collects all client messages since the last tick.
  2. For each player, it processes at most one claim per tick (50 ms window).
  3. Excess claims are discarded — they do not queue up for future ticks.
A client that floods the server with match_claim messages will have all but one processed per tick, and that one will be validated on its merits. The flood provides zero advantage and wastes bandwidth. The rate limit is configured in the Nakama match handler logic. When implementing the handler, ensure the rate-limit check runs before claim validation, so attackers cannot even reach the validator with excess messages.

Simultaneous Claim Fairness

Simultaneous claims are resolved entirely by server-received timestamp. The only way for a player to win a contested claim is to send the message earlier — which means tapping faster. No client-side manipulation can backfill a timestamp on the server. The only edge case — two messages with identical server-received timestamps — is resolved by lower session ID, a deterministic tiebreaker that is documented, tested, and consistent.

The OnlineMatchController as a Security Boundary

The OnlineMatchController enforces a clean separation between the network and the game session, which also serves as a security boundary. GameSession never directly holds a reference to IMultiplayerService, which means:
  • Game logic code cannot inadvertently call SendClaim with a pre-validated result.
  • There is no code path where the session can inspect server messages before the controller translates them — raw ServerMessage types never appear in gameplay code.
  • A developer cannot shortcut the flow by having GameSession write state outside of ApplyServerState, because the session’s multiplayer interface only exposes ApplyServerState and SendClaim.
This architectural enforced ignorance is the structural complement to the server-side authority model.

Client-Side Save Integrity

Local save data (personal stats, settings, unlocked cosmetics for offline modes) is protected by an HMAC signature with an obfuscated key. On load, LocalSaveService verifies the signature. If tampering is detected:
  1. The corrupted data is discarded.
  2. Stats are reset to defaults.
  3. Competitive data (scores, MMR, leaderboard positions) is unaffected — it is owned by the server, not the local save file.
The HMAC defence is a deterrent against casual save editing. It is not a cryptographic guarantee against a determined attacker with root access — but that does not matter, because the server owns all competitive data. A player can corrupt their local save to infinity without affecting their leaderboard position or opponents’ matches.

IAP Receipt Validation

Cosmetic items purchased via Google Play are not granted by the client. The flow is: The client never decides ownership. Even if a client modifies its local save to mark a cosmetic as owned, the server-side Nakama Storage record is authoritative. The cosmetic will not appear on other players’ devices or persist across reinstalls.

Server-Side Data Ownership Table


What Is Not In Scope for v1.0

The following anti-cheat features are explicitly out of scope for the initial release. Do not implement them:
  • Replay system for dispute resolution. Disputes are handled by the server’s authoritative log. A full replay viewer is a post-launch roadmap item.
  • Real-time anti-cheat scanning (e.g., memory scanning, root detection). Structural authority is the primary defence; scanning adds complexity without changing the attack surface meaningfully for this game type.
  • Spectator mode for moderation. Spectator mode itself is out of scope for v1.0 (per Hard Boundaries §3.1).
Any of these require a formal Change Request before engineering work begins.

Hard Boundary Reminder

Server-authoritative for all multiplayer is a Hard Boundary. Any change that introduces client-side Set validation in online modes — even temporarily, even “just for testing” — requires a formal change request reviewed by the Product Owner and Tech Lead before implementation. This is not a preference; it is a project constraint documented in the Hard Boundaries specification.

Implementation Checklist

  • IOnlineGameSession has no ValidateSet method — only SendClaim. Verify this before writing any multiplayer game flow code.
  • OnlineMatchController is the only code that calls IMultiplayerService.SendClaim and subscribes to IMultiplayerService.MessagesGameSession never holds a reference to IMultiplayerService
  • Rate limiting is configured in the Nakama match handler before the claim validation path
  • LocalSaveService computes and verifies the HMAC signature on every load and save
  • IAP receipt validation runs server-side via Google Play’s verification API before any Nakama Storage grant
  • Deterministic tiebreaker (lower session ID) for exact-millisecond simultaneous claims is implemented and covered by a server-side unit test
  • Client-side cosmetic unlock UI reads from server-confirmed Nakama Storage, not from the local save file
  • Leaderboard submissions go through the Nakama leaderboard module API, not a direct client write

Common Mistakes

Common Mistakes
  • Adding a local SetValidator call in multiplayer “just for responsiveness.” Even if the result only drives a visual (e.g., a pre-emptive glow on the selected cards), you have now made the client’s visual truth diverge from the server’s true outcome. When the server sends a different result, the rollback is jarring. Use a neutral “claiming…” state instead.
  • Storing competitive scores only in the local save file. If the server is the source of truth for MMR and leaderboard position, writing to the local save for “convenience” creates a split truth. On reinstall or account migration, the local data is gone. The server data persists.
  • Skipping rate limiting on claim messages. Without rate limiting, a bot can send thousands of claims per second, saturating the tick’s processing queue and causing server-side lag for all players in the match.
  • Granting cosmetic ownership on the client. Even if you trust the purchase flow, a client-side grant is trivially bypassed with a local save edit. The server must always be the granting authority.
  • Assuming a valid-looking claim is valid. The server must validate every claim from first principles using SetValidator, regardless of how plausible the claim looks. Never short-circuit server validation.
  • Bypassing OnlineMatchController to call GameSession.ApplyServerState directly from networking code. The controller is the correct translation point. Direct calls break the security boundary by letting raw network data into the game session without the command translation layer.

Authority Model

The IMultiplayerService contract, the OnlineMatchController pattern, and the IOnlineGameSession interface design.

Match Lifecycle

The full match flow including simultaneous claim resolution and post-match MMR updates.

Sync & Reconnect

Full-state broadcasts, the client mirror model, and the 30-second reconnect window.

Architecture Layers

How the Infrastructure assembly boundary prevents Nakama SDK types from leaking into gameplay code.