Map Replication
A client never assumes it already has the map a server is running. Instead, the server advertises the current map’s identity and content hash, and the client either hash-skips straight to loading a copy it already has, or requests a chunked transfer of the pallet bytes over the network. This is protocol v19 (NetProtocol.Version) — the version that introduced it; the current wire version is higher.
Advertising the map
Section titled “Advertising the map”The server names its current map as a (name, contentHash) pair everywhere a client needs to know what to load:
- On join, the pair rides
serverInfoasMapNameandMapContentHash— no separate round trip is needed before a fresh client can start resolving the map. - On a live map switch, the server broadcasts a
MapLoadmessage (id12) carrying the same pair to every already-connected session, so a client mid-session reloads to match without disconnecting.
MapContentHash is an FNV-1a-64 hash computed over the whole .pallet file’s bytes — content-addressed, not name-addressed, so two maps that happen to share a name are never confused and a byte-for-byte-identical map recompiled elsewhere hashes the same.
Hash-skip vs. chunked transfer
Section titled “Hash-skip vs. chunked transfer”On receiving either advert, the client resolves the named map the same way in both cases:
- Deterministic maps the client can build locally from its own content (the built-in
corepallet, for instance) resolve immediately with no network round trip at all. - Content the client already has cached by that exact hash loads straight from the on-disk cache — a hash-skip. This is the common case once a client has joined a server’s map once.
- Content the client does not have triggers a request: the client sends
MapRequest(id13) naming the content hash it needs, and the server answers by streaming the pallet as a sequence of reliable-orderedMapChunkmessages (id14).
A chunked transfer carries the pallet bytes in fixed MapChunkPayloadBytes (1024-byte) pieces over the reliable channel — small enough that a whole chunk packet, header included, stays under the engine’s 1200-byte unreliable-packet budget, even though the reliable channel itself could fragment a larger message. The client’s assembler accepts chunks as they arrive (out of a total chunk count carried in each chunk), so assembly never blocks the client’s poll loop. Once every chunk has arrived, the client re-hashes the assembled bytes and verifies them against the advertised content hash before trusting them.
Disk cache
Section titled “Disk cache”A verified transfer is written to a local cache keyed by its content hash, at <AppContext.BaseDirectory>/cache/maps/<hash>.pallet. A later join or switch that advertises the same hash — from this server or any other — hash-skips against that cached copy instead of re-requesting it. The cache is content-addressed, so nothing needs to track which server a given map came from.
Fresh join vs. live switch
Section titled “Fresh join vs. live switch”- On a fresh join, the client’s
ClientReady(which asks the server for a pawn) is gated until map resolution completes — hash-skip or full chunk transfer — so a slow transfer over a bad link can never race a pawn spawning into a map the client hasn’t loaded yet. - On a live switch, an already-connected, already-playing client reloads in place when it receives the
MapLoadbroadcast, running through the identical hash-skip/request/chunk path.
Embedded host vs. remote clients
Section titled “Embedded host vs. remote clients”The embedded loopback host (singleplayer, and the local player of a listen server) reloads its map in-process on a live switch, because it already holds the exact bytes the server is running — which is why a server broadcasting MapLoad explicitly skips it.
On a join it resolves the advert like anyone else. It has to: a client can switch servers at runtime, so the map it has meshed may be some other server’s, and the only honest way to know what the world it is joining is running is the content hash that world advertises. Joining the map already on screen hash-matches and costs no re-mesh (the boot map’s own pallet hash is seeded at startup for exactly that); anything else re-meshes, from the local cache when the bytes are already here and from a transfer when they are not. A client-side live switch clears that hash to zero — what is meshed came from in-process, not from any advert — so the next join always re-resolves rather than trusting a hash describing a map two switches ago.
Map hot reload
Section titled “Map hot reload”You do not have to restart the engine — or even leave the map — to see a map edit. Rebuild the map’s pallet with dh build while the engine is running and the loaded map reloads for everyone on the server.
Nothing locks the pallet. The engine holds a map’s compiled .pallet open for the whole life of that map, so the compiler publishes a rebuild by writing a sibling scratch file and then replacing the target, rather than truncating it in place. Every pallet reader opens with FileShare.Delete so that replace is permitted. A reader holding the previous build keeps seeing a consistent view of it (the old file is unlinked, not overwritten) until it reopens.
The server is the authority. Detection rides the same pallet watchers that keep the maps list current: when the pallet owning the currently loaded map is republished, the embedded host reloads the map through the identical path a typed map command uses — client re-mesh, then the server’s authoritative switch, then a MapLoad re-advert to every remote client (which hash-skip or stream the new bytes as above). Clients never watch their own copy of a pallet; if they did they would drift out of sync with the server’s map.
- Debounced and coalesced. A compiler writes in bursts, so notifications are collapsed into one reload; a rebuild that lands while a reload is still running becomes a single follow-up, never a queue.
- Atomic. The server independently loads and validates the rebuilt map before touching anything. A broken rebuild leaves the previous map fully installed, broadcasts nothing, and reports the error — clients are never left on a half-installed map.
- Players stay put. A reload is a content refresh of the map you are standing in, not a change of venue, so pawns are not respawned (unlike a
mapswitch, which does respawn them) — you keep looking at the thing you just rebuilt. Geometry can move under a player; the character controller’s depenetration recovers them, andrespawnis the escape hatch. - Everyone is told. The server broadcasts
ClientNoticetoasts to every session —Scene change detected: <barcode>when a rebuild is picked up, thenScene reloaded: <barcode>orScene reload failed: <reason>. These appear in the DigitalHeaven overlay whether or not it is open.
Pass --noMapReload to turn this off. It applies only while this client is the server: a client that launched into a remote one — or left its own for one — takes its map from that server, and a dedicated server watches no map registry.
Wire summary
Section titled “Wire summary”| Message id | Direction | Reliability | Carries |
|---|---|---|---|
MapLoad (12) | Server → client | Reliable | (name, contentHash) — broadcast on a live map switch; the join-time equivalent rides serverInfo instead |
MapRequest (13) | Client → server | Reliable | The content hash the client needs streamed |
MapChunk (14) | Server → client | Reliable, ordered | One 1024-byte slice of the pallet, plus its offset and the transfer’s total chunk count |
ClientNotice (16) | Server → client | Reliable | (severity, message) — an operator-facing overlay toast, broadcast to every session including the local host |
See Engine Assets & Audio for what happens to the pallet bytes once they’re loaded, and Engine Overview → Listen Server for how this path is shared with a player-hosted server.