Spawn
spawn / swhat we're building

pinned

start herewhat spawn isfaqfrequently asked questionsthe betthe spawn bet

updates

engine v5.2Lume2 weeksengine v5.1ConnectionJune 18, 2026engine v5.0For RealJune 4, 2026engine v4.6AtelierJune 1, 2026engine v4.5Surface TensionMay 22, 2026engine v4.4SolidMay 15, 2026engine v4.3GroovyMay 13, 2026engine v4.2ContinuumMay 9, 2026engine v4.1FoundationsMay 4, 2026engine v0.1GenesisApril 29, 2026

pinned

what spawn isstart herefrequently asked questionsfaqthe spawn betthe bet

updates

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

engine v5.2.10

Engine v5.2.10

July 30, 2026

A patch in the Lume line.

Worn gear welds on, moving platforms carry you, cooking models show their art

what's new

  • Gear attached to a character's bones (armor, hats, held items) now moves welded to the body — no more trailing a beat behind when you run or spin. No script changes needed; interpolation: null workarounds can stay or go, they no longer matter.
  • Editing a live effect now just works: save the .fx.js and every running emitter picks up the new program — no more destroy-and-respawn, no filename rotation. visible: false actually hides an object's particles now (and your own torch flame stops rendering inside your first-person camera), and fx.paused: true keeps suspending the deck like before.
  • Fast vehicles and characters no longer hop on flat ground. The engine used to briefly lift the physics body ~25 cm at speed and float it back down — cars read as bouncing, and collision/traction wobbled with it. The body now stays glued at any speed; slopes, stairs, and kerbs still climb exactly as before.
  • If your game's input map carries a binding that can't work — a misspelled key name, an axis declared with no source, an engine-owned axis name — Savi now sees exactly which binding is broken and why the moment it's written, instead of your controls silently half-working until someone debugs it. Working bindings are never affected by a broken one.
  • Look scripts can now tile textures: ctx.texture("/cdn/paper-fibre.png", { wrap: "repeat" }) repeats past the texture's edge instead of smearing the last row of pixels into streaks across the screen. "mirror" reflects instead; offscreen targets take the same option.
  • You can design your phone controls now: twin sticks, gas-and-brake pedals, drag-to-aim buttons, touch pads — and swap layouts per player mid-game (driving vs on foot). Savi can finally read exactly what's on a phone screen, and the engine tells her when a button didn't fit instead of quietly hiding it.
  • Buttons in custom game UI can be held down now (sprint, charge shots), and hand-built on-screen sticks stop snapping back under your thumb.
  • Fixed two ways a 3D model could silently render nothing: a model file the device can't decompress now reports a clear load failure (instead of an invisible "successful" load), and a model that's taking unusually long to generate now says so — to you and to Savi — instead of leaving empty floor with no explanation.
  • NPCs told to walk somewhere while facing something — moveTo(target, { face: player.id }) — now actually keep facing it the whole way: backing away while aiming, strafing a range band, or sidestepping around obstacles no longer snaps their gaze to wherever their feet point. Drop the face option and they look where they're going again.
  • When a game looks frozen because its hosting player's tab is hidden (solo rooms pause while you're away — that's by design), Savi can now see and explain exactly that instead of chasing a phantom engine bug.
  • Spawned models show their concept art in seconds now: while the 3D model is still generating, the spot where it will land shows a billboard of its 2D image (the one Savi's generation mints almost immediately) instead of just the loading swirl — and the real model replaces it the moment it finishes cooking.
  • Fixed a silent failure where, in worlds with very many uniquely-shaped generated objects, newly added ones could stop appearing entirely — with no error anywhere — once a rendering batch filled up. The engine now tells Savi exactly which batch filled and what frees space, the moment it happens.
  • Worlds with very many uniquely-shaped generated objects no longer hit an invisible ceiling where newly built things stop appearing: the rendering batches now grow with the world (up to what the player's GPU can actually hold, which is several times the old limit). If a device's true limit is ever reached, it still announces itself instead of failing silently.
  • Savi can now actually see your game's UI when she checks it — before, a technical wall meant her UI checks always came back empty, and she could mistakenly report your HUD as broken (and prescribe reloads) while you were playing through it just fine. Her reads now also come with the engine's own delivery receipts, so false alarms about invisible UI stop at the source.
  • On a shaky connection, multiplayer games no longer stutter backwards: NPC timers, animations driven by state clocks, and moving objects run by your game's scripts keep advancing smoothly instead of repeatedly snapping back a half-second every time the connection strains.
  • On a bad connection that goes one-way (you can still see the world but the game stops hearing you), the server no longer yanks you back to your landing spot every ~30 seconds forever — after a few minutes of provable silence it tells your game to reconnect once, you land back in your own body, and play continues.
  • Fixed a bug where nudging just one rotation axis (like rotation.roll) on an object could silently do nothing — the object stayed frozen at its old angle. Single-axis rotation writes now turn the object, keeping the axes you didn't touch.
  • Savi can finally see what she builds: her inspection camera now carries your world's look and bloom (godray scripts, grades, vignettes — the same pixels you see), and she can read exactly where your sun is instead of guessing from shadows. Look work stops being "a bet until you turn around and look."
  • If a scripted object's geometry script produces an empty mesh (nothing to draw), Savi now finds out immediately — the error log names the object and the script, and asking her to look at the object tells her the geometry came up empty instead of guessing it's still loading.
  • Stepping off a moving boat onto a dock (or any ground) now cleanly ends the ride — no more being dragged along the pier at boat speed. Riders also survive server host changes and tab-away/return without getting glued to the boat.
  • Moving platforms just work now: stand on a boat, elevator, or any moving object and you ride it — walk around on deck at normal speed while it moves, jump and you keep its momentum, step off and you're free. No follow scripts needed, and the ride is glued at render cadence (no more shaking while the deck moves).
  • Fixed a bug where long-running rooms could get stuck naming every new thing with the exact same id — weapons, projectiles, and effects would silently replace each other and cleanup would miss. Affected rooms heal themselves the moment they mint their next id; no reset needed.
›technical notes
  • Bone-attached children join the render-composed rail (ledger 1860 — tucker's armor trailing its wearer). A tome/parent op carrying attachment.bone used to parse to parentId: null, transformAlreadyPresented: true: excluded from the renderer's parent-relative composed-child rings and drawn from raw wire rows that the SIM composed through the cross-worker bone-pose feedback — the bone's world transform from the renderer's LAST displayed frame, plus a channel hop. Net: worn gear = the body's drawn pose 1–2 render frames ago, trailing by speed (measured 0.39 units at a 0.25 u/tick sprint in the new harness). Now bone children keep their parent edge and compose RENDERER-side at draw time: parent's displayed pose ⊗ live bone local ⊗ authored offset — the live rig providers already register renderer-side (lume/models/skinned-character.ts), so the composition is same-frame with zero feedback round-trip. This is #11535's stand-rider rail (drawn composition against the parent's displayed pose at render cadence) extended to bone anchors.
  • The drawn chain, not the physics chain: the new writeDrawnBoneTransform provider leg runs the base through the visual's own trailing chain (local offset ⊗ geomScale + ground-alignment lift — composeVisualWorld's math), so the bone lands where the body actually draws. The existing writeBoneTransform base leg stays deliberately lift-free (ragdoll seeds pin that contract).
  • #7350 stays dead, structurally: the pass-through rail (transformAlreadyPresented + its sync path) is deleted, and bone children never seed a snapshot ring — the wire rows CANNOT be re-interpolated (the double-delay #7350 cured has no code path left). Pinned from both directions: corrupt the feedback channel and the drawn pose stays welded to the live rig while the sim rows dutifully carry the corruption.
  • The feedback channel keeps serving the sim, unchanged: hierarchy-render-solve still composes the child's World* rows from the SAB feedback every renderPrep (gameplay reads, script queries, ecs-sync). Those rows draw only as the fallback while no live rig is registered (model loading / horde-batched parent — the ledger-#308 seed baseline), exactly the old first-frames behavior.
  • Named gaps: a pivoted DIRECT bone child draws unfolded (the pivot fold needs bounds the op stream doesn't carry, and anchor inference can't serve here — a direct bone child's authoritative rows are feedback-stale, so the inferred anchor would bake in the trail); children NESTED under a bone child infer normally. Bone children of an interpolation-disabled or record-less parent keep the raw-row fallback.
  • Running fx decks adopt every recompile — live .fx.js content edits included (ledger 1834 face 1). The renderer-side diffs (lume particles store, particle service, both backends) adopted a program only when FxCompiledProgram.sourceVersion (= FxEmitter.version) moved — but a live script-content save recompiles under an UNCHANGED emitter version, so a running deck kept executing the original program until the ref itself was rotated to a new filename ("the effect system reloads by filename, not by file contents" — savi taught filename-rotation as the workaround, baking ref churn into specs). FxCompiledProgram now carries compileSeq, a per-entity monotonic counter minted by tome/fx-compile on every program write, and every downstream diff keys on it: any recompile — params write, script edit, lib edit, cache-epoch reset — reaches the running deck by construction. The recompile-invisible-downstream class is unrepresentable now, not patched per trigger.
  • fx decks honor visibility (ledger 1834 second face, incident b62bd296 THE VOW). The particle path never read draw/visibility: properties.visible = false hid the model while the deck kept emitting (mist visibly rendering while visible === false; same leak under the first-person body hide, the editor-only courtesy hide, and the foreign camera-attach gate — all DrawVisibilityOverride writers). tome/fx-compile now resolves effective visibility the way the mesh path does (client-local override wins over authored; layerMask 0 = hidden) and a hidden emitter drops its compiled program exactly like paused: true — deck gone next frame, unhide recompiles from scratch (decision 11; non-zero layer masks stay visible). fx.paused and api.destroy() behavior unchanged and pinned.
  • Ground-follow lift gate (ledger 1853, tiger's car-bump): a kinematic character controller driven across a FLAT heightfield at race speed popped up to ~0.20 m in one tick and floated back down over the next several — track flat, zero script y-writes, autostep disabled. Mechanism (measured, ground-follow-lift.integration.test.ts reproduces it from a plain world): rapier's controller sweep collides with the SHARED edges between coplanar heightfield triangles and parry reports phantom tilted contact normals (n = (-0.28, 0.96, 0) on an all-zero field); collide-and-slide climbs the phantom ramp, and the taller the per-tick sweep the taller the climb — race speed turns a sub-millimeter artifact into a visible hop, and the motor's ground press (-6 m/s) takes ~3 ticks to bring the root back down. The float-back was never a separate mechanism; the up-pop is the whole injection.
  • The fix verifies the output against the world instead of trusting the phantom: when the motor is pressing down (no commanded upward motion) and the computed movement still rises more than 5 mm, down-probes at the landed position — axis, thin lateral offsets, and leading samples out to the shape's full support reach along the motion direction (a rotated car box reaches 2.2 m lengthwise; slope entries and kerb step-ups ride the leading corner) — measure the ground that is really there. A rise the surface backs (slope, step, kerb) commits untouched; a rise into air gets clamped to follow the measured surface, and the grounded verdict rides the committed pose. Probe origins sit INSIDE the shape near the feet, so an overpass above the car can never masquerade as support, and embedded floors stay visible. Rays fire only on rising ticks (≥ 5 mm) — idle, walking, and flat cruising pay nothing.
  • Not fixable at the source, and mantle needs none of this: HeightFieldFlags.FIX_INTERNAL_EDGES (rapier 0.19.3) only corrects the contact-manifold path — measured: controller pops byte-identical with the flag on — so the gate lives at the controller output seam. Mantle's heightfield narrowphase already folds neighbor-triangle normals structurally (narrowphase/static-heightfield.ts edge activity) and never manufactures the ramp.
  • Pins: ground-follow-lift.integration.test.ts — (1) race-speed box on a flat two-chunk heightfield stays glued (pre-fix: +0.2035 m single-tick rise, red; post-fix: ≤ 0.0134 m, within contact noise); (2) a 15 % grade still climbs at race speed (the gate's leading-corner samples keep real hills honest); (3) a 0.2 m kerb still autosteps at driving speed.
  • Per-binding input-map refusals (ledger 1851 residue (a), forensics 3b93f733 — tucker's pirate sloop): the input compile was already per-binding tolerant — a binding that can't resolve is dropped ALONE and its neighbors stay live — but nothing ever SAID so: resolveKeyBinding returned null silently, a case-broken code-shaped name ("Keyw") passed through verbatim as a binding that can never match an ev.code, an engine-internal axis declared in inputs.axes (aimYawSin: {} — the pirate map's exact content) compiled to nothing, and the only diagnostics were console.warns in whatever realm compiled the map. A creator sat pinned at spawn for an afternoon while Savi probed for what one write-time line would have named. New collectInputBindingFaults in input-config.ts shares the exact resolver tables (no drift possible) and emits one fault per broken binding — path, reason, fix, with the valid form suggested when the mistake is spelling case ("Keyw" → did you mean "KeyW"?, "escape" → "Escape"). Two lanes consume its printable form (capped at 12 lines + remainder): patchInputs warns on the mutation-warn rail (Savi's getLogs, at write time, validating the MERGED map so still-broken bindings re-warn on the next write), and spec-sync's control install warns on the client console ring once per distinct authored map (the existing inputs-signature gate) — so a map written by ANY path names itself.
  • What faults, exactly: engine-internal axis declarations (ENGINE_INTERNAL_AXES — never bindable, the declaration does nothing); dead key tokens (silent-drop null resolutions AND verbatim-passthrough tokens whose case-fold matches a known binding); Tab (named reservation); wrong containers (axis keys must be a slash-string, action keys an array — a bare string binds each CHARACTER); unknown mouse/gamepad names with their valid vocabulary; junk activeOn/modifiers; near-miss field names on inert defs (key → keys). What deliberately does NOT fault: any case of a documented shortcut ("arrowup"/"Arrowup" resolve — the pirate map's lowercase arrows always worked), unenumerated code-shaped tokens with no case-fold match ("NumpadComma" — the raw-capture escape hatch), empty ACTION defs (the sendAction UI pattern), empty AXIS defs get a notice teaching their live uses (sendAxis / authored touch sticks) rather than a refusal — declared-but-unbound axes stay declared, and creator metadata beside a live source stays unpoliced.
  • The anti-cliff pin: input-binding-validation.test.ts pins survival (WASD moves and actions fire with three broken axes in the same map; a dead key name disables its one binding alone) and byte-identical compile of the pirate map, so per-binding tolerance is now a contract instead of an accident — input validation can never become all-or-nothing without a red test.
  • Look-script textures gain wrap control — UV>1 no longer forces silent edge-texel smear (THE VOW's screen streaks, ledger 1847: every look-pass ctx.texture() bound a hard-coded clamp-to-edge sampler, so a paper-fibre texture sampled at screenUV × aspect rendered ~2/3 of a 16:9 frame as horizontal/vertical streaks with zero signal). ctx.texture(assetId, { wrap: "clamp" | "repeat" | "mirror" }) and ctx.target(name, { …, wrap }) (riding .previous too) lower to the GPUSampler address mode (clamp-to-edge / repeat / mirror-repeat) — the smallest primitive, no pipeline: the wrap rides the ShadeTextureRef, and the look pass picks the sampler per ref at bind time. Defaults stay clamp everywhere (existing content pixel-identical); the engine-owned frame/depth inputs stay clamped by construction (edge-extension is what screen-space kernels want). Unknown option keys and unknown wrap values throw teaching errors inside look(ctx) — one diagnostic naming the vocabulary, look parks on the copy fallback (the fx-sink invented-keys law).
  • Authored touch surfaces: inputs.touch.surfaces — named, complete TouchSurfaceDef documents (multiple sticks with zone/anchor/quantize, buttons incl. axis pedals and drag-aim with release payload {x, y, magnitude}, absolute/relative pads, one-action world gestures, ordering slots, icons, sizes, roles) — plus inputs.touch.active, a per-player selector script (default export (ctx) => name, worker-evaluated on the god-posture ship pattern; junk/unknown picks park that player to the draft with a teaching fault; oscillation is rate-limited and faulted). Surfaces compile into the same ControlSurfaceSpec the draft and god specs feed deriveLayout; the engine never merges two surfaces.
  • Silent drops died as a class: every clamp/drop/park is a named TouchSurfaceFault (unknown-channel, zone-conflict, button-overflow, stick-suppressed, gesture-folded, …) surfaced through compile errors and getLogs teaching faults; main-side geometry drops post an engine.diagnostic (touch-layout-overflow).
  • The draft is a value: api.resolveControls(surface?) returns the engine's auto-projected draft (no arg / "auto") or a named surface, each with diagnostics. The draft's projection semantics stay byte-stable (pin-tested at reference viewports) — floor, not ceiling.
  • Authored button capacity is geometry, never a fixed cap: deriveLayout fits what safe-area + chrome reservations + 44px thumb floors physically allow and drops loudly from the slot-order end. The draft keeps its cap of 4. Slot is an ordering number; axis-hold's game-path ban and role's god-only scoping died.
  • Runtime: slot-indexed analog SAB (16 slots — twin-stick works), input.axes.touchActive (1 while touch is the live input; desktop trace-free), surface swaps are edges with cancel synthesis, ?touch=1 gives desktop creators a live touch-chrome eye.
  • Tome UI contract fixes: sendAction(name, undefined, { down: boolean }) plumbs the wire's held-button field (routes to setAction — press/release edges, hold bindings included); the god-slider data-dragging morph guard is generalized — any authored element holding an active pointer capture is never morphed mid-drag (hand-rolled UI sticks stop being rebuilt under the finger).
  • A model that cannot decode now says so out loud instead of rendering nothing (ledger 1862, dig e6888eaf). parseGlb/parseGltfJson refuse a file that REQUIRES EXT_meshopt_compression when no meshopt decoder is available (GltfParseError), instead of "succeeding" with every compressed accessor read as zeros — full node/mesh topology, invisible geometry, all load books green (repro: 0/165,246 nonzero positions). Since the v7 quantize+meshopt republish that was every Magic CDN visual model for any client whose wasm init failed. The refusal rides the EXISTING model-load failure machinery — renderer retry ladder, console.error, and the ledger-1025 model-load-failed diagnostic carrying the parse refusal verbatim — never a new lane. Files that merely USE the extension (fallback buffers present) keep the per-bufferView warn-and-zero degrade; healthy loads parse byte-identically (pinned).
  • ParsedGltf.warnings has a consumer (ledger 1862's second face: written since the lume cutover, read by nothing). A model that loads successfully but parsed with warnings — zeroed accessor reads, skipped primitives, unsupported required extensions — now surfaces them once per id on the new allowlisted model-parse-warnings diagnostic (log-only: the load succeeded, so it is why-does-it-look-wrong context in getLogs, never a DM) plus a worker-console warn.
  • A Magic CDN cook loop past five minutes is a named fact, not a silent treadmill (ledger 1025's never-terminal hole, dig e6888eaf: badgerblunts' ten-clip ?animations= bot variants looped 202→cooldown all session with zero Savi-visible signal). 202 answers never escalate attempts by design, so the attempt-5 model-load-failed diagnostic can never fire for a cook that simply never lands. New allowlisted model-generation-slow diagnostic fires once per pending episode when a model is still answering 202 past 5 minutes — deliberately worded NON-terminal (the engine keeps re-checking; the model appears when the cook completes; regenerate/simplify only if it stays stuck).
  • Cooldown-parked models stay visible to getUnreadySceneAssets (the dig's confirmed second gap). Render-on-change stops prepares in a quiet scene, so a model waiting out a retry cooldown aged past the 2s poll-liveness window and vanished from the unready report for most of every cooldown — Savi's captures read "nothing missing" while ten bot variants were mid-cook. A model whose retry is scheduled (finite retryAfter in the future) now counts as scene-referenced until that deadline: the due-retry wake buys a prepare, so a scene that still wants the model re-polls (stamp refreshes) and one that dropped it ages out at the deadline (pinned both ways).
  • moveTo(target, { face }) now holds the face override for the whole move (MechaBlade "regression on untouched code" dig: the option carried faceTargetId in the intent but stamped only the move beat, and resolveYawTarget honors the override only while the FACE beat is fresh — which only the explicit face() verb stamped. Net: the documented option decayed to travel-direction yaw within one beat grace, so "walks toward you facing you" worked only when travel happened to point at the target — empty-yard geometry — and died the moment pathing sidestepped around an obstacle). The fix follows the beat system's own grain: a face option IS a face assertion, so moveTo stamps both the move and face beats (assertNpcIntent now stamps a beat per asserted channel); the override holds exactly as long as the moveTo keeps being re-asserted and releases with it — travel yaw resumes within the same decay grace as everything else. No resolver/system special case.
  • A plain moveTo no longer clobbers a live face() override's fields — same root, second symptom: moveTo's patch used to carry explicit-undefined face keys, so api.face(x) called BEFORE api.moveTo(y) in the same pass was silently wiped (only the face-after-moveTo order worked). moveTo now writes the face channel only when its option asserts it; face() beside a plain moveTo holds in either call order. When both writers assert in one tick, the last call wins (merge-and-stamp semantics, pinned).
  • Sweep result: face was the only intent option gated on a beat its writer never stamped — speed/range/exact/avoid/fleeUntil/wander opts are all honored while their own verb's move beat is fresh, and steer() stamps its own beat. No other silently-decaying option found.
  • Held/unhosted places tell you they're frozen (ledger 1839, dig e6ed67b2 — the ledger-931 pause-legibility pattern extended to host state). In client-hosted multiplayer the server never simulates place content; while a place's seat is successorless-HELD (ledger 1788 — the sole host's tab hidden/silent/mid-reconnect with nobody else to elect) or the place is unhosted-but-occupied, update()/api.on events/interactions/timers are frozen there by design while onSpawn, crons, api.sql, and edits keep working — previously with zero Savi-visible surface (Savi filed the designed freeze as an engine bug four times in one night; ~447 hold episodes across 48 apps in a 4h window is standing weather for solo-occupied rooms). Now: (a) every run_script result during the freeze carries a prepended place "X" is not being simulated right now … note naming the reason, the since-tick, the frozen-vs-alive class split, and the "designed netcode behavior, not an engine fault" verdict; (b) an edit landing in a frozen place writes one getLogs runtime-log line per hold episode (episode-deduped like 931's pause line); (c) api.reportEngineBug incident dumps carry a placeHosts block on the server context — every table row plus hold state (reason, sinceTick), tenure confirmation, and occupancy — so a "update() never fires" filing self-classifies. One truth for all three surfaces: tome/place-sim-status.ts reads the same hold-episode record the assignment system maintains (moved into the cross-system PlaceHostTenureState; zero simulation-behavior change).
  • The placeholder ladder's middle rung is back: a cooking model shows its 2D concept image as a billboard within seconds, then swaps to the model when the cook completes (thread 1785358887). The three-era hologram (extensions/models/placeholder-visuals.ts) crossfaded to the Magic CDN preview image; the lume port (#7602) cut that lane with a TODO(lume-placeholder-previews) and shipped pure-procedural rings — the "swirling circle until it loads completely" regression. The lume placeholder module now resurrects the old logic minus the bubble shell (removed on taste back in ledger 417): collectPlaceholders already derives previewTextureId from the preview metadata poll (preview_image_url is written ~2-3s into a Trellis/Meshy cook, minutes before the GLB), and packPlaceholders now registers that URL with the lume texture service (registration + subscription IS the load demand — the override-map mount pattern), partitions instances procedural-first then one contiguous run per decoded preview texture, and draws each run with its own bind group and a firstInstance offset into the shared instance buffer. The preview's own alpha masks immediately (BiRefNet cuts the background CDN-side); the procedural hologram crossfades out underneath at the three-era 4/s rate. The rung is visual-only: pick volumes (placeholderDrawnSize) and physics are untouched, failed cooks stay procedural red (legibly dead beats pretty), a missing/undecoded/layered preview falls back to today's procedural hologram instance-for-instance, and the model-ready swap releases the texture retain and subscription the frame the placeholder drops.
  • The batch-lane arena-full refusal is loud on every rail (ledger 1833, War Cloud d757e8d9 root). When a primitive batch lane's shared geometry arena hits its cap (LANE_MAX_VERTICES/LANE_MAX_INDICES), every NEW unique shape routed to that lane is skipped by design — the object exists, answers queries, keeps physics/bounds, its bespoke-geometry wait resolves (data was delivered), and it renders NOTHING, permanently. The engine already emitted primitives-lane-arena-full for this, but the code was never allowlisted server-side (the #330 emitted-but-dropped class) and the emitter printed no console line — so the loss was invisible end to end: clean console, clean getLogs, zero stall reports (War Cloud saturated one vertex-PBR lane at exactly 1,048,576 reserved vertices across 2,664 split-slot registrations from ~440 scripted bodies; everything refused after the cap — the creator's viaduct among them — was silently invisible for two sessions and ~100 rebuild attempts). Now: the diagnostic is allowlisted (full-body DM — ACTION-classified with the #297 consent rule, the outline/shadow-slots shape), it prints a server-console breadcrumb (client-side emitter; the stdout line is what makes arena exhaustions DD-countable fleet-wide), and the emitter warns on the worker console (once per lane) so client dumps carry the refusal.
  • Batch-lane geometry arenas grow on demand — the fixed 1M-vertex wall is gone (ledger 1833, the capacity half; the silence half landed as primitives-lane-arena-full loud rails). A primitive batch lane's shared vertex/index arena was admission-capped at a fixed LANE_MAX_VERTICES = 1<<20 / LANE_MAX_INDICES = 1<<21, even though the arena's whole growth stack already existed (pow2 CPU mirrors + GPU buffer recreation that preserves flushed content with a buffer-to-buffer copy — mobile memory diet phase 2a). War Cloud held one vertex-PBR lane at exactly the cap (2,664 registrations, 702,604 live vertices, ~33% pow2 reservation padding), so every later unique shape — the creator's viaduct among them — was refused at the batch seat and rendered nothing, forever. The fixed caps are deleted: admission now checks a device-derived ceiling (laneArenaCeilings) — min(maxStorageBufferBindingSize, maxBufferSize) over the lane's vertex stride (pulled lanes bind the whole arena as ONE storage buffer, so bindability is the bound, not creatability), maxBufferSize/4 for the u32 index arena, and an absolute 2^24 vertex cap (instance-param float 21 carries each slot's vertex-arena base as f32; integers stay exact through 2^24). Under the ceiling the arena simply grows on the existing ladder; before a lane has seen its device, the WebGPU spec-default limits are the floor (any device can bind what they admit), so cold-boot bursts grow too. On a desktop 512 MiB grant a vertex-PBR lane now tops out ~7.07M vertices (~6.7× the old wall); at the TRUE ceiling the loud primitives-lane-arena-full refusal (DM + breadcrumb + console) fires exactly as before, now carrying used/max vertex+index counts. Growth cost is a one-time hitch per doubling (a lane doubles at most a handful of times in its life): one GPU-side copy of the old arena (~76 MB at the 1M-vertex boundary — sub-millisecond at VRAM bandwidths, and it replaces what would otherwise be a CPU re-upload) plus one prepare's worth of mirror re-uploads and bundle re-records. A permanent one-frame hitch per size class beats permanently invisible geometry. The pow2 reservation padding (~33% burn in the specimen) is deliberately untouched — growth, not packing, is the fix.
  • Authored UI is now READABLE from the tool side — the realm-aware read relay (ledger 1845 fix b, the structural cut behind the c13632c4 phantom-blackout family). Since the authored-realm isolation cutover, ui.js DOM renders inside a sandboxed opaque-origin iframe, so run_ui_script's host-document reads structurally cannot see it: every probe of the taught addresses returned empty and healthy HUDs got filed as "never paints" while creators played through them. New client RPC read_authored_ui (contract read-authored-ui/1) crosses the frame broker into the realm and executes the query where the DOM is legible: a closed read-verb vocabulary (snapshot | html | text | rect) + selector via querySelectorAll — never evaluated, realm answers only (never initiates), bounded payloads with honest truncation markers, write/eval-shaped requests refused loudly at the RPC gate AND structurally unrepresentable on the wire (protocol family 23 read, strict decode). The response carries the realm boot state and a FRESH HUD-pipeline receipts block (compile state, sent count+sig, applied outcome + sent→applied lag, dedupe count+last-tick — minted per read from the runtime worker) alongside the DOM answer, so a "never paints" claim has to beat the pipeline receipts in the same payload.
  • The reset-path client-sim rewind dies (ledger 1848, kin 1523). The reset-delta path was the only inbound lane that could rewind client-simulated state in normal play: sweepProjectionForReset despawned host-simulated/owned entities and the reset's create rows rebuilt them from the server's canonical blobs — but for an entity THIS client simulates, the canonical copy IS the client's own uploads, ~RTT stale by construction, so every reset rewound live hot state (TomeState clocks, owned appearance values) by RTT. Send-failure reset storms (Bun ws.send backpressure → strict false → reset; ledger 945's prod specimens) made that a per-0.6s event on saturated links. Family contract, now enforced at the seam: an inbound apply must not write client-simulated (or script-diverged) hot state it cannot prove newer.
  • The guard shape: the applyAll bypass stays (load-bearing — owned entities must re-materialize on place transitions). What changes: a new shouldAdoptEntityStateOnReset ingest hook (control-utils shouldAdoptReplicatedEntityStateOnReset) marks entities this client currently simulates; the reset sweep keeps them alive, and their reset rows are rewritten to server-stamped (clientAuthWrite !== "owned") components only — spawn-stamped fields (TomeLifetimeDeadline) and authority transitions keep landing, owned values keep the local copy: exactly the value the server itself defers to. Preserved entities the reset does NOT re-show are despawned after the row passes (server-side kills of simulated entities keep landing via resets). Unknown/foreign entities adopt wholesale, unchanged.
  • The wake handback is exempt by design: this session's OWN suspension-stamped envelope adopts the reset wholesale — while the tab was hidden the place HOST simulated it, so the resume reset's blobs are the newer truth (docs/client-auth-suspension-takeover-plan.md). A suspended FOREIGN envelope this client host-simulates under the takeover is protected like the rest of the remainder.
  • The no-confirm reset-resend loop gets a terminal state (ledger 1850 — #11360's disease, server edition: unbounded retry, no terminal). The o-53 ingest-truth lane deliberately removed the give-up-flip-to-Streaming (the 2026-07-20 one-directional freeze) and assumed a pipe that never confirms is owned by other rails — but a HALF-ALIVE socket (downlink delivering every frame, uplink eating every echo) is visible to none of them: the client's silence watchdog keeps hearing heartbeats, its self-heal re-request rides the dead uplink, and the ledger-695 eviction guards on Streaming. The GOHN specimen (07-29): full 1.26–1.7MB world snapshots every ~32s (the hold ceiling) for 25+ minutes with zero ingest_confirmed, each delivered resend re-seating the mid-flight creator back at the arena landing ("DAMNIT IT TOOK ME BACK TO AMONKET"), BAD@277 landing 0.3s after the final resend — version-independent (same shape on 5.2.6 and 5.2.9).
  • The verdict shape: a new deafness budget RESET_ACK_TERMINAL_HELD_SENDS = 11 (5 doubling holds + 6 at the 32s ceiling ≈ 3m43s of provable deafness — far past every genuine recovery the ledger-945 client budget observed). At the hold expiry that would cut snapshot N+1, the egress instead logs netcode.reset_projection.terminal (warn, with heldSends), closes the socket retryable (4437 / reset_ingest.deaf — deliverable, the downlink works), and detaches the connection into the ordinary disconnect grace. NOT a despawn and NOT a Streaming flip: the entity keeps simulating through the grace window, no snapshot rides the transition (the per-resend re-seat was the harm), and the client's existing reconnect lane lands a reattach that resumes into the same entity via replaceReadyConnection with every reset counter re-zeroed — a fresh socket, the only thing that can heal a dead uplink.
  • Ground truth always wins: a late ingest_confirmed anywhere inside the budget clears the counter through the existing echo path and the episode ends as a normal confirm — the terminal check only ever reads the counter, so a healed link can never be condemned after the fact (pinned by test).
  • The road/spline ECS component is deleted (residue cut named by #11164, probe 60f4933c, row r-dd0a41ea). The component had zero producers since roads migrated to the spline system (#6308, 2026-04): every remaining reference was a has-guarded remove or a has-guarded set — branches that could never fire. Dies with it: the component definition + its bespoke encode/decode (features/road/components.ts, the whole file), its registry row (defineRoadComponents, the road/spline wire-dictionary entry and its share of schemaHash), the 8 dead guard sites in tome/api/properties.ts + tome/interpreter.ts, and the fossil RoadSpline section of docs/API.md. Survivors, untouched: features/road/geometry.ts (getRoadGeometry/RoadSplineValue — rivers and spec-level spline: { kind: "road" } visuals are a different, live system) and the road feature barrel, which now re-exports geometry only.
  • Dotted euler writes no longer vanish against quaternion rotations (ledger 1863, GUTTERBT snowboard dig). setObjectProperty("rotation.roll", …) deep-merges onto the {x,y,z,w} read-back and the merged {x,y,z,w,roll} hit resolveRotationSpec's quat branch first — the fresh euler intent dropped with no rail (the lookAt+euler conflation had one; quat+euler had none). Both resolver twins (api/object-api.ts, spline.ts) now detect euler keys riding a full quaternion and apply them onto the quat's decomposed axes (quatToYawPitchRoll, new in the math library: intrinsic Y-X-Z, the exact inverse of quatFromEulerDegrees, gimbal-lock safe); unnamed axes keep the quaternion's values, so per-frame rotation.roll writes are absolute, not accumulating. warnRotationQuatEulerConflation joins the spec-shape rail — once per room, names what the engine did. Pure-quat and pure-euler resolutions are pinned byte-identical.
  • Savi's inspection camera sees the post chain — camera/frame captures composite the active look + bloom (the lume port of #8344's composite leg; r-df174d4e gap 1). view_live_scene camera/frame captures rendered the raw scene (ACES + sRGB only) and skipped the whole post chain, so Savi authored look scripts blind ("my inspection camera can't see the post chain… this fix is a bet until you turn around and look"). The capture now owns a post rig of the chain's own pieces (lume/capture/postfx.ts): the dual-filter bloom pyramid, the generated composite kernel (veil 0, no outline slots, no RCAS — scene + bloom → ACES → fused look → sRGB + dither), and a capture-owned LookPass for two-hop looks (offscreen ctx.target/ctx.pass graphs — the godray shape), with a 1× depth resolve feeding the depth-reading kernels (haze, dof, ctx.depth). One-shot correctness: BloomPass.prepare() / LookPass.prepare() await pipelines where the live chain keep-presents, so the first capture after a look edit carries it. Nothing active keeps the plain path byte-for-byte; any post-leg failure falls back to the plain render (never a failed capture). Every capture note names exactly what the image carries — composited / nothing-active-matches / still-compiling-recapture / broken-look-matches-viewport / could-not-apply. Burst tiles ride the same path, so filmstrips are graded too.
  • The render-time sun is readable — getAtmosphere().sunState (r-df174d4e gap 2). Savi inferred the sun's world-space direction from shadow angles in a render; the derivation (timeOfDay orbit, authored-rotation pin, cycle override, overcast dimming, night moon handover) lived only inside the client atmosphere-sync system. The sun block is extracted verbatim into one shared resolver (atmosphere-sun-color.ts resolveAtmosphereSun) feeding both the sync's light entity and the new derived read-only sunState on getAtmosphere(): { direction, rotation: { pitch, yaw }, color, intensity, body: "sun" | "moon" }, or null when the atmosphere has no sun at all — the "new worlds ship with no sun" truth as a readable fact instead of a black render to reverse-engineer. patchAtmosphere strips sunState on the way in, so a full read patched back round-trips clean.
  • The empty-derive silence hole dies (ledger 1833, War Cloud/viaduct-river): realizeScriptedPrimitiveGeometry's ok:false reason:"empty" outcome — geometry() returning without error and without a single emit — used to return geometry: null with NO report (only thrown errors were loud), after which the interpreter removed the entity's DrawMesh pointer entirely: no log, no DM, no heal traffic, no vocabulary in getLogs or view_live_scene. An object that exists, answers queries, and renders nothing anywhere, forever, silently. Now the empty outcome reports through the same behavior-error rail every other geometry() failure rides (runtime log + deduped DM, naming entity + script + "emitted no geometry — check the params/seed"), from the ONE derive funnel (deriveScriptedPrimitiveGeometry), so every lane is loud: server mint, client applySpec, live property writes, the #929 ingest drain, ground redrape.
  • view_live_scene's frame-mode miss names the real cause: a failed/empty derive on a client world now leaves a ClientBespokeGeometryDeriveFailed row (client-plane, replicate:never, forwardToRenderer:always — the Unhealable pattern) carrying { script, reason: "empty" | "error" | "no-geometry-export" }; cleared by the next successful derive, dies with the entity. The renderer's primitives store keeps it as a per-entity note (it is the ONLY renderer-side trace — a no-mesh entity has no pointer, no wait, no record), and sceneViewObjectBounds' miss carries it into the frame-miss sentence: Object "x" exists but renders nothing: its geometry script (gen/foo.js) ran and emitted no geometry on this client — the mesh is empty under the object's current params/seed. — replacing the "assets may still be loading" guess that cost a creator two sessions.
  • Stand-parent grounded egress (#11535 landed-look, finding P1). Walking from a moving platform onto foreign non-moving ground (static dock, terrain) while staying grounded now ends the ride: the attached tick detaches (no momentum — feet never left ground) when the CC's support entity is neither the platform, nor a moving attachable (the transfer branch), nor a member of the platform's own parent chain (a static-bodied part of the same assembly holds the ride until the pose watch qualifies it for transfer). Persistence-gated at 2 consecutive foreign-ground ticks so single-tick support misreads at the deck edge can't drop the ride and re-base the anchor. Before this, every detach path required airborne ticks, despawn, or a moving support — stepping onto a dock left the carry composing the rider in the departed platform's frame, dragging them at platform speed indefinitely (measured: 2.000 m of drag over 60 idle ticks at 2 m/s, exactly the deck's travel).
  • Stand-parent authority handoff (#11535 landed-look, finding P2). The engine's edge now survives its simulator: attach() writes TomeParent { parentId, stand: true }, and a simulator that finds a bare flagged edge with no local state (host migration, suspension wake, control transfer — the replicated product outlives the realm-local TomeStandRider marker and the WeakMap) ADOPTS it instead of classifying it as a script mount and orphaning it. Before this, the orphaned edge re-entered the hierarchy solve and the −10 projection on the new simulator — the two pinned-red grinds (teleport-compose depenetration + mixed-frame erosion) with no detach path ever. Scripts-win is unchanged: script writes lack the flag (never adopted), and a script overwriting a flagged edge still makes the system forget it.
  • Destroy cascade reads the flag. A rider spared from a platform's destroy cascade is now recognized by the wire-visible flag on every realm, not only where the realm-local marker lives — an NPC rider no longer dies with its platform when the destroy is issued by a realm that doesn't simulate the NPC. (SessionOwner players were already spared everywhere; the marker check remains for pre-flag edges.)
  • Named the rotation gap. Platform rotation (facing does not turn with the deck; detach momentum misses tangential ω×r) is now in the system header's out-of-v1 list rather than unstated.
  • Stand-parent — the moving-platform primitive (thread 1785355069: jacob's "parenting to the boat that should just work" + "unparenting when leaving"; tucker's brain dump is the spec). Stand on a moving entity → the realm that simulates your character parents you to it (TomeParent + Local* anchor): movement becomes deck-frame-relative (walk speed independent of platform velocity), the platform carries you through rapier's own controller pipeline, and the renderer composes your drawn pose from the platform's drawn pose via the existing parent-relative presentation rings — the deck-rider world-space follow workaround and its structural one-platform-step sawtooth die. Leave (jump, walk off, platform despawn) → unparent with world-pose continuity plus the platform's last measured velocity carried ballistically until the next grounding. Jump straight up on a constant-velocity deck and you land back on the deck (re-attach) — the deck only leaves you if it accelerates.
  • Mechanism: the rapier character controller now reports its ground-support ENTITY (getCharacterControllerGroundEntity — axis support resolved whenever grounded). A carry system (order 9000, pre-physics) composes the rider's target from the platform's current pose ⊗ the stored local anchor and adds the HORIZONTAL delta to the controller's desired translation — deliberately NOT a hierarchy body-sync: teleport-composing a capsule into exact deck contact makes rapier burn its solve on depenetration and eat the desired movement (walking dies on deck — pinned). Vertical stays the controller's own (snap-to-ground follows a falling deck; depenetration push-out follows a rising one). The anchor system (order 10000, post-physics) owns attach/detach and re-derives Local* from same-tick frames. Attach: grounded on a non-terrain, non-character physics body that is moving (kinematic/dynamic by declaration; script-moved static bodies the first tick their pose changes), cycle-guarded. Detach: airborne past a 4-tick grace, despawn, or support transfer.
  • The 1848-family seam, handled: both hierarchy solves and the client Local*←World* projection skip TomeStandRider children — at the -10 projection point the rider's World* is last-physics-step truth while the platform's is this tick's fresh ingest, and dividing those mixed-authority frames bakes the platform's inter-tick delta into Local*(the ride erodes to a standstill — pinned red).TomeStandRider is realm-local (replicate: "never"); the managed TomeParent/Local\* rows are the replicated product, and each rider's edge is managed exactly once, by its own simulator (client-predicted set / not-client-auth-external — the hierarchy solve's own predicate family). Relay tier unmounts both halves (steps no character physics).
  • Destroy cascade: riders and players (SessionOwner) parented to a destroyed platform detach instead of dying with it — a rider is not a part.
  • Scope (v1, named): any character-controller entity rides — players and kinematic NPCs alike. Out: characters as platforms (head-standing never parents), items without character controllers (dynamic props ride via physics friction, not parenting), mantle-runtime places (mantle's CC already velocity-follows platforms), 2d-top (no vertical support semantics).
  • api.uniqueId() can no longer collapse into one permanently-identical id (incident 0cb62ea7, THE VOW on 5.2.6). The poison chain: any exec's setup spawns the bare tome/exec entity with no TomeIdSeq; a later spawn-only exec shipped a PARTIAL counter delta ({ spawn: k }, the untouched unique lane dropped as a zero delta), merge built a partial live component, and the next uniqueId mint did undefined + 1 = NaN — from then on every call returned the same _NaN-suffixed string forever (spawns silently adopted each other, destroys missed), and the NaN delta re-shipped on every merge. Killed at all three layers: the mint is total over malformed stored values (a non-finite lane re-derives from the persisted-spec scan — long-lived rooms already carrying the poison self-heal at their next mint, no reset needed), the exec overlay never ships non-finite counter deltas and ships snapshot-missing keys at delta 0 (a partial TomeIdSeq is unrepresentable at merge), and merge-side counter arithmetic skips non-finite input on both arms. Applies to both lanes of the machinery — api.spawn's minted ids ride the same component.