engine v5.1.13
Engine v5.1.13
July 8, 2026
A patch in the Connection line.
what's new
- When your game's music code hits a wrong note — an unknown drum name, a bad pattern — Savi actually hears about it now, with a helpful nudge toward the closest real instrument. And "tabla" just works.
- Games open behind a smooth veil instead of flashing a half-assembled world for the first second — the reveal waits until the scene has actually come together.
- Holding Ctrl while scrolling in god mode no longer flies the camera — Ctrl+scroll is reserved for your game's own bindings (and your browser's), so a zoom binding doesn't fight the editor.
- Box colliders on scripted meshes now sit exactly where the mesh is — an off-center shape (like a car whose body reaches forward of its pivot) no longer leaves an invisible wall behind it or lets players walk through its front.
- Testing in your dev room no longer touches your players' real saves — dev gets its own save space (starting fresh), while your published game's saves stay exactly as they are. Shared world data (terrain edits, shops, chat) still flows from build to publish like before.
- If god mode gets switched off in your game while someone is still flying around in it, they can now always land back into normal play — no more being stuck as a ghost until they reload.
- The god-mode help overlay now tells you Ctrl is sprint.
- Games can now have real, permanent leaderboards! Scores survive server restarts and rank players across every room and session — all-time high scores, fastest laps, daily bests. Time trials work too: mark a board as lowest-wins and the best time comes out on top.
- Liquid marks (paint-a-pond terrain) no longer leave invisible ghost objects piling up on the server for every player who joins — and rooms clean up the ghosts left behind by older sessions on their own.
- When a 3D model can't download, Savi now gets a clear "model failed to load" message naming the asset — instead of the game silently showing a placeholder all day with nothing in the logs.
- Games that build asset paths with
${…}templates no longer try to download those half-written paths behind the scenes — the phantom fetch errors that spammed logs every session are gone. - AI-generated models with layered shells (hollow casings, double-walled builds) stop coming out with scorched-black patches — ambient shadows now bake from the surface you actually see.
- Game scripts can now ask which room they're in:
api.getRoomMode()returns"dev"in your build room and"live"in your published game — handy for keeping test data (scores, shop stock) separate from your players' while world data keeps flowing from build to publish. - When Savi looks at your game, she now sees exactly what players see — your color grading and glow included — so her verdicts on how things look are finally trustworthy.
- Scripted materials that failed to arrive after joining a room used to silently show up as plain default surfaces forever — now the engine retries the handoff on its own and tells Savi exactly what went missing.
- Editing your scene reuses already-built materials instead of rebuilding all of them, so edits land faster and smoother.
- Players on slower machines no longer get trapped in a rebuild-the-world loop that ends in a disconnect — if the world falls behind, the engine recovers once and then backs off instead of thrashing.
- When a terrain texture can't load, that layer now shows a neutral grey instead of hanging invisible or wrongly-colored — and Savi gets told exactly which layer failed and why. If the texture recovers later, the real look comes back on its own.
- Transparent generated shapes (glass, paper lanterns, light shafts) render both sides again — translucent builds that turned inside-out (caps floating, fins splayed) snap back to their assembled look.
- When a game hits its voice-generation limit, the engine now waits exactly as long as the server asks before trying again — instead of hammering the same request every few seconds for hours.
- Savi can read each device's live render-quality state again (
api.getClientHealth()), engine-bug reports include per-device health instead of nothing, andapi.resetQualityLanding()actually resets a stuck quality landing — all three had been silently dead since mid-June. - Setting a rotation hinge on a character mid-game no longer makes them jump sideways — the object stays exactly where it is when a script sets or clears its
pivot, and only turns around the new hinge from then on. - Pixel-art sprites no longer get permanently stuck broken when the network rate-limits during loading — no more whole sprite sheets squeezed into one tile, wrong-size buildings, or characters that ignore the lighting. The engine now keeps retrying politely until the art arrives.
- Characters no longer blink invisible for a moment when turning or changing animation in 3D games with pixel-art sprites.
- Fixed 2D characters turning invisible after walking between places — leftover 3D animation names can no longer hijack a sprite's animation and point it at an empty image.
›technical notes
- Vibe audio runtime errors reach Savi again across the worker boundary (#8349, merge 4e113a7018, ledger 1018): the error drain was structurally dead in prod — errors are born in the WebAudio renderer on the MAIN thread, but the drain consumer (AudioPrepSystem → sendClientBehaviorFault → engine-diagnostic rail) runs in the runtime worker where the renderer handle is realm-local null. Batches now relay across the existing audio seam (
audio.vibeErrors, same lane asaudio.completed), and prep consumes both the realm-local handle and the relayed queue. Plus:_suggestBankNamespeaks bank-level vocabulary (avsco_cellos_pizzmiss suggests avsco_cellos*bank, not a per-pitch group member), andtablaaliases totabla_naso the bare drum name resolves likebd/sn/hat. - TransitionVeil engages on cold boot — the raw-world first-frame flash dies (#8353, merge f6a9c8dd6a, ledger 1021): the session's first renderer frame starts in the veil's holding state, so the first present is black instead of the assembling world (assets streaming, materials compiling) that painted ~1s before the game's HTML UI could show its loading screen. Boot readiness rides
sceneAssembled: first authoritative snapshot applied AND world-residency's streaming lanes quiet this frame (new un-latchedquietNowverdict); at boot the flip signals are vacuously true. SameVEIL_MAX_HOLD_MScap, same reveal fade; ECS still reflects spec instantly — only the visual reveal is held. 2D-place boots get the same dimension-agnostic engage. - Chorded scroll belongs to authored bindings — ctrl+scroll no longer dollies the god-mode camera (#8368, merge d4f545c617): the god-mode wheel dolly now suppresses itself when
modifiers.ctrlis held, alongside the existing over-UI / coarse-pointer / brush-gesture suppressions. Bare scroll keeps the dolly. - Box colliders derived from bespoke-mesh bounds carry
bounds.centeras an optional entity-localtranslationonPhysicsColliderBox(ledger 1001;deriveColliderFromBoundspreviously dropped the center, so any off-centercollider()mesh got a box snapped back to the entity origin — the iso-town car's ~1 m phantom wall behind / walk-through front). y rides the body origin viacomputeFeetOffset, x/z land as the collider's local shape offset in both engines (rapiersetTranslation, mantlelocalPos); carried through sanitize, the wire codec, collider signatures, AOI radius, NPC nav/perception AABBs, the collider debug view, and trace. Not breaking: the translation is omitted whenever the bounds center equals the implicit default (x/z-centered, feet-anchored — every builtin primitive), so already-correct derivations keep identical component values and collider signatures; only previously-wrong off-center shapes move, and they move onto their visuals. - Dev rooms get their own player-save namespace; live saves untouched; world data still shared dev↔live (ledger #995). Dev and live are room modes of one variant and the documents API is mode-blind, so a dev-room script writing
user/<uid>/savepreviously hit the same document as the live room — player saves alternate-clobbered whenever both containers were up. Now both storage rails (the pool-worker rail injobs/worker-env.tsand the lifecycle rail in room-runtime's LifecycleContext, applied in object-api's fast path — mapping shared viaengine/runtime/jobs/storage-namespace.ts) mode-segment PLAYER-scoped script keys only: a dev-roomuser/...read/write/list/lock resolves to documentjobs/dev/user/..., while live/published rooms keep the barejobs/user/...names — zero migration, every existing live save untouched; dev starts empty. World-scoped keys (everything not underuser/— terrain-edits, market/state, chat/state) stay shared across modes by design: the build→publish workflow depends on world data riding the mode-blind lane. Absent/unknown room modes fail toward the old behavior (bare names, shared with live).forwarded-storage-namespace.test.tspins dev↔live disjointness foruser/keys, continued world-key sharing, and that both rails resolve identically. - God-mode exit is always allowed — the
spec.godMode.enabled === falseguard now gates entry only (#8341, merge ebf2c5ae96, ledger 1006).toggleGodModeForPlayerran the spec-enabled guard before the exit branch, so a mid-session spec flip to disabled stranded an in-god creator ("God mode disabled in spec" on every exit) until disconnect/reload. Both authorities share the fix (server command + singleplayer worker call the same core); pinned ingodmode-spec-update.test.ts, and the #8326 posture characterization updated to the new semantics (mid-session disable → atomic exit posture; blocked re-entry'sgod:errorstill never mutates posture). - God-mode controls overlay adds a "Ctrl — sprint" row to the starter shortcut legend (#8351, merge 0eeed3ef5f).
- New builtin job
leaderboard:submit— platform-durable score submission from creator scripts:objectApi.job("leaderboard:submit", { leaderboardId, score, userId, direction? }, cb). Posts server-side to kiln's SDK leaderboard submit route with the room's SDK identity (the API key lives in a worker-env capability closure —env.leaderboard, built at pool-worker spawn from workerData — and is never readable from script space, args, or results).direction: "lower"(time trials) negates the score on submit to fit the GREATEST-hardwired platform tables; scores are rounded to integers and rejected outside ±2^52. Soft-fail contract: the job DATA always resolves{ ok: true, rank?, improved? }or{ ok: false, error }(missing_user,user_not_in_room,invalid_score,rate_limited, …) — never throws for runtime failures, so fire-and-forgetif (!result.data.ok)checks are safe. - Room-trust gating at the two builtin-job seams (
tome/job-dispatchon the authoritative server world,tome/job-forward-serverfor forwarded singleplayer/client-auth submissions): the targetuserIdmust be a connected player (presence stamped into args server-side, overwriting anything script- or wire-supplied; the handler fails closed without the stamp), plus an in-room cadence cap of one submission per (userId, leaderboardId) per 2s. Server submissions also opt in to kiln's DB-side rate limit (enforceRateLimit: true). - Skill docs:
leaderboardgains a "Durable Leaderboards (platform)" section;jobs-and-storagelists the new builtin. - Mark-liquid surfaces stop minting server husks (#8354, merge 0f788c664b, ledger 1020): TerrainMarkLiquidSystem (mode client) derives liquid surfaces locally on every client, but on a place host the entities satisfied
isHostSimulatedEntityand published as hosted-remainder state — the server stripped the replicate-never geometry and kept a permanent geometry-less husk per surface, fanned to every joiner, with signature drift stranding old husks forever. Three walls: (1)isHostSimulatedEntityexcludes engine-derived ClientEntity locals (covers the upload view, the reset stray sweep, and the rail flush in one predicate); (2) the server's upload create gate refusesterrain/mark-liquid/*rows so stale-engine hosts can't re-mint (create-only — deletes stay host-writable); (3) as live host, the system reaps stale-signature husks for mark keys it owns and publishes the deletes through the new ClientAuthHostDespawnPublishResource, re-arming on host claim between rebuilds. model-load-failedjoins the allowlisted engine-diagnostic codes (#8359 kernel leg, merge 043b479c3f, ledger 1025): the model sibling oftexture-load-failed. Neither of the model rails covered a failing FETCH — the contentless report fires only on successful-but-empty loads and the unready-asset note rides scene-view only — so a 5xx-looping model (e.g. a 24h magic-cdn tombstone) held its "Couldn't spawn" placeholder all day while Savi's log rail stayed clean. Same emitter, same once-per-asset-per-episode cap. The kiln legs of ledger 1025 (429/502 propagation on the serving routes) ride kiln's own lane, not this kernel build.- asset-preload-sweep skips template placeholders (#8347, merge fd67a70350, ledger 1017):
SCRIPT_ASSET_REF_PATTERNstring-scans raw script source including backtick template literals, so refs like`/cdn/${SLUG}/tracks/theme.mp3`were prefetched as literal URLs (~9 phantom URLs × 3 retries × every session on the specimen app, ~311 iframe errors/2h). Any scanned ref containing${is now skipped — a template placeholder is by definition not a fetchable literal. Literal refs in the same source still preload. - Remesh GPU bake casts AO from the outside-visible sheet on nested shells (#8352, merge ef67ac807d, ledger 1013 step 1): Trellis raws carry nested same-facing onion shells, and the nearest-agreeing texel search landed on the inner enclosed sheet wherever the decimated surface drifted inward — the AO hemisphere then measured the inside of the shell stack (occlusion ~1, scorched black; the y2k/frutiger mottling class). Before the AO loop, one BVH scan walks +N to the cage-clamp ceiling and relocates the AO origin to the outermost same-facing crossing below the nearest opposing crossing; single-sheet texels keep the classic origin bit-for-bit, and albedo/normal/roughness/metal transfer is untouched. Ships in
packages/remesh(the remesh worker image), not the kernel tarball — aboard for every remesh regardless of pinned engine version. - Scripts can read the room's mode:
api.getRoomMode()returns"dev" | "live"in behaviors, lifecycle hooks, crons, andrun_script. The value is mirrored from the room's SDK identity (TomeRoomModeResource, written by room-runtime from the samegetRoomModeIdentity()both storage rails key their mode segmentation on — storage-namespace.ts) so the read can never disagree with where auser/save actually lands; absent/unknown modes read as"live", exactly howmodeSegmentStorageKeytreats them (ledger #996, follow-on to #995). - Dev-room shared-key FYI for Savi: when a game script in a dev room writes (
storage:set/storage:del) a world-scoped key — anything not underuser/— the engine records one informational runtime-log line (thegetLogschannel) per key per room session, naming the by-design dev↔live sharing and the deliberate mode-split options (user/keys, orapi.getRoomMode()in the key) without instructing a rewrite. The hook sits on the ObjectAPIjob()seam, so the lifecycle fast path and the queued/pool lane both pass through it while engine-internal storage lanes (terrain-edit persistence submits straight to the JobQueue) stay silent. Live rooms and unknown-mode worlds never warn. view_live_scenecamera/frame captures composite the active look + bloom (#8344, merge 32684f98c4, ledger 1009): captures previously skipped the whole post chain (noatmosphere.lookgrading, no bloom), so Savi graded look-dependent work against pixels the player never sees. The live post graph can't be re-driven from a capture pose (scene pass, bloom pyramid, TAAU history are main-camera-keyed and accumulate across frames), so captures run a capture-owned chain built from the same pieces: scene → linear HDR (+ capture-pose depth) → the engine's dualFilterPyramidBloom driven by the same resolved-look dials and quality-governor skip → ACES → the same LookPass class (scripted graph + vocabulary + sRGB + dither) when a look is active → the capture's output target.- Scripted-material library join failure gets a loud diagnostic + a join watchdog retry (#8342, merge f98c0073f3, ledger 1014): on some joins the derived MaterialScripts library (replicate-never, forward-always) never reached the renderer worker, so every scripted intent built as its Std/PBR fallback — silently (the report was gated on
libraryReceived) and permanently (only a source-hash edit or renderer restart refreshed). Now: ascripted-material-library-missingdiagnostic (reportOnce per ref per session, distinguishing never-received from received-empty), a renderer-local join watchdog (~10s after boot-drain latch, one re-check, never a poll loop), and the server-side engine-diagnostics dedupe gains a TTL so later joins' byte-identical reports aren't swallowed room-lifetime. Fallback behavior itself unchanged. - Standalone-primitive material mints resolve through a scene-scoped refcounted content-identity registry (#8315, merge 8b13fa8fa0, perf-apex L16, ledgers 900/952/959): same mint inputs → the same already-compiled material instance across wearers and across edits; changed content mints new, and a replaced identity disposes exactly when its last wearer swaps off. Per-entity-mutable mints (textured leases, draw/effect carriers, active flash, appearance-owning water/shockwave/slash) stay per-entity; patches on shared identities settle first (sole wearers re-home in place, divergers split before any uniform write can bleed).
- The stale-path reset↔recompile strobe is capped with the bespoke branch's epoch budget + backoff (#8361, merge f78ff89b14, ledger 1026): the stale branch of
prepareRenderBacklogRecoveryfiredwriteRecoverySnapshotunbounded — on a cold-shader client the recovery snapshot's own respawn mints all-new batches/materials, the compile storm puts the renderer behind again, and the trigger re-arms from its own hangover (observed 60s→29s cycles, 379 programs on 25 draws, 691ms renderer frames, until connection death). Now: a sim-side epoch that survives the reset, first recovery immediate, then doubling backoff (30s/60s/120s…) with the strobe declared via one warn once the rapid budget (3) is spent. One deliberate divergence from the bespoke sibling: the backoff clamps at 300s instead of refusing forever (the stale reset is also the sim-side backlog's memory-relief valve), and the epoch dies after 120s of quiet so a later real staleness event starts fresh. - Terrain splat-layer texture fetch failures get a neutral-albedo fallback + a Savi-visible diagnostic naming the layer (#8345, merge aed167474f, ledger 1008): the layer atlas only subscribed to load successes, so a failed fetch (the staging moodboard KTX2 401 family) left the layer waiting invisibly forever and Savi never heard the layer's name.
renderer-asset-servicegainssubscribeTextureLoadFailures(the failure twin ofsubscribe(), fed from the retry ladder's verdicts; 202 still-generating and non-exhausted auth parks stay silent); a SETTLED failure flips the layer's tint row to a neutral mid-grey through the authored tint pipeline (never black, never a stale full-strength tint) and reports material + texture + HTTP status through the existing allowlisted terrain-atlas fallback codes. The success subscription stays armed — a late heal re-textures the layer and restores the authored tint. - #8345 review follow-ups (#8348, merge a0a5aa5dba): the heal path's authored-tint restore is un-gated from the join verdict (a healed-but-unjoinable texture takes its authored tint back); the failure channel gains a settled-verdict query (
getTextureLoadFailure) so an atlas built after a terminal settle still learns of it; dead-asset fix text is auth-flavored. - Transparent bespoke-mesh material leases default to
THREE.DoubleSide, completing the one-winding-convention pairing (ledger 977; the original rescue increateMaterialLeasewas scoped!willRenderTransparent, which left transparent one-sided bodies — exhibit-leveltransparent: trueor vertex-alpha-forced — rendering the flipped side of ctx-authored faces). One default for every bespoke lease now: DoubleSide unless the author setssideor the material owns its graph. Cost is three's transparent+DoubleSide two-pass (BackSide then FrontSide), which buys correct within-mesh self-ordering and lands only on the rare standalone transparent bespoke population; it also matches the batched transparent lane (always DoubleSide) and what a live opaque→translucent geometry patch already produced (applyVertexAlphaTransparencyflipstransparentand leaves the lease's DoubleSide in place). The alpha-split draw grouping (ledger 956) is untouched — translucent slots keep their own groups, lanes, and shadow flags byte-for-byte; only the lease side default changes. - Magic-cdn probe honors Retry-After per filename (#8355 kernel leg, merge e1b641342e, ledger 1023): a dialogue-heavy game that exhausted the per-app speech cap kept re-requesting capped assets ~3.2/min for hours (937 rate-limit rejections/6.5h). A 429 with a quota reason now remembers the server's own Retry-After in a per-filename tombstone (conservative 5-min fallback, 1h ceiling); re-asks during the window get the same MagicCdnFetchError with zero network round-trip, and expiry re-admits a real probe. Per-URL-identity (other assets keep serving), bounded map (512 entries, oldest-out). The kiln legs of ledger 1023 (single-designer voice claims) ride kiln's own lane, not this kernel build.
- The
engine.clientHealthsnapshot producer is restored (ledger 1033; deleted by the #6891 runtime-worker rewrite, which leftCLIENT_HEALTH_COMMANDa server terminus with no sender andrecordLocalClientHealthwith zero callers). A factory module (client-health-report.ts, the renderer-perf-rollup pattern) taps the raw quality-governor block off eachrenderer.perfSample, and reports on the 15s perf-rollup cadence plus a throttled edge report on rung transitions — writing the local mirror (singleplayerapi.getClientHealth(), engine-bug dumphealthblocks) and the client command outbox (multiplayerapi.getClientHealth()via the server mirror, still re-validated + rate-limited server-side). The transition-trigger allowlist widens with the kernel'sGovernorTriggervocabulary (pacing-engage/pacing-release, the #8314 pair). Thequality.landing.resetclient control case and the singleplayer reset transport (setClientQualityResetTransport) — deleted by the same rewrite — are re-wired, soapi.resetQualityLanding()reaches the renderer again in both realms. - Runtime pivot writes (
setProperty("pivot")/setLocalPivot) are now pose-preserving (ledger 1038). The pivot foldT(F + S·P − R·S·P)·R·Snails the pivot's fixed point at its identity-rotation world location, so a runtime set on an already-rotated entity teleported the folded pose by(I − R)·S·Pthat frame — and the clear snapped it back (Rabies Bear's rear-up: 1.40m lunge at yaw 180, 0.99m diagonal at yaw 90/270, collider riding the fold). The property writer now rebases the authored local position by the fold-offset delta at write time (writeLocalPivotPreservingWorldPose, tome/pivot.ts): the entity doesn't move on set or clear, and rotation changes after the write hinge at the pivot's world location at write time. The rebase reads only replicated authored state (Local*, LocalPivot, renderable bounds — never a sampled world pose), is exactly zero at identity rotation, and works identically on roots and parented children. Spec-frame surfaces keep composing the authored fold with no rebase, via the newapplySpecPivot: boot + spawn (interpreterapplyObjectProps,applySpawnProperties) and property patches (applyPropertyPatchToWorld— updateObjectSpec, undo/redo envelopes, where a mixed{feetPosition, pivot}patch must land exactly where it says). - REMINT (#8403, merge 5ad2155ea6, ledger 1050 — the 5.1.13 remint blocker): 429s are transient, not dead — sprite atlas/meta hydration un-starves. The #8274 boot pre-warm floods magic-cdn in the boot window, and the #8259/#8270 give-up ladders classified any 4xx as a dead asset (
status >= 400 && status < 500), so a staging 429 storm latched permanent give-up after 8–10 attempts: sprite atlas metadata starved (resolveSpriteUv→UNIT_UV— a whole sheet rendered into one quad),?normals=companions starved (unlit lit-sprites), andspawn.metaworld-height starved (default-size sprites). Dead is nowisDeadAssetHttpStatus(4xx except 429/408/425) in both give-up lanes (renderer texture/model retries + sprite metadata hydration); transient statuses retry indefinitely like 5xx/network, honoring the server'sRetry-After(the #8355 park doctrine). The renderer's atlas-hydration lane carries HTTP status through the meta fetchers instead of folding every failure to null — give-up latches only when EVERY failed source answered a dead 4xx, and served-but-no-meta settles authoritatively in one probe. A KTX2 kvd-block fetch failure is a retryable failure now, not a silent header-only fallback that latched fps=12 forever. - The #8277 auto-size gate, narrowed to its stated purpose: an authored
collider2dno longer pins an UNSIZED sprite to[1,1](Village Green's t2-cottage). Authoredsprite.sizestill beats hydrated size (art and hull stay glued); the auto-anchor gate (#6713) is untouched. - 3D-place batched sprites no longer blink invisible on facing/clip texture swaps (ledger 1041, tiger's iso-town walk trace). Texture identity is the batch key, so a
?facing=X&animated=CLIPswap re-keys the visual across batches — and the re-key hold ghost used to release when the destination's COMPILE settled, while a destination batch still waiting on its texture lease is mesh-wide hidden by the anti-flash gate and the async compiler drops invisible objects at drain time without compiling them: "no compile pending" released the ghost into a batch that had never drawn, and the sprite rendered nothing for the fetch/transcode window (~230ms+ in the trace). The hold now arms and releases on PRESENTABILITY (spriteBatchCanPresent: texture lease bound or the mesh already past the visibility gate, plus compile actually settled — the release probe re-queues dropped compiles until the first visible pack lets them land). - The recurrence engine dies with it: the idle-dispose sweep (
ageIdleSpriteBatches) now spares empty batches whose texture BASE (spriteTextureBaseOf— the URL before its variant query) is worn by a live visual, so a walk-kit hero's 16 facing/clip variant batches stay warm (compiled material + bound lease) once seen instead of re-fetching and re-transcoding the KTX2 on every 5s-idle revisit. The whole family disposes on the sweep after its last wearer despawns. The standalone (ySort) lane and 2D pens are untouched. - Sprite mixer selection never composes a non-storyboard clip name into a
?animated=variant URL anymore (ledger 1047, the zoo 2d-side invisible player). A place transition can strand stale 3D locomotion channels (idle: "Idle"at weight 1 — the a3dc vocabulary) in a sprite hero's mixer when the skin script's one-shotsetProperty('mixer', …)replace is lost across the transition write-window; the stale channel won selection, composedhero.png?facing=right&animated=Idle-loop, and magic-cdn answers such URLs with a renders-nothing placeholder — an invisible player that only a refresh (fresh onSpawn skin derivation) healed. The composability rule is shared (isStoryboardClipSlugin sprite-appearance-rules — lowercase kebab slugs, the 2d-mode skill's taught vocabulary): selection skips non-slug channels (the next declared 2D clip wins; all-invalid mixers hold the visible base frame), sprite clip timing reports the nominal ~1.3s fallback for them immediately (no bogus hydration fetches, and no permanentduration: null—getChannel().finishedkeeps firing on wall-clock and atlas probes read the taught failed-cook signal), and the warm derivation stops minting their variants (?animated=Idle-loopwarm refs were real: the scraper crossed a script's 3D-branch clip literals with its sprite textures). The skip teaches through the Savi-visible diagnostic rail (sprite-clip-not-storyboard, once per entity+clip — getLogs + one DM, likesprite-atlas-missing) plus one console warn per entity, same lane as the blank-clip skip.