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.6

Engine v5.2.6

July 27, 2026

A patch in the Lume line.

what's new

  • Fixed a failure mode where a player could sink into (or bob on) one patch of ground forever with no explanation: if the game client repeatedly fails to build the ground under a player, it now stops silently retrying, tells Savi exactly which chunk failed and why, and shows the affected player an honest "reload to rejoin" message instead of leaving them stuck.
  • Fixed a bug where models spawned by scripts with a layout.maxExtents size limit could render at their raw file size forever — giant enemies, or tiny ones invisible under a floating name tag — until the room was reloaded. Size limits now apply as soon as the model's real dimensions are known, and a size already computed elsewhere is never discarded while a client is still loading.
  • If your multiplayer game polls getOverlapping()/getTouching()/isTouching() from server-side code, Savi now gets told once — with the actual reason (the multiplayer server doesn't simulate physics) and the fix (read contacts where the entity simulates and share results as state) — instead of the game silently answering "no contacts" forever behind a generic warning.
  • When a game spawns more physics props than the simulation can carry (piles of falling crates, rain-of-weapons spawners), Savi now gets told once — with the actual numbers (how many dynamic bodies, how far over the tick budget) and the fix (despawn or pool spawned props, cap spawner counts) — instead of the room silently grinding to a slideshow with no diagnostics anywhere.
  • Fixed mouse-only and touch-only games permanently losing input after interacting with the chat overlay: if the engine's input mode got stuck thinking the overlay was open, keyboard games self-healed but pointer games stayed dead until a page refresh. Clicks and taps now trigger the same self-heal keys always had.
  • Fixed mouse clicks doing nothing in games built on the new engine UI layer — every world click was being cancelled by a focus bounce before your onInput code could see it (keyboard bindings were unaffected, which is why key controls kept working).
  • Fixed right-click over the game world popping the browser's context menu instead of reaching your game, in normal play and god mode.
  • Fixed multiplayer rooms losing their server-side scheduling after an engine upgrade: crons, lifecycle hooks, shared storage writes from crons, and custom jobs all worked on 5.2.4 and silently broke on 5.2.5 (storage errors like "A player can only read/write its own storage", jobs that never came back). Server-side execution stays on for these rooms now.
  • Custom jobs submitted where no executor exists now fail immediately with an error that says why and where the job runs, instead of hanging forever.
  • Objects placed by ground height in a place whose terrain isn't set up no longer get silently pinned underground forever: the engine refuses to invent a ground of 0, says so once in the logs, and re-seats the object automatically the moment the place's terrain arrives.
  • In worlds with legacy sculpted terrain, "place on the ground" now means the ground you actually see — sculpt edits count for placement exactly like they count for walking.
  • getTerrainHeight returns null instead of a misleading 0 when a place has no terrain — so scripts can tell "flat ground at 0" apart from "no ground here at all".
  • Physics calls that can't work where you put them now tell Savi why, once, with the fix — instead of silently doing nothing. If your enemies never moved or your traps never triggered, this is the family that now explains itself.
  • Fixed multiplayer rooms losing cron autosaves and disconnect saves that touch shared storage: on relay-hosted rooms, storage writes from crons and lifecycle hooks failed with "A player can only read/write its own storage" even though the same game worked on server-hosted rooms. Crons and lifecycle hooks now carry their server-context storage authority regardless of where the room makes them run; player storage stays protected from other players exactly as before.
  • Your world's look can now use multi-pass effects: motion trails, glow that accumulates over time, dreamy feedback loops, and blur chains — all from the same look scripts Savi already writes.
  • Generated characters and models stopped turning near-black in shade: models whose material never said how metallic it was were being treated as polished mirrors. They now read as normal matte surfaces — fabric, skin, and painted detail show again, including models generated over the last week and a half.
  • Placing things on blocky (voxel) terrain now puts their feet exactly on top of the ground — no more objects sinking into blocks or hovering over them, especially in 2D worlds with small block sizes.
  • Terrain queries far from any player now say "that part of the world isn't loaded here" instead of pretending it's empty air — so a probe 100 m away can't convince anyone (Savi included) that solid ground never generated. The log line that appears alongside explains where probing works and why.
  • Characters can now ragdoll: api.ragdoll(enemyId, { impulse: [300, 150, 0] }) flips a rigged character into loose physics — it crumples with its running momentum, tumbles off ledges, and settles into a heap. api.unragdoll() stands it back up with a smooth blend into animation (no pose snap). Works on players and NPCs in mantle-physics worlds; ragdolls freeze solid after settling so a battlefield of bodies stays fast.
  • In first-person games the player's shadow no longer vanishes: hiding your own body from the camera used to erase its shadow too — the shadow now stays on the ground while the body stays out of view.
  • First-person weapons Savi builds now line up by construction: iron sights land on the crosshair from the weapon's own declared geometry instead of guessed offsets, and hands are authored in the weapon's frame so they can't drift off the grip.
  • Savi can now actually see the code-authored models she builds for you (scripted and custom geometry) in her isolated preview before they enter your world — those pieces used to be skipped, so hand-crafted props were built blind.
  • Signs keep their authored size and text styling everywhere: updating a sign's text (like a score or percent counter) can no longer reset it to a giant default board after the room restarts or changes hosts.
  • "Delete everything and start me over" now works in one clean step: when you explicitly ask for a full wipe, Savi can reset your world to a fresh starter instantly instead of dismantling it piece by piece. Switching starters while keeping your work stays protected exactly as before.
›technical notes
  • Ledger #597's failure-visibility cure now exists on the CLIENT chunk-build path (ledger 1771 — the same half-cure class as the #10902/#10924 blur family: a fix that landed on one of two symmetric paths). Pre-fix, every client build failure (job error/canceled/stale, stuck-past-deadline) was consumed silently, re-marked, and resubmitted every tick forever — no reason, no counter, no backoff, no terminal state; cal's support chunk looped 3 hours emitting nothing (report 2b9742c1). Four pieces, mirroring the server's #597 vocabulary:
    • Failure reasons: every failed client chunk build logs the actual reason via tomeLogger ([terrain/client-build] chunk build failed: … consecutiveFailures=N retryBackoffTicks=… reason=…), first failure immediately, re-warn once a minute.
    • Counter + backoff: per-chunk consecutive-failure count with the server's doubling backoff (serverChunkBuildRetryBackoffTicks, 1s→60s) gating resubmission in the Phase-2 scan; a completed build resets it.
    • Terminal park (park-don't-hammer): after 8 consecutive failures of UNCHANGED inputs the chunk parks on its inputs hash with a logged verdict — rescue re-requests and place-wide dirty sweeps are absorbed instead of hammering the proven-failing build; the park lifts the moment the chunk's inputs actually change.
    • Scene-critical escalation (the #10927 wall pattern): a terminal park on the LOCAL player's support chunk — they're standing in ground this client provably cannot build, sinking into the rescue's containment loop — emits the new terrain-collider-build-parked engine diagnostic (server runtime log + DM + stdout breadcrumb, naming chunk and reason) and raises the player-facing reload wall through the loading-state error channel (one per world). Fires both at park time and when a player later walks into an already-parked chunk.
  • Rescue-remediation attempts survive across episodes (the report's second bug: attempt=1 three times across 3 hours). The remediation entry no longer resets on a firing gap — the rescue pauses when the player leaves, not when the chunk heals — it resets only when the side actually observes the chunk's collider ready (client: collider readiness counts; server: done build + intact collider plane). The #597 honesty bound (escalation after 3 attempts) is now reachable for the episodic failure shape, on both sides symmetrically.
  • Server build path (server-terrain-system.ts) untouched — byte-identical.
  • Coverage, red-first on the pre-fix build (93 submissions in 93 ticks vs 3 with backoff): src/engine/features/terrain/__tests__/client-chunk-build-failure.test.ts (reason logging, stuck reason, backoff cadence 1/32/93, reset-on-success, terminal park + absorb + lift-on-inputs-change, active-chunk escalation + one-shot wall, walk-into-parked-chunk escalation, no escalation for unoccupied parks) and chunk-rescue.test.ts (attempts 1→2→escalate across gapped episodes on both sides; observed-ready resets).
  • layout.maxExtents can no longer die permanently on runtime-spawned GLB children (ledger 1770, P1 — ITERO's 8m-tall zombies / invisible speck enemies with floating HP labels, physics staying correct throughout). Two structural halves:
    • Park without delete. geometry-fit-solve no longer removes GeometryScale when layout + model are present but the model's bounds are unresolved in THIS realm. GeometryScale is a replicated component every realm independently re-derives — deleting on "I don't know yet" turned one cold BoundsRegistry in one realm into a permanent raw-native render. Ignorance now holds any present factor (replicated or previously solved) and parks on the bounds waiter; deletion stays reserved for genuinely-removed fit inputs (layout removed — the interpreter's design-intended strip — or model removed).
    • The wake. The onModelAnalyzed writer (game module serverMount) now lands analyzed bounds through the shared writeBoundsToSpec writer instead of a hand-rolled spec merge. That gains the BoundsRegistry side-write (setBounds → onBoundsReady), so entities parked by geometry-fit-solve re-solve the moment analysis delivers bounds — previously they woke only on the next full applySpec, i.e. possibly never. It also gains the TomeUnfilteredSpecResource merge the hand-rolled path skipped. Still deliberately triangle-less, so bounds-prefetch's completeness gate keeps the glb-bounds derive eligible.
  • Regression coverage, red-first on the pre-fix build: src/game/__tests__/geometry-fit-analyzed-bounds-wake.test.ts (the ITERO shape end to end — production game module, singleplayer client-authority spec via the TomeSpec carrier, behavior objectApi.spawn child with model + layout.maxExtents + scale, cold registry, bounds arriving via the analysis listener only after the entity parks) and src/tome/__tests__/geometry-fit-solve.test.ts (park-holds-replicated-factor, park-then-wake solve, layout/model removal still delete).
  • The overlap/touch pair reads (getOverlapping/getTouching/isTouching) join the server-realm loud-once teach rail (ledger 1763's class-phrase enumeration, completed — the raycast and velocity/impulse families rode it in #10315/#10888). The multiplayer server never mints a physics runtime (client-auth: mantle and rapier runtime creators are client-only systems), so server-run pair reads are structurally dead — empty answers per call, forever, with no replicated mirror to fall back on — while the old warn told a server behavior that reads are "unavailable in run_script; use behavior scripts", a redirect to exactly where it already was. Server-side the warn now names the mechanism and the redirect (read contacts where the entity simulates; replicate results as state/component writes), and one Savi error DM per (place, script) covers the whole three-verb family on its own notifyDmOnce key — a per-tick contact poll teaches exactly once instead of storming the log with zero DMs. The empty degrade, the mantle/rapier answer paths, the rate-limited warn trail, and every other realm's warn text are byte-identical.
  • physics/step joins the tick-budget watchdog — the population melt is no longer silent (ravi's probe 5703dcd7: falling dynamic props cross the half-tick budget between 400–800 bodies, and an 8000-body room ran 2.4Hz with ZERO diagnostics, because the watchdog sampled behavior update() brackets while physics/step is an engine system). The behavior watchdog (behavior-watchdog.ts) gains an engine-system lane watching the whole physics step bracket with the module's own conventions — half-tick budget, BEHAVIOR_BUDGET_STRIKES strictly consecutive separated over-budget ticks, the #443 strike-separation rule, the hidden-tab law — plus a lane-local self-measured stall grace (the behavior lane's inter-pass probe only arms on the authority; multiplayer clients, exactly where physics steps under client-auth, never run it). Sustained over-budget emits the same shape as behavior overruns: a physics-step-budget engine diagnostic naming the mechanism (dynamic-body count, awake count, step ms, tick budget) into the runtime log, one loud-once Savi DM naming the fix (~250 dynamic bodies comfortable, ~400 the ceiling, despawn/pool spawned props, cap spawners — with the #297 consent rule inline, since the named fix reduces live creator content), and a DD-visible server-console breadcrumb (under client-auth the melt costs the browser, not the container — every server-side signal stayed quiet by design). Deliberately diagnostics-only: no parking, no body governor — population is the author's lever and the honest ceiling is device-relative; the trip-time body census is the only added sweep (≤ once per 10s cooldown, never per tick).
  • Fixed pointer-only games permanently losing all mouse input when the input mode strands in "overlay" (ledger 1774, incident 36984a9f "Idle Homestead"): the mode is a kernel-side replica of parent overlay state over a lossy change-only postMessage channel, and the #233 self-heal (overlay-state refresh request + honest fallback to game) was keyed to eaten KEYDOWNS only — a mouse-only game never presses a key, so a stranded mode ate its world clicks forever (only a page refresh recovered, and it re-wedged). The healer's evidence now matches the overlay gate's diet: an eaten world-bound mouse press, touch tap, or frame-forwarded keydown feeds the same re-sync lane. A legitimately open overlay is never fought — the parent's answer to the refresh keeps the mode exactly where the parent says it belongs.
  • noteEatenGameplayKey renamed to noteEatenGameplayInput (it is no longer key-fed only). No wire changes: the fix reuses the existing spawn:kernel:overlay-state-refresh / parent:overlay:state lane.
  • Fixed every world click being wiped before behavior onInput on realm-architecture games (member #2 of the #10902 blur family; field receipts 0/1542 clicks vs 169/169 keys): a world click focuses the full-viewport data-tome-realm frame, the game window fires blur, and raw-capture's window-blur RESET landed in the same drain as the forwarded MOUSE_BUTTON_DOWN — then focusGameplayCanvas bounced focus back so every click re-fired the blur. Blur classification now lives in ONE shared predicate (realm-focus.ts isBlurIntoAuthoredRealm) consulted by both raw-capture and the pointer transport (#10902's private copy migrated); real blurs (other window/tab/devtools) still reset.
  • Eliminated the family at the source: a fall-through pointerdown in a realm frame (target <body>/<html> — the world's press) now preventDefaults, so focus never enters the frame on a world press and no host blur fires. Presses on authored UI keep native focus; the spec keeps click firing after a canceled pointerdown, so the pointer-lock gesture lane is untouched.
  • Fixed right-click over the world opening the browser's native context menu in both normal play and god mode (dump 259821ee): pre-realm the canvas node suppressed contextmenu, but inside the realm frame suppression only lived on the mount containers — fall-through right-clicks hit <body>. One document-level listener per frame now cancels the default; authored contextmenu handlers keep firing.
  • Relay sim tier is now an EXPLICIT host declaration — never an inherited default (ledger 1772, incident de79b468). Since #9786, EngineHostCore defaulted simTier to "relay" and the Bun container host passed no simTier, so the CONTAINER tier — a trusted in-process host that runs creator JS — silently declared "nothing server-side executes" the moment a room upgraded past 5.2.4: crons forwarded to a player seat (whose shared-storage writes refuse with storage_unauthorized), lifecycle hooks skipped hostless, and spec.jobs submissions hung. The default is inverted: an undeclared host is "host-capable" (capability-preserving), and the RoomDO shell — the one host that IS the multiplayer relay tier — declares simTier: "relay" explicitly (SPAWN_SIM_TIER=host-capable stays the ops rollback knob; deployed DO behavior is byte-identical). A version promote can no longer act as a silent capability removal. Pinned by the slim-server manifest suite: an undeclared host registers the full host-capable allowlist, a relay-declaring host registers exactly the relay allowlist.
  • A custom (spec.jobs) job submission on a relay-tier room settles an immediate refusal instead of hanging forever. The relay RPC bridge classified non-builtin job ids as {kind:"pass"}, which fell into the relay egress dead-lane drop — no job.response ever arrived and awaitJob wedged silently. Non-builtin ids now settle ok:false immediately with a message naming the tier and where the job class actually runs ("custom job X cannot run on this room's relay tier — … crons and lifecycle hooks run on the resident host client, and a custom job runs on the client that submits it"), the same failNow shape as the bridge's other honest refusals. Red-first e2e coverage in relay-rpc-bridge.e2e.test.ts.
  • Placement height answers loudly or not at all (ledger 1773; digs 7ed968cf + 3694dc1f, cal's Final Abyss — vgw-35 parked 12.16m under its own ground because a baked Y fossilized a pre-steepening terrain). hasTerrain(placeId) === false now REFUSES: the samplers return null and warn [terrain] no definition installed for place X once per place per session, instead of silently answering 0 + offset. getTerrainDefinition with no placeId refuses instead of answering whichever place was first in the map (a stranger's ground). Every caller was audited to a chosen behavior: placements PARK at the bare offset with their terrain anchor armed, baking paths (spline, scatter, god-mode gestures) read refused ground as the park origin so { terrain: offset } round-trips exactly, and feature paths treat refusal as feature-off.
  • A parked placement re-resolves the moment its place's definition installs. The re-anchor system's watched version is now the ground's inputs — the per-chunk terrain:height field token PLUS a definition install token (terrainDefinitionInstallToken) — so an install by ANY arrival path (spec apply, first-residency expansion, client replication) revisits the parked answer. Before, the gate was the field version alone and an install never moved it: parked meant permanent.
  • One ground truth for the drawn and the placed surface. Heightmap placement sampling (sampleReplicatedTerrainHeight, the y: { terrain: N } pin lane, api.getTerrainHeight) now folds in the same heightmap sculpt edits the chunk builds realize (applyHeightSculptEdits, extracted from jobs/chunk-build.ts into one shared module) — a sculpted world's placements land on the ground players see and collide with, not the pre-sculpt generator surface.
  • The repair anti-pattern, as doctrine: any re-seat that writes an absolute Y is a future sink — the repair primitive is { terrain: N }, which follows every subsequent terrain change for free.
  • Velocity/impulse teach rail completed (ledger 1763, #10888 + #10945): every silent no-op arm in the server-realm contact/motion family now speaks once. setVelocity/applyImpulse on a client-realm body, and the pair-read family (getOverlapping/getTouching) polled from a server-realm manager, each emit one runtime-log diagnostic naming WHY the call can't work in that realm and WHERE to move the code (the shooter's onInput, a client behavior, or an authority-side handler) — instead of returning nothing and letting the script author debug a ghost. Same #597 vocabulary as the collider rails; episode-surviving counters; one report per script per condition.
  • A relay-tier room's forwarded crons and lifecycle hooks can write SHARED storage — the exemption keys on host-forwarded provenance, never on the seat and never on "any client may write shared" (ledger 1772 residual, incident de79b468; composes with #10934's explicit tier declaration). On relay tier, crons/lifecycle hooks execute on the resident place-host client, but that seat's shared-key storage writes refused with storage_unauthorized — authorizeForwardedStorage only knew the player-context law plus the singleplayer engine exception, so the taught persistence patterns (cron autosave to world/state, a departed player's disconnect save to user/<departed>/…) silently saved nothing on the tier that is about to become the prod default. Now the lifecycle/cron execution lane stamps its forwarded job submissions (lifecycleSubmitted — minted from hook-forward's marked ObjectAPI lane state, never from creator args, threaded job-dispatch → forwarding facade → wire), and the server honors the stamp only when its OWN facts corroborate it: effective sim tier is relay AND the sender is a currently named place host in the DO's replicated host table (the same table the fire sites target). A non-host client forging the stamp refuses exactly as before; the host's own unstamped player-context writes refuse exactly as before; singleplayer semantics (deliberate shared-key refusal, dig 93bab0de) are untouched — resolveWorldSimTier never answers relay for SP. Red-first coverage in job-forward.test.ts: resident-host cron CAN write shared while a stamped non-host player CANNOT, cross-player disconnect saves, anonymous hosts, tier/declaration gating, hostless rooms, and the end-to-end stamp mint through the wire parse.
  • Look scripts gain offscreen render targets: ctx.target(name, { scale | width+height, format }) returns a write-only descriptor with .previous (last frame's contents — touching it arms a ping-pong pair) and .size (live vec2, patched per frame). Default format rgba16float; rgba8unorm option.
  • ctx.pass(target, (uv) => colorNode) renders a fullscreen pass into a target and RETURNS the readable texture node (sample/sampleLevel/.size); declaration order IS execution order, and eager graph recording makes read-before-write unrepresentable (a target has no readable surface until a pass hands one back). Passes read ctx.scene/depth/viewZ/params/textures and earlier passes' returned nodes. Passes attach between the composite and look.present at one call site (LookPass.declareScriptPasses).
  • Budget rails with teaching errors naming the cap: 8 targets, 16 passes, 2048px fixed-size cap, scale ≤ 1, 256 MiB total target memory.
  • Closes the documented TODO(lume-postfx-rtt) boundary — multi-pass effects (separable blur chains, feedback trails, radiance-cascade-style accumulation) are now expressible in plain look scripts.
  • GLB parse (ledger 1776, ravi dig b28329ec): a material that authors neither a metallicRoughnessTexture nor a metallicFactor — the raw Meshy 1-texture export that 86% of humanoids ship since the bake removal (#9425/#9440 + #10396) — now defaults metallic 0 / roughness 0.8 instead of the glTF spec's silent metallic=1.0 (near-black in shade under lume; shading.ts's own comment names the hazard). Authored values honored verbatim: an explicit metallicFactor (even 1.0) parses unchanged, mR-map materials keep spec factor-1 (baked assets byte-identical), authored roughnessFactor always wins. Aligns the parse-side default with lume's own DEFAULT_MATERIAL_SOURCE (unauthored = dielectric) — one opinion, one home. Heals the 10 days of already-minted raw assets at load, no backfill. Fixture-tested across 6 material shapes; content-verified on the farmer-woman raw/baked boundary pair. (Re-landed on tiger's KEEP after the before/after pair — #inventors 1785101325.)
  • Fixed feet-anchored placement (feetPosition without y / y: { terrain: N }) and api.getTerrainHeight on voxel terrain: the ground sampler pre-floored x/z to whole meters, so at cellSize < 1 fractional positions sampled the neighboring column and baked its height (objects sank into or floated over slope cells).
  • 2D voxel places: the height sampler now reads the gameplay slice's cell layer (THE SLICE CONTRACT) — an object's own z is cosmetic depth and no longer selects a phantom column. One correction in the sampler, shared by feet anchors and getTerrainHeight (which previously corrected only its own reads).
  • A voxel column with no supporting surface now refuses (null) instead of fabricating verticalRange.min — terrain-relative placements park at their bare offset with the anchor armed and re-seat when ground appears, matching the no-terrain-definition convention (ledger 1773).
  • Voxel reads answer loudly or say they can't (the #10949 loud-or-nothing doctrine, chunk-residency edition; dump 8a692ef1 / anatomy 011ef557: Savi's isVoxelSolid(0,30,0) read un-built rock 90–150 m from the player as air BY DESIGN, she announced "the asteroid never generated" and chased a generator bug that didn't exist). VoxelMaterialLookupResource.getCell now returns the third state "unloaded" when a cell sits inside the loadable chunk band (the streaming lattice enumeration over verticalRange) but has no resident grid AND no edit-overlay entry; outside the band air stays air (no chunk can ever exist there — known by construction). Fresh grids, the edit overlay, and stale-grid reads are untouched.
  • Every consumer audited to a chosen behavior (the type union forces the audit): ObjectAPI probes surface it honestly + one teach-rail warn per (place, script) shared across the whole family (isVoxelSolid → null, getVoxelMaterial → "unloaded", raycastVoxel/queryVoxels/getWaterLevelAt report unconfirmed scans via onUnloadedCell, setVoxelState/interactWithVoxel/breakVoxel refuse); block-hook local reads treat an unloaded neighbor as absent (writes still land through the edit log + overlay); NPC line-of-sight blocks only on CONFIRMED solid; the stuck-body rescue fires only on confirmed solid and never teleports into unconfirmed clearance; block ticks skip unconfirmable cells; the renderer pointer-raycast bridge deliberately keeps pass-through (picks target what renders, client-resident by construction).
  • api.ragdoll(id?, opts?) / api.unragdoll(id?, opts?) (jacob's rq r-5d43631f): a rigged character flips from animation to physics and back — the primitive, not a pipeline; Savi owns the choreography. The skeleton's bones become dynamic mantle capsule bodies (dims from bind-pose bone lengths, sub-scale bones fold into parents, mass by volume share) joined along the skeleton's own hierarchy — hinges derived on straight chains (knees/elbows), balls at branch points (hips/shoulders), no humanoid bone-name table. Initial velocities come from the animation pose delta across the arm tick, so a running character crumples with momentum; opts.impulse adds a hit direction.
  • Joint-limit derivation is ONE swappable module (engine/physics/ragdoll/joint-limits.ts) — Jure holds physics design word on ragdoll constraints, and his steer (signed hinge ranges, cone limits, stiffness ladders) replaces that module's internals without touching the builder, runtime, or wire. Design doc: docs/ragdoll-design.md.
  • Replication rides client-auth semantics: only the machine that owns the entity's physics builds bone bodies; it publishes RagdollPose (motion-class, canonical forEachParsedBone order — no bone names on the wire) plus the entity transform, and every other machine renders the wire pose. The entity's own body/CC handle is suppressed while ragdolled and re-ensures from unchanged components on release.
  • Unragdoll blends: the renderer cross-fades from the last ragdoll pose back to the mixer over blendSeconds (default 0.25 s, smoothstep) — the pose snap is the named failure mode and is pinned red-first (ragdoll-pose.test.ts).
  • Rails per the no-lag law: 12 concurrent ragdolls per place (RAGDOLL_PLACE_CAP, teach-rail refusal naming the consent rule — settled ones count; free one deliberately), and settle-freeze default ON (3 s of stillness converts bone bodies to static — corpses keep collision at ~zero solver cost). The physics watchdog (#10952) keeps watching the melt; the cap keeps ragdolls from causing one.
  • Red-first coverage: bone-body creation from a fixture rig, joint hierarchy matches the skeleton (world anchors coincide < 1 mm at birth — the mantle-joint-feet-anchors lesson pinned at creation), hinge/ball derivation, pose-delta velocity seeding, drop-and-settle simulation, settle-freeze, cap refusal, unragdoll blend continuity.
  • First-person / god-mode local-player hide keeps the player's shadow (jacob's 07-25 report; a95dc46ee9): the hide wrote DrawVisibilityOverride { layerMask: 0 }, which excluded the mesh from the shadow pass as a side effect — the body vanished AND its shadow died. The hide now writes SHADOW_ONLY_LAYER_MASK (bit 31 alone): no camera draws the character (skinned-character tick derives camera-hidden from the camera-mask bits), but it keeps casting, so the first-person player still reads as present on the ground. Root contract pinned by six camera hide asserts (#11014).
  • Skills teach the authored-weapon contract for held/aimed/mounted items — the second pole of verts-in-the-bag (#11000, dig 981498cb; jacob's 07-25 "verts in the bag" ruling reached scenery but every held-item example still routed to generated GLBs). camera-first-person.md §Held-Item Viewmodel now builds the viewmodel from a gen script whose first lines declare the frame (-Z muzzle, +Y up, origin at the grip web — identity rotation aims where the camera looks), exports landmarks as constants (export var SIGHT_LINE), computes the ADS anchor by arithmetic instead of a guessed lerp, and authors hands in the weapon's own frame mounted at identity (they cannot drift off the grip). depthTest: false is qualified to flat/simple viewmodels — it erases self-occlusion inside a detailed mesh. custom-geometry.md routes anything the game must aim, grip, or mount to authored geometry (a generated model is a shape with no contract), and api-reference.md's bone-attachment example marks the held-item rotation as a per-rig knob, not a number to copy.
  • Look-pass reshape (post-land review of #10980, pre-release — no shipped script is affected): ctx.target now returns a write-only descriptor (.previous + .size, no sample surface); the readable texture node is what ctx.pass returns. Pass builders record eagerly in declaration order, so a read that precedes its producing write is unrepresentable — the read-before-write detector and its teaching error are deleted. One narrowed rail survives: a rewrite pass sampling its own target's already-returned readable node (WebGPU-illegal read-while-write) teaches .previous.
  • A stale async pipeline rejection from a superseded look build can no longer park the healthy rebuilt look (built-identity guard on both rejection handlers); a pipeline-failed park now frees all target memory; swapping look scripts recreates same-named targets (history starts from black — param-only rebuilds of the same script keep trail continuity); ctx.target with width but no height (or the mirror) is a teaching error instead of a silent 512×1 texture.
  • skills/looks.md gains a radiance-cascades worked example (disjoint annuli gathered from an emissive key, coarse→fine merge), compiled and lane-pinned by the skill-examples tests alongside trail + separable blur.
  • preview_object renders scripted (kind: "scripted") and custom (kind: "custom") geometry through the engine's own realization path (#11015; wisp-gun dig 981498cb §4 — the booth refused the DEFAULT asset form and the AK wisp burned two failed inspection-camera workarounds). realizeScriptedPrimitiveGeometry is extracted as the pure core of the live spawn derive (compile → seeded ctx → geometry() → UV resolve → createBespokeGeometryValue) — the live lane wraps the same function, behavior byte-identical, and a test pins preview-realized signature === engine-derived signature. The booth renders bespoke pieces on the live slot plan with lane-parity materials (vertex color/PBR/emissive selects, double-sided, translucent slots off the caster draws). Script failures skip with reasons naming the script + error (scripted geometry (scripts/gen/ak.js: radius must be positive)), never a mute skip. Deliberate booth deviations, noted in the reply when hit: no ground sampler (ctx.groundY reads flat ground — the fields: "zero" sibling) and ctx.albedo texture runs render base material (live texture-array residency would make the probe non-deterministic).
  • The authored sign spec is now world state (ledger 1783): a replicated component (tome/sign-spec, replicate aoi) replaces the module-global signSpecCache Map in object-api.ts — the only authored property value that lived outside the world. It now rides the wire, join/reset snapshots, and room-state resume, and dies with its entity (the old Map never evicted on destroy and leaked across rooms sharing an isolate). Consequence fixed: a dotted write (setProperty("sign.text.content", …)) on any isolate that hadn't itself run the sign setter — host takeover, headless room host, room-recycle resume, any fresh client — read null, merged onto {}, and silently rebuilt the whole sign from engine defaults (the 2.4×1.2 board / text maxWidth 2.04 fingerprint).
  • A dotted sign write that finds no sign spec to merge into on an entity that already renders text or a primitive now warns loudly (with the recovery: set the full sign once) before proceeding — silent-rebuild-from-defaults is gone; creation-by-dotted-write on bare entities is unchanged.
  • getProperty("sign") now answers truthfully on every realm the entity replicates to (server reads of client-authored signs returned null before — sign == null was never a valid damage detector).
  • replaceSpecWithStarter gains { wipe: true } — the explicit destroy-everything path through the built-work guard (#11018; holodaek read 83b5ebe3: "slet alt tag mig til grasset" had no honest path — the guard's only offered option, { convert: true }, PRESERVES creator work, the opposite of the ask, so Savi hand-rolled a 7-minute demolition through two schema walls). With wipe the young swap runs unchanged: the starter lands exactly (fresh world, nothing preserved, nothing parked), terrain-edit history demolished on the swap's own rail, undo stacks + session camera overrides cleared, lineage stamped, exactly ONE persistable mutation; the result carries wiped: true. The guard still refuses by DEFAULT and the refusal now names BOTH explicit paths (convert = preserve, wipe = destroy); the option docs and refusal both say wipe fires only on the creator's explicit delete-everything ask, in their words. wipe + convert and wipe + dryRun refuse loudly. Convert mode extracted verbatim to convertSpecToStarter; mutation shape untouched (pinned by test).