engine v5.2.29
Engine v5.2.29
August 17, 2026
A patch in the Lume line.
The 08-17 walk train, re-cut: everything since 5.2.26 in one cut — the join-wall relief, the confession family, the ghost cures, exact math — plus the walk-found voxel sim fix.
what's new
- Big long-lived worlds no longer melt down in multiplayer. When a world grew past an internal size ceiling, our background room helper was refused entry over and over — each attempt paying a huge download — and all that churn kept disconnecting the actual players, resetting them to spawn and timing out saves. The helper now joins the cheap way players already do and streams the world in behind the scenes, and when something does fail to join, it retries politely instead of hammering the room.
- Savi's helpers and the room's background host now enter big worlds the cheap way players do. Instead of downloading the entire world just to get in the door, they join instantly with the essentials and stream the rest in behind the scenes — so a large world's helper shows up in seconds instead of paying a giant download at the doorstep.
- A player whose game freezes for a while (heavy tab, background throttle, giant world load) now snaps to the room's CURRENT state when it thaws, instead of replaying minutes of stale multiplayer history — a window during which their client silently dropped other players' actions.
- Remote players and NPCs recover to their live positions faster when a busy room backs up. When chat and world updates interleave with movement, a slow device now skips straight to each mover's latest pose instead of replaying a backlog of stale ones.
- Bots and props no longer freeze or snap backwards for some players when the room's host changes. During a host handoff the engine used to throw away the outgoing host's last updates on players whose view of the room was a beat behind — now it holds them and applies them the moment the handoff settles, so everyone sees the same world through the switch.
- Saves and loads that run when a player joins no longer fail during the room's first moments. Every boot had a short window where the room was still connecting; a script reading its saved data there got errors and fell back to defaults, and saved terrain edits waited on a retry instead of loading cleanly. The engine now holds those reads until the connection is ready and runs them immediately — and if the room genuinely can't connect within 30 seconds, the script gets one honest error saying the call was never sent, so retrying it is safe.
- When the engine slows something down on purpose, Savi can now see exactly what and why. If the engine parks heavy shader work to keep a session playable, that shows up as a named fact — which safeguard engaged, how much it's holding, since when, and whether it will come back on its own — so "why does my game look different on that phone" gets a straight answer instead of a mystery hunt.
- Five more engine safeguards now explain themselves instead of looking like bugs. When a game runs slow because the device can't keep up (dropped sim ticks), when multiplayer movement snaps to the latest position under backlog, when terrain is slow to build on an overloaded device, when a run_script times out, or when textures unload under memory pressure — each now shows up in Savi's health check as a named fact with the real cause. The run_script one matters most: a timeout on an overloaded host now says "the host is overloaded — your script is not proven at fault" instead of wrongly blaming your code for a busy loop.
- A UI whose script library outgrows the mount budget now tells you, instead of silently keeping the old UI alive. If your
ui.jsrequire closure crosses a realm cap you get one loud fault naming the measured size and the budget, the receipts read "mount dropped over cap" instead of "sent", and trimming or merging lib modules re-mounts immediately — no dummy edit needed. The module-count ceiling also rose 256 → 4096, so the byte budget is what governs. - Clearer answer when a script destroys a generated piece of a wall or road. Fences, walls, and roads generate their segments automatically; those segments disappear (and come back) with their parent. When a script tries to destroy one that's already gone, the engine now explains that instead of guessing the script used a wrong id format.
- Scripts that touch an object living in another area now get told exactly that. Writing to an entity that lives in a different place used to fail with a generic "not found" that read like a typo; now the error names the entity's real place and states the rule — writes only apply within the calling place — so you stop hunting for id typos that aren't there.
- Animation channels playing your authored clips now report how long they run.
getChannel().durationused to read null forever for authored clips —finishednever fired andprogresssat at 0 — so scripts couldn't sequence off a clip's end. All three now answer from the clip's own minted timing; a duration you set by hand still wins. - Model references pasted as bare Magic CDN filenames now just load. A ref like
public.aHR0….glb(the canonical import filename without the/cdn/prefix) used to sit as a permanent placeholder while the scene note claimed the asset host had declared it dead. It now fetches as the identity it names — and references that genuinely can't resolve say so honestly: "no request was ever made", never a fabricated death verdict. - Savi can see broken shaders, looks, and script errors in multiplayer worlds again. When a look, material, or texture script fails on someone's screen in a multiplayer room, Savi now gets the actual error (in her logs and as a heads-up message) instead of nothing — so she fixes your one-line bug instead of chasing a phantom engine problem.
- Savi can no longer mistake a silently frozen game tab for the live world. When a tab's connection looks alive but has stopped receiving updates, her scene captures now carry the world version they were rendered from, so she says "your tab is showing an old version — reload to catch up" instead of describing the stale frame as current and rebuilding things that already exist.
- A silently frozen game tab now heals itself instead of gaslighting the session. When a tab's world stops updating while its connection looks alive (the "everything I ask Savi to build never shows up" freeze), Savi's tools now detect the frozen version, save a diagnostic snapshot of the tab, and quietly direct it to re-sync — or, if that fails, to rebuild its game session in place — instead of describing a stale world as current and rebuilding things that already exist.
- Objects stop silently going missing — and destroyed ones stop haunting the screen. When a create, destroy, look change, or effect got lost on the way to a player's screen, that player used to see a permanently stale world until they reloaded: new buildings invisible, destroyed enemies still drawn, finished effects fogging the screen forever. The engine now compares what the screen holds against what the game says exists, every second, and repairs exactly the difference within a few seconds — both directions, without touching anything that's healthy.
- Edits and heals now stick in empty rooms: when Savi (or you) fixes an object and saves, the fix survives everyone leaving and coming back — the room no longer resurrects the object's old position or state from stale multiplayer history at the next join.
api.getPlayers()can no longer be broken by tag writes. A script that overwrites or wipes a player's tags used to make that player vanish fromgetPlayers()(while they kept playing, visibly in the world) — and a non-player session body mis-tagged"player"could show up in rosters and counts. Player enumeration now reads the platform's own record of who each session is, sogetPlayers()always returns exactly the connected players — whatever your scripts do to tags.tags: ["player"]queries still work exactly as before for your own gameplay logic.- Long play sessions on phones stop eating memory until the game dies. Every shot, hit, and pickup sound used to leave a little audio machinery behind forever, and long iPhone sessions grew until iOS killed the tab. Finished sounds now clean up after themselves, and several behind-the-scenes buffers that were sized for desktops now use phone-sized versions on phones.
- Long music tracks stream instead of sitting fully decoded in memory. A track past ~43 seconds — or a looping one past ~22 — now plays as a stream with a tiny memory cost, at worst a small seam when a very long loop wraps. Short loops and stems still loop seamlessly, exactly as before.
- Less wasted work every frame. The engine stopped re-sending unchanged particle, terrain, decal, and lighting data to the GPU every frame — particle-heavy and effect-heavy scenes get that budget back.
- Voxel worlds with heavy terrain generators no longer grow holes and see-through ground around the player. Chunk builds that used to be silently thrown away after 10 seconds — because the engine bundled far-apart chunks into one oversized job — now build one chunk at a time and land.
- Voxel worlds stop pinning the sim on chunk install — a walk-found regression, fixed same-day. The previous cut (5.2.28, never shipped to players) made voxel worlds burn most of their simulation budget on bookkeeping every time a terrain chunk streamed in, so just walking across a voxel world could grind the whole game. Our own engine walk caught it the same day, and this cut carries the fix — chunk streaming is back to costing what the chunks themselves cost.
- Math.floor and friends are exact again in scripts and behaviors. Math.floor, Math.ceil, Math.round, and Math.trunc now return exactly the number you'd expect on big values — Math.floor(334350000) is 334350000 again, not 334350016. Integer math, indexing, and anything built on flooring big numbers works the same in your script files as it does everywhere else.
- Numeric constants in your scripts are exact now — physics constants survive to the last digit.
6.674e-11is exactly6.674e-11in behaviors and required modules,0.1 + 0.2is0.30000000000000004like everywhere else in JavaScript, and a constant compared against the same constant from another file is byte-identical. Your source numbers are never rounded behind your back anymore. - First-person and aim-over-shoulder games shoot down the crosshair on phones. Tapping the fire button used to send the shot toward your thumb instead of where you were looking; the crosshair is now the aim on touch exactly as it is under the desktop pointer lock. Cursor cameras (
pointerLock: false) still aim at the finger. - Your character stops walking when a chat box grabs the keyboard. Before: holding a movement key at the exact moment any text field in your game took focus (a chat composer opening on Enter, for example) left the character walking in that direction forever — the release landed in the text field and the game never heard it. Now the engine clears held keys the moment a text field takes focus, the same way it already did for the platform's own text fields. Any script watchdogs you built to zero stuck movement when your chat opens are no longer needed.
- Savi's builder body no longer materializes inside your face. In first-person games her body used to appear exactly at the spawn point — inside your camera — every time she came to work, until a correction kicked in seconds later. She now arrives a polite conversational step away, already facing you.
- Asking Savi to replace the world now works in multiplayer rooms. "Start over from the 2D platformer starter", "wipe this and make it a UI game" — these used to fail in multiplayer with an authority error while working fine solo. Now they work everywhere; other players in the room still can't trigger a world wipe from their side.
- Shader materials the engine parked for performance now always find their way back when the scene calms. Before, if frames dipped right after the engine re-enabled a parked material, that material stayed on its plain fallback until a full page reload. Now the engine just waits longer between retries (doubling up to about a minute of steady frames), so a scene that calms down gets its look back on its own. And lag from game logic — lots of moving objects, heavy scripts — no longer counts against a material being probed back; only real GPU cost does.
- Players whose device genuinely can't run Spawn now hear the truth instead of "Update your browser". Phones and computers whose graphics are below Spawn's floor used to be told to update a browser that was already current — forever. Now they get an honest answer: this device can't run Spawn, here's what's worth trying. Machines where a browser setting is the real fix get the setting steps instead.
- Phones and browsers that can't start 3D now get an honest answer instead of an endless reload loop. When a device's graphics can't run a game, the loading screen now explains what's going on and what to try — before, it said "reloading usually fixes it" forever on a device where reloading never would.
›technical notes
- Past the 32,768-heads join wall, the room-host seat now joins on the player lane's scoped bootstrap instead of dying at the budget refusal forever (ledger 2198 — abbi/Animalia, the fifth walled app and the first a creator felt). The typed budget refusal (
RelayIngestVerdictfatal-join) now carriesheadsWall: { heads, budget }structurally; when a receive-all pinned dial (room-host shell / wisp / instrument) draws it, the runtime latches a per-room scoped fallback: the redial's hello drops theaoiRecv=allrider (grant kept — emission stamping stays armed; never-declare kept — the relay's 10s declare-timeout backstop must owe the seat the FULL drain), so the join is the scoped globals-only bootstrap players already ride in prod (p50 21 heads rows vs the ~35k-row/4.4MB unscoped drain that died at the wall), the seat goes live in seconds, and the backstop bulk-loads the withheld world paced and bracketed. While warming, the session'saoiScope()answers a REAL coverage-gated claim instead of the pin's null — a partial-world host must answer world-sync only for bracket-receipted places (a false coverage claim reaps peers' entities); the "*" backstop receipt flips it receive-all in fact. Under-budget rooms never see the refusal and never latch — byte-identical joins, pinned by test. One loud structured line (room.relay_join_budget_wall_fallback) names the world-size fact for the shell/savi: the room outgrew the join budget, nobody's code is at fault, and no reset heals it while heads ≈ world size. - A1: consecutive relay JOIN failures earn exponential backoff with jitter. Every welcome legitimately resets the general retry budget (first authenticated message), so a join that failed AFTER its welcome redialed at the flat base delay forever — the 1.2s no-backoff storm (986 refusals/40min at abbi's room) that ground the DO and starved the humans' sockets (153 session drops in 3.5h).
failRelayJoinnow escalates the ladder's exponent to the bootstrap-failure streak (which already survives socket generations and resets on join success / new room), capped atRELAY_JOIN_FAILURE_BACKOFF_ATTEMPT_CAP = 8(default ladder ceiling ≈ 25.6s) so escalation alone never exhaustsmaxRetries— a walled room paces at the ceiling instead of dying terminal — and arms a ±20% jitter consumed by exactly that one schedule; every other reconnect trigger keeps today's deterministic delays. - A2: the bounded container-tier fallback rides IN-BAND across the private-transport seam.
?hostTier=container(r-72d60fe1 M3) was a URL decoration minted in the worker realm, and the bridge redials the fixed signed URL — so the fallback was dead code on every real client (field receipt: the falling-back warn, then 22 more relay dials). It now travels the bootId pattern exactly: lifted off the pseudo-URL into a structured connect field, sanitized in the trusted realm (only the literalcontainerever rides), re-appended to the signed dial. - Join-scope semantics: every seat class now declares what its join fetches (the move-2 SCOPE half of "no seat drains O(room) at join"). The seat classes are derived from the code's three connect shapes and mapped in one home (
_entry/client/relay-join-scope.ts): the PLAYER dial's scope is "place" (declared place heads + global heads now, other places on declare-place demand — the prod-proven p50-21-rows bootstrap, unchanged); the pinned receive-all-destined seats (room-host shell, helper walking sessions, wisps, instruments) declare "world-bootstrap" — the scoped globals-only join plus a full "" bulk drain the relay owes IMMEDIATELY, bracketed and paced through the per-DO drain queue. This generalizes #13022's heads-wall fallback (ledger 2198) from a refusal-latched exception into the scope the seat requests at every join: cheap join, live in seconds, world bulk-loaded behind the live flip, withaoiScope()answering the same coverage-gated claim while warming (the "" receipt flips it receive-all in fact). - The protocol addition is version-gated (the versions law — old pins keep receive-all). The request rides the hello's
aoiRecv: "bootstrap"; the relay'saoiRecvdecode is a fail-closed whitelist, so the client sends it only when the hello-invite advertedaccepts ≥ RELAY_INGEST_ACCEPTS_AOI_BOOTSTRAP(the new rung 3). Below the rung — old relays, URL-advert dialers — pinned seats keepaoiRecv: "all"and the legacy full in-join drain byte-identically, and old pinned engines keep it forever. One room-scoped exception: once a room's seat rode any scoped bootstrap (requested, or wall-latched), a later generation meeting a pre-bootstrap invite (relay rollback mid-room) drops the rider entirely — the #13022 fallback hello shape — instead of claiming receive-all over a possibly-partial world. - The no-places-world caveat, declared honestly: in a world whose heads never tag places, the global set IS the whole world — scope cannot shrink that fetch, only declare it truthfully; the drain half's paging (#13051 — paced heads drain, both join-size refusals deleted) is what carries its size. The two halves compose: scope shrinks the fetch, paging makes any fetch safe at any size. This half deliberately leaves the drain internals and pacing untouched.
- Relay side:
relay.aoi_bootstrap_drain(info) names the owed drain as the seat's declared scope working as designed — never the backstop's wedged-client warn; the debt rides the same settled-only-after-drain attachment bookkeeping, so an eviction between enqueue and drain restores still-owed and the wake-time backstop covers it (over-delivery, never blindness). - Chunk-coord lookups ride a writeVersion-memoized index — the 5.2.28 voxel sim regression (both backends) dies at its multiplier (#13130; tucker's 08-17 walk, thread 1787004469). The resident-grid deslop (c2accb5bd6 — in 5.2.28's cargo, not in 5.2.26) made every chunk install re-derive base (+) journals for the owner and its 26-cell halo — 27
getTerrainChunkEditsStatecalls per install — and that function's "rare" fallback,findTerrainChunkEntityByCoord, is a full scan of EVERY TerrainChunkKey entity with a raw PlaceMembershipworld.getper entity. The fallback fires for exactly the common cases of a streaming walk: an un-edited chunk (canonical entity exists, no TerrainChunkEdits) and a not-yet-resident halo neighbor (no entity at all) — tucker's walk traces put the sim worker 94% busy on Rapier / 71% on Mantle, ~81% inclusive under terrain/client-build's install lane, withEntityWorld.getalone at 40.9% self-time. The fix removes the scan's preconditions instead of gating its callers:findTerrainChunkEntityByCoordanswers from a coordKey→entities map memoized on the TerrainChunkKey store's writeVersion (the place-membership-read.ts pattern; buckets in lexicographic EntityId order so every pre-index tie-break — edits-carrier first, then canonical, then first fallback — is unchanged);getTerrainEntityPlaceIdrides the memoizedreadEntityPlaceIdRawinstead of a raw per-callworld.get; and voxel-field'sjournalOfdelegates togetTerrainChunkEditsState(call-time, not init-time — the module sits on an import cycle with edits.ts) instead of duplicating its body. Red-first: the new chunk-identity.test.ts perf pin (5400 miss lookups over 2000 chunk entities — the halo-at-the-frontier shape) measures 1137ms on the pre-index code and fails its 500ms bound; indexed, the whole 8-test file runs in 21ms, and the 7 semantics tests pass on both shapes. - Relay-tier ingress overflow self-heals by AGE — a thawing client abandons a stale queued replay for the F10 fresh fold (fix 2364, dig 60e20d09 lane 2). The kernel tier self-heals a stalled simulation at 64 queued StateDeltas (
room-runtime.tshandleIngressOverflow), but relay peer frames never touched that counter:pushRelayPeerFrameIngressrows had no cap and no heal, so a client whose sim stalled for minutes thawed into the ENTIRE queued replay (field specimen: 11.7k rows ≈ 3.5 minutes of arrivals applied in one gulp) — re-adjudicating every peer fact from a stale world and generating not_owner / stale-host-stamp drops at peers the whole time. Now every ingress drain checks the OLDEST queuedrelay.peer.framearrival stamp; pastRELAY_INGRESS_STALE_REPLAY_MAX_AGE_MS(30s — under one relay journal self-compaction window at the arrival rates where receive-side backlogs form: JOURNAL_SELF_COMPACT_ROWS = 2048 at the specimen's ~55 payloads/s ≈ 37s, and above the election's own 20s despawn-grace staleness tolerance) the queued replay is dropped wholesale and the session ROUTES to the existing redial-fresh machinery:prepareFreshRedialparks the failed-resume posture (cursor 0, keyed outbox pruned — now the shared home with handleWelcome's F10 verdict) and the socket closesRELAY_REDIAL_FRESH_CLOSE_CODEfor the ordinary reconnect ladder, whose fresh dial claims resume=0 and receives the keyed-heads fold; a stall that outlived its socket skips the close and lets the already-running ladder redial fresh. Age, not rows: motion already coalesces latest-wins per (author, stream, cell), so a deep queue of DISTINCT keys is honest load and drains normally — coalesce always, expire by age, and the bound is its own backoff. Deliberately neverfailRelayJoin: a routine self-heal must not count toward the container-tier bootstrap fallback. The kernel tier's 64-cap heal is untouched. - The relay ingress coalescer keeps latest-wins under mixed traffic: barrier-watermark + tombstone supersession replace the per-barrier index clear (fix 2365, dig 60e20d09 lane 2b). The live-motion coalescing index (
room-runtime.tspushRelayPeerFrameIngress, #11733's receive-side latest-wins) was cleared whole by EVERY non-motion ingress row, so interleaved chat/state/journaled traffic stranded one more stale motion row per stream per barrier — exactly the restatement-storm shape where a slow drain needs the queue to hold one row per stream, not minutes of superseded poses (abbi's session: 2.48M promoted rows fanned out uncoalesced into an 11.7k backlog). Now non-motion pushes advance a barrier watermark instead of clearing the index: a queued motion row indexed AFTER the last barrier still replaces in place (byte-identical to before), and a row stranded BEHIND a barrier is tombstoned in place while the fresh restatement lands at the tail — remove-old + push-at-tail, the same rule the relay's own join buffer applies on the fan-out half (fanout.ts), so no pose ever applies ahead of a journaled row it followed on the wire and the queue holds at most ONE motion row per (author, stream, cell) regardless of interleaving. Pure-motion streams, catchup-phase replay (never coalesced — history, not restatement), and the drain/teardown/compactor index lifecycle are unchanged. - Nobody-stamped frames park, never drop (tide's ruling generalizing tucker's fold-never-drop veto; envelope-dig lane 3, r-b035693c). The relay host-stamp gate (
relay-peer-apply.tstranslatePeerStateDeltas) used to drop a live host-stamped message whenever this fold's replicated table disagreed — including when the table named NOBODY (mid-election, or a fold behind the seat fact), which manufactured desync: a deposed/incumbent host's last hosted-remainder truth (bots, props, NPCs) vanished on exactly the observers whose fold was behind, unrecoverable if the host went quiet. The unadjudicable case now PARKS (relay-host-stamp-park.ts): frames wait keyed by the sender's session until the stamp becomes adjudicable — the election fact folds and the table names someone, either seat, either verdict — then re-enter the same gated pipeline (a confirmed stamp applies ahead of the sender's next live frame; a refuted one refuses exactly like a live straggler). The quiet-stamper case drains from the netcode ingress tick sweep. A table naming ANOTHER client keeps today's refuse — that verdict is real and LWW-corrected. The park's bound is the session: the membership tomb (and the late-join orphan sweep) reaps a dead sender's park whole; there is no TTL and no eviction, and a park that grows past the sanity line (256 frames) confesses on the governor channel (host-stamp-park, why-classbacklog) instead of silently ballooning. - The paged join's fold-completeness fact is readable: the budgeted ingress drain publishes its deferred backlog (
ClientNetcodeIngressBacklogResource), and the relay e2e harness's settle() holds until every stack's fold completes (the shard-1 CI intermittent, runs 31974390857/31979604866). The completeness gate lives at ONE place — applied-through-catchupThroughSeq, the welcome's single ceiling — and pages are INTENTIONALLY partial until the cursor reaches the ceiling (#13051 kept one welcome/one ceiling as the sole settlement truth). Every presence-shaped proxy is wrong by construction: worldBaselined flips at the self-spawn edge BEFORE the drain finishes (relay-join.ts maybeFinalize, grant-before-drain), and the test harness's settle() keyed "done" on hub quiescence, which goes silent the moment the relay finishes DELIVERING — while the #12973 budgeted drain (8ms/tick, 128-message floor) is still APPLYING the fold across ticks. On a contended CI tick the wall-clock budget expires inside every tick's minimum batch, the fold pages, settle's quiet-exit fired mid-fold, and the terrain-compaction test read partial heads —undefinedwhere the last-seq head (cell 0's dirt overwrite) hadn't applied yet. Solo it passed by a zero margin: 561 rows against exactly 5 quiet-exit ticks × 128. Now the drain stamps its carry remainder (on change only — a never-deferring world never writes), the harness exposes it per stack (ingressBacklog()= transport queue + deferred carry), settle() counts backlog as work, and the terrain test pins the paged shape deterministically (700 cells > 5×128,ingressDrainBudgetMs: 0= the contended-runner model). The ingress-budget suite's hand-rolled carry flush ("settle's quiet-exit cannot see a stack-local backlog") is deleted — the gate sees it now. Engine behavior on the wire is unchanged; production terrain already converges reactively as pages fold (client-terrain-system remeshes in the tick an edit lands). - GameDB door calls PARK through wire-not-send-ready windows instead of burning (ledger 2381, dig a04bde80). Every relay boot has a deterministic window where the world ticks — and
onPlayerConnectedfires — before the wire session reacheslive(relay-wire-session.sendRpcrefuses until then), andsendDoorCallwas fail-fast: a creator'sapi.sqlat boot burned its bounded retries sub-second and landed on defaults (Duskfall's progress table, 3 tries at ticks 16–18), and the engine's own terrain-edit restore (_spawn_chunksrow census) died on the same window on every relay-hosted authority (the headlessroom_hostshell, the SP client authority on a relay wire). A PRE-send refusal now parks at the door (game-db-relay.ts): nothing crossed the wire and nothing executed, so the park is replay-safe by construction; the tick sweep drains the park FIFO the moment the session opens (the reply clock starts at the SEND), and a wire that never opens hits a bounded 30s ceiling (RELAY_GAME_DB_PARK_MS) and dies typed with the one verdict a post-send timeout can never offer: this call was NEVER sent — safe to retry. Post-send failures keep the fail-fast contract untouched (the park is not an outbox; a call that crossed the wire is never re-sent), calls issued while anything is parked queue behind it so the window never reorders, parked calls count against the in-flight cap, and a room reset answers them with the never-sent honesty. The engine's terrain census and the room-state resume ride the same park unchanged — the boot read now lands on the live edge instead of warning and waiting out a retry cadence. - Governor confessions: engine governors that reduce work now confess it as a structured fact (r-2fe97ac9; the r-da0b1dbe governor-legibility class). New channel
engine/renderer/governor-confessions.ts— a per-worker ledger (the diagnostics-sink pattern) where a governor reports engage / level-change / release with a closed why-class vocabulary; facts carry which governor, how much, since when, and why. State changes dedupe, the transition ring is rate-bounded, and a session where no governor ever engages pays nothing (the snapshot is null and every surface omits the block — absence IS "nothing governed"). The renderer worker's ledger rides the existing 1 Hz perf-sample relay; the sim worker's client-health reporter merges it with its own ledger into a newgovernorsblock on theengine.clientHealthsnapshot (server-side re-validated: why-classes/events allowlisted, strings sanitized, arrays bounded), andapi.getClientHealth()answers with a per-clientgovernorSummaryline plus the full block. First wired face: the frame-budget park machine — parks, staged unparks, futility restores, and probe re-parks all update one fact derived from the park registry itself, with the distinction that survives ledger 2353's probation redesign structural instead of prose: parked materials auto-probe back (parked/refs), a parked look that delivered never auto-returns this session (look: "parked"). Build-failure parks never confess — a broken script is the script's story, not a governor move. - Governor confessions: the five remaining governor faces confess (r-da0b1dbe residue; #13040's channel, no new plumbing). Every face is one
confessGovernorcall at its own shed/pace/kill site, folding into the existinggovernorsblock onengine.clientHealth(sim-worker ledger directly; renderer-worker ledger via the perf-sample relay). (1) Tick shed (sim-health.ts): the ticker's backlog clamp confesses shed count + shed ms + the cause window ashost-overload, releasing after a full clean window — bounded slow motion stops reading as "the game is slow for no reason". (2) Relay ingest (relay-ingest-confession.ts, judged at room-runtime's drain edge; ledger 2331's 13k→48k unbounded-queue dump): latest-wins motion coalescing volume or raw queue depth past their lines confessesbacklogwith the measured counts, releasing after a clean window. (3) Chunk-build pacing (client-terrain-system.ts; the ledger-2012 starvation class): liveness-starved chunk builds (stuck/canceled — no job throughput) confessbacklogderived from the failure registry every tick; deterministic build-error failures and terminal parks never confess (the content's story, the build-failure-park rule). (4) Exec timebox (tome/exec/host.ts; ledger 2325): a watchdog kill now measures the host's own tick cadence while the exec was in flight — the watchdog's starvation is the evidence — and confesseshost-overload/"host-overloaded" (avg tick gap ≥ 100ms) vsrate-cap/"script-exceeded"; the caller's error string carries the same verdict, so an overloaded host no longer teaches "likely a busy loop — split your work" for innocent scripts. Released when the next exec settles. (5) Texture residency (lume/assets/service.ts): keep-alive-budget evictions confessmemory-pressurewith cumulative pressure-eviction and thrash-reload counts (page-derived housekeeping retirements never confess), releasing after 600 under-budget frames. - Over-cap authored-UI mounts confess and heal instead of dying silently (row 2399 — derrf's Loop Machine, incidents 44324169/761406fa/9e47956e). A game-HUD/creator-panel lifecycle mount ships its entry module plus the whole require closure to the sandboxed authored realm, and the realm channel guard refuses any mount over
REALM_SIZE_CAPS— correct against hostile traffic, but the refusal was a frame-side silent drop: the worker's receipts read "sent",lastMountKeyadvanced, and the previously mounted code stayed live across reloads while the creator's lib grew underneath it (262 modules crossed the old 256sourceCountcap at under half the byte budget; the blank minute was the crossing). Four moves: (1) the worker controller preflights every mount withmeasureLifecycleMountCaps— the guard's own predicate, exported fromrealm/protocol.tsso admit⇔accept parity is pinned by test — and refuses loudly on the fault rail (ui.mount, once per mount key) naming the binding cap, the measured closure (count/chars/largest module), and the fix; (2) the HUD pipeline ledger carries a lifecycle-delivery verdict (sent/dropped-over-capwith reason), so receipts can no longer read "delivered" for a mount that never ran, and the debug-dump summary prints it; (3) a refused or send-failed lifecycle command never advances the resolver's cursor (the game mount key / creator panel id commits only afterpostMessagereturns — thelastSignaturerefusal, mirrored), so shrinking the closure below the cap mounts on the next sample with no unrelated edit; (4)sourceCountrises 256 → 4096 — it is a decode-abuse ceiling, and the byte caps (1MB/module, 8MB total) now always bind first for real module graphs. - A missing
__spline__child id names its root instead of blaming "array indices" (ledger 2338's secondary wart).api.querylegitimately returns spline/scripted expansion children (<root>__spline__…) — they are real, measurable, tag-bearing objects — but their lifetime is coupled to their root: a root destroy cascades them and the apply pass re-mints them, so a query-then-sweep destroy loop reaches ids that died with the root moments earlier. Both zero-match mouths (the behavior warn rail'semitMissingTargetTeachand the exec seat'sdescribeMissingDestroyTargetthrow) told that loopnot found — call ignored. Use entity IDs (e.g. "my-object"), not array indices— a wrong theory about an id the engine itself handed out (Denmark By's script noise, 08-15). Both mouths now detect the generated-child marker (inferSplineGeneratedOwnerId) and answer with the coupling: root live → "generated children die (and re-mint) with their root — destroy the root instead"; root gone → "this child died with its root; generated ids never need their own destroy call". - Cross-place mutations confess instead of silently no-oping (r-c79a5004; the sumo-volleyball "engine limitation" specimen, mavi's dig 4fcf89aa). A script in place A mutating an entity in place B no-opped with the generic missing-target teach ("not found — call ignored … not array indices") — a wrong-cause verdict for an entity the spec provably declares in another place; creators built workarounds around the silent drop and Savi built typo theories at authoring time. The miss rail (
emitMissingTargetTeach— one seam under setObjectProperty, patchObjectState, deleteObjectState, the physics verbs, …) now checks the unfiltered+effective spec view: a target declared underplaces.<B>while the caller stands inplaces.<A>gets the structured verdict — "cross-place write dropped: <entity> lives in places.<B>, this script runs in places.<A> — writes only apply within the calling place" — delivered where the writer reads (the runtime-log ring for behavior scripts, attributed to the writing entity; the exec sink verbatim for run_script). Zero behavior change on the write itself — apply semantics stay the separate design track. Conservative by construction:findSpecObjectprefers the caller's place, so a cross-place duplicate whose local copy died keeps the plain miss teach, and destroy never reaches this rail for spec-declared ids (its clientDestroy fan-out is the delivery path). Same-place writes and unknown-id / dead-local-id teaches pinned byte-for-byte. - getChannel resolves authored-clip duration from the minted library entry (ledger 2384 — Nine Heavens' unfalsifiable railDead probe, dig 1c219e7d). An authored-clip play writes only the identity STRING into the mixer channel — the minted
AuthoredClipEntry(with its duration) lives in the derived AuthoredClips library, and nothing ever wrote it back into the channel — sogetChannel().durationread null FOREVER for authored-clip channels (finishednever fired,progressstuck at 0), on every engine version, healthy or dead. Nine Heavens latched railDead on exactly this probe and spent eight rounds of pose work building GLB-puppet workarounds around an unfalsifiable artifact. The read lane now answers what the write lane minted: when the channel's clip resolves in the AuthoredClips library,duration = entry.duration / |speed|— one lookup — andfinished/progressderive from it consistently. A caller-supplied channel duration still wins, and sprite-atlas timing is untouched (both pinned in get-channel-timing.test.ts). - A bare canonical Magic CDN filename is now a loadable model reference, and a loader-refused id can never wear the receipted-death caption (row 2405, dig 82a978a9 — iz's charzoo m20). A spec ref of
public.<b64>.glb— the canonical tagged-import identity minus its two-character/cdn/prefix — was refused bystartLumeModelLoad(not in manifest, not URL-shaped), and the refusal then flowed throughgetUnreadySceneAssetsasfailed, whose scene-note clause teaches "the asset host itself answered a terminal verdict … a receipted death, not a guess" — for a request that was never made, about a file that was serving 302/ready the whole time. That caption sent Savi and two digs hunting server-side status doors. Two halves: (1) the primitive —synthesizeModelUrlextends the loader's existing URL-shaped synthesis to bare canonicalpublic.<b64>.<ext>filenames (shape-tested byBARE_CANONICAL_MAGIC_CDN_FILENAME; the kiln grammar owns validity, a malformed payload earns the CDN's own teaching 400), used by bothstartLumeModelLoadandresolveModelLodId, so the two-missing-characters failure class stops existing; (2) the truth surface — ids the loader still refuses (neither URL-shaped nor canonical-shaped) report the newunfetchablestatus, whose clause states the actual truth: "not a fetchable reference … no request was ever made, so nothing failed and nothing is generating," with the/cdn/prefix teach. The refusal console line now says the same. (Companion kiln change, same PR: the geowire fail-open row marker rewordsfailed-open:<reason>→degraded-serving-original:<reason>— the bookkeeping bits stay, the death-prime on a serving row dies.) - Renderer/client engine diagnostics reach Savi on relay rooms — the egress fold (ledger 2380, dig 292859c6).
engine.diagnosticCommands (the scripted-look/material/texture-script fault family, renderer crash rails, mutation warns — every client-reported code intome/engine-diagnostics.ts) had ONE terminus: the kernel server's command registry. On the relay tier — the default room host — every report died at the egress dead-lane drop (relay-command-lanes.tslane"none") with no fold anywhere: the seam note claimed "the local runtime log carries it" while nothing did, so Savi was structurally blind to every renderer-side creator-fixable fault. The dig's specimen: five namedscripted-look-compile-failederrors fired (a creator's one-line TDZ) while Savi filed a phantom engine bug twice. The row is{ lane: "local", consumer: "diagnostic" }now: the egress consumes the command into the SAME validate → sanitize → dedupe → runtime-log → DM-class machinery the kernel server runs (foldEngineDiagnosticLocally), against the LOCAL client world — the ring row lands wheregetLogsand incident dumps read, and the error-class first-occurrence DM rides the EXISTINGtome.dm.notifyforward the same egress already consumes into the "dm" RPC lane (never a new pipeline; kiln's notify door holds the durable once-key dedup, and the spec-version-scoped key means N clients folding the same condition cost one Savi turn). Warning/info classes stay logs-only; storms and budgets are the kernel terminus' own. One known redundancy, accepted as honest:mutation-warnnow lands twice in a relay ring (the local sink's raw row + the fold's framed copy), both cooldown-gated. Perf-category pointer DMs (notifyPerfDm) still no-op on client worlds — their log bodies now land, the hourly pointer remains kernel-only. - Scene RPC answers stamp the APPLIED spec version — the attached-but-frozen eye is convictable by version (ledger 2337, 2266's residue face, incident c24dc25b). The 2266 room-feed stamp moves only on TRANSPORT edges (room-feed-state.ts: connect/attached/reconnecting/detached), so a socket that stays "attached" while receiving/applying NOTHING passed every EYE-STALE gate — the field specimen answered camera-responsive identify/look RPCs from spec v2 for ~15 minutes while the room's doc head was v14, and Savi burned the window on restarts, terrain rewrites, and a false engine-fault filing.
RoomFeedSnapshotnow carriesappliedSpecVersion— the world's applied TomeSpec.dbVersion (the same number run_script results carry asspecVersion), recorded at the worker→parent telemetry relay hop worker-browser-host already owns (noteAppliedSpecVersionFromTelemetry, beside the viewport injection) — so every scene-view/identify answer names the world version it was rendered from and the tool side can compare it against the doc head it already holds. Deliberately a stamp fact, never a state edge: the transport machine is untouched (its job is transport). Absent until the first telemetry tick names a dbVersion — old engines and version-less worlds keep today's stamp byte-identical. - The frozen-feed recovery directive — the tab that never processed a welcome can now be cured from outside (ledger 2342, the eye-integrity family's fourth face). The 2342 class: a realm frozen at join (applied spec stuck at v2/v3) that keeps answering camera-responsive scene RPCs, restart-immune (a relay rooms reset forces fresh welcomes and the field tab stayed at v3), invisible to every transport gate (heartbeat echoes keep the silence watchdog green) and past every in-worker self-heal lane (884/945/987/1509/F3/F4 — all present on the field engine). Two faces: worker room-session ingress dead behind a live MessagePort bridge, and an orphaned realm serving eye+telemetry with no transport. The chat side convicts on the applied-vs-head stamp (ledger 2337) sustained across the engine's own five-heartbeat silence standard, captures a frozen-tab debug dump FIRST (three field specimens yielded zero), then directs recovery over the kiln page channel — the one path the conviction proved alive. Engine half (this changeset): a
recover_room_feedclient RPC (room-feed-recovery.ts, spawn.ts's lazy-import law). Rung 1 condemns the REAL room socket from the page realm that owns it (BrowserWebSocketClientTransport.condemn— the chunk-failure pattern: raw-socket close so the close event reaches the runtime; newCLIENT_EYE_RECOVERY_CLOSE_CODE = 4437so the server's close row says why), converting the freeze into the existing close→reconnect→reattach→fresh-baseline lane. Rung 2 recycles the realm: the client session boot is now a named re-invocable (startClientSession), so the directive disposes the host — terminating the runtime worker and both renderers — and reboots into the current room through the same connect machinery the authored nav.room swap uses; single-flight by construction. No new detector rides the engine: the conviction edge lives tool-side, outcome-keyed, and old chat deploys simply never send the directive. - Entity residency reconciles sim↔renderer — the tilemap sweep (#12968) generalized to the entity forward/release lane (ledgers 2382/2338/2376). The sim→renderer entity lane has no delivery guarantee either: stream-reset recovery discards buffered frames, a poisoned reader drops queued ones undecoded, and the lane can wedge wholesale while both endpoints stay alive (2382: the renderer answered RPCs and timed frames while every create/destroy/material after some T never materialized; 77a6fe7f: 12 spec-present houses invisible until restart). A silently lost frame on an otherwise-live channel left the renderer's applied set diverged forever — spawns that never materialized, and destroys whose release never landed: 2338's orphans drawn forever, their fx populations with them (2376). Now the renderer reports its applied entity set (
RenderChannelReader.entityResidencyCensus— the reader's ownliveEntities, cached per membership revision) on the same 1Hz perf-sample relay the tilemap census rides, and the sim's sweep (entity-residency-reconcile.ts, run from render/ecs-sync after the delta path) diffs it againstsentEntitiesand repairs exactly the delta: missing entities re-forward FULL live state through the include-reconcile encoder (SPAWN + every forwardable component — draw, materials, fx programs — re-derived from world truth), and sim-dead ghosts get a delete frame whose DESPAWN reaps their fx populations with their carrier (handlers/particles.ts →removeParticleEntity). Pacing is the tilemap sweep's own — same quiet window, consume-once censuses, repairs re-stamping their own ids — with one forced adaptation: the quiet gate is per entity id (last lifecycle write), because entity churn is continuous in exactly the games this class hits (2338's belt spawns every hit) and a place-wide gate would starve forever, while a per-id gate leaves an untouched orphan judgeable regardless of churn elsewhere. Deferred (never discarded) while the channel is delivering, reset wholesale on writer swap / recovery snapshot / place transition / initial snapshot, and idempotent by construction: a misjudged in-flight forward re-states identical live values (never a despawn, never flicker), so a wrong judgment costs wire bytes, not correctness. The tilemap lane's synthetic ids are exempt in both directions — its own sweep governs that seam. - Client debug dumps carry the applied entity set.
EngineBugRenderDiagnostics.entityResidencymirrors the census (count + space-joined applied entity ids, ids clipped at 4KB on ingest withcountcomplete;{count: 0}is a positive empty-world receipt, null = pre-instrument). The applied set in dumps was previously ZERO — 2382's "everything after T never materialized" took a full dig to establish; it is now one count compare against the sim's entity table and one grep overentities. - Ordinary saves retire the journal fossils they supersede — the #12821 fence's condition extended from REPLACE-class to per-key (row 2394 P1, dig c37047e0). The gap: a host's adoption restate journals every hosted keyable entity as a keyed head — a live-world snapshot — and an ordinary-write save (Savi's heal, a creator's edit) superseded the book's values for the objects it touched but retired NOTHING in the room's journal, because the replace fence keys on replace TRANSITIONS only. A static entity never sim-writes, so its stale head was never re-published; it lay dormant while the room stayed hosted, and the first UNHOSTED join bootstrapped from the healthy doc then refolded the fossil over it (iz's Northshire: 13,694 entities re-journaled at a new epoch, y=15.5 fossils clobbering the healed y=17.7). Now the save's writer names the keys it superseded: kiln's mutation funnels (game-specs mutations, spec-save mutation batches) extract the batch's touched object ids (
supersededObjectIdsForMutations— O(batch), never a spec diff), the relay SPEC lane mints them into entity-key prefixes (e:{id}:, deduped, alphabet-checked, capped at the mirrored 128 with honest under-retire on overflow) and rides them as additiveretireon the checkpoint marker; cf-relay range-retires LIVE client-authored heads at or behind the marker under exactly those prefixes, in the same transaction as the truncation, under the fence's proven discriminators (is_tomb = 0, author ≠ #relay) and §9.2 hold discipline. Untouched keys' heads park, never drop (runtime spawns are legitimately un-authored by the doc); heads above the marker survive (a post-save publish is newer than the doc); replace-class markers are unchanged (their fence already retires a strict superset). Old relays drop the unknown field — today's exact marker; old writers never send it. Whole-doc PUT surfaces send nothing (they cannot cheaply name a touched set) — honest today-behavior, not a guess. - The reflection rails (rail.emit/rail.noise) go LIVE-only — journaled history stops being re-adjudicated and stops re-firing at joiners' folds (ledger 2363; kills the invisible-players rejoin class). emit/noise are keyless journaled STATE, replayed to every joiner until self-compaction rotates them out, and their peer translators ignored the frame's phase entirely: every fold-replayed row re-ran the
senderOwnsSourceownership walk against the receiver's MID-FOLD world. At a join fold the author's avatar hasn't applied yet (drop), and after the author's own rejoin it never will — avatar ids areplayer/<clientId>and a rejoin mints a new identity, so old rows' sourceIds dangle forever — manufacturingnot-owner:unresolveddrop verdicts for settled history at exactly the rejoin that most needed silence. Worse, a fold-replayed emit that DID pass the walk synthesized a liveevent.fwdthe wire apply core fires unconditionally — the room's emit journal stacking as join-storm stimuli at every join (noise.fwd feeding the NPC perception queues the same way). The cure is the #12915/#12937 phase discipline the terrain rails already carry, at the strength the delta-events law demands for stimuli: a reflection is a stimulus, not state — its live consumers heard it when it rode — so catchup/repair rows now translate to nothing, silently (no re-fire, no ownership re-judgment: history was adjudicated when it was live; replay is not a new claim), exactly the posturetranslatePeerStateDeltastakes ondelta.eventsone lane over. Live-phase rows keep the ownership gate exactly as deliberate (forged sources still refuse loud), and the sender's egress loopback stays live by construction. Pinned red-first in relay-peer-apply.test.ts; the terrain rails' phase tests ride unchanged. Design word (tide, settled): place-scope acts adjudicate by MEMBERSHIP — ownership walks are for entity-owned rails — so the live-path membership migration for reflection rails is the named follow-up, deliberately not part of this change. - getPlayers answers SESSION TRUTH — both faces of the tag-predicate contract die in one predicate (ledger 2350, dig 28ec3308; 2141's escaping class). Every getPlayers/getHelpers lane (behavior ObjectAPI, camera API, the exec lane's bare globals) enumerated
tags: ["player"]over the per-place tag index — a GAME-MUTABLE surface. Face A: a session body wearing the wrong class tag counted as a player (the summoned builder shell impersonating a roster member — 2141's class escaping its #12572 fix). Face B: a connected creator's avatar, in-sim and answeringapi.query({ radius }), vanished from getPlayers for 44+ minutes because its class tag was gone/index-invisible — and "SessionOwner without the class tag" is a real engine state (spawnPlayer's own suspension-stamp defense names it) a tag predicate structurally omits. The fix:spawnPlayerstamps the VERIFIED session class (the same room_host-token-derived helper bit the tag is minted from — kernel auth.ts, relay runtime-worker.ts) asSessionAvatarClass(engine/components/session.ts): plane authoritative,clientAuthWrite: "never"(stripped from every kernel-tier upload), no ObjectAPI writer (scripts structurally cannot touch it), replicate"aoi"so it rides the relay self-spawn create and kernel AOI replication to every peer. One enumeration body (tome/session-avatars.ts → query-utilsquerySessionAvatarsInPlace) now serves all lanes: stamped entities are judged by the stamp ALONE (reads components directly, never the tag index — a wiped tag can't hide a player, a wrong tag can't promote a helper, a suspension-stamped avatar stays enumerable); avatars WITHOUT the stamp (a pre-stamp engine's spawn folded into a mixed-version relay room) keep the historical class-tag query verbatim, a legacy lane that dies out with pre-5.2.27 engines. Same session truth now backsresolveObjectId("player")'s active-player sweep,resetPlayerEntity's class re-mint (closing the "re-mint off already-wiped tags" escape lane the dig named on 2141), and the SP-mirror exec rebase's player population. Row shapes pinned red-first in getplayers-session-truth.test.ts, both lanes. - The audio decode budget is enforced at the service chokepoint — an oversized clip demotes to the stream lane before any PCM is paid (#13021, the #12517 split; 2026-08-12 device forensics). The renderer priced forced-buffered starts before decoding, but every other decode — the preload warmer's spec sweep, a JIT start's park→prefetch poll — reached
decodeAudioDataunpriced, so one long track held its whole PCM resident (the measured 66 MiB single-clip specimen behind the iOS WebContent kills). Two rails give the service's own paths the verdict, cheapest first: pre-decode pricing via one element-metadata probe when the encoded size is suspicious (>1.5 MiBSUSPICIOUS_ENCODED_CLIP_BYTES— past which no plausible lossy encoding decodes under budget), and a decode-time backstop so an over-budget buffer is never RETAINED (the transient decode is paid at most once; the recorded duration prices every later session before it even fetches). Pricing failure is never load failure. Either rail demotes the clip to the HTMLAudioElement stream lane the renderer would have chosen, and both are loop-aware — the estimate counts loop-conditioning's second copy — so a LOOPING clip whose decoded PCM lands between 8 and 16 MiB (~22–43 s of 48 kHz stereo) now streams with a wrap seam where it used to slip through and hold double its PCM.ensureDecodedBufferstays buffer-guaranteed by contract and never demotes. - Naturally-ended voices detach their WebAudio node graph — the leak behind the residual phone jetsam kills. Device forensics (2026-08-12, iPhone 14 Pro Max) traced the remaining game-tab deaths to jetsam reason=highwater at WebContent's 1536 MB soft limit, and syslog showed the growth driver: 17,903 AudioNode constructions against ONE destruction over a 22-minute session — every combat SFX built a gain(+panner)+bufferSource triad whose natural-end path deleted the voice map entry but never disconnected the nodes, each leaked source also pinning its decoded AudioBuffer. Disconnects are now a shared per-node-guarded
disconnectVoiceGraph(one throw can no longer strand the rest of the graph — a naturally-ended source'sstop()throws, and the old single try/catch skipped every disconnect after it), and the natural-endonendedpath runs it unconditionally. - Constrained tiers stop paying desktop memory sizing. KTX2 decode runs ONE worker on non-desktop tiers — each pool member pins a ~17 MiB Basis wasm heap for the session, and the pipeline byte budget already bounds decode parallelism, so the second worker bought memory without throughput — and the transcoder releases its fetched wasm binary after instantiation in every realm. The fx terrain-height mirror is priced per tier (
fxTerrainCapacityForTier): mobile gets 1 M samples + 512 chunk slots (4 MiB CPU + 4 MiB GPU) for the standard streaming profile it actually runs, instead of the desktop extended-horizon 16 + 16 MiB; overflow degrades exactly as designed — skipped chunks sample as "no chunk", counted instats().overflowedChunks. And baked pose palettes (~4.6 MB of CPU pose frames per horde-batched model) are released on model asset dispose — the acknowledged F13a retention closes. - The per-frame upload blanket dies — unchanged bytes stop re-shipping to the GPU every frame. The fx pool's pop-table writers self-mark their own rows (
markTablesDirtydeleted — interface, implementation, and all four call sites), so a frame uploads exactly the rows something wrote instead of every population row at 60 Hz (~330 of a measured 348 MB/min was provably-unchanged bytes). Terrain's slot-major pool mirrors die outright: GPU uploads read the per-chunk records — the wire truth — deleting ~25 MB of duplicated renderer CPU mirrors at 1089 chunks, with one named ordering change: picking sees edited heights at op-apply instead of at visual sync, the same freshness ground sampling always had. And the decals, lighting, and scripted-arena upload sites change-gate on content — static decal stamps upload once instead of 106×/s, the lighting pass + cluster-cull uniforms merge into one buffer with settled light records gated by compare, and still entities stop restaging their matrices (UploadArena.writeIfChanged). - Voxel chunk batches build per-chunk inside the sliced batch loop — the fused shared-field batch mesher and its LOD group key are gone (dump a887d221, forest-carved).
buildVoxelChunkBatchDatasampled ONE field over the batch's bounding box; the client packer picks its 8-chunk batches by streaming priority, not adjacency, so a priority-order pick strung along a column or row (the ring's tick-7715 batch spanned 1×3×14 chunks) sampled 42 chunk volumes for 8 chunks — 5-40× the sum of its members' single builds (measured on the reporter's generator: 55 ms per single chunk, 2.2 s / 4.7 s / 10 s per batch, 27 s for a spread pick), one synchronous task with no cancel point. The client's 10 s stuck window then reaped every batch unread while the worker kept computing, resubmitted it after backoff, and the treadmill never converged: 32 chunks a session failedoutcome=liveness reason=stuckon a desktop whose per-chunk cost was healthy, holes and see-through ground where those chunks should stand. NowbuildTerrainChunkBatchOutputsruns every generator through the same per-chunk loop: N chunks cost exactly N single builds (byte-identical outputs — pinned), no batch geometry can multiply the work, the loop yields at the slice budget, and a cancel lands between chunks. The packer'sterrainChunkBatchGroupKeyLOD lanes (PR #6713's mixed-batch precondition) die with the mesher — a batch is one bounded pack of the priority-sorted selection and may mix LODs. Ronnie's session batches build in 0.4-0.5 s on the same box. - Math.floor/trunc/ceil/round are spec-exact native pass-throughs in the sandbox Math shim (ledger 2317 — savi-filed incident ae7bdf9e).
unaryMathquantized the ARGUMENT throughMath.froundbefore calling the native, so every integer above 2^24 changed before the op ran —fround(334350000) = 334350016, floored:Math.floor(n) === nreported false for good integers, floor-division indexing silently shifted, and an integer-only sim corrupted inside every deterministic compile unit (behaviors andrequire()'dscripts/lib/*modules alike — only ad-hocrun_scriptescaped, compiling with native Math). These four ops are correctly-rounded per ECMA-262, natively bit-identical across realms — f32 quantization bought zero determinism there — so they joinimul/clz32in the shim's deliberate native-bypass family (exactUnaryMath: ToNumber the arg, run the native, no quantization either side; the result is always an exact integer, which doubles represent exactly to 2^53). Everything implementation-approximated keeps the fdlibm table and the f32 in/out contract unchanged. Pinned on Savi's exact repro numbers, on Object.is bit-identity to the natives across the f32-unrepresentable range (±0/±Inf/NaN included), and through the real compiled sandbox. Pinned games keep their engine's math per the shim's compat law — the version boundary is the compat layer. - Numeric literals in authored source are exact — the f32 literal-quantize pass is deleted (ledger 2319 — savi-filed incident f4451f3f, 2317's sibling).
quantizeFloatLiteralswrapped every float-typed literal (decimal AND exponential notation) in_f32(...), so authored constants changed before any code ran —fround(6.674e-11) ≠ 6.674e-11broke byte-identity to a research sim's published physics constants inside behaviors andrequire()'d modules alike, while integer literals were already exempt and ad-hocrun_scriptescaped (the same realm split as 2317). ECMA-262 numeric-literal → f64 conversion is exact rounding, identical in every realm, and IEEE f64+ − × ÷are correctly rounded per spec — so quantizing literals bought zero determinism anywhere; the f32 contract belongs to the sandbox Math shim's implementation-approximated RUNTIME members (unchanged: fdlibm table, f32 in/out), never to source constants. The pass, the_f32runtime member, and its injection binding are dead; every literal spelling (integer, decimal, exponential) now parses to the same bits as native JS, in every compile unit (behaviors, lib modules, generators, UI — the UI realm was already exact, so the realms now agree). Pinned games keep their engine's math per the shim's compat law — the version boundary is the compat layer. - On phones, a lock-preferring camera aims from the crosshair — the touch aim pin (Jacob's shooter: "fire shoots from where I click the screen instead of the center"). The pointer transport (
worker-browser-host.tscreatePointerTransportHandlers) publishes every pointer's NDC to the renderer, which authors the canonicalpointerOrigin*/pointerDir*ray from it each frame — and the only thing that ever pinned that NDC to (0,0) wasdocument.pointerLockElement, which a finger can never hold. So under a first-person camera on a phone every touch — the fire chip, the move stick, a look drag — dragged the canonical ray to the thumb, andgetInputRay(input)hitscan left the crosshair. The pointer-lock manager now exposes the lock's touch twin,isTouchAimPinned()= authored intent held AND no visible cursor-need UI (the desktop retention rule minus the browser: adata-modalfrees the finger the way it frees the mouse, the pass-through canvas tap re-pins the way the rescuing click relocks; mode and visibility never enter, exactly as an engaged lock survives them). The transport reads it per touch event alongside the browser lock — NDC pinned to (0,0),isLockedreported,input.pointerabsent — so the sim, the renderer, andgetInputRaysee one lock story on every device. Mouse/pen pointers and wheel read the browser lock alone (a hybrid keeps its acquire/release flow; a touch beside a locked mouse aims from the crosshair through the touch-release exit). God mode and cursor cameras arrive as authored intent false (resolvePointerLockWirePreference) so tap-select, god handles, and tap-the-world gestures keep the finger, press stamps included;pointerLock: "drag"never pins.InputState.pointer/getInputRaydocs and the pointer-raycasting / camera-first-person / camera-third-person skills now say the same thing: phones follow the camera, not the finger. - A key held when an authored text field takes focus no longer strands — the self-walk input latch is closed (ledger 2345). The latch: press Enter while W is held → the game's composer autofocuses → W's keyup targets the focused field → the realm frame withholds it (
isTextEntryTargetgate, deliberate — typing must never fireactiveOn:"keyup"bindings) → the host doesn't RESET on blur-into-realm (deliberate — resetting there wiped every world click) →keysDownclears only on KEY_UP or RESET → the axis stays latched and the character walks until the same key is re-pressed or the page restarts. Two games independently built script watchdogs against this seam (Animalia's chatTyping, Death Valley's RUN-OFF GUARD). The fix is the realm twin of the host's existing editable-focus reset: the frame posts atextentryfocuspass on focusin into a text-entry target (the exact complement of its keyup withhold — a readonly field's keyups still forward, so its focus doesn't reset), and the host runs the same silenthandleInputReset(RESET clears held state without synthesizing release edges, so no keyup binding fires from focus movement). Both deliberate behaviors are pinned unchanged: typing stays withheld, and world clicks survive the blur carve-out — the reset rides focusin-into-text-entry, never blur. - The summoned builder body spawns a conversational step OFF the spawn point — the face-inside-the-camera frame is unrepresentable (BT's Fortress Run QA, dumps 8d7d5182/260e69a8). Every joining session self-spawns at the place's ONE deterministic spawn point (lifecycle.ts configurePlayerEntity: defaultSpawnPoint → player feetPosition → implicit terrain default), and in a first-person game the player's camera sits exactly there — so every builder summon materialized Savi's body 0.0m inside the player's head, and the shell's post-hoc arrival teleport (arrival-position.ts) only corrected it ~1.3s later, once per summon, every summon (three "spawned 0.0m from player/…" placements in one QA day on one app). The helper class (the verified room_host token class — never game-declared) now spawns HELPER_SPAWN_OFFSET_M (3.25m, the arrival etiquette's conversational band) along the spawn facing and already turned around to face the spawn point: the spot the etiquette used to correct into, decided at placement so the coincident frame never exists. Slope-aware (the offset spot never lands below the terrain sample there), 3D places only (2D modes have no first-person camera and their axes carry different meaning), and the same placement rides resetPlayerEntity's helper re-mint, so game resets stop slamming the body back onto the spawn point too. The spawn is receipted (HelperSpawnPlacementResource → the new
session.spawnPlacementdebug answer,supported:trueseam-marker idiom): the shell's arrival positioner stands down on the receipt (arrival: engine-placedon /healthz) instead of re-correcting a body the engine already placed — a post-hoc correction would anchor on the body's own offset spot and could step it right back onto the spawn point. Older pinned engines answer no marker and keep the legacy post-hoc teleport byte-for-byte. Player spawns are untouched. - Seat-aware authority family:
replaceSpecWithStarter(and the naive mode-as-authority guards) work on the relay tier's elected host (ledger 2314 — jacob's live repro; tide's census, thread 1786845973). Savi's run_script on relay rooms executes on the ELECTED HOST client — the room's real authority since R5 (#12739): it runs the sim, and the exec arrives through the trusted room_host writer class (relay-script-exec.ts). But mutation guards testedgetWorldMode(world) !== "server"— a pre-R5 rule that knows only server worlds and singleplayer clients — so the exact seat that IS the authority was refused. The primitive family (object-api.ts, besidegetWorldMode):isPlaceAuthorityHere(world, placeId)on the readPlaceHosts spine (kernel server TRUE even for client-hosted places — it persists/validates; SP true; a relay-wire client from its elected seat, handoff-safe via the table's write-version memo + the make-before-break barrier),isRoomAuthorityHere(world)= the ANY-held-seat form for place-less verbs (the exec lane's own election: an unstamped request runs at the defaultPlace host when one exists, and at the sorted-first held seat when every player has walked out of the default place — room code is room-scoped, so the whole-room predicate answers the same question), andisGodModePersistAuthorityhoisted beside them from the god-mode edit accumulator (semantics unchanged). Swapped in this pass, per the census's naive-safe list: thereplaceSpecWithStarterguard (the throw fix — jacob's repro; the swap's live-teardown choreography is a separate follow-up), the field-bound scatter refresh (refreshFieldBoundScattersForField), the apply-path scatter heal (the delta-0 short-circuit's pending-recovery veto + its bed-set collection), first-load scatter re-admission (readmitScatterBedsForModel, per-bed seat), andupdateObjectSpec's persist axis (explicit persist on the place's elected host persists through the kiln mutation lane; every existing path keeps its default). Deliberately NOT swapped (each needs its own choreography, named so nobody naive-swaps them later):reset()(relay drain + peer convergence),emit(deferred-op), player-body minting (owner-client axis), the teach rails. Kernel-tier duties, thegetWorldExecutionModefamily, and the SP no-netcode carve-outs keep their mode reads — those really are mode questions. Original law preserved: spectator/non-host multiplayer clients refuse with the same teach verdicts. Pinned on the relay multi-client harness (exec-lane swap on the elected host, non-host teach refusal, election handoff re-answering) and unit-side beside the scatter-host-mint precedent. - The shader shed's probation is backoff, never terminal — and CPU-fused frames can't burn it (ledger 2353, jace's Tab M11 no-recovery report). Two legs in the frame-budget park machine (
frame-budget-report.ts). (a) A staged-recovery probe that re-offended used to re-park as"probe-reoffense"— one probation per ref per session, edit-only after — so on hardware where any single re-enabled material tips the 40ms fused line, every ref burned its one probe and the whole scripted lane converged to a reload-only shed. The terminal reason is deleted; a re-offender re-parks recovery-eligible ("frame-budget") and each re-offense doubles the stable stretch the next probe must wait for (×2…×4 capped,RungWalker.probeBackoff's proven shape; a probe that survives probation resets it). The backoff IS the flap protection — a ref the device can't afford converges to rare probes, never to a permanent park. (b) The driver-lane re-park judged the fused signal — max(cpu, gpu, interval) — so a CPU spike coincident with a probe permanently latched the material on evidence the machine's own attribution law (handleFrameBudgetEvent: parking GPU work cannot recover CPU-bound frames) classifies as non-evidence; laggy CPU-heavy combat on a Helio-G88 was exactly this shape. The re-park site now requires GPU-attributed evidence, same as the park that opened the episode; a CPU-fused over-line frame only restarts the probe's stable clock. Post-park baseline judging, relief verification, futility restore, and look-park semantics are unchanged; the repark diagnostic no longer claims "for this session". - The boot gate's adapter-null verdict stops latching the Browser-Update wall (ledger 2352 — 500 adapter-null rows / 15 sessions in one day on one app, current-stable Android Chrome/151 told "Update your browser" by a wall updating could never clear).
runWebGpuBootGaterequests the engine's core-level adapter (featureLevel: "core"— lume's floor: cube texture views, per-stage storage budgets above the compat limit); on GLES-only/compat-mode Android GPUs that request resolves null, and the null latchedWEBGPU_REQUIRED_ERROR_MESSAGE→ kiln's update wall. The API's presence already proves the browser is current enough, so the gate now classifies instead: a one-shotfeatureLevel: "compatibility"reprobe (a classifier, never a render adapter — the ledger #294 same-adapter law is untouched) splits the class into (a) compat adapter answers → a real, working GPU below lume's core floor → the newwebgpu-core-unsupportedcondition + cant-run copy (no update or setting fixes it; if lume ever grows a compat tier, this probe is where boot re-routes instead of walling), and (b) nothing answers at any level (acceleration off, blocklisted driver — ledger 144's population) → the existingwebgpu-device-unavailablecondition + honest no-adapter settings copy. Adapter-request rejections join (b).WEBGPU_REQUIRED_ERROR_MESSAGEnow has exactly one producer:navigator.gpuabsent — the one condition a browser update actually fixes. Both new messages ride the retries-cannot-heal condition rail (kiln walls by condition and stands its boot-stall auto-retry down) and match none of the wrong-class matchers on either side; console warns keep the "WebGPU adapter request returned null" stem the fleet's DD counting rides. device-perf.ts's ledger-895 comment claimed these devices "stop at the device wall" — corrected to the truth in the same diff. Kiln's routing half lands beside this: the core-unsupported condition renders the cant-run wall (jacob's #12893 copy family), adapter-unavailable messages from pinned engines render the settings wall instead of the update wall, and a requires-WebGPU message on a page whose own navigator HAS the API routes to the settings wall too — the retrofix for rooms pinned at ≤5.2.26, which keep latching the old message for weeks. - Worker-side gpu-unavailable boot deaths carry
terminalBootCondition: webgpu-device-unavailable(ledger 2316 — savi-filed incident 97ca7a7c, Shoguns Return on mobile). The renderer worker's own WebGPU acquisition can refuse when the page probe passed (lume device.ts adapter-null / OffscreenCanvas-context class — ledger 280's split, now common on phones), and that death routes throughlatchFatalRendererError→playerWordsForBootFatal, whose r-9f46c2eb copy mapping rewrites the raw diagnostic to the calm gpu-unavailable sentence. The mapping ERASED every raw string kiln's wall matchers key on (isWebGPUAdapterUnavailableErrorknows "webgpu adapter request returned null", never the mapped sentence), so kiln's recovery-verb passthrough rendered the reload chip for a deterministic capability refusal — reload re-earned the refusal forever. The fix carries the CLASS, not the words:latchFatalRendererErrorclassifies the RAW diagnostic (the sameGPU_UNAVAILABLE_DIAGNOSTICthe mapper uses) and latches the namedWEBGPU_DEVICE_UNAVAILABLE_CONDITIONbeside the mapped copy — the rail kiln already reads end-to-end (isWebGPUDeviceUnavailableVerdict→ guidance wall;boot-stall.tslatches the verdict and stands the auto-retry down). Sticky re-asserts keep the condition (a racing progress post used to be able to strip routing); reload-class faults (device-lost, generic worker deaths) stay conditionless on the reload lane — the ledger-206 boundary. Pinned on both sides: the producer's condition rides every re-assert, and kiln's wall routes on the condition under ANY copy, including a deliberately reworded one.