engine v4.4.2
Engine v4.4.2
May 20, 2026
A patch in the Solid line.
what's new
- Build bigger worlds with lots of places — areas no one is in quietly sleep so the game stays smooth, and snap back to life the moment someone walks in. Your spawn area is always ready.
- Joining a game is more reliable — fewer hangs on the loading screen when you click play.
- Busy worlds stay in sync — when lots of players are exploring different corners of the same game, things no longer snap or jitter on screen.
- Avatars don't vanish on you anymore — Savi catches her own slip-ups when updating your character, instead of accidentally making it disappear.
›technical notes
Engine
- Place residency (#6528): added
PlaceResidencyResourcetracking which places have materialized ECS state on the server. Non-default places lazy-load onenterPlace/spawnPlayer— ECS entities, physics runtimes, terrain chunks, and atmosphere are only created when a player enters. Empty non-default places unload after 300 ticks (~10s) with no players: entities destroyed (onDestroyfires), physics/terrain/atmosphere freed. Ephemeral places additionally drop from the spec on unload; authored/session/persistent places retain their spec definition for re-loading. Default place is always resident.ensurePlaceResident()triggers a spec update so the place materializes in the same tick. Observability:tome.place.resident,tome.place.unload_scheduled,tome.place.unload.
Networking
- Fixed connection-startup races (#6523) where client messages (e.g. fullsync requests) arriving before connection setup completed were silently dropped at three layers: container WebSocket open handler, runtime worker attach flow, and room-runtime pending-connection queue. Early-arriving messages are now buffered per-connection and replayed in order once the connection materializes.
Tome / ObjectAPI
- Fixed non-deterministic
api.random()under Area of Interest (#6529). The behavior-update system previously used a single shared RNG stream per tick; when client and server iterated different entity sets due to AOI, subsequent entities drew from different stream positions, causing permanent state divergence. Each entity now gets an independent PRNG seeded from(tick, entityId)viarngFor, making random output invariant to which other entities are present. api.patch("player", ...)now validates againstPlayerDefSchema.strict()at the API boundary (#6526). ObjectProperties keys passed at the PlayerDef root (e.g.model,feetPosition,physics) used to merge silently and never render — the patch landed in the spec but the engine never read them. Invalid patches now early-return and emit amutationWarnto Savi'srun_scriptlog naming the offending keys with a "nest underproperties" hint. Shares the strict-schema precedent used bypatchTerrain; renamed the helperformatTerrainValidationIssues→formatZodIssues(4 callers updated).
Renderer
- Packed
PointLightDataNode,SpotLightDataNode, andDirectionalLightDataNodeinto single stride-NuniformArray("vec4")bindings shared by JS writer and TSL reader through a typedSTRIDE/SLOTconstant (#6525) — writer/reader drift is no longer expressible. Fragment UBO bindings for batched dynamic lighting: point 3 → 1, spot 4 → 1, directional 2 → 1. Per-point-light memory 48B → 32B. Lighting math,countNodeLoop trip count, andDynamicLightsNode.customCacheKey()are untouched — no material recompiles when light counts change. Dropped// @ts-nocheckfrom all fourDynamicLighting/data/*nodes; they now type-check under strict TS via narrowedbuilder.contextaccess and a smallBatchedLightSentinelNode subclass for thelightNodeslot ofLightingModelDirectInput.