Spawn
spawn / swhat we're building

pinned

start herewhat spawn isfaqfrequently asked questionsthe betthe spawn bet

updates

engine v5.0For Real1 weekengine v4.6Atelier1 weekengine v4.5Surface Tension2 weeksengine v4.4Solid3 weeksengine v4.3GroovyMay 13, 2026engine v4.2ContinuumMay 9, 2026engine v4.1FoundationsMay 4, 2026engine v0.1GenesisApril 29, 2026

pinned

what spawn isstart herefrequently asked questionsfaqthe spawn betthe bet

updates

For Realengine v5.01 weekAtelierengine v4.61 weekSurface Tensionengine v4.52 weeksSolidengine v4.43 weeksGroovyengine v4.3May 13, 2026Continuumengine v4.2May 9, 2026Foundationsengine v4.1May 4, 2026Genesisengine v0.1April 29, 2026
← All posts
← All posts

engine v5.0.13

Engine v5.0.13

June 12, 2026

A patch in the For Real line.

what's new

  • Boardwalks built down slopes with tight switchback corners now render coherently — no more railing bars shooting past hairpin turns or flickering planks on long straight runs.
  • Savi editing scripts while you play no longer makes the world's grass and decorations flash and hitch — the scatter only rebuilds when you actually change the decorations.
  • Calling an ObjectAPI method that doesn't exist — whether it was removed in an engine update (like api.patch, removed in 4.5.2) or never existed at all (like api.removeObject) — now tells you the real method to use instead of a bare "is not a function".
  • When a script calls an old removed API name, the error now tells you the new name to use instead of a bare "is not a function".
  • The breeze is back! Grass, flowers, and reeds with wind on them sway again — bases planted, tips swirling in the wind you authored.
  • Screenshots no longer hitch the game: when Savi peeks at your world, a thumbnail gets captured, or you snap a shot for chat, the frame used to stall for ~8 frames mid-gameplay. Captures are now visually identical and nearly free.
  • Platformer fix: characters now fall off platform edges cleanly instead of stuttering up and down at the lip. Walking down slopes and stairs still feels glued, exactly like before.
  • A spotlight given a too-big cone angle (like an angle in degrees) used to go completely invisible with no error. Now it lights up at its widest cone instead, and Savi gets a log telling her exactly how to convert the value to radians.
  • Games with rich terrain no longer gray-screen on GPUs with strict texture limits — the engine now simplifies terrain shading on those machines instead of failing to draw the world.
  • Placing objects on the room grid now works in every spawn shape the docs show: spawn({ properties: { feetPosition: { tile: [x, y] } } }) (with optional offset) lands on the tile like the 3d-rooms guide says, including inline children. Mistyped tile positions get precise messages instead of a generic rejection, and using a tile position in a world without rooms terrain tells you why the object landed at the origin.
  • Walking through translucent things — light shafts, ghosts, holograms, glass effects — no longer slices the screen with hard edges. They now dissolve gracefully as you get close, exactly like you'd expect.
