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.4.0

Solid

May 15, 2026

Things stack right, worlds load clean, and clicks land where you point.

what's new

  • Stacking just works. A mug on a chair on a house keeps its size. Ask Savi to put something on top of something else and she'll land it first try — huge for any game where things sit on shelves, stack into towers, or get carried around.
  • Worlds load clean. Things appear at the right size right away. No more pop-in where stuff suddenly grows or shrinks as the level comes in.
  • Clicks land where you point. The sky doesn't steal clicks anymore, and big crowds of repeating things (trees, rocks, props) can be clicked one by one. Shooters, click-to-place, and pick-up-anything games all feel sharper.
  • Effects stick to surfaces the right way. Bullet holes, splats, dust puffs and decals face the wall they hit instead of floating sideways.
  • Camera stays still when you open the menu — no more drift while you're trying to read.
  • Heads up: a few old scenes might look a little different where you'd attached one thing to another. Take a peek and tweak if anything looks off.
›technical notes

Scene graph + layout (breaking)

  • BREAKING: scene graph rewrite (#6480). Replaces Scale + LayoutScale + RenderScale + LayoutScaleFeature + RenderScaleFeature + the multi-stage hierarchy composition with three explicit components, each with one job:
    • LocalScale / LocalRotation / LocalFeetPosition / LocalPivot — authored inputs.
    • WorldScale / WorldRotation / WorldFeetPosition — solved hierarchy outputs (World* = parent.World* ⊗ Local*). Pure tree math, no asset awareness.
    • GeometryScale — per-asset fit factor derived from (DrawModel.rawBounds, TomeLayout). Computed for every entity, leaf-only at consumption, never inherited.
  • BREAKING: fixed layout cascade bug — a root's layout.maxExtents no longer shrinks every descendant. Renderer / physics now read WorldScale × GeometryScale per entity instead of a composed RenderScale that mixed asset-fit into the hierarchy. Regression test: tome/__tests__/mug-on-chair-on-house.test.ts.
  • BREAKING: fixed parented-clamp drop bug — a parented entity's own layout.maxExtents now applies. GeometryScale is written regardless of parenthood. Regression test: tome/__tests__/parented-layout-clamp.test.ts.
  • BREAKING: maxExtents is a ceiling (factor = min(1, maxExtents / rawBounds)), minExtents is a floor (factor = max(1, minExtents / rawBounds)), minExtents === maxExtents = exact fit.
  • Added api.getWorldBoundsBox(id): { min, max, size, center } | null. Backed by tome/api/world-bounds.ts; WorldBoundsBox type surfaces in Savi's prompt via the shared-schemas section.
  • Renderer reads WorldScale × GeometryScale uniformly. Model matrix = T(WorldFeetPosition) × R(WorldRotation) × S(WorldScale × GeometryScale). Feet alignment uses the combined factor.
  • Rapier reads WorldScale × GeometryScale so collider geometry matches visible geometry on every entity. Pose stays in pure graph-transform space; feet-to-body-center offset = (rawHeight/2) × WorldScale × GeometryScale.

Asset bounds prefetch

  • New server-side BoundsPrefetchFeature (#6490) discovers entities with DrawModel, submits Range-fetch jobs that parse GLB headers (~64KB per model) off the main thread, and writes results to GameSpecResource.assets.metadata. Supports KHR_mesh_quantization dequantization.
  • Bounds persist to Supabase via patchAssets, so subsequent room starts find them cached and avoid the refetch.
  • Hooks-driven, not per-tick (#6491): onComponentAdd/onComponentSet(DrawModel) + a one-time seed of existing entities. Steady-state cost is zero. Catches applySpec, api.spawnObject, and runtime setProperty("model", …) swaps without scanning script source.
  • Removed the now-unused collectModelUrls / MODEL_URL_INLINE_RE spec walker.

Raycast

  • Raycast results now include per-hit surface normals (packed + decoded) for both CPU and GPU paths (#6504).
  • CPU/GPU raycast behavior aligned for first-hit queries; both use snapped ray directions so results are consistent across paths.
  • Sky meshes are excluded from raycasts — click queries no longer hit the skybox before the world.
  • Raycast reset and distance handling hardened against invalid (NaN/Infinity) values.
  • Fixes raycast against IndirectBatchedMesh in the compute raycast path.

Input

  • handleMouseMove in engine/input/raw-capture.ts now gates on getInputMode() === "overlay" like the other input handlers (#6501). Pointer-lock mouse deltas no longer rotate the camera under an open overlay.

Skills (Savi-facing)

  • Removed six phantom API method names from the engine skill files (#6500). All were taught to Savi but didn't exist on ObjectAPI / TomeCameraAPI, causing tool-call failures or silent [Tome] patchTerrain() ignored… rejections:
    • world-composition.md: api.setSpec("...decorations") → api.patch("terrain", { decorations: … })
    • voxel-terrain.md: api.patchTerrain({…}) → api.patch("terrain", { addMaterials, addMarks, … })
    • voxel-terrain.md: objectApi.getPointerRay(input) → objectApi.getInputRay(input)
    • pointer-raycasting.md: getPointerRay / getAimOrigin → getInputRay / getProperty("feetPosition") + offset
    • pointer-raycasting.md: objectApi.raycastPhysics(…) → objectApi.raycast(…)
    • 3d-billboard-sprites.md: objectApi.getPosition() → objectApi.getProperty("feetPosition")
    • turrets.md: objectApi.damage(id, n) → getObjectState + patchObjectState({ health: … - n })