Spawn
playmakespawnjam
⌘K
visitorclaim namesign in
sign up
playmake
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.23w agoConnectionengine v5.11mo agoFor Realengine v5.02mo agoAtelierengine v4.62mo agoSurface Tensionengine v4.52mo agoSolidengine v4.42mo agoGroovyengine v4.32mo agoContinuumengine v4.22mo agoFoundationsengine v4.13mo agoGenesisengine v0.13mo ago
← All posts
← All posts

engine v5.2.15

Engine v5.2.15

August 4, 2026

A patch in the Lume line.

what's new

  • You can now put the game's "ear" anywhere — pin it to a ring-side mic, a security camera, or a moving NPC, and all spatial sound pans and fades from there instead of your camera. One place setting, and clearing it puts the ear right back.
  • Statue false alarms gone: 3D models whose textures already ride our optimized format no longer trigger the false "content may be missing or wrong" warning. (Models with webp-only textures still render untextured by design — re-generating the texture is the road.)
  • Savi can now see your game's HTML UI the way she sees your 3D scene: ask "what's on the screen UI right now?" and she gets the real list — every named element and button with its position, size, and whether it's actually visible — and "what UI is at that spot?" answers with the exact element stack, including an invisible overlay eating your clicks. No more guessing whether your menu rendered.
  • AI characters walk properly. NPCs now truthfully report standing-on-ground and their real speed, so animation logic that checks those no longer traps a walking character in a looping fall pose — and scripts can read an NPC's actual velocity instead of guessing from position changes.
  • Games boot a little lighter — the simulation engine dropped nearly half a megabyte of renderer code it never ran.
  • Terrain textures now tile cleanly by default — the faint ghosted seams that could trace the texture grid are gone. If a big natural surface reads as an obvious repeating stamp from a distance, ask Savi to turn on anti-tiling for that one material (antiTiling: "stochastic") and the repeats decorrelate.
  • Terrain edits during busy builds no longer stutter the room. When terrain changes land in a burst — like Savi reshaping a world while you play — the engine stops rebuilding soon-to-be-replaced ground over and over. A single terrain edit applies just as fast as before.
  • Paintings keep their colour! Putting a soft or near-white color on a textured object no longer turns the art black-and-white — the texture shows its own palette. Strong colored tints still repaint the hue like always.
  • Landing on a platform's corner no longer locks your jump — corners that hold your weight count as ground.
  • Editing a script no longer counts its creations as casualties — death effects only fire for real deaths.
