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

Engine v5.2.11

July 30, 2026

A patch in the Lume line.

what's new

  • Savi can now measure your world instead of eyeballing it: asking for an object's bounds answers the real loaded size of models (stacking, fitting, and aligning become arithmetic), and while a model is still downloading she gets an honestly-flagged placeholder or a clear "still loading" note instead of a silent nothing — so things land ON tables, not 0.18m into them.
  • Savi can now see your colliders. Ask her to check a collision problem and she can capture the world with the actual physics shapes drawn over it — including invisible walls and floors — instead of doing blind geometry math. The same look tells her whether the live world still matches what was built (a recycled room once silently un-solidified 143 islands; that class is now a one-glance read instead of a 20-minute hand-rolled sweep), and she can frame a shot on an invisible collider directly. If you use the debug inspector (?debug), its Physics tab now shows the same live-vs-spec verdict. Nothing changes in your game: the wireframes never appear on players' screens unless you toggled them on yourself.
  • If the room restarts while you're in god mode, you come back cleanly in play mode — no more being stuck half-in/half-out where you move way too fast, look choppy, or the God Mode button does the opposite of what it says.
  • Savi can no longer accidentally break your controls by deleting god-mode's internal camera entity while cleaning up — the engine refuses and tells her why.
  • Your game's own buttons keep working while you're in god mode. Before, clicking a menu or HUD button you built did nothing until you left god mode — now the click reaches your game's code either way. God-mode flying and editing controls are untouched.
  • A script that probes for an API member that doesn't exist (world.screen && world.screen.width) no longer crashes — the check just comes back false, and a one-time note in the logs names the near-misses. Calling a method that doesn't exist still fails loudly with the full list of what does.
  • Savi can now ask the running game "what object is that?" — by a spot on the screen, a point in the world, or a half-remembered name — and get the exact object ids with their size and place, instead of guessing from her code map. Fewer wrong-object edits when you point at a thing and say "fix that".
  • Characters can now hold a gaze: ik: { head: { target: { yaw: 180 } } } turns a head to face straight behind the body (any angle, any chain — degrees relative to where the body faces) with no bone math and no world point to keep updated. It sticks through world saves, follows the body when it turns, and a pose set before a model finishes loading simply takes effect the moment it lands — no more re-kicking scripts after restarts to straighten tilted heads.
  • Friends joining a multiplayer game no longer break scripted objects for a moment (or for good): a car built by an onSpawn script could arrive "in pieces" — duplicated parts, physics jitter — because the joining player's machine re-ran the build script it had no business running. Now only the machine actually simulating the object runs its scripts, and joiners just see the real, already-built state.
  • Games left running on old engine versions could get "stuck": the room played fine for whoever was already inside, but every new join spun at "Starting game…" forever, and reloading just landed on the same stuck server. The platform now notices a room that keeps refusing to let anyone in, restarts it on the spot, and tells the waiting player honestly that it's retrying — so the next attempt actually gets in.
  • Camera scripts that read a held key (a lean axis, a zoom axis, any inputs.axes entry bound to keys) no longer jitter on high-refresh displays. Holding the key now reads steady at full value every frame; releasing it still reads 0 the moment you let go. Mouse-look feel is untouched.
  • Editing a generator script now updates every object built from it — including objects your scripts spawned at runtime and objects in rooms that restart later. Before, some of those kept geometry from the old version of the script forever, and players who joined after the edit saw nothing at all where they stood.
  • Visual effects left behind by players who disconnected no longer pile up invisibly and slow your game down. One-shot effects (impacts, muzzle flashes, explosions) minted in the last moments before someone leaves used to be stuck forever — eating effect slots and CPU every frame for everyone still playing. The engine now cleans them up automatically, on the same schedule they would have cleaned themselves up. If you wrote a janitor script to sweep these, you can delete it.
  • Sounds that fire on every click or hit — a creature's cry, a UI confirm — used to pile on top of themselves, getting louder and louder until they broke up. You can now say mode: "restart" on playSound and rapid retriggers replace the playing copy instead: one voice, steady loudness, no click. Sounds you don't mark keep layering exactly like before (gunshots still stack — that's their texture).
  • playSound now always hands back a sound id, and stopSound(id) actually stops one-shot sounds too — cutting a long sound short finally works.
  • Occasionally a save would report success but a busy game room kept serving the previous version until the next save happened to land — players saw old content, telemetry reported the old version, and nothing anywhere looked wrong. Rooms now detect that their copy of the game is provably behind a save they were told about and re-fetch until they catch up.
  • Fixed another way a player in a relay room could silently miss your latest save: if the one "new version!" message got lost on the wire, that player kept playing the old world until your NEXT save — with a lone player in the room, nothing would ever correct it. The room now re-states its current version in the background heartbeat (every 2 seconds), so a player who missed the memo catches up within a couple of seconds, automatically.
  • Live edits now always reach everyone in a relay room, even through a brief network or storage hiccup. Before, if a player's client missed the ~3-second window to download your latest save, that player silently stayed on the old version of the game until your NEXT save — you'd see "saved", they'd see the old world, and nothing looked wrong on either side. Now the client keeps trying (backing off politely) until the save actually arrives.
  • Roads are one mark now: give a road a spline of [x, y, z] points and the terrain flattens to your curve, the surface paints along it, and grass and trees keep off it — one source of truth instead of hand-syncing a flatten strip, a paint stripe, and clear-scatter patches that drift apart. Corners round like a real road instead of kinking at every point.
  • When a player's game tab is running an older build than the server (usually right after we ship an update), their view could silently miss or mangle objects with no error anywhere. Now both sides say exactly what happened — and Savi can see it too — with the fix spelled out: that player reloads the page.
  • Fixed the last known way a solo player could silently miss your latest save: in singleplayer, if the one "here's the new version" message from the server got lost, the player kept the old world until your NEXT edit. The room's background heartbeat now tells the game client which saved version (and content) it should have, and a client that provably missed one quietly re-fetches it within seconds — while normal autosaves of the player's own progress stay exactly as quiet as before.
  • Repeating textures on world sprites now behave like you'd expect: set uvRect: { x: 0, y: 0, width: 4, height: 1 } and the art tiles 4× across the sprite — it stays lit under your lights, batches like any other sprite, and clicks/raycasts hit it. Previously asking for a repeat silently turned the sprite into an unlit background plate that followed the camera.
  • Scrolling parallax backgrounds (the camera-attached strip recipe) are unchanged.
  • Multi-part actors (bodies built from parented objects) no longer stay dismembered. If a part ever comes unstuck from its body — the bug where heads and limbs froze in the air while the body walked away — the engine now re-welds it from your game's definition within a second, and reloading or rejoining a room always restores bodies intact.
  • Scrolling a list inside a creator panel or any interactive game UI no longer also zooms the world camera behind it. The panel scrolls; the world holds still. Scrolling over the open world (or plain HUD text) zooms exactly as before.
  • Games whose scripts read lots of objects per tick run noticeably smoother — reading an object's position no longer pays hidden costs for forty properties the script never asked about. On busy worlds this was the difference between a stable tick and visible stutter.
  • If a playSound call carries an option the engine doesn't know (like an invented soundId:), Savi now gets told once — with the real option list — instead of the call silently ignoring it.
  • If stopSound is handed an id that was never a real sound handle, it says so once (stopping a sound that already finished stays quiet — that's normal, not an error).
›technical notes
  • The 5.2.11-cut fps latch is dead before release (#11640). Two freight commits interacted into a silent full-spec fetch live-lock on the sim worker (fetch loop re-entered hot when a plain adopt gated on the per-realm revision counter while the covered-check spoke dbVersion) — 112fps pinned to ~22 after any pointer skew, forever. Non-covering resolves now take the level-triggered miss ladder and kiln saves ahead on dbVersion adopt past snapshot-inherited revision counters; a progress gate makes the hot re-enter unrepresentable.
  • God-mode flight reaches the server (#11630). The god entity's ownership walk attributed it to the creator, so server input dispatch skipped it — the flight existed only in local prediction and exit honestly snapped back. One law at four seams: god-mode entities are server-simulated; the owner's rig sim is presentation-only.
  • Focused text fields stop eating game keys; solo write-fence tells the truth (#11624). Readonly/disabled fields release the keyboard ([data-input-capture='ignore'] outranks editability); the solo-session drop was correct two-writer enforcement wearing a lying receipt — the receipt now teaches, and sustained fencing escalates to a warn.
  • Alt-tab no longer wipes script state (#11622). The projection sweep destroyed verdict inputs mid-iteration (make-before-break re-baseline, one verdict per entity); the suspension-takeover wake handback is wired and pinned.
  • God-mode exit position pins through production netcode (#11626). E2e pins drive the full egress/wire/ingest path in five hide/show permutations.
  • getWorldBoundsBox becomes honest about pending model loads (demand row r-0223b62a, engine-demand wave p-35fbe727 item 2). The bounds read has always measured real resolved geometry (BoundsRegistry: GLB header POSITION min/max, or replicated assets.metadata from a prior parse) — but a model whose bounds hadn't landed answered a bare null, indistinguishable from "nothing drawable", and Savi assembled blind (the 0.18m-eyeballed lantern, a-1389). Now: while measured bounds are unknown, a FULLY declared properties.layout (per axis maxExtents else minExtents; all three axes or nothing — a fabricated axis would be a lie) answers as the placeholder box flagged geometryPending: true, in post-fit meters × authored scale with GeometryScale deliberately skipped (declared extents are already the fit target; a held-stale factor would double-fit). Partial or absent layouts still answer null, now with a loud-once (entity, script) runtime-log teach naming the loading model and both cures. Subtree unions (getWorldBoundsBoxWithChildren, god-mode grips) propagate the flag when any contributor is a placeholder. The read stays pure: no waiters parked, no spec mutation, no components touched (pinned in world-bounds-api.test.ts). Docs regenerate from the source JSDoc; getWorldBoundsBox joins the always-on Properties scent (the invisible-verb law — coverage-test ledger entry 2026-07-30) and a BOUNDS_DRIVEN_ASSEMBLY example rides api-reference.
  • Collider overlay in Savi's eye (gap 2): view_live_scene({ colliders: true, camera|frame }) composites the F3 collider view's exact wireframes into the capture — same sim-side builder (physics/systems/collider-view.ts), same lume overlay-lines geometry, same color code (static green, character cyan, dynamic/kinematic orange, sensors yellow-dashed). A counted debug-state hold (armSceneViewColliderHold) arms the builder for the capture's duration without opening the inspector; hold-only views carry captureOnly and are excluded from the player's live frames — no wireframe flash while Savi looks. The capture appends collider draws after scene draws (always-on-top) and reports colliderBodies, so a still-arming overlay captions an honest zero instead of "no colliders exist". Composes with burst. The plain viewport form refuses the flag loudly (the player's literal screen never wears wireframes).
  • Live-vs-spec drift read (gap 3): new tome/live-spec-drift.ts owns the comparison Savi hand-rolled as a 314-object sweep while a recycled room kept wiping physics off the same 143 objects (dig 2bdedc41). Bounded to where it is structurally true: objects whose spec DECLARES physics; body-type equivalence through the spec's own words (character/vehicle compare presence only); resting feetPosition only for live-static bodies with plain-number authored positions (1mm epsilon); spec objects with no live entity count missing, never guessed at. Exposed on the debug surface family: the colliders: true capture result carries the report (over the physics.liveSpecDrift debug request — the include-only/never-arm rail), and the physics inspector panel appends the one-line verdict (2s-cached compute; also reachable via __spawnCollectDebugConsole panel pulls with the inspector closed). Zero cost while no debug surface asks.
  • Frame-index collider lane + runtime-spawn coverage (gap 4): frame-mode object resolution (scene-view-bounds.ts) gains a collider-outline lane — own visual → parent-linked subtree → collider outline (armed overlays only) → honest miss — so an object that renders NOTHING (visible: false collider treads, invisible walls) frames by its collider shape when colliders: true is on. Runtime-spawned bodies ride every lane identically to authored ones by construction (the builder reads PhysicsBodyConfig off the live world, never the spec) — pinned by tests, alongside a pin that the original row shape (frame-index NOT-FOUND for rendered runtime spawns) stays dead (#10828 killed it).
  • God-mode half-session no longer survives a reconnect (ledger 1886, dump 2f28abea). A room restart mid-god-session left the client holding replicated session state while the fresh room joined the player in play mode — the join reconcile reaps god wreckage server-side only (tome/lifecycle.ts), and reapGodModeWreckage had no client-realm caller. Now noteProjectionReset (the client's reset-boundary reconcile, beside the speculative-control/sim-lease/travel-cover flushes) reaps any god session whose god entity the reset snapshot did not re-show, through the same one teardown every other exit path uses (tome/god-mode/session.ts). A live session the reset re-shows (suspend-resume wake, mid-session recovery resets) is untouched; relay-wire worlds never reap (the local world IS the god-mode authority there).
  • The raw-flip toggle is dead on the wire (the structural leg). dispatchGodModeToggle shipped god-mode.toggle with no {desired}, so a desynced exit INVERTED into an enter — enfeul's specimen: the first-entry DM re-fired in the fresh room and the server parked his player at the god pose (a 300 m owner split, god flight speeds in normal play). The sender knows which way it means the flip; now every shipped toggle stamps desired (the negation of the client's own replicated view) and the server's existing set semantics absorb a desync as a no-op. Old clients' bare frames still flip (decode-compat) and the dispatch log line carries legacyRawFlip: true so the raw-flip population is watchable. Local-authority dispatch (singleplayer, relay wire) keeps the bare flip — sender and authority are the same world.
  • api.destroy('god/player/…') refuses with the teaching (the pre-#386 wreckage class). Savi's live repair destroyed the engine god entity from game code, out from under the session — dangling control-mapping halves, the exact class the one teardown exists to prevent. ObjectAPI destroy() now refuses the engine god namespace and any entity a live session claims, before any rail forward or local despawn; the error text teaches that sessions end through the engine's own exits and leftovers reap automatically. Non-god destroys, zero-match honesty (ledger 1247), and the server-side reconcile are all untouched (pinned).
  • God mode no longer eats the game's own UI buttons (ravi's badgerblunts read cec6b199 §F′, request r-f670d207). The control-target resolution in tome/input-applier.ts routes the whole input frame to the god rig while god mode holds TomeControlTarget, so a creator's authored sendAction('join') button was silently dead exactly when they were most likely to be testing. The swallow was incidental, not designed — nothing suspended authored actions on purpose. Now sendAction rides the wire object-shaped whether or not it carries a payload (input-resolution.ts; hardware binding pulses stay plain strings, so the shape IS the origin bit), and the applier fans the sendAction plane — object-shaped, non-god:* actions only — to the god-moded creator's parked body with empty axes. Hardware stays the god rig's (Space flies the camera, the parked avatar never jumps), god:* chrome stays the god rig's, vehicle/turret control transfers keep the documented "handle it there" routing, and the primary god dispatch is byte-identical to before.
  • Unknown-API teaching: reads never fault, only invocations do (badgerblunts §6, request r-eb34af9f). The teacher's throw-on-property-read semantics detonated the universal JS existence probe — world && world.screen && world.screen.width threw every render frame from inside the && guard, and a taught try/catch swallowed it into a 46-minute invisible-UI incident. Now a read THROUGH a teaching thrower (api.screen.width) returns undefined and delivers the full teaching message once per name via console.warn (getLogs / ui.log), and a namespace miss (music.fadeOut) answers with a callable thrower instead of throwing at read time. Phantom CALLS are unchanged and stay the loud teaching channel: api.updateMaterials(...), music.pause() throw the same teaching TypeError text as before (ledger #699 / ledger-805 messages preserved verbatim). Named residual: a bare if (api.foo) probe still reads truthy (the documented tombstone tradeoff), and world still has no real viewport surface — the world.screen reach itself is an API gap left open deliberately (new surface gates on vocabulary review).
  • The identify verb — savi's "what object is this?" query (r-8c412240). Third eye of the inspection family: identify_object maps a screen position (0..1 image fractions through the live camera), a world point (nearest-by-bounds, 0 = inside), a world ray (first visual hit per entity, terrain included), or a name (case-insensitive id match: exact > prefix > substring) onto object ids with kind + world bounds + position + parent link. Resolution is renderer-side over the live lume stores (renderer/scene-identify.ts, riding lumeSceneRaycast and the scene-view bounds vocabulary), so matches are RENDER truth — colliderless spec dressing matches (the spawn-pusher verdict's six-wrong-objects case), and by name a live-but-unrendered record answers honestly with bounds: null. Every lane's ordering is deterministic and pinned (__tests__/scene-identify.test.ts); a miss is an honest empty, never an error. Wire: one new identify renderer-worker message + the identify_object client RPC (spawn.ts dispatcher, _entry/client/scene-identify.ts validation); chat-side the tool enriches matches with authored-spec facts (place, one-word visual summary, tags, behavior, parent — authored: null = runtime-spawned) and translates the pre-verb engine's "Unknown client RPC method" into the engine-version fact. No spec words, no ObjectAPI surface — a read-only query lane.
  • Head-pose/gaze primitive: direction-form IK targets (r-29637275). { yaw, pitch? } in degrees resolves at the sim plane from replicated entity state alone (feet + rotation → a far aim point at 1000 m along the body-relative direction) — deterministic across realms, follows the body as it turns, needs no bone reads at authoring time, so nothing exists to misread before the model loads (the class savi's savi-gaze-steer.js/savi-behavior-kick.js kick-scripts hand-rolled). Direction-authored channels carry wide look clamps through DrawIK (maxYaw 179°, maxPitch 89° — a stated pose is honored; the exact antipode is excluded because a 180° swing has no unique axis), consumed only by the head solve; point/object/script channels carry no clamp fields and stay byte-identical (pinned). updateIK/properties.ik/getIK all speak the form; getIK echoes the authored direction: { yaw, pitch } alongside the resolved point.
  • Pose/IK application is model-load-order independent (r-29637275's second demand). draw/ik and draw/mixer are change-driven renderer forwards; a value arriving while the entity had no model visual was dropped by the store and never re-sent, so a pose set before the model property landed was permanently lost when the visual was created later. The models store now holds the latest ik/mixer per entity (the shadowIntents pattern) and seeds visuals at creation; a model remove + re-add re-dresses from the hold (the components still live entity-side), and DESPAWN disposes it. IK/mixer arriving while the asset is still loading was already held on the visual and is unchanged.
  • Joining clients no longer replay hosted onSpawn (ledger 1867, tucker's sev — the car-in-pieces join): kernel-tier clients are spec-built + delta-corrected, and spawnObject ran the composed onSpawn with zero authority consultation on whatever client materialized a spec object locally first. At join, the global-AOI spec can land before an entity's AOI-scoped replicated CREATE row, so the joining OBSERVER built race-car from def bytes and replayed the host's onSpawn — deterministic-id duplicate children, local physics/pose stomps, a locally-diverged pose the host's fan-out then fights forever. The replay fired even with the host table present and naming another client: the spec-build lane never consulted it.
  • The fix is one predicate at two lines, not a new mechanism: spawnObject and teardownAndRerunOnSpawn (the script-edit rebind sweep, which re-ran onSpawn ungated on every client per revision — same class) now skip execution when isClientAuthObserverEntity — the existing classification behavior-update/interaction-dispatch already gate on, fail-closed pre-table (absent host table → not simulated here → skip). The entity still materializes everywhere (spec-built warm start; late-join visuals keep working) and the simulating side's fan-out carries the authoritative state. Observers skip the rebind teardown too: their owned-children rows are replicated entities whose destruction (and onDestroy hooks) belongs to the simulator; compile-registry updates stay unconditional, so an observer's compiled spec stays fresh for a later host adoption.
  • Genesis execution is unchanged on every authority: server worlds keep running onSpawn at spec apply — including RELAY-TIER server worlds, whose applySpec deliberately compiles and runs object behaviors as the world-structure generator class (sim-tier.ts option-(b) carve-out), so no deferred re-delivery machinery is needed; singleplayer clients keep running everything (the local client owns everything); the named place-host client keeps its runs.
  • Pins (join-hosted-onspawn-replay.test.ts — the 5 core pins dug and red-tested by the pin-1867 dispatch, report cfd3e5a1, adopted with the fix): observer + absent table and observer + other-client table execute nothing (formerly red), host client / server world / singleplayer keep executing (controls); plus relay-tier server genesis, and the rebind-sweep sibling lane (observer skips the revision re-run; host client and server keep it).
  • Join-liveness watchdog (ledger 1888, the warm-room join-starvation class): a room on the old 4.2.0 pin admitted only bootstrap-time sockets and silently starved 13/13 later joins (~25–35s each, zero egress) across two multi-minute windows — every reload landed on the same wedged-warm worker, and the idle reap was the accidental cure. The shell now treats starvation as evidence: JOIN_STARVATION_RECYCLE_THRESHOLD (3) CONSECUTIVE starved joins (each one a full 45s join-honesty deadline of proven zero egress) while the room holds ZERO admitted players recycles the isolate NOW through recoverFromWedge — the same teardown the tick deadline uses, escalation ladder to the terminal park included. Conservative by construction: one admission anywhere resets the streak (SocketGateway.onFirstFed), a room with live admitted players never recycles on a newcomer's starvation (hasFedConnection gate — that starvation stays log-only, stamped roomHoldsAdmittedPlayers/starvationStreak on the room-do.join_starved row), a slow-but-alive boot feeds its first socket inside the deadline and never strikes, and every recycle resets the streak so a wedged replacement must re-earn all N strikes.
  • room.join_starved finally sent (reserved since ledger 1460, named by 1695(b)): fireJoinStarvation sends the verdict frame immediately before the retryable close, so the client can say "room stuck, retrying" instead of an eternal "Starting game…". The reservation's hazard — a pre-close frame on a RETRYABLE close resets the client's first-message authenticated latch and reconnect budget every cycle, so a permanently starved room loops forever — is discharged by byte identity, the LIVENESS_BEAT_FRAME pattern: the message is now field-less so the whole frame is one constant (JOIN_STARVED_FRAME) the client latch exempts before parsing. Past the latch it parses as a normal control message; the runtime worker posts the honest "The game server is stuck letting players in — retrying…" loading state (a parsed server verdict outranks the boot-flap silence gate), cleared by the boot's real Ready. Client and server ship in the same engine bundle, so the sender and the exemption always travel together.
  • Pins (room-do-join-starvation.test.ts, room-runtime-resilience.test.ts, connection-presentation.test.ts): the 1888 shape recycles at N and the fresh isolate admits again; live-resident rooms never recycle; an admission between starvations breaks the streak; healthy churn accumulates nothing; the verdict frame reaches the same bounded reconnect terminal as the no-frame arms (budgets never re-armed) while still parsing into ingress; the starved verdict posts pre-Ready and clears at Ready.
  • Held level axes no longer fabricate a release on frame-less render passes (ledger 1879). runFrame's steps==0 branch runs renderPrep alone at display rate between fixed 60Hz sim ticks; camera-behavior calls onInput unconditionally with framesSince() empty, mergeCameraAxes([]) produced {}, and the input Proxy minted 0 for every spec-defined-but-absent axis. Any custom camera reading a held key-bound axis on a display outrunning the tick rate saw the axis oscillate commanded/0 at the render/sim beat — tiger's lean camera equilibrated at ~half depth with a 60Hz roll shiver, and his script-side guard was defeated because the fabricated 0 is indistinguishable from a real one. Fix: merged level axes persist in camera modeState exactly like heldInputActions and are served on frames.length === 0 passes; the snapshot is replaced (never merged) on every real pass, so explicit releases (the explicit-zero law: key-bound axes ride every frame as ±1/0) and quiet analog sources clear it naturally. Delta axes are excluded from the snapshot — absence of a frame genuinely means no new motion for them, so the proxy's 0 stays correct. The law: absence of a new input frame reads as the last-sampled level value, never 0 (memory/shared/absence-is-never-a-value.md).
  • Scripted-primitive re-mint is now LEVEL-triggered, killing the stranded-mint class (ledger 1880; dig 33af4d53; closes #1069's admitted residual). #8455's re-mint lane was edge-triggered (fires only on the one applySpec whose detectChangedScripts is non-empty), def-driven (iterates spec defs only), and single-world — while recipe rows are level state. Three lanes carried stale rows past it forever, each a permanent scriptContentSkew derive-mismatch stranding post-edit joiners on terminally invisible geometry (savi's observed split on Pixel Online c37f7ea3: mint-era clients see stale-but-visible meshes, everyone else sees nothing): (a) def-less runtime spawns (api.spawn / children(ctx)) unreachable by the def loop; (b) worlds booting INTO the post-edit spec (detectChangedScripts(undefined, …) is empty by design); (c) TomeScriptedPrimitive is clientAuthWrite:"owned", so a stale backgrounded owner re-uploaded pre-edit recipe+pointer rows over the server's re-mint. The fix is one enforcement funnel (tome/scripted-primitive-remint.ts) for the invariant recipe.scriptHash == buildGeneratorScriptContentHash(currentScripts, recipe.script): applySpec sweeps entitiesWith(TomeScriptedPrimitive) when the scripts identity moved and on a world's first apply; the isolate-swap restore lane sweeps after overlaying captured rows; the upload drain re-judges every adopted recipe row (stale hash ⇒ re-mint from the adopted script/params/seed — the owner's intent survives, the stale mint does not; the deliberate single-writer exception, since the scripts registry is level state only the authority holds current). Cost: one memoized hash compare per row, only on script-move/first applies and recipe-row ingest. The ground-redrape system's local re-mint funnel deduped onto the shared one. warnDeriveMismatch's cause-1 prognosis ("converges when the server re-mints") was provably false for def-less/LWW-stranded rows — the sweep makes it true, and the text now names the mechanism.
  • Orphaned one-shot fx are reaped by the room authority (5.0 debug day #69 class; DUST LINE's measured instance: 15 anchors from two departed sessions pinning ~70 of 256 fx slots). fx-reap ran only on an entity's CURRENT simulator, and a fully departed session's fx has none: the leave path's raw avatar despawn breaks the fx's TomeSpawnedBy chain, the server's owner walk reads null → yields it as place remainder, while every host's walk reads the broken chain as dangling → maybe-owned, never adoptable. No peer's predicate ever claimed it again, so the anchors pinned fx slots forever — a creator hand-wrote an allowlisted janitor script as the backstop.
  • The fix is one deterministic gate, not a scan heuristic: on the client-auth SERVER world (full world knowledge, no AOI — a broken link there is definitive), a runtime fx whose replicated-owner chain is BROKEN (isReplicatedOwnerChainBroken, the same single walk body the host predicates read) joins the ordinary track → derive-completion → reap pipeline with the same fresh conservative clock any successor simulator starts, and counts toward the same runaway cap. No client can be simulating a broken-chain entity (owners walk the same chain and lose it; hosts refuse dangling), so adoption can never cross another writer.
  • Deliberately NOT reap-on-disconnect (engine-principles test 8): departure changes WHO reaps this one engine-owned lifecycle class — self-reaping one-shots with no behavior and no spec entry, whose lifetime fx-reap already owns — never whether or when it would have reaped. Looping/scripted/spec-backed orphans are untouched (the cap remains their backstop); general departed-ownership reversion (ledger 671 / #7414) stays separate work.
  • Pins (fx-reap.test.ts orphan-adoption suite, red-first): a departed client's finite one-shot reaps on the authority at completion + grace from adoption (formerly leaked forever — the red run pinned it surviving 10k+ ticks); a CONNECTED owner's identical fx is never adopted however long it lives; an adopted LOOPING orphan is never lifetime-reaped; a connected client's own fx still reaps on its own simulator; the existing clean-unowned place-remainder pin (host reaps, server abstains) stays green.
  • playSound(clip, { mode: "restart" }) — the Pokemon-cry law (ledger 1705, dig 0f49d653). A new play of the same clip from the same emitter REPLACES the still-playing copy instead of stacking: the client sweep despawns the emitter's live same-clip one-shot voices before minting the new one, and the teardown rides the same cull fade the governor's steal uses (VOICE_CULL_FADE_SEC, 50ms) under the fresh attack transient — no click. The sweep runs AFTER the per-clip governor so a within-floor (~30ms) retrigger drops the new play and the standing voice keeps ringing; replace-then-drop would go silent. Scoped to (emitter, clip): other emitters' voices and the emitter's other clips are untouched. Anything other than "restart" — including the explicit default "overlap" — lowers to the absent wire field. Loops ignore mode (a loop is a keyed channel; there is nothing to replace). Previously this was inexpressible in ObjectAPI (~3 lines in raw WebAudio): repeats summed toward ~8× loudness and then the 8-cap steal-oldest broke sustained triggers — "louder and louder until it breaks".
  • One-shots are stoppable (ledger 1706). playSound minted an id only for loops or onSoundEnd behaviors and returned "" otherwise, and the client stopSound consulted the loop registry only — a one-shot voice was unstoppable by construction, so every script clamp built on stopSound(id) was dead code that reported success (the enfeul specimen published on one). Now every play mints the deterministic handle (unconditional mint = TomeIdSeq lockstep by construction on server and predicted client), the id rides the wire, the one-shot registry records soundId + source entity per voice, and the client stopSound falls through from the loop registry to the one-shot registry. Unknown ids stay a no-op.
  • Pooled-id recycle hazard closed for the new teardown lanes. The restart sweep and one-shot stop despawn voices whose renderer teardown is still in flight, keyed by entity id — releasing the pooled id there recycles it straight into the next minted voice, and the in-flight completion would reap the fresh voice (reproduced in the red run: the replacement voice died to its predecessor's completion). Those lanes hold the id out of the pool; the completion drain (cleanupCompletedSoundEntities) returns it once the teardown reports, and a missed completion falls back to minting, never to a collision (the pool's own law).
  • Poke convergence — the stale-latest-read strand closed (spec-delta continuity F2, design 67870fb4 §3; the 1537 family's server face). Kiln's save fanout pokes /rooms/{id}/update with the dbVersion it just committed; the room answers by fetching kiln LATEST and applying behind monotonic gates. Nothing compared the FETCHED version against the POKED one — so a latest read that lost a race with the commit (replica lag) skipped-or-applied old content and returned 200 while the room silently stayed behind until the next unrelated save: a dead write with a receipt, structurally invisible to the spec-skew heartbeat because the server head and every connected client AGREE on the stale head.
  • The rule: fetched < poked is a provably stale read, never truth — kiln commits before the fanout ever fires, and dev latest is append-only (revert CAS-publishes a NEW head). applyTomeSpec now tracks the highest poke-named version as a convergence floor and re-arms the existing frozen-stamp self-fetch lane (the same bounded-backoff fetch a self-authored gap uses, ledger #352) each stale round, so the 200 means "applied or converging", never "read whatever was lying around". The apply itself is untouched: the fetched content still flows through the ledger 1268/#352 echo-dedupe and stale/self-echo gates byte-identically; the floor only decides whether a follow-up fetch is owed.
  • Bounded stand-down, loud: on the live lane a rollback publish can legitimately move latest backward (game_publishes resolves by published_at, not by version), so a claim that never converges stands down with a warn after 8 stale rounds (~90s of 1s→30s backoff) instead of retrying forever. updateSlug-pinned rooms never track — their fetch pins a published version by design, so "latest < poked" there is the pin working.
  • Plumbing (the design's verify-first item, confirmed): the DO/container shell passed the poke's dbVersion into ApplySpecRequest all along, but EngineHostCore.applySpec dropped it before applyTomeSpec on the ambient tier. It now rides into ApplyTomeSpecOptions.dbVersion. On loopback (workerd DO) hosts the primed spec already carries this exact version, so the comparison is a tautology there — the shell-side fetch's own stale-read exposure on that tier (dispatchUpdate stamps payload.dbVersion ?? fetched.version) is a named sibling seam, not covered by this change.
  • Relay-tier head beacon (spec-delta continuity F3, design 67870fb4 §3 — ledger 987's invariant extended to the relay tier, ends-only). On the relay tier a save rides as one edge-triggered SPEC frame; if that one delivery died (a swallowed fanout send, a seq hole handleSequenced consumes silently, a single-occupant room with no peer to hear anything from), the client stayed one version behind until the next unrelated save — F1 (#11585) chases a fetch it KNOWS about forever, but nothing re-stated a head the client never heard. Now the relay re-states its own durable meta on every heartbeat echo: relay-do.ts stamps journal.getCheckpoint().pointer onto the echo (zero payload parsing, zero decisions, one field — the dumb-pipe law holds; a never-checkpointed room echoes the exact pre-F3 shape). Client-side, the session surfaces the pointer verbatim (onHeartbeatPointer, wire-layer only) and the join coordinator parses it with the existing parseRelaySpecPointer, compares the named dbVersion to the adopted head, and — only when behind, only once the world is baselined — arms the SAME F1 fetch loop a sequenced SPEC frame would (armSpecClaim, the one shared door; never a parallel retry system). Because savi's spec lane checkpoints on every save, the pointer is fresh-versioned on exactly the live-edit rooms the strand class lives in: a dead SPEC frame now decays into ≤ one 2s beat + one bounded fetch of staleness. The host-tick peer-gossip hardening the design names stays deferred (not load-bearing).
  • Inert by construction everywhere else (never worse than today): absent pointers (old relays, never-checkpointed rooms), latest pointers (dbVersion-free by design), unparseable/legacy pointer shapes, versions at-or-behind the adopted head, and mid-bootstrap arrivals (the join machinery owns convergence until baseline) all do exactly nothing. Repeated beats during an armed backoff hold F1's ladder floor — a dead edge sees the 48s-capped cadence, never a 2s hammer — and a newer SPEC frame supersedes a beacon-armed claim instantly, while an older beacon never downgrades a pending claim. A wrong-typed pointer decodes as absent (lenient like welcome.connected): the beacon is advisory and the heartbeat is the liveness signal, so a malformed rider never condemns a socket.
  • Pins (red-first — on the pre-F3 code the wire decoder drops the field, so the core test died with the head stuck at 9): relay-join.test.ts F3 describe drives every echo through the REAL wire decoder — behind-by-one arms the loop and adopts within the beat, loud once per armed claim; the empty-world single-occupant fresh room adopts the first save named by a beacon (head null counts as behind a concrete version); controls pin pointer-less/latest/at-head/behind-head/unparseable beacons byte-identical on the event stream. relay-wire-session.test.ts pins the additive decode + the hook surface with bookkeeping identical for bare and stamped echoes. heartbeat-head-beacon.workerd-test.ts pins the real DO: bare pre-F3 echo before any checkpoint, the accepted marker's pointer re-stated verbatim to EVERY socket (checkpointer or not), and the beacon tracking checkpoint advances.
  • A known spec hole is never abandoned (design 67870fb4 §3 F1 — the nexuiz strand's codified door). On the relay tier a save rides as a sequenced SPEC frame naming a dbVersion; the client fetches that version via the edge. The fetch ladder was 3 attempts with 1s/2s backoff, then a permanent give-up — "the world stays on its current spec until the next save or rejoin" — so a ~3s edge/store hiccup left an ACTIVE client one version behind indefinitely (7 minutes in the field, healed only by the next unrelated save). Worse, a positive-204 gave up after ONE attempt: the store's "that version does not exist" was trusted over the durable sequenced claim that it does — the frame is proof-of-existence (the writer saved, acked, checkpointed); the store read is the lagging party. relay-join.ts now chases the hole with the same withheld-content shape the kernel tier ships (tome/spec-resync.ts — doubling backoff to a 48s cap, forever, loud at each miss), 204s included. pendingSpecUpdate stays non-null through the flight as the loop's liveness truth, so every async resume point re-validates against live state: a newer version superseding cancels the armed wait immediately (a fresh save never queues behind a dead version's 48s timer — host.schedule has no cancel, so a bumped token deadens the old timer), the room reset kills the loop, and the loop survives socket bounces (the hole belongs to the room's spec lineage; a resume re-delivers nothing whose seq was already seen, so the loop is the only healer there). One pending fetch at a time, exactly as before. The one remaining genuine give-up: a realm with no ctx/variant coordinates structurally cannot fetch (unchanged, warns once).
  • Pins (relay-join.test.ts, red-first — the red run died at 3 attempts / 1 attempt for 204 verbatim): a fault retries past 3 on the doubling ladder 1s → 2s → 4s → 8s → 16s → 32s → 48s (cap) and adopts when the fetch lands, loud at every miss, never a failJoin; a 204 for a frame-named version retries the same way and adopts when the store catches up (join-time 204 semantics untouched — pointer/fresh-room pins all green); a newer version cancels the older armed wait and fetches NOW, dead timer inert; the loop heals across a socket bounce; happy path, burst coalescing, replace-at-head triad, and replay coverage byte-identical (39 pre-existing tests unchanged and green).
  • Flatten-along-spline: kind: "road" marks carry a spline and project the whole road footprint from that one curve (r-7e87c657, ledger 1148's shape). spline: [[x, y, z], …] control points resolve through a new 3D centripetal Catmull-Rom sampler (sampleCentripetalSpline3D, the 2D river sampler's sibling — interpolating, cusp-free, curvature-adaptive, the same law #10898's drape crests ride) into one dense centerline + height profile per mark, and every consumer measures against it: terrain FLATTENS to the curve's authored Y profile across the corridor (width, then a falloff shoulder blending back to natural ground — smoothstep, default max(2, width/2)); material/edgeMaterial PAINT the same corridor (unchanged road edge-band law, now curve-following); and object scatter CLEARS off corridor+shoulder (clearScatter: false opts out — honored by legacy roads too). Roads apply after the water kinds in mark order, so the authored curve is authoritative where it crosses a carve (causeway, not trench). Legacy points roads: never move terrain, pinned paint weights, no behavior change (controls in marks-road-spline.test.ts); path/area flatten marks resolve byte-identically (shared profile sampler refactor is arithmetic-identical). Schema: TerrainRoadMarkSchema gains spline/falloff/clearScatter, points+material go optional; api-reference/heightmap-terrain docs regenerated from the schema.
  • Schema-hash connect-time comparison — the registry-skew ghost class closed (#11216 §schema; request wf-a43d8b69). A client connecting with a stale/skewed component-schema registry used to read as a GHOST: entities exist server-side but deserialize wrong/empty client-side (unregistered names drop row-by-row at the shared apply core; changed encoders read garbage), and #11216's dig verified no client↔server schema comparison existed anywhere on the join path. Now the client declares its registry's content hash (ComponentRegistry.schemaHash() — already sorted by name, replicated components only, wire-contract fields only) as ?schemaHash= on the connect URL at every dial (the hostEligible pattern: URL → SocketAttachment → connection.attach, surviving hibernation restore and isolate-swap reattach), and the server's netcode attach compares it against its live registry.
  • Mismatch is a NAMED outcome on both sides, accept-with-warning: server netcode.schema_skew warn log (roomId/clientId/both hashes, deduped per socket + hash pair so restores/swap reattaches don't spam while every real reconnect re-fires) + a schema.skew control frame to the client, which console-warns both hashes with the reload recovery and reports the allowlisted netcode-schema-skew engine-diagnostic — so the fact lands in Savi's getLogs (log-only, no DM: skewed joins during deploy windows are planned churn, the renderer-degraded-adapter #8779 lesson). The behavior ladder's shipped rung is accept-with-warning because the hash is opaque content evidence: it proves the replicated wire contract differs but carries no direction (which side is older) and no compatibility grade, so a hard refuse would kick sessions a benign additive skew leaves playable AND reload-loop the server-is-stale direction (a reload fetches CURRENT bundles, which re-skew against a stale long-lived room). The one direction a reload provably heals — client-stale COLD boots — already hard-refuses with reload copy at cf-edge's stale-hash 409 (ROOM_ENGINE_STALE_CONDITION).
  • Named residuals: a pre-mount first dial carries no declaration (the registry doesn't exist yet client-side; every post-mount reconnect declares — and the deploy-window skew class reconnects by definition); relay tier is registry-less (peer↔peer skew is a different seam); post-attach server hot-reload skew is compared at the next (re)attach, not mid-session.
  • Singleplayer dbVersion-skew watchdog (spec-delta continuity F4, design 67870fb4 §3 — the K2 strand, last leg of the program). The ledger-987 skew lane is deliberately inert in singleplayer: revision counters are minted independently per realm (ledger #718), so getLocalSpecHead answers null there and a dead control-lane spec push — a Savi save the room applied whose tome.spec.push never reached the authority — had NO detector (the withheld-content rail, #7613/ledger 1825, only fires for pushes that arrive and are gated). The heartbeat already reaches suspended singleplayer connections; it now carries enough to audit: noteSingleplayerSpecSkew (room-runtime.ts) compares the beat's claim on the two axes both realms share — kiln dbVersion and structural content hash — and arms ONLY when the server is strictly ahead on dbVersion WITH mismatched content. Sustained past the same 10s window, the episode is handed to the EXISTING withheld-content resync backoff (requestWithheldContentSpecResync — its 48s-capped doubling floor, server-side kiln-lineage adjudication, and #718 admission gates all unchanged; spec-resync.ts is untouched by this PR), never a projection reset — the singleplayer projection is suspended by design. The answer adopts through the ordinary dbVersion gate and the strand decays into ≤ one beat + one bounded fetch.
  • Why the compare is dbVersion AND hash, not dbVersion alone (the 987/#718 inertness audit this leg was budgeted for): dbVersion-behind with EQUAL content is the healthy steady state of every singleplayer session — the room persists this client's own forwarded writes and stamps the new version with no push (noteAuthoredDbVersion), so a bare dbVersion compare would fire on every idle world after every autosave, turning the persist cadence into a permanent kiln-consult + full-doc-fetch cadence. Hash parity names that state silent. The armed clock carries the 987 armed-evidence discipline transposed onto dbVersion: delivery proof is the ARMED version landing (busy worlds — perpetually one persist behind, adopts jumping to emit-time heads — clear within a beat and never sustain), evaluated only at post-apply instants (thaw bursts acquit first).
  • Inert by construction on every case the current inertness deliberately protects: multiplayer worlds — including Suspended hidden tabs whose withheld deltas are healthy — report a null singleplayer head (readSingleplayerSpecSkewHead is getLocalSpecHead's exact mirror; the 987/F3 lanes own those tiers and their code paths are untouched); revision divergence of any size or direction never consults this lane; content mismatch AT the dbVersion tie stays the withheld DROP rail's jurisdiction (its pushes arrive — arrival is its trigger); mid-bootstrap (no world, no baseline, no dbVersion stamp) and old servers (no beat head, no dbVersion, no hash) are unknowable and never skew; socket death discards the clock; relay-tier singleplayer never records a kernel beat head at all (F3's pointer beacon owns that tier).
  • Pins (red-first — on pre-F4 code the lane is inert, so the strand test dies with zero fires): room-runtime.test.ts F4 describe — the K2 strand arms/sustains/fires/converges; controls pin the self-persist stamp lag (60s behind-with-equal-content, silent), revision divergence at the tie (silent), mid-bootstrap and protocol-additivity disarms, the busy-world armed-delivery ladder (60s, zero fires), thaw acquittal, socket-scoped evidence, and the multiplayer lane firing its 987 heal with both hooks installed while this lane stays dark. runtime-worker.spec-push.test.ts pins the head reader's null cases; netcode-upload-echo.test.ts pins the beat carrying {revision, dbVersion, hash}.
  • Wrap/repeat decoupled from plate-class (r-1f85d7ec, plan p-35fbe727 item 3). repeatX === true reclassified a world sprite to plate-class (sprite-appearance-rules.ts + the lume copy in sprite-logic.ts), so asking for a repeating texture silently bought unlit + unbatched + camera U-recenter. Both classifiers stop consulting repeatX; plate-class remains parallax + the moodboard plate grammar (backdrop-*/strip-*/floor-*). Aligned with #11486's move one surface up: wrap is sampler address state, carried by the texture lease, never a class.
  • spriteWrapModes(sprite) is the one wrap rule (sprite-logic.ts): U wraps for repeatX or an authored uvRect overflowing U; V wraps for an authored uvRect overflowing V; everything else clamps. Three consumers, by construction in agreement: the texture lease options (spriteTextureOptions), the batch key (a |wrap: segment appended only when wrapping — clamp keys stay byte-identical), and the texel-AA snap exemption (the AA sampler is linear-clamp; substituting it would stomp tiling).
  • The resurrected three-era strip machinery dies again (sprites.ts): repeatRecenterX (whole-tile camera recenter) and the world-sprite 3×-quad span (1 + 2·tilesX in packVisual and the bounds path) are deleted; getSpriteBatchKey no longer nulls on repeatX. Camera-attached strips are untouched — resolveCameraAttachedPlacement still spans the viewport and scrolls the UV phase.
  • Sim raycast parity (tome/api/sprite-raycast.ts): a repeating world sprite has an observer-independent quad again, so the lane hits it — what the player sees is what the ray hits. Parallax and camera-attach exclusions unchanged.
  • Torn-parent heal goes LEVEL-triggered, closing the two proven heal holes of the marionette-tear class (ledger 1884; dig 58262db3). Authored parented children were observed losing their live TomeParent mid-session and freezing at stale world coordinates while their roots kept moving — whole actors dismembered at one instant, spec rows healthy the whole time. The reattach pass (rebindExplicitParentObjects) provably heals this — but it was edge-triggered, running only inside a content-CHANGING applySpec, which left two holes: (1) the delta-0 short-circuit (interpreter.ts) skipped it entirely, so every join/reload/reset re-applying the SAME revision left a torn room torn forever (field receipt: a creator's client reloaded and the room was still torn two minutes later); (2) a fresh client's first spec apply ran before its AOI rows existed (hasEntity false ⇒ silent skip), so entities landed torn from replicated rows with nothing left to re-run at that revision. The fix: a spec-entity index (TornParentHealIndexResource: runtime child id → authored def, derived by the exact rebind derivation, cached against doc identity + place-instance registry content) and one sweep over it (sweepTornAuthoredParents — O(authored parented rows), heals missing/dangling edges only). The delta-0 short-circuit now runs the sweep (precedent: it already ran the spline-orphan sweep — both are state-dependent passes no content compare can veto), and a new simulator-resident system (tome/torn-parent-heal, every 30 ticks, beside lifetime-reap) enforces the invariant between applies on the world that simulates the entity, so a host-side tear heals at its home and uploads through the owned component lane. A heal fires tome.parent_heal.reattached — loud while the tear's origin remover is still hunted (the O1 mask-fold apply-diff lane is disconfirmed by a committed probe; origin fix lands separately on its receipt).
  • Wheel over UI never reaches world input — the same silence presses already get (ledger 1883, savi-filed f98584b1). A wheel over a god-mode/creator-tab panel scrolled the panel AND zoomed the world: the frame forwards every wheel with its overUi verdict attached (input-forward.ts, capture-phase document listener — authored stopPropagation can never suppress it), but the verdict died in both host sinks of routeWheel. Raw-capture lane: ForwardedWheelPass carried no overUi field, so the sole gate was slot geometry (always passes fullscreen) and the delta rode the input ring → mouseWheelDy → any spec binding wheelY (NEON APEX's zoomIn + camera-chase = the felt zoom). Transport lane: handleWheel was gated only by slotBlocksWorldPress, a press-shaped predicate whose !pointer.overUi press-exemption (correct for presses — press consumers read the paired overUi flag) wrongly admits over-UI wheels, whose delta has no such pairing. Fix at the boundary, since authored axes structurally can't check: ForwardedWheelPass gains overUi and injectForwardedWheel drops over-UI wheels before the ring (mirroring pressMouseButtonIntent's overUi silence); the transport handleWheel drops the wheelDelta whenever the event's own verdict is overUi, regardless of slot state (freshness post still ships, mirroring the slot-blocked branch); and the native raw-capture wheel listener gets the same data-input-capture hit test (canon §3 one-code-path law — native overlay and realm agree). The god dolly's own sim-side overUi re-check (scrollConsumedByAuthoring) was the one protected consumer; now every consumer behaves like it. Presses are byte-identical: slotBlocksWorldPress itself is untouched.
  • Lazy view materialization (ledger 1894 leg a). buildObjectView ran all ~40 propertyGetters per api.getObject call — spec locates and deep clones for properties the calling script never read, per entity per tick (dig c961d106: 17.2% of MechaBlade's sim-worker busy CPU inside this loop, GC sawtooth 65→96MB from clone garbage, worst tick 287ms). The view's properties bag is now built on first access, and each key is an enumerable accessor that memoizes into an ordinary data property on first read. Key set/order, spread, Object.keys, JSON, structuredClone, local assignment/delete, and per-view value stability are all preserved (pinned in object-view-read-path.test.ts). Script-transaction overlay worlds keep the eager snapshot (their staging worlds die at commit).
  • Spec object index (leg b). findSpecObject ran a findIndex linear scan over every place's objects array per call — the top JS leaf in the trace, reached per read through the spline/blueprint/brush getters. Now an id→row index per spec identity (WeakMap<GameSpec, …>, the SPRITE_DEF_INDEX shape), revalidated per lookup by a freshness walk (places-record identity, per-place array identity AND length) that mechanically catches the one in-tree in-place writer (ensureSpecEntryForSpawn's spawn-stub push) plus place add/delete/REPLACE under an unchanged root; hits additionally validate the row's id and fall back to the verbatim linear reference on any mismatch. Precedence semantics (hint place → entries order → legacy scope → namespaced-id fallback, first row wins on duplicates) are pinned test-by-test.
  • Cheaper isolated clones (leg c). The model getter's per-read structuredClone(parts) becomes cloneSpecValue (the plain-data cloner the spec getters already use). Reads stay per-read copies rather than shared frozen views — deliberately: the mutate-then-write-back round trip (const m = …model; m.parts.X.visible = false; setObjectProperty('model', m)) is a documented creator contract (ledger #735), and sharing structure with the store would make the write-site equals gate compare the mutation against itself and silently drop it. Write semantics byte-identical; isolation pinned by test.
  • Microbench (scripts/bench-object-read.ts, 600 authored objects + 120 runtime-spawned, bun 1.3.14): getObject+position/state 14.31µs → 1.05µs (13.6×); getObject+read model 18.10µs → 9.19µs (2.0×); getObject on runtime-spawned ids (the full-scan miss path) 18.24µs → 1.00µs (18.2×).
  • Unknown playSound option KEYS warn once (dig ec4cb85d — the sfx-zoo fight). Savi invented a soundId: option and the engine swallowed it silently; her stop calls then aimed at a handle that never existed — three debugging rounds, zero engine feedback. The engine already warned on given-but-unusable VALUES of known keys (bus/priority/maxDistance); the unknown-KEY class now teaches through the same audio-verb throttle: once per entity, value-free dedupe key, the offending keys and the valid key set ride the first message. soundId among the unknowns names the true lane outright (playSound RETURNS its handle). Legacy gain/spatial spellings are read, so they never warn — but they're not advertised in the teaching list either.
  • stopSound(id) with a never-minted id warns once; finished handles stay silent. Every handle playSound has ever minted carries the uniqueId("sound") shape; an id without it (and absent from the loop registry) was never a handle in any engine version — the typo/invented-handle tell, and exactly the id shape the sfx-zoo stops died on. A MINTED handle whose sound already finished stays silent: finished is normal, and the server keeps no one-shot liveness record to tell live from done. No-op semantics unchanged — the stop event still rides, downstream stays tolerant. The loop registry's silent-tolerance comment now points at the verb-site teach (the registry write also ingests relayed events, where a warn would be ops noise).