Spawn
spawn / swhat we're building

pinned

start herewhat spawn isfaqfrequently asked questionsthe betthe spawn bet

updates

engine v5.2Lume2 weeksengine v5.1ConnectionJune 18, 2026engine v5.0For RealJune 4, 2026engine v4.6AtelierJune 1, 2026engine v4.5Surface TensionMay 22, 2026engine v4.4SolidMay 15, 2026engine 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

Lumeengine v5.22 weeksConnectionengine v5.1June 18, 2026For Realengine v5.0June 4, 2026Atelierengine v4.6June 1, 2026Surface Tensionengine v4.5May 22, 2026Solidengine v4.4May 15, 2026Groovyengine v4.3May 13, 2026Continuumengine v4.2May 9, 2026Foundationsengine v4.1May 4, 2026Genesisengine v0.1April 29, 2026
← All posts
← All posts

engine v5.2.9

Engine v5.2.9

July 29, 2026

A patch in the Lume line.

Saves stop storming, cities stop blinking, Safari plays

what's new

  • Saving a script in a world that rebuilds its objects (city generators, wave spawners, destroy-and-respawn builders) no longer makes everything blink out and back over and over — rebuilt objects land once and stay.
  • When the room does refuse to create something (flood limits, permission gates), your game's log now says so by name instead of the object just quietly never appearing.
  • Models you place while they're still being prepared now show up on their own once they're ready — no more invisible props that stay gone until everyone rejoins the room.
  • When Savi checks the scene, a model that actually failed now reads as failed instead of "still generating" forever.
  • Firefox players get your world instead of a black screen — a shader Firefox refused to compile is fixed, and every shader the engine generates is now checked against Firefox's rules before it can ship.
  • Safari and iPhone players stop losing their session to "graphics device stopped responding" — a Safari bug the engine now works around automatically.
  • Big outdoor worlds run roughly twice as fast on phones (one test iPhone went from ~15 to ~27fps), and phones get their own lighter bloom so glow effects stop eating the frame budget.
  • Scattered objects (forests, rocks, crowds) now respect every setting you put on their template — most visibly castShadow: false: turning shadows off on a scatter of 400 trees actually turns them off now, instead of silently ignoring you and paying the full shadow cost.
  • Savi got sharper at two things this build: kills in multiplayer combat feel instant for the shooter (no wait-for-server hitch in the death reaction), and floating-island worlds get true open sky below the horizon instead of a fake ground haze — she now reaches for both patterns on her own.
  • When a texture script draws something but the texture comes out blank, Savi now gets told exactly that — instead of the engine calling it a success and leaving you staring at an untextured object wondering why.
  • Fixed a runaway save loop: some games were silently saving new versions every second (one hit 2.6 saves per second, thousands of junk versions) — burning storage and making version history useless. Saves only happen when something actually changed now.
  • Savi can now look at your game the way a phone player sees it — checking that menus and HUDs fit a small screen — instead of only ever seeing the desktop view.
  • Side-facing walls (facing: 'x') with off-center doors or windows now collide where the opening is drawn — no more invisible wall in a visible doorway with a secret walk-through spot mirrored across the wall's center.
  • Savi's 3D-model workshop got a box of professional modeling shortcuts — custom models come back with nicer edges and surface detail in one pass.
  • Your game has a real database now — one save file per game, with your build room's data kept physically separate from live players' data. Savi writes ordinary SQL for save systems, load-at-join, save slots, shared world state, and analytics queries — and games that saved before the database keep every old save, carried over automatically.
  • Misspell or invent an option on an effect sprite/ribbon/light and Savi now gets told exactly which word the engine doesn't know, with the list of words it does — instead of the effect quietly ignoring it and leaving everyone staring at a fireball that "should" have soft edges.
  • Games with always-on regen, timers, or meters no longer flood their version history with a save every half-second — pure player-state ticks batch up quietly (real changes still save instantly, and nothing is lost if the room crashes).
  • Ask for crouch in a first-person game and Savi now lowers the camera the way you'd expect, instead of tilting it toward the floor.
  • Swapping or deleting sprite art mid-session no longer leaves the engine silently re-requesting the dead art every 10 seconds until someone reloads the room.
  • Dragging a file from your computer onto the game view works now — the drop lands in the import overlay instead of vanishing into the game.
  • Right after sending a voice message, tapping Tab opens the chat box again instead of doing nothing.
  • Cold animation sheets no longer flash giant — the engine holds the whole last frame until the new sheet lands.
  • Safari and iPhone players get another big speed-up: the engine stops using a rendering path Safari secretly rebuilds from scratch every frame, and three per-tick busywork loops that hit Safari's JavaScript engine hardest are gone entirely.
