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

Engine v5.0.14

June 12, 2026

A patch in the For Real line.

what's new

  • Colored and projector light shadows now work for as many lights as your scene needs — they previously stopped working past one or two on many graphics cards.
  • Fixed mouse-look games erroring on Firefox when capturing the mouse.
  • Worlds with lots of lights and detailed terrain now pick a fitting quality automatically on limited graphics cards instead of failing to draw.
  • A texture script that takes too long to draw can no longer freeze your game — the engine stops the draw within its budget, keeps the texture's last good image, and tells Savi exactly which script ran long and for how long.
›technical notes
  • Special-spot shadows (IES/projector/custom-color spots) folded into the local-light ShadowAtlas: the atlas grew a lazy transmitted-color layer (transmittedRenderTarget — atlas-shaped, shares the depth texture, so a special-spot cell renders depth + tint in ONE scheduled pass at the ordinary per-face budget cost), and each special spot's stock per-light ShadowNode is replaced by a custom light.shadow.shadowNode (SpecialSpotAtlasShadowNode, the SunCascadeShadow/directional-bank seam) that samples the shared atlas depth + slot records + transmitted layer. N colored-shadow lights now cost a flat +1 sampled texture and +1 sampler (the per-light era cost 2N of each — on the universal 16-sampler grant that capped colored shadows at 1-2 lights, the jure field report). Scheduling is the atlas's existing top-K/budget/caching, with special spots competing as ordinary spot candidates via the data node's extra-lights channel.
  • The #7176 special-spot admission machinery is deleted (spot-shadow-admission.ts: admitSpecialSpotShadows, releaseSpotShadowRequest, the demotion diagnostics; maxAdmittedSpecialSpotShadows, SPECIAL_SPOT_SHADOW_SAMPLED_TEXTURES in quality.ts) — there are no per-light shadow pairs left to admit. The demand model is composition-free: the lit stack counts the atlas as a trio (depth + slots + transmitted; tiers land at 14, low 11), terrainPipelineSampledTextureDemand/selectTerrainShadingVariant lose their special-spot parameter, and the pipeline-rejection alarm handler is unchanged. Atlas-less sessions (low tier, single-sun device floor) give special spots a shared neutral shadow node — lit, castless, structurally unable to mint per-light shadow targets, matching the sun-only semantic every other local light already has there.
  • The atlas tap TSL (slot decode, gutter window, rotated-grid compare kernel) is extracted to shadow-atlas/atlas-tap.ts and shared verbatim by the froxel loop and the special-spot node; the transmitted tap samples at explicit mip 0 (uniformity-safe). WGSL receipts: 5 shadow-casting custom-color spots compile to byte-equal binding/sampler counts as 1 (4 textures + 2 samplers for the whole lighting stack in the test material), inside a 16/16 grant, dominance-clean.
  • Firefox pointer-lock fix (ledger 629, prod DD receipts: TypeError: can't access property "catch" on every acquire): Chrome/WebKit return a Promise from requestPointerLock, Firefox returns undefined — the pointer-lock manager chained .catch directly on the return, so on Firefox every acquire threw in the edge-triggered reconcile. The lock request itself was issued before the throw, but the rejection handler / gesture-gate arming / prompt update never ran and the TypeError propagated up the reconcile path. The return is now normalized through Promise.resolve before chaining (return-normalization only — no gate/reconcile semantics change), and Firefox's void-return signature joins the documented browser realities in the manager header. Pinned by a Firefox-shaped test (void-returning request mock: acquire never throws, lockchange still settles, gate behavior intact).
  • Capability admission replaces the texture-budget ladder (Jacob's ruling: never request more resources than the adapter supports — rungless; rungs are for performance, not device capability). The sampled-texture demand model is complete: shadow-casting IES/projector/custom-color spots bind a per-light depth + transmitted pair in every lit shader (previously uncounted — the gray-screen mechanism on 16-grant devices, partner report "Higher": counted terrain stack 11 + 3 uncounted pairs = 17). maxAdmittedSpecialSpotShadows caps those pairs per scene composition at the light-membership refresh (spot-shadow-admission.ts — deterministic creation-order demotion with a one-time diagnostic, restored when budget frees), and selectTerrainShadingVariant picks the terrain shading variant (full / simplified-lit / unlit) statically from the grant at terrain-resources creation. No pipeline whose stage demand exceeds the grant is ever built.
  • The reactive walk machinery is gone: degradeTerrainTextureBudget, the scene-level textureBudgetRung escalation, and the @budget material-name re-arm regex are deleted. The simplified-lit and unlit terrain compile paths survive as static selection targets. A pipeline-resource rejection now hides the object and reports an engine resource-accounting bug — it is an alarm, never an input.
  • The texture-budget suite pins the demand model against compiled WGSL (full variant 5 fragment bindings, simplified-lit/unlit 3), proves demand ≤ grant by construction for worst-case compositions on every tier and grant down to the WebGPU spec minimum, and pins the deterministic composition-change demotion order.
  • Client-wedge class fix (ledger 628 forensics, game "Press Quest", app 8f40c804: a scripted texture re-baked client-side for ~446s and silently froze the player): the texture-script bake budget (TEXTURE_SCRIPT_BAKE_BUDGET_MS, 50ms wall clock) is now enforced MID-DRAW instead of only after the draw returns. Every script-facing 2D context is wrapped in a budget proxy (clock sampled every 32 ops, methods bind-cached), ctx.random() and ctx.canvas() sample the clock, and ctx.atlas() checks at every cell boundary — a runaway draw aborts within the budget with the existing budget-fault park instead of running for minutes. This closes the unbounded synchronous hole in the inline bake transport (hosts without nested Worker, where the bake runs on the renderer host thread); on the worker transport it also retires most 5s-watchdog kills. The residual case (a loop that never touches the ctx surface) remains covered by the worker watchdog.
  • A faulted RE-bake now keeps the previous texture: the asset service's script-edit sweep restores the old cache entry when the new bake parks (compile/runtime/budget), so consumers keep the last good texels instead of dropping to a placeholder and the retired-texture grace sweep can no longer dispose art that is still on screen. A later fixing edit swaps and retires normally.
  • Budget diagnostics now carry the elapsed time (elapsedMs in the report data and in the message) and state what the engine did ("The engine kept the texture's last good image (or its loading placeholder if it never baked)"), riding the existing texture-script-budget rail to getLogs + DM so Savi can see a client-side bake stall instead of misreading it as engine perf.