Spawn
playmakespawnjam
⌘K
visitorclaim namesign in
sign up
playmake
spawn / aboutwhat we're building

pinned

start herewhat spawn isfaqfrequently asked questionsthe betthe spawn bet

updates

engine v5.2LumeJul 10, 2026engine v5.1ConnectionJun 18, 2026engine v5.0For RealJun 4, 2026engine v4.6AtelierJun 1, 2026engine v4.5Surface TensionMay 22, 2026engine v4.4SolidMay 15, 2026engine v4.3GroovyMay 13, 2026engine v4.2ContinuumMay 9, 2026engine v4.1FoundationsMay 4, 2026engine v0.1GenesisApr 29, 2026

pinned

what spawn isstart herefrequently asked questionsfaqthe spawn betthe bet

updates

Lumeengine v5.21mo agoConnectionengine v5.11mo agoFor Realengine v5.02mo agoAtelierengine v4.62mo agoSurface Tensionengine v4.52mo agoSolidengine v4.43mo agoGroovyengine v4.33mo agoContinuumengine v4.23mo agoFoundationsengine v4.13mo agoGenesisengine v0.13mo ago
← All posts
← All posts

engine v5.2.26

Engine v5.2.26

August 16, 2026

A patch in the Lume line.

what's new

  • Terrain edits now sync reliably in always-on worlds. Players who rejoin get the terrain exactly as it was edited — edits made by scripts or by players who have since left no longer silently vanish on rejoin. And freshly edited chunks rebuild ahead of background work, so digs and builds turn solid and visible faster even while the world is busy streaming.
  • Drawn textures (lava, water, animated art made by texture scripts) no longer vanish for a minute and a half when a crowd of players joins at once. The engine now recognizes that the stall was the join rush, not your texture script, and keeps retrying quickly instead of putting the script in time-out.
  • Destroying something now cleans up everything it generated. Roads, rails, fences and other spline-built objects leave no debris: destroying the root also removes the segments, posts, and collision pieces it spawned — including ones orphaned by an old destroy. If your world already has stranded leftovers, one destroy of the old root id (or one destroyObjects sweep) clears hundreds of them; no room restart needed.
  • Every light in your world now shows its marker in god mode — including lights nested inside other objects and lights buried in walls. Markers draw through geometry and you can click them through walls to select and edit the light. No more invisible, uneditable lights.
  • Performance health readouts now tell the whole truth on small screens: when the engine holds your game at native resolution because a lower render scale wouldn't actually help, the report says so in plain words — and the auto-quality tuner stops wasting time on resolution cuts that change nothing, reaching the settings that DO help faster.
  • Always-on worlds stop losing their hosts under load. A busy world working hard no longer gets mistaken for a dead one and restarted — the multi-minute freezes that hit heavy worlds over and over are gone. And when a world genuinely IS overloaded, Savi's tools now say so plainly ("this world saturates the host's sim budget") instead of failing with mystery errors.
  • Textures that get stuck as flat placeholder colors (the pink-blocks look) now heal themselves: the engine quietly retries every few minutes until the real texture arrives, and flipping to another tab and back still kicks an immediate retry. No more refreshing the page to get your textures back after a flaky connection at load.
  • Games can take photos now! window.spawn.view.captureFrame() in your game UI hands your script a snapshot of that player's own view — build safari cameras, photo quests, postcard booths. Each player can only photograph their own screen, the shot is pure game world (no menus or chat in the frame), and nothing is saved or shared unless your script chooses to upload it.
  • Uploading a model with shape keys (morph targets — blinking faces, facial expressions) now tells you plainly what happened: one clear warning says the engine doesn't support shape keys yet and skipped them, and the rest of the model loaded fine. No more models silently arriving with frozen faces — and no more parser jargon drowning out the warnings that matter.
  • Multiplayer worlds get their animations back. Since 5.2.18, animations authored in a multiplayer world were never actually saved to the world's animation library — other players saw characters frozen in place (one world had 129 rigged characters stuck like statues). Authored animation clips now save and reach every player, and existing worlds heal on their own: the next time an animation plays, the library mints and everyone sees the motion.
  • Leftover Savi todo-marker debris cleans itself up. Worlds that collected stray marker cubes from long-gone todo notes (one world was carrying over 2,000) get swept automatically — and the sweep sticks in always-on worlds instead of the shards creeping back on your next visit.
  • Invisible-wall checks now match where walls are actually drawn. Occupancy probes against uploaded and conjured meshes (cave shells, sculpted walls) used to test a ghost box at the object's origin instead of where the geometry really sits — the drawn wall read empty while the empty ground at its feet read blocked. Cave and mesh interiors now answer occupancy correctly, so scripted barriers and "is this spot solid?" logic agree with what players actually see.
  • Looping sounds now register in always-on worlds — Savi's audio tools can see and stop them again. In multiplayer rooms, started loops never landed in the world's sound census: "what's playing?" answered nothing, stray loops couldn't be hunted down and stopped, and players who joined late had no record of the ambience already running. Loops now register for everyone in the room, late joiners included.
  • Discord links in game UI now work in every spelling. A plain HTML link (<a href="https://discord.gg/…">) now asks the player to confirm and opens Discord in a new tab, exactly like openLink(...) and window.open(...) — and a link to a non-partner site tells you why it can't open (in your logs) instead of doing nothing.
  • Steep-slope climbing, sliding, and ground-snap are now yours to tune — skate parks and parkour walls just became authorable. On any physics: { body: "character" } object: maxSlopeClimbAngle: 70 (degrees) walks up steep quarter-pipes, snapToGroundDistance: 1.2 hugs snowboard downhills (or false to launch off ramp lips), minSlopeSlideAngle makes too-steep ground slide you downhill, and characterMass sets how hard you shove crates.
  • Rejoining an edited world can't serve you stale terrain anymore. The engine now recognizes terrain edit history by its actual content, so a rejoin that merges histories (always-on worlds, long-lived sessions) rebuilds exactly the chunks that really changed — no more terrain that looks like it forgot the last session's edits until something else forces a rebuild.
  • Passengers and vehicle parts no longer drift apart for other players. Parts spawned by a vehicle (lights, turrets, seats) used to slide away from it — unboundedly, and the drift stuck — on everyone else's screen whenever a non-host player was driving. Now the driver's machine is the single source of truth for the whole vehicle, whoever drives it.
  • A graphics-driver crash can't blind Savi for an hour anymore. When the GPU device dies, her screenshots and view tools now answer plainly — "the renderer is restarting; your content didn't cause this" — instead of surfacing raw driver errors, and her capture eye reboots itself on the very next look.
  • Debug dumps stop crying wolf about audio. The engine's planned network probes and cross-site audio fetches always LOOKED like failed requests in a dump ("[error] 0B" walls) even when your audio was working perfectly — Savi would investigate healthy sound as if it were broken. Those rows now say what they are ([probe], [opaque]) so real network errors stand out — and they never crowd real failures out of the dump.
  • Savi can now SEE which objects are solid when she looks — no more guessing why players fall through. When she identifies an object or frames it in a look, the answer says whether it actually collides right now (solid: static-mesh, solid: none, …), so a wall that only looks like a wall gets caught the moment anyone checks it instead of the moment a player falls through it.
  • Textures no longer flicker or swap after heavy editing sessions — a texture-cache cleanup racing the renderer could previously freeze the whole session with a "graphics driver hiccuped" error until reload.
  • Tile maps stop going blank mid-edit. If your tile world's generator script briefly breaks while you're editing (or hasn't finished loading), painted chunks now hold their last good art instead of flashing to flat empty slabs — and the engine tells you the generator is unavailable instead of staying silent. The moment the script heals, your pending edits fold right in.
  • Timers scheduled while a world is still starting up (runInTicks, runInSeconds, runSchedule) now fire once the world is running instead of silently disappearing — staged builds queued at startup no longer stall.
  • Voxel block textures stop getting stuck as flat white/pink. Rebuilt chunks no longer forget they're waiting on a texture (the "first block pink, second correct" bug), worlds with more than 64 block materials get all their textures instead of flat tint from material 65 on, and when a texture genuinely can't apply the engine now says why instead of silently painting white.
  • Voxel worlds stop going black when their generator script breaks. Same cure as tile maps, one lane over: if a voxel world's generator script briefly fails to compile, built terrain now HOLDS its last-good look (and hand-placed voxels keep rendering) instead of whole regions silently turning to permanent black nothing — the engine says the generator is unavailable, retries, and heals the moment the script recovers.
  • Recoloring voxel terrain is instant now. Changing a voxel material's color, texture, roughness, metalness, or emissive updates the world as a palette swap — no more full-world remesh (with its multi-second hitch and physics churn) for a cosmetic edit. Structural edits (solidity, collision, block shape) still rebuild what they must.
  • Joining an always-on world with terrain edits stops paying a growing tax. Every join used to quietly grow the world's edit history and force edited terrain to rebuild even when nothing changed — long-lived edited worlds got slower to join with every visit. Syncs are now a clean no-op when nothing changed, so joins stay fast no matter how many times the world has been synced.
  • Game photos come out the way you ask. window.spawn.view.captureFrame() still hands back a JPEG by default, but now captureFrame({ type: 'image/png' }) gives crisp pixel art with no compression, { maxSize: 4096 } keeps the full resolution, and { type: 'image/webp', quality: 0.6 } makes a small share-sized shot. Rapid double-clicks take two photos instead of dropping the second.
  • Savi stops learning object paths that only half-work. Reading an object by its list position used to succeed, so she'd keep using that number as a name — and then every edit or destroy aimed at it silently did nothing. Now the read itself tells her the object's real name the first time, so she talks about your objects the same way she changes them.
  • 2d worlds stop developing permanent invisible holes — the picture always catches up to the world. A tile chunk that got lost on the way to your screen (or kept showing old art after a re-save) used to stay wrong until a reload; now the engine notices within seconds and repaints exactly the chunks that diverged. First loads that raced the terrain script no longer come up empty with no ground to stand on — the map fills in the moment the script is ready.
  • A model that failed to load because of a hiccup now says it will retry — renaming is never needed. When the asset host answers a failure with its own retry window, Savi's scene view now reads "temporarily failed — retries on its own" instead of declaring the asset dead and regenerating it under a new name.
  • Falling through the world now puts you back instead of dropping forever — and your script can choose where. Heightmap worlds get an automatic floor far below the deepest terrain: anything that falls past it comes back at the spawn point. Tune it per place with worldBounds: { bottom: -140 } (any world kind, null for no floor at all), and take over the response by exporting onOutOfBounds(event, api) from the body's behavior — return false and the respawn is yours: last checkpoint, a damage penalty, a game over. Underground builds (terrain.underground: true) keep working exactly as deep as you build them; the net sits 64 m below the lowest terrain.
›technical notes
  • Catchup-phase rail.voxelEdit rows skip present-authority re-adjudication — settled terrain history applies at the joiner's fold (ledger 2268 fix 2, #12915). A journal row replayed at catchup was already admitted by every live observer when it rode, and the relay attested its sender — but the peer translator re-judged it through senderOwnsSource against the FOLDED PRESENT, where journal self-compaction has checkpointed away the prefix carrying the source's create and seat facts. A departed session's edits therefore resolved to nothing and settled terrain silently dropped at exactly the rejoin that most needed it (dig e5e315b8: 65 drop lines in 7d on one app). Catchup/repair rows now skip the re-adjudication; the skip stays visible (warnOnce rail.voxelEdit-catchup-skip when the folded view would have refused). Live-lane rows keep the authority gate byte-for-byte.
  • Player-edited chunks jump the background remesh/collider queue (ledger 2268 fix 5, #12917). Dirty marks sourced from a TerrainChunkEdits mutation (owner + halo) carry an edit-touched flag, and both terrain build systems promote a bounded number of flagged chunks per tick ahead of the background rebuild ordering (residency sweeps, epoch-reset restates, connect backlogs), after the existing safety lanes: server promotions cap at max-submits−1 so a full tick always leaves a budgeted background submit; clients ride up to 6 promoted chunks/tick as single FULL-artifact jobs (mesh AND collider in one build) above already-queued batches. The live voxel-edit fast lane is untouched; beyond the cap a sustained edit storm competes at ordinary streaming rank, so background rebuild always progresses.
  • Churn shield for the texture bake-budget park rail (ledger 2271, dig 51dda5db, #12921). The bake budget's wall-clock law (ledger 628) measured a starved worker's DESCHEDULING as script fault: at join crunch two false faults parked healthy texture scripts 30s, and the probation probe re-faulting under the same churn doubled it to 60s — the 2271 lava-over-invisible-ground 90-second window. Third shield of the family in renderer/scripted-texture.ts (worker startup never counts against the draw; the runaway watchdog re-arms per slice): a budget fault measured during host frame churn — a >250ms tick gap observed inside the attempt window, or the live clock mid-gap at settle — doesn't count toward TEXTURE_BUDGET_FAULTS_TO_PARK and doesn't double a probation delay; the ref retries on the quick 2s ladder until the host produces calm frames. Calm-measured faults keep the law exactly as written (a hot draw loop faults in every window, so ledger 628's park is intact — pinned by test). churnShielded receipt rides RetryState into getTextureResidencyDebug for incident dumps.
  • destroy(root-id) reaps an orphaned derivation tree — and orphans are honestly destroyable (ledger 2275, savi filing 79255910, #12924). Gran Premio: spline roots spawned via run_script were destroyed, but their derived children (~788 live entities — spline segments, collision, posts, rails, kerbs) survived with NO destroy path left: destroy(root) hit the dead-target branch and read spec-absence + entity-absence as "already destroyed", and the persist half's skip receipt ("no object in the spec — destroy is idempotent") taught Savi the orphans were indestructible; the only cleanup was a room restart. A live derived child is now the counter-proof of "already destroyed": destroy(dead-root-id) reaps the live derivation tree before the zero-match warn — each stranded child through the full per-id destroy machinery (relay tomb routing, provenance, nested cascade), exactly what the live cascade would have done. destroyObjects catches the orphan shape and verdicts "destroyed" instead of "not_found" (the re-sweep is honestly not_found); the clientDestroy fan-out receiver reaps other worlds' locally-lofted derivation trees too; kiln's mutations-route skip copy now names the DOCUMENT half a no-op instead of saying "destroy is idempotent". Genuinely-absent ids keep the warn and their idempotent no-op unchanged, and the orphan enumeration is gated cheap-first (marker-column scan before the id-inference walk) so the missing-target hot loop never pays a hierarchy scan per miss.
  • God-mode light markers draw x-ray, pick through walls, and mint for nested children (ledger 2264, dig 91d1634e, #12904). Three convicted mechanisms behind "lights with no icon, can't edit them" (cal's Final Abyss), fixed together because each half lies without the others: (1) markerMaterial gains depthTest: false, routing markers to lume's overlay lane — a depth-tested marker embedded in a wall plane rendered nothing from nearly every angle, and marker-only lights had zero visual presence (guides — range rings, beam edges — stay depth-tested); (2) commitTapSelection and resolveHoverTargets promote editor-visual ROOT hits above nearer world-geometry hits, in lockstep, so the x-ray icon the creator aims at receives the click even through a wall; (3) forEachSpecObject recurses into children (464 of 1040 Final Abyss lights are children — no marker ever minted), child ids mirroring the interpreter's expansion via the shared specChildEntityId so id-less children get the same child-{index} ids applySpec spawns. Depth-capped like the interpreter.
  • The quality governor judges renderScale cuts by the SERVED scale, and health names the internal-resolution floor's fold (ledger 2261, dig 9b12cc1b, #12901). The governor's walk now folds through the same SceneScaleFoldFacts the frame seam consumes (renderer supplier → foldSceneScaleWithFacts), so relief and costHint charges for renderScale cuts are measured on served values: a rung the floor nulls is passed through instead of burning a settle window, and a partial fold charges only the pixel work it actually removes (the ≤1440px density-≤1.5 canvas class — ledger 2261's tablet). Client health gains renderScaleAsked beside the served renderScale, and describeClientQuality names the fold in words: "renderScale 1 (the ladder asked ×N; the internal-resolution floor keeps small canvases legible by design — planned, not a stuck cut)".
  • Reap rails require a stalled sim, not a missing beat — the saturated-world treadmill is dismantled (ledger 2265, dig 50710955, #12907). On the 1-vCPU room host the relay beat rides the sim drain, so a world that saturates the sim budget starved the beat past the 15s window while the sim kept ADVANCING; stale-shell-reap and beatless-replace read that starvation as death, killed the working host, and re-paid a 2-3 minute hydrate per kill (103 summons in one day — the treadmill manufactured the outage it policed). advanceShellLiveness now folds worldTick into every answered probe (lastWorldTickAdvanceAtMs + an observed ms-per-tick receipt); decideShellLiveness holds the stale reap while an advance is inside SIM_SATURATED_HOLD_MS (10 min — a frozen sim stops bumping the stamp, so a corpse still reaps bounded); the beatless-replace gate reads the same facts off the ensure answer's shellLiveness rider and LEAVES an advancing sim alive past the cutoff, while a stalled/never-observed worldTick replaces exactly as before (pre-worldTick engines keep the old gate byte-for-byte).
  • Overload confesses instead of mystifying. Beat proven absent + sim advancing slower than 2× budget mints a HostOverloadFact that run_script's terminal copy quotes — "this world saturates the host's sim budget (observed ~Xms per tick vs 33ms)" instead of relay_exec_no_host_tick mysteries; retired by the next ticking proof and by age, rate-gated so an egress fault never wears saturation copy. view_live_scene's connected-but-no-frame class (RPC-ladder timeouts, no-frame-yet, WebGPU readback teardown) is now booth-eligible with a caption naming the drowning tab instead of claiming "not connected". And a 401/403 on a server-realm /magic asset warm mints a 10-minute tombstone with retryAfterMs (an ungenerated Magic CDN variant needs an authorized surface the headless shell doesn't have) instead of re-asking every spec-sync forever; browser /cdn/* 401s and /magic 404s keep today's behavior exactly.
  • Savi-placed todo markers leave the walked world (jacob's four-beat ruling, #inventors thread 1537819871167914064). Todo markers now carry placement provenance (author, stamped at creation — kiln's savi_todos.author, never inferred). Creator-placed notes render exactly as before, everywhere they did. Savi-placed todos are hidden from the walked world by default and materialize only while the creator is in god mode (the workshop view), spawning settled on the mode flip — no assemble storm. In god mode they are display-only: every _savi-todo/ entity resolves to null in resolveSelectionOwnerEntity, so god-mode controls (select, hover→drag, attach-pick, the server selection applier) pass through a spark like glass instead of grabbing it. The wire also accepts worldVisible: true per row — the creator-said-yes opt-in that renders a savi todo like a creator one (kiln-side storage for the flag is a follow-up; the engine contract is complete).
  • The texture give-up park is a bounded 5-minute re-probe ceiling, not a terminal verdict (ledger 2267, chukinice's pink blocks, #12912). 15 anonymous transport failures used to park a texture {terminal, retryAfter +1h} — and revalidateResidency deliberately skips terminal parks, so a boot-window network flap stranded healthy voxel textures on the placeholder for the whole session, page refresh the only reliable cure. But the ladder reaches the give-up branch only WITHOUT a server verdict (tombstones, dead 4xxs, quota windows all classify first), so the 15-failure fact is about this session's transport, never the asset — terminal was the wrong shape. The crossing now settles the ladder on its ceiling: non-terminal, retryAfter = 5min (the existing long-cooldown family) + up-only jitter. The tick timer re-probes every ~5min forever (12/hr/client/asset), the visibility/projection recovery sweeps cut the clock like any transport backoff (the reporter's observed tab hide/show cure, now mechanical), and source changes / the cdn-session wake still clear immediately. Server-verdict parks (cook-kill tombstones, dead 4xxs, transport-less URLs) stay terminal — the tombstone-hammering class (dig 628c4096) stays closed.
  • window.spawn.view.captureFrame() — game scripts photograph the calling player's own view (r-709895c6, #12913). Safari Islands' photo mechanic died on the authored-realm cutover: HUD JS lost spawn-canvas.toDataURL() and no realm lane handed game code the rendered frame. The primitive, not a pipeline: captureFrame resolves a JPEG Blob of the invoking player's own game-canvas frame (the same swapchain-readback core Savi's chat screenshots use — ≤1280px, encoded in the render worker); display is URL.createObjectURL, sharing is the already-allowlisted assets.uploadImage — capture→upload→use, each verb doing one thing. One capture in flight per realm; coded capture_unavailable verdicts. Own-view-only and canvas-only are structural, not policed: the responder runs on the capturing player's page, no protocol message addresses another client, and the capture reads the WebGPU swapchain — kiln chrome, the rail, and the HTML HUD cannot appear in the pixels. No pixels reach a server unless the script uploads. game-ui.md + the PHOTO_CAPTURE_UI tome example teach the compose.
  • Shape-key drop at GLB parse warns once per file, honestly (ledger 2279, #12925). The lume parser's fixed vertex slots drop morph targets by design (jure holds the support door). The drop's only trace was a per-primitive "N morph target(s) skipped" line — parser jargon that a multi-material face minted once per primitive, flooding the 3-named cap on the model-parse-warnings diagnostic; a creator uploading a blinking face learned nothing. Now ONE file-level warning rides the same ParsedGltf.warnings lane (surfaced per-asset via reportModelParseWarnings, ledger 1862): "this model carries N shape keys (morph targets) — the engine does not support them yet; they were skipped, the rest of the model loaded". Counted from the raw JSON (primitives skipped for mode/Draco still count); per-mesh count is the max across primitives per the glTF spec, the file's is the sum across meshes. No behavior change otherwise — the drop stays, it stops being silent.
  • Relay tier: authored function clips ratify again — the simulator mints, the save lane delivers (ledger 2280, #12930). Third specimen of the unported-server-organ family (after relay-reap-lane and relay-script-exec): the ratifier (ingestAuthoredClipOffers, a mode:'server' system) never got ported when the Aug-5 flip made relay the default room host, so every multiplayer app on engine ≥5.2.18 staged AuthoredClipOffers nothing in the room could ingest — assets.authoredClips stayed empty and every remote renderer held rest pose (Nine Heavens' 129 frozen NPC rigs, 4 reports). Relay needs no ratifier organ at all: kiln is the spec authority there, so the single-live-writer rule the offer leg respected dissolves. canMintAuthoredClips now answers true on relay wires (ClientRelayWireModeResource); the entity's simulator — the one machine that can sample the played closure — mints directly, and a new client-seat system (tome/authored-clip-relay-fold, self-gated relay-and-not-singleplayer) drains the same pending→fold path the kernel server runs: both spec views + TomeSpec push + one patchAssets row the mode-both drain forwards into the relay save lane; kiln folds it onto the durable head and the SPEC fan-out delivers the library to every member. The offer leg is structurally unreachable on relay; stale pre-fix offers retire through the existing client sweep the moment a re-play mints their identity. Kernel tier byte-identical (offer→ratify→sweep); non-editor simulators' mints stay session-local at the save lane's permission gate. E2e twin: relay-authored-clips.e2e.test.ts (verified red on the pre-fix gate).
  • Savi todo markers own their prefix — untracked _savi-todo/ entities reap through the ordinary delete path (ledger 2278, dig 9b07936f, #12929). The marker reconcile despawned only what its own state.entries minted THIS session, so a fossil from any other origin — a prior session's copy restated by a relay journal head (the pre-2081 statement lanes journaled client-realm sparks as durable keyed heads), a peer's world-sync answer — was invisible to it from birth and stood forever once its todo row died: ress's 2,244 shards = 132 todos × 17 cubes, one full set per marker the app ever minted; raw-exec destroys were client-local and the journal restated every shard on the next visit. THE INVARIANT: the marker system OWNS _savi-todo/ — anything under the prefix the live entries did not mint is an orphan, reaped at every reconcile through the ordinary delete path (the ObjectAPI destroy verb): on a truth relay the ghost-destroy gate's provably-unowned lane tombs the journaled head on the reap lane, so a swept fossil cannot be restated at the next fold. Deterministic ids make the sweep exact; belt-and-braces, every minted spark entity (cubes + shockwave) now carries TomeSpawnedBy rooted at the live local avatar so client-realm-scope-reap covers the family by the standing lifecycle rule too.
  • Mesh-kind occupancy stand-ins carry the local-bounds center (ledger 2282, #12931). api.overlapPoint/overlapSegment resolved convexHull/trimesh/bespokeMesh colliders to a bounds box centered at the BODY ORIGIN — but the 3D body factory attaches the raw vertices (plus the collider-desc translation) to the body untranslated, so geometry authored off origin (Savi's shared-anchor cave walls, ~30m from their own feet) probed at a ghost position: the drawn wall read empty, the empty feet read blocked. resolve2DMeshFallbackHalfExtents becomes resolveMeshFallbackLocalBounds — half-sizes plus the BODY-frame center (vertex-cloud midpoint composed with the collider-desc translation, resolved from the same sources makeColliderDescs builds colliders from). The occupancy OBB and the F3 mesh marker now sit at that center, rotated with the body (the box case's translation precedent). 2D consumers are untouched by construction: the 2D fallback CUBOID is origin-centered (makeColliderDescs / mantle parity), so the feet-offset, 2D collider-desc, and mantle paths keep reading half extents only. Rapier-truth pins verified red on pre-fix code (dig cff2411e): an offset-authored bespokeMesh answers at the DRAWN wall and stays silent at the feet-displaced ghost, both directions, with rapier ray receipts for where the vertices actually are; centered-mesh control unchanged. The approximate contract (a concave shell's box covers its interior) is documented behavior and unchanged.
  • Relay tier: the loop-voice registry gets its wire — 4th specimen of the unported-server-organ family (ledger 2280 census). On relay rooms the registry (TomeSoundLoops, music-apex D3) had NO writer and NO wire lane: recordSoundLoopStart's gate (isSoundLoopAuthorityWorld) refused every multiplayer client, the ingest sweep is mode:'server' and never runs on the tier. So audio.playing().loops answered [] in every relay room (the census the audio skill teaches Savi to sweep with), the stopSound never-minted teaching lost its registry belt, and a late joiner had no standing-loop truth at all — loop starts are juice EVENT rows and relay catchup strips event rows as history (the join-storm-juice gate in relay-peer-apply). The port is the relay-music-lane pattern verbatim (same tome/spec entity, same free-running-clock problem, same journal): the play-executing client — the room's one machine that saw the verb — records into its LOCAL registry (isSoundLoopAuthorityWorld gains the ClientRelayWireModeResource arm, the #12930 mint-gate stamp), publishRelaySoundLoopsHeadIfChanged ships the head as one anchored tome/spec keyed row on both netcode egress edges, and the ingest half rebases every entry's startTick into the receiver's clock, strips the rider, and stamps the echo guard — LWW whole-map by the room's total order (the concurrent-jukebox posture, named in the lane doc). Scope, honestly: this delivers the CENSUS and with it the primitive creator code needs to replay standing loops for joiners (the D3 posture — the sweep stays creator code); no tier auto-replays registry entries audibly, a cross-tier pre-existing gap. E2e twin relay-sound-loop-lane.test.ts (mint gate both wires, anchored egress + quiet-tick dedupe, stop head, catchup rebase = the late-join delivery, round trip through the real parse gate, anchorless strip), verified red pre-fix: reverting the mint gate alone fails exactly the four mint-dependent pins.
  • <a href> anchor clicks in authored game UI ride the openLink door-out lane — the third silently-dead external-link spelling dies (ledger 2286, savi filing ff51ce5b, Shogun's Return round two). The realm sandbox has no allow-popups and the shell owns navigation, so a target="_blank" anchor click died with no confirm, no window, and no error anywhere — and a default-target anchor would have navigated the realm document itself away, killing the game UI behind a frame-refused external page. A document-level bubble listener in the frame (realm/frame/open-link.ts) now consumes every external http(s) anchor/area click and routes it through the same openLink machinery as window.open: partner domains (exactly discord.gg) request the owning shell's confirm sheet naming the destination; anything else gets the readable refusal verdict plus the teaching line in getLogs; http refuses not_https loudly. Authored preventDefault() keeps the click (game code that owns its anchors is untouched); fragment/javascript:/unresolvable hrefs keep native in-frame behavior. Same allowlist, same confirm, same rate caps — no policy widening beyond jacob's 08-14 ruling.
  • Character-controller tuning is now authorable from object physics (savi platform-gap filing 043591b0). PhysicsCharacterControllerConfigValue always had every field; the spec merge exposed only autostep. The merge (tome/physics.ts) now resolves the whole config from the spec: maxSlopeClimbAngle / minSlopeSlideAngle (DEGREES at the spec surface, clamped 0–90, radians on the config), slideEnabled, snapToGroundDistance (meters, non-negative, false = never snap), characterMass (kg, > 0), applyImpulsesToDynamicBodies, offset, normalNudgeFactor. Defaults unchanged — 45° climb, 0.4 m snap, slide on — so every existing game builds a byte-identical controller config. Wrong-typed/non-finite values drop per field so the default stands; filterGroups stays owned by collisionGroups, up/filterFlags/gravityScale stay non-spec (probe incoherence / raw backend bitmask / dead field). Authored values echo in getProperty('physics') (degrees/meters/false, only when differing from the defaults) and re-applying the read-back reproduces the identical config; removing an authored field resets it to the default on re-apply. The controller-config defaults now live in ONE home (tome/physics.ts DEFAULT_CONTROLLER_CONFIG; interpreter + ObjectAPI consume it), and spec re-applies value-compare (f32-tolerant) before writing so an unchanged re-apply no longer dirties replication.
  • Chunk journal identity is a content hash, not the (revision, timestamp, count) triple — the rejoin stale-terrain hole (ledger 2268 fix 3's residual class). The chunk build cache key, the durable-baseline install's converged-skip, and the serialize-reuse fingerprint all identified a chunk's edit journal by appliedRevision:lastServerTimestamp:count — unique on ONE lineage, but a relay rejoin merges lineages (the container-era baseline rebases under the session journal, an answerer's world-sync statement is compacted into synthetic ids, a reorder wears the same stamps). Two journals of different content under one triple meant the cache answered the STALE inputsHash and the installed mesh passed as current — or the converging install was skipped as "already converged". terrainChunkEditsContentHash hashes the ordered editId sequence plus each command's bbox/payload, memoized per value object; all three fingerprints now include it. Pinned: same-triple journals with reordered/foreign ids/different payloads hash differently; a byte-identical re-statement stays the churn-free fixed point.
  • The ownership walks are controller-aware on chain links — transferControl now moves a whole spawned subtree's authorship, not just the target's (ledger 2299, incident 9eca5c1d). Both owner walks — the server's resolveEntityOwnerClientId (tome/ownership.ts) and its client mirror walkReplicatedOwner (tome/api/control-utils.ts) — probe TomeController at EVERY link of the TomeSpawnedBy chain instead of only on the starting entity. Controller-blind, children of a member-driven vehicle resolved unowned: the place host claimed them into its hosted remainder and published them while the driver's envelope published them too — two writers on one hierarchy, and the host's stale/projection-poisoned child locals LWW-fought the driver's live stream (unbounded parented-child drift on every observer, made durable by the host-realm spec save). Now a driven hull's spawned children resolve to the driver's anchor: exactly one publisher, and host-driven vs member-driven vehicles behave symmetrically. Untransferred chains, stand-riders, and bone children are untouched (the walks never read TomeParent); a chain link's controller outside local AOI reads conservatively as maybe-owned, like every other dangle. The dead isEntityOwnedByClient export (zero callers) is deleted rather than left as a third controller-blind copy of the walk.
  • A lost GPU device can no longer strand the headless capture rig — captures answer a structured renderer-restarting verdict (ledger 2292, savi filing bc24dd58, #12940). The Ashenreach dead hour: the capture rig's GPU device died and stayed dead ~1h — every free-camera capture surfaced the raw Dawn throw ("Failed to execute mapAsync on GPUBuffer: [Device "spawn renderer"] is lost"), viewport grabs claimed "the renderer has not produced a frame yet", and six wisps stayed blind continuously. Kernel half: after device.lost latches, the frame loop keeps ticking (WebGPU calls on a lost device are no-ops), so the capture admission gate saw a live loop and parked captures toward raw mapAsync throws. Now deviceLostPosted answers every capture ask immediately with the structured verdict (DEVICE_LOST_CAPTURE_ERROR — planned-events law: "this is a device/renderer failure, your content didn't cause it"), checked first in frameLoopPausedReason and translated at the three result funnels (scene view / object preview / pixel provenance) so even the in-flight capture racing the loss answers the verdict, never the raw driver text. The booth half (dead-eye retirement → reason 'renderer-restarting', the automatic renderer restart for headless sessions) deploys with the booth process, engine-version independent. Receipts: pre-fix red (3 failed) then 32/32 renderer-device-loss vitest green.
  • The network ring names its own planned requests — designed opacity wears [probe]/[opaque], never failure vocabulary (ledger 2290, dig 63bce214, #12943). Every dump of every audio-playing game carried a wall of fake "[error] 0B" rows (~2k/day platform-wide): magic-cdn readiness probes answer opaqueredirect (status 0 / 0 bytes BY DESIGN), and payload fetches ride a 302 to spawnfile.io whose missing Timing-Allow-Origin zeroes sizes for fully successful 200s. The ring classified both as [error]/[stall], teaching every reader — Savi included — that audio was failing while the serving path was healthy end to end. A probe is field-identical to a genuine network error in every PerformanceResourceTiming attribute (verified empirically in Chromium: all-zeros either way — opaque-redirect filtering, not the TAO check, zeroes it), so the engine now names its own planned request: ensureMagicCdnAssetReady registers the URL with the ring (noteResourceProbeUrl, bounded) before the probe fires, and the matching all-zeros row labels [probe] — regardless of duration, replacing the <2ms carve-out that missed every network-served probe on a real-RTT link. A media element loading a registered URL reads the same designed zeros and labels [opaque]; the TAO-blinded successful fetch is detectable without registration (Chromium exposes responseStatus through a failed TAO check while zeroing sizes AND responseStart, so status 1-399 + zeroed sizes + responseStart 0 labels [opaque] — a TAO-passing cache hit keeps decodedBodySize/responseStart and can't match). Both labels ride the NORMAL TAIL, never the anomaly ring — designed opacity can't evict real failures. The honest-failure lane is untouched: status>=400 and unregistered same-origin all-zeros with wall time spent still label [status]/[error], and slow outranks opaque because duration survives TAO opacity. Verified end-to-end in real Chromium against real probe/no-TAO-redirect/socket-destroy traffic.
  • identify_object and frame looks now carry the solid verdict — collision state is visible on the surfaces authors already use (ledger 2297, the Castle Oravelle forensics: 63% of a wisp-built castle carried no collider and every verification look passed the ghost walls). Each 3D identify match wears one field, solid — the entity's LIVE collider registration at query time, read off the sim's physics components (tome/solid-state.ts), never the spec's intent: <body>-<shape> in the physics spec's own vocabulary (static-mesh, static-box, dynamic-box, trigger-box, character-capsule, vehicle-box, …) or the one word none — no collider registered, players and physics pass through. view_live_scene's frame captures carry the same field for the framed object. The read rides the existing main↔sim debug rail as a new pure physics.solidState batch request (liveSpecDrift's sibling; the rail helper is now shared in _entry/client/sim-debug-rail.ts), fails OPEN (no rail / old worker ⇒ the field is absent, never an invented none), and skips terrain matches (terrain collides per-chunk under engine-local ids — a per-object none there would be a lie players stand on). No spawn default changed: omitted physics still means no collider — the census showed most omissions are correct (lights, fx, overhead vaults, banners); what was eliminable was their invisibility, not their existence.
  • The lume texture service's live destroys (eviction, retired-handle sweeps, superseded repaint sources) now defer behind submit retirement: handles queue and destroy when device.queue.onSubmittedWorkDone resolves, so a destroy can never fire between a frame task's encode and its queue.submit — the "Destroyed texture used in a submit" validation class renderer.ts latches as an unrecoverable session (dump 718f34ef, ledger 2298). Handles released while a fence is in flight join its batch. Teardown (dispose(), device-lost recovery) destroys immediately — recovery never waits on a dead queue — and fence-less stub devices keep synchronous destroys.
  • A null tilemap generator module must never silently empty a chunk (ledger 2283, savi filing — app af41454d "Ashenreach", dump group 28625c83, #12934). Any chunk touched by a runtime tile override (api.clearTile / api.setTile) repainted as a flat pale slab while the definition's generator module was transiently null (mid-edit broken script; the generator-module memo pins null per definition object): evaluateTilemapChunkCells silently evaluated overrides-only cells and the forward lane faithfully delivered the fabricated-empty chunk over standing content. The silent degrade dies: evaluateTilemapChunkCells returns null when the definition declares a generator but its module has no tileAt (compile failure, missing export, or a pinned transient) — it never fabricates empty cells for a generator-backed map. refreshDirtyChunk HOLDS on null: last-good cells and colliders stand, both cursors stay diverged so the sweep retries every tick, and the pending edit folds in the moment the module resolves or the next revision bump republishes wholesale. publishChunk degrades to overrides-only on null (a fresh publish has no last-good cells to hold; broken-from-install maps keep rendering their hand-painted setTile tiles — the documented posture). Either way the engine says so: a tilemap-generator-unavailable diagnostic, once per content version. Pure-override maps (no generator authored) evaluate exactly as before. Tests pin both directions: the constructed mid-edit null holds the chunk, says so once, and heals on restore (fails pre-fix with the exact silent-empty re-forward); the healthy same-sweep carve lane is pinned untouched; the broken-from-install degrade is pinned loud.
  • Timer dispatch now HOLDS a due timer whose owner is simulated by NO world (a cold room's place-host election window, an unhosted or successorless-held place pause) instead of consuming it unfired — it fires on this world's first simulated-here tick at or after due, mirroring the authority-pause retention semantics. New predicate isEntitySimulatedNowhere (tome/simulation-authority.ts). One dispatch site covers the whole timer family: runInTicks, runInSeconds, runSchedule, onModelReady's poll, engine-owned cleanup timers.
  • Voxel block textures stop stranding on the white placeholder — three causes in voxel-buckets, none of them the fetch ladder (ledger 2267, chukinice's World of Spawncraft, 117 materials). The pink/white-block class was fixed twice at the texture service's retry ladder, but the module that decides whether a voxel block gets a texture (lume/terrain/voxel-buckets.ts) had three defects, each sufficient alone: (1) rebuild severed texture→chunk tracking — syncTerrainVoxelChunk bound the fresh palette BEFORE removeVoxelBucketChunk deleted the chunk from chunksByTexture/chunksByPendingTexture, so every rebuilt chunk (edit remesh, forced remesh, LOD swap) referencing a still-loading texture was forgotten and wore layer 0 until an unrelated remesh — now split into removeVoxelBucketChunk (residency only, replace-in-place) and retireVoxelBucketChunk (left the scene); (2) a literal 64-layer albedo array, never grown, reservations never released — material 64+ of a 117-material world rendered flat tint forever; the array now grows by doubling (bounded by the device's granted maxTextureArrayLayers), copying resident layers on growth; (3) every mask verdict (uncompressed source, family/mip mismatch, ceiling) was silent — verdicts now land on rails dumps and Savi read.
  • A null voxel generator module must never silently empty a chunk (ledger 2293, savi filings 9985add3/3c4ee508/933ef3c5 — app b2537c36 "Blockcraft: Coelho & Cobaia"). The voxel twin of the tilemap silent-empty law (ledger 2283): when the generator module resolved without materialAt (compile failure, or a transient pinned by the generator-module memo — never authored state; spec-apply refuses voxel definitions without a compiled materialAt), buildVoxelChunkData returned null and materializeVoxelChunkOutput minted a VALID EMPTY output — geometry renderDataState "omitted", colliders [], a real outputsHash. The job succeeded, tracking marked the chunk clean forever, later edits rebuilt through the same null and stayed empty, and nothing said so (the ledger-1771 failure-visibility lane only sees throws) — a contiguous canyon region rendered as a pure-black never-rendered mass on every client while host sim stayed healthy. The silent degrade dies, on the tilemap template: the build emits the OVERRIDE-ONLY degrade (an all-air grid with the chunk's voxel edits applied — hand-placed placeVoxel content keeps rendering) tagged generatorUnavailable, plus the anomaly-law voxel-generator-unavailable diagnostic once per content version. Consumers with a standing output HOLD: last-good mesh/colliders stand, the chunk routes into the ledger-1771/#597 failure lane (visible warns, doubling backoff, park on proven-deterministic with the park lifting on any inputs change), and heals the moment a build completes with the module resolved. Never-built chunks install the degrade — the broken-from-install posture — and later degrades keep installing so edits keep folding in while the generator is down. The generator-module memo's null pin stays: the hold makes it non-destructive. Tests pin both directions with pre-fix receipts; the healthy lane is pinned unchanged.
  • Voxel material shading edits (color/texture/roughness/metalness/emissive) no longer rebuild every chunk — the voxel edition of the heightmap shading fast path (ledger 1324's split, voxel side). Palette-only fields were folded into the terrain signature, the definition revision, AND every chunk's definitionContentHash — so any patchTerrain touching a color or roughness re-keyed every resident chunk (~1500 on a desktop voxel world), marked the whole place dirty, dropped every fast-lane resident grid, and paid a full remesh + collider swap across the world. The split mirrors the heightmap classification: STRUCTURAL (id, opacity, transparent, liquidKind, surfaceInset, solid, cutout, collision, countsAsTerrainSurface, the block script + baked shapeTable — everything the mesher reads) keeps re-keying builds; SHADING-ONLY (color, texture, roughness, metalness, emissive, emissiveIntensity) rides the per-place TerrainPlaceVoxelMaterials library to buildVoxelMaterialOverrides with no chunk rebuild.
  • The world-sync terrain statement ships journals verbatim — compaction there made every sync grow every edited chunk's journal and tear down its resident grid. The answerer compacted each chunk journal before stating it, but compaction mints synthetic terrain_compact_* ids that match NO live row, and the requester converges by editId — so every statement was a non-append: the journal grew one compact stamp per sync (probe receipt: 20 → 21 → 22 → 23 → 24 across four syncs), and the fast lane tore down every edited chunk's resident grid on every join/rebase. Compaction keeps earning its keep at the durable save/load seams (ledger 790) where its ids never meet a live journal; on the relay statement the payload is byte-budgeted per frame anyway and a verbatim statement over an identical journal is a no-op install. Pinned: a joiner holding the host's 20 edits sees three successive statements leave its journal byte-identical (red on the old compacted statement).
  • view.captureFrame({ type?, quality?, maxSize? }) — the encode is the game's, and calls queue instead of dropping (chucky, incident 4ebda0e1 follow-up to #12913). The primitive shipped with its policy baked in (JPEG q0.92, ≤1280px — the chat-screenshot defaults) and one-in-flight semantics that dropped a rapid second press with capture_in_flight. toDataURL(type, quality) gave the Shutter Isles HUD both knobs; the realm verb now does too. The encode contract has one home (renderer/screenshot.ts resolveScreenshotEncode(w, h, options) — the same function every viewport capture rides: chat screenshots, SEO/savi-note thumbnails, scene views): type ∈ image/jpeg | image/png | image/webp, quality 0..1 (lossy codecs), maxSize = longest-side fit in px, never upscaled — the swapchain frame is the structural ceiling, no separate constant. Defaults are byte-identical to before. The responder validates the ask at the realm boundary (parseScreenshotOptions): a typo ('jpg', quality: 92, maxSize: 0) answers a bad_args verdict, never a throw across the port; the validated options travel realm → responder → renderer handle → capture-screenshot worker message (screenshotOptions) → the readback+encode. Captures now SERIALIZE per realm — each call awaits the one before it and takes its own frame, in order (a double-click is two photos), bounded by the sdk.call rate cap and the 15s host screenshot timeout; the chain does not hold the small asset lane, so a photo burst never starves audio/texture requests. capture_in_flight is gone from the code, the skill, and the tome example; capture_unavailable (no frame yet) and too_large (the 32MB relay cap — a structural backstop, unreachable for any real frame) remain. Renderer-side, pending screenshot requests carry their options and requests sharing an encode still share one readback+encode of the same frame.
  • getSpec refuses list positions on id-keyed arrays — the read lane now answers the way the write lane treats the same handle (ledger 2307, ravi's teach-savi ruling; the ledger-1247 masquerade). The read lane used to RESOLVE places.main.objects.1922 (a numeric array position) with a warning while destroy/setObjectProperty refuse it — an inconsistent primitive that taught the wrong grammar: Savi learned the shape from read receipts and died at the writes (app 3ad02cb1 lost a session to destroy("1922") no-ops minted by exactly this read). At the shared resolver (readSpecPath, the one spec-path door — god-mode/inspector reads go through id-only findSpecObject), a numeric segment landing on an id-carrying array element now refuses: getSpec answers null — the same handle those verbs would ignore — with a teach verdict naming the durable id ("1922" is a list position, not an id; the object at places.main.objects[1922] is id "body-canary-hands" — use getSpec("places.main.objects.body-canary-hands")), digit-folded to one teaching per path shape per cooldown. Ids win over positions: an id that merely LOOKS numeric (an object literally named "1922") still resolves and is never shadowed by whatever sits at that index. Arrays without id-keyed elements (tags, spline points, …) have no id grammar to protect and stay positionally readable, unchanged.
  • Tilemap chunk residency reconciles sim↔renderer — the silent-divergence class dies (ledgers 2303/2300). The sim→renderer tilemap chunk forward (terrain/tilemap-chunk SAB lane) had no delivery guarantee and no reconciliation: render-channel stream-reset recovery discards buffered frames and a poisoned reader drops queued ones undecoded, and forwarded cells are not ECS components, so the recovery snapshot never re-derived them — a lost chunk frame left the renderer's resident set diverged forever (2303's never-painted rows, 2300's stale-after-resave art). Now every forward carries a publish revision (codec v6), the renderer reports its resident chunk keys + revisions per place on the 1Hz perf-sample relay (collectTilemapResidencyCensus, cached per residency revision), and the sim's reconciliation sweep (reconcileTilemapResidencyForPlace, run from render/ecs-sync's tilemap drain) diffs the census against the retained forwards — re-sending exactly the missing/stale chunks, sending cells: null removals for ghosts, and pacing itself behind a quiet window so in-flight publishes are never judged as divergence.
  • The boot-window generator race repaints in-session — both terrain lanes (the 5.2.26-candidate zoo side2d first-load specimen). A first load that sweeps before the generator module resolves used to install the override-only degrade and mark it clean forever: tilemap publishChunk had no retry at all (fingerprints move on override churn, the window on viewer movement, the revision on def edits — none on module resolution), and the voxel lane's never-built generatorUnavailable install (ledger 2293's broken-from-install posture) reset the failure entry and recorded the degrade as the inputs' installed hash. Now tilemap tracking remembers degraded publishes and republishes them — cells, forward, and colliders — the sweep after tileAt resolves; voxel degrade installs enter the failure lane (backoff retry, park on proven-deterministic) and deliberately stay out of installedHashes, so the dirty mark survives the spurious-mark absorb and the healed rebuild passes the install verdict on both realms.
  • Client debug dumps carry a tilemap census. EngineBugRenderDiagnostics.tilemaps mirrors the same census rows (place, chunkCount, space-joined cx,cy@revision — clipped at 4KB per place on ingest, chunkCount stays complete; [] is a positive no-tilemaps receipt, null = pre-instrument). Tilemap state in dumps was previously ZERO (terrainTextures is heightmap-only) — 2303's "rows r ≥ 0 never paint" took a multi-session dig instead of one grep over this field.
  • The failed-asset teaching splits by the server's own Retry-After (ledger 2306, incident 14e46bc9). The scene-view unready report's "failed" bucket taught one story — "a receipted death … stays a placeholder until regenerated under a new name" — for every failed verdict, including the TTL'd tombstone kiln mints for TRANSIENT trouble (the import/serve lane answers a source-fetch 5xx as 502 + x-cook-state: failed + Retry-After = remaining TTL). Five known-good uploaded GLBs earned that permanent-death teaching off a ~6-minute workshop-door restart while the files served 200/valid glTF 40 minutes later — reference churn for alive assets. Now a failed verdict that carried the server's own Retry-After window rides the unready atom as retryInMs (RendererAssetService marks serverRetryWindow on the terminal-park and ladder writes; the lume texture service's cook-kill park mirrors it), and buildSceneViewAssetNote renders it as its own clause: "TEMPORARILY failed to generate … the engine re-asks on its own within ~Ns … do NOT regenerate or rename". True terminal verdicts — dead 4xx answers, content-policy rejections, no-window parks — keep the receipted-death/rejected teachings, and the terminal clause still leads the temporary one. The wording deliberately avoids "still generating/loading", so capture settle gates read a timed pause as settled trouble, not streaming.
  • worldBounds.bottom + onOutOfBounds — the bottom world bound as scriptable vocabulary (r-1a763818, the second half of DJ Def1le's ask). terrain.underground: true (#12802) removed the surface fall-catcher for declared under-terrain builds, leaving below-surface falls bottomless. The new place field is the deep net: a character or vehicle whose feet cross below the resolved bound (authored worldBounds.bottom anywhere; 3D heightmap places default to verticalRange[0] − 64) fires the body's own onOutOfBounds(event, api) hook once per excursion — event = { bottom, position, placeId, respawnPoint } — and, unless a hook returns false to own the response, the engine teleports the body back to the place's spawn point with motion zeroed (physics-coherent feet+body teleport, the chunk-rescue's transform discipline). The engine owns detection and the default; the response is the creator's script — respawn-at-checkpoint, damage, end-the-run are all one hook, never engine surface. Detection, hook, and default all run on the body's simulator (shouldSimulateEntityHere, the behavior execution-authority rule), every catch speaks in the runtime log ring (world-out-of-bounds), and the latch re-arms only when the body is next observed above the bound — no repeat-fire loop while below. The chunk-rescue is untouched: where it is armed it catches sunk bodies at the surface reading long before any bottom bound.