engine v5.2.40
Engine v5.2.40
September 3, 2026
A patch in the Lume line.
Walls in top-down games block along their whole height and the collision shapes you drew survive a save, a world with terrain off no longer shows a mountain skyline it does not have, the parts a player's body builds at spawn show for everyone who joins, fences attached to a raised building sit on the ground, a host script's sky change reaches every player, tearing down a whole floor no longer freezes physics, big generated worlds load their places many times faster, and one bad packet from another player no longer drops the good ones with it.
what's new
- Walls and other flat sprites in top-down games block the player along their whole height again.
- Collision shapes you drew for a sprite stay in place when a script or Savi updates that object's physics — a foot-high wall no longer turns into a full-height one after a save.
- When the game's own default cloud texture has not loaded yet, Savi's look at your game now says exactly that, instead of telling you a texture failed to generate and sending her to search your game for something you never made.
- Savi's picture only says "those are your editing handles" when the handles are actually in the shot — select something, fly off to look at another part of your world, and she reads that view as the game it is.
- When you ask Savi to look while you have something selected in god mode, her picture now tells her that the editing handles in it are the editor's, not part of your game — so she stops hunting for "floating diamonds" that are not there.
- Props keep their real collision shapes across edits and updates instead of reloading them all.
- For creature models made from now on, Savi's look says when the model has no skeleton by design, instead of waiting for animations that were never coming.
- No change to how any game sounds: the reverb plumbing this removes was muted and unreachable from scripts.
- Clearing out almost everything solid in a level at once — tearing down a whole floor before building the next one — no longer freezes physics for a moment and drops the ground checks that follow it.
- Games that create a fresh arena or level for each match no longer slow down over time: when a match's place ends, everything in it is cleaned out of the room's memory, so joining later stays fast instead of loading every arena that ever existed.
- Savi's scene report no longer counts decals behind the camera as covering the screen.
- An effect asking for more particles than the CPU fallback runs now says so in the log.
- Big worlds with generated terrain load their places many times faster — the ground is measured once per load, not once per object.
- Savi can read a model's bones and shape on the server, not only in a browser.
- Savi's builder body no longer shows up in your
tags: ['player']queries when your player template lists theplayertag itself — the game-side filters that skipped her by id are no longer needed. - Deleting or undoing something you painted with the scatter brush now removes everything it placed for good, even if the room changed hands in between — the pieces no longer come back when you reload.
- Game UI built from your
ui.jsno longer keeps ghost copies of an element around: when your rendered HTML stops containing a node, it is gone from the screen on that render, even if the same id appears elsewhere in your UI. - When something is in your world but not on screen, Savi's look and her object check now say so — "in the world, not in the draw" — and when a texture is there but the texture budget left the object flat, they say that instead of reading it as fine.
- When the game's host seat moves back to you while one of your scripts has a write to a shared object still in flight, that write now lands instead of being undone — your spawns behind it stay, and no "simulator unavailable" line appears in your logs.
- Joining a game whose starting place is not called "main" no longer builds the "main" place first and throws it away — players land in the starting place directly, without the extra load time.
- Pressing a keyboard or headset Play/Pause key no longer silences your game's music while the game keeps running. Game audio ignores the key the same way on every track.
- onPlaceStart runs again when Savi's builder is the first to enter a brand-new room — saved terrain and state your game restores there come back.
- In a busy multiplayer room, walking into another area, leaving, or coming back from a hidden tab now takes effect on your own screen the same way it does for everyone else — before, your own game could keep treating you as still in the old area or still away, so the area you stood in could end up with nobody running it for you.
- Fences and paths attached to a raised building now sit on the ground instead of floating at the building's height.
- When Savi reads your game's state from your browser, she can now see the last few times the tab was in the background — so a room that changed hands while you were on another tab reads as "you were away", not as a problem with the game.
- The parts a player's body script builds at spawn now show for everyone who joins. Before, each player could see their own rig, weapons, or pets, but everyone else saw a bare player.
- After a connection hiccup, the dead connection's leftover traffic no longer trips a burst of extra reconnect errors on the way back — it is dropped cleanly and the reconnect picks up where it left off.
- Savi's raycasts can filter by tag again when she runs code in your world from the chat — asking for "only hit the walls" or "skip the players" now works the same there as it does inside your game's scripts.
- Saving a helper file no longer wipes the things a builder script spawned with fixed names. A script that clears its own pieces and rebuilds them under the same ids keeps every rebuilt piece after the save.
- After a connection hiccup, a world with many moving creatures no longer grows heavier in the background each time it reconnects — rejoins and late arrivals load the same world, not a swelling copy of it.
- A sky or lighting change your game's host script makes with patchAtmosphere now reaches every player, late joiners included. The day/night cycle's hour agrees across players after such a change.
- One bad packet from another player no longer drops the good ones with it: when something in a player's update is malformed, only that piece is set aside — everything else in the same update still arrives, and other players' characters and the game's creatures keep moving.
- When your connection drops for a moment and comes back, other players see you again — before, they could be left looking at an empty spot where you stood until they reloaded.
- Empty per-party areas that a game creates on the fly (the "session" kind) now close about ten minutes after the last player leaves, even when the player who opened them has left the game — they used to stay open until the room restarted, and a player who came back within those ten minutes still lands in the same area.
- Music, ambient loops and sky settings set while the host was briefly away no longer snap back to the older setting when it returns.
- A world with terrain switched off no longer shows a mountain skyline it does not have.
- When a script removes and re-adds a terrain mark in quick succession (a builder re-carving a cellar), other players no longer get a flash of un-dug ground under their feet.
- Drawn textures that were cheap to describe but heavy to paint no longer sneak past the 50 ms bake budget or load the GPU; a texture that is too heavy to paint now parks with the same "over the bake budget" note as a slow draw loop, and everything else bakes as before.
- When the renderer trips over its own drawing setup, Savi's report says so instead of blaming the graphics card.
- When you join a friend's game, one badly-named object in their world no longer hides everything else from you — including their character. And if one of your objects has a name too long for the network to carry, your game now tells you which one, so other players can see it once you shorten it.
- A reconnect after a dropped acknowledgement no longer trips over the old connection's leftover data and reconnects a second time — the loop that could keep a busy room's host reconnecting every minute is closed.
- Deleting a fence or path removes all of it for every player and after every reload — its posts and rails no longer stay behind on other players' screens.
- After a room restart, a reloaded tab and Savi's builder no longer disagree about who runs an area — spells and other host-driven objects no longer freeze in place on other players' screens.
- Particle effects that use curl noise cost about half what they did per frame, and no longer make the game's memory grow while they play.
- Savi's answers about SpawnJam judging, rules and deadlines now point you to the jam page instead of guessing.
- Joining a world that holds thousands of tiny objects no longer loses a whole batch of them on the way in.
- Worlds whose custom geometry keeps regenerating — living terrain, animated cloth, meshes rebuilt by a script — no longer fill the browser's memory over a long session until the game freezes or the tab dies; the old versions of each mesh are released as the new one arrives.
- A texture added after your world is already full of textures no longer comes out the wrong size or cut off when its repeat is not a whole number.
- When Savi works in an area of your game that nobody is standing in, her scripts there now run right away — before, if you were already in the game when her builder joined, every script she aimed at an empty area could fail with "wait for the room" for as long as the session lasted.
- When your game's on-screen UI fails to load, Savi now sees which file never answered and whether it was the network or the page — and a UI that grows past half a million characters per frame gets one plain warning instead of silently loading fifteen times.
›technical notes
- Sprite auto colliders are sprite-plane rectangles, so 2d-top statics cover the band the sprite draws (ledger 2986 — jace0ethaniel's Witch's Brew rollback, "the counter is also walk through-able"). A sprite collider is authored in the sprite plane (x right, y up, feet-anchored), and the runtimes map that plane per place mode: 2d-side keeps it, 2d-top puts sprite-up on −Z, 3D extrudes a prism. Only the hull kinds took that mapping. The auto collider (
spriteAuto), the degenerate-collider2d fallback (the legacy 2-point outline) and the hydration fallback for a texture with no hull were all a WORLD-spacebox[w/2, h/2, depth/2], and a world box's 2d-top footprint is its x/z cross-section — every static sprite without a hull collapsed to aw × 0.5 msliver on the feet line: walls a player walks through, and a stop on open floor where the sliver sits. Savi's receipts reproduce exactly through ObjectAPI.spawn + physics/step + api.raycast in a 2d-top place (sprite [4,2] at feet z 30: north ray hit 29.75, side ray at z 29 missed; expected the 28..30 band), on 5.2.36's source bytes and on 5.2.39's alike — this is not a .36 → .39 regression but the standing shape since the 2D V1 landing (only hulls were mode-mapped there). Now every sprite-derived rectangle is aconvexPolygon(spriteRectPointsin tome/sprite-collider.ts — one shape behindspriteAutoPrimitive's collider,spriteToTightRectPrimitive, and the hydration fallbackspriteFallbackCollider), the one sprite-plane collider both backends, the collider overlay and the signatures already handle; theautoCollider: { kind: "sprite" }marker and the hull-upgrade pass are unchanged (the pass now compares rectangle to rectangle and stays a no-op when the texture carries no hull). 3D and 2d-side keep the exact geometry the box had (x ±w/2, y 0..h, z ±0.25 in 3D) — the control pins in2d-top-sprite-collider-band.test.tspass on both the old and the new bytes; what changes there is the shape kind (a 4-point convex polygon / 8-vertex prism instead of a cuboid). Not in this landing, filed as its own finding: aphysicsre-set on a sprite entity (setObjectProperty, the reclaim path a script re-save takes) rebuilds the collider from the world and drops the authoredcollider2dhull — with this fix that path yields the whole sprite rectangle instead of a sliver, which for a SOLID_RECT wall is the authored hull, but a foot-band outline still loses its band there. - An authored
collider2dhull survives aphysicsre-set (ledger 2993 — the second half of jace0ethaniel's Witch's Brew rollback). The authored outline lived only in the spec: the boot lane andapi.spawnboth derived the physics primitive from the spawn properties (resolvePhysicsPrimitive) and never stored the outline on the entity, so every later derivation from the WORLD sawDrawSpritealone and rebuilt the sprite-sized auto rectangle. Three writers ran that derivation: thephysicsproperty writer (setObjectProperty(id, "physics", …), a body-type change, arun_scriptre-set),refreshAutoPhysics(asign/primitive/modelwrite on the same entity), and — new in 5.2.38 (ledger 2858) — the reclaim batch a script re-save runs on a STAMPLESS child:TomeSpawnOriginnever replicates, so on every reload, room recycle, journal replay, and host migration the onSpawn re-run re-lands the full spawn spec through the batch property path,physicsincluded. On 5.2.36 and 5.2.37 that re-save landed nothing on such a child and the spawn-time hull stood; from 5.2.38 every script save on a reloaded room re-set physics on every sprite child, and every authored hull on those children became the full rectangle (before #14025, aw × 0.5 msliver). That is the bisect answer the row asked for: the re-set path did change between .36 and .39, and the change made the standing drop fire on every save instead of only on an explicitphysicswrite. The cure is ledger 1783's sign-spec move: the authored outline is world state —TomeCollider2d(tome/collider2d, authoritative, replicateaoi), written by the boot apply (applyAppearanceProps), byapi.spawn(applySpawnProperties), and by the newcollider2dproperty writer; one derivation (spriteColliderPrimitivein tome/sprite-collider.ts — authored hull, else degenerate tight rect, else tagged auto rectangle) now serves the spec lane and the world lane (resolvePhysicsPrimitiveFromWorld).collider2dleaves the no-writer key set:setObjectProperty(id, "collider2d", { outline, sourceDimensions })rebuilds the hull live (no full applySpec pass),nullreturns the sprite to the hydration-eligible auto rectangle, andgetObjectProperty(id, "collider2d")reads the authored outline back. The hull-upgrade pass, theautoCollidermarkers, and the 3D / 2d-side / 2d-top plane mapping are unchanged. Pinned red-first incollider2d-survives-physics-reset.test.ts: the spec-authored wall, the runtime-spawned wall, the stampless-child reclaim and the fresh-boot adopt (ledger 2858's two branches), the named-new-outline / null writes, the room-state snapshot lane, and the registry lane. - Boot milestones now reach the parent as loading-state transitions, so kiln's boot-stall ladder measures its window from the last milestone instead of from
starting/50(majid's 2026-09-03 boot loop; ledger 3033; kin 3026, 1913). Between the entry'sstarting/50("Starting game...") and the scene gate'sready,worker-browser-host.tsposted nothing on thespawn:kernel:loading-statechannel — the only pre-ready word in that span was the 10 s park post (room-connect-pending/room-world-pending), which kiln's boot-stall ladder deliberately does NOT count as progress. So a boot whose sim had joined the relay, taken the host seat and been ticking at 30 Hz within 2 s, while its RENDERER lane was still loading over a congested link (renderer asset manifests + textures took 44–92 s on the creator's machine; the relay's egress_shed rows for his identity read RTT 1.5–2 s every hour he played), read from the parent as a boot that had not moved for 30 s — and kiln's ladder (core/kernel/boot-stall.ts: "any pre-ready transition resets the clock; 30 s without one remounts the iframe, 30 → 60 → 120 s per named wait, six attempts") remounted the iframe, killing a boot that was seconds from done, on a loop. The relay saw each remount asstale-replaced→first-beat-takebackseat churn; the container tier saw a 6.5 MB reset ingested at 3 s and the socket gone at 30 s. The host now posts one monotonestartingtransition at each milestone it already observes:transport.attached(60, first attach only — a mid-session re-attach is not boot progress),transport.projectionReset(70, the join baseline), and the first half of the scene gate (85 — the renderer's first frame OR the sim'sscene.readyHint, whichever lands first while the other is owed). Same substatus as the entry's post; nothing posts once the curtain has lifted, and progress never walks backwards under out-of-order arrival. Red-first inworker-browser-host.scene-gate.test.ts(the milestone sequence; monotone under baseline-before-attach; silence after ready). NOT a visible change: kiln's loading bar is pure time (core/kernel/loading-progress.ts, "Loading bar detached from boot stages") and mobile's is the same model, so these posts move no pixel — they reach the ladder and the breadcrumb trail only. CROSS-APP: the milestone posts land AFTER the park post with nobootWaitCondition, and the ladder's backoff record used to key on the LAST post's condition — so on the unfixed ladder these posts stripped the 60/120 s windows from exactly the slow-link class (a 91 s boot remounted at 47 s, 47 s, 47 s, 47 s until the attempt budget). The kiln half (boot-stall.ts, same landing as this text) keys the record on the wait the kernel named during the episode, so a milestone re-arms the clock without forgetting the wait: the 91 s boot remounts at 47 s and 77 s and survives the 137 s window. Not changed here: the ladder's 30 s base window, and the renderer lane's own progress (manifest bytes) — a link slow enough that the world baseline lands inside 10 s (no park post) and then NO milestone lands for 30 s still remounts at the base rung with no backoff, on this engine and on 5.2.39 alike. Also open: the park post hard-codesprogress: 50(loading-state-broadcast.ts), so the breadcrumb trail reads 60 → 50 when the attach lands before the park; cosmetic, nothing reads progress as regression. - An engine-owned texture that did not load never reads as a failed generation (ledger 2979 — Savi filing c668576c, 5.2.37). The scene-view asset note (
engine/renderer/scene-view-asset-note.ts, the caption behind Savi'slook) put the engine's default cloud texture into the magic-cdn failed bucket:textures/cloud-puffis minted bytome/spec-assets.tsfromDEFAULT_CLOUD_TEXTURE_URL(a GitHub raw URL), the loader's URL rewrite rides that URL through the public-import lane (/cdn/public.<b64>.png), sounreadyTextureStatusclassified it as a magic-cdn asset and a park on that lane read "1 texture FAILED to generate (…) — the asset host itself answered a terminal verdict … regenerated under a new name". Savi scanned 174 objects for a texture nobody authored and filed an engine fault. New predicateengine/assets/engine-texture-ref.tsisEngineTextureRefnames the engine'stextures/namespace (the cloud puff, the baked star point); the texture leg (lume/assets/service.tsunreadyTextureStatus) classifies atextures/*id WITH a manifest entry before every other rule — "loading" while a fetch is in flight, the newUnreadySceneAssetstatus"builtin-unloaded"otherwise, whatever park or receipt the transport recorded — and atextures/*id with no manifest entry (a creator typo;normalizeAssetRefrefuses the namespace for creator refs) keeps the unresolvable read. The note gains one bucket, last in the sentence, named by the engine id and carrying no elapsed time:N built-in texture(s) did not load on this client (names) — the engine ships it, nothing was generated and nothing needs a rename; a reload or a second look usually fetches it. The failed clause is byte-identical for genuine magic-cdn deaths, and the built-in never enters its tally. Not the "still generating/loading" wording, so the capture settle gates (cf-studio-chatcapture-core/settle.tsnoteIndicatesStreaming) read it as settled. Pinned red-first:scene-view-asset-note.test.ts(the sentence verbatim, plural, beside a real death with the death byte-identical),lume/assets/__tests__/service.test.ts(a spec with clouds enabled → the collected default entry → a dead-4xx park →builtin-unloadedand the built-in clause with zero failed-to-generate; 202-paced and plain-retry built-ins; thetextures/typocontrol), andeval/arena/capture.test.ts(the settle gate ignores the clause). - The capture stamp
authoringSelectionVisiblenow means "drawn AND in this picture" (ledger 2961 follow-up, ravi's review finding 3a). The first cut stampedtruewhenever the selected object's handle cluster was drawn anywhere in the world, so a select-then-fly-away viewport look, aframe:look at another object, or acamera:look pointed elsewhere all claimed handles the image did not carry. Now the sim'sgodmode.authoringSelectionVisibledebug read answers{ visible, cluster }— the world-space box around the selected object's drawn gizmo entities (fills + outline and connector twins,god-mode/authoring-selection-visible.tsreadAuthoringSelectionCluster; the object's own box when no gizmo bounds are readable) — and every capture result carries the camera it rendered through:SceneViewWorkerResult.viewProjection(the presented frame's live camera for viewport grabs, the rig's pose for camera/frame/burst renders,lume/capture/scene-view.tsrenderLumeSceneViewPixels) and aviewProjectionbesideblobon thecapture-screenshot-resultmessage, surfaced asRendererHandle.captureScreenshotFrame()(captureScreenshot()keeps its Blob contract). The kernel main thread (_entry/client/sim-debug-rail.tsauthoringSelectionField) tests the box against that camera's frustum (lume/frustum.tsaabbOutsideFrustum, the p-vertex test beside the existing sphere test) and stampstrueonly when the box reaches into the view; a cluster partly clipped by the frame edge counts as in view, one wholly outside stamps nothing. The field's shape is unchanged: literaltrueor absent, neverfalse— absent also when the renderer reported no camera or the cluster could not be measured, since "in the picture" cannot be asserted then. Pixel paths untouched. Pinned red-first on the pre-change main thread: fly-away viewport look → absent,frame:/camera:elsewhere → absent, edge-straddling cluster → true, no camera → absent, no measurable cluster → absent; the chat-screenshot envelope's fly-away and pre-field-handle cases; the sim cluster read boxes the cluster around the selected object. - Savi's captures name the god-mode handles they carry (ledger 2961 — horse's "floating diamonds" and "sticks", The Cure Is Worse fd84c00c, 5.2.38). The plain
look(view_live_scene→captureSceneViewImage→captureViewportScreenshot, the swapchain texture) and the per-message chat screenshot (chat-screenshot-capturer.ts) read the presented frame, and the selected object's handle cluster — the diamond beads, the axis bars, the connector rods — is ordinary client-only geometry in it (god-mode/api/authoring-gizmos.ts: BespokeGeometry + DrawMesh in the primitives lane's alpha and overlay bands, the bands ledger 2637 made the rig draw on purpose), so the framed look carries them too. No world-only image exists today, so this stamps the fact instead of moving pixels: the runtime worker answers agodmode.authoringSelectionVisibledebug read off the god-mode interior (authoring-selection-visible.ts— some god-mode creator'sselectedEntityIdhas an alive<anchor>/handle/…entity carrying DrawMesh or DrawPrimitive; the editor GC reaps undeclared handles the same tick, so alive-and-drawn is exact; false while the interior has not loaded, since only the interior paints handles), and the kernel main thread kicks the read alongside every capture.view_live_scene's RPC result growsauthoringSelectionVisible?: trueon every source (viewport, camera, frame, burst) and thespawn:kernel:chat-screenshot:responseenvelope grows the same field besidedataUrl; both are present only when true — absent on deselect, in play mode, on mounts with no runtime-worker rail, and on engines predating the field, so the pre-field shapes stay byte-identical whenever nothing is there to name. Pixel paths untouched. The Savi-facing sentence on the tool result is the chat side's (cf-studio-chat). Pinned red-first: the sim read (selected + painted → true; deselected, play mode, never-god-mode → false), the RPC handler (viewport and frame → true; absent on deselect, no rail, null answer), and the chat envelope. - The client collider scheduler reuses held cooks across manifest-signature changes (ledger 3000 — cal's Final Abyss, ~1,000 collider requests per shell boot after the 5.2.39 pin move). A collider requestKey hashes the whole asset manifest, so any manifest change (a spec edit that adds one model, an engine pin move) re-keys every body in the world. The client pass (
physics/collider-assets/client) consulted onlybyRequestKeyand, where a replicated component exists,bySignature; on the relay tier no component ever exists, so every model's byte-identical collider went back to the network on every edit — ~490 re-fetches per new model placed on a 19-place world.processClientCollidernow consults the manifest-agnosticbyAssetindex before scheduling a fetch, and proves identity on what the cook is a function of: the asset key (id, kind, scale, selector) AND the visual-model URL the cook was derived from (ModelColliderCacheValue.byAssetSource, written by the cook). A manifest that re-points an id at a different URL, or a new scale, still fetches.findManifestModelEntrynow indexes each manifest once (WeakMap on the manifest object) instead of a linear scan per body per pass. - The payload-GET 429 behind a passing probe is typed as the probe's own quota verdict.
loadModelAssetanswered a 429 on the GET leg withAssetFetchHttpError(structural status only, no window), so the collider ladder'sreadVerdictWindowMsread null and the fetch rode the flat 5 s rung against a 10 sRetry-After, drawing a second 429 (3,603 such rows in one night). It now throwsMagicCdnFetchErrorwithretryAfterMsread throughreadRateLimitVerdictFromResponse(the lume texture and audio lanes' idiom), so the ladder parks for the server's window and a windowed 429 never counts toward the 3-failure degradation report. A 429 that names no window anywhere keeps today's ladder. Pinned red-first incollider-assets-relay-client.test.ts(zero fetches on a pure signature bump; re-point and scale changes fetch),collider-assets.test.ts(the GET-leg 429 parks 10 s, one re-probe per window, no "failed to load" line) andservice.test.ts(the error shape off the real asset service). - The cook's rig fact reaches the engine's look (ledger 2757 arm 3, 2997 by-design half; #14045). A creature model the cook decided not to rig (rig intent on a non-humanoid —
output_metadata.rig = none-by-design, #14032) now carries that fact in-band on the GLB asasset.extras.spawn = { rig, rig_reason }, stamped at the artifact write in the meshy and trellis lanes over the glb-splice container helpers (BIN chunk byte-identical; a refused container ships unstamped).gltf-parse.tsreads it intoParsedGltf.spawnRig. The scene-view asset note gains a creature clause from loaded models carrying the fact — "N creature model(s) is/are unrigged by design (names) — nothing failed and nothing is pending; their motion is procedural, not generated" — after the built-ins; these models never ride the generating/loading/failed buckets. Themodel-not-animatablediagnostic reads the same fact and says the verdict first ("no skeleton by design — motion is procedural"), then the move, whether or not the id asked for?animations=; unstamped bodies keep the existing sentences. Post-landing cooks only: a creature cooked earlier confesses through the serve header at the tool result until it is re-cooked, and the write-once base row the?animations=fallback stamps keeps its original bytes. - The dead per-voice reverb send is deleted (ledger 2984, the defect half).
AudioReverbZone(engine/components/audio.ts, encoder/decoder incomponents/formatting/audio.ts), prep's per-frame zone snapshot andevaluateReverbZone, thereverbWetfield on the mailboxstart/setcommands, and the renderer's per-voice send gain are gone. The send terminated in aGainNodewithgain.value = 0wired to the master bus — a surface that existed and did nothing — and the zone component had no writer anywhere in the engine or the ObjectAPI (no verb, no spec word, no skill; the only writer was a unit test). Consumer story: there was none, so nothing is renamed, wrapped, or kept for compat. The"audio/reverb-zone"row in the relay payload register's static name table stays, because that table is a wire format pinned by the register version and removing a row renumbers every later token. The demand half of the row — a listener-side per-bus effect lane — is a new API surface and stays a written paragraph in the ledger, not code. - A demolish that leaves exactly one collider in the world no longer kills rapier (ledger 2970, dig of Savi filing 177b8f4f / AFTERMATH 446712a1). parry 0.25.3 — the BVH inside the rapier 0.30.1 wasm we ship — corrupts its broad-phase tree when one step's removal batch takes it from three or more leaves down to exactly one:
Bvh::removeleaves the orphaned wide nodes in the node array "until the next refit", the root becomes a partial root, andoptimize_incremental(which rapier runs BEFORE the refit) treats the zeroed right half of the root as a node — the root's parent pointer points at itself and the next insertion walks the loop into a wasm trap (key not presentinbvh_insert.rs; the filing's "Unreachable code should not be executed" is JSC's spelling of the trap). Zero survivors clear the tree; two or more survivors rebuild cleanly (raw-BVH fuzz, 1600 runs per survivor count: only 1 fails, 26% — the rest of the odds is the tree's frame parity and which side of the origin the survivor sits on, which is why the dungeon's earlier raises "got away with it"). Upstream fixed it in parry 0.28 (dimforge/parry#409); rapier.js has not shipped it. The scene shape that reaches it: a place whose whole physics content is runtime objects (terrain off, no authored bodies) demolishing its floor with no player in the room — one body stands for a step, then the corrupted world dies on the first piece of the next floor, the corruption detector rebuilds it two ticks later, andapi.raycastanswers "no active physics runtime" in between. Fix (adapter-side, structural):createPhysicsRuntimegives every rapier world (3D and 2D) two inert sentinel colliders for its whole life — sensors with no collision-group memberships and no active collision types, 10 km below the playable space — so a removal batch can never end at one leaf and the corrupting state is unrepresentable. They never enterhandles/colliderToEntity; raycasts default toEXCLUDE_SENSORSand the character controller always excludes sensors, so no query path sees them; the server physics telemetry and terrain client-inspect subtract them. Red-first pinlone-survivor-demolish.test.tsreplays the demolish through the engine's own sync/cleanup/step across six BVH frame parities in 3D and 2D (red on the shipped wasm at parities 1, 3, 5 — "WASM world corrupted during stepPhysics:rapierStep"; green with the sentinels) and pins the sentinels as unmapped, never a raycast hit, and not counted as content. Real-product receipt on a second stack: a spec with terrain off spawning 60 statics, destroying 59 in one tick, then landing 3 more — the ecs-sync burst shows the 59 despawns, no corruption line, the sim keeps ticking. Named residual: the sentinels can be retired the day the rapier.js dependency carries parry ≥ 0.28; the pin stays either way. - An ended place instance no longer leaves its entity heads in the relay journal (ledger 3013 — SHUKYU DEN's dev room: 56,810 heads for a 5-place game with ≤ 3 live instances, ~750 more per instance minted, never fewer; 35–77 physics worlds per shell lifetime, most for places with no row). The row drop was the whole end of an instance (p-022df344: "a dropped row IS the teardown signal"), but on the relay tier the instance's ENTITIES were journaled as keyed heads by whoever hosted it, and nothing ever tombed them: the host's leave is a local scope teardown that publishes nothing (ledger 2921's Writer 1), and the seat that finally drops the row — the departed minter's, or the default place's host — never published them. Keyed heads' own bound ("heads is O(live world), not O(history); dead entities range-delete at despawn", plan 7b3d91c4 §6.1) did not hold for instanced places. Two mechanisms, both through the existing reap lane (
relay-reap-lane.ts— unstamped idempotent tombs by id, the grammar the room-deadline reaper, the ghost-destroy lane and the world-sync ghost sweep already journal through; its drain retires each id from the uploader's dead-row memos, so nothing double-tombs): (1) the row-drop tomb (place-instances.ts dropPlaceInstanceRows→place-instance-teardown.ts): on relay wire mode, before the row entity despawns, the dropping seat tombs every entity it HOLDS in the place (ingested husks, runtime spawns, its own hosted rows) ∪ every AUTHORED object id of the row's expansion against the UNFILTERED doc ({instance}:{bare}+ nested children — the ids the host instantiated whether or not this seat ever drained them), excluding member avatars, terrain chunks, local-plane / client-derived entities and any live member's envelope spawn; then despawns the held husks locally. The row drop is a real death, not a scope leave, so it does not ride the reconcile that stamps the local-scope-teardown mark. The authored-def-wins reconcile passestombEntities: false(a shadowed row's entities are the authored place's). (2) the orphan-heads reap (place-cleanup.ts relayPlaceInstanceOrphanReapSystem, client, order 141): entities whose PlaceMembership names a place that is neither authored nor a live row are tombed by the default place's host (or, when the default place has no host, any member with a complete view) — budgeted atRELAY_ORPHAN_REAP_BUDGET_PER_TICK(32) per tick, rescanning at most every 60 ticks and only when the authority epoch moved. It REFUSES ON ABSENCE: the verdict requires the newrelay-place-coverage.tsfact (the session live, and the place covered by the "*" backstop / the receive-all legacy drain / the place's own drain receipt) plus a settled spec view — "row not in my table" mid-drain proves nothing (a restated row's head sorts after the entities it minted; ledger 3010's class in reverse). This cures journals that already carry dead instances the first time a covering seat (the standing shell) folds them. Also fixed, caught by the pin: the local-scope-teardown mark (2921) missed NESTED authored children (parent/child) — the parent's cascade killed them before the reconcile loop reached their mark, so the leaving host tombed an authored row; the mark now runs as its own pass before any destroy (interpreter.ts). New:RelayPlaceCoverageProviderResource(wired in runtime-worker fromClientRoomRuntime.relayPlaceCoverage(); mirrored in the multi-client harness). Red-first on the relay harness (relay-instance-end-tombs.e2e.test.ts, both red on the pre-fix bytes): the host leaving publishes no delete for the instance's authored rows and the row drop tombs every head of the instance, in one test; a live instance's heads are untouched; a fresh receive-all joiner's drain carries none of the dead instance; a journal already carrying a dead instance (row tomb retired at a checkpoint) is reaped by the first covering seat; a live instance whose row folds after its entities loses nothing to the reap. Not in this landing: one predicate for "who owns an orphan row" shared by the sweep and the uploader (control-utils.ts— a decision surface; its own held PR), and a session row's local-tick deadline (ledger 3014). - The fx census stops counting contributors behind the camera (ledger 2991).
fxCensusCoverage(lume/particles/fx-census.ts) clampedviewDepthto ≥ 0.5 m with no sign check, so a bespoke-lane instance behind the camera (viewDepth ≤ 0) was estimated as a stamp 0.5 m in front of the eye. On soon's world 33 beast-track decals (0.35 m unit-cube volumes, up to 200 m behind the player) read asdecal (bespoke) · 33 alive · ~100% of framein Savi's look caption (filing 7b04e9a7), and ranked first in the ladder's weighted census while drawing nothing visible. The particle driver frustum-culls each population before it estimates; the bespoke lanes (decalsdecals.ts, slash/shockwaveeffects.ts) had no equivalent. The rule now lives once infxCensusCoverage:viewDepth ≤ 0returns 0 before the clamp, so every caller inherits it; in front of the camera the estimate is byte-identical to before (the 0.5 m clamp still holds).reportBespokeFxCensusalready drops zero-coverage frames, so a lane whose every instance is behind the camera leaves the caption entirely. Not covered here: an instance in FRONT of the camera but outside the lateral frustum still counts (the callers pass only a view depth); that is the next class if it shows up. Red-first pins:fx-census.test.ts(−200 → 0, 0 → 0; 0.5 → today's 0.06125 for a 0.35 m stamp; 0.1 → today's clamp; the full-frame cap),decals.test.ts(a 0.35 m decal 10 m ahead reportsalive 1, decal cost weight and the real coverage; the same decal 200 m behind reports nothing). - The CPU fx backend's silent count truncation now has a tell (ledger 3022 b, dig c7a57367). A population's authored persistent
count:above the CPU particle backend's per-population budget ran truncated with no word to the script:count: 5000yielded 3000 live and nothing anywhere Savi reads said so (engine/particles/cpu-backend.tsspawnPersistent). The backend now fires a new init-ctx hook,onFxPopulationCapped(FxPopulationCapInfoinengine/particles/backend.ts), once per population per spawn timeline (reset withpersistentSpawnedon a version bump), when the population has filled to its cap AND the engine's baseline rail did the clipping — the 3000 default with no authoredmaxParticles, the 5000 ceiling with one. A creator's ownmaxParticlesclipping their count is their vocabulary and stays silent; a quality-ladder scaled default below the rail is the governor's action and stays silent (perf-notify law), while a count above the baseline rail still reports under a held scale with the baseline numbers. The renderer store (lume/particles/store.tsreportFxPopulationCapped) carries it onto the #848 pressure tell (reportFxPressureEvent) under the already-allowlistedfx-cpu-fallbackcode — warning class, so getLogs and never a DM — with the newfxCpuCapMessagebody ("asked for 5000 particles (count:) and runs 3000 on the CPU particle renderer — its per-population budget") carrying the #297 consent rule, both sizes in the data payload, and an entity-independent dedupe key so fifty spawns of the same over-budget effect say the fact once per session. The 3000 default and the 5000 ceiling are unchanged — they are the recorded budget, and this line only names it. Red-first pins:fx-cpu-cap-tell.test.ts(5000 → exactly one report with both numbers; 3000 → nothing; ceiling; authored-cap silence; governor silence and governor-over reporting; version-bump re-evaluation; hook-less backend unchanged),pressure-telemetry.test.ts(message body, code, payload, dedupe), and the #297 consent-coverage + 500-char delivery pins inengine-diagnostics.test.ts. - Ground sampling no longer rebuilds the voxel column sampler's expensive parts per sample, and a cold apply mints each ground recipe once (ledger 3003 — World of Spawncraft's return to
main, one 201 s synchronous applySpec on the creator's browser, 20–93 s on the shell). Everyctx.groundY/ctx.groundNormalcall from a scripted-primitive generator runssampleVoxelColumnSurface, which builds acreateVoxelMaterialSamplerfor its 1×1 column. That constructor re-keyed every structure template of the definition throughJSON.stringify(compileSerializedVoxelTemplate's content cache key — 18 templates of thousands of voxels each) and rebuilt the edit lookup over the place's whole edit set (buildVoxelEditLookup(getTerrainPlaceEdits(…))) — per sample, 10.5k samples on a visit-all apply of that world, 27k cold. Two identity memos remove both: the compiled template is cached by the template object (the content cache stays behind it, so a fresh object with the same content still compiles once), and the edit lookup is cached by the identity of the place's edit array —getTerrainPlaceEditsmints one fresh, never-mutated array per edits version, so a live carve turns the memo over on its next sample (pinned);TerrainChunkEditsValueobjects are deliberately not memoized, because the automata fast path grows an owned.editsarray in place. The column scan also asks the lookup once whether any edit touches its column and skips the per-cell probe when none does (same answer, no probe), and the probe itself is oneMap.get. Separately, the terrain-installed reload arm of applySpec re-minted everyusesGroundrecipe that the spawn loop had minted moments earlier in the same apply against the same freshly installed definition — 789 derives for 398 primitives on a cold apply; rows spawned in the current apply now skip that arm (pinned: 2 derives for 2 primitives cold; a later terrain edit still re-mints the standing ground recipe and only it). Behavior preservation receipts on World of Spawncraft's head spec: every recipe tape, geometry signature and position/index hash, every component of every entity in the applied world, and a 16,920-column direct dump ofsampleVoxelColumnSurface+sampleReplicatedTerrainHeight(fractional lattice, with 0 / 5,800 / 29,000 edits) are byte-identical before and after. Bench (same spec, same box): visit-all 2.87 s → 1.26 s (ground 2.01 s → 0.31 s); visit-all + 5,800 edits 20.3 s → 1.16 s; + 29,000 edits 104 s → 0.90 s (ground 100 s → 0.21 s); cold 6.8 s → 1.37 s (derives 789 → 527, samples 27,072 → 15,786). What remains in the visit-all apply is the generator scripts' own execution (~0.4 s) and compileSpec (~0.2 s). Not in this landing: the anchor-landing redrape still mints parented ground children twice on a cold apply (the spec-apply spawn lane does not mark the unresolved-anchor window the object-api lane marks — ledger 2770's design; 129 of the 527 cold derives). - A host with no renderer loads the models its entities wear, so the model-derived reads answer from a loaded model there (ledger 3001 — ravi's bench on the #14031 amend:
api.getObjectBoneNames(id)read[]for a 40-bone rigged horse on the RoomHostContainer's shell, five cells out of five, and Savi wrote "no bones"). The reads —getBoneNames/getObjectBoneNames,getBones/getObjectBones,getMorphTargetNames/getObjectMorphTargetNames, the ragdoll skeleton check, IK bind anchors, geometry-fit's bounds fallback — are synchronous and answer from the sim-side asset store (engine/assets/service.ts,getModel). That store loads lazily: a read of a model nobody loaded yet starts the fetch and answers "nothing" that once. In a browser the renderer worker holds every model and the sim store's laziness is a memory saving; on the headless room-host shell (the container the studio summons when no creator tab hosts the room) nothing else ever loads a model, so a one-shot run_script probe read[]every time. ThehasDomguard inpreloadAssetwas not the cause — every liveloadManifestcaller passespreload: false, in browsers too — the cause was that no path on the headless host loaded a model before a read asked for it. Nowtome/model-warm-hooks.ts(mounted by the tome feature on every non-relay world) asks the asset service to warm a model the moment an entity gets aDrawModel/DrawAnimated3DCharacter(add, model swap, and the entities already present at mount), and the newAssetService.requestModelWarmlane loads them in request order with at most two loads in flight (HEADLESS_MODEL_WARM_MAX_IN_FLIGHT, the renderer warmer's figure) — a big world trickles its models in instead of fanning out at boot (the collider lane's storm class, ledger 3000).requestModelWarmis a no-op in browser realms (window or WorkerGlobalScope present): a tab's sim store stays exactly as lazy as before, and the relay's asset stub no-ops it too (the relay loads no models by design). Loads share the existing store, so the collider lane's local-cook fallback (ensureModel) hits a loaded model instead of fetching again. What this does not change: the reads' return shapes ([]still means "no bones in a loaded model" OR "not loaded yet" at the API — the store can tell the two apart internally, the contract does not; a distinct not-loaded verdict is a contract decision, not made here), and a probe inside the first moments of a host's life can still land before its model's fetch finishes (that read still starts the load, so the next one answers). Pinned red-first intome/__tests__/model-warm-hooks.test.ts(a DOM-less world with the real asset service: a rigged fixture answers["Root","Tip"]on the first read with no prior read warming it, an unrigged fixture answers[]with the store holding a loaded skeleton-less model, a model swap warms the new model) andengine/assets/__tests__/service.test.ts(browser no-op; request order, two in flight, dedupe, drain past a failed load). - The helper body never wears the "player" tag, whatever the player template lists (ledger 3006). Two engine writers put the literal "player" on the standing seat's helper body when the player template listed it in its own
tags(gfta's 1-2-many-goblins does; the game-manager examples teach it):spawnPlayerspread the template's tags after the class tag (["helper", "player"]), and the spec re-apply's template merge ininterpreter.tswrote"player"as the class word for every session body (["player", "helper", …]the moment the creator edited the template). Either wayquery({ tags: ["player"] })returned the builder — the exact face the third class removed (ledger 2141) and the opposite of whatgetPlayersteaches. One function now owns the list (tome/session-body-tags.ts): the body's verified class (SessionAvatarClass) first, a re-apply's existing live tags next, then the template's own game tags with both class words stripped.getPlayers/getHelpersalready keyed on the stamp and are unchanged. Pinned red-first inhelper-join-third-class.test.tsand verified in a running room with one human and one room_host shell. - A host-destroyed row tombs even after the seat changed hands (ledger 2956 — the ghost scatter children, Cobra 2D f3a58f75). On the relay tier every non-avatar, non-terrain entity the host spawns journals as a keyed head (
relay-wire-session.tsisKeyableEntityId) and every fresh fold — every reload — replays it. Its DELETE reached the journal only through the uploader's include filter, whose dead-entity branch read one thing: the published-entity memo (engine/runtime/client/runtime-client.tsclientAuthPublishedEntityIds), and the claim bootstrap-adopt clears that memo on every host-seat assignment or epoch bump. So a bed stroked under one tenure and removed under the next (the tab hidden and shown between them — 2930's seat trades) destroyed its scatter children in every live world and journaled nothing: the heads outlived the world and the hosting tab replayed 77 tufts and an empty bed at every reload, withapi.destroyclearing them only until the next one. The exposed destroys were the DIRECT ones — the spec interpreter's old-vs-new reconcile (Savi'sdestroymutation on the bed, the spawn-kind undo) cascading a bed's runtime children throughdestroyEntityWithHook;api.destroyon a truth relay already tombs through the ghost-destroy gate family's reap lane (object-api.tsjudgeRelayGhostDestroy). The cure is a simulated-despawn ledger beside the memo: at the despawn dispatch — the entity's components are still on it — the uploader records whether THIS client simulated the entity right then (own envelope, hosted remainder, sim lease: the same predicate the live branch publishes by), and the include filter ships the dead row's delete when the ledger holds it, regardless of the memo. Fed only by despawns outside aningestRoomDeltapass (room-delta-ingest.tsisIngestingRoomDelta— a depth-counted bracket around the wire apply): a peer's or the server's delete, the reset sweeps and the membership tombs all land inside one, so a foreign authority's row despawned locally never echoes. A local spawn that dies before its create ever drained is skipped (Stage A's die-fast law is unchanged: nothing on the wire, nothing to tomb); a respawn at the same id voids the record; every cursor move that skips the log (adopt, bootstrap, reset, the relay Ready re-adopt) and the end of every drain clears it. Pinned end to end on the production-parity relay harness (__tests__/relay-host-destroy-tombs-after-seat-flip.e2e.test.ts): stroke → seat away and back → bed removed from the doc → every child's@chead tombed by the host alone, a fresh fold materializes none, and the non-host peer that destroyed its own replicas published nothing; two controls (no seat trade; the Ctrl+Z path) stay green on the parent. - The client hierarchy solve's realm gate now covers what the client simulates, not only what it predicts (
tome/systems/hierarchy-solve.tsrunHierarchySolvePass:isClientPredictedEntity || isClientAuthLocallySimulatedEntity; the server-side gate is unchanged). On a relay client that holds a place's seat, the hosted remainder (Stage B) was excluded from the simulation-phase solve (order 50) and the post-physics solve (order 10001). What this changes is small and specific: readers inside the simulation phase — behaviors, the spatial index, the dispatch systems past order 10050, andsolveChild's static/kinematic body sync to rapier — now see a hosted child's World* refreshed in-tick, where before they saw it one renderPrep behind its root on every tick and N ticks behind across a catch-up burst (runtime-client.tsrunOneStepskips renderPrep on intermediate ticks). What this does NOT change: production clients already mounttome/hierarchy-render-solve(renderPrep, no realm gate), which recomposes every parented child's World* from its parent each frame the parent moved, so hosted children already followed their root on screen, on the wire and in the adoption restate before this gate changed. The earlier text of this changeset claimed hosted children "froze at their attach-time World*" on every replica; that read came from a harness pin that mounted a partial stack (no render solve, no post-physics solve) and is withdrawn. Pin re-derived with all four production transform rails mounted:relay-seat-move-parented-children.e2e.test.tsreads each hosted body piece against its root from a simulation system at order 10002 on the hosting seat — red on the parent tree (one tick of root motion, 0.0167 m at the harness's 60 Hz) and green here, across ordinary ticks, a six-step catch-up burst, and four seat trades. Follow-up, not fixed here: the simulation solve and the render solve disagree at the f32-ulp level on a composed child's y, so a moving hosted child now takes two World* set events per lane per tick on the host (three with the post-physics solve) — same wire (the drain collapses same-window rows), more change-log churn. - Orphan pose pinned, no behavior change (ledger 3029's named candidate, refuted). Both hierarchy solves skip a child whose parent is not in the world, so an orphan keeps whatever World* it has.
relay-orphan-child-pose.e2e.test.tspins what that is on the client-apply path with every rail mounted: a body whose root's create never landed stands where the host has it (the wire row carries World*) and keeps following the host's motion updates; a body whose root was retired and re-minted under a new id, with the old tree's deletes lost, holds its last pose at the spot the root died. Neither shape puts an orphan at the local frame read as world or at the local player. The renderer's composed-child rail emits no synthetic pose for an orphan (synthetic-transform-delta.test.ts), so the raw wire World* is what it draws. The creator symptom on ledger 3029 (other creatures' body pieces drawn on her own rabbit, on her client only, during seat flaps) is not explained by anything in this landing. - The authored-UI morph now produces exactly the rendered html's node set — an id'd node the new html no longer contains is removed even while another node still wears that id (ledger 2953, Savi filing 6c225e0f, Hexroot 5.2.37).
engine/ui/tome/realm/frame/apply-render.tsmorphs each render into the live container with morphdom, which defers the removal of a keyed node it could not place and resolves that removal BY ID at the end. A DOM holding two nodes with one id (a render that authored the id twice for one frame, or any earlier render's leftover) has one lookup entry; the node the morph matches consumes it, and the other copy survives that morph and every later one. On Hexroot the creator's vitals plate stood twice at one fixed position (invisible: exact overlap); when the plate lostposition:fixedthe orphan fell to (0,0), and when the plate moved into a new#hexroot-vt-stackwrapper the orphan stayed at top level through hundreds of "morphed" applies — the census read two#hexroot-vitals, one inside the stack and one the html did not contain. The morph now records every node it matched (onBeforeElUpdated) or added (onNodeAdded), remembering whether the update was refused (a frozen subtree it never walked), and after morphdom sweeps every[id]element in the container: claimed itself, or below a frozen ancestor — legitimate; below an updated ancestor with no claim of its own, or with no claimed ancestor at all — a node the html does not contain, removed (media unload runs on it like any discard). One[Tome UI] removed a stale #<id> …line per id names what was swept. A duplicate id the CURRENT html authors is the creator's and both copies stay. The reparent itself (an id'd node moving into a new wrapper) was already correct in morphdom; it is pinned beside the sweep. Red-first inapply-render.test.ts(one-render duplicate → one node after the next render and every later one; the creator's shape: two plates at one position → plate static → plate into a stack ends with one plate inside the stack; a duplicate buried under an updated parent is swept while nodes under anisEqualNode-frozen parent are untouched). - identify_object and the look now say "in the store, not in the draw" and "drawn, but flat — the texture budget refused the map" for a live primitive (ledgers 3012 and 2978, half B of the 3012 dig). Two states of the primitive lanes read as "rendered, real bounds" on every query surface. (1) A slot the store holds and picks —
raycastSlotsandslotWorldBoundsread slot state — while the lane's DRAW SET (the membership tables the main pass windows, the shadow pass and the capture path all draw from:drawLiveCounts, the survivor windows' caps, the CPU active list) does not include it:PrimitiveLane.slotInDrawSetreads that table (slotToShadowPos),membershipPendingsays whether a rebuild is owed.primitiveDrawSuppressionanswers{ reason: "lane-draw-set-excluded", lane }when every part of a representation is excluded and no lane owes a rebuild; a fresh allocation (rebuild pending by design) and a partially drawn bespoke body stay null. The filing: 54 spec-baked walls solid and ray-pickable at 41 m, terrain drawn 123 m behind them, identify silent. (2) A holder whose declared albedo is resident on the client but draws layer 0 because the page admission refused it (TextureLayerHolder.textureDegradeis stamped at the flat floor in both the primitive and bespoke sync paths with the admission's reason — budget/profile/unsupported — and whether the direct pool refused it too; cleared at every re-resolve):{ reason: "texture-degraded", textureId, cause, poolRefused }. The one member of the union that means drawn-but-wrong, not undrawn; a viewmodel never tries the pools, so its whole story is the array refusal. Both ride the existing lane — identify'shidden, scene-view'ssuppressed— so the look's framed-object note speaks them too (framedObjectSuppressionNotePart, interim wording marked for ravi). The verb's own tool description in cf-studio-chat is not touched here. Test seams:PrimitiveLane.dropSlotFromDrawSetForTests(the standing excluded state is unreachable through the mutation API by design) andsettleMembershipForTests;peekEntityLaneSlotForTestson the store. Red-first: store (both verdicts, the pending-is-not-a-verdict guard, the untextured-solid control, the textured-neighbour controls), scene-identify (kind and bounds kept beside the verdict), scene-view suppressed note (both sentences). Half A — why the 54 walls left the draw set on a fresh load — is not cured here; the dig report names what the render harness reproduced (nothing: the direct-pool and array-lane late-spawn paths draw) and what is excluded. - A pending intent whose target's seat moves ONTO the sender is accepted as "own", never dropped as
target_unavailable(ledger 3024 — TERRA's handoff tape,[relay-peer-apply] synthesized a target_unavailable verdict for a pending intent on "terra-delta-core"15 ms after every[relay-elect] host(main) -> <creator>). The relay tier's verdict-silence sweep (relay-peer-apply.tssweepRelayIntentVerdictSilence/classifyVerdictSilence) claimed to mirror the kernel's intent answers, and forauthority === selfit synthesized a DROP. The kernel's own answer for that race is an ACCEPT —client-auth-intents.ts, the dispatch'ssimulator.clientId === ctx.clientIdbranch: "Ownership/hosting moved to the SENDER while the intent was in flight: its optimistic local apply IS the authority now … accept as done",ack(ctx, "own"). Under the drop the sender's intent context honored the verdict by REVERTING its own optimistic write (the stored inverse — on the one machine that now owns the truth) and killing the previews held behind it, with a confession naming the target's simulator "unavailable" while the target's simulator was the sender itself. The sweep now emitscmd.ack {ref, info:"own"}for that entry: the pending buffer resolves accepted, the held effects commit, nothing reverts, no diagnostic line (an accepted write is the ordinary outcome of a seat moving onto the writer). The re-statement lane: the accepted write's change-log row drained while this client was not the target's authority (the include filter dropped it) and the seat's arrival adopts the upload cursor past the whole unread window, so no ordinary drain would ever ship it;client-auth-intent-effects.tsgainsqueueClientAuthOwnAcceptedStateUpload, andruntime-client.tsappendClientAuthUploadRepairsre-states the entity's liveTomeStateas one UPDATE row on the next publish — the same repair lane promoted spawns already ride ("uploads through the normal drain", the kernel's words). State kinds only (set/inc— the intent vocabulary's component is TomeState). Every other sweep answer (unhosted,unknown_target,timeout,target_parked) is byte-for-byte unchanged. Red-first pins:relay-peer-apply.test.ts(the self case answerscmd.ack own, no warn),__tests__/relay-intent-own-ack-on-seat-move.e2e.test.ts(two engine clients over the relay double: the guest writes the host's manager with a spawn held behind it, the brain reseats the place onto the guest, only the guest steps — accepted 1 / dropped 0, the write stands, the spawn survives, both reach the deposed seat when it wakes, no confession; control: a seat that moves to a THIRD seat keeps the entry pending on the kernel's 150-tick clock and times out exactly as before). - A joining client syncs the spec's
defaultPlacefirst, not the literal placemain(ledger 3015). Before its own body carries aPlaceMembership, the client's first spec apply has to guess which place to keep.getPlayerPlaceIdForSync(tome/systems/spec-sync.ts) guessed the literal"main", andresolveKeptPlaceIdkeeps any place that exists — so a game whose default place has another name but also authors a place namedmainbuilt the wholemainplace on every boot and destroyed it one tick later when the body landed in the default place (SHUKYU DEN: 663 objects, 2.2–3.4 s, then 605 destroys). The fallback now readsdefaultPlacefrom the doc about to apply, then the applied doc — the same precedencemergeGameSpecgives the field — and"main"only when neither names one, which is the field's recorded default (types.ts: "controls where new players spawn (defaults to "main")"). A doc with nodefaultPlace, or one whose default ismain, is byte-identical to before. No wire shape moves; the relay's ownplace=join declaration is untouched. Pins inspec-sync.test.ts: a two-place doc withdefaultPlace: "lobby"syncs lobby on the body-less first apply, the body landing in lobby fires no place transition and despawns nothing; a doc with nodefaultPlacekeeps themainfallback. Receipt on a second stack with a 60-objectmainand a 4-objectlobbydefault: before, the client's apply episodes readobjectsTotal 60, spawned 60thenobjectsTotal 4, destroyed 60; after, oneobjectsTotal 4, destroyed 0and no 60-object apply anywhere. - A hardware Play/Pause key no longer pauses the music element behind the engine's back (ledger 2983 — Savi platform-gap e58709c1, Spell It Right or Don't?, 5.2.39). Music priced over the decode budget (
MAX_BUFFERED_CLIP_BYTES, ~43 s stereo) rides anHTMLAudioElement; the renderer registered nonavigator.mediaSessionaction handlers, so the browser answered a keyboard media key, headset button or lock-screen control by pausing that element itself — the sim ran on,music.now()(authoritative, tick-anchored) still said playing, no script could see it, and the same track under the budget (a buffered voice) ignored the key. Which lane a track rides is a decode-budget detail and must never leak into behavior. The renderer (engine/audio/renderer.ts) now owns theplay,pauseandstopaction handlers for its lifetime (armed instart(), cleared indispose()), so the browser runs the page's handler instead of its default and the element keeps playing; the handler re-asserts playback throughtryStartMediaon any owned stream element a UA paused anyway, skipping elements the renderer stopped itself or that ended. No new API: pausing the sim on the key would be a game decision (a new event surface), and per-client element state cannot be folded intomusic.now()without changing the music design of record (docs/music-api-design.md: target-only authoritative state, positions derived from ticks, never client-reported). Pinned red-first inrenderer-media-session.test.ts. - onPlaceStart fires again on relay rooms — the relay stamps room freshness on the welcome (ledger 2987; tide's seam ruling). Since the room-truth dial landed (2026-08-06, #12407),
onPlaceStarthad not fired on any production relay room: the lifecycle seat (relay-lifecycle.tsB2) judged "genuinely fresh" aswelcome.snapshot === null && welcome.catchupThroughSeq === 0, but under truth the relay journals the joiner's OWNm:*membership head beforecompleteJoinreads the welcome's ceiling, so a fresh room's first welcome carriescatchupThroughSeq: 1and read as lived — on every engine seat, shell included. Prod 7 d:"onPlaceStart fired"= 0 rows against 53,462[relay-lifecycle]rows. The cure is the wire contract, not a kernel subtraction: only the relay holds "this join is the room's first ever" exactly, and a kernel that subtracts its own row is inference over a count that moves again the moment anything else journals at join. Relay half (cf-relay):completeSocketJoinreadsstampedSeq === 0as its FIRST act — before the membership mint, the clock arm, or anything else this join writes — andcompleteJoincarries it aswelcome.roomFresh(true OR false) toward exactly the sockets whose dial advertedtruth ≥ 3(RELAY_TRUTH_ROOM_FRESH_MIN_LEVEL); every other welcome stays byte-identical.stampedSeq === 0is "nothing has ever entered the room's total order": no frame, no checkpoint, no reset marker, no relay-authored row, nothing pending in the group-commit window — the conservative side, because a wrongly firedonPlaceStartre-runs a terrain restore over a lived world. Binary lane:WELCOME_EXT_ROOM_FRESH = 0x10with a u8 0|1 slot (two-valued: 0 is "lived", never absence; out-of-range fails closed), gated on the advert the same wayWELCOME_EXT_ROOM_MSis, so no fielded decoder ever meets the bit unadverted. Kernel half (cf-kernel):RELAY_ROOM_TRUTH_PROTOCOL_VERSION2 → 3 (the hello adverts it);RelayWelcomeInfo.roomFresh(JSON lenient, binary mirror inrelay-wire-binary.ts); the seat's verdict iswelcome.roomFresh ?? (snapshot === null && catchupThroughSeq === 0)— the stamp when the relay speaks one, the legacy reading otherwise — and the fire line names which reading produced it (verdict from the relay stamp/legacy count). Fire-once is unchanged: the verdict latches at the first welcome, so a resume, an F10 redial, a seat move, a post-compaction join, and a second shell all arrive stamped lived and never re-fire. Both halves are safe alone: old kernel + new relay never sees the field (the advert gate); new kernel + old relay meets no stamp and keeps today's verdict. The relay half deploys with master; the kernel half rides the next engine version. Pins:relay-lifecycle-hook-spawns-reach-every-seat.e2e— theit.failsred pin is nowit(red on master with the exact assertiononPlaceStart fired exactly once: expected [] to have a length of 1 but got +0; green with the fix) and the production dial gains the non-fresh faces: a second seat, a late joiner, a post-compaction joiner, the shell's own F10 redial, a second shell process after the first departed, a player-first room, the shell's graceful depart — each exactly one fire or none;relay-lifecycle.test.tspins the stamp in both directions (true fires atcatchupThroughSeq 1; false refuses even at0/null; absent falls back to the legacy reading) and the latch across stamped welcomes;relay-wire-binary.test.ts+ cf-relaybinary-wire-vectorsgrow the maximal fixture (theRequired<>enumerator caught the field at compile time) and move the fail-closed bit to0x20; cf-relayroom-truth.workerd-test.tspins the stamp on the real DO — first-ever join stamped fresh atcatchupThroughSeq 1, every later join (second identity, supersede, post-truth<3, post-v5-write, post-reset, post-eviction wake) stamped lived, andtruth ≤ 2/ v5 dials carry no field. Named residual: a player-first room is still not placeStart-covered (the shell arrives at a lived room) — the standing boundary, now stated by the relay instead of inferred. - An author's OWN journaled fact now reaches its own election fold when its ack lands behind a higher-seq peer row of the same relay window. The relay fans a group-commit window as one container per subscriber and acks each sender only AFTER the window's fan (cf-relay
relay-do.tsfanWindow— relay-egress-ladder Move A, default on since 2026-08-07: the ack must never precede a LOWER-seq peer row of its window). The ack names the row's true journal seq, so when the author's row N and a peer's foldable row M > N share a window, the author receives M before its receipt for N. The election fold (relay/relay-place-elect.ts) is the author's only live sight of its own rows — fan-out excludes the sender — and it folded that receipt through the strictly increasing peer cursor written against the pre-Move-A order ("the relay acks N before it fans M > N"): the cursor already read M, and the own fact at N was dropped from the one fold that authored it, while every peer and every late joiner folded it. Own PlaceMembership moves left the author homed in its OLD place (seat tables forked: the author seated a peer in the place it stood in), the deliberate depart's own tomb never settled at the departing client (the headless host's leave pollssettledfor 2.5 s and gives up), and a visible-again presence fact left the author reading itself hidden (the id-ranked agreement seats — despawn coordinator, world-sync answerer — then named different members at different observers). Any foldable peer row in the window triggers it: a place move, a presence fact, any delete row (a host despawning bullets), a relay-authoredm:*orh:*row. The fold now has two doors: the SEQUENCED door (peer rows, replays, relay rows) keeps the strictly increasing cursor; the RECEIPT door (foldOwnFact) applies an own fact at the seq its ack named regardless of the cursor — an author cannot race itself, its receipts arrive in its own seq order, and the peers' rows they interleave with only touch other members' records — and advances the cursor only when above it (the fold-fingerprint stamp stays "the max seq folded"). One own row still folds exactly once: a row the catchup replay already folded is remembered against the retransmit's re-ack at the same seq (replayedOwnSeqs, bounded byRELAY_OWN_REPLAY_MEMORY). The per-place seat break is now a monotone max (noteSeatBreak) so an own fact folded behind the cursor lands the epoch every in-order observer holds. The relay harness's hub double models window fan-out (__tests__/relay-multi-client-harness.tssetWindowFanout: the window's fan, then per-senderacks), and with it the newrelay-own-fact-window.e2e.test.tsandrelay-place-elect-own-fact-window.test.tswere red on master engine bytes. No relay change: Move A's order stands for the reason it names. - A parented spline bakes its ground reads in the parent's frame (ledger 2972 — BT's sky fences, Paws & Reels 5.2.38→.39). A spline row authored as a child (
parent:set, or a${parent}/${child}id) lowers in its parent's frame: the owner's feetPosition and points are local offsets, the generated__spline__*defs carry those local poses, and the interpreter re-parents them under the owner's parent by the id convention. Every step of that is frame-neutral except the ground read.snapToTerrain(andy: { terrain },y: "surface") sampled the LOCAL x/z as if it were world and baked the world height as a LOCAL y — so a yard fence parented to a cottage at terrain height (world y 12) stood at world y 24. The ground seam (splineGroundY) now composes the parent chain for every read: local column → world column → sampled world height → local y. The chain composes from the spec rows being lowered (the interpreter passes this spec's rows; the re-loft paths fall back to the current GameSpecResource), with the same seed asattachEntityToResolvedParentand the samecomposeHierarchyTransformthe hierarchy solve uses, so every world lowering the same spec bakes the same bytes; an ancestor with no row falls back to its live world pose; a dangling parent keeps today's world-frame lowering. Root owners keep the identity frame — their lowering is byte-identical (23 kinds × 5 point variants, 2119 lowered defs). Parented rows leave the lowering memo (their ancestors' poses are not keyed), like lookAt rows. Semantic decided:snapToTerrainon a child means the ground, not the parent's surface — a fence child under a platform held at y 20 snaps to the terrain below it. Not folded: an ancestor'spivot/attachmentanchor. - The park report on every diagnostics read now carries the tab's recent hidden-tab parks (ledger 3029 — Coelho Selvagem: a "visible" tab whose host seat traded to the standing shell 23 times in 18 minutes). The creator had three dev-room tabs open in one browser session and rotated among them; each tab's frame loop parks while hidden by design (
shouldSelfScheduleWorkerRuntimeFrames), so its sim stopped ticking and its host-tick beat went silent while the heartbeat timer kept firing — the relay reseated the standing shell exactly 15 s into every park (stale-replaced) and handed the seat back on the first tick after resume (first-beat-takeback), in all three rooms. Two digs read the tab as visible because the dump'sparkedblock only echoes a park for 15 s after resume (SPEC_APPLY_PARK_RESUME_ECHO_MS): the two dumps were captured 7 s and 16 s after she tabbed back, one said "park of 8 s", the other said nothing, and the park timeline had to be rebuilt from[sim-health]tick windows in Datadog. The fact the runtime worker tracks (tome/spec-apply-park.tsSpecApplyParkFact) now carries a bounded ring of the most recent completed parks (recentParks,pushRecentPark, depthSPEC_APPLY_PARK_RECENT_MAX= 8), andderiveSpecApplyParkReportnames every park that ended insideSPEC_APPLY_PARK_RECENT_WINDOW_MS(5 min) asrecentParks(start, end, length) plus one sentence in the note: the windows, that the sim did not tick inside them, and that a seat trade lining up with one of them is the park and not a wedge. A tab past the echo but inside the recent window now mints aparked: falsereport ("not parked now: the tab has been visible for 16s; this answer describes a live, ticking world." + the list) where it minted null before; past the window the report is null again, so a tab that parked once at boot still carries no standing confession. The parked and echo sentences are unchanged and gain the list behind them. The untrusted-wire sanitizer bounds the list (ring depth, bounded numbers, malformed spans dropped, empty survivor set omits the field) and its note cap rises toSPEC_APPLY_PARK_MAX_NOTE_CHARS= 1400 so the longest honest note passes whole. Consumers (engine-bug-report,client-sight, the authored-UI receipts, the HUD ledger) relay the block as before. Red-first inspec-apply-park.test.tswith the receipt's own numbers (the COELHO tab hidden 18:57:24–18:58:15 and 18:58:36–18:59:08 on 2026-09-03). Not in this landing: the seat-trade window and the takeback hysteresis (ledger 2828's forks for tucker), and nothing on the beat path — the engine's beat did what the 08-31 ruling says for a parked tab. - A player's onSpawn children reach every other seat on a fresh relay join (ledger 2980 — iz's Marrowgate bodiless wizards, Savi filing 6124ef7e, dig 7368b41d A(i), 5.2.39 relay). On the relay fresh path
ensureSelfSpawn→spawnPlayerruns the player template's onSpawn synchronously (tome/lifecycle.tsconfigurePlayerEntity→runPlayerOnSpawn) before the Ready grant adopts the client-auth upload cursor at the log end (runtime-client.tsadoptClientAuthUploadBaselineForRelay). Whether the hook's spawns sit behind that cursor depended on frame cadence: the self-spawn runs from a timer callback, so its rows are pending mutations, and a rendering tab flushes them into the change log on the next render-only frame (runtime-client.tsrunFramewith zero accrued steps →renderer/ecs-sync.tsflushPendingToChangeLog) — before the next sim step's netcode ingress applies the Ready welcome. Every child the hook built (a rig withparent: api.id, weapons, pets, name tags) then had its CREATE row behind the cursor forever: the root rode the join announcement (buildRelaySelfSpawnCreateMessage), the children existed only on the machine that built them, and their per-tick rows arrived at every peer as updates for unknown ids, dropped silently (room-delta-apply.tsspawnIfMissing=false). Symmetric for every seat. The cure is the repair that already exists for the parked-onSpawn drain's identical class (drainDeferredObjectOnSpawns— "the world it builds would exist only on this machine"): the relay self-spawn's onSpawn now runs underTomeSpawnCaptureResourceand every captured child is queued throughqueueClientAuthPromotedEntityUpload, so the uploader ships it as a synthetic creates-only snapshot on its first publish after Ready (appendClientAuthUploadRepairs), which also replaces the normal drain's create rows for the same ids in the same publish — one create per child on either cadence. Both producers share one helper (withSpawnCaptureForUpload). Gated onspawnPlayer'slocalSessionoption (the relay client's self-spawn); server worlds — the kernel and container tiers — never pass it and are byte-identical. The F10 rebase branch never callsspawnPlayer, so its envelope announce (ledger 2782) stays the one return statement. Chosen over sending the envelope announce on the fresh path: the envelope walk deliberately excludes world-scoped spawns (a pet or totem the body script makes withoutparent), which this class also loses; the capture covers every spawn the hook makes, including nested ones. Noted, not changed:player/…-prefixed rig parts stay off the keyed heads by the fold constitution (relay-wire-session.tsisKeyableEntityId— avatar lifecycle belongs to membership), so after a compaction a late joiner receives them from the world-sync answerer's live statement, which now holds them; the harness pins that path. The relay multi-client harness gainedsettle(..., { renderOnlyFrameBeforeStep })— the production cadence it had compressed away, under which the fresh-join pins were red on the parent (0 of 5 children at the peer) and are green now (__tests__/relay-player-onspawn-children-fresh-join.e2e.test.ts). - The private room transport detaches the dial on
close()(engine/client/worker/private-client-transport.ts) — a condemned socket's trailing frames no longer re-enter the first-frame sniff (staging receipt d6989b0e).ClientTransport.close()is an owner-driven teardown that detaches the handlers (runtime/transport/types.ts), and the direct browser transport keeps that contract by nulling its socket. The worker-side port transport did not: after the worker postedclose, the dial's already-posted traffic — a relay catchup burst coalesced in the bridge's outbox or already on the port — kept arriving stamped with the CURRENT dial, so the stale-generation bar (row 2515) admitted it and the runtime ran it on a socket it had just condemned. After a 4437 wire-grammar close the wire mode is back to "undetermined", so each trailing binary frame re-entered the sniff, resolved kernel (not a welcome), failed the kernel parse, decoded as relay speech, and closed 4437 again: one receipt run logged sevenrelay-grammar frame on a kernel-mode socketcloses in 25 ms and awire-tier stamp flipped true → falsethat held travel/uploader semantics on the kernel tier for 1.3 s until the redial's welcome flipped it back.close()now marks the dial closed until the nextconnect();open/message/messageBatch/closefor the closed dial are dropped, exactly as the direct transport drops a closed socket's late events. The stale-generation counters and warn are untouched. Pinned inbrowser-host/__tests__/room-transport-bridge.test.ts(red on the old transport: four deliveries where one was owed). The redial's first-dial death itself was relay-side (a held identity-bind counted as taught before it reached the wire — cf-relayfanout.ts, landed beside this) and is not pinned to any engine version. - api.raycast tag filters answer through the exec overlay (ledger 2964 — Savi's run_script raycast with includeTags returned null, excludeTags passed vacuously). Every run_script / exec cast rides a script-transaction overlay (
tome/script-transaction-overlay.ts), which holds no live entity indices:resolveEntityIndexanswers undefined for every entity there whileworld.get/world.hasread through to the base world. The hoisted per-hit filter (object-api.tsacceptsTomeRayHit, #12086) read an unresolved index as a dead entity — visible, tagless — so on the overlayincludeTagsrefused every collider hit,excludeTagsaccepted every hit, and the default sensor policy stopped hiding plain trigger zones.getObject(id).tagsandapi.query({ tags })read the same TomeTags component by id and saw the tags, which is why the two reads disagreed on one client. The residency hypothesis in the filing is not the mechanism — the overlay is; a resident creator session's run_script had the same fault. Cure: an unresolved index now falls back to the id-based helpers (isDefaultRayVisible+matchesTagFilters, the pre-#12086 path); the base-world fast path is unchanged, and a truly dead entity on a base world answers exactly as before. Behavior hooks (update,onCollision) never rode the overlay and are unaffected. Pinned inraycast-api.test.ts("physics raycast through the exec overlay"): include (all listed tags required), exclude, multiple, and the plain-sensor default, all throughcreateScriptTransactionWorldOverlay. - The rebind sweep keys its reap on the pre-reload entity, never on the id (ledger 2955 — medivhus's Veilhunt, Savi filing b9d91283, 5.2.38 relay).
interpreter.tsteardownAndRerunOnSpawnmarks a script's onSpawn-stamped children before the re-run and reaps, hook-suppressed, the marks the re-run did not re-assert. The mark was an id: a builder whose onSpawn clears its own pieces first (destroyObjects({ tags })) and raises them again under the same deterministic ids (house-wall-N) despawned every marked entity inside the re-run and re-created the ids through the fresh-spawn path (no reclaim, so nothing inreclaimed); the sweep then found a live entity at each marked id and destroyed it — the fresh one. Every save of a lib in the manager's require graph left 0 of 357 pieces standing (or only the ids the edit had shifted: 67 of 357, 112 of 339), with the reload line reading "665 spawned entities removed and onSpawn re-ran". The sweep now tracks, through aworld.onDespawnsubscription held for the re-run only, which marked entities the re-run itself despawned, and never touches those ids: whatever stands there is the re-run's own creation. The reclaim-in-place contract (#12090, ledger 2858) is untouched — a plain re-spawn under the same id still adopts the live entity, live state wins, new keys land — and a builder whose ids shift with the edit still sees its old set reaped and the reload line written. Red-first pins inrebind-continuity.test.ts(destroy-then-respawn under deterministic ids through a lib edit; the same through a lazy-required json leaf; a shrinking plan; the plain reclaim; the shifting-id builder). Noted, not changed: arequire()inside a function body is a tracked dependency edge —extractDependenciesscans the source statically, so a json leaf reached lazily still invalidates the whole chain up to the manager. That is the design (the module context compiles its graph up front). - A host's reconnect re-assert of its hosted remainder speaks keyed creates through the adoption restate lane — one standing head per entity; heads no longer multiply per component across reconnects (gavi's staging read, 2026-09-02: joinHeads for the same 1,000 host-simulated mobs 3,030 → 7,311 after one platform reset; the prod 33k → 73k standing-heads class). The reconnect re-assert (ledger 2634,
engine/runtime/client/runtime-client.tsrestateOwnPublishedTruthAtReAdopt) spoke every own-published, still-simulated entity as one UPDATE row carrying its whole peer-visible component set. The keyed lane splits an update row into one head per component (e:{id}:c:{name}), while every host statement's create is scrub-and-insert (e:{id}:@c+ tomb — one head per entity), so after one socket loss every hosted mob carried a head per component forever, and a full-component update row per mob rode the wire at every reconnect. No second set of mobs ever existed. The re-assert now splits by scope: the OWN ENVELOPE keeps ledger 2634's update-row shape (the avatar's create is a presence fact only the join lane authors); the HOSTED REMAINDER routes through the adoption restate lane (relay/relay-world-restate.tsrearmRelayHostRestateForKeys, generalized from the departure-relinquishment re-arm), window-scoped to the ids this seat published — keyed creates, the lane's holds on top (§13.5 scope, catchup apply, replace claim), and a published id that died across the outage tombs through the 2701 rider while the ledger is under its cap. On the production-parity relay harness (40 mobs × 7 components): standing heads 84 → 371 after one F10 rebase before, 84 → 43 after (the create head carries each mob's state whole and scrubs its stale per-component head); a second reset adds only the avatars' motion settle heads. Pinned in__tests__/relay-reconnect-remainder-heads.e2e.test.ts(F10 rebase over a lived world and a reload under the host's identity: mob heads ≤ the pre-reset count, every mob head the host speaks is a create, one set on the host, the resident and a late joiner, a second reset plateaus). - The world-sync ghost sweep's candidates are wire entities — peer-shown OR self-published (
relay/relay-world-sync.tscollectWorldSyncSweep;client/room-delta-ingest.tsClientAuthPublishedEntityIdsResource). The sweep walked only the projection set (what the room showed this client), and fan-out excludes the sender, so a rebasing EX-HOST's own former remainder — spawned here, published as host, destroyed by the next seat while this socket was dead, its tomb compacted — was a frozen ghost the sweep could not name. It died anyway, by accident: the old re-assert spoke it as update rows stamped with the STALE seat epoch, the seated host refused them (stale-host-stamp), its window ledger tombed the dead key, and the rebaser applied that tomb. Moving the remainder re-assert to the restate lane removed that carrier, so the sweep now walks the uploader's published memo beside the projection set — the kernel tier's reset stray sweep (sweepClientAuthStraysForReset) always walked both halves. Every other bound holds for the published half (locally simulated never sweeps, liveIds membership wins, the covered-places bound, the journal-truth bounds). The cross-place-holdings pin inrelay-aoi-place-scope.e2e.test.tsnow asserts the real healer: the rebaser's own sweep tombs the ghost, and it speaks nothing stale about a key it no longer hosts. - A relay lane for the session atmosphere override layer (ledger 2968 — Paws & Reels, Savi filing 1e71868f, 5.2.39 relay; kin: ledger 2832's Pour Decisions storm darkness).
api.patchAtmospherewrites the override resources and mirrors them ontotome/specasTomePlaceAtmosphereOverrides; on the relay tier that component had no transport (tome/specsits inside nobody's simulation envelope, the restate and world-sync lanes exclude it, only music and sound-loops had spec-entity lanes), so a host-simulated weather manager's patch applied on the seat's tab and reached no guest, and a late joiner never got it.tome/relay-atmosphere-lane.tsre-lands the kernel guarantees in the relay-music-lane shape: the place's SEAT publishes the local head as one host-stampedtome/specupdate row (the existing stale-host-stamp gate refuses a deposed seat's late row at live peers), the relay journals it as a keyed head so live peers apply it in room order and catchup replays it to every joiner, arelayAnchor {tick, ms}rider rebasescycle.anchorTickinto the receiver's clock so the day/night hour agrees across machines, and ingest folds the head into the same override resources the writer's patchAtmosphere writes —getAtmosphere()and the atmosphere-sync renderer read the replicated sky (the replicated component alone never reachedgetAtmosphere(), which reads the resources first). Write authority is the place's seat only: an observer's local patch stays local, as before. A seat restates the head once when its epoch for a place moves (adoption) and once after every relay Loaded grant (a room reset wipes the journal's heads while the seat's world lives on). Live receivers fold only the sender's hosted places and never their own. Wired inclient/netcode.ts(both egress edges + the welcome edge) andrelay/relay-peer-apply.ts; the kernel/container tier is untouched by construction.tome/api/object-api.tsis untouched. Red-first pins inrelay-atmosphere-lane.test.ts(seat-only publish, observer's local patch stays local, kernel-wire client ships nothing, late-join catchup with the anchor rebase, echo guard, deposed-seat refusal, sender-hosted-places-only fold, adoption restate, grant restate, refused-send retry) and__tests__/relay-atmosphere-lane.e2e.test.ts(guest + late joiner through the relay double, seat turnover restate, replace fence + F10 rejoin re-publish). Named residual: a spec-authoredcyclethat no script ever patches still derives its hour from each client's own tick clock (no row is published without a patch) — the cycle reading the room tick is object-api's change and its own landing. - The relay peer door's parse gate skips the malformed ROW, not the frame (ledger 2982 — tide's ruling; 2,478 worlds in one prod day, dig 7368b41d). Peers decode each other's payloads, so
relay-peer-apply.tsran the kernel server'sparseRoomClientMessagegate on every peer frame and dropped the WHOLE frame when one row tripped a bound, with a once-per-worldpayload-parse-failedwarn that named neither the bound nor the row. Every StateDeltas row is an independent keyed upsert, so the drop over-closed by N−1 honest rows — and the shapes that carry many rows per frame are exactly the ones that hurt: the keyless journaled frame (an entity whose id is too long to key —RELAY_KEYED_KEY_MAX_CHARS— rides keyless beside member-avatar creates and place moves, and pastMAX_STATE_DELTA_ENTITY_ID_LENGTHit fails the gate: the nested index-path id class) and the stamped motion frame (one host frame per tick carries every hosted mover, so one illegal NPC row froze every NPC at every peer).room-protocol.tsgainsparseRoomClientMessageSkippingRows: the SAME bounds as the server door, now named (StateDeltaBound:entity_id,component_name,component_value,value_bytes,entries_per_row,row_shape,rows_per_array,lane_shape; envelope:tick,delta,host_stamp,op) and attributed per row — zero-copy while every row is honest (the server door's cast), the kept array materializes at the first refusal. The server door (socket-gateway,relay-world-sync, the wire bench) is byte-identical in behavior. What still drops whole: an envelope failure (no row means anything without its ticks) and non-StateDeltas ops. The motion chain is the carve-out: a motion delta carries only the components that changed since the author's previous send, so applying one over a skipped predecessor lands a mixed pose (§11.3's partial truth). The dependency unit is the alias segment (bind → deltas → next bind): a refused row, bound or delta, poisons its alias on the receiver chain (MotionReceiverChain.poisoned,relay-payload-register.ts), the decoder discards that alias's later deltas fail-closed (counted on the gate's meter under boundchain_segment), and the poison lifts when the alias's next BOUND row passes the gate — at the next keyframe at the latest (≤1 s by the cadence and its wall floor). A malformed bind takes its segment the same way. Other aliases in the frame stand alone. The decoder now returns per-row provenance (rows: {alias, bound}[]) for the door to read. The warn (noteRelayParseGateSkips) is per-author rate-limited on cure 3's template — first refusal always, then one line perRELAY_PARSE_GATE_WARN_INTERVAL_MS(5 s) carrying the cumulative total — and names the bound, the row id (bounded byboundLogId, so an over-long id shows its overflow) and the lane. The counterrelay.parse_gate_row_skippedrides beside it throughtomeLogger(therelay.purchaseshape: event name + structured meta{author, lane, bound, rows, total, rowId}): one line per (lane, bound) bucket that accumulated since its last line, so sums stay exact across the closed windows; an envelope drop counts on laneframe. Red-first pins:relay-peer-apply.test.ts(a) N−1 applies with bound + row + lane named and the counter tagged, (a′) every bound on every lane, (b) a malformed motion DELTA skips its alias segment through the next keyframe while the other mover applies every tick, (c) a malformed BIND takes its segment only and the segment resumes on a well-formed re-bind, (d) a byte-identical frame the old gate refused whole applies its good rows and thepayload-parse-failedline is gone, (e) per-author rate limit with exact bucket sums;room-protocol.test.tspins the per-row twin against the whole-message door;__tests__/relay-parse-gate-row-skip.e2e.test.tsruns both prod shapes through the production client composition — a keyless create frame and a stamped motion frame, each with one illegal-id row — and shows the honest neighbour lands at the guest while the old gate refuses the same wire bytes whole. Named residuals: the poison does not arm a keyframe-please (the cadence is the resolution, as for every fail-closed motion discard);relay-world-sync's statement apply keeps its whole-statement gate (a different door); the authority verdict for named entities is not in the dump (the capsule seam is a new worker debug request plus a capsule field, not a one-line add — left for its own landing). - One relay lane for every head parked on
tome/spec(ledger 2976; tide's census c3e2ce2b). On the relay tier thetome/specentity sits inside nobody's simulation envelope (spawned bare, no PlaceMembership — the uploader's owned / host-simulated / lease-simulated gate never admits it), so music, sound loops and the session atmosphere overrides each earned a hand-built lane after a creator saw two worlds.tome/relay-spec-entity-lane.tsreplacesrelay-music-lane.ts,relay-sound-loop-lane.tsandrelay-atmosphere-lane.ts(all three deleted) with one publish sweep and one ingest translate over a table of registered components; a new component ontome/specis one row in that table. Wire shapes are byte-identical to the three lanes (journals written by older engines replay through the same gates). What the fold BOUGHT the two whole-value heads (music, sound loops), which the atmosphere lane had earned and they had not: a publisher that hosts a place stamps its row (hostedPlaceId/epoch), so the existing stale-host-stamp gate refuses a deposed seat's late row at live peers; a seat re-publishes an unchanged head once when its epoch moves (adoption) and once after every relay Loaded grant — before this, a room reset left the music and loop heads gone until the next change. A publisher that hosts nothing still publishes unstamped (a player's jukebox press converges LWW as before) and never restates. Wired inclient/netcode.ts(both egress edges + the welcome edge) andrelay/relay-peer-apply.ts; the kernel/container tier is untouched by construction. Existing pins (relay-music-lane.test.ts,relay-sound-loop-lane.test.ts,relay-atmosphere-lane.test.ts, the atmosphere e2e) run unchanged on the generic lane; new pins inrelay-spec-entity-lane.test.ts(the table, the seat stamp, the deposed-seat refusal, the two restate edges, non-seat quiet). - Ledger 2976's creator specimen is a different class — receipt pinned, no behavior change.
__tests__/relay-manager-state-lane.e2e.test.ts: a host-simulated spec-authored referee'spatchStateDOES reach a resident and a late joiner (the manager has an envelope; it never needed this lane). What the filing's own dump line shows — "Entity match left this client's upload scope: another client holds its sim lease now" — reproduces exactly: a guest's guarded cross-writer state write against the referee (a player's "join team" press from his hook) files a combat-lease claim because the bot-class shape gate (isCombatLeasableNpc: carries a behavior, sits in a place, unowned) admits an invisible bodiless referee, and the referee's simulation moves to the presser. Whether a manager object should be leasable is a design word, not landed here. - A member that resumes after the room tombed it re-announces its session envelope again (ledger 3002 — gavi's staging run for 2989: on each of three forced-outage redials the guest applied the ex-host's departure envelope and never held the avatar again; players read 1 vs 2 for 90 s). The engine's return announcement for a plain resume was armed by folding the rejoiner's OWN
member-leavetomb from its resume tail (relay/relay-place-elect.tsfoldMembershipRow, self branch — the R3 design, and ledger 2782's plain-resume pin). Since the relay's rebirth fold at admission (cf-relayjournal.tsappendBatch, 2026-08-20), that row no longer exists for the rejoiner: its freshm:*head is minted at its admission, before its own catchup reads the journal, and the fold deletes the same key's standing tomb row in that transaction. Every resident saw the tomb fan live and despawned the envelope; the rejoiner's tail carried the fresh head and a seq gap. Its outbox then spoke only the ledger-2634 re-assert — update rows for the avatar — whichroom-delta-applydrops at a world that no longer holds the entity, so nothing ever re-created it. The fold now reads the fact the deleted row carried from the row that replaced it: a fresh SELF head folding over a folded prior self head on a generation that did not bootstrap proves the prior generation was tombed during the gap (the relay mints a head only past a departure —memberHeadOnRecord, membership is a join-time fact), and it arms the same envelope re-announce the tomb arms (one slot: a relay that still delivers the tomb coalesces both into one announcement). A fresh bootstrap (F10 / first fold) is excluded — its join announcement is the create — and an ordinary resume with no departure mints no head and re-announces nothing. The relay harness's hub double now models the rebirth fold (__tests__/relay-multi-client-harness.tsingestKeyedRow): with it, ledger 2782's plain-resume pin and the newrelay-resume-after-tomb.e2e.test.tswere red on master engine bytes exactly as staging was, and are green with the change. Relay half: comment only (journal.tsnames the one reader the fold's deletion was not verdict-preserving for); no behavior change on the relay. - A session-persistence place instance prunes on the relay tier (ledger 3014, dig 6fd5e018). The relay row sweep (
relayPlaceInstanceSweepSystem,tome/systems/place-cleanup.ts) stamped its prune deadline ascleanupAtTick— the sweeping seat's own free-running tick — and compared it across seats. The seat that schedules is often not the seat that fires (the minter departs, the default place's host adopts the orphan, the standing shell re-bootstraps and re-folds the row), so a long-running adopter dropped the row on its first pass, a fresh seat waited ≈ 10 min of ITS OWN ticks after boot (the receding deadline), and a shell simulating at 4–12 Hz stretched the 18,000-tick window to 25–75 min — longer than it lived (animalia-vale-4: 307prune_scheduled, 0prunein 24 h). The sweep now stampsTomePlaceInstanceRow.cleanupAtRoomMs= room-now + the window translated at the spec-pinned tick rate, and drops when the relay room clock (room-truth R1,relay-room-clock.ts— the domainTomeLifetimeDeadline.deadlineRoomMsrides) passes it. The stamp is an absolute instant carried by the row through the journal, so an adopter fires the minter's deadline once with no re-basing; occupancy clears it (the retry-reuse cancel, p-4c207ba4). Window meaning unchanged: generated session rows ≈ 10 min empty, ephemeral rows ≈ 10 s, at the spec's tick rate. Without a room clock (v5 relay) the tick domain applies byte for byte; kernel rooms are untouched. Pinned:relay-place-instance-prune-room-clock.e2e.test.ts(hand-off mid-window fires once; a fresh re-bootstrap does not restart the window; a 4 Hz seat fires on room time; the named retry-reuse test — a player walking back inside the window is never evicted by a seat change; ephemeral grace; v5 compat).prune_scheduled/prunelog lines on relay carrycleanupAtRoomMs+roomNowMs. Door (1) of the same row — the uploader refusing the adopter's schedule write — is ledger 3013's shared predicate, not this change. - The spec-entity lane's welcome/adoption restate holds on the catchup-apply fact (ledger 2976 — tide's composition finding F7 on the fold that made one relay lane of music, sound loops and atmosphere). A seat re-publishes every whole-value
tome/spechead once per relay Loaded grant and once per adoption epoch (tome/relay-spec-entity-lane.tspublishWhole). The resume re-grant lands at the WELCOME edge (relay/relay-join.ts), the election writes the seat table at the LIVE edge (relay/relay-place-elect.tsonSessionState), and the ingress drain is budgeted (client/netcode.tsINGRESS_DRAIN_BUDGET_MIN_MESSAGES), so a returning seat's first sweeps ran over a pre-outage table and a pre-outage head. Two faces, both reproduced on the relay harness: a seat DEPOSED during its outage restated under the dead stamp — live peers refused it (the stale-host-stamp gate works), but the relay journals every row as the newest head, so every late joiner folded the deposed seat's music until the seated guest's next write; a seat still seated whose outage spanned a guest's jukebox press journaled past the drain budget re-shipped the PRE-OUTAGE head under a valid stamp, so every live peer regressed to the old track and nothing reconciled (journal rows[pre-outage, pressed, pre-outage], the seat alone holding the press).publishRelaySpecEntityHeadsIfChangednow takes the same settled predicate the world-restate sweep holds on (relay/relay-world-restate.tssweepRelayHostRestatescopeSettled— transportrelayCatchupApplySettled: this socket generation reached live AND the ingress fully-applied edge fired after it) and defers the whole sweep without consuming the welcome generation or any epoch (the bookkeeping advances only on a send);client/netcode.tsthreads it into both egress sweeps. After the hold the local head IS the room's last word as this world knows it and the table is the live edge's, so a deposed seat hosts nothing and stays quiet, and a seated one restates the guest's press under its own stamp. Absent predicate (kernel wire, harnesses without a session) = settled, v1 timing exactly; a healthy live session is unaffected (the fact latches true at the first fully-applied tick after live). Pinned in__tests__/relay-spec-entity-restate-catchup-hold.e2e.test.ts(red on master bytes for both faces; a control pins the ordinary resume's one restatement). Harness:cutWire()on a client stack models the client-noticed socket death the relay has not seen (frames fanned to the dead socket drop, the hub keeps the socket until the redial supersedes it), andsetDeflatedDrain(null)disarms the paced drain lane. - A terrain-off place no longer draws the engine default heightmap's mountains as its horizon ring (ledger 3018 — Savi's filing d7364cfc, a brown fog-colored skyline around an otherwise empty world). The horizon system (
terrain/horizon.ts) resolved its definition throughreadTerrainDefinitionForPlace, the build systems' resolver, which falls through toTerrainDefinitionResource ?? DEFAULT_TERRAIN_DEFINITIONfor a place with no entry in the per-place map — exactly the stateapplyTerrainSpecleaves forterrain: { kind: "off" }.computeDesiredTerrainHorizongated only ongenerator !== "heightmap"(the default IS a heightmap), the extended distance profile, and a fog band that cuts the extended bands, so a terrain-off world under the desktop profile with linear fog sampled the default procedural mountains into a ring while streaming, correctly, streamed no chunks at all.resource.tsgainsreadStreamedTerrainDefinitionForPlace: the one resolver for "what does the streaming pass stream for this place" — the map entry when a map exists (null for a place with none), the legacy resource or engine default only for a world with no per-place map. The streaming pass reads it (a behavior-preserving fold of its inline three-way branch), and the horizon reads the same function: the system builds from the streamed definition andcomputeDesiredTerrainHorizonanswers null when the place streams nothing.readTerrainDefinitionForPlace's fall-through stays for the build systems — they act on chunk entities streaming already created, and a terrain-off place has none (checked:runTerrainStreamingskips a place the map carries no entry for, so no phantom chunks exist). Red-first pins inhorizon.test.tsagainst the master horizon bytes: Savi's repro shape (terrain off, linear fog near 120 / far 400, extended-high) desires no ring even when handed the default heightmap; a heightmap place under the same fog band keeps a buildKey byte-identical to a legacy single-definition world; the client system submits no build and owns no ring entity for a terrain-off place, and switching terrain off live removes an installed ring on the next tick. Real product (second stack from this branch, headed Chrome under Xvfb on the GPU): the terrain-off world's client sim carries 0terrain/horizon-meshand 0terrain/chunk-keyentities at ticks 818 and 908 with a flat horizon in the screenshot; the heightmap control under the same atmosphere carries 1 ring (inner 480 m, outer 2560 m) and 361 chunks, its fog-colored skyline visible behind the near hills. - A terrain chunk build that lands after its chunk was re-keyed is judged against the chunk's CURRENT inputs, not the inputs it was submitted with (ledger 2966 — medivhus's Veilhunt, the house cut present on one machine and missing under another player's feet). The client build system's completed-output verdict (
client-terrain-system.tscompletedOutputInstallVerdict) computed "the chunk's current inputs hash" from the pending job'sTerrainChunkKeyinstead of the entity's live one, and memoized that hash under the chunk's current dirty stamp. Streaming re-keys every resident chunk on a definition reinstall (the revision is the terrain signature, so a mark add/remove moves it), so a house builder'sremoveMark→addMarkpair whose removal build was still in flight when the marks came back hit exactly that seam: the installed cut already realized the current inputs, but the verdict hashed the stale key, called the cut refuted, and installed the flat removal output over it — the player stood embedded in ground the authority had already re-cut, for as long as the follow-up build waited behind the place-wide rebuild (45 s on the reporter's client). Phase 2 then took the poisoned memo as the follow-up's label, so the rebuilt chunk carried aninputsHashits own inputs could never reproduce — the 43 chunks on the host's dump that hashed to the cut definition under the no-marks key. The verdict now readsworld.get(entity, TerrainChunkKey)(the server verdictresolveCompletedServerOutputalready did): a landing whose inputs are refuted while the installed content realizes the current inputs is discarded, no rebuild is owed, and every submitted job's label is the hash of exactly what it carries. Geometry of every build is unchanged —chunk-build.tsnever reads the key's revision. Pinned red-first inclient-terrain-system.test.ts(the remove→add race discards the flat output and owes nothing; a follow-up owed after a stale-key landing is labeled with its own inputs). Real product (second stack from this branch, an instanced arena whose row carries a flatten cut, the carver removing and re-adding the mark on consecutive ticks three times while the player stands in the cut): the player's feet read −4.000 on every 100 ms sample across all three cycles. - The texture-script bake's 50 ms wall-clock budget now covers the raster, and the raster never runs on the GPU (ledger 2974 — two Windows D3D12 DEVICE_HUNG specimens with a scripted texture bake in the ring). Canvas2D records draw calls and rasterizes the recording at the first readback, which in the bake lane was
readTextureBakeCoverage— AFTER the stopwatch stopped. Measured on the L4 box with a GPU-accelerated worker canvas (rigscripts/verify-texture-bake-raster-budget,--headed): a JS-cheap draw of 600 blurred fills on a 1024² canvas baked "ok" with the budget seeing 0.8 ms while the machine spent 67.8 ms; 10k tiny fills at 1024² saw 3.6 ms and spent 204.8 ms; the specimen's own oak script saw 5.8 ms and spent 85.2 ms — the raster ran as 14,106drawRect+ 855drawPathops in the GPU process, outside anything the law judged, and the churn shield (ledger 2271) then excused whatever the JS clock did see. Two rails close it inrenderer/texture-bake.ts: every bake context is created with{ willReadFrequently: true }(the trace shows the provider switch fromCanvasResourceProviderSharedImagetoCanvas2DResourceProviderBitmap— the raster runs on the bake worker's own thread, so no bake ever hands the GPU a command list), and the env'ssettleRasterhook (a 1×1getImageData) forces the pending recording to rasterize every 256 ops, at each atlas cell boundary, and at the end of the draw, so the raster's cost is on the stopwatch and a raster-heavy draw stops partway like a hot JS loop does (the park bounds work, not only measured time). After: the 600-blur draw and the 10k-fill draw fault as budget at 51–69 ms; the specimen's oak/plaster/road scripts bake ok at 15–24 ms measured (wall time unchanged at 20–32 ms — the cost was always paid, it is now counted). Worker and inline transports both carry the hook; test stubs without it bake exactly as before. Not established by this landing: that either specimen's device loss WAS a bake — both specimens' scripts are raster-light (≤512², no blur/shadow/filter), and the row's other candidates (the first materialized frame of 24k draws with 18 shadowed lights on a 60 ms/frame GPU; a CPU-drowning Intel iGPU box) stay open on the row. - A persistent GPU validation storm is its own death class,
renderer-validation-storm, not a device loss (ledger 2995; inescapable's session 75ec2cb7, juani's Miscast Siege on Chrome 125). Both storm ladders inrenderer.ts— the #187 burst rail (Invalid BindGroup/Sample count … doesn't match/Invalid CommandBuffer) and the ledger-1232 unattributed storm window — used to escalate pastGPU_MAX_RECOVERIES(2) render-chain rebuilds intopostDeviceLostOnce, so the death carried therenderer-device-lostdiagnostic, the[renderer] GPU device lost:host console line kiln's render-dead beacon prefix-matches (reasondevice-lost), and arenderer-gpu-recovery-exhaustedreport whose text said "This points at a device/driver problem". Validation errors are the API layer refusing the renderer's OWN calls, and a storm two clean rebuilds cannot clear is deterministic Spawn renderer code (the specimen:Bind group layout ["lume placeholder empty"] of pipeline layout ["models/placeholder/rg11b10ufloat/depth24plus/s1/mrt1"] does not match layout ["lume pass-li…"]→Invalid CommandBuffer from CommandEncoder "lume frame"), so 49 sessions in 7 days counted as GPU weather. Now both ladders converge onpostValidationStormDeathOnce(same one-wall-per-session latch, same reload wall): the worker logs[renderer] renderer-validation-storm: <verdict>plus a separate… last GPU error (<rail> rail): <raw>line, reports the newrenderer-validation-stormerror-class diagnostic (Savi register: frozen screen, suggest refresh, Spawn's renderer not the player's machine and not the game, deterministic on that browser if it repeats), and stamps thedevice-lostpostdeathClass:"renderer-validation-storm"with the verdict as its message. The host (browser-init.ts) prints the verdict under the newRENDERER_VALIDATION_STORM_CONSOLE_PREFIX([renderer] renderer-validation-storm:), logs[client-entry] boot-fatal renderer-validation-storm held to the console — the player sees "…", and speaks the device-lost class's player sentence byte for byte (Something hiccuped — reloading brings it back.— recover, don't explain). The verdict sentence (renderer-death-class.ts validationStormVerdictLine) is fixed text — it names a Spawn renderer fault, the rebuild count, that a reload recovers, and that a repeat on the same browser is deterministic — and never contains "device" or "driver"; the raw GPU text never rides it. The tworenderer-gpu-recovery-exhaustedreports are gone (the class diagnostic is the one death report; the rebuild receipts stay). True device losses (device.lost, the dead-device burst, the destroyed-texture submit rail, the boot watchdog) are unchanged and now carrydeathClass:"device-lost"explicitly. Kiln side (same landing):render-dead-beacon.tsrecognises the second prefix and fires under reasonrenderer-validation-storm(breadcrumb[render-dead-beacon] renderer-validation-storm beacon fired), and the perf-rollup ingest enum admits it, soreason:device-lostreads exclude the storm. Pins:renderer-device-loss.test.ts,renderer-gpu-recovery-probe.test.ts,browser-init.device-loss.test.ts,loading-state-broadcast.test.ts(the player sentence byte pin),engine-diagnostics.test.ts, kilnrender-dead-beacon.unit.test.tsandperf-rollup/route.unit.test.ts. - The world-sync statement door skips the malformed ROW, not the statement; the minting world gets a receipt for an over-long id (ledger 2982, tide's trailing read of #14010).
relay-world-sync.tsapplyWorldSyncStatementwas a SECOND posture on the same bytes the peer door now handles row-wise: it ran the kernel server's whole-messageparseRoomClientMessageon the embedded StateDeltas, so a late joiner's live-layer statement chunk carrying one row past a wire bound (an over-256-char nested id,MAX_STATE_DELTA_ENTITY_ID_LENGTH) dropped WHOLE — up to a full 448 KiB chunk of honest live rows never reached the joiner, nothing warned, nothing counted, andawaitingStatementstayed true, so the requester re-askedRELAY_WORLD_SYNC_MAX_REQUESTS(4) times and the answerer restated the same bytes each time. The discriminating loss is a resident's AVATAR: member avatars are keyless by the fold constitution, so behind compaction the statement is the only lane that carries one — one over-long id anywhere in the answerer's world resurrected resx's "B cannot see Player A's character at all" for every joiner. The door now callsparseRoomClientMessageSkippingRows(one parser, shared with the peer door — no copy), counts each refused row on the SAME meter (noteRelayParseGateSkips,relay.parse_gate_row_skipped) under laneworld-syncwith the bound named, rides the same per-author rate-limited warn (now wording "in its live-layer statement (world-sync)" / "the statement's other rows applied"), and settlesawaitingStatementon the final chunk whenever its ENVELOPE reads — a retry would only re-fetch the same rows. An envelope failure (tick/delta/host_stamp/op) still drops that chunk whole, counted on laneworld-syncunder the envelope bound with "statement chunk dropped whole"; a final chunk lost that way keeps the request open (the retries can reach a different answerer at escalation, and a bad envelope is an assembler bug, not a data shape).RelayParseGateSkip.laneadmits"world-sync"; the describe/kept wording keys off the envelope-bound set, so the peer door's lines are unchanged. The sender-side receipt (client-auth-wire-id-bound.ts, wired atenqueueClientStateDeltas— the one seam every outgoing StateDeltas passes through): one warn per over-long create id per world, on the runtime-log ring (getLogs, entity attached,data.code: "entity-id-over-wire-bound", length + bound) and the console, naming the id bounded byboundLogIdand its length against the 256 bound, with the one move (spawn deep children under a short id, or give this one an explicit short id). The memo is capped at 256 ids (a runaway minter must not grow it). The structural cap on minting — refuse vs shorten atqualifyChildEntityId— is a creator-visible decision and rides its own held PR. Measurement (finding, not a fix here): the statement chunker bounds BYTES (RELAY_STATE_DELTAS_PAYLOAD_BUDGET_BYTES, 448 KiB) while the door caps 2048 rows per lane (MAX_STATE_DELTA_ROWS_PER_ARRAY); on the production client composition the emptiest Savi spawn (api.spawn("m1"): realm + place-membership + spawned-by, 2-char id) is a 201 B row → 2282 rows fit one chunk, over the cap, so a world holding ≥2049 such entities has its creates lane dropped whole (rows_per_array) under both doors; an anon empty spawn is 224 B → 2047 (just under); anything with a position or primitive is ≥419 B → ≤1095 rows/chunk (safe); the netcode publisher already chunks by the row cap (enqueueClientStateDeltas), the world-sync assembler does not. Red-first pins:relay-world-sync.test.ts(honest rows land + counter on lane world-sync/entity_id + request settles with no retry; the sweep still runs; a non-final chunk keeps the request open; an envelope failure drops whole and keeps the request open; the assembled template parses with zero skips),client-auth-wire-id-bound.test.ts(one receipt per id, bounded, on the ring and the console; the memo cap; the upload seam),__tests__/relay-world-sync-row-skip.e2e.test.ts(A spawns an honest box and an illegal-id entity through the real ObjectAPI, compaction, B joins: A's avatar and the box reach B, the illegal one does not, the request settled on ask 1, the counter ticked on lane world-sync, and A's ring carries the sender receipt — against the old door A's avatar never reached B). - Every client-initiated relay condemn tears down the wire-mode lane — the 4440 ack-silence heal and the 4439 redial-fresh door take the same teardown as a wire-grammar close (ledgers 2989/2990's second half — the prod 4440 → 4437 redial loop on room_host shells; b1d04fe2f6).
ClientTransport.close()detaches the handlers, so a client-initiated condemn never gets anonCloseand each door owns the whole socket-death sequence itself. Two doors did only part of it: the 4440 upload ack-silence heal (healRelayUploadAckSilence) and the 4439 redial-fresh verdict (ingestRelayFrame) closed the transport, reported, and scheduled the redial WITHOUTawaitFreshProjection→teardownSocketWireMode, which leftrelayIngestGenerationunbumped,relayIngestBusylatched, the old decoder installed and the relay session's heartbeat beating. An op-9BIN_OP_BATCH_DEFLATEinflate in flight at the condemn outlived its socket, read the generation as current, took the REDIAL's fresh decoder (installed by the new welcome ~180 ms later), walked its item stream through an alias table only the dead socket had been taught, failed closed, and 4437-condemned the socket that had just gone live — the prod loop on the shells (a drowning single-vCPU shell misses its upload acks on a 50–70k-row drain → 4440 → resume redial → 4437 → redial → 4440, every 30–120 s all day, on the deployed relay and the rolled-back one alike: the mechanism is entirely client-side). One door now:condemnRelayGeneration(code, reason, detail, via)in_entry/client/room-runtime.ts— close, report, flipsocketOpen,syncWatchdogTimer,awaitFreshProjection, pushconnection.close, schedule the redial — and all four client-initiated condemns route through it (wire-grammar violation, redial-fresh, ingress-overflow, upload ack-silence); the two already-correct doors are byte-identical, the two gap doors gain the teardown and (redial-fresh) thesocketOpenflip + theconnection.closeingress row it never pushed. Red-first inroom-runtime-condemn-teardown.test.ts(the prod signature exactly — a 4437 on gen 2 — red on the old door; green now: no 4437, no third dial, gen 2 ingests normally; the 4439 pin condemns once and gen 3 dials fresh). It does not stop the 4440 that starts each cycle — that is the shell's CPU budget, reported separately. - A destroyed spline owner's derived segments die on every world (ledger 2973 — BT's sky fences, Paws & Reels 5.2.39; #14067). A child-row fence's segments hang under the COTTAGE by last-slash parent inference, so the owner's
TomeChildrennever lists them. On the relay tier a non-simulator's destroy rides the ghost-destroy tomb lane, whose cascade enumeration walkedTomeChildren+TomeOwnedEntitiesonly: the owner tombed, every derived keyed head stood, wire deletes never cascade at receivers, so every live peer kept its lofted segments and every fresh fold replayed 1,780 orphans at y 24–27 beside the root fences at ground; the world-sync sweep acquitted them because the answerer'sliveIdsnamed them. Three legs:collectRelayDeleteCascadeIdswalks the spline-derived set (TomeSplineGenerated.ownerId) so the derived tombs ride the same lane as the owner's; the world-sync sweep reaps a wire entity carryingTomeSplineGeneratedwhose owner is named by neitherliveIdsnor this world, with its structural subtree; a wire delete trims the parent'sTomeChildrenmirror before the raw despawn (one primitive intome/hierarchy-detach.ts, shared with the local destroy), so the parent's simulator stops re-publishing the dead id. Red on master sources (54 orphan segments on the live peer and at the fresh fold), green with the fix; an undestroyed fence keeps its segments and heads byte-identical. - A fresh redial retires the relay rows its drain did not deliver — a fold never seats a verdict the journal no longer holds (ledger 2981 — Wizardry Mishaps / Marrowgate; 3290f14b48). Two folds of one journal seated two holders for
main: the standing shell's worker (an incumbent fold) stampedmainfrom anh:*row a room reset had wiped, the creator's reloaded tab (a fresh fold of the same journal) derived the creator, and each side's identity gate refused the other's host-stamped frames — a guest's table named a host whose own fold did not seat it, that host simulated nothing in the place, the other fold's holder simulated and stamped, and every stamped frame died at the guest's gate (the frozen bolt). The room reset (cf-relayperformReset→journal.reset) deletes the whole heads overlay,m:*andh:*included, and journals nothing about it; a same-room full rejoin never reset this fold, sotruthMembersandrelayAssignmentskept what they held.relay-place-elect.tsnow records them:*identities andh:*places a fresh redial generation delivers (a redial's drain is the complete live set by construction) and at that generation's live edge retires every folded assignment and head the drain did not deliver, then recomputes the touched places — the journaledh:*row is the seat source under truth mode (R5), and a wiped row is not journaled. Member records stay (inert without a head; retiring them would fire a disconnect the lifecycle seam cannot tell from a real one). Plain resumes never arm the sweep; a socket death mid-drain disarms it. The fold fingerprint now carries the seat table's identities, so a seat-table split is visible in the receipt. - A curl node resolves its permutation tables once per compiled node, not per particle sample (ledger 3022 a; #14133). The closure compiler's curl node (
engine/fx/compile.ts) calledcurl3Intoper particle, andcurl3Intoresolved its three permutation tables through the LRU cache on every call; a cache hit refreshes recency withMap.delete+Map.set, so every sample minted hash-table garbage — at 3000 particles one tick grew the JSC heap capacity from 1.8 MB to ~75 MB over 20 ticks while the function's comment called the path "zero allocation". The compiled node now resolves its tables once (curl3Tables) and callscurl3IntoTablesper particle; tables are pure functions of the seed and immutable, so a held table stays valid after the cache evicts its seed. The LRU cache and its policy are unchanged. Receipts (bounded yielding harness, 3000 particles, one curl node): before 3.0–3.25 ms/tick, RSS 151–243 MB after 600 ticks, heap capacity +3.6 MB/tick with no GC; after 1.6–1.7 ms/tick, RSS 47 MB after 600 ticks, heap capacity flat. Field output is byte-identical (curl-field-golden.test.tsrecorded on the pre-hoist tree passes unchanged;noise.test.tspinscurl3IntoTablesagainstcurl3and the held-table-survives-eviction property). - A reap-lane tomb retires its id from the uploader's dead-row memos — a swept self-published ghost journals one tomb, not two (a250fdf0f3). After the world-sync ghost sweep began walking self-published ids, a rebaser's own former remainder swept off a peer statement shipped its tomb twice: once from the reap-lane drain (the journaled, unstamped adjudication) and once from the change-log drain through the include filter's published-memo branch. The reap-lane drain in
publishClientAuthEnvelopeAfterCommitnow retires each queued id from the published memo, the own-envelope and remainder-place scope memos, and the simulated-despawn ledger before the change-log walk in the same publish. The reap tomb stays (it lands at every peer where a stamped drain row could be refused at the stale-stamp gate); the drain's row was the redundant one. A death with no reap tomb keeps its memo entries and its drain delete. - One
tome.apply.slowline per apply over 5 s names the per-phase ms, the scripted-derive and ground-sample attribution, and the places (ledger 3003 companion; #14055).tome/apply-phase-clock.ts+ the interpreter: a slow apply confesses where its time went instead of reading as a silent freeze in the logs. - The
model-not-animatablediagnostic stops prescribing an?animations=rebake to a body that already asked for one (ledger 2998; cde65be5dc).lume/models/models.tsreads the request that was made; the kiln half stamps the rig fact on the base row for the?animations=fallback. - Skill
game-ui: speech recognition does not ride the mic grant (#13734).SpeechRecognition.start()needs a permission the UI realm can never hold, so it errorsnot-allowedeven while the microphone track flows — the skill now says so; voice features ride the track, there is no platform speech-to-text lane. - Skill
jams(SpawnJam): judging is point-don't-invent (#13634). Savi has no inside line on judging, rules or eligibility; the skill sends the creator to spawn.co/jam as the record and teaches making the case from players, not from recalled rules. - The world-sync statement assembler splits chunks by rows OR bytes — the door's row ceiling (the measurement finding of ledger 2982's world-sync half, closed; #14026).
relay-world-sync.tsbounded its chunks by bytes (the 448 KiB relay frame budget) but not by rows, while both receiving doors cap a StateDeltas lane atMAX_STATE_DELTA_ROWS_PER_ARRAY(2048) and refuse the lane WHOLE when it is over (rows_per_array, index −1) — an envelope-level bound the row-wise door cannot save row by row. The emptiest Savi spawn (api.spawn("m1")) is a ~200-byte row, so ~2,280 such rows fit one chunk under the byte budget: a world with ≥ 2,049 property-less short-id entities on one answerer lost that chunk's every create at the joiner. The assembler now emits a chunk when EITHER bound would be crossed, using the constant the doors and the netcode publisher already share; the joiner assembles the extra chunk as it does today. Pin: 2,300 PlaceMembership-only entities → on the old assembler zero creates land and the gate counts onerows_per_arrayrefusal; now every entity lands and the chunk count grows by exactly one ([2300, 0]→[2048, 252, 0]). - The renderer worker's bespoke-geometry cache is held by the records that draw from it, and the hold follows the draw/mesh pointer (ledgers 2934, 3037 — TERRA's two-hour session ending in "Array buffer allocation failed" in draw/material, draw/shadow and draw/mesh at once). The cache is keyed by content signature and every delivered scripted geometry was inserted and never removed on the renderer side, so a body whose scripted geometry re-derives (a terrain chunk on every data-chunk version, a cloth flag on every
primitive.params.phasewrite, an ash hull per generation) left every superseded mesh resident for the life of the tab. Real browser (WebGPU on the L4, 24 scripted tiles of 3,600 vertices each re-deriving once a second): the renderer worker's ArrayBuffer backing storage grew 216 MB per minute on 5.2.39 with the cache census reading +24 entries per second while 24 bodies drew; on this version the cache holds 24 entries / 3.8 MB and the backing storage stays flat over the same six minutes with the same churn.lume/primitives/store.ts: a bespoke record now holds up to three signatures — itsgeometry/bespokerow, itsgeometry/bespoke-clientrow, and the signature itsdraw/meshpointer names — each for that row's own lifetime (reassign, REMOVE, despawn), and an entry lives while any hold on any record names it. The pointer hold is the correction to #13943 (reverted in #13959): the sim's settled-geometry eviction (scripted-primitive-ingest.tsattemptDueEviction) REMOVEs the geometry row about five seconds after a no-physics body settles, on the recorded premise that the renderer keeps drawing from its own cache; a row-scoped hold freed the entry under the live pointer and settled decor blinked out in a ~7 s loop. Now that REMOVE releases the row hold and the pointer hold keeps the drawn entry; the pointer moving to re-derived content is what frees the old one. The render channel's writer mirror (render-channel.ts) learns the same third role from thedraw/meshrows it commits, so the consumption-proved wire-ref tier keeps its proof through an eviction and drops it when the last pointer moves — a ref is never shipped for a signature the reader freed. #13943's other rails come back with it: transactional delta-spill frames (a throw rolls backsentEntities, strings, signatures and hold transitions and re-admits the rows through the restate lane) and the desync recovery held while the reader reports allocation pressure. Red-first pins: store (eviction under a standing pointer keeps the entry and the draw; 256 chunks × 12 versions through the evict-then-re-derive cycle hold 256 entries; a pointer ahead of its content holds it on arrival) and writer mirror (a REMOVE under a pointer keeps the proof; the pointer moving releases it). The primitives render census that rides the 1 Hz dump sample now carriesbespokeCacheEntriesandbespokeCacheBytes. Not in this landing: the render channel's 512 MB arena ceiling, and whether animated scripted geometry deserves a cheaper primitive than per-tick regeneration (2934's design question, open). - A direct-pool map with a non-integer
repeat.ydraws at the same vertical phase as an integer repeat would — no more frac(repeat.y) shift (ledger 3032 — leon81's Herrmanns Radiowerkstatt, the door sign that came back "mit falschen Maßen" / "~1×2" after a material swap). A plain primitive'smapdraws on the shared texture-array lane when the map seats in a page and on an oversized direct pool when the page fleet refuses it — over-profile (every >512² texture on desktop,tailAdmission: false), unsupported format, or the page budget spent (16 families on desktop; every distinct canvas size is a family). The pool's binding bakes a V flip asscaleV = -|repeat.y|withoffsetVpre-advanced past the flipped span, and that pre-advance was|repeat.y|itself. The direct fragment samplesin.uv * scale + offsetthrough a repeat sampler, which keeps only the fractional part of V — so an advance of 8.33 landed the image at phase 0.33: the 0.26×0.12 m sign at repeat[3.85, 8.33]drew cut a third of the way up, and at fresh load it did not, because the sign's family seated in a page then and only the mid-sessionsetObjectProperty('material', …)with a new URL arrived after the budget was spent.resolveOversizedBindingnow pre-advances byceil(|repeat.y|)for repeat wrap: the same number for every whole repeat (a fresh-load box with a 1024² image at repeat 1 gets a byte-identical binding — the pin's control) and the next whole repeat otherwise, so the flip pivots on the face. Red-first instore.test.ts: two families through the production direct route (pageBudget: 1, the budget refusal) at repeat 8 and repeat 8.33 sample the same phase at matching face positions; the orientation pin assertsscaleV = -|repeat.y|,offsetV = |repeat.y| + offset.yfor the integer case exactly as before. Real-product receipt on a second stack: the 20-sign rig (20 canvas families, the last four route direct) plus a behavior swapping one sign to a new URL at 8 s — the four late signs and the swapped sign now read whole and upright; the 16 page-seated signs are pixel-for-pixel what they were. Not changed: the pool's V orientation (three's upright, the flip stays) and the array lane's (no flip) — the two lanes still disagree on which way is up, and whether they should agree is the open decision on ledger 2414/1968, not this landing's. Clamp-wrap maps: the pool's clamp window is not a phase, so its transform is untouched (the pool-vs-lane clamp UV-source divergence stays a separate row). UnauthoredmapRepeat(lane infers from entity scale, pool uses[1,1]) and negativerepeat.xhandling also stay as they were. - The standing shell's host-seat claims now reach the journal when the shell joins a room that already has a creator in it (ledger 3038 — Savi filing 055db19b). The runtime worker states the shell's standing claims — the defaultPlace claim and, for the slotted dev builder, the whole effective place set (
relay-standing-claims→relay-place-electsyncHostPlaceClaims) — at the bootstrap edge, whichrelay-joinfires right after the self-spawn create is SENT: the session is not live yet and the fold has no self record yet. Both claim verbs journal the set throughsendSelfFact("refresh"), which self-guards on exactly those two facts, so nothing left; the contract text said "the live edge's refresh / the bootstrap create carries it", and neither did — the create was already on the wire, and the live-edge refresh runs only on a plain resume. The claims stayed DESIRED and unjournaled. When the room already held a peer (the dev room's everyday order: the creator's tab first, the shell summoned second), no join edge ever re-announced the shell's presence, so nothing else carried them either — under the room-truth dial the exec writer's own join happened to trigger the join-edge avatar re-announce (a side effect built for the pid-undefined census), which is the only reason current engines mostly got away with it; a pre-truth pinned engine (the filing's 5.2.23) never did. The felt face: a place-targetedrun_scriptinto a place the creator is not standing in fans to the shell, whose exec seam takes the unhosted branch and asks the claim verb — which finds the place already desired, answers true, states nothing — and parks the exec "until the claim folds"; no fact is coming, the park expires at the exec deadline (sevenparked exec … expired before the claim for "maren-lake" seatedon one shell's tape), the shell's host-tick beat names no place, kiln's writer answersrelay_exec_no_host_tickafter its reseat wait, and chat mintsroom_host_summon_failed(still-summoning: the shell's healthzhostingis null because it hosts nothing in its own view) — for every exec, for hours, while both members beat. The cure is level-triggered and self-quieting: a desired claim the fold does not show on self's record (unfoldedSelfClaims) is re-stated (restateUnfoldedClaims) at every edge where the session can author — the live edge, the fold of our own presence fact (the create's receipt), the claim verb's already-desired branch (the exec lane's lazy re-claim now always has a fact coming), and the low-rate re-sweep as the retry lane. One refresh carries the whole set; an in-flight latch keyed on the claim set (claimsRestateInFlight, cleared by the next own presence fold or the socket's death) keeps the live edge and the own-create fold from double-stating before the first receipt lands; a receipt that carried the claims and still left them unfolded is not retried from the fold (a refusal, not a race). The multi-client harness gainsclaimSpecPlaces(the worker's bootstrap-edge sweep, pre-live, byte-parallel toruntime-worker.ts). Red-first on the parent engine bytes:relay-place-exec-standing-claims-occupied-room.e2e.test.ts(v5 dial, person-less place: 0 answers, 0 claim rows — the filing's tape) and five unit pins inrelay-place-elect-claims.test.ts; green with the change. The body-in-place half of the same filing (the shell's own PlaceMembership move folding behind the window's peer cursor) is the previous entry's cure. - The ui.realm shell-load fault names the URL and who never answered; the HUD confesses a per-frame payload past 500k once (ledger 3026; 9f3b081542). The realm's no-load exhaustion line used to say only "no byte of the document ever arrived — asset fetch failed/blocked or the main thread never reached it", so Savi could not tell which URL never answered or whether the network or the frame failed; the filing behind 3026 (the ui-game starter on 5.2.39) shipped a 1,053,200-char HUD fifteen times with nothing in the pipeline saying a word. Now one compact fault line, verdict first: what the frame's own progress beacons saw on the last try, what the host's own fetch of the same document saw (status, bytes, ms — or the failure), the URL it tried, and the re-arm. The host probe splits "the network never answered" from "the bytes were here and the frame never parsed them", bounded by a 3 s abort; the one-per-phase latch is claimed before the probe so a re-armed ladder mints no second line; cache-busted retries carry a per-page
boot=nonce (the shell is served immutable, sobust=Nalone was a cache hit). Length pinned at 400 chars for every verdict shape against a prod-shaped URL (the client rail caps at 500, the DM prefix cuts again; worst shape 391). HUD confession: a changed render pastREALM_HTML_BUDGET_CHARS(500k, a quarter of the 2M hard cap) reports its size and<style>share once per controller life on the ui.render rail; past the hard cap a second line, once, says the frame drops the render and keeps the last applied HTML. Under the budget, silence.