# Network sync: shared state (server-validated actions) (net.state) ## What it is for Syncs a shared, versioned state through server-validated actions, with no simulation tick: turn-based and board games, card games with hidden hands, party games, lobbies and persistent data. It needs `net.transport`, `game.engine`; installing it brings them in. ## Turn it on Install the kit in a project, then change a setting (the values shown are its defaults): ```text gripforge_gamekit_install { project_id: "gkp_…", id: "net.state" } gripforge_gamekit_configure { project_id: "gkp_…", id: "net.state", config: { "preset": "none", "initial": null } } ``` ## First example This is the body of the passing test `an_action_travels_as_a_command_and_answers_its_id_a_refusal_answers_null`; the helper functions it calls are defined at the top of the same test file. ```rust #[test] fn an_action_travels_as_a_command_and_answers_its_id_a_refusal_answers_null() { let mut s = online(json!({})); answer(&mut s, "c:act", json!(21)); assert_eq!( call(&mut s, KIT, "act", json!(["draw", { "n": 2 }])), json!(21) ); let sent = commands(&mut s); let act = sent.iter().find(|c| c["c"] == "act").unwrap(); assert_eq!(act["type"], "draw"); assert_eq!(act["args"], json!({ "n": 2 })); // No arguments: an empty object travels. call(&mut s, KIT, "act", json!(["pass"])); let sent = commands(&mut s); assert_eq!(sent.last().unwrap()["args"], json!({})); call(&mut s, "probe.core", "refuse", json!(["act"])); assert_eq!(call(&mut s, KIT, "act", json!(["draw", {}])), Json::Null); assert!(logged(&s, "net.state: act draw: act refused")); } ``` ## Settings and defaults Descriptions come from the kit manifest and stay in English. | Setting | Type | Default | Range or values | Meaning | |---|---|---|---|---| | `preset` | string | `"none"` | `none`, `board-game`, `card-game`, `party-lobby`, `rooms`, `simultaneous-duel` | Ready-made option set applied between the defaults and this config (defaults ← preset ← your keys, merged key by key): board-game… | | `initial` | object | — | | Initial state document (a JSON object). | | `actions` | object | — | | Action types: {type: {args: schema, turn?, phase?, who?, when: [conditions], effects: [effects], endTurn?, hook?, secret?}} (see… | | `visibility` | array | — | | Hidden information: [{path: "/hands/*", to: owner \| none \| [seats], ownerField?, reveal?: conditions, others: hide \| count \|… | | `turns` | object | `{"enabled": false, "timeoutMs": 0, "autoStart…` | | | | `seats` | object | `{"max": 8, "rejoinByName": true, "takeOver": …` | | | | `simultaneous` | object | `{"enabled": false, "choicesPath": "/choices",…` | | Simultaneous decisions (secret choices resolved together). | | `end` | object | `{"onLeave": false, "joinTimeoutMs": 30000, "p…` | | The terminal state of a match: set once by the end command, a hook answer with end, or a player leaving (onLeave). | | `rooms` | object | `{"enabled": false, "challengeTtlMs": 30000, "…` | | Rooms service rules (see the README). | | `history` | object | `{"max": 64}` | | | | `undo` | object | `{"enabled": false, "max": 8, "by": "actor"}` | | | | `crdt` | object | — | | CRDT fields without arbitration: {"/path": "counter" \| "lww" \| "set"}. | | `crdtLimits` | object | `{"maxSetTags": 1024, "maxValueBytes": 1024, "…` | | Bounds of the CRDT fields (server and clients). | | `timers` | object | — | | Named timers: {name: {effects: [...]}} run when the timer (started by the `timer` effect or command) expires. | | `maxTimers` | integer | `32` | 1–1024 | Named timers running at once (a new one beyond is ignored). | | `hookUnknown` | boolean | `false` | | Undeclared action types go to the server hook instead of being refused. | | `hookTimeoutMs` | integer | `5000` | 50–120000 | An action the hook does not answer in time is rejected (hook_timeout). | | `rosterPath` | string | `"/players"` | | Where the seats are mirrored in the state ([{name, on}]); empty = off. | | `phasePath` | string | `"/phase"` | | The phase the `phase` conditions and effects use. | | `maxStateBytes` | integer | `262144` | 1024–16777216 | An action that would grow the state beyond this is rejected (too_big). | | `maxArgsBytes` | integer | `1024` | 16–16384 | Largest action arguments (JSON bytes; clients check it before sending). | | `maxEffects` | integer | `256` | 1–100000 | Effects one action (or timer, or turn default) may run, nested ones included. | | `maxEffectCount` | integer | `10000` | 1–1000000 | Largest `count` of a deal / draw effect. | | `actionsPerSecond` | number | `10` | 0.1–1000 | Per peer; beyond, actions are rejected (rate). | | `actionsBurst` | | `0` | | Per-peer action bucket (0 = auto: 2 × actionsPerSecond). | | `maxQueuedActions` | integer | `256` | 1–65536 | Server: actions waiting for the hook, all peers together; beyond, rejected (busy). | | `maxQueuedActionsPerPeer` | integer | `16` | 1–4096 | Server: the same per peer. | | `maxPatchBytes` | integer | `48000` | 1024–16777216 | A patch larger than this is replaced by the full view (chunked by chunkBytes); ≤ transport maxMessageBytes. | | `maxPendingActions` | integer | `64` | 1–4096 | Client: actions in flight (sent, not yet accepted or rejected). | | `maxLogEntries` | integer | `4096` | 16–1000000 | Client: history entries kept. | | `seed` | integer | `0` | 0–4294967295 | Seed of shuffle / random (0 = random per match). | | `crdtResendMs` | integer | `250` | 20–60000 | Client CRDT writes are resent this often until the server has them. | | `crdtSyncMs` | integer | `1000` | 50–60000 | Server re-broadcast period of the CRDT states (anti-entropy). | | `chunkBytes` | integer | `32768` | 1024–1048576 | Full states larger than this travel in chunks (≤ transport maxMessageBytes). | | `playerName` | string | — | | Web runtime: the player name sent in the handshake (used by rejoinByName). | ## Services, events, variables - Service `net.sync`: The project replication model (one per project: the net.* sync kits conflict with each other). - Service `net.state`: act(type, args) / state() / turn() / history() / undo() / crdt(); patch, accepted, rejected, turn, timer events; server hook, snapshots. - Events emitted: - `net.state.sync` (`v`, `seat`) — The full view arrived (join, resume, resync). - `net.state.patch` (`v`, `ops`) — The view changed (compact JSON ops). - `net.state.accepted` (`id`, `v`) — An action of this client was applied. - `net.state.rejected` (`id`, `why`) — An action of this client was refused (rule reason, hook reason, rate, disconnected). - `net.turn` (`seat`, `mine`, `n`, `round`, `remainingMs`, `active`) — The turn changed. - `net.timer` (`name`, `remainingMs`, `expired`) — A timer started, was cancelled or expired (`turn` for the turn timer). - Variables: - `net.status` (string) — Network status - `net.rtt` (number) — Ping (ms) - `net.peers` (number) — Players online - `net.turn_seat` (number) — Seat to play - `net.my_turn` (boolean) — My turn - `net.turn_ms` (number) — Turn time left - `net.state_version` (number) — State version ## Hooks and modding This kit declares no hook and no collection to mod. ## Engines Declared targets: `web`, `godot`, `unity`, `unreal`. `gripforge kit api net.state` lists the exact settings and services of your version. ## Versions Kit version 1.3.0.