›technical notes
  • PlaceDef.listenerEntity — the spatial-audio ear is now a spec property. The Web Audio listener pose derives from the named entity's live transform (position + rotation) instead of the camera; absent = today's defaults exactly (camera; 2D modes: the controlled character on the play plane). Set/clear live through updatePlace(placeId, { listenerEntity: "ring" | null }) — no new verbs. A dangling id falls back to the default ear rather than going silent; 2D places keep their fixed mode-derived stereo bases (sprite facing flips still can't invert left/right), with the bound entity supplying position; instance places resolve template-authored ids through the same <placeId>:<bareId> rename their objects get. Resolution lives in audio/listener-follow (client), pinned by listener-follow.test.ts.
  • GLB parser accepts the EXT_texture_webp DECLARATION — and deliberately never resolves its source (#11992, ledger 1969; resolution half trimmed same-day per jure's ruling, thread 1785781497: "we don't want non-ktx2 textures going into the engine, ever"). The extension joins SUPPORTED_EXTENSIONS, so the required extension "EXT_texture_webp" is not supported — content may be missing or wrong warning class dies: ~40 per dump on moodboard GLBs whose declaration is stale cook-era metadata while every texture actually rides KTX2 (two savi-filed engine-bug incidents were this false alarm). parseTextures does NOT read extensions.EXT_texture_webp.source: a webp-ONLY texture (extension reference with no top-level source) stays unresolved — the model renders untextured, and the honest texture[N]: no image source warning stands — until the asset is recooked to KTX2 (the ledger-1198 recook lane, recook-webp-avatars.ts). Textures riding KHR_texture_basisu or a top-level source resolve exactly as before.
  • Identify UI-plane mode — the scene-identify verb's named sibling (r-3934ca4b). identify_object gains ui: true: the same read-only query-primitive bar aimed at the AUTHORED UI plane (the creator's HUD in its sandboxed realm iframe — the plane host-page reads and 3D lanes structurally cannot see; ravi's badgerblunts receipts cec6b199 §3 class B′). Alone it enumerates the rendered census — elements that are addressable (id), take input (the pointer-events law's INTERACTIVE_SELECTORS, the exact grant vocabulary), are world-anchored, or are top-level overlay roots — and with screen: [x, y] (0..1 viewport fractions, the identify family's convention) it answers "what UI is at this point", latest-in-document (painted-on-top) first, style-hidden click-catchers included and marked. Each match: id + kind (tag) + screen-space rect (CSS px + the frame viewport) + visibility + interactive + a short label. Resolution rides the realm's own rails end to end — two new read-family query verbs (elements | hit, strictly decoded like the rest of the closed vocabulary) executed by the frame's own read executor against the tree it renders; never a DOM-scrape parallel, and platform chrome (Spawn's rail) lives outside the realm, invisible here by design. An empty plane is an HONEST EMPTY: ok with matchCount 0 plus a note derived from the HUD-pipeline receipts naming whether nothing was ever rendered or the render just emptied. Zero new spec words, zero ObjectAPI surface — an inspection verb, not an authoring one.
  • Platform-side (kiln magic-cdn — rides platform deploys, not this kernel build): the write-once refusal metric learns its outcome (#11991, ledger 1967). spawn.v3.magic_cdn.write.overwrite_refused fired once at the top of refuseOverwrite with {component, layer} only, so a benign crash-resume spike and a cook-killing identity-error spike were indistinguishable without a log dig. The single emission now lands at the three consumption terminals tagged outcome: declared_handled (caller declared handlesRefusedOverwrite), resumed_identical (existing bytes verified byte-identical — crash-resume, benign), identity_error (existing bytes are not this run's bytes — the cook fails loudly, the alert-worthy slice). Exactly one emission per refusal, so totals across outcomes match the pre-tag count and existing monitor sums are unchanged.
  • isGrounded() and getVelocity()/getObjectVelocity() truthful for NPC agents (#11994, ledger 1970). Both verbs read only physics-body state (PhysicsCharacterControllerState / rigid bodies) that agent-moved NPCs never have, so isGrounded() returned a plausible false forever on every npc: {} entity (air-state machines tripped into fall on tick 1 and looped the fall clip against the walk cycle — enfeul's Spartans) and getVelocity() read silent zeros. The agent mover's own NpcMotion (grounded + achieved velocity, engine-computed by npc-move-resolve every tick) now answers: character-controller state first (players byte-identical), then the agent lane — and the agent lane answers ONLY where the mover is the system of record (npc cfg/intent present, not controller-owned, not force-driven dynamic; a stale motion row can never shadow live physics). Velocity reads ride the same 0.05 read grid as every other velocity read. Stale jsdoc corrected at the source (types.ts → api-reference/TomeAPI regen) plus one npc.md line.
  • Sim worker sheds the renderer (#11990). One value re-export of createLumeAdapter in engine/renderer/index.ts dragged 253 lume modules (frustum-cull, primitive lanes, lighting, sky, decals, shade graph…) into the sim worker and main thread — realms that render nothing. The re-export is deleted (the type-only export stays, erased at build): sim runtime-worker −0.373 MiB, main index −0.035 MiB, renderer unchanged — −0.408 MiB total off the minified kernel bundles.
  • Terrain stochastic anti-tiling becomes a per-material enum, default off (jacob's ruling, thread 1785760739). New spec knob antiTiling?: "none" | "stochastic" on terrain materials (default "none"). The enum rides the shading chain (interpreter → buildMaterialPack colorBy → render config → lume atlas params PARAM_ROW_PBR2 .y as TERRAIN_ANTI_TILING_MODE) and gates the jitter strength per LAYER in the heightmap surface shader — mode 0 collapses to the pinned bit-exact plain sample, so default terrain tiles seamlessly; "stochastic" is the unchanged #9338 per-meter jitter mechanism. The string enum is deliberately the general form: hex tiling (jure's branch) lands as a third accepted value and mode index, never a new spec field or params row. Shading-only field: toggling it takes the materials fast path (no chunk rebuild). Device gate (verify-terrain-jitter) grew default-clean and per-layer-independence scenarios plus before/after captures of the repro class.
  • Server terrain full-place sweeps: stale-cancel + save-burst coalescing (#11995, ledger 1965 cut 1). Every terrain-content save re-fired a FULL server rebuild sweep (256 chunk builds — 30–100s on the 2vCPU prod container), and during wisp world-build bursts the sweeps STACKED: each save saturated the pending budget with builds of dead content while the previous sweep drained (the measured tick collapse, 14 ticks/71.8s). Two scheduling moves at the seams, content and determinism untouched: the interpreter's terrain-content trigger now carries supersedeInFlight — every pending build for the place cancels before its body runs and the place-wide marks resubmit under current inputs the same tick (no warn, no backoff) — and a re-sweep landing within 10 ticks (~333ms) of the last sweep start holds until the burst settles (trailing edge, 90-tick max hold; held sweeps park as persisted dirty marks). First save of a burst fires immediately, so single interactive saves keep today's latency exactly. Player-support chunks and voxel authority-window reentry are exempt from the hold. Stacked-sweep bench (real spec, 5 consecutive terrain applies): 640 → 355 builds executed, 79.4s → 39.6s build CPU.
  • Saturation-weighted texture tint on the primitive lane (#11989, ledger 1707). TINT_WGSL's colorize was binary: a primitive material carrying both texture and color ran luma-colorize at full strength, so a near-white color was a black-and-white conversion of the art (nickd's 8 gallery paintings, ma1neac's Sugar Rush — two creator specimens in 11 days). The law is now the FX sprite lane's mix(base * tint, colorize(base, tint), saturation(tint)): neutral/near-white tints degrade to an exact multiply (model-lane parity — near-white ≈ no-op; the accidental-grayscale class stops existing), fully saturated tints land byte-identical on the old luminance-preserving colorize (the June disco-ball/pizza panels keep their look). One shared prim_applyTextureTint serves the arrays and oversized lanes; tintStrength stays the store's binary tint-present flag; terrain WGSL tint laws untouched. VISUAL: worlds relying on ACCIDENTAL grayscale change look (they regain the texture's own colors) — flagged for the zoo walk; version-pinned worlds unaffected.
  • Platform-lip gate gains a weight-bearing exception — the corner-landing jump lock dies (dig cf6b52b8, #12001). Landing on a convex plan-view corner, the bottom-sphere contact normal tilts only asin(d/r) from vertical (d = axis→corner distance) — well inside the slope limit, so the contact solver holds the capsule as walkable ground — but the lip gate's support probes are axis-aligned (center + ±x/±z offsets, straddle pairs requiring BOTH sides) and a corner's support lives in one diagonal quadrant: every ray missed, no opposing pair existed, and the gate demoted a stance bearing full weight. "Airborne but load-bearing" is a contradiction no motor survives: isGrounded() false forever (coyote never re-arms — jump permanently dead standing there) while fall velocity integrates to terminal (stepping off fell INSTANTLY at maxFall). The gate now verifies the OUTPUT, like the lift gate one screen up: when the motor pressed down (desiredVertical < -EPSILON) and the committed movement blocked the fall (|committed y| ≤ EPSILON), the stance bears weight and stays grounded — can bear weight ⇒ can jump. Everything the gate exists for still demotes: a walk-off lip pivots the capsule down around the edge (committed y < 0 every moving tick), and a mid-fall rim catch carries fall-quantum or snap displacement (committed y ≠ 0). Snap cannot forge the hold — it only ever ADDS downward displacement (the dig's discriminator: snapToGroundDistance=null holds the identical pose at the identical height, so the hold is the solver's, not snap's). Weight-bearing/straddle stances keep groundEntity null (no single axis-support identity; the gate's probe result is not re-probed). Pinned by corner-landing-jump-lock.integration.test.ts; snap-lip-jitter.integration.test.ts re-pinned alongside.
  • Script re-save reload suppresses onDestroy on owned spawns + one reload log line (ledger 1973, #12000). The rebind sweep's teardown (teardownAndRerunOnSpawn) now destroys previously-owned children with skipOnDestroyHooks: true — a re-save reload is a planned event, not gameplay: onSpawn-authored children re-mint under IDENTICAL ids in the same synchronous turn, so an onDestroy fired there narrated deaths that never happened (phantom casualties, ghost wrecks — the planned-events law: a planned reload must never present as an in-fiction death). Same option class as its original consumer, the cross-writer destroy preview: a destroy whose fiction does not belong to this executor. Suppression makes the reset silent, so the sweep writes ONE "log"-level line per save (logScriptSaveReloadDespawns — never "warn": planned events never present as anomalies): which scripts saved, which owners reloaded, how many spawned entities despawned, whether onSpawn re-ran, plus the teach tail — spawns made in onSpawn re-create with stable ids; spawns made outside onSpawn (events, timers, update) do not re-create until code re-spawns them. The line is the witness that gives the god-mode dangling-id warnings a cause standing next to them in the ring. Pinned by script-resave-entity-lifecycle.test.ts.