Online zones
Usage guide of net.zones: a trusted server coordinator that admits players into zones, binds them to a network peer, checks every command (epoch, sequence, budget, target) and moves players between zones by ticket.
Zones, admission and transfers for an online world (net.zones) #
A server-side coordinator for a game with several zones (areas, dungeons, instances): who may enter which zone, which network peer plays which character, whether a command from a client is fresh and allowed, and how a character moves from one zone to another. Readable offline: gripforge docs net-zones, or the MCP tool gripforge_docs (topic net-zones).
What it is for #
net.zones runs on the trusted server, on top of the network session of the net.* kits (multiplayer guide). The server gives it the peer id that the network layer reports, never one claimed by a client packet. It stores no positions and no physics: those stay in your game, in metres, +Y up, -Z forward. It keeps zones with a capacity, the characters (actors) each account may play, the NPC placed in each zone, and one lease per connected peer. A lease says which actor and zone a peer holds, and carries an epoch and a sequence that make old or replayed commands useless.
It is not a login system: authenticate the account and grant its character (allow) before bind. join is a guest or demo shortcut that takes the first free admitted actor.
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "net.zones" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "net.zones",
config: { "maxZones": 16, "commandsPerFrame": 4 } }
The kit requires game.engine. It works on the web and in the native Rust host.
First example #
<!-- test: kits/net_zones/tests/net_zones.rs::a_transfer_is_prepared_then_committed_once_with_its_ticket --> Two zones (a for 2 actors, b for 1) and an admitted actor ada. Every call is net.zones.request("default", command):
{ "op": "register", "zone": "a", "capacity": 2 }
{ "op": "register", "zone": "b", "capacity": 1 }
{ "op": "allow", "actor": "ada", "zones": ["a", "b"] }
{ "op": "bind", "peer": 1, "actor": "ada", "zone": "a" }
{ "op": "prepare", "peer": 1, "zone": "b" }
prepare answers the lease with pending: { zone: "b", ticket, until } and reserves a place in b; the actor sends no command and is no target while it waits. When the destination is ready, { "op": "commit", "peer": 1, "ticket": <ticket> } moves the route once: the zone becomes b and the ticket is the new epoch. A second commit, or a wrong ticket, is refused.
Settings and defaults #
| Setting | Default | Range | Meaning |
|---|---|---|---|
maxZones | 64 | 1 to 256 | zones per coordinator |
maxActors | 1024 | 1 to 16384 | actors with a grant plus placed NPC, together |
transferFrames | 600 | 1 to 36000 | ticks a transfer reservation lasts |
resumeFrames | 3600 | 1 to 216000 | ticks a disconnected actor keeps its place |
commandsPerFrame | 8 | 1 to 64 | commands accepted per actor and tick |
A value out of range, or an unknown key, is refused when the coordinator is created.
Services, events, hooks #
net.zones(service):create(id, config),request(id, command),step(id?),ids(),remove(id),targetable(actor, zone). The coordinatordefaultexists at start and survives scene changes; a game can hold up to 128 instances in all (separate worlds).stepadvances one tick: it resets the command budgets and expires transfers and suspended actors. Call it once per authoritative tick, even with nobody connected.targetable(actor, zone)answers true when the actor is in that zone, online and not in a transfer: a read-only check for server-side AI.net.zones.created(event):{ instance, preset }; net.zones.removed (event):{ instance }.net.zones.changed(event):{ instance, op, result }after any command that is not a read or a fixed step.- net.zones.refused (event):
{ instance, op, by, reason }when a hook cancelled a command. net.zones.create(hook): before an instance is built;configis mutable; cancellable.net.zones.request(hook): before a deciding command;commandis mutable; cancellable. The command then answers{ ok: false, cancelled: true, by, reason }and nothing changes.
Commands (op) #
| Group | Ops |
|---|---|
| Setup (trusted) | register { zone, capacity }, allow { actor, zones }, place { entity, zone } for NPC targets |
| Session | bind { peer, actor, zone }, join { peer, zone }, suspend { peer }, resume { peer }, leave { peer } |
| Per command | authorize { peer, epoch, sequence, target? } |
| Transfer | prepare { peer, zone }, commit { peer, ticket }, cancel { peer } |
| Reads | route { peer }, view { peer }, members { zone }, status, config, targetable { actor, zone } |
| Save | save, load { save }, preview { command } |
Ids are 1 to 128 characters; a peer id is a positive integer. Actor and NPC ids are distinct. One actor has at most one peer.
How to use them:
- On connection,
bindthe authenticated character (orjoinfor a guest). On a final disconnect callleaveat once, so a reused peer id cannot inherit the lease; on a lost link callsuspend(it cancels transfers and rotates theepoch), thenresumeorjoinwhen the player is back. - Before running any gameplay command from a client, call
authorizewith the epoch and sequence the client sent. It refuses an offline or transferring peer, an oldepoch, a sequence that does not rise or jumps by more than 1024, a command over the per-tick budget, and atargetoutside the zone. The sequence is consumed even if your gameplay action then fails, so a retried cast cannot be paid twice; a command refused for budget does not use its sequence. - To change zone,
prepare, stage the destination in your game, thencommit.cancel, a suspension ortransferFramesticks without commit release the reservation. - Send each client only
view { peer }(the online actors and the NPC of its zone).statusis for the server: do not broadcast it.
save stores zones, NPC placement and grants, never leases or session secrets. load validates the whole save before applying it, clears every lease, and rejects a save made with other settings; players must be admitted again.
Hooks and modding #
Every key of the settings is open to mods, plus the net_zones_presets registry: create(id, { preset: "<id>", ...own keys }) takes the config of the preset under your own keys. The script verbs net_zones_request (action) and net_zones_exists (condition) call request and test an instance from content.
Engines #
The coordinator is a Rust core run by the kit host on the web and natively; the same core is available as a C library and a WebAssembly module. It decides who may do what; each engine still renders the world, runs the network session and applies the transfer in its own scenes.
When it does not do what you expect #
admission denied: the actor was notallowed in that zone, or does not exist.zone full: capacity reached, a reserved transfer place counts.peer already bound,actor already bound: one peer per actor;leavethe old peer first.stale or invalid sequence: replay, no rise, or a jump over 1024.inactive or stale route: wrongepoch, a suspended peer, or a transfer in progress.command budget exceeded: more thancommandsPerFramecommands in one tick; callstepeach tick.target outside zone: the target is not a placed NPC of that zone or an online actor there.invalid transfer ticket,no pending transfer: wrong ticket, already committed, cancelled or expired.unknown peer: the peer was never bound, or already left.zone limitorinvalid zone entity:maxZonesormaxActorsreached, or a duplicate id.save mismatch: the save was made with other settings.
Limits #
One coordinator is one authority: a transfer is a local hand-over between zones that the same server has loaded, not a distributed transaction between servers. No login, no persistence of sessions, no positions. Zones share the world of the server; they are logical areas, not streamed scenes.
Versions #
Kit 1.0.0, ABI v1 of the zones library.