Network sync: shared state (server-validated actions)

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.

Source: module-kits:kits/net_state/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs net-stateMCP tool: gripforge_docs { "topic": "net-state" }

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):

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 #

<!-- test: kits/net_state/tests/net_state.rs::an_action_travels_as_a_command_and_answers_its_id_a_refusal_answers_null --> 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.

#[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.

SettingTypeDefaultRange or valuesMeaning
presetstring"none"none, board-game, card-game, party-lobby, rooms, simultaneous-duelReady-made option set applied between the defaults and this config (defaults ← preset ← your keys, merged key by key): board-game…
initialobject—Initial state document (a JSON object).
actionsobject—Action types: {type: {args: schema, turn?, phase?, who?, when: [conditions], effects: [effects], endTurn?, hook?, secret?}} (see…
visibilityarray—Hidden information: [{path: "/hands/*", to: owner | none | [seats], ownerField?, reveal?: conditions, others: hide | count |…
turnsobject{"enabled": false, "timeoutMs": 0, "autoStart…
seatsobject{"max": 8, "rejoinByName": true, "takeOver": …
simultaneousobject{"enabled": false, "choicesPath": "/choices",…Simultaneous decisions (secret choices resolved together).
endobject{"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).
roomsobject{"enabled": false, "challengeTtlMs": 30000, "…Rooms service rules (see the README).
historyobject{"max": 64}
undoobject{"enabled": false, "max": 8, "by": "actor"}
crdtobject—CRDT fields without arbitration: {"/path": "counter" | "lww" | "set"}.
crdtLimitsobject{"maxSetTags": 1024, "maxValueBytes": 1024, "…Bounds of the CRDT fields (server and clients).
timersobject—Named timers: {name: {effects: [...]}} run when the timer (started by the timer effect or command) expires.
maxTimersinteger321–1024Named timers running at once (a new one beyond is ignored).
hookUnknownbooleanfalseUndeclared action types go to the server hook instead of being refused.
hookTimeoutMsinteger500050–120000An action the hook does not answer in time is rejected (hook_timeout).
rosterPathstring"/players"Where the seats are mirrored in the state ([{name, on}]); empty = off.
phasePathstring"/phase"The phase the phase conditions and effects use.
maxStateBytesinteger2621441024–16777216An action that would grow the state beyond this is rejected (too_big).
maxArgsBytesinteger102416–16384Largest action arguments (JSON bytes; clients check it before sending).
maxEffectsinteger2561–100000Effects one action (or timer, or turn default) may run, nested ones included.
maxEffectCountinteger100001–1000000Largest count of a deal / draw effect.
actionsPerSecondnumber100.1–1000Per peer; beyond, actions are rejected (rate).
actionsBurst0Per-peer action bucket (0 = auto: 2 × actionsPerSecond).
maxQueuedActionsinteger2561–65536Server: actions waiting for the hook, all peers together; beyond, rejected (busy).
maxQueuedActionsPerPeerinteger161–4096Server: the same per peer.
maxPatchBytesinteger480001024–16777216A patch larger than this is replaced by the full view (chunked by chunkBytes); ≤ transport maxMessageBytes.
maxPendingActionsinteger641–4096Client: actions in flight (sent, not yet accepted or rejected).
maxLogEntriesinteger409616–1000000Client: history entries kept.
seedinteger00–4294967295Seed of shuffle / random (0 = random per match).
crdtResendMsinteger25020–60000Client CRDT writes are resent this often until the server has them.
crdtSyncMsinteger100050–60000Server re-broadcast period of the CRDT states (anti-entropy).
chunkBytesinteger327681024–1048576Full states larger than this travel in chunks (≤ transport maxMessageBytes).
playerNamestring—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.