›technical notes
  • Spline corner joins now apply a miter limit (SPLINE_CORNER_MITER_LIMIT_RATIO = 2): the exact offset-line intersection is kept for turns up to ~127°, sharper corners fall back to a bevel. Pre-fix, hairpin corners extended laterally-offset bars (boardwalk/conveyor rails, corner fill polygons, stair corner landings) by offset * tan(turn/2) — unbounded, e.g. ~2.6m floating rail spears on a switchback boardwalk.
  • Hard-corner boardwalk segments no longer emit duplicate plank/rail-post/pile boxes at collinear subdivision boundaries (exact coincident twins that z-fought).
  • Fixed the live-edit decoration flash (dump 97f8b3c0): setTerrainDecorationConfig now content-guards at the renderer boundary — a re-emitted terrain/decorations op whose config is structurally unchanged keeps its revision instead of bumping it, so the scatter rebuild guard in syncTerrainDecorations stays satisfied and the full teardown (clearEntries + synchronous material compile) only runs on real config changes. The compare is a structural walk over the modest JSON-shaped config (key-order-insensitive, undefined-valued keys count as absent), run once per decorations op, never per frame. Real changes — layer add/remove, item edits, budget/settings changes — rebuild byte-identically to before.
  • Classified tome/resim-telemetry-emitter as live-channel in resource-dispositions.ts. The resource is new in 5.0.12 (fleet resim telemetry) and was hitting the exec snapshot builder's plain-data gate ~30x/hr in prod ("clone disposition but failed the plain-data gate (function value)") — the same post-audit class as tome/scratch (#7143): it landed after the #7057 declared-type sweep, and the value is a room-runtime logger closure (the spec-push-notifier pattern). Behavior is unchanged — the gate was already shipping it as absent, and absent matches the resource's own contract: it's installed by the server room runtime, absent on clients and singleplayer glue, and resimTelemetrySystem probes-and-degrades (no emitter → bail before touching state). The live realm reinstalls it at room init; nothing about it can or should ride a snapshot back.
  • Sibling sweep of every resource registered between the 5.0.11 and 5.0.12 mints: tome/runtime-cursor-override (boolean), tome/sound-durations / tome/sound-duration-reoffers / tome/pending-sound-ends (plain Maps/arrays of scalars) are all plain data and correctly default to clone; no other function-valued resource is unclassified. The dispositions test's audit list gains the emitter, and a snapshot round-trip test pins no-warning + absent + system-no-op-after-restore with the closure-bearing emitter installed.
  • Classified tome/scratch as re-derive in resource-dispositions.ts. It was the remaining resource hitting the exec snapshot builder's plain-data gate in prod ("clone disposition but failed the plain-data gate (function value)" — the #7057 sweep audited by declared type, and TomeScratchState declares plain Maps/Records, but arena values are script-owned and games stash closures in them at runtime). Behavior is unchanged — the gate was already shipping it as absent, and absent matches scratch's own contract: arenas are realm-local working state, ensureScratchState lazily re-creates empty state on first worker access (scratch.ts documents lazy init as its restore path), and scratch writes are definitionally non-transactional so they never ride the merge log back. This makes absent-by-design explicit and silences the per-dispatch error log.
  • Prod log audit of the gate-failure pattern (7 days): only tome/quality-landing-reset-broadcaster (fixed in #7057, noise continues from pinned pre-#7057 engine versions) and tome/scratch (this change). The dispositions test's audit list gains tome/scratch, and a snapshot round-trip test pins the no-warning + absent + worker-side-fresh-arena behavior.
  • The removed-API tombstones (src/tome/api/removed-api-tombstones.ts) now also cover the top dead ObjectAPI names from prod failure mining (~1,100 calls/week T7d): patch (×443), patchObject (×173), removeObject (×156), patchPlace (×88), despawn (×86), setSpec (×76), updateObject (×73), patchSpec (×64), readScript (×15), readFile (×3). Calling one now throws a TypeError that leads with the name and gives the exact replacement call shape (e.g. api.removeObject was never an ObjectAPI method — use api.destroy(id); the setSpec/patchSpec family teaches the per-slice patch verbs; readScript/readFile teach getScript(path)/listScripts()). All but one are PHANTOMS — methods that never existed — and their messages say "was never an ObjectAPI method", not "was removed": the message is the teaching, so it doesn't lie about history. The exception is api.patch, which is real history: it shipped as the generic dot-path dispatcher in engines 4.4.0–4.5.1 (#6417) and was un-merged into the discrete patchX verbs in 4.5.2 (#6568) — prod apps pinned to those engines still run it today. Its message says "removed in 4.5.2" and teaches per intent: own state → patchState; other objects → setObjectProperty/batchSetObjectProperties/patchObjectState; spec slices → the per-slice patch verbs; dot-path spec writes → the matching slice verb. Same mechanics as the original tombstones: non-enumerable call-time throwers installed once per world prototype, no Proxy, zero new code on live call paths; every phantom was verified absent from the current api surface (the once-removed now-live patch* slice methods stay live and unshadowed, guarded at world construction), and a new test asserts every verb a tombstone message teaches is itself a live method — a tombstone can never teach another phantom. Also fixed: updatePlace()'s non-keyed-map "objects" warning taught spawnObject/removeObject/updateObject (two of which are phantoms tombstoned here); it now teaches spawn()/destroy()/setObjectProperty().
  • Calling a removed ObjectAPI name (raycastPhysics, raycastPhysicsAll, raycastPhysicsDown, getAimDirection, getPointerDirection, getPointerRay, getAimOrigin, directionFromYawPitch, rotationFromDirection, destroyObject, patchEphemeralState, setEphemeralState, replaceEphemeralState, defaultState) now throws a TypeError carrying the migration path from the consolidation changelog (e.g. api.getPointerRay was removed — the cursor ray is api.getInputRay(input), including the maxDistance → distance / ignoreIds → ignoreEntities option renames for the raycast trio) instead of a bare "is not a function". Tombstones are non-enumerable call-time throwers installed once per world prototype (src/tome/api/removed-api-tombstones.ts) — no Proxy, no compat alias, zero new code on live method call paths. Both behavior hooks and run_script (including read-only) are covered; the enriched message flows through the existing behavior-fault DM unchanged. A construction-time guard throws if a tombstoned name ever returns as a live method.
  • Resimulation stats: the expired-sample sweep (prune) on the record paths is amortized to once per second instead of running on every record/recordPushDelivery/recordBaselineAdopt/recordResimDeferral call (3.3% of sim busy during correction storms). Snapshot output is byte-identical — retention (2s) exceeds the reporting window (1s) and snapshot() still prunes unconditionally before reading; pinned by test.
  • Restored terrain scatter wind displacement lost in the renderer deslop (#6517): wind: { force, scale, speed } on grass/sprite decoration items sways the cards again. The GPU-scatter card material now installs an mx_noise_vec3 displacement (instance-world-position seeded, time-scrolled, masked by normalized card height so the base stays pinned) whenever a usable force is authored — windless items compile the exact same node chain as before, zero added vertex cost. Old defaults preserved: scale 0.8, speed 1.0, force 0 (wind without a force does not move). Displacement is branchless WGSL (select()-lowering hazard) and pinned by a dominance-analyzer suite.
  • Viewport screenshot capture (captureRendererScreenshot) downscales BEFORE encoding instead of after: the frame's ImageBitmap is drawn onto an OffscreenCanvas capped at 1280px on the long edge and encoded as JPEG q0.92, replacing the synchronous full-resolution PNG encode that ran on the render worker (trace-proven at ~137ms / ~8 dropped frames per capture on a retina viewport, fired by every chat/Savi/SEO screenshot). Every consumer already downscaled to ≤1280px JPEG on the main thread — chat 400px q0.5, scene views 768px q0.7, SEO/savi-note thumbnails 1280px q0.92 — so final outputs are unchanged while the hot-thread encode runs on ~10-20x fewer pixels with a cheaper codec (and the consumers' own decode gets equally cheaper). The sizing/format decision is a pure exported function (resolveScreenshotEncode); the 2D/viewport scene-view path reports the encoded dimensions.
  • Character controllers (rapier + mantle) no longer jitter at platform edges. Walking off an edge, snap-to-ground (a full-capsule shapecast down) re-caught the platform lip while only the capsule rim still overlapped it: grounded=true zeroed the motor's accumulated fall velocity, one gravity quantum followed, and the snap caught again — a self-sustaining loop (every catch re-armed the snap grace window) that held the character hovering at the lip in 1-frame gravity quanta instead of falling. Both controllers now require support under the capsule AXIS (an inner-radius down-probe within snap reach) before a snap commits or a contact classifies as ground; a rim-only lip catch reads NOT grounded and never re-arms the snap grace. Slope descent, stair descent, step-downs within snapToGroundDistance, and flat-ground walking are unchanged (the probe hits the surface that continues under the axis) and pinned by parity tests in both engines, 3d and 2d-side. No new cross-tick state: the probe is a pure function of position + world geometry, so prediction/resim determinism is untouched.
  • Standing astride a seam or gap (two platforms, tops level, character axis over the split) stays grounded. The axis probe alone demoted that stance to airborne forever — jump denied once coyote drained, air-pose while standing, NPC isGrounded deadlock, ride-velocity cut on seamed moving platforms, and unbounded phantom fall-velocity accumulation. The rapier probe now has lateral width (offset rays at mantle's thin-capsule ratio, so centimeter seams between abutting blocks can't be threaded) plus a straddle probe (rim-height support on BOTH sides of the footprint counts; one-sided rim support still demotes, so the lip fix is intact). Mantle falls through to its wedge test when ≥2 opposing bottom contacts exist — opposing rim contacts that block descent ARE support — with the down-probe run in the support's reference frame so descending platforms keep their riders.
  • Spot light angle is now clamped to THREE's defined cone domain (0, π/2] at the engine's single THREE.SpotLight.angle write (setSpotLight). Above π/2 the cos-based smoothstep cone edges invert and BOTH lighting paths (clustered packer + legacy dynamic) render exactly zero light inside the cone — the prod invisible-flashlight class, where a degrees-shaped angle: 26 silently killed a creator's spotlight across 9 spec versions. Degrees-shaped values (> π/2) additionally report once per light on the engine diagnostic rail (light-spot-angle-clamped, getLogs + one-time DM) with the copy-pasteable radians fix; non-positive angles clamp to the 0.0001 epsilon floor (THREE's smoothstep degenerates at exactly 0) without the degrees message. Authored spec values stay untouched — the clamp is render-side only. The api-reference skill now states radians explicitly on the LightSpec line, the spotLight builtin, and the spot example.
  • Gray-screen class fix (partner report, game "Higher"): a terrain pool material whose pipeline the preflight resource guard rejects (granted maxSampledTexturesPerShaderStage exceeded — the WebGPU spec minimum is 16) no longer aborts every frame forever. The renderer walks a texture-budget ladder (degradeTerrainTextureBudget): rung 1 rebuilds every pool on the simplified lit variant (LOD>=2 resolve, PBR/NRO compiled out — 3 fragment-stage bindings), rung 2 on the unlit floor (material.lights = false — no cluster/shadow/IBL taps at all). Scene-level rung; pools created later inherit it. Each rung reports through the engine diagnostic rail.
  • Non-terrain materials that exceed pipeline resource limits are now hidden (object.visible = false) instead of re-throwing per frame, so one over-budget material can no longer blank the whole scene.
  • New suite pins the terrain material's per-stage sampled-texture count by compiling the real WGSL: a maximal library (PBR + NRO + noise + gradient + height tint + emissive) binds exactly 5 fragment-stage textures, and the worst-case lit stack stays ≤ 16 on every tier.
  • spawn()'s feetPosition validator now admits the { tile: [x, y], offset?: [x, y, z] } position shape that resolveRoomsTilePosition already resolves and the 3d-rooms skill teaches — previously the gate layer rejected the shape while the writer layer fully supported it, failing whole spawn batches in room worlds. Tile coords must be two integers (the ASCII-grid indices the resolver looks up; fractional coords would silently skip the floor-height lookup), offset must be [x, y, z] finite numbers, and stray keys reject by name. Covers every spawn shape: direct spawn(), inline children recursion, and setProperty("feetPosition", ...).
  • Tile positions in a place WITHOUT 3d-rooms terrain still resolve to the origin (the fallback is load-bearing — interpreter phases must never see a throw), but no longer silently: a teaching warn lands in the runtime log, once per place per world.
  • spawnFx and damageNumber — the two position entry points where tile positions genuinely can't resolve (they take raw world points) — now say so explicitly when handed a { tile } shape, instead of a generic shape rejection.
  • Mesh materials in the ghost/shaft class (transparent && depthWrite:false, depth-tested) now compile a camera-proximity alpha fade: fragment alpha ramps to 0 by 0.5m from the camera and is EXACTLY 1 at/beyond 1.5m (WGSL smoothstep — far-field byte-identical). Fixes the near-plane degenerate case for creator-built god-ray shells / ghost meshes: the shell wall no longer sweeps the screen as a hard-edged clipped polygon when the camera enters it, and stacked-shell brightness no longer pops stepwise wall-by-wall (each wall now dissolves through the ramp). Applied on both construction paths: standalone mesh node materials (createMeshNodeMaterial/...FromSource, re-settled after GLTF source flag copy) and the transparent non-overlay primitive batch lanes. Opaque, depthWrite:true, depthTest:false (overlay), and appearance-owning (water/shockwave/slash/scripted) materials keep node graphs untouched. Fade is alpha-only (no emissive hue shift), branchless TSL (no select()/toVar — r0.184 lowering hazard), and dominance-analyzer pinned.
  • Scene-depth ("soft particle") intersection fade deliberately not built: the only scene depth reachable from a scene-pass material is viewportDepthTexture, whose mid-pass framebuffer grab forces the MSAA store every frame such a material is alive (viewport-share, ledger #253/#332) — a standing perf tax on every world containing a ghost mesh. Hard floor/prop intersection seams at a distance are unchanged.