Skip to main content
Every system that reads or writes data in SET: 3D Edition — config loading, save migration, network serialization, IAP validation — depends on a shared understanding of these formats. This page is the single reference for all of them. If you are writing a loader, a migration function, or a network handler, start here.
Pre-production status. These formats are fully designed and agreed upon, but the Unity code to read, write, and migrate them is built incrementally across Phases 1–10 of the implementation roadmap. When you implement a system, treat this page as the specification the code must conform to — not the other way around.

Card Identity Encoding

The 81 cards are procedurally generated at runtime — there is no static JSON file listing each card. The four attribute enums fully define the card set. A build-time validation test confirms exactly 81 unique cards are produced with no duplicates.

Attribute Enums

Note that Number starts at 1, while all other enums start at 0. This matters for the encoding formula.

CardId Formula

This produces a unique integer in the range 0–80 for every card. The formula is used in network messages and save files as a compact card reference. Examples:

Decoding a CardId

Given an integer n in range 0–80:

AI Difficulty Config

File: Assets/_Project/Data/ai_difficulty.json Controls the timing and error-rate parameters for each AI difficulty tier. Designers can edit this file to tune feel without touching code.
Field definitions: Validation rules enforced at runtime:
  • minDelaySec must be ≤ maxDelaySec
  • All rate fields must be in the range [0.0, 1.0]
  • A missing tier falls back to the Medium values — it does not throw

Game Rules Presets

File: Assets/_Project/Data/game_rules_presets.json Named presets for the four standard match configurations. Private Room hosts can override individual fields from one of these presets.
Field definitions: These preset names map directly to the GameRules value object. The loader deserialises the JSON into a GameRulesDto and then constructs an immutable GameRules instance.

Local Save Schema

File: Application.persistentDataPath/set3d_save.json The local save is a single JSON file written with Newtonsoft.Json. It is versioned: the version integer increments with every schema change, and MigrationRunner.Run(save) is called on load whenever the saved version is lower than the application’s CURRENT_VERSION.

Cloud vs Local Sync

Not all sections are synced to Nakama cloud storage. The settings block is device-local only: Cloud saves are written to Nakama’s storage with collection "player_save" and key "profile".

Schema Versioning & Migration

Each migration function is a simple transformer. Examples:
  • v1 → v2: Add default value for shapeAssist (new field).
  • v2 → v3: Rename avgFindTimeavgFindTimeMs.
If the saved version is higher than the application’s CURRENT_VERSION (downgrade scenario), the file is ignored and a fresh default save is created, with a warning logged.
Always increment version when you add, remove, or rename a field in this schema, and write the corresponding migration function. Forgetting this step corrupts saves silently for users upgrading from an older version.

Save Integrity

A CRC32 or SHA-256 checksum is appended to the local save file. On load, if the checksum does not match, the file is treated as corrupt: the corrupted file is moved to set3d_save.json.bak and a fresh default save is created. For cloud saves, integrity is guaranteed by Nakama’s own storage layer.

Network Message Format

All client–Nakama communication uses JSON. This keeps debugging straightforward and meets performance requirements since match state messages are small (under 1 KB).

Client → Server (Action)

cardIds contains exactly three CardId values (0–80). The server validates the claim independently — the client never asserts that a claim is valid.

Server → Client (State Broadcast)

board is an ordered array of CardId values (or null for empty slots), matching the slot indices 0–20. Serialization notes:
  • Fields are serialized in a defined order using [JsonProperty(Order = X)] attributes to allow future checksumming.
  • Use Newtonsoft.Json with dedicated DTO classes for network messages — not JsonUtility, which cannot handle nullable types or nested objects reliably.
  • If performance or bandwidth becomes a concern in future phases, the format can be migrated to MessagePack (Nakama supports custom serializers) without changing the message schema.

Cosmetics Catalogue

File: Assets/_Project/Data/cosmetics_catalogue.json Defines all purchasable and unlockable cosmetic items. Each entry references a Unity Addressables key.
Field definitions: The build-time validation pipeline verifies that every assetKey in this catalogue resolves to a valid Addressables entry. A missing asset is a build error, not a runtime warning.

Localization Strings

File: Assets/_Project/Data/strings.json All user-facing strings are keyed even though v1.0 ships English-only. This avoids code changes when localization is added in a future version.
A LocalizationService resolves keys to the current-language string. Use LocalizationService.Get("game.set_found") in code, never a raw string literal.

Serialization Quick Reference

Common Mistakes

JsonUtility does not support Dictionary<,>, nullable types, or polymorphic lists. Use Newtonsoft.Json for any object that has nested structures, nullable fields, or dictionary keys. Reserve JsonUtility for simple, flat value types only.
Every time you add, remove, or rename a field in set3d_save.json, you must increment CURRENT_VERSION in code and add a migration step in MigrationRunner. Skipping this silently corrupts saves for users upgrading from an earlier app version.
The save integrity system only catches corruption if the checksum is written on every save. If your code writes to set3d_save.json via any path that bypasses the SaveManager, the checksum will be stale and the next load will discard valid data. Always save through SaveManager.Save().
Network message handlers on both client and server must validate that every CardId in a cardIds array is in [0, 80]. Values outside this range indicate a client bug or a tampered message. The server rejects claims with out-of-range IDs.

Glossary

Definitions for CardId, CardAttributes, GameRules, PenaltyMode, and every other term in the codebase.

FAQ

Why Newtonsoft.Json over JsonUtility? Why server-authoritative validation? Answered here.

Card Model

How Cards and CardAttributes are constructed from attribute enums.

Multiplayer Authority Model

How the server validates claims and why clients never self-report valid Sets.