›technical notes
  • Entity fences stop eating CREATE rows — the city-blink/anti-phase-rebuild class dies (ledger 1795, dig 8b34645c). A forwarded-DELETE entity fence no longer drops CREATE rows (updates/deletes/events stay fenced): a create row is a deliberate re-mint (ENTITY_NEW at the sender's log walker), never a pre-adoption straggler, and the admission path already owns same-id collisions. This deletes the wild loop's only receipted create-killer — prod 5.2.8 "Clog a Toilet" (incidents 119a08e8 → 8b34645c) churned a 123-tick anti-phase rebuild for the room's whole life: fence eats creates → never acked → 3s ghost retire → confirm walk fails → rebuild → next fence. Destroy/respawn builders now converge by construction.
  • create.nack on every create-drop class: every create row the StateDeltas drain DROPS now answers {kind:"create.nack", ids, reason} beside the create.ack push — create-gate rejects, chain-validation unwinds (displaced-host included), flood-budget prefix drops, and the suspended-session wholesale drop. Client-side a nack fast-forwards the pending entry's grace (ghost retires on the next drain, not 3s later) and the client-ring receipt names the room's reason. No create can die silently — silence now means only "the message never arrived", which is what the grace window remains armed for.
  • Dead-parked model loads re-probe on a long cooldown — the silent forever-park is eliminated (ledger 1808, the Thunder Bluff latch, dig 22f87a4d). Models placed while their import was still cooking never transitioned to rendered after the asset completed: dead answers collected during the cook window burned the terminal budget into retryAfter=INFINITY, and both documented re-arm doors are dead mid-session (manifest re-add dedupes at the sim AssetService; room reattach needs a reconnect). Dead-4xx/auth give-ups now park on LONG_RETRY_COOLDOWN_MS (one bounded re-probe per 5 minutes, autonomous via the retry wake) — any asset that becomes servable converges within one cooldown with zero external signal. INFINITY now belongs only to states with their own explicit door (pre-session cap, content-policy rejection). Room attach still re-arms immediately (ledger 1183 semantics preserved).
  • Eye honesty: getUnreadySceneAssets reads a dead 4xx answer as 'failed', never 'generating' — a cooking asset answers 202, so a dead answer is a verdict, not progress (401/403 stay 'generating': auth verdicts the session cookie, not the asset). Savi's live-eye stops promising assets the engine had stopped requesting.
  • The upload-drop receipt rail's dedupe key drops its per-entity component (dash-named ids made one 80-entity sweep 80 warn buckets in one tick, evicting the asset-load receipts this incident needed): one line per (client, gate) per 30s window, every drop still counted in stats.
  • Firefox-valid generated WGSL — cullSelectWgsl gains an explicit unreachable tail return in aggClaim so all five variants validate under naga (r-859dee09). Firefox's WebGPU validates shaders with naga where Chrome/Safari use Tint/Dawn; a Tint-accepts/naga-rejects shader parks the pipeline at createShaderModule, the canvas goes black, and telemetry reports only second-order resource-state noise (forensics: 152/153 victims of the affected family were Firefox, one a fresh starter world). The pulled vertex source also threads through displacementWgsl correctly.
  • CI gains the missing validation axis: a naga gate over lume's GENERATED WGSL (#11314). Every WGSL gate previously validated against Dawn only, so the whole Tint-accepts/naga-rejects family was structurally invisible. The gate enumerates the generators' output — every lume shader generator × its realistic parameter matrix, couplings taken from the engine's own derivation sites, 1,538 artifacts across culling, terrain, and post — and validates the exact strings prod builds pipelines from. The class can't come back unseen.
  • Shipped Safari/WebKit renders again — fragment stub for vertex-only shadow bundles (WebKit bug 319980). Root cause of the Safari/iOS GPU session deaths ("graphics device stopped responding" on desktop, silent WebContent kills on iPhone): every lane shadow bundle pairs a vertex-only caster pipeline with a depth-only bundle encoder, and shipped WebKit's RenderPipeline::validateRenderBundle rejects that spec-legal shape at setPipeline — the bundle records invalid every frame and the sustained validation storm survives both render-chain rebuilds. Fixed on WebKit main 2026-07-22 but months from shipping, so: probeFragmentlessBundleSupport() runs one throwaway vertex-only bundle at device init inside an error scope (a probe that cannot run never convicts), and when armed, laneShadowWgsl gains an empty @fragment fn fs() {} bound with zero color targets — a construction-frozen session latch, same law as the sample-count/color-format contracts.
  • Phone texture-page mip admission — 15→27fps on iPhone (7.9× draw cut). The mobile texture profile (256² pages) refused every 512² world texture to the oversized direct pools, whose lanes can never activate pulled-attribute batching — phone-tier sessions drew the forest one indirect draw per unique geometry (1,333 draws vs 169 on desktop, ~18ms of empty-indirect Metal encoder overhead in scene.opaque). A 512² chain's mip 1 IS the 256² image, so admission now seats an oversize chain at its first fitting level and the residency copy reads the tail. iPhone receipts: pinned rung 0 1,320→156 draws, scene.opaque 19.6→5.6ms, GPU 38→25.8ms; unpinned natural ladder ~17→~27fps, and the frame-budget guard no longer parks the look (thermal ramp flattened). Tail admission is per-tier policy — desktop keeps native-res pools.
  • Offset admission refuses off-block-grid base dims — the zoo 512×285 device-loss storm. Compressed formats require block-aligned dims at createTexture's BASE level; halving a block-aligned NPOT chain can land off-grid (2048×1140 ASTC at offset 2 = 512×285), minting an invalid texture that stormed every lane bundle binding it and killed the session with GPU device loss. Off-grid offsets are refused at admission.
  • Bloom shape presets — full/lean/phone on the quality ladder. Bloom's 7-pass half-res 4-level pyramid becomes 'full'; 'lean' (half-res, 3 levels, 5-tap Kawase) and 'phone' (quarter-res, 3 levels) join it as a construction-frozen session contract. The post stack runs at OUTPUT resolution — TAAU internal-res scaling never protected the phone from bloom — so the phone preset is the first bloom cost cut that lands on device. A/B override: globalThis.spawnBloomShapeOverride + render-harness --bloom-shape.
  • Depth-local look fusion — haze rides the composite, look.present dies on hazed frames. classifyLookFusion three-classes the active look (pixel-local / depth-local / offscreen); vocabulary haze (a fixed per-pixel depth tap) now fuses into the composite (zero new bindings on the TAAU-fused kernel), freeing the full-res rgba16float look intermediate on leaving two-hop. Standalone pass stays byte-identical as the offscreen path + A/B control arm.
  • Transient textures survive usage injection (F1) — zero validation errors at any creation time. WebGPU Inspector ORs COPY_SRC|TEXTURE_BINDING inside wrapped createTexture and Dawn rejects the merged mask (every scene.alpha submit failed under instrumentation). The pool probes once at first transient request (a 1×1 attachment-only create reveals the wrapper), degrades to non-memoryless up front, and keeps per-texture destroy-and-recreate as backstop for late-attaching wrappers.
  • Scatter templates stop silently dropping castShadow/receiveShadow (+14 more keys) — the hoist list is derived, not hand-maintained (incident 7fdfd766). SCATTER_TEMPLATE_PROPERTY_KEYS omitted 16 keys that were valid per the type (ScatterTemplate includes Partial<ObjectProperties>) and silently vanished in normalizeTemplateProperties when written top-level: attachment, pivot, visibleInFirstPerson, visibleTo, castShadow, receiveShadow, spline, mixer, ik, jiggle, collider2d, sign, fx, interpolation, npc, blueprint, brush. Incident app ca4e9c87: 403 scattered trees all read cast:true against templates declaring castShadow:false — full sun-cascade cost for shadows the creator turned off. The hoist list is now DERIVED from SCATTER_TEMPLATE_KEY_ROLES, a role map whose satisfies makes classification exhaustive — a new ObjectSpec/ObjectProperties key is a tsc error until marked hoist/spec/dropped (with a reason comment for every drop). Spelling-parity tests cover every hoisted key.
  • Skills teaching wave (rides this build's skills.json): combat.md teaches predicted-kill presentation — death reactions project locally, authority reconciles (tide's f3a09a5f §3c, #11288); world-composition.md teaches the open-sky idiom — floating islands set atmosphere.ground: false (two-creator demand in one week: Lightwake's 14 burned versions + izkimar; the cure itself ships since 5.2.8, the teaching now points at it); weaves.md teaches the structured inter-wisp handoff shape that parses at the seam (#11285); match-a-reference.md's three prompt clauses learn their true targets (#11338); drawn-art.md's cliff step gets a bounded budget — one doubling, then it's a camera-height problem with named levers (#11341).
  • Texture bakes ship a pixel-coverage receipt — a blank bake is a named outcome, never silent success (incident c2c60fea, ledger 1805). The bake worker reads an exact non-zero pixel census + strided sample hash off the finished canvas before transferToImageBitmap and ships it with every ok bake; the host raises texture-script-blank-bake when the census reads blank after a draw that ran ops. The blank bake still ships — the receipt is a name, never a gate.
  • texture(ctx) returning a thenable is a runtime fault (texture(ctx) must be synchronous): the canvas is captured the moment texture() returns, so an async draw silently shipped only the ops before its first await.
  • New browser-gate suite bakes the incident probe through the REAL built worker artifact on chromium+webkit and asserts bitmap content pixel-exactly — the raster class vitest's recording stub structurally cannot see.
  • STOP PAIR: the update storm (runaway version-minting) is cured at both ends (dig 535afdad, ledger 1797). Engine side reverts #11111's bidirectional singleplayer template fold: the fold judged live writes against a template mirror its own enqueue never advanced, so a per-tick state write on a promoted key re-enqueued every tick forever, and the coalescing persist sink minted one game_specs version per batch — Slime Arsenal (90469935) minted at 2.6 versions/second. Live state writes on template-carried keys are once again live-only (the one-directional promotion at persist:true write time stays). Known trade, taken deliberately: #11111's resurrection cure re-opens — a template-carried key cleared by a plain live write re-seeds its stale template value on rejoin (the goblinjo class, dig 7ed7f11e) — until a storm-proof fold design lands.
  • Platform backstop (kiln-side, active for every room at kiln deploy regardless of engine pin): a mutations batch that changes nothing can never mint a game_specs version — the SDK mutations route serialize-compares the folded doc against the pre-apply head and answers the existing no-op shape (200, updated:false, head version) on equality, inserting nothing (prod receipt: Sky Islands 9bfc70a0 minted byte-identical v4740/v4741). The platform-wide floor for the empty-mint class, whatever the future writer.
  • Zoo: VAT Lab pen — vertex animation textures from existing primitives, zero engine changes (jure's spike, #11306). Three exhibits + the bake-on-display board (KeyX): soft (80-figure one-draw crowd wave), rigid (60-brick shatter with baked quaternion tumble + staggered time-reverse rebuild), and sim-sourced cloth (Verlet flag, crossfaded seamless loop) — texture scripts bake the data (RGBA8, alpha-255 law), bespoke geometry carries VAT addressing in uv, scripted materials replay it in the vertex stage (~287 KB total). Engine findings for the rendering lane, named: vertex-stage texture sampling works today; scripted materials draw standalone (merged-mesh crowds cap at the 16,384-vert bespoke budget — instancing is the scale-up ask); positionNode does not reach shadows (exhibits set castShadow:false); culling uses rest bounds (envelope markers).
  • view_player_screen accepts viewport: { width, height } — the schema catches up to the booth (r-c853e0d1 item 1a). The tool's inputSchema was z.object({}) while the booth underneath already honored viewport end-to-end (resolveViewport clamps 64–4096, warm eyes resize per job) — Savi could not ask for the phone-sized look her own craft clause ("the HUD fits a thumb-wide screen") requires. Viewport rides the booth request verbatim; the caption names the served size from the booth's own receipt; bounds are mirrored at the tool boundary; a provider-level schema pin asserts only viewport {width, height} exists (a param the booth ignores would be a new lie). game-ui.md retires "asking the player — they see them; captures don't" (false since the composited eye landed): view_player_screen IS the capture that shows anchored plates.
  • facing:'x' wall colliders no longer mirror their holes across the wall's center (SHUDDER incident 4bbd28d3; born with the wall feature in #5529, survived all three renderer eras). The mesh (createWallGeometry) builds the wall shape in XY and rotates it +π/2 about Y, sending an authored hole.x=+8 to local z=−8; the collider grid (generateWallCollider) wrote shape-x straight into +Z, landing the same hole at z=+8. Every facing:'x' wall with an off-center hole got both player-facing faults at once: an invisible wall at the drawn doorway and a walk-through phantom at the mirrored spot (2·hole.x apart). The collider now mirrors hole.x into the mesh's frame — creators authored five months of worlds against what renders, so the invisible side conforms. Centered holes (x=0) were mirror-invariant and are byte-identical before/after; facing:'z' and 'y' were already consistent and are pinned by new tests, including a mesh↔collider projected-occupancy parity sweep across all three facings.
  • Blender workshop: quickmenu's 10 real algorithms land with their teaching — qm_apply one-call procedural detail (jure's harvest, #11335). The workshop image gains qm.py (355 lines of real quickmenu ports) provisioned as a Blender addon, with the blender-game-assets skill teaching the one-call qm_apply shape so wisps reach for proven procedural detail (bevels, greebles, surface breakup) instead of hand-rolling operators. Workshop-image change rides its own image build; the engine build carries the skill teaching.
  • One save file per game+mode — api.sql is Savi's whole storage surface (the storage-rail deslop, #11259). Every game owns one SQLite database per mode, dev and live physically separate, and api.sql is the one verb: a tagged template whose interpolations bind as parameters, one call one atomic transaction (semicolons = ordered statements that commit or roll back together), errors throw, budgets fail loud (≤64 statements / ~250ms wall / ~10k rows / 4MB per call; size warn near 8GB, growth refused at 10GB with reads and cleanup DELETEs always working). Behavior-script awaits settle at tick boundaries so a load enters the sim atomically; run_script hits the dev DB the moment it executes; scripts/db.js migrate(sql) runs under a door-held lease exactly once per (mode DB, spec version) — a throwing migrate never blocks play, it reports and re-runs at the next boot. The old rail is deleted, not wrapped: api.updateStorage, the lifecycle storage lane, the eight storage:* builtin jobs, createSDKStorage, and the gsdk documents/CAS family are gone, with tombstones on every old name teaching the live verb. Terrain edits and room-state resume are re-planted on _spawn_chunks/_room_state rows (the 10MB doc cliff deleted); pre-database saves surface read-only in legacy_kv via an idempotent, crash-resumable boot importer. New data-and-saves skill replaces lifecycle-and-persistence + jobs-and-storage.
  • fx sink options the engine does not know now talk back instead of silently no-oping (the soft: lesson — soft: 0.4 depth fade was authored in the wild for months against enfeul's fireballs while sprite() silently dropped the key; B5 landed the fade, this closes the class). sprite() / ribbon() / light() refuse unknown option keys with the builder's full vocabulary in the message (fx: unknown sprite() option "softness" — options are: texture, textures, blend, layerMask, align, stretch, axis, soft), and the compile issues rail (validateFxProgram) holds hand-built sink objects (render: { kind: "sprite", … }) to the same law: unknown sink keys, unknown sink kinds, and out-of-law hand-built soft values (non-finite / negative) each report a path-addressed issue instead of a silent drop or a mid-validation crash. Builder throws and FxProgramError both surface through the existing fx.compile fault channel (kept-last-program + one DM per broken version).
  • Singleplayer per-tick player-state persistence coalesces — the third door on the version-storm class (ledger 1814, dig ef0675cc). A behavior script writing player state every 30Hz tick (enfeul's NANO-WELD regen perk, +1.2 hull/sec) minted one FULL game_specs row per drain at the kiln RTT cap — ~115 rows/min × 775KB for as long as regen ran. The persist funnel now stamps kernel-authored singleplayer batches that are exclusively patchPlayer{state} writes as playerStateOnly; the write-ahead ledger merges stamped appends into the queue tail (latest-wins per key where merge-patch composition is provably exact against the fold, sequential otherwise) and holds a sole stamped head until 30s since the last player-state flush. Any structural batch behind the head lifts the hold immediately — order never bends — and /update freshness, idle save, and container shutdown force the flush. Crash-safe from append (the held fold is DO-durable and replays whole); only kiln VISIBILITY of pure player-state deltas lags ≤30s. Pinned: a 60s regen storm mints ≤2 rows (was ~115); unstamped batches drain byte-identically; god-mode hands, multiplayer worlds, and mixed batches are never stamped.
  • Skills teaching additions (ride this build's skills.json): camera-first-person.md teaches crouch as an eye-height change on the FPS feature list — route eyeHeight, never camera pitch (creator report ma1neac: Savi's first-try crouch tilted the camera; eyeHeight existed all along, invisible from her base context); combat.md's Predicted Kills section gains its premise line naming the current host-event NPC damage shape (an engine change to client-auth NPC intents rewrites the section in the same PR); debugging.md gains the measured-signature-is-a-grep-key line (dig f4510109: five mechanism fixes beaten every tick by a stale clamp the user's own number named twice).
  • Retired sprite art stops being asked for forever — the sprite-metadata derive queue prunes at consumption (ledger 1813, dump 9766d757). The host's derive queue had success-only exits: entity despawn and spec art-swap never pruned it, so a mid-session art retire kept paying derive fetches for retired art through the claimed-fetch seam and re-offered rail.assetMetadata every 10s into the relay's unreferenced refusal — forever (4 retired refs × ≥49min; only a reload cleared it). The sprite2d reference walk moves into ONE shared predicate (tome/sprite-texture-refs.ts) consulted at fetch launch and offer/landing — the queue is scheduling state, never a shadow reference list, so despawn and art-swap retire their asks the same sweep — and the relay's unreferenced verdict now routes into hydration as a terminal door. Deliberately not #11334's give-up pole: still-referenced refs keep their ordinary retry ladder (pinned), and a retired ref re-queues on a fresh ladder from any new live reference.
  • Studio input pair (kiln-side — rides kiln deploys, live for every room regardless of engine pin). (1) File drags over the game canvas reach the page again (#11352): the kernel iframe is cross-origin, so a hovering drag routed every drag event into its document and the parent's AssetDropOverlayV4 went silent — drops over the canvas vanished into the game (thread 1785273583, dump e5cab07b). Kernel frames now go pointer-inert only while a Files drag is in flight (DataTransfer.types gate keeps in-game pointer interactions untouched; a depth counter defeats dragenter/dragleave flicker), and every end-of-drag signal restores — drop, dragend, leave-to-outside, window blur, safety timeout, unmount. (2) A quick Tab tap right after a voice send toggles chat again (#11354): a released take now answers hasVoicedAudio() false, so the 0.5–6s settle window stops misreading the tap as TALK and swallowing the chat toggle (cal, dump ea53b5c2).
  • Cold animation sheets stop flashing giant — the texture-swap hold keeps the WHOLE last-good draw tuple (ledger 1817, #11370). A sheet-per-animation script authors texture+uvRect+size+anchor as one write, but applyComposedSprite's mid-fetch hold kept only the old texture while the new sheet's geometry applied immediately — first play of a cold sheet rendered the old sheet's texels stretched over the new-sized quad (~6× blowup) until the fetch landed. The hold now carries the entire last-good draw value and the existing subscribe re-applies the latest authored value whole when the texture arrives; geometry churn without a texture change never enters the hold (the warm spritesheet-frame path stays immediate, pinned by test).
  • WebKit sessions bypass render bundles — the Safari/iOS wall collapse closes (#11337; WebKit RenderBundleEncoder.mm, deep-dive receipts in docs/renderer-gpu-deep-dive.md). Shipped WebKit marks any bundle carrying drawIndirect/drawIndexedIndirect as requiresCommandReplay and re-replays its commands into freshly constructed Metal indirect command buffers on EVERY executeBundles call — the AGX ICB construction pegged the GPU process's WebGPU work-queue thread (93.6% of its samples in replayCommands/makeICB) and the frame's present handshake queued behind it at p50 275ms. This is #8970's iPhone wall collapse, named. Every lume bundle draws indirect (cull args buffers), so on those builds bundles are pure overhead: the UA verdict reusableIndirectBundles:false (bare-AppleWebKit desktop UAs + every iOS browser; not probeable via error scopes — a perf pathology, not a validation failure) drops the bundle profile and every contributor takes its existing direct-command path. No parallel mechanism; Chrome/Dawn keeps bundles. Riding the same hunt, three JSC per-tick churn eliminations (sim-side −63%): bind-group/layout cache keys become an interned-integer trie and canonicalJson dies (per-frame rope flatten + hash was a dominant JSC cost); ground sampling goes revision-driven with a per-foot IK memo (the per-frame terrain index rebuild could never survive a frame by construction); scripted-primitive ingest settles SILENT on a hook-fed dirty set (~4,800 resolveDeriveWork calls × 30Hz on a settled world → zero, pinned by silence tests).