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 throughupdatePlace(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 inaudio/listener-follow(client), pinned bylistener-follow.test.ts.- GLB parser accepts the
EXT_texture_webpDECLARATION — 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 joinsSUPPORTED_EXTENSIONS, so therequired extension "EXT_texture_webp" is not supported — content may be missing or wrongwarning 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).parseTexturesdoes NOT readextensions.EXT_texture_webp.source: a webp-ONLY texture (extension reference with no top-levelsource) stays unresolved — the model renders untextured, and the honesttexture[N]: no image sourcewarning stands — until the asset is recooked to KTX2 (the ledger-1198 recook lane,recook-webp-avatars.ts). Textures ridingKHR_texture_basisuor a top-levelsourceresolve exactly as before. - Identify UI-plane mode — the scene-identify verb's named sibling (r-3934ca4b).
identify_objectgainsui: 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'sINTERACTIVE_SELECTORS, the exact grant vocabulary), are world-anchored, or are top-level overlay roots — and withscreen: [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_refusedfired once at the top ofrefuseOverwritewith{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 taggedoutcome:declared_handled(caller declaredhandlesRefusedOverwrite),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()andgetVelocity()/getObjectVelocity()truthful for NPC agents (#11994, ledger 1970). Both verbs read only physics-body state (PhysicsCharacterControllerState/ rigid bodies) that agent-moved NPCs never have, soisGrounded()returned a plausible false forever on everynpc: {}entity (air-state machines tripped intofallon tick 1 and looped the fall clip against the walk cycle — enfeul's Spartans) andgetVelocity()read silent zeros. The agent mover's ownNpcMotion(grounded + achieved velocity, engine-computed bynpc-move-resolveevery 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 onenpc.mdline.- Sim worker sheds the renderer (#11990). One value re-export of
createLumeAdapterinengine/renderer/index.tsdragged 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): simruntime-worker−0.373 MiB, mainindex−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 bothtextureandcolorran luma-colorize at full strength, so a near-whitecolorwas 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'smix(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 sharedprim_applyTextureTintserves the arrays and oversized lanes;tintStrengthstays 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=nullholds the identical pose at the identical height, so the hold is the solver's, not snap's). Weight-bearing/straddle stances keepgroundEntitynull (no single axis-support identity; the gate's probe result is not re-probed). Pinned bycorner-landing-jump-lock.integration.test.ts;snap-lip-jitter.integration.test.tsre-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 withskipOnDestroyHooks: 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 byscript-resave-entity-lifecycle.test.ts.