Spawn
playmakespawnjam
⌘K
visitorclaim namesign in
sign up
spawn / aboutwhat we're building

pinned

start herewhat spawn isfaqfrequently asked questionsthe betthe spawn bet

updates

engine v5.2LumeJul 10, 2026engine v5.1ConnectionJun 18, 2026engine v5.0For RealJun 4, 2026engine v4.6AtelierJun 1, 2026engine v4.5Surface TensionMay 22, 2026engine v4.4SolidMay 15, 2026engine v4.3GroovyMay 13, 2026engine v4.2ContinuumMay 9, 2026engine v4.1FoundationsMay 4, 2026engine v0.1GenesisApr 29, 2026

pinned

what spawn isstart herefrequently asked questionsfaqthe spawn betthe bet

updates

Lumeengine v5.22w agoConnectionengine v5.11mo agoFor Realengine v5.01mo agoAtelierengine v4.62mo agoSurface Tensionengine v4.52mo agoSolidengine v4.42mo agoGroovyengine v4.32mo agoContinuumengine v4.22mo agoFoundationsengine v4.12mo agoGenesisengine v0.13mo ago
← All posts
← All posts

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 state field 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 updateChannel from 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.raycast excludes 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 redundant path property is gone.
  • Eight pointer/aim/raycast methods replaced by two clearer ones.
›technical notes
  • REMOVED: DrawAnimation component (draw/animation). Animation state flows through draw/mixer only. Legacy model: { id, animation } is converted to a _default mixer channel in derive-appearance.syncAnimationForModel.
  • REMOVED: Animated3DCharacterFeature's client-side DrawModel deriver. The server now writes DrawModel alongside DrawAnimated3DCharacter so 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 knows draw/animated-3d-character exists; it consumes mixer channels only.
  • REMOVED: "draw/animated-3d-character" from RENDER_COMPONENTS — the component no longer crosses the render channel.
  • ADDED: Animated3DCharacterLocomotionFeature (server+client) — back-compat shim that translates DrawAnimated3DCharacter config + 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-animations skill — 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: _default auto 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: writeDrawAnimated3DCharacter no longer writes DrawModel as a side effect; the interpreter and setProperty callers compose writeDrawAnimated3DCharacter + writeDrawModel explicitly.
  • BREAKING: Removed built-in auto-persistence system (engine.persistence config, player/room/singleplayer save systems)
  • BREAKING: Removed player.onDisconnect BehaviorRef — replaced by onPlayerDisconnected lifecycle hook
  • BREAKING: Removed patchEphemeralState(), replaceEphemeralState(), setEphemeralState() from ObjectAPI — ephemeral state unified into TomeState
  • BREAKING: Removed onDisconnect from 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's state field; use patchState() in onSpawn for 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 skipOnSpawn parameter through attachClient chain — entity creation split from onSpawn for persistence
  • Added connection.rejected control message with client-side error overlay
  • Added graceful shutdown: worker-thread dispose awaits completion, disconnect hooks run for all connected players
  • Added specApplied gate — connections queue until first spec is applied
  • Lifecycle scripts compiled once per ref (module-level state shared across hooks)
  • objectApi/cameraApi parameter naming standardized across all skills
  • Added persistTerrainEdits?: boolean to PlaceCreateOptions — 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) and api.patch("places.<id>.terrain", v) replace the old second-arg form.
  • patchState, patchEphemeralState, and patchObjectState are unchanged — they target entity state, a different concept.
  • Internal recorded mutation kind tags (patchAtmosphere, patchTerrain, etc.) are unchanged so persistence/replay/serialization stay stable.
  • BREAKING (public API): path removed from ObjectProperties type. Runtime handler retained for backward compatibility — existing games' specs still parse and render.
  • The string-keyed setProperty("path", ...) overload still accepts path at runtime via the legacy registry entry. The typed setProperty<K extends WritableProperty> overload no longer admits "path" (use "spline").
  • PathSpec is no longer @tomeapi-tagged and no longer appears in the generated Tome API prompt.
  • kind: "pipe" in SplineSpec maps to the same tube primitive used by the legacy path handler, 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. Default ignoreSelf: true. Supports shape: { sphere: number } and multiple: 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).
  • directionFromYawPitch and rotationFromDirection available via require('builtin/vec3').
  • getAimDirection(input?) and getPointerDirection(input?) now accept omitting the input argument — falls back to the camera state resolved by getCamera() so they work in update/onCollide/etc., not just onInput. Eliminates the cryptic undefined.axes crash 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 ECS Rotation. Under pointer-lock the script-side rotation drifts from on-screen orientation because it integrates lookX/lookY without the renderer's MOUSE_SCALE, so click-to-throw / hit-detection rays were diverging from the crosshair. Fixed in resolveViewState.
  • 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.) via getBespokeGeometryBySignature. Fixes invisible 1m capsules under tiny lathe milk-bottles that overlapped and exploded in stacks.
  • Sign text: when an object has explicit rotation (or yaw), text.billboard defaults 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 poleHeight to 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 poleHeight to opt back into the freestanding pole+wire layout.
  • Heightmap terrain.materials now coerces a Record<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/beziers doc: 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.ts exercises every primitive kind × every collider override (81 cases) and asserts the result is sized — catches future primitives that ship without a deriveColliderFromBounds entry.