Networking¶
Status: Current
Last reviewed: 2026-07-07
Source: shared/src/protocol.ts, shared/src/constants.ts, server/src/game/zone.ts
The transport is one WebSocket per client carrying JSON messages. Every message has a string t discriminator. The protocol is versioned by PROTOCOL_VERSION (2); a client that sends the wrong version in join_world is rejected and disconnected.
Client to Server¶
| Message | Purpose |
|---|---|
join_world |
Enter the world with a protocol version and a name. |
player_input |
One fixed-step input command: seq, dt, moveX, moveZ, yaw, pitch, jump, sprint, block. Never carries a position. |
equip_weapon |
Request a weapon swap (blocked while the weapon state machine is busy). |
attack |
Start a melee swing (or a quick snap shot with the bow) with an aim yaw/pitch. |
draw_start |
Begin drawing the bow. The server timestamps the draw itself. |
draw_cancel |
Abandon a draw without loosing. |
draw_release |
Loose the arrow with a final aim direction. |
cast_spell |
Cast fireball, lightning_bolt, or rift_burst with an aim direction. |
interact |
Interact with a target id (loot bag or NPC). |
quest_action |
accept, turn_in, or abandon a quest. |
use_item |
Consume an item (healing or mana draught). |
chat |
Send a chat line. |
ping |
Latency probe; echoed as pong. |
request_zone |
Ask for the full zone document (sent on a cache miss). |
admin_login |
Authenticate an editor session with a token. |
admin_cmd |
Issue one editor/playtest command (see Level Editor). |
Server to Client¶
| Message | Purpose |
|---|---|
welcome |
Join accepted: self id, zone id/name, zoneHash, tick rate, inventory, skills, quest definitions, quest log, ember snapshot, admin flag. |
zone_data |
The full ZoneData document plus its hash (answer to request_zone or after an import/reload). |
zone_object_update |
A single editor change (create/update/delete) plus the new hash, so live clients rebuild one object. |
server_snapshot |
The per-tick world state (see below). |
entity_spawned / entity_removed |
An entity entered or left the client's view. |
damage_event |
A hit resolved: target, source, amount, kind, position, killed, blocked. |
loot_event |
Items granted from a loot bag. |
skill_progress_event |
A skill advanced (and whether it levelled). |
inventory_update |
The player's inventory changed. |
chat |
A chat line on the say, system, or combat channel. |
dialogue |
An NPC's lines plus the quests it can offer or complete for this player. |
quest_update |
New per-objective progress for one quest. |
quest_event |
A quest was accepted, became ready, or was turned in (with rewards). |
ember_state |
The current Emberwake snapshot (level, pressure, gate, champion). |
effect |
A one-shot visual/audio cue (explosion, lightning beam, parry, ground slam, ember shift, gate open, and so on). |
death / respawn |
The player died (with a respawn timer) or respawned. |
pong |
Latency reply. |
error |
A fatal or advisory message. |
admin_state |
Result of an admin_login (is-admin plus a message). |
admin_result |
Result of an admin_cmd (ok plus a message). |
Snapshot Structure¶
server_snapshot is the heartbeat, sent at 15 Hz:
tick,time(server clock),ackSeq(the highest input sequence the server has processed for this client).self: the authoritative local player state, including position, velocity, on-ground flag, hp/stamina/mana and their maxima, weapon, dead and safe-zone flags, the weapon-state-machine valuewstate, an optional bowdrawamount, and any non-zerocooldowns.entities: an array of smallEntitySnaprecords (id, position, yaw, and where relevant pitch, velocity, hp, anim state, weapon) for everything else in view.
Full descriptions arrive once via entity_spawned; snapshots then carry only the small changing fields.
Interest Management¶
Foundations are in place; full per-player spatial interest is future work.
- Entities beyond
INTEREST.nearRange(90 m) from a player are sent only on everyINTEREST.farEvery-th snapshot (every 4th). - Combat-relevant entities (aggro'd goblins, all projectiles) are always included regardless of distance.
- The starter zone is small, so this mainly exercises the mechanism. A production build will replace it with a real spatial grid and per-client subscription sets.
Zone Data Loading¶
Map geometry is never streamed per tick. welcome carries a zoneHash (an FNV-style hash of the zone document). The client keeps the last zone in localStorage; on a hash match it builds from cache, otherwise it sends request_zone and receives one zone_data message. Editor edits arrive as small zone_object_update messages rather than whole-zone resends.
Prediction and Interpolation¶
- The client predicts its own movement by running the shared
stepMovementon eachplayer_input. On each snapshot it rewinds to the server's authoritativeself, replays inputs newer thanackSeq, and folds any small residual into a decaying visual offset so the camera never pops. Corrections larger than 3 m snap. - Remote players, goblins, and projectiles are rendered about
INTERP_DELAY_MS(140 ms) in the past, interpolating between the two buffered snapshot states that straddle the render time.
Anti-Cheat Sanity Checks¶
The trust boundary is thin in the prototype (names are unauthenticated), but the server never trusts client-supplied physics:
dtper input is clamped toMAX_INPUT_DT(0.1 s) and at mostMAX_INPUTS_PER_TICK(6) inputs are consumed per tick; the queue is bounded.- Horizontal speed is clamped to
MOVE.maxSpeedinside the shared movement step, which the server also runs. - Stamina, mana, cooldowns, and bow draw time are all validated server-side. A client that claims a full draw still only gets the draw the server timed.
- Pitch is clamped; chat is length-limited and name characters are filtered.
Known limitations
There is no delta-compression of snapshots yet (each snapshot is a full state for the entities in view), no per-client spatial interest beyond the near/far split above, and no account authentication. These are called out on the Roadmap.