Skip to main content
overlayed.sand.readyForGameEvents() must be called before any events will be emitted.

Retained vs transient

Retained events are held by the module, which replays the latest copy to a client that connects late — so you never miss one by subscribing after the fact. Transient events are only seen live. Some retained events are stored per subject rather than one overall. Those have a key: a later event with the same key replaces the earlier one. party_member_joined is keyed on account_id, player_joined on platform_id, extraction_added on extraction_id, and the trampler events on trampler_id. Retention has two levels. Menu-level events are held for the life of the process. Match-level events are all dropped at match_ended, so a new expedition starts from a clean slate. module_loaded, module_unloaded, and unsupported_game_version are emitted for Sand Raiders too, but they arrive on the universal module — see Universal. Positions are world space with y as height, ready to use as given.
The type.ts block under each event below describes that event’s content object. The event you receive wraps it: { game, type, creation_time, content }. So a handler reads event.content.state and event.creation_time.

logged_in

Retained at menu level. Emitted once and held for the life of the process. account_id is the same identity as party_member_joined.account_id. platform_id is a SteamID64 on the Steam build.
account_id and player_joined.player_id are different ids and never match, even though both identify an account. Use platform_id to tie a logged-in account to an in-match player.

game_state_changed

Retained at menu level, replaced on each change. Expedition status, or menu when there is no expedition.

party_changed

Retained at menu level, replaced on each change and released when the expedition ends. There is no separate disband event. The party survives into a match, so this is not menu-only in the sense of being dropped at match_started.

party_member_joined

Retained at menu level, keyed on account_id, released by party_member_left. One per party member, including the local player. Re-emitted with the same account_id when a member’s details change, which replaces the retained copy. Carries the same identity fields as logged_in, plus the member’s selected character and whether they lead the party.
display_name can arrive empty, most often for a member who joins while you are in a match. It is filled in by a later party_member_joined for the same account_id, so do not treat the first event for a member as final. A name that has been resolved is never replaced by an empty one.

party_member_left

Not retained. Releases the retained party_member_joined with the same account_id. When the whole party disbands, one of these is emitted per member before party_changed is released.

inventory_changed

Retained at menu level, replaced on every change. A full storage snapshot.
crowns, mechanical_parts, pneumatic_parts, and computing_modules are totals for items that also appear in items, not separate balances. crowns equals the item_coinCrown entry’s amount. Adding them to the items total double-counts.
Currencies are ordinary items — they appear in items under their own definition and occupy slots. slots_used counts one slot per stack whatever its size, and can exceed slots_total. Stacks sharing a definition are summed into a single items entry, so the entry count can be lower than slots_used. The snapshot can stay unchanged for a while after a match before the next update arrives. The last snapshot is held rather than being reported as empty.

inventory_item_changed

Not retained. One per definition whose total moved, emitted alongside the replacing inventory_changed. A definition whose last row is gone reports amount 0 with a negative delta. Currency movement arrives under its item definition — crowns as item_coinCrown — never under the aggregate name.

tech_tree_changed

Retained at menu level, replaced on every change. The full research tree state, never partial.
is_available means researchable right now — requirements met and not yet bought. It is not “usable”: an already-unlocked node reports is_available false, because there is nothing left to research on it. Treat is_unlocked and is_available as a state pair, not as independent flags.
unlocked counts the nodes with is_unlocked set, total is the node count in the tree, and nodes is sorted by id. tier is depth in the tree, 0-based.

tech_tree_item_unlocked

Not retained. One per node that flipped from locked to unlocked, emitted alongside the replacing tech_tree_changed. Availability changes are not broadcast — a node becoming researchable is only visible in the retained tech_tree_changed state.

walker_changed

Retained at menu level, replaced on each change. The trampler the account has built. It is not updated during a match, but the retained copy carries across it, so the walker taken into a match is still readable while the match runs. walker_id is the same id trampler_acquired.master_server_id reports in a match, which is how the two are matched up.
blueprint_file is a saved blueprint’s file name, without its extension. It is empty when the walker was not built from a saved blueprint — the stock expedition walker, for instance — and is unrelated to blueprint_unique_id.
first_name_index and second_name_index are opaque name indices, meaningful only as a pair. 0, 0 means the walker is unnamed.

match_started

Retained at match level until match_ended. world_bounds is the playable extent on the xz plane — use it to map world coordinates onto a rendered map. landmarks are the named islands and forts, the places drawn on the in-game map. server_start_time is Unix milliseconds and marks when the match began, not when you joined it. In Voyage it can be days in the past.

match_ended

Not retained. The game state was left — every retained match-level event is dropped with it.

world_time_changed

Retained at match level, replaced on each change. Fires when the in-game hour rolls over.

player_joined

Retained at match level, keyed on platform_id. One per player on the in-game player list, which keeps players after they disconnect. Re-emitted to replace the retained copy if their details change. There is no counterpart leave event — a player who drops stays on the list. player_id is the id trampler_acquired.creator_account_id also reports, so the two can be matched directly. It is not logged_in.account_id and will never equal it; match on platform_id to tie a player to the logged-in account.

extraction_added

Retained at match level, keyed on extraction_id, released by extraction_removed. In Storm Dive these arrive through the match rather than all at once, so do not expect the full set at match_started. radius is the extraction zone radius in world units.

extraction_removed

Not retained. Releases the retained extraction_added with the same extraction_id.

storm_changed

Retained at match level, replaced on each change. Storm Dive only. delay is the pause before the circle starts closing; moving is the close itself. storm_changed fires on stage and state transitions only, so the live circle is not streamed to you. To draw a closing circle, interpolate from center/radius toward next_center/next_radius over the time remaining until end_time. At the next event, center and radius equal the previous next_center and next_radius.

trampler_acquired

Retained at match level, keyed on trampler_id, released by trampler_lost. One per trampler the account owns — the one you started the match with, and any captured later. This event is identity only. The trampler’s position arrives on trampler_changed, emitted at the same moment. trampler_id is scoped to the match and means nothing outside it. master_server_id is stable across matches, and is 0 when the trampler has no such id. When it is non-zero it matches walker_changed.walker_id, which is how you tell that the trampler you are flying is the walker you built:
creator_account_id is who authored the blueprint, not who is driving it. On a captured trampler it names the other player. It matches player_joined.player_id, not logged_in.account_id.
blueprint is the blueprint name — EXPEDITION_WALKER for the stock walker.

trampler_changed

Retained at match level, keyed on trampler_id, released by trampler_lost. Emitted on acquisition and then about every 10 seconds for as long as the trampler is owned, whether or not it moved. It is a heartbeat, not a movement feed — do not treat a new event as proof of motion, or the gap between two as a movement duration. yaw is degrees about the up axis. speed is the magnitude of velocity, supplied so you do not have to compute it.

trampler_lost

Not retained. Releases both trampler_acquired and trampler_changed for the same trampler_id. This arrives promptly rather than waiting for the next trampler_changed, so a trampler can be treated as gone as soon as you see it.