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.21mo agoConnectionengine v5.12mo agoFor Realengine v5.02mo agoAtelierengine v4.62mo agoSurface Tensionengine v4.53mo agoSolidengine v4.43mo agoGroovyengine v4.33mo agoContinuumengine v4.23mo agoFoundationsengine v4.13mo agoGenesisengine v0.14mo ago
← All posts
← All posts

engine v5.2.35

Engine v5.2.35

August 29, 2026

A patch in the Lume line.

Games can read their launch link (QR/invite codes) and send players to specific rooms, deleted things stay deleted for everyone, saves stop un-saving themselves, gravity applies at full authored strength, attached models can wear their parent's animations, materials gain renderOrder and honest always-on-top drawing, spot light angles are plain degrees, giant worlds stream between places without freezing, and quiet NPCs stop taxing every player's machine.

what's new

Characters walking across scaled vehicles and platforms animate at their real speed — no more foot-sliding on giant carriers or sprint animations on miniature props.

  • Phones that can't run Spawn now say so honestly instead of reload-looping. When a phone's graphics driver permanently refuses the engine's texture uploads (a hardware/browser limit, not a glitch), players used to get "Something hiccuped — reloading brings it back" — advice that could never come true. They now get the same honest can't-run guidance the unsupported-device screen already gives, including the Desktop-site tip on Android, and the automatic boot retries stand down instead of looping.
  • Games run much smoother on MacBook Airs and other everyday Macs. The engine used to treat every Mac like a top-end studio machine and max out the graphics; now it starts sensibly and raises quality only where the hardware proves it can afford it — powerful Macs still climb back to full quality within the first minute, automatically.
  • Savi's camera snapshots of your world now show your sprites. Before this, when she looked at a 2D world from her own camera angle, every sprite outside your current view was missing from her picture — she'd see the ground but report your NPCs and objects as not rendering, and could spend a whole session chasing a bug that wasn't there. Her snapshots now show what a player standing at that spot would actually see.
  • Big NPC worlds stop paying collision costs for the ones standing still — now on multiplayer too. Every NPC carries an invisible touch-sensor bubble, and multiplayer clients used to re-check every bubble against the whole world every tick even for NPCs parked far away. Parked NPCs are now nearly free on every player's machine until something actually moves near them, and enters/exits still fire the exact tick a player walks in or out.
  • Performance reports now measure the server too: server-tier incident context carries the same sim timing block player clients ship, so in rooms where the server runs part of the simulation, a slowness report names which engine systems and which of your scripts burned the server's time — previously only players' clients reported timing, and a server-side burner could go unnamed.
  • When Savi reports a performance problem to the team ("this world runs slow"), the report now includes exactly where the game's simulation spent its time — which engine systems and which of your scripts — measured in your live session. Slowness reports name their culprit instead of needing a follow-up debugging session.
  • Effect scripts can read ctx.device — author cheaper phone variants. effect(ctx) now gets the same { class, coarse } device facts your material and look scripts already use, so one explosion can throw 100 sprites on desktop and 40 smaller ones on a phone, full fidelity where the hardware affords it. It's per-viewer and render-only: counts and sizes may branch, but keep lifetimes and timing windows the same on every device — whichever machine owns the effect (maybe a phone) schedules everyone's cleanup from its own branch.
  • Config panels in creating mode whose images finish loading after the panel draws now scroll immediately — previously the newly-too-tall panel could stay stuck unscrollable until it re-rendered.
  • Tab panels in creating mode (your authored config UIs — armories, shops, settings) now scroll when their content is taller or wider than the panel, instead of cutting off everything below the fold. Every field is reachable on any screen size.
  • Quality settings now come from measuring your graphics chip instead of guessing from its name. Thin fanless laptops that share a name with much bigger machines used to open at max settings and stutter; now every device runs a fraction-of-a-second graphics test once, remembers the answer, and opens at settings it can actually hold.
  • Heightmap terrain obeys your lodRanges and verticalRange — deep worlds and pulled-in horizons are authorable. Dig a gorge past −32 by lowering verticalRange.min (the out-of-bounds net follows your floor down), pull the streaming bands in so a compact world spends its budget on the dense middle, or push them out for vista and observer worlds. Perfectly flat worlds are legal too: verticalRange: { min: 0, max: 0 } is a flat sheet, not an error.
  • Savi now knows when the 2d tile layer is covering a 3d object instead of hunting a render bug. In top-down 2d worlds, painted tiles draw over blocks and models that sit below the camera's eye level — that is how 2d layering works, not a glitch — and when you ask her about such an object she is told "drawn but covered by the tile layer" instead of "rendered", so she can say what is actually happening and how to see it (lower the camera, or build 2d scenery from sprites).
  • Your game can now read the link it was opened with: api.launchParams gives you the URL's query parameters (like ?code=1234 from a QR code or invite link) right from the player's spawn — great for party codes, invite flows, and deep links into a specific mode.
  • api.enterRoom(roomId) sends a player to a specific room — build lobbies, party-up flows, and "join my friend" buttons. It's a request to travel, not a teleport receipt: the platform still picks the door, and your code gets an acknowledgment that the move was dispatched.
  • Fixed a bug where re-running a patch after a failed save could silently lose it. If a save died halfway (a flaky room, a rejected batch), re-applying the same terrain/player/camera/inputs patch used to report "changed nothing" and never reach the saved game — your world could revert to starter terrain after a restart. Re-runs now always reach the save, and truly redundant patches still don't clutter your version history.
  • Worlds full of NPCs stop paying physics for the ones standing still far away. Every NPC carries an invisible touch-sensor bubble, and the engine used to re-check every bubble against the whole world every tick — even for NPCs parked at the far end of the map. Parked NPCs are now nearly free until something actually comes near them, and everything still fires exactly when a player walks in.
  • Fixed falling through the world when spawning or teleporting into a place whose terrain is still loading. Players now land on the terrain's true ground height immediately — the engine stands them on the height model's own answer while the real terrain streams in underneath, instead of letting them fall and flicker until it catches up. A player template with visible: false stays invisible — builder bodies stop appearing in observation games. The engine no longer dresses the default profile avatar on any body whose template declares it invisible, including Savi's own builder body when she joins to work in the background.
  • Fixed a serious multiplayer bug that could permanently delete parts of a world. Rejoining a large game that had been sitting idle could trip a race where the rejoining player announced an incomplete copy of the world as the real thing — and everything it missed (usually the most recently built entities) was deleted from the room's history forever. The room's new host now waits until it has fully absorbed the world before speaking for it. Switching rooms quickly no longer risks losing the last save from the room you left — the game finishes writing your departure data before it connects to the next room, and a room switch still always completes within a few seconds even if that write gets stuck.
  • Scripts that do a lot of math got faster. Math.max, Math.min, and Math.hypot in your behavior scripts and terrain generators used to pay a hidden tax on every call — up to 19x slower than they should be. Hot loops like distance-to-track checks, clamps, and steering math now run at close to full engine speed, with results bit-for-bit identical to before.
  • Moving around in god mode stays smooth while background builders are working. When wisps were writing heavily, the engine's own bookkeeping could bury the simulation step that moves your body — movement visibly chopped while looking around stayed smooth. Both bookkeeping passes are now capped per tick, so your movement never waits on them.
  • Point at any part of a generated build — Savi now sees exactly which block you mean. "Remove these bars" used to send her probing a whole procedural tower by hand; now the exact piece under your cursor answers with its own name and measurements.
  • Your game can now tell a fresh arrival from a reconnect: api.resumed is true when a player's session picked back up where it left off (their connection blipped, or they tabbed away and came back) and false on a genuinely fresh start — so you can skip the intro cinematic for someone who was just here, or say "welcome back" only when it's true.
  • Fixed a false alarm that blamed your scripts for "oscillating" touch controls. In busy multiplayer rooms the engine could mistake its own internal refreshes for your game switching touch surfaces rapidly, then point at an innocent script file — even in games with no touch-surface selector at all. The warning now fires only when a selector you wrote actually flips between surfaces faster than players can follow, and it names the actual selector script.
  • Touch sticks: place them, keep them level, keep them visible. A fixed stick now takes position: { x, y } (0..1 within its thumb zone) so you can pull it in from a tablet's edge or line two sticks up — the engine still keeps it out of notches and platform chrome. symmetric: true on a touch surface keeps the left and right analog zones the same size even where platform chrome trims one side. And visibility: "always" on a stick keeps its resting pad on screen instead of fading when untouched. Games that hide the player template (visible: false) and dress bodies from scripts now work end to end: your script's model writes always land (including dressing players in their own account avatar), and script-driven characters animate under your animations instead of the engine's. If a script reveals a player that nothing has dressed yet, the runtime log now says so once and names the fix, instead of showing an invisible body with no diagnostic.
  • Resizing the game window is smooth now. Dragging the window edge used to make the engine rebuild its render surface dozens of times a second — on some machines that could freeze or crash the graphics process mid-drag. The view now scales fluidly while you drag and snaps sharp the moment you let go.
  • Adding a behavior script to something that already exists now actually starts the script — no more "Savi attached it, the save looked fine, and nothing happened."
  • When spawns can't land because a room area has no one simulating it yet, you and Savi now get a clear one-line explanation instead of things silently never appearing.
  • When Savi looks at your game and something isn't drawn, her tools now say why it isn't drawn instead of reporting it as rendered — fewer wild-goose chases after "invisible" objects.
  • Gravity now does what it says: falling and thrown objects accelerate at exactly the strength you set for the world, instead of a bit more than half of it. If you tuned gravity extra strong to make things fall right, ease it back to the number you actually wanted.
  • Scripts that ask for the player list now find everyone, in every kind of room — players who joined before your script's connection no longer come back as an empty list.
  • When a script inside one of your lib/ files crashes, the error now points at that file and line (lib/boom.js:3) instead of blaming the engine — including crashes after an await inside the lib.
  • depthTest: false now draws over things instead of disappearing — a material with depth testing off composites on top of the world, and Savi's eye finally sees it exactly like your players do.
  • Materials gained renderOrder (default 0, higher draws later) for explicit draw order among always-on-top and transparent surfaces — eyes through hair is two properties: hair stays normal, the eye patch wears { depthTest: false, renderOrder: 1 }. Also the recipe for UI-over-world quads, x-ray outlines, and decals that must win. renderOrder is an integer in ±1000 — fractional values truncate.
  • Editing one player script — or adding or removing one — no longer resets the others: summons, equipment, and timers survive saves that didn't touch them.
  • If a script stores a function in a spawned object's state, the object no longer becomes impossible to clean up with run_script — and when a save still can't be applied, the error names the exact object and component carrying the bad value.
  • Multiplayer rooms hold together better: players who stand still no longer drift out of sync for others, a page reload can't wrongly crown itself the room's simulator, and a player joining mid-game can no longer cause a chunk of the world to vanish for everyone.
  • Saves stop un-saving themselves: a script save that landed while your connection was mid-shuffle can no longer be silently rewritten back to its old bytes over and over. If a save genuinely loses to someone else's newer edit, the loss is recorded instead of hidden.
  • Deleted things stay deleted for everyone: a monster or object killed while one player's connection was catching up no longer lives forever on that player's screen.
  • Spinning and moving arenas built from static parts no longer risk killing physics. Rotating a parent full of static walls, floors, and cover no longer rebuilds every part's collider each tick — the parts just move. Big rotating rings, carousels, and script-driven set pieces run much cheaper and no longer degrade into the "physics died, reload the page" state.
  • Standing on a moving platform no longer flips your position readings. When the game auto-attaches a player to a platform they're riding, scripts that read the player's feetPosition keep getting world coordinates — camera rigs, aim code, and referees keep working mid-ride.
  • Script errors from run_script now point at your own line (run_script:2) instead of dumping engine file paths, and engine hiccups during run_script say what happened in one line — "your code didn't cause this" — instead of dumping engine internals.
  • Script-built pieces (fences, pickaxes, generated props) made on an older engine version no longer silently stop being drawn after the room moves to a newer one — they rebuild themselves automatically.
  • Multiplayer trigger zones now count every player's enters and exits — visitors' crossings no longer vanish, and zone counters stay the same on every screen instead of silently forking per viewer. Attached models can now wear their parent's animations: attachment: { skin: true } makes a child model deform with the parent's rig — a jacket that bends with the body, armor that rides the run cycle — with bones matched by name (case-insensitive, mixamorig prefix tolerated). Joints that match no parent bone hold their rest pose and Savi gets a diagnostic naming them, with a skin: { map: { ... } } escape hatch for rigs whose bones are named differently.
  • Skinned characters can read your baked vertex data: per-vertex masks work on rigged bodies. vertexColor() delivers baked COLOR_0 on skinned draws, uv(1) delivers a second UV set (and refuses loudly when the model doesn't carry one — never silently serving uv(0)), and the new attribute('normal') / attribute('position') read the raw rest-pose streams the pose can't bend — the clean channel for masked anime outlines.
  • A material that can't run now says why: the fallback to plain Std/PBR lands a one-line verdict in getLogs naming exactly what this model's geometry doesn't deliver, and the park stays visible in debug dumps for as long as it holds.
  • Very large worlds with thousands of scripted meshes no longer black-screen when moving between places — the world streams in steadily instead of the game running out of memory and freezing until a reload.
  • Worlds that reuse the same generated mesh across many objects (module-built forests, repeated props) load noticeably faster — the mesh crosses to the renderer once instead of once per object.
  • Spot light cone angles are now plain degrees like every other angle — ask for a 62° cone and you get a 62° cone.
  • Scripts that contain escaped backticks inside strings or comments (markdown docs, code samples) no longer get silently corrupted at load time — legal code compiles exactly as written.
  • Debug reports now capture what your screen was actually drawing, not just what the game world contained — "my friend is invisible" bugs get found much faster.
  • Bug reports you send us now show whether your character actually entered the world and whether your controls were reaching the game — so we can find the real problem much faster.
  • Games download and boot faster: the engine's client code shed about a fifth of its total size.
  • There's now an "Exit god mode" button on screen, next to "Hide controls" — no more being stuck flying because nobody told you about Tab+G.
  • If your game binds "hold Ctrl" (a crouch, a sprint) in a WASD game, Savi now gets a warning that pressing W while holding Ctrl closes the player's browser tab — so the binding gets fixed before a player hits it.
  • Scheduled game logic — crons for day/night cycles, waves, restocks — now actually runs in your published game while players are in it. It used to run only in the dev room while you were building.
  • Updating one terrain material with a partial update no longer silently wipes the others from the save — the world that reloads is the world you saw.
  • Models and images that finish generating while Savi builds without an open game tab now appear in the world as soon as they are ready, instead of staying invisible to that session until a reload.
  • 2D characters with many animations load in more reliably when the art kitchen is busy: the game now waits exactly as long as the server asks before re-requesting sprites, so stuck animations heal sooner instead of hammering the queue.
  • Fixed a rare freeze where a game tab kept its connection but silently stopped taking in edits and saves — and screenshots still claimed everything was current. The freeze class is gone, and Savi's tools now report exactly which version of the world the screen is really showing.
  • Digging or placing single blocks in big voxel worlds is much lighter on physics — one block edit no longer rebuilds the whole chunk's collision.
  • Mining and building in really big voxel worlds feels immediate now — block edits show up in roughly a third of the time, with no hitch as the world grows.
  • Voxel blocks with custom shapes — stairs, slabs, anything a material's shape hook builds — are solid now. Players used to walk or fall straight through them whenever the chunk also held plain blocks.
›technical notes

The a3dc gait shim converts parent-local deltas to meters through the parent's composed WorldScale. The seated-rider gait fix (census 449f52a6) measures a parented avatar's speed from its LOCAL feet-position delta, but local deltas are parent-frame units — the hierarchy compose multiplies them by the parent's scale before they become world positions (hierarchy-transform.ts) — so a walk across a ×3 carrier read a third of its real speed (idle plus foot-slide) and a walk on a ×0.25 prop read ×4 (a phantom run). The parent's WorldScale is already the composed product, so nested parents need nothing extra; rotation preserves magnitude, so scaling the delta components before the hypot is exact.

  • Deterministic AHardwareBuffer import-refusal device losses are classified and routed to the cant-run story (ledger 2630, dump 508b170a). On flag-forced-WebGPU Android below Chrome's floor, the first CDN texture upload makes Chrome's zero-copy ImageBitmap import ask the driver to import an AHardwareBuffer (vkGetPhysicalDeviceImageFormatProperties → VK_ERROR_FORMAT_NOT_SUPPORTED) and Dawn escalates to a device loss seconds AFTER ready — which the DEVICE_LOST class spoke as the transient "Something hiccuped — reloading brings it back", reproducing forever. The renderer worker's device-lost funnel (postDeviceLostOnce) now matches the import-refusal certificate — the vocabulary conjunction of the format refusal and the AHardwareBuffer/external-memory import context, never a broad regex — and stamps deterministicUnsupported on the device-lost post. The host (browser-init.ts) routes the stamp to the honest cant-run copy the adapter-null face of this same population already gets (ledger 2352: coreUnsupportedPlayerMessage — the Android sentence offers the Desktop-site toggle; a desktop UA gets the another-device sentence) and carries webgpu-core-unsupported as the terminal condition, so kiln's boot-stall auto-retry stands down mechanically; pre-ready losses latch the sticky cant-run wall. Kiln's terminal-pill gate (isReloadableSessionError) learned the desktop face's "reopen" recovery verb so that copy rides the session pill instead of the infinite "Unable to reconnect" toast. Unstamped device losses keep today's transient story byte-for-byte.

  • Apple desktops no longer open at ULTRA on identity alone (jacob-confirmed, #inventors thread 1787453884 — kevin's fanless MacBook Air booted the full ultra construction; subagent report 45fa721e). Two cures, one seam. (1) Corroborated ultra: guessLightingQualityTier treated Apple + confirmed desktop as ultra with zero capability signal — "apple" is one undifferentiated vendor string from an M1 Air to an M3 Max, and Safari reports only "Apple GPU". Ultra now requires big-chip corroboration: a Pro|Max|Ultra model suffix where the renderer string carries it (Chrome/ANGLE, WebGPU adapter info), or hardwareConcurrency ≥ 12 (base chips report 8–10, Pro/Max/Ultra 12+ — already collected in the DeviceProfile, now ridden into DeviceTierHints; on Safari's bare "Apple GPU" the core count is the load-bearing signal). Uncorroborated Apple desktops take high; the mobile branch, the iPad-masquerade guard, and every non-Apple desktop answer are byte-identical. (2) Conservative opening rung: openingRungPrior opens integrated desktop silicon (the same apple/intel/APU predicate as the #228a shadow floor) at the shadows-shorter rung (rung 2 — effects trimmed, shadows shortened, the held one-step lighting walk at construction) instead of 0, exactly the per-class prior its own comment named as a waiting tuning surface. Session 1 opens conservative, the governor's existing probe-ups restore the top in ~40s where the silicon affords it, and the measured landing owns every session after — a landing now outranks the guess in BOTH directions (a Max that measured calm at rung 0 opens there), while the software-rasterizer prior stays the one live fact no landing can shadow (61ab2157 law preserved). A player-perf pin never takes the guess prior (a guess must not walk construction below the player's choice), forces are untouched, and the F2 tier line names a prior-opened cheapened boot "opening prior" instead of lying "ladder landing". Net: an Air's first session constructs medium lighting (iPhone-class froxel/shadow work on iPhone-class silicon) inside desktop world budgets; a Chrome M3 Max still detects ultra and re-earns rung 0 in its first minute; a Safari Max opens high-then-climbs (jacob's answered-by-default taste call — veto in review moves one constant). Not in scope, by charter: deeper lightingTierWalk ladder rungs (structural companion) and the piece-4 boot micro-bench.

  • Savi's free-camera captures now see sprites (ledger 2408, five faces, span 5.2.26→5.2.34). The sprite lane packs + frustum-culls its instances in the contributor's prepare, fitted to the MAIN camera, and mainDraws replays that pack — so a scene-view capture collecting from any other pose saw exactly the sprites the player happened to have on screen. The tilemap culls at collect time and the indirect lanes get the capture's own GPU cull re-dispatch (ledger 2009), which produced face 5's sharpest signature: ground tiles paint, ZERO sprite bodies, at the origin, under every pitch, with days-warm textures (Spawn Online cbf724ad; the 2d-side "terrain but no entities" face 2 and the flat-green face 1 are the same class). Savi filed the blindness as an engine fault, voided her own past free-camera findings, and creators paid for the investigations. The ledger's candidate mechanism (DrawSpriteResolved as an uncomposed client-plane overlay) is disproved: the resolved overlay rides forwardToRenderer into the same lume store both paths read — the blinder was the pack's camera fit, never overlay composition. Cure: contributors gain an optional captureRefit(frame) hook (scene.runCaptureRefit), run by the capture render after refitVisibility and before the collect; the sprite lane re-packs every world batch against the capture pose with captureFit guards on every cross-frame mutation — no cull-hysteresis writes, no motion-history retention (instances carry prev := current; presented positions are camera-dependent through parallax/camera-attach, so retaining one would fabricate a live TAAU velocity), no trail-ring touches, no idle aging, no census. The next live prepare's full pack restores the live fit by construction. Instanced sprites (draw/sprite-instances) ride the same pack and are cured with it. Known kin left named, not cured here: skinned model hordes (packHordeBatch) and scripted model/primitive visuals CPU-cull in prepare the same way — 3D capture faces of this class trace there.

  • The client-auth collide lane learns the pose-diff fast path — parked sensors stop billing MP clients (ledger 2625, client half; savi dump 94333978). The reporter's measured 13.9ms p95 (physics-collide-predicted-client, relay multiplayer) was the client-auth trigger/liquid lane, which derived every sensor's exact overlap set per tick through the rapier broadphase + precise filter — 176 auto-minted parked NPC hurtboxes re-derived on every client every tick. #13475's zero-events gate is useless here (peer transforms arrive as wire component writes, no local event stream), so the carry-forward gates on OBSERVED state instead, three layers: (1) the structural clock — runtime.colliderSetEpoch plus runtime identity, bumped by every body/collider create/remove/rebuild/solidity mutation (which covers static moves — statics only move by dispose+rebuild — scale-signature collider rebuilds, place moves, and runtime recoveries); an epoch delta re-derives the whole place once. (2) the moved-mobile set — quantized body-pose diff over a hook-maintained kinematic∪dynamic index (clientDispatchPose), observing the pose itself so every write seam (controller, script teleport, wire sync, asleep teleport) is covered at once; a moved body marks dirty its own sensor, every sensor its reverse broadphase hits (the same intersectionsWithShape the forward candidate discovery runs — intersection is symmetric, so it finds exactly the sensors whose own query would find the mover), and every sensor whose previous pair row contains it (the exit side). (3) the verified token (clientSensorVerified) — carried rows must descend from a pass that actually collected the sensor; TomeBehaviorRef/DrawMaterial/PhysicsBodyConfig/PlaceMembership events, despawn, collection skips, tracking rebuilds, and projection-reset reseeds all drop it. A PhysicsBodyConfig write that only retunes activeCollisionTypes mutates colliders in place with NO epoch bump and no pose delta — the component hook is the one gate for that class, which is why the hooks stay wired alongside the epoch. Dirty sensors run the canonical full derive; clean sensors carry their previous row (liveness-filtered) verbatim, so a skip is only ever taken when the carried row equals what the canonical derive would produce — favor-the-defender: a false skip is a defect, a false re-derive is only cost. Census bench (176 parked npc hurtboxes + 2900 statics, client lane, vitest module graph): parked steady tick 562.6µs → 169.8µs mean (p95 809.7µs → 213.9µs), moving-player tick 524.3µs → 179µs mean (p95 562µs → 206.5µs). The onCollide pair sweep of the same system (scoped to locally simulated collide targets, narrow-phase reads only) is untouched; mantle places already collect from the event book and are untouched.

  • Bug-report follow-ups from the adversarial review of #13462. (1) Rail-tier incidents now carry the sim-owning SERVER world's timing attribution: startEngineBugGather attaches simTiming: snapshotSimTimingAttribution(world) to the incident's server context beside connectedClients. server-runtime installs the provider on that world, and that tier's client dumps honestly ship no script rows (behavior sampling is authority-side), so before this the one lane whose sim burns server-side was the one lane whose incidents could not name a script burner — #13462's "reports name their burner" is complete for the rail tier only as of this change. Kiln's intake passes the block through whole (boundRealmContext returns the server context untouched under the 256KiB budget; over budget the stub keeps the discriminating head, same as before). (2) The sim-attribution wire sanitizer no longer mints non-finites from finite hostile inputs: round3(1e306) overflowed to Infinity, which JSON.stringify laundered into {"totalMs": null} in the stored incident — violating the sanitizer's own non-finite-drop rule. Post-round non-finites now drop the row whole, and ticks clamps to the producer ring's 45-tick capacity (Math.trunc(1e308) previously stored as-is).

  • Engine bug reports now carry the sim's own timing attribution (ledger 2623 fix 1). getSimAttribution — the per-system/per-script 45-tick ring, bounded at top-12 systems + top-8 scripts with "(other)" folds — already rode kiln debug-dumps as diagnostics.simTiming, but was absent from the client dump api.reportEngineBug gathers, which is exactly where perf-shaped filings need it: the 2623 hub simmed at ~93ms/tick where the local repro of the same content costs ~3ms, the dig exonerated physics/static-sync/triggers by measurement, and naming the actual burner still needed a live-session instrument Savi's own filing lane didn't ship (she named the gap herself: "physicsProfiler is not script-reachable"). buildLocalEngineBugDump now reads the SimAttributionSnapshotProviderResource the sim-loop-owning runtime installs (the perf-attribution-mirror route — no runtime handle) and attaches the snapshot as dump.simTiming, so every lane that ships a client dump carries it: multiplayer gather answers, singleplayer forward pre-attach, and the relay-rpc self-dump (the 2623 creator's exact client-auth relay-host shape). Absent provider, empty ring, or a throwing provider degrade to null — a dump never dies on its own instrument. parseEngineBugClientDump re-validates the untrusted wire block: kind allowlist, markup strip, non-finite/negative drop, 3-decimal re-rounding, and an entry ceiling equal to the producer's own cut, so a hostile flood cannot bloat the incident store.

  • fx effect(ctx) carries ctx.device — the materials precedent extended (r-7418cbf1, API pass B). .fx.js effects now receive the SAME session-frozen ScriptDeviceFacts object ({ class, coarse }, device-perf.ts scriptDeviceFacts()) that scripted materials and looks already read, so an effect can author a cheaper phone variant in plain JS (const N = ctx.device.class === "mobile" ? 40 : 100) instead of the only previously-expressible fix — degrading desktop to what a phone survives. The spawn site could never substitute: spawnFx params replicate, so a value chosen from the spawning client's device would wrongly scale the effect on every other screen; ctx.device is per-client by construction because fx compile is client-realm (fx-compile.ts never touches authoritative state). Plumbing is a type change on CompiledFxEffect["effect"] plus the three call sites (fx-compile, fx-reap's completion derivation, getFxState). The one contract edge is documented rather than machined: the reap completion bound is computed with the LOCAL device's facts — and the reaper is whichever machine simulates the entity, possibly a phone — so lifetimes/windows never branch on ctx.device (counts, sizes, rates, populations branch freely — they don't feed the bound); a device-branched lifetime gets other classes' programs truncated or overheld by the reaper's own branch. getFxState now honestly reports the stats of the program THIS client renders. Kin fence: ledger 1469 (behaviors want device signals) is a DIFFERENT safety shape — behaviors replicate and need a deterministic-safe design — and does not ride this diff.

  • The god-mode panel scroll grant follows the content, not just the render traffic (ledger 2618 follow-up, adversarial review of #13451). The scroll-on-overflow grant landed in #13451 re-derived only after applied renders and rect changes — content that grew WITHOUT a render (an <img> decode landing after the morph, a web-font swap, an authored height animation) left the container pointer-events:none with overflowing content: the original 2618 symptom in a timing costume, unreachable below the fold until the next render happened to arrive. The reverse half-state held a stale grant (rect-wide click capture) over a panel whose content had shrunk back inside the rect. One ResizeObserver over the container's direct children (the container itself is pinned by the slot rect and never resizes) re-derives the grant on any layout change; re-observation rides afterRenderApplied because a morph can replace the children. jsdom has no ResizeObserver — there the seam degrades to render-driven re-derivation exactly as before. Also pins the frame-side half of the ledger-1883 chain the fix leans on: a wheel over the granted overflowing container forwards overUi:true (the verdict input-passthrough's ring/transport drops key on), previously unreachable because the container never took pointer events.

  • God-mode config panels bound to the viewport and scroll on overflow — clipped fields are reachable again (ledger 2618, jesse's report; second symptom r-938c715a). The creator frame positions the authored panel container inside the rail's measured slot rect (chrome.panelBox), but the container shipped overflow: hidden from construction — authored panel content bigger than the rect on EITHER axis (schema panels with paragraph-length field docs past the fold, a weapon-tab strip wider than the w-80 column) was clipped dead, with no scroll affordance anywhere: the container is a pointer-events:none pass-through plane by input Law A, so even a scrollbar had nothing to grab. Now panel mode scrolls instead of clipping: applyPanelBox flips the box to overflow: auto, and while content actually overflows the container takes pointer events so the wheel and scrollbar reach it — the over-UI wheel copy is already dropped host-side (ledger 1883), so scrolling the panel never zooms the god camera, and an overflowing panel covers its whole rect, so the grant steals no world clicks the panel isn't visibly occluding. A panel that fits keeps the pass-through plane byte-exactly: world clicks under the slot's unused remainder stay the world's. The grant re-derives after every applied render (content grows/shrinks) and every rect change. Extent (never position — kin row 2132's lane) additionally clamps to the frame viewport, so a rect measured against a stale viewport cannot strand part of the box past an edge, beyond even the scrollbar's reach.

  • The tier truth source is measured now, not guessed (perf-program piece 4, pulled forward — jacob's commission, #inventors 1787453884). The lighting-tier identity table graded GPUs by NAME (lighting-quality.ts): every Apple-silicon desktop — a fanless M1 Air and a 38-core M3 Max are byte-identical adapter strings — opened at ultra construction (2048-light froxel grid, ~3.2M scene pixels) while an iPhone with the same raw GPU power got honest medium. Kiln now runs a bounded (~100–200ms budget) WebGPU micro-bench on the PARENT page, in parallel with page load and once per device (verdict cached in kiln localStorage, keyed adapter+resolution, 30d staleness horizon — a cached verdict costs zero boot ms), approximating the tier-scaled cost drivers: a 256-light shading pass at session resolution, a 2048² shadow-atlas-shaped instanced depth draw, and a bloom-shaped blur pyramid; cost read via timestamp-query where granted, wall-clock where not (Safari). The measured median frame cost maps through one band table (data, not scattered ifs) to a tier, posted down as parent:gpu-bench (the savi-todos echo pattern) and folded at the session-parameters seam (tier-override.ts gpuBenchTier): the bench OUTRANKS the identity guess; the persisted ladder landing's boot cuts, player pin/creator ceiling, and F2 forces all still outrank the bench. No verdict (old parents, direct iframe loads, probe failed) = the identity guess exactly as today. Thermal honesty is a feature: a hot Air measures hot and opens cheap; the governor still walks rungs live either way.

  • Heightmap terrain reads authored lodRanges and verticalRange — def plumbing parity with voxel (API pass A; r-d2503990, r-1c0ec711, ledger 2617). The heightmap def-build read installed-or-default where voxel reads the authored spec, so a compact world could not pull its streaming horizon in, an observer world could not push it out, and a deep world could not author past the default −32 floor. Both keys now land on TerrainHeightmapDef (types + tome-schemas, taught in the heightmap-terrain skill) with voxel's exact precedence chain — authored > existing-same-kind > default — leaving the tilemap→heightmap flip guard's law (incident 6ac4bd05) untouched. lodRanges keeps voxel's filter (finite, positive) plus the heightmap ≥2 band law; a bad authored verticalRange fails the install loudly through the existing place-named report. Both keys join the heightmap terrain signature (both variants, conditional on presence) so an authored change actually re-applies while keyless specs keep byte-identical signatures and revisions — no reinstall or remesh for the existing world population; the voxel signature gains the same conditional lodRanges membership (an authored voxel lodRanges change never re-applied either). Downstream needed zero design: the standard streaming tier IS the authored bands and extended device tiers scale from the last one (engine/features/terrain/streaming.ts).

  • verticalRange min == max is the flat-sheet contract (ledger 1502). validateTerrainConfig accepts min ≤ max: a min == max heightmap is a deliberate dead-flat plane at exactly that height (every span consumer already guards the zero span); inverted ranges keep refusing loudly. Landed as its own commit so the contract change reverts independently.

  • identify_object names the 2d-band occlusion — drawn-but-covered stops reading as rendered-and-visible (ledger 2626 honesty half). In any non-"3d" dimension, tilemaps and sprites replace their clip-Z with an absolute near-NDC band depth (sprite-sort-key.ts band [0.001, 0.01]; the tilemap pinned at the band floor, tilemaps/shader.ts finalZ = sortDepth * clip.w with depth-write on) while primitives/models keep physical clip-Z — so wherever a painted tile lands on screen it draws over EVERY depth-tested mesh pixel behind it, and a mesh pixel can only clear the 2d-top ground band's horizon when its world point sits above the camera's eye level (the dig's mechanized predicate: the filed "vanish threshold" was exactly camera eye Y crossing the box top, not pitch). identify answered "rendered/solid" for such a mesh, which is what sent Savi hunting a phantom render bug at the creator's expense. Now sceneIdentify annotates matches with occlusion: "2d-band" plus one teach note when ALL exact gates hold: 2d-top scene (the tilemaps store's live dimension), a primitive/model match (the physical-depth lanes) not routed to the overlay lane (authored depthTest: false draws after all depth-tested content and is never band-covered — isOverlayPrimitive/isOverlayModel), the whole AABB strictly below the query camera's eye level with the eye above the tile plane (below/at y = 0 the plane fills the far side of the shared horizon and can cover nothing below eye), and painted tilemap cells resident inside the match's projected tile-plane footprint — the AABB corners cast from the eye onto the y = 0 plane, painted cells scanned only in chunks intersecting that rect (anyPaintedTilemapTileInRect; sim-authored cells, texture residency is presentation) — never a global paint scan, which convicted an object standing over unpainted ground because a test patch was painted in a far corner. The screen lane reads the camera the pick actually cast through (a request-supplied look camera included), the other lanes the live frame's. Precision law: exactly which footprint pixels tiles land on is view-dependent, so the note stays conditional ("wherever the tile layer lands on screen") and the verdict never fires above eye level, without painted tiles under the footprint, in "3d", in "2d-side", or on matches with no drawn pixels — a false occlusion claim would teach the wrong lesson in the opposite direction. 2d-side is deliberately out of scope: its tilemap is a vertical backdrop and eye level says nothing about coverage there. Vocabulary, not pipeline: a verdict field + sentence on the existing identify surface; no rendering change, no new tool. The chat tool passes the field through enrichment untouched and its description teaches the one line; engines predating the field simply never send it — absence proves nothing.

  • The join-lifecycle contract: arrival facts on the player entity + api.enterRoom(roomId) (API pass C; #13471, follow-ups #13483). SessionArrival component stamped on the player entity in the same write as the relay self-spawn, so the player's own onSpawn reads api.launchParams settled — the game page's own query params, packed by kiln player surfaces as one iframe param (?launchParams=<json>, auth-door + dunder keys stripped, 2KB cap both ends), decoded on worker.mount, stamped at self-spawn. Owner-scoped and untrusted by contract (URL surface — the game validates). The kiln auth door carries live query params minus ?auth= through the OAuth round trip, so a ?apt=/?code= QR launch link survives login. Named tier gap: the kernel/container-tier spawnPlayer does not thread arrival — api.launchParams reads undefined there (never {}), and the doc says so. api.enterRoom(roomId) is a directed disconnect-and-rejoin whose target is the HINT: shape-validated (shared gateRoutedRoomId law, tome/room-routing.ts), session-class-validated (builder shell refuses — the host never travels; sink-less realms refuse loudly), returns a dispatch ACK (never an arrival receipt), and the page host runs the routed re-pick with requestedRoomId=target — pickRoom still owns the door, no engine reservation. Follow-ups (#13483): a same-room dispatch is an honest no-op ack (coordinator resets now ride the runtime's own fresh-connection edge, so 'reset' and 'a connection that will re-welcome' share one fate across every lane); racing dispatches are serialized through a generation token (createRoomSwitchConnectDispatch — every dispatch supersedes the parked one, and a dispatch back to the current room after teardown latched is a real reconnect, never a silent stay).

  • A "changed nothing" patch verdict can no longer drop a write from the durable batch (ledger 2609 P2, the Cambrian Explosion app — ravi's 08-22 struggle-hour audit W2). The spec-doc patch verbs (patchTerrain and the mark/material verbs, patchPlayer, patchCamera, patchInputs, patchGodMode, patchUi, patchEngine, patchRouting, updateBuyable, updateJob, updatePlace) gated their mutation RECORDER on the live-mirror no-op verdict (ledger 2551). But "identical to the live mirror" is not "identical to the durable doc": a persist that dies after its live apply (a wedged room, a lost verdict, a 409'd sibling batch caught mid-resync) leaves the two diverged, and the creator's recovery re-run is exactly the write that reads "identical" — the gate dropped it from the durable batch while the warning claimed idempotence. A dropped write misreported as a no-op is the kernel's cardinal sin (told-success over a lost write): the specimen's patchTerrain in a multi-patch script reported "patch applied but changed nothing — the merged terrain is identical to the current spec" while the saved head still carried flat-starter-terrain.js, and Savi wrote "terrain patches silently no-op" into her memory. Now exec-lane (run_script) no-op patches RECORD anyway (warnSpecPatchNoOp, object-api.ts), and the durable lane owns the idempotence verdict — kiln's mutations route already serialize-compares the folded doc against the pre-apply head and answers updated:false with no version mint when nothing truly changed (dig 535afdad), so a truly idempotent re-run still mints nothing. This is the posture patchAtmosphere has carried all along ("deliberately UNGATED by the no-op verdict") and the same refusal-to-trust-live-equality the state writers encode in durableMirrorVetoesStateSuppression. The 2551 suppression stays where its churn lived: LIVE-world writers (per-tick behavior withPersistence riding relay-spec-save) and instance-row targets (whose mutations never enter the durable batch — recording a row no-op would only mint a false "dropped: instance-place" receipt). The no-op warning still fires in the exec lane, reworded to name the recording ("The patch was still recorded for the durable save") so an idempotence claim can never again be read over a dropped write.

  • Parked kinematic sensors take the zero-event fast path (ledger 2625, savi dump 94333978). The server trigger pass's cheap path (carry-forward + moved-kinematic reconcile) refused every kinematic sensor outright, so an auto-minted NPC hurtbox (interpreter's props.npc ⇒ kinematic trigger capsule) standing still with nothing near it paid getCandidateEntitiesForSensor (a rapier shape query) plus the nested per-collider intersection loop every tick — 176 of them in the reporting world. Compounding it, hasMovedKinematicBody read "no pre-step capture ⇒ moved", and sleeping bodies skip the capture, so parked kinematics never drained from the moved set. Both mechanisms replaced by one honest primitive: kinematicDispatchPose, the quantized pose each kinematic held at its last verified dispatch pass. Moved = pose differs from the record (covers controller step motion, pre-step teleports, teleports that ride a same-sync sleep re-assert, and in-place rotation — kinematics move by transform writes, which land before the pre-step capture, so the old capture compare could not see them); no record = never verified = moved for exactly one pass (fresh bodies, tracking rebuilds, config changes re-derive their standing overlaps without trusting rapier to have reported them). Quiet kinematic sensors then qualify for the fast path like any other sensor: edges arrive as rapier events (fired same-step for velocity/controller motion even against a sleeping sensor body) or through reconcile (moved kinematics re-tested per quiet sensor). Census-scene bench (176 parked npc hurtboxes + 2900 statics, server dispatch): steady tick 639µs → 219µs mean (p95 738µs → 361µs), moving-player tick 610µs → 292µs mean. Named residual, disclosed: a setTranslation teleport of a NON-kinematic body into a quiet kinematic sensor now fires enter one tick late (rapier reports teleport pair-creation one step late — static sensors have carried this exact latency all along); velocity, controller, and kinematic movers stay same-tick. The client-auth trigger lane (physics-collide-predicted-client, the lane the reporting multiplayer world actually measured at 13.9ms p95) re-derives every sensor per tick by design and is untouched — its own fast path is a separate design question on the ledger row.

  • The spawn/teleport fall window dies wherever the height model answers (ledger 2620/2554 — spawnster dumps 3256a747/3e3833dc, leonia dc6cb9d1, dig 9bd6d952). The steady-state provisional-floor sweep (ledger 2012) runs in the terrain build system at netIngest order 40, but both edges where a body first goes physics-live in a place land later in the same tick — the boot unpause flips at physics/simulation-state (simulation order 5) and enterPlace arrivals write membership/position during behavior update (simulation order 100) — so the body's first simulated tick (physics/step, order 9999) always ran before the sweep saw the new position, and under a melted sim that window was a real fall into the sink-rescue flicker (skipark's whole chunk set held zero colliders at a mid-session teleport; the per-place collider gate covers a cold place for at most 90 ticks and the boot gate only first-unpause). The new gate (ensureProvisionalSupportFloorsAtPlaceEntry) runs at the step edge itself — stepPlacePhysics/stepMantlePlaceGated call it before their static-collider sync, client-side only — so a local player's first simulated tick in a place synthesizes and realizes the existing provisional heightfield floor (res² = 65×65 = 4,225 height-model samples, the sweep's own few-ms cost) in the same synchronous flow, strictly before the first step, whichever path made the body live: boot unpause, per-place gate open or force-open, enterPlace self-apply, or a forwarded travel landing via projection reset. One consumed-edge probe per local player per place entry; the install stays out of collider-readiness bookkeeping, so every gate still waits for the real build exactly as before, and the first real chunk build replaces the floor through the ordinary signature-change rebuild. Where the model cannot answer (no terrain definition — the author's void, 6f0ee6cc17/ledger 1773), no ground is invented and the edge's one refusal hands retries back to the sweep's throttled cadence. The steady-state sweep and the 2012 backstop are untouched. playerTemplateWantsDefaultAvatar (the one predicate every default-avatar dress lane keys on — spawn, spec re-apply live-rewrite, relay self-dress, and the ledger-1496 fallback-dress reads) now treats player-template visible: false as declared invisibility intent, joining animated3DCharacter: false as the other spelling of "an invisible body is intentional" (r-ee9b3cd1, API pass C3). A template that says invisible is never dressed in the profile avatar and never draws the "renders NOTHING" warns — the invisibility is authored, not an accident. The helper class (the summoned builder body) rides the identical player-template data plane through configurePlayerEntity, so it inherits the game's law by construction — spec-level and applied at spawn, which covers the 2557 wedge windows where behaviors never run to park the body. The live model-clear seam (restorePlayerDefaultAvatarIfBare) honors the same declaration: model: null on a visible:false template ends bare by intent instead of re-dressing the avatar. Zero new keys; declined builderBody: hidden (a second visibility word that can disagree with the first). Templates without explicit invisibility keep the ledger-936 fallback byte-for-byte.

  • A first-hosting restate can no longer permanently delete the journal tail of a long-idle unhosted room (the journal-death conviction — seele's thread 1540585245345058886; dig reports 08778e74/fd226797, ledger 2311). The convicted race, source-traced end to end: a solo rejoiner's election folds at wire speed (runtime-worker noteSequencedFrame at frame receipt → writeHostsTable) while the same catchup applies through the budgeted ingress drain (netcode INGRESS_DRAIN_BUDGET_MS, 8ms/tick min 128 rows — a 1.49M-row room takes minutes), and the first-hosting full statement (relay-world-restate fullOwedPlaces — the keyless-journal self-heal, which skips the turnover debounce by design) composed from that prefix-applied world and latched one-shot. The prefix statement converts only the applied prefix to keyed heads; the next checkpoint (every ordinary spec save plants one; self-compaction at 2048 rows) moves C past the unstated keyless tail and truncateThrough physically deletes it — the field specimen's contiguous journal-order tail death (indices 156–194 + a 108/108 later batch, whole entity classes gone on every future bootstrap). The cure is the third settled-fact in the family the sweep already holds on (defer-without-consuming, retried per tick): relayCatchupApplySettled on the transport, composed beside relayHostRestateSettled (the §13.5 cell-window hold) and relayReplaceClaimSettled (the replace-claim hold) — TRUE only once this socket generation's wire session reached LIVE (every catchup row at least queued on the ingress; the session owns that edge, reset at each live flip) AND the ingress fully-applied edge (noteIngressApplied, already the transport's post-apply seam) fired at least once after it. Three seams, family-exact: the wire session owns the fact (catchupApplySettled / noteWorldIngressApplied), room-runtime feeds and delegates it, the netcode compose adds the conjunct. The only behavior change is WHEN the statement composes: same timing for healthy small rooms (elections take ticks; the fact is long true by seat time — v1 preserved, pinned e2e), exactly-as-late-as-needed for deep catchups. Bonus coverage from the same state gate: a turnover whose settle debounce matures mid-outage no longer restates a pre-catchup world (the fact is false short of live). Turnover keeps its 300-tick debounce; the fact resets per socket generation. Room-switch dispatch law 4 (review of #13483, F-1): a transport.connect dispatch landing while a dispatcher-owned graceful-teardown settle window is still running now RE-PARKS behind it instead of touching the socket. The old fall-through's immediate connect (double-clicked enterRoom(B)) and law 3's immediate closeRoomSocket (an A→B→A bounce inside the window) truncated the departing room's flush — departure-hook storage saves still sitting as api.job rows died with the socket, the settle machinery's own named "one lost save" residual. Both lanes now wait the flush out; the #13483 generation token already makes stacked parked callbacks safe (only the newest connects; law 3's close moves to fire time). Bounded, never a wedge: the settle poller (createTeardownHookSettler, extracted for the deadline-path pin) ALWAYS fires within RELAY_TEARDOWN_HOOK_SETTLE_MS (4s) even when the queues never drain, so a genuinely stuck window completes the switch with truncation — today's residual — within one settle deadline of the last dispatch. The re-park keys on the dispatcher's parked-window counter, not teardownBegun (beginGracefulTeardown latches tornDown even when it holds nothing, and hookless/kernel switches must stay synchronous); the truncation residual is now reachable only through the deadline pass.

  • The sandbox Math shim's variadic members (max/min/hypot) carry fixed-arity fast paths — a creator max/min clamp is back within ~2x of native, from ~19x (terrain-compare finding, subagent report 301246dc §3–4). Every compiled behavior script and generator binds Math to the deterministic shim (tome/numeric-runtime.ts), and its rest+map+spread wrapper priced the hottest spec-exact members absurdly: a Math.max(0, Math.min(1, t)) clamp cost 153 ns vs 8 ns native (bun 1.3.14), and a creator generator with a 60-seg track-distance loop — 60×(clamp+hypot) per terrain heightAt call, the go-kart shape — ran ~7x slower through the sandbox than the identical source under native Math. In practice virtually every call is 2-arg with arguments that are already numbers, where the wrapper's toNumber is the identity — so a typeof === "number" guard now hands the SAME kernel the SAME argument list with zero allocation: clamp 153 → 17 ns (~2x native), 2-arg max/min ~1.9x native, hypot faster than native (the deterministic kernel beats native hypot's overflow-safe path), the track-loop shape 14,424 → 1,511 ns/call (~9.5x). Every other call shape — other arities, coercing arguments — falls through to exactly the old wrapped shape, so coercion order, arity semantics (max(x) vs max(x, undefined)), and the f32 result law are untouched; bit-identity is fuzz-pinned against the wrapped reference across the NaN/±Infinity/-0/string/valueOf zoo, and the detector was proven red on a hand-broken fast path. atan2 and pow need no fast path — their wrappers were already fixed 2-arg and their cost is the deterministic software kernel, which the cross-realm bit-identity law does not allow bypassing. The sandbox's security posture is unchanged: the shim still never exposes native Math members, the table stays frozen, and Math.random's seeded-RNG interception is untouched.

  • The two burst eaters behind god-mode movement chop during wisp builds are bounded (ledger 2537's follow-up face; enfeul's thread 1540911966682484776, dump 52db700e, dig bf35e041). The receipted specimen: 4 wisps building at ~1,100 journal rows/s ran the client sim at 7.5Hz for 1.4s — 11 consecutive ticks at 127–141ms — and movement integrates on sim ticks while camera look is renderer-authoritative, hence "movement chops, look stays smooth". The profiler named the eaters: tome/client-realm-scope-reap 78ms p95/tick and god-mode-editor 43.6ms p95/tick. Two bounds, shipped together. (a) The scope-reap's all-dirty re-judge — its "rare by construction" edges (coordination-table writes, control-set changes such as a god-mode toggle) fired on consecutive ticks under the burst, each re-judging the whole candidate population, up to 10 cascade rounds in ONE tick — now runs at most SCOPE_REAP_JUDGE_BUDGET (128) verdicts per tick, from the dump's own arithmetic: the pre-edge-driven sweep priced a cold verdict walk at ~36µs (470ms/13k), so 128 ≈ 4.6ms, under a 5ms bound inside the 33ms tick. The remainder keeps in the dirty set and a fresh all-dirty batch front-loads it, so repeated storms rotate through the population instead of re-judging the head; verdicts, edges, and the reap are unchanged — everything still reaps, spread over ceil(N/128) ticks (the reap is orphan-cosmetics cleanup; a few ticks of latency is free). Bench at dump scale (10k decor + 2,000 candidates, a table write every tick): p95 2.3ms/tick, max 128 judged, handback drains all 2,000 in 16 ticks. (b) The god-mode editor's presence walk (syncGodModeEditorVisuals, self-documented O(presence-bearing entities) per tick — markers for every invisible authored thing, in a world four wisps are stuffing with lights, emitters, and scripted objects) now runs on a 3-tick stride while god mode is held: a marker or guide-state change landing up to two ticks (≤67ms at 30Hz) late is invisible; a 43ms tick is not. Entry still walks immediately, and leaving god mode never waits — the idle sweep reaps markers the tick the last creator leaves. Red-first both: a table-write storm over 1,000 candidates judged ≤128/tick (red: 1,000 in one tick) with the handback still reaping every one; a mid-hold spawn's marker landing on the stride tick, not the next (red: next tick), with the leave-sweep immediate. Named remainder, not in scope here: restatement-fold application still lands 4096-row batches in one tick (the sustained 12–17ms elevation) — that amortization is its own charter (2537 (c)).

  • Scripted primitives gain sub-part identity — identify names the block under the ray (ledger 2627; the enfeul dig 5495837e). A primitive.kind:"scripted" entity was one anonymous blob: identify_object resolved id + bounds + hitPoint at entity granularity and nothing below was addressable — Savi had a precise hitPoint on a 23 m tower and no name for the slab bar under it, rasterized raycast occupancy in ASCII, and guessed the emitting generator call wrong twice (24 minutes, 3 asks, 0 fixes). Now the ray and screen lanes of identify_object, for hits on bespoke (scripted/custom) primitives, also answer block: { ordinal, count, localBounds } — the emitted block under the ray. A block is one disjoint island of the mesh (vertices welded at 1e-4, engine/features/bespoke-geometry/blocks.ts), numbered in emission order; the invariant is stated and tested: a derive is a pure function of its recipe, so the same generator output always yields the same ordinals and bounds, on every realm. The resolve is a pure query over the already-derived bytes (renderer-side, memoized per cached mesh) — no derive-time state, no wire growth, no new tools, no generator-source parsing: localBounds lives in the generator's own local frame, so Savi matches dimensions against the source herself (the uvRect law). Representation-independent: batched-lane, oversized-pool, and scripted-material-standalone hits map the world hitPoint through the one bespoke draw compose back to local space and answer identically. Raw-three.js cost of the same answer: a raycaster intersection → sub-mesh + local bounds, ~5 lines — Savi could not get it at any price. Unlocks unasked: per-block material reasoning, "what am I standing on" inside kit buildings, collision debugging.

  • SessionArrival.resumed + api.resumed — client-asserted document continuity under the 2189 trust class (API pass C half 2; #13478). SessionArrival grows { resumed: boolean } (launchParams now optional in the value; api.launchParams still reads undefined — never {} — when the surface carried none; old stamped payloads decode resumed:false). The fact tracks DOCUMENT identity per room, never the relay's resume token: false on the document's first establishment (fresh boot, reload, room switch, every bare kernel/SP boot); true on every same-document re-establishment (wire-loss re-dial resume, F10 fresh-redial rebase, tab-back long-park). Derived client-side where the truth lives (the relay join coordinator's worldBaselined at the establishment's begin edge — the relay holds no continuity fact; the dial's resume= is the client's own claim, validated for replayability only). Stamped in the same self-spawn write launchParams rides; the two spawn-less re-establishment edges mark the lived entity through markSessionArrivalResumed — the one writer of the flip, monotonic and idempotent. Owner-scoped: never rides the self-spawn re-announce. api.resumed is always a boolean, false wherever the fact is not readable (peers, non-players, kernel-tier/SP server spawns). Typed and documented untrusted-for-authority, exactly like launchParams: games read it for feel; nothing authoritative may read it.

  • The touch-surface arbiter no longer counts its own same-name re-posts as flips — the false "inputs.touch.active is oscillating" behavior-fault dies (ledger 2494, six specimens across 5.2.29–5.2.33). touch-surface-sync keyed the posted pick on name#generation, refreshSpecCaches bumps the generation on EVERY GameSpecResource object turnover (relay catch-up bursts, restate churn, frequent saves — no creator write needed), each turnover bypassed the 33ms eval gate and re-posted the byte-same surface, and ≥4 posts inside the rolling 1s window tripped the oscillation guard — minting "make the selector a stable function of player state" against games that never authored a selector, pinned on innocent behavior scripts (scripts/player.js, os.js, turret.js, viewer-controls.js across the specimens) via the resolveBehaviorRefs attribution fallback. The guard now counts posted NAME transitions only (the thing a player feels; a same-name re-post is content delivery, never a flip) and is armed only while a compiled selector is registered — with no selector, every name change is an authored spec edit that must deliver, and no fault can fire. A registered selector implies inputs.touch.active is its authored script ref (the compiler's only touchActive source), so the fault always names the real selector script. The suppression re-arm clock keys on the name too, killing the starvation rider where every spec turnover re-armed lastDesiredChangeMs and a genuinely-edited surface couldn't deliver until writes paused a full second. Genuine A→B→A flapping still trips at the identical threshold (4 swaps / rolling 1s) with the identical fault text — the pre-existing oscillation test passes unchanged.

  • Touch sticks gain position (zone-normalized), surfaces gain symmetric, sticks gain visibility (API pass F; r-c8a1b7a0 + follow-ups r-f659b4cf / r-04c90a52). Three smallest-property keys on the authored-surface vocabulary, no pipeline: (1) TouchStickDef.position { x, y } — fixed-anchor only, normalized [0..1] within the zone pocket, lerped across the pocket with the 112px plate held fully inside (fixedStickCenter, layout.ts), so the existing safe-area/reservation-shave machinery IS the clamp and a position can never land under chrome or off the glass; the deriver publishes the resting point as DerivedAnalogRegion.center, the arbiter steers fixed sticks from it, and the chrome parks the resting plate on it. Default (absent / 0.5) is byte-identical to the old pocket-center math. Floating sticks reject it with a named fault (their placement is the finger); declined: a second inset word (two words, one meaning) and pixel units (device-dependent). (2) TouchSurfaceDef.symmetric: true — the two analog zone pockets stay congruent: levelZoneRegions takes the smaller width/height across zones and re-anchors each pocket at its own bottom corner, mirroring a reservation shave in both directions — the platform rail's right-zone shave (portrait 373×696: left pocket 292px tall, right 198px) minted an asymmetry the author could not correct. (3) TouchStickDef.visibility: "fade" | "always" — "fade" (default) is today's behavior exactly (pocket ghost until the session's first stick use); "always" keeps the resting plate whenever the chrome shows, at the chrome idle opacity (0.5, a persistent control) instead of the 0.3 invitation, never joining the first-use dismissal list. All three validate in compileTouchSurface with named invalid-value faults (out-of-range position clamps to [0..1] loudly; junk shapes drop the field); absent keys compile byte-stable with before. Not in this landing: resting-plate guide overlays (crosshair/quadrant lines, the r-04c90a52 second ask) — a presentation-surface follow-up for the pass's next round; the floating-stick re-anchor-across-room-transition defect from the original filing is a bug lane, not surface. Follow-ups from the adversarial review of #13466 (r-c67df292). F1: a runtime visible: true flip on a session-owned player whose template declares visible: false and who wears no body drawable now records a once-per-room runtime warn (the 936-rider convention) naming the permanently-bare reveal and the two honest cures — flip visible at the SPEC level (re-enters the default-avatar fallback and re-dresses) or dress the player explicitly first. Never a re-dress: the template's declaration stands. The spec-apply lane writes layerMask directly and never crosses the runtime setter, so spec-level flips stay out of the rail by construction. Ask 1: the two ledger-1496 model-write classifiers (retire-vs-retarget for a worn a3dc, and the r-10356579 profile-avatar-redress skip) now key on the new owns-a-body predicate playerTemplateAuthorsOwnBody (body visual or explicit animated3DCharacter word) instead of !playerTemplateWantsDefaultAvatar — template visible: false declares invisibility, not body ownership, so in that world a worn a3dc is script-owned (a separate model write retires it, the cdc07f2e direction) and a profile-avatar model write is the script's own embodiment step (it lands instead of being skipped with the wrong a3dc:true remedy). Bare, a3dc:false, a3dc:true, and authored-visual templates keep their pre-13466 classifier behavior byte-for-byte.

  • Window drag-resize no longer storms the GPU (ledger 2606 P1, kevin's resize-triggered device-lost; kin 1046, cal's chronic device-lost — dig d12e618e). The window resize event forwarded PER EVENT into resizeRenderer → a worker resize message → a FULL render-target reallocation (post chain, TAAU history, depth) plus swapchain churn per present — and macOS interactive drag-resize fires an event per frame, exactly the renderer's validation-burst recovery pressure (ledger #187) and, on weak GPU processes, consistent with the dead-instance wall (GPU_DEVICE_DEAD_RE). The in-page slot path (canvas-slot.ts) has settle-gated this same GPU resize since #187; the window path never got the gate. Now it has the twin (window-resize-settle.ts): resize events only arm a rAF-coalesced gate; the GPU resize fires after 8 stable (width, height, dpr) frames or a 250ms quiet window (stalled rAF); long drags get one coarse mid-transition resize per 500ms so the view tracks; CSS scales the stale buffer meanwhile — identical UX to the shipped slot behavior. DPR rides the settle signature (a monitor hop mid-drag resets stability and lands on the new ratio); the boot-time first resize stays a direct call (the first size is load-bearing); the slot path's own settle-gated calls bypass the window gate. The worker door dedupes identical (width, height, dpr) resize messages (renderer.ts resize()), so the two gates re-deriving the same surface never reallocate targets, invalidate occlusion history, or force presents for a no-op. Structural elimination: the storm class dies regardless of who kicks it — drag, layout loop, HMR.

  • addBehavior on a live entity executes — attach runs the added refs' onSpawn, detach tears their wiring down (ledger 2700). The live-entity attach lane (patchECSEntityBehavior + patchPendingSpawnBehavior's ECS half) compiled and registered the new refs and stopped; a registry entry never runs hooks by itself, so an onSpawn-shaped script attached to an existing entity never executed — the attach returned ok and nothing ran. The lane now routes through the def-sourced rebind sweep's own unit (teardownAndRerunOnSpawn), scoped to exactly the refs the call added (onSpawn arms fresh) and removed (ears/timers tear down); unchanged siblings keep their wiring. Verified in the running product (riser script attached 9s in — box turns red and rises).

  • When an arena has no host, the engine says so instead of silently eating spawns (ledger 2661, confession only — seat/lease semantics untouched). In a place whose host table names nobody, a manager's deferred spawns died sender-side with zero receipt. The deferral-abort now re-asks classifyVerdictSilence's question at the abort site and, exactly when the answer is 'unhosted', writes ONE episode-bounded getLogs line per place per window whose count moves in place ('spawn held then dropped: <place> has no host simulator … rejoining usually heals it'). The vanished ids register (bounded, evict-oldest), and getObject names the death on a miss instead of answering a bare null; run_script's exec overlay ships the ring line back in returned logs. The review hardened it: the confession reads the drop's own verdict, so false no-host claims die, and records clear on re-spawn.

  • Probes confess their gates — the ghost-hunt class dies in every lane (ledger 2653 family). identify_object and the frame-capture caption speak the sprite pack-gate verdict (a sprite culled/parked by its own gate no longer reads as a phantom render bug); primitives and models confess their gates the same way; the visibleRange tripwire guards both directions; failed texture loads confess failure instead of pleading "loading" forever. UI-layer captures gain imagesNote (fontsNote's shape) — the capture's honest image-blindness verdict, per-lane honest: the bg clause claims only blindness, the img clause keeps the earned render-for-players claim.

  • Dynamic bodies now measure authored gravity exactly at every sim tick rate (savi e599ab78). The physics write-back derived PhysicsBodyState.linVel from position deltas and re-injected it into rapier every tick as part of the F32 determinism contract. A position delta is the step's AVERAGE velocity, which under constant acceleration lags the end-of-step velocity across the solver's internal substeps — the per-tick overwrite compounded that lag into a constant gravity deficit of 1-(n+1)/2n (n = 4 rapier solver iterations × engine substeps): 37.5% lost at 60Hz, 43.75% at the 30Hz default, in 2D and 3D alike. Dynamic bodies now report and write back rapier's native linvel (the same rationale angVel already followed); kinematic bodies keep the position-delta derivation, which is the only observable velocity a motor-moved body has. Pinned by dynamic-gravity-free-fall.test.ts (free-fall dv/dt and position integral vs authored, red at 56–62% on the old write-back).

  • api.getPlayers() finds every session avatar on relay realms — peer-join self-reannounce (pid-undefined census: 14 throws, 12 apps, 30h). Place-less getPlayers promises 'searches all places' but answered [] on relay realms whose join postdated the roster: avatar creates ride keyless STATE by the fold constitution, so no drain could carry one, self-compaction drops the keyless create behind the checkpoint, and world-sync is scope-gated shut for exactly the seat that runs exec. The repair enforces the constitution's own §5 invariant as delivery: each live member re-publishes its own full create (the existing ghost-delete-defense verb, idempotent) when a new member's m:* head folds — bounded by genuine join edges. Also: getPlayers({ place: <non-string> }) teach-throws instead of silently enumerating a place named '[object Object]' and answering [].

  • Lib frames get their own names — the false engine-bug steering dies; mixed-frame engine bugs reach the logs (review of #13514, findings 1+2). The worse lie first: a required lib crashing past its own internal await carried no tagged frame (no run_script call-site frame survives the resumption), so it classified as engine_internal_error and told Savi "your code didn't cause this… flag it with api.reportEngineBug" — false incident filings for her own bug, and the landed test pinned that outcome as expected. Cured on both layers: compileModule (script-modules.ts) now appends a per-module //# sourceURL=<normalized ref> pragma after the compiled body (the same append-after trick as run_script's pragma — the last pragma wins in both VMs, so a hostile pragma inside the lib loses), and composeExecErrorStack/trimErrorStack rebase tagged lib frames past the codegen preamble (MODULE_FRAME_LINE_OFFSET = 2 new Function lines + the 2-line numeric-runtime bridge, probed live on V8 and JSC), so the crash reads at lib/boom.js:3 — her file, her line, better teaching than pre-#13514 — and classifies as hers. The verdict for what still classifies as an engine fault now appends the raw error (Raw error: TypeError: …), so the true cause survives even a misclassification. Finding 1: a REAL engine bug surfacing through her sync call (classified hers — correctly) used to strip its engine frames from the result while tome.exec.engine_internal_fault only logged frameless-script faults, leaving the engine frames NOWHERE; the exec settle now logs the full stack as tome.exec.engine_frames_in_script_failure when a VM-native crash's raw TOP frame is untagged (top-frame gating keeps creator null-derefs out of the platform log). The flipped test asserts the truth it used to pin as a lie.

  • The loose-dynamic lease arm mirrors the sensor exclusion; the stale-row heal is pinned (review of #13511, ledger 2642). isLeasableLooseDynamic / isServerLeasableLooseDynamic — the other walk into the same lease table — still admitted a dynamic-bodied sensor (physics: { body: "dynamic", trigger: true }). No honest client could mint that lease (a sensor produces no contact manifolds, so the contact trigger is blind to it, and the guarded-write door already refuses sensors), but the arm is also the ADMISSION door (relay adjudicator, kernel claim handler) and the reconcile sweep's shape test, so a forged or pre-narrowing claim was admitted and its stale row survived the sweep's not_leasable heal. Both mirrors now refuse sensor: true; NPC agents with sensor bodies stay admitted through the combat arm, so the union is unchanged for #11625's bot class. Pins: the kernel double (dynamic trigger body refused at the door, both arms, both sides of the wire; a seeded stale row drops on the reconcile sweep the next tick) and the relay double (a seeded stale sensor-zone row heals through the holder's own abandon clock — sweepCombatIdleLease refuses the sensor, the loose-dynamic abandon clock fires lease.release down the lane, every table clears, authority returns to the place host, and the re-mint dies at the shape gate). docs/combat-sim-lease.md now names the one shape that pays for the exclusion — the behavior-only trigger-bodied mover (place-host-routed writes: latency, never correctness) — and its properties.npc escape hatch.

  • depthTest:false materials draw in every capture; materials gain renderOrder (ledger 2637, ress's eyes-through-hair). Two halves, one mechanism family. THE BUG: the scene-view capture pass (Savi's eye, the view tool, every wisp measurement) executed only [opaque, alpha] and dropped the whole overlay band as "debug gizmos" — but the band carries CREATOR content (every lane routes depthTest:false materials there with always-pass depth pipelines), so an authored no-depth-test surface was invisible to every capture while identify_object honestly said "rendered" (savi measured it four ways on app fe1339ba and filed the flag itself as broken; the live player screen drew it correctly the whole time). The tooling/content conflation is now unrepresentable: overlay lines (selection boxes, god-mode beams, collider view) moved to their own gizmo bucket in MainPassDraws; the live scene pass draws [... alpha, viewmodel, overlay, gizmo]; captures draw [opaque, alpha, overlay] — the world exactly as the player sees it, instruments excluded (pixel-provenance same). THE PRIMITIVE: renderOrder (number, default 0) on standard AND scripted materials, top-level or per-part — the overlay band's defined order (DrawCommand.order, scene assembler sorts ascending, stable ties keep collection order) and a fold into the transparent band's back-to-front sort via the existing sort-order.ts renderOrder machinery (dominates view depth, exactly like sprite/text sortingOrder). Structural in batch identity across every lane (primitives lane key, oversized pool key, static/horde/character signatures, scripted shade) — ± renderOrder visuals never share a draw command. Order-0 content keeps byte-identical lane keys, signatures, and sortDepths.

  • renderOrder normalizes to integers in ±1000 at every door (review of #13505). renderOrder is structural batch identity on every lane, so an animated fractional value minted a new lane/pool/signature per unique float per frame (idle lanes dispose only after ~1min). clampRenderOrder (truncate + clamp, non-finite → 0 — the 2D clampSortOrder discipline) now runs at the spec door (normalizeMaterialSpec, which bounds the override records that signatures and scripted record keys stringify) and at the engine's value reads (resolvedRenderOrder, batchRecipeFromOverrides, the oversized bespoke-slot binding, the scripted-lane override apply). Every in-range integer keeps byte-identical ordering semantics.

  • The player lane joins the per-script teardown law (ledger 2675, keystone completion of #12090). The rebind sweep's player branch passed teardownScope: "all" on every player-behavior save — the whole composed onSpawn re-ran, every entry's ears/timers/schedule-overrides cleared, and every TomeSpawnOrigin-stamped child of every player script was marked, so any conditional spawn (a spawn-once state guard whose key survives reloads by design) skipped its re-assert and the sweep reaped the child as a "derived artifact". Now the sweep computes the player's edited refs from the old/new player.behavior lists × changedScripts (the same per-ref diff the modern-dynamics lane runs) and passes a scoped teardown: saving one player script touches only that script's wiring and children. A ref-LIST change scopes to the delta the same way — rebindReclaimedChild's removed/added split, {clear: removed ∪ edited, rerun: added ∪ edited} — so appending or removing a script is not a save that resets the siblings either (review of #13545, F1: the first cut fell back to "all" on any list change, and appending equip.js reaped summon's guard-skipped golem mid-battle). Whole-entity "all" survives exactly where no per-ref delta can be pinned — same membership, nothing edited (reorder/respell) — and for hand-composed behaviors without composedEntries. Both apply lanes carry the diff inputs; the deferred burst carry pins the old-side list at burst start like its other old-generation inputs.

  • TomeSpawnOrigin stamps JSON-safe bags, and the exec refusal names its poison (ledger 2675 shape c). Both stamp sites (fresh spawn + reclaim restamp) now run spawn props/state through the same function/symbol filter the TomeState write path uses, so a function in a spawn call's state can no longer ride the world as a component and detonate every later exec that stages it ("staged changes not serializable", the whole batch discarded). And when a staged batch still defeats structured clone, clone-fallback probes the log and names the offender — entity + component for component patches, resource/event names, the lane for engine-built shapes — instead of refusing namelessly.

  • Relay fold/election correctness wave (ledgers 2634, 2636, 2641 — one incident family, gavi's thread 1787518914 + dig 7f40ed5b). Four repairs: (1) a resting author's truth becomes a fold fact — the fail-closed join gate that discarded mid-chain motion deltas "until the author's next keyframe" gains its bounded resolution, so a peer no longer holds a stale offset forever when the author simply stops moving (ledger 2634). (2) Rejoin membership blindness — a partial fold never takes a host seat: a page reload can no longer self-elect host with members=[self] while the relay's h:* row names the true live host (ledger 2636 face 1). (3) Relay uploads classify peer visibility at SEND — replicate:"owner" component rows never ride the peer fan or the journal, closing wire/journal cost at write rate and the owner-row cross-wire privacy hole at engines predating the reader strip (ledger 2636 face 2). (4) The world-sync ghost sweep defers to journal truth, gates on view completeness, and journals its verdicts — a booting member's incomplete world statement can no longer convict live entities (the 51-of-183 spec-root mass drop, ledger 2641).

  • The relay pending-spec-mutation overlay can no longer un-save a save forever (ledger 2677, app efb133a0 — ui.js reverted v731→v764 while every receipt read healthy). Three moves, one contract — custody must converge or confess: (1) custody is bounded against the adopted head — each row stamps the head dbVersion and the key's adopted bytes at enqueue, and a fold that supersedes the base while carrying third-party bytes at the row's key is a conflict kiln already won: the row stops overlaying, stops posting, and the loss confesses once per key on the runtime-log ring; (2) custody owns its own dirty clock — tick() re-arms off pending presence alone, closing the parked-forever, missing-head-forever, and netcode-remount orphan arms (parked batches retry on RELAY_SPEC_SAVE_PARKED_RETRY_MS); (3) overlay receipt — when the overlay rewrites a script key the incoming doc carried differently, RelaySpecOverlayReceiptResource counts it (masked/superseded) and the engine-bug dump ships it as the specOverlay block. The adversarial review pinned the block's build/round-trip/hostile-sanitize contract (engine-bug-report.test.ts).

  • Restate frames now carry tombs — a host-stamped delete refused by one observer's stale table stops being a permanent ghost there (ledger 2701 fix 1, incident 7f261f20 Frenzy at Greywall). The relay tier's window-loss repair lane was create-only: restatePlace sent deletes: [] on every restate frame and hostedKeyableCreateRows walks live entities, so a dead window-ledger key produced no row at all — nothing ever re-delivered a delete, and one observer kept four immortal beasts for 151 minutes. restatePlace now emits window-ledger keys that resolve to no live entity as delete rows on the same host-stamped restate frames; deletes are idempotent at observers that already applied the live delete. Live keys never tomb; member avatars, terrain chunks, and local-plane ids stay excluded. Named residual (fix 2's territory): a host seated by a fold that never witnessed the drop restates an empty ledger.

  • Moved statics re-place in place — the dispose+rebuild churn is gone (ledger 2687, savi incident c8cd5645). A static body whose World* transform moved was destroyed and recreated from scratch: syncPhysicsBodyToComponents's static branch disposed the fixed body inline (hierarchy-solve calls it for every solved child with a body), and the statics pre-pass recreated it next sync — so a rotating parent with ~120 static children (Blob Wars' spinning arena: a 144-segment ring wall and a radius-44.5 disc with mesh colliders, plus dens and cover blocks) put ~240 rapier body+collider create/destroy calls per tick through the wasm allocator, two of them trimesh cooks. Every prod specimen of the rapier step corruption ("corrupted and quarantined at stepPhysics:rapierStep: unreachable") rides exactly that create/destroy churn, across two engine versions and three different rigs. Realized statics now move with setTranslation/setRotation on the standing body (moveRealizedStaticBody, all three move lanes: the inline authoring sync, the stable-static dirty lane, the pre-stable signature lane) — body identity persists, the broad phase re-indexes the moved colliders itself (pinned by raycasts against the rotated poses in rotating-static-parent.integration.test.ts), colliderSetEpoch still bumps per move for the trigger carry-forward, the realized-pose caches follow the move, and the inline path keeps waking sleeping dynamics exactly as the dispose path did. No body classification or solver behavior changes: fixed stays fixed, same pose math (resolveTranslationFromComponents), same f32 quantization. Repro rig on the incident's shape (124 rotating statics + resting dynamics + a walking character controller, 2000 ticks): per-tick sim cost dropped ~2× and the body churn count dropped from ~248/tick to 0.

  • run_script failures never ship engine frames; engine-internal crashes settle as a structured platform verdict (ledger 2647, ravi's friction sweep — 3 apps in one day). The specimen: a creator script crashing on a legal null (api.getPlayers()[0] in an empty room, then p.id) surfaced as at anonymous (file:///tmp/spawn-room-host-engine-cache/engine-client-runtime-worker.<hash>.mjs:5:26) + withMutationLogContext/runIsolatedExec frames — new Function attributes compiled frames to the file that called it, so HER line-2 crash wore the engine bundle's path and taught Savi to investigate engine internals at the creator's expense. Now every ad-hoc compile carries a //# sourceURL=run_script pragma (appended after the script so a creator pragma can't win; the async wrapper rides the strict-mode line so line numbers match the sync shape), and composeExecErrorStack (error-stack.ts) shapes every failure: script frames rebase past the synthesized preamble to HER line numbers (at run_script:2:26) and are the only frames a result carries. A VM-native crash class (TypeError/RangeError/ReferenceError — stack exhaustion exempted) with frames but no script frame anywhere is an engine-internal fault: the result is one honest line ("your code didn't cause this…") + code: "engine_internal_error" (threaded through the worker protocol and the client-authority wire gate like circular_dependency), and the FULL stack goes to the platform log (tome.exec.engine_internal_fault / tome.exec.worker_endpoint_fault) — we keep the diagnostic, she never sees it. Engine teach-rails must never wear VM-native classes for this to hold, so the two deliberate TypeError refusals (readPositions/setPositions buffer-too-small) became plain Error. Known residual, named for follow-up: factory-compiled spec-module frames still wear engine paths, so a required lib crashing in a deferred continuation (no run_script call-site frame survives an await resumption) classifies as an engine fault until modules carry their own sourceURL tags.

  • The numeric-runtime generation is part of a scripted recipe's identity — cross-engine derive-mismatch parks die by re-mint (ledger 2311, Seele's Prison Realms; 5.x backport of 11960f4064). A kind:"scripted" piece replicates its recipe + a DrawMesh pointer, and every client must reproduce those bytes; the 5.2.31 sandbox-Math change (results-only f32) made recipes minted on ≤5.2.30 derive different bytes on newer clients — bespoke-unhealable derive-mismatch, parked forever, while the source-only scriptHash read "fresh" on both sides. TOME_NUMERIC_RUNTIME_GENERATION now folds into the scriptHash domain at its one door (hashSourceSignature — mint, judge, ingest skew compare, spline twin, decoration memo): a recipe minted under another generation (or before generations existed — every 5.2.x row) hashes stale by construction and the EXISTING funnel re-mints it at the authority before any client derives. No wire field, no second judge, no new sweep. A golden pins every implementation-approximated sandbox Math member so a table change forces a generation bump in the same commit.

  • MP trigger zones no longer fork per viewer on relay: scripted sensors are excluded from the combat sim-lease (ledger 2642, dig 80e7ae74; pre-existing since ≥5.2.34). The guarded-write lease gate (isCombatLeasableNpc / server mirror isServerCombatLeasableNpc) admitted ANY entity with behavior + PlaceMembership + no owner — i.e. every scripted trigger zone. Trigger hooks are MOVER-keyed (physics-dispatch pushLocalTriggerEvent: each visitor's machine fires its own crossings), so the joiner's first enter write filed a sim-lease claim on the ZONE, named the joiner its authority, and the joiner's own intents died sender-side (relay-peer-apply synthesizing target_unavailable on the self-authority read) — a per-viewer fork based on onSpawn zeros that the host overwrote back (counter regressions both directions on the dig's tape). The narrowing is derived from what the entity IS in the spec: a body with sensor: true (physics.trigger compiles there) fails the gate unless the entity carries NpcAgentCfg — properties.npc's default hurtbox is a kinematic trigger capsule by design, so the agent mark keeps #11625's whole bot class leasable (all combat-lease pins stay green, plus a new pin that a default-hurtbox agent still claims). Zone writes now ride the intent rail to the place host, multi-writer by design; pinned end-to-end in both topologies (client-auth harness + relay double).

  • attachment: { skin: true } — skinned child models deform with the parent rig (ress, dump cec3129e). One variant on the existing attachment verb: the child stays an ordinary CharacterRuntime with one field changed; the bind reads posed rig worlds downstream of any retarget (composes with clip retargeting by construction), and the sim's authored Local*/World* transforms are never read for the palette. Unbound joints emit a structured diagnostic (model-skin-child-unbound-joints) listing the parent's bones and the per-joint map fix.

  • Skinned scripted materials read baked vertex data; the park speaks (ledger 2643). Three measured gaps on ress's anime-outline bench (a rigged GLB with a per-vertex outline-width mask baked into COLOR_0/TEXCOORD_1), cured end-to-end: (1) attribute(name) joins the TSL vocabulary (builtin/tsl) — the RAW vertex attribute exactly as the layout delivers it, so on a skinned draw attribute('normal')/attribute('position') are the UNSKINNED rest streams (the palette blend never touches them) while positionLocal/normalLocal stay the posed pair; unknown names and fragment-stage reads refuse with teaching errors (.vertexStage() named). (2) uv(1) now reads TEXCOORD_1 as its own input/varying: the parser's second UV set (renamed uv2→uv1, the modern three name) uploads to ModelPrimitiveGpu.uv1, both scripted lanes (static scripted-model.ts + skinned scripted-skinned.ts) carry it through signature/layout/buffers, and the assembler plumbs in.uv1 with its own needs/varying/validate legs — a layout without it refuses with reads uv(1) but the vertex layout has no 'uv1' attribute instead of silently serving uv(0) (which taught a creator wrong for a session); uv(≥2) refuses at authoring. COLOR_0 on the skinned lane was already delivered by the lane rework since 5.2.26 — now pinned by ress's exact bench (mask × positionNode push, all seven vertex buffers bound). (3) The invisible park dies twice: assemble-time throws (layout-dependent — the geometry lacks what the graph reads) get their own verdict (reportScriptedMaterialAssembleFailure) that names the geometry gap instead of claiming a GPU pipeline failure (tilemap/decoration vertexPath lanes switched too), and every park now records a one-line verdict + timestamp in a park register (scriptedMaterialParkRegister()) that rides the renderer perf sample as a dump-only rider into the engine-bug dump's render diagnostics (scriptedMaterials, sanitized ≤16 rows × 400 chars, [] = positive nothing-parked receipt) — "why is this object plain Std/PBR" reads straight from any dump for as long as the park stands, instead of living in one getLogs line at park time.

  • Whole-place render snapshots are now byte-budgeted streams instead of one materialized copy of the world (gavi, GG EZ OOM dig 2026-08-26). The sim→renderer snapshot encoder used to run its cursor to the end of the id list in one call, pushing every spill frame into the unbounded sim-side backlog — for a 2310-scripted-mesh world that is hundreds of MB of fresh heap allocation per attempt, the allocator refused, and the per-tick system retry re-encoded the whole place from zero while leaking each partial attempt into the backlog (the black-screen collapse loop). The encoder now parks its cursor when the pending backlog crosses 64 MiB — under even the 256 MiB pressure latch's clear threshold, so a snapshot can no longer arm the stream-collapse valve whose remedy was another full snapshot — and resumes as the reader drains. Slices are transactional: a mid-stream encode failure rolls back its slice (visible-set registrations, string-table definitions) and retries from the parked cursor, never from zero. Pinned by render-channel-snapshot-stream.test.ts (bounded allocation, eventual exactly-once delivery, resume-not-restart on mid-stream failure).

  • Snapshot retry edges are transactional (the same dig's amplifier). A throw out of the place-transition / initial-snapshot / recovery encode used to unwind before state.lastPlaceId / state.sentInitialSnapshot were set, so the edge re-fired every tick — a full delete + whole-place re-encode from scratch, per tick, forever. The edges now commit before the one call that can throw; a failed encode resumes from the writer's parked cursor instead of re-deriving the world. Pinned by ecs-sync-snapshot-retry.test.ts.

  • Bespoke geometry dedups on the render wire by content signature (the dig's mass multiplier). Entities sharing one geometry signature used to ship one full copy EACH (up to ~1.4MB per mesh) in every snapshot and delta batch; the renderer's geometry store was already signature-keyed. The channel now ships the full payload once per write batch and a ~40-byte wire reference for every later carrier. Batch-scoped on purpose: heal-rail retransmits and recovery snapshots are new batches and re-ship full bytes, so a reader whose cache lost the first copy still heals. Pinned by render-channel-geometry-dedup.test.ts.

  • BREAKING: spot light angle is now the cone half-angle in DEGREES (0-90, default 60) — it was three.js-inherited radians, the only radians rotation field on the tome surface (r-66f22fef: Savi wrote angle: 62 meaning degrees; the engine clamped to 90° and DM'd the radians teach). The authored value stays degrees through the spec and draw/light (round-trip safe: getProperty("light") reads back what was written); the one degrees→radians conversion happens at the trig edge (lights-store.ts setLight → record.spotAngle; clustered cos packing and shadow fov consume radians, unchanged). The wild-angle clamp+teach now reads degrees (>90 clamps to 90). Also flipped: terrain rooms auto-light cone calc (writes degrees), builtin/primitives spotLight default (30°), god-mode placeable default (32°), god-mode cone editor + beam visuals (convert at read/write, drag math stays radians), render-harness fixtures, examples, api-reference and zod describes.

  • The template-escape rescue fires only on escaped-at-code-position shapes — legal JS compiles byte-identical (ledger 2704). normalizeTemplateLiteralEscapes rewrote every \` and \${ file-wide the moment one appeared anywhere, so a legal file carrying escaped backticks inside strings, templates, or comments lost its REQUIRED escapes at compile time and the whole require() chain died with a bare 'Unexpected token'. The trigger is now parse-aware: the string-content scanner decides whether any \` sits at an evaluated (code or interpolation) position — which no legal file can contain. The #4630 rescue (whole scripts arriving with template syntax escaped at code position) still fires byte-for-byte. One home in export-rewrite-scan.ts; the three engine copies import it. Write time refuses the rescue shape by name ('escaped backtick will be rewritten: <site>' in validateScriptDraft) instead of acorn's bare 'Unexpected character'.

  • Creator debug dumps now carry the renderer's applied-entity census in the ECS capsule (ledger 2722). "Entity present in ECS but not drawn" was undecidable from a creator dump: engine-bug samples carry the renderer's applied set (RenderChannelReader census, ledgers 2382/2338/2376), but the dump's ECS capsule did not. The capsule now stamps entityResidency — {count, entities, atMs}, the same census the entity residency sweep diffs, read from the render-diagnostics mirror the 1Hz perf sample already retains (no new collection). Diff entityResidency.entities against the capsule's entity keys for the drawn-vs-present verdict; atMs far behind capturedAtMs means the renderer stopped sampling. The capsule's small facts (wire, entityResidency) now serialize before the entity record, so the dump row's inline head carries them without the R2 fetch.

  • Debug-dump rings now carry the relay join's success verdict (dig 0d14d6a7). The dump error rings retain warn/error only, so a healthy join looked like a missing self-spawn — the success signal (tome.player.spawned) was INFO-level and never entered any ring, and two prod misreads rode exactly that gap. The relay join coordinator now logs one WARN per completed bootstrap: [relay-join] self-spawn complete: <entityId> (instrument realms name their body-less-by-design verdict instead). One line per join — fresh fold or F10 re-bootstrap; plain resumes never mint one.

  • The perf rollup's mode.inputRouting block is emitted again (ledger #386 restoration). The client-auth rewrite (2026-06-17) dropped the engine-side emit while kiln's retention and dump-summary render stayed live, so the dump summary's Input: line ("routed to body | body last dispatched Ns ago") has been silently absent since. buildWorkerSessionModeBlock again reports the local player's control target, god-mode presence, and body dispatch age — "are inputs reaching the sim" reads straight off the dump summary next to the error ring. Producer now pinned by tests so the emit cannot die silently again.

  • Client bundle diet: all client output −19% (10.96 → 8.87 MiB minified), the jobs worker −76% (2.74 → 0.65 MiB), page realm −13% (r-ee055c9e). Four cuts, no behavior change. The default game template imports concrete modules instead of the engine barrel — the barrel's export * graph statically bought createClientRuntime (ObjectAPI, the mantle solver, spline, relay wire codecs) into the jobs worker for two terrain job tokens, dead by construction in a realm that cannot construct a client runtime; the same fix in game/index, _InputFeature, and the build entries takes main-thread machinery (WebAudio wiring, touch chrome, the CPU particle backend) out of the sim worker's graph. titleCaseKind moves to a god-mode leaf (god-mode/kind-label.ts) so the cursor label stops paying 81 KiB of effect decks in every page realm. The renderer inspector (spawn-inspector + tab-mirrors, ~75 KiB) dynamic-imports on first open instead of booting with every page. The macOS executor live bridge dynamic-imports inside maybeArmExecutorLiveBridge, so browser renderers stop shipping the recorder (−29 KiB). The standalone jobs bundle ceiling drops 3,300,000 → 820,000 bytes: any single barrel re-attachment is engine-scale (+0.5 MiB minimum) and trips the build instead of shipping.

  • An "Exit god mode" chip joins the top-left chip cluster beside See/Hide controls (adversarial review of #13362). Testers entering god mode couldn't find the way out — Tab+G is invisible knowledge and kiln's God Mode button hides behind the Tab overlay. Fine pointers only; touch keeps kiln's own always-visible god-mode button in the mobile creator chrome. The chip dispatches the worker-local god:exit UI event over the delegated chrome-event lane; the creator UI controller routes it to the mount's onExitGodMode, which runtime-worker wires to dispatchGodModeSet(world, false) — the exact set-intent exit kiln's button and Tab+G converge on, never a parallel exit path.

  • A held-ctrl binding beside W now bills a write-time input-bindings fault (dig 5c68c6dd). The browser owns its ctrl chords — ctrl+W never reaches raw capture, so a crouch-on-hold-ctrl WASD game closed the creator's tab mid-playtest and no engine code can intercept it; the only cure is the binding itself. collectInputBindingFaults emits one collapsed fault line per map when ctrl is kept held during play (a hold-mode action triggered by ctrl, a hold-mode combo with a ctrl modifier, or a ctrl-fed axis — axis keys are held by nature) while the same map binds W. The fault rides the existing write-time rail: patchInputs' mutation-warn lane (Savi's getLogs) and spec-sync's console ring. Tapped-ctrl shapes (keydown menus, ctrl+key hotkeys, keyup binds) and maps with no W stay silent, so ctrl menu toggles and hotkeys never bill.

  • Published rooms run their scheduled work: the builder seat joins occupied live rooms (ledger 2708; 46 affected games at the 2026-08-26 census). A relay-hosted room executes no game code, and the room_host seat that fires crons + host-seat lifecycle hooks (relay-lifecycle.ts) only ever joined DEV rooms — every published game's crons sat silent. The engine half: ROOM_HOST_ROOM_MODE=live replaces the shell's dev-rooms-only hard rail with a structural one — live mode FORCES the empty-room depart (empty-room-watcher.ts: depart after the room is CONTINUOUSLY empty for the window, default 120s, floor 45s, disable unrepresentable) and refuses the dev yield etiquette. Occupancy reads the new world-view presentForeignPlayers census: connected, SUSPENSION-BLIND — a hidden tab is a player the seat serves, so an idle game whose players all tab away never saw-tooths — while foreignPlayers stays suspension-aware for the dev yield rule. The summon half deploys platform-side: kiln's relay presence lane pokes a live seat for occupied published rooms, gated on the published spec actually declaring scheduled work (engine.crons / engine.behaviors), one poke per room per minute; cf-studio-chat's RoomHostContainer accepts roomMode "live". Empty published rooms keep exactly the old behavior: no seat, no execution.

  • A raw materials record in a terrain patch persists as the merged whole map (ledger 2724). The durable fold treats a record materials value as a monolithic replace (ledger 2709 — the material verbs' whole-map echo demands it), while the live layer deep-merges the same record. So a partial record persisted AS WRITTEN silently deleted every durable key it didn't name — a { emerald } patch deleted stone from the durable head while the live room kept it until reload — and a null-valued entry (a merge-patch delete live) folded durably to an empty map. buildPersistedTerrainPatch now expands a plain-object materials value through assignPersistedTerrainCollection, the exact whole-map shape the material verbs already echo, so durable behavior equals live behavior and the "deep merge" teach stays true on the raw lane. Heightmap material arrays and materials: null never enter the lane: arrays replace identically on both layers, null deletes the key on both.

  • The headless room-host shell's /magic asset fetches carry the game's server identity (ledger 2729, r-6b4131ea). The shell realm — Savi's tab-less builder seat, the only engine instance in an empty dev room — fetched generated assets unauthenticated: kiln 401s anything still mid-generation, the worker's bounded load budget burned inside the asset's own 10–45s import window, no session-wake exists in a shell realm, and a model pointed into the spec during its own import stayed invisible to that session forever (the Cardhouse invisible-GLB class, postmortem docs/postmortems/2026-08-28-cardhouse-workshop-glb-invisible.md; plus chronic transform=collider 401s across apps). The engine's server-asset-auth.ts contract has carried the fix shape since 5.0 — SPAWN_SDK_API_KEY + SPAWN_VARIANT_ID in the worker env, sent only to the pinned asset origin, the container tier's exact trust shape. This wires the missing shell half: headless-room-host config parses ROOM_HOST_SDK_API_KEY and buildRuntimeWorkerPublicEnv forwards the pair to the worker, so mid-generation fetches answer authenticated 202s the engine already retries politely, and the shell's own first ask can start the import. The supervisor (cf-studio-chat RoomHostContainer, deploys platform-side) resolves the served variant's own integration key at startRoomHost; a failed lookup boots keyless — the pre-fix posture — with one named warn.

  • Sprite-atlas hydration honors the server's 429 window, jittered per URL (the engine half of the ledger-2710 landing; PR #12561 report). The 2D atlas hydration lane re-asked on a flat unjittered 30s cadence and threw away the retryAfterMs its own classified fetchers computed — during a rate-limit park the engine's re-asks were a burst of their own, re-buying refusals the limiter had already priced. A 429's Retry-After (header, body fallback when intermediaries strip headers, 1h ceiling) is now that texture's retry floor: the next round waits max(30s, window × up-to-1.25 deterministic per-URL jitter), so a sprite family that failed together re-probes spread out instead of re-bursting the moment the window lifts. Plain failures keep the flat 30s cadence byte-identical, and the 30s politeness floor means a short server window never speeds hydration up. RateLimitVerdict grows window ("hourly" | "daily" | "queue") so teaching surfaces can phrase a drained budget ("back at HH:MM") differently from queue backpressure ("starts as slots free"). The service half of the same landing — magic-cdn's cook queue counting sprite FAMILIES rather than variant rows against the depth cap, so one 2D character's ~17–49-variant spawn burst is one admission — deploys platform-side and is not pinned to this version.

  • A frozen frame loop can no longer hide behind a healthy event loop (ledger 2733). The field class: attached clients silently stopped folding spec updates while captures captioned the saved head, and restart-rooms did not clear it. Three fixes in the frame machinery: (1) the fixed-step ticker REBASES on negative time instead of accruing it — a reset seeded from a different clock domain sank the accumulator minutes below zero and step() answered 0 for exactly the gap: sim frozen, event loop alive, no verdict; one warn per rebase edge, and the debug snapshot counts clockRebases. (2) The hidden→visible edge resets the frame clock with the WORKER's own clock, never the page's performance.now() (different time origin — the poison source that parked studio tabs for minutes). (3) The frame loop re-arms its self-schedule chain in finally, so a throw escaping one frame is loud on the worker error event instead of ending the sim permanently while sockets and telemetry stay alive.

  • Telemetry's specVersion is now the APPLIED dbVersion, with the adopted head beside it (ledger 2733's verdict half). specVersion answers from TomeAppliedSpecDbVersionResource — written only by a successful spec-sync apply, falling back to the store head on worlds that predate the stamp — and the new specStoreVersion carries TomeSpec.dbVersion, which keeps climbing event-driven while a stalled fold applies nothing. Divergence between the two is the client-side stall signal, so a stalled world reads as the stale version it is and the EYE-STALE version face fires instead of captioning the head over frozen pixels.

  • Single-cell voxel edits patch the rapier collider in place (ledger 2696, the physics seam of the per-voxel-edit hitch). A one-block dig recreated the chunk's whole voxels collider — every solid cell (1.9k–3.8k on the World-of-Spawncraft shape) re-cooked and re-broadphased per edit, on the edit tick. The terrain-chunk handle now retains the realized voxels lattice (RealizedVoxelsShape: grid coords, cellSize, origin); when the collider signature moves and the new payload is the same lattice, the sync diffs the two cell sets and applies Collider.setVoxel per changed cell — O(changed cells), with automatic grid growth for cells outside the allocated bounds. Collider identity survives, so reverse lookups and contacts on unchanged cells are untouched; colliderSetEpoch still bumps, so rest-skipped character controllers wake over the edit exactly as they did for a recreate. Anything that is not a small same-lattice cell diff falls back to the ordinary recreate: bulk fills past the 128-cell cap, lattice moves (origin/cellSize), shape flips (voxels to boxes/hulls), a rapier refusal mid-patch, and every non-terrain collider. 2D runtimes never emit voxels payloads and are untouched.

  • Per-voxel-edit bookkeeping is O(edit), not O(world) (ledger 2696). One voxel edit on a large world (World of Spawncraft shape, 2,560 resident chunks) cost ~15ms on the edit tick plus ~11ms on the next while the actual remesh is ~2ms — the rest was bookkeeping that scaled with world size. Four cuts: the edits-for-key cache evicts through a coord-keyed index over the dirtied coord's 27-neighborhood instead of sweeping every resident entry per edit-version bump; a single-cell edit marks exactly the owner plus the neighbors whose one-cell shell contains the edited cell (1–8 chunks, the same halo predicate the aggregation and the voxel field use) instead of the full 26-halo, while region commands, removes, replication ingest, and every delta-less mutation keep the superset; the field drain stops content-hashing four grid-sized arrays per rebuilt chunk (a drain rebuild only runs when grid content moved, so a monotonic remesh sequence serves the visual-signature contract); and the drain's take reads a dirty queue fed by the one dirty funnel instead of walking every resident field chunk every tick, with stale entries dropped at take time. Receipts on the WoS shape: bench edit-tick drain 7.34ms → 0.73ms mean; real browser edit-to-visible p50 27–31ms → 10–14ms, p95 31–374ms → 12–25ms.

  • The voxel in-place collider patch refuses payloads carrying shape-table cell parts (ledger 2723). The ledger-2696 fast path mutates ONLY the voxels lattice (Collider.setVoxel per changed cell), so adopting a payload whose signature carries box/hull shape-cell parts (the :pN: extension from ledger 2706) would leave those parts unrealized until chunk reload — the walk-through symptom resurfacing on the live-edit seam while the cell renders and answers raycastVoxel. tryPatchTerrainChunkVoxelsCollider now refuses any payload with boxes or hulls and falls back to the ordinary recreate, which realizes every payload lane. The bounded cell diff moved into its own helper (diffVoxelLatticeCells, 128-cell cap), and the same-cell-set signature-adopt path is provably lattice-only now that part-carrying payloads are refused above it.

  • Voxel shape-table cells collide alongside plain solid cells (ledger 2706, family dig 08fd8e65). buildRoomsBodyConfig returned early with ONLY the voxels collider whenever any plain solid cell existed in the chunk, so the shape-table cells' solid parts (appendVoxelShapeCellColliders output) were built and then ignored: giving a material any blockBehavior shape hook silently dropped its cells off the physics rail — render and raycastVoxel kept answering while players walked or fell straight through the cell. A shape cell in a chunk with zero plain solids escaped through the boxes-only lane, which made the bug look intermittent. One composed body now carries the voxels lattice AND the shape cells' box/hull parts, and both physics syncs (rapier + mantle) realize every payload lane present instead of collider XOR boxes/hulls. Shape parts extend the collider signature so a shape-cell edit rebuilds it; a chunk with no shape parts keeps the exact previous signature and stays a single voxels collider, so the in-place voxel-edit patch fast path (ledger 2696) is untouched for the common all-plain chunk.