engine v4.2.0
Continuum
May 9, 2026
Saved games, smoother animation, sharper aim, and a basket of polish fixes you'll feel right away.
what's new
Saved games, smoother animation, sharper aim, and a basket of polish fixes you'll feel right away.
Save your games
- Player progress persists across sessions — position, inventory, and stats all come back when players return.
- World state persists too: tag objects to save, set up cron jobs to auto-save on a timer, and graceful shutdowns save everyone before the server goes down.
- Multi-place persistence — walk through a portal, disconnect, and come back in the same place with your stuff.
- Static initial values on an object's
statefield are applied automatically. Add new fields later and returning players get the defaults without losing their saves. - One unified state system — no more confusing split between "persistent" and "ephemeral" state.
Animation, reborn
- One animation system now: named mixer channels driven by
updateChannelfrom your scripts. - Two-clip blending (Walk over Idle, Cast over Run) is first-class — give channels names and they layer cleanly.
Smarter pointer & raycasts
- The "I shot myself" bug is gone —
api.raycastexcludes the caller by default. - Sphere casts now work for aim assist and area effects:
{ shape: { sphere: radius } }. - Click-to-throw, click-to-shoot, and other click actions fire exactly where you're looking. No more first-person balls flying off behind you.
- The first click that locks the cursor no longer wastes your first action.
Bug fixes you'll feel
- Signs Savi makes no longer spin their text to face you and clip through the panel.
- Strands of fairy lights and bunting now actually look like strands — a thin cord drooping through your points with cute bulbs or flags hung from it, no random poles in the scene.
- Stacks of bottles, mugs, and other small lathe-shaped objects don't explode into orbit anymore. They just sit on the table.
Behind the scenes
Existing games migrate to all of these automatically when you upgrade:
- One
api.patch()method replaces eight separate per-slice patches. - One way to make tubes, roads, pipes, and fences:
spline. The redundantpathproperty is gone. - Eight pointer/aim/raycast methods replaced by two clearer ones.
›technical notes
- REMOVED:
DrawAnimationcomponent (draw/animation). Animation state flows throughdraw/mixeronly. Legacymodel: { id, animation }is converted to a_defaultmixer channel inderive-appearance.syncAnimationForModel. - REMOVED:
Animated3DCharacterFeature's client-side DrawModel deriver. The server now writes DrawModel alongsideDrawAnimated3DCharacterso it replicates normally — no per-client derivation, no prediction mismatches. - REMOVED from renderer (
render_v2/state/model.ts):tickLocomotion,computeLocomotionVelocity,AnimConfig,narrowAnimated3DCharacter,record.animConfig, the"loco"LayerOwner, and the locomotion constants. The renderer no longer knowsdraw/animated-3d-characterexists; it consumes mixer channels only. - REMOVED:
"draw/animated-3d-character"fromRENDER_COMPONENTS— the component no longer crosses the render channel. - ADDED:
Animated3DCharacterLocomotionFeature(server+client) — back-compat shim that translatesDrawAnimated3DCharacterconfig + entity velocity into mixer channels and handles facing rotation. The only file in the engine that knows a3dc exists; deleting it removes a3dc support entirely. - ADDED:
3d-animationsskill — covers mixer channel API, locomotion recipe, one-shots, bone masking. - ADDED: Fixed humanoid capsule for character controllers without an explicit collider, replacing the model-derived convex hull that produced unstable collisions on terrain.
- ADDED:
_defaultauto mixer channel for skinned models that have no explicit animation/mixer, so loaded models don't sit in bind pose. - DEPRECATED:
DrawAnimated3DCharacter/animated3DCharacter. Hidden from Savi's prompt. Runtime continues to accept it via the compat shim. - CHANGED:
writeDrawAnimated3DCharacterno longer writesDrawModelas a side effect; the interpreter andsetPropertycallers composewriteDrawAnimated3DCharacter+writeDrawModelexplicitly. - BREAKING: Removed built-in auto-persistence system (
engine.persistenceconfig, player/room/singleplayer save systems) - BREAKING: Removed
player.onDisconnectBehaviorRef — replaced byonPlayerDisconnectedlifecycle hook - BREAKING: Removed
patchEphemeralState(),replaceEphemeralState(),setEphemeralState()from ObjectAPI — ephemeral state unified into TomeState - BREAKING: Removed
onDisconnectfrom compiled behavior hooks - Added
engine.behaviors: BehaviorRef— lifecycle hooks as named exports (same composition pattern as entity behaviors) - Added
engine.crons: { schedule, script }[]— scheduled jobs with config-side scheduling - Added lifecycle hooks:
onPlaceStart,onPlaceShutdown,onPlayerConnected,onPlayerDisconnected - Removed
objectApi.defaultState()— static defaults belong on the spec'sstatefield; usepatchState()inonSpawnfor computed values - Added
objectApi.awaitJob(jobId)— Promise-based job result for async lifecycle/cron hooks - Added
objectApi.getPlaces()— returns all spec + instanced place IDs - Added
LifecycleContextResource— built-in storage jobs execute immediately via SDK in lifecycle context - Added async detection in entity behavior compilation —
async function onSpawn/update/onInput/...rejected at compile time - Added
skipOnSpawnparameter through attachClient chain — entity creation split from onSpawn for persistence - Added
connection.rejectedcontrol message with client-side error overlay - Added graceful shutdown: worker-thread dispose awaits completion, disconnect hooks run for all connected players
- Added
specAppliedgate — connections queue until first spec is applied - Lifecycle scripts compiled once per ref (module-level state shared across hooks)
objectApi/cameraApiparameter naming standardized across all skills- Added
persistTerrainEdits?: booleantoPlaceCreateOptions— opt-in voxel terrain edit storage - Stripped non-voxel persistence from place-persistence system (object spawn/destroy/state tracking removed — use lifecycle hooks instead)
- BREAKING: Removed eight per-slice patch methods (
patchAtmosphere,patchTerrain,patchPlayer,patchCamera,patchInputs,patchGodMode,patchUi,patchEngine) from ObjectAPI. - Added
api.patch(path: string, value: Record<string, unknown>)— single method that dispatches to the right spec slice based on dot-path. Existing per-slice validation preserved internally. - Per-place targeting now uses path syntax:
api.patch("places.<id>.atmosphere", v)andapi.patch("places.<id>.terrain", v)replace the old second-arg form. patchState,patchEphemeralState, andpatchObjectStateare unchanged — they target entity state, a different concept.- Internal recorded mutation
kindtags (patchAtmosphere,patchTerrain, etc.) are unchanged so persistence/replay/serialization stay stable. - BREAKING (public API):
pathremoved fromObjectPropertiestype. Runtime handler retained for backward compatibility — existing games' specs still parse and render. - The string-keyed
setProperty("path", ...)overload still acceptspathat runtime via the legacy registry entry. The typedsetProperty<K extends WritableProperty>overload no longer admits"path"(use"spline"). PathSpecis no longer@tomeapi-tagged and no longer appears in the generated Tome API prompt.kind: "pipe"inSplineSpecmaps to the same tube primitive used by the legacypathhandler, so visual output is identical for the common point-array tube case.- BREAKING: Removed from ObjectAPI:
raycastPhysics,raycastPhysicsAll,raycastPhysicsDown,getAimDirection,getPointerDirection,getPointerRay,getAimOrigin,directionFromYawPitch,rotationFromDirection. - Added:
api.raycast(origin, direction, distance | opts)with positional and options overloads. DefaultignoreSelf: true. Supportsshape: { sphere: number }andmultiple: true. - Added:
api.getInputRay(input?)returning{ origin, direction } | null. Reads from pointer axes (handles both pointer-locked center and cursor modes via existing input-axis dispatch). directionFromYawPitchandrotationFromDirectionavailable viarequire('builtin/vec3').getAimDirection(input?)andgetPointerDirection(input?)now accept omitting the input argument — falls back to the camera state resolved bygetCamera()so they work inupdate/onCollide/etc., not justonInput. Eliminates the crypticundefined.axescrash when scripts called these from non-input hooks.- Pointer-direction ray now uses
rendererTransform.pos/.rot(renderer-authoritative camera SAB) instead of the script-side ECSRotation. Under pointer-lock the script-side rotation drifts from on-screen orientation because it integrateslookX/lookYwithout the renderer'sMOUSE_SCALE, so click-to-throw / hit-detection rays were diverging from the crosshair. Fixed inresolveViewState. - Suppressed the mousedown that acquires pointer lock from also firing as a left-button press. Previously the first click both locked the cursor and triggered whatever action was bound to mouse-left (throw, shoot, place block).
- Collider override sizing:
physics.collider: "box" | "sphere" | "capsule"overrides now derive dimensions from the primitive's actual bounds instead of falling back to the engine's hardcoded 1m defaults. Extended to bespokeMesh primitives (lathe, cone, pyramid, hemisphere, ellipsoid, torus, etc.) viagetBespokeGeometryBySignature. Fixes invisible 1m capsules under tiny lathe milk-bottles that overlapped and exploded in stacks. - Sign text: when an object has explicit
rotation(oryaw),text.billboarddefaults to"none"so labels stay flush to the surface they were placed on instead of billboarding through the panel. - Stringlight default layout is now a poleless cord+primitive-bulbs strand that follows the authored points exactly. Set
poleHeightto opt back into the freestanding pole+wire layout. Bulbs are emissive sphere primitives (cheap), not point lights. - Bannerline default layout is now a poleless cord+pennants strand that follows the authored points exactly. Set
poleHeightto opt back into the freestanding pole+wire layout. - Heightmap
terrain.materialsnow coerces aRecord<id, material>shape to the typed array form instead of crashing the runtime when malformed specs arrive (Savi was occasionally confusing heightmap with voxel material shapes). - Spline
points/beziersdoc: added the frame-rule explanation (offsets from the owner; standalone splines anchor at scene root). Same content threaded into the prompt's spline section. - New audit test
primitive-collider-matrix.test.tsexercises every primitive kind × every collider override (81 cases) and asserts the result is sized — catches future primitives that ship without aderiveColliderFromBoundsentry.