State replication and streaming
Follow instance baselines, component deltas, deferred references, audience filtering, and motion streaming between server and client.
Asterune replicates a world through several cooperating streams. Parts, ordinary instances, component rows, tools, scene state, terrain, and physical motion have separate publishers and client application paths. Each publisher tracks the state it has sent rather than serializing the complete live world on every tick.
The separation avoids sending static properties at the frequency needed by moving objects. It also creates ordering problems: a component row can reach the client before its instance, and a property patch can precede a part arriving on another transport lane. Client mirrors retain such work until the required objects or definitions exist.
Place baselines and delivery generations
Initial place content establishes a baseline with the server's instance IDs. The ordinary-instance publisher loads that client-visible place representation and records its stored properties. A joining client then receives the creations, changes, and removals needed to reach the live state. An unchanged authored object does not need a second full record merely because a player joined later.
Player instances, character models, and services have additional identity rules. Each client creates its own player and character objects. Component updates can target a player or character by player ID instead of assuming that those locally created objects share the server's instance ID. Service records are similarly matched to the client's service by class when their settings are patched.
Place transitions attach a delivery generation to instance, component, tool, and terrain messages. A client applies a matching generation, retains a future generation until that place is entered, and ignores generations older than the entered place. Generation zero uses the direct application path. This prevents delayed state from one place from modifying the next place's world.
Separate publishers for separate state
The major publishers divide responsibilities as follows:
| Publisher | State tracked | Change representation |
|---|---|---|
| Parts | Replicated part snapshots | Full creation, changed non-motion properties, and deletion |
| Ordinary instances | Stored properties and hierarchy under replicated services | Whole record on creation, named-property patches, reparenting, and destruction |
| Components | Replicated rows, tags, attributes, and supporting containers | Declarations, field masks, collection edits, and removals |
| Tools | Tool trees and holders visible to each player | Whole tree, holder move, detach, and destroyed IDs |
| Scene and environment | Lighting, sky, and scene snapshots | Replacement when the snapshot differs |
| Terrain | Grid revision and changed or removed chunks | Encoded chunk replacements and snapshot reset |
| Physics | Unanchored-part and player motion snapshots | Periodic motion batches with server ticks |
Part property comparison explicitly excludes position, rotation, velocity, and angular velocity before deciding whether to send an ordinary property change. Those fields have their own motion stream. A change to shape or material can therefore send a property snapshot without making every physics step produce that same reliable update.
The ordinary-instance publisher walks replicated services only when the global mutation or topology revision changes. During the walk, a node's mutation revision skips property comparison when its state has not changed. Geometry extras use their own fingerprint because the part snapshot already carries other part properties. Changed records are batched with at most 256 records per message.
A client patches an existing instance through property-aware update logic, keeping runtime state rather than replacing the object wholesale. Changed properties generate change notifications. A destroyed object also clears waiting patches and parent operations for its ID, preventing late work from recreating that instance.
Component declarations and dirty masks
A component declaration assigns a wire index to a component name and ordered field list. Later changes identify that index rather than repeating the schema for each row. The client resolves the declaration against its component definitions before applying values.
The publisher includes a definition only when that definition belongs to the client-visible place content and its replication policy permits delivery. Declaring an unrelated server-only schema does not distribute its definition to clients through a row update.
A field change carries a 64-bit mask and values for the selected fields. The publisher consumes dirty masks from the component store. It combines them with metadata journals, topology journals, and removal history to build the set of affected instances. A normal pass examines those subjects rather than scanning every component row.
Some changes require a broader pass. Replacing the component store, overflowing a topology journal, losing required removal history, changing definitions, or changing the replication reach signature can force reconsideration of the published view. An unchanged field value is not sufficient to decide whether an object remains reachable after its parent or audience changes.
The publisher remembers each subject's target, owner, rows, and audience. If those identity or reach rules change, it retracts the preceding representation and publishes the new one. Otherwise, it sends field-level differences. Supporting container shapes are ordered by hierarchy depth so parents appear before their children within that tree update.
Collections have another delta layer. Lists send indexed replacements and
truncation; maps send key replacements and removals; structures send named
field replacements. The publisher keeps the preceding collection value and
uses edits when 2 × edit_count is no greater than the new collection length,
with a minimum comparison length of 1. This is a count-based heuristic, rather
than a measurement of serialized byte savings. Other cases send the whole
collection value.
Deferred application and dependency ordering
Transport lanes do not provide a global order. The component mirror first accepts declarations and supporting tree changes, then processes removals, row changes, tags, and attributes. If a row operation lacks its target, declaration, or local component definition, the mirror holds it instead of applying an incomplete row.
Operations for one pending target retain their order. A later operation for that target also waits, even if it could otherwise apply immediately. On retry, the mirror processes held work oldest first. The pending queue holds at most 16,384 operations, removing its oldest operation when it reaches that limit. A held operation expires after its bounded retry count reaches 600; this count measures retries, not seconds.
An ordinary instance reference inside a component value follows a different
rule. The field can retain the incoming instance ID before that instance
arrives; reading the reference returns nil until live lookup can resolve it.
The mirror does not hold every row merely because another referenced instance
is absent.
Ordinary instance replication has a separate parent and patch mechanism. A child whose parent has not arrived retains its intended parent ID. A patch for an instance that has not arrived retains the incoming record and affected property names. Part arrival triggers another opportunity to settle those relationships. References to an already destroyed ID are not treated as an instance that still needs to arrive.
These mechanisms explain why an object's data can arrive without becoming immediately usable. Reliable delivery establishes eventual message receipt while the connection remains viable; it does not make separately replicated objects appear atomically in one frame.
Audience changes and client visibility
Audience filtering is distinct from distance-based motion interest. A part's audience determines which players receive its creation and property state. When an existing part stops admitting a player, the server sends a deletion for that client's copy. If the audience admits that player again, the server sends a whole creation snapshot.
Ordinary instance and tool replication preserve additional lifecycle states. Reparenting an ordinary instance to no parent can remove it from the client's visible hierarchy without destroying its identity. A tool can detach when its holder is no longer visible. These operations have different meanings from server destruction, even though the object disappears from that view.
Component publication also distinguishes shared, owner-only, and explicit audience groups. A changed owner or group can require removal from preceding recipients and a full row for new recipients. Tags and attributes follow their published audience rather than automatically becoming public metadata.
Spatial interest and periodic motion
The spatial streaming path filters periodic physics updates. For each player, the server builds interest origins with squared radii. Without an explicit radius, an origin uses the workspace's target radius, bounded below by its minimum radius. When streaming is disabled, the default radius is infinite. A player with no usable origins passes the spatial visibility test.
A part first passes its audience rule. An always-relevant part then bypasses the distance test, but not that audience rule. Other parts reach a player when at least one origin contains their position. A player always receives their own character's motion; other characters use the same origin-distance test.
This path reduces motion delivery outside a player's interest. It does not by itself implement spatial unloading of every instance or asset. Creation, property replication, and place content follow the publishers described separately. Treating a missing motion sample as proof that a distant part was destroyed would conflate those paths.
The server clock runs at 60 ticks per second. Its default replication divisor is 3, producing a 20 Hz physics-broadcast opportunity. A configured replication rate changes that divisor. Non-motion state has its own publication decisions, so the physics broadcast rate does not define one universal replication rate.
Unanchored parts send changed physics snapshots, with a staggered refresh every 20 broadcast opportunities. Refreshing unchanged state lets a later sample replace a lost unreliable update. The refresh is distributed using the part ID rather than sending every unchanged part in the same broadcast.
Compact motion and datagram budgets
Motion batches reserve a conservative 96-byte envelope from the 1,100-byte transport payload, leaving a 1,004-byte item budget. The batching function adds serialized items until another item would exceed that budget. The ordinary broadcast path sends an individual oversized item reliably on the state lane.
Part transforms keep translation as floating-point coordinates and encode rotation as a normalized axis and angle. The axis uses 3 signed 16-bit values; the angle uses one unsigned 16-bit value over 0 to π. Encoding first normalizes the quaternion and chooses its nonnegative-W representation. Decoding renormalizes the axis and reconstructs a quaternion. An invalid or degenerate input rotation encodes as identity.
Clients use the server tick and per-subject freshness checks to reject reordered physics samples. Their presentation then runs on a delayed motion timeline, separately from local ownership and character input replay. See Prediction and reconciliation for the interpolation and handoff rules.