Spawn

Make Games with Words

Explore or make your own

spawn / swhat we're building

pinned

start herewhat spawn isfaqfrequently asked questionsthe betthe spawn bet

updates

engine v4.6Atelier3 daysengine v4.5Surface Tension1 weekengine v4.4Solid2 weeksengine v4.3Groovy3 weeksengine v4.2Continuum3 weeksengine v4.1FoundationsMay 4, 2026engine v0.1GenesisApril 29, 2026
← All posts

engine v4.1.0

Foundations

May 4, 2026

First big engine update. Faster, smoother, smarter — and a small mountain of fixes.

what's new

Spawn's first big engine update. Faster, smoother, smarter — and a small mountain of fixes.

  • Huge engine perf upgrade. The whole renderer was rebuilt, terrain streams better, and big scenes draw faster and smoother.
  • Savi's tools are way more reliable, and she can now see and poke at your game's UI — huge unlock for UI-heavy games (Discord-style chat games, dashboards, card games, etc.).
  • Multiplayer feels much smoother — camera, controls, and effects (vignettes, particles, music swells) all stay in sync now instead of glitching during rollback.
  • Hundreds of fixes — actually hundreds. Screen flashes, object juice, screenshots, voxel marks, query ordering, and a long tail of small things that used to glitch now just work.
  • New animation stuff! Savi can now tween any property on any object — bounce a chest open, pulse a crystal, fade things in, flash red on hit. More animation features coming soon.
›technical notes
  • Added channel-based animation mixer to ObjectAPI. Declarative setup via api.setProperty("mixer", { <channelName>: { clip, weight?, duration?, speed?, loop?, blendIn?, direction?, mask? } }). Runtime control via api.updateChannel(name, opts) (pass null to clear) and api.getChannel(name) returning { clip, weight, elapsed, duration, finished }. Channels blend by weight with optional per-bone masks ({ from: <bone> } or { bones: [...] }). Backed by new DrawMixer ECS component (replicate: AOI).
  • Added anime.js-style property tween API: api.animate(targetEntityId, { keyframes, duration, easing, delay, direction, loop }) and api.isAnimating(targetEntityId, dotPath?). Keyframes target dot-paths like "feetPosition.y", "material.emissive", "scale". Supports scalar tween values, value arrays ([a, b, c] evenly distributed), per-segment timing ([{ value, duration, easing }]), and relative deltas ("+=5" / "-=5"). Server-authoritative via new tween-evaluator system (order 105, after behavior-update); state stored in TweenState component (replicate: never).
  • Extracted shared easing.ts (easing curves + OKLCH color interpolation) used by both behavior builtins and the tween evaluator.
  • Added parseMixerChannel shared validator used by both the mixer property setter and updateChannel runtime call.
  • Added interpolation property (getter/setter) backed by DrawInterpolation; { teleportThreshold: number } or null to disable.
  • Note: the previously documented animated3DCharacter.action: { clip } shorthand never had a real implementation (the setter silently dropped the field). Use the mixer instead — declare locomotion + action as separate channels.
  • Camera authority split: render worker is now sole owner of yaw/pitch, integrating mouse deltas directly and publishing back to sim via a new CameraAngles SharedArrayBuffer channel. Spring-arm smoothing and physics-collision raycast moved from renderer into camera-behavior (sim), collapsing five per-frame orbit params into a single orbitDist the renderer lerps toward. Removes input-lag and stale WASD movement axes.
  • Renderer now also publishes its final pos+quat through the camera-angles SAB; sim consumes rendererTransform inside buildPointerRay() so click rays match what's on screen at >60 Hz refresh. View-state pos/rot intentionally left on sim-frame semantics so script-side camPos* is unchanged. Unit-norm guard rejects zero-initialized SAB state.
  • Compiled behavior scripts: Math.random() now delegates to a seeded RNG when one is installed (globalThis.__tomeSeededRng), making prediction/resimulation deterministic. New setTomeRng / clearTomeRng helpers in tome/resources.ts install the RNG into both the ECS resource and the global bridge consumed by compiler.ts's deterministic-Math injection. Wired through interpreter.ts, input-applier.ts, and behavior-update.ts.
  • TweenState now replicates with AOI + snap correction so animation tweens stay in sync across clients during rollback and resimulation.
  • ECS event component system restored (reverts the entity-based event experiment): queueEventAdd / drainEventAdds / injectTransient re-instated on the ctrl channel. Particle bursts (ParticlesBurstEvent) drained per tick and forwarded through the SAB render channel. Juice/audio dedup logic survives rollback.
  • Snap-frame application bypasses the prediction entity filter for replicate:"owner" components. Server-authoritative owner state (e.g. TomePlayerJuiceState) was previously dropped on predicted entities, breaking vignette, letterbox, music, and other effects in multiplayer.
  • ECS query-utils: query results are now sorted on every path (not only the index path) so behavior scripts iterating queries see a stable order frame-to-frame and across server/client.
  • New client-only DrawVisibilityOverride (replicate: never) lets camera-behavior hide the local player without clobbering the server's DrawVisibility. All render_v2 states honor overrideLayerMask with fallback to server layerMask.
  • SkipReplication removed from terrain streaming. Terrain entities now replicate normally and are protected from server despawn; event components on the client are fixed.
  • Camera API (tome/api/camera-api.ts) getProperty/setProperty/lookAt/setRotation read and write FeetPosition + Rotation directly instead of going through the now-removed InterpTransform blob. Script-visible shape and semantics are unchanged.
  • Replaced legacy renderer with render_v2: new RendererFeatureV2 (engine/render_v2/feature.ts) is the sole renderer wired by engine/client/engine-bootstrap.ts. Worker-side ECS sync, camera, smoothing, and decorations all flow through engine/render_v2/* instead of the deleted engine/render/main, engine/render/extractors, engine/render/commands, and engine/render/worker/worker-prep.ts paths.
  • Removed legacy render pipeline: RendererFeature, worker-renderer.ts, worker-prep.ts, render-worker-state-adapter.ts, transform-smoother.ts, the prep/extractor/command system, and ~75k lines of supporting tests/utilities are gone.
  • New SAB-backed RenderChannel (render_v2/render-channel.ts) replaces postMessage ECS sync. Adds a string table with generation-based eviction, u32 frame-header numOps, local-buffer overflow instead of dropping ops, growable arena/string-table SABs, and per-frame perf metrics + transport instrumentation.
  • Renderer now consumes ops directly — legacy intent conversion deleted. AppearanceIntentValue/LightIntent are gone; per-property Draw* writes (DrawModel, DrawSprite, DrawText, DrawSign, DrawMaterialOverrides, DrawAnimation, DrawVisibilityOverride, etc.) are written directly by the interpreter and replicated as their own components.
  • IndirectBatchedMesh primitive batching for cubes/spheres/etc., with shadow fixes: per-instance shadow override materials, follow-camera shadow frustum, cast/receive flags honored on model meshes.
  • Troika text vendored in-tree under render_v2/text/vendor/*. Forces the bundled Geist Pixel atlas, short-circuits FontResolver.resolveFallbacks, and drops the per-spec data.font path so the renderer worker never races on the troika unicode CDN. Outline/highlight bleeding fixed.
  • Screenshot capture rewired for v2: captureScreenshot is now exposed on RendererV2Handle and threaded back to Savi's view_game_canvas_screenshot tool; fixes a freeze and reprojects the selection beam to the crosshair on the render worker.
  • Renderer is now sole authority for camera yaw/pitch; spring-arm collision moved to sim, rendered transform sent back to sim each frame. New camera-smoother.ts, orientation.ts, and renderer-camera.ts under render_v2/camera/.
  • Entity smoother batches pos/rot ingest per tick, adds per-object interpolation component and flash effects. Transform/layout-scale now applied in render_v2 model and scene state.
  • ECS-driven outlines and selection visuals moved into render_v2. Skinned models, LOD, decorations, and water materials added. Voxel terrain pipeline rewritten on render_v2.
  • New sprite-node-material.ts, EnvironmentCore, FogCore, LightsCore, PostProcessingCore, RendererPipeline, RendererAnimation, and terrain-decoration-service.ts under render_v2/.
  • New run_ui_script Savi tool plus general client-RPC system (cf-studio-chat DO ↔ kiln ↔ iframe). Scoped to the active user's most-recent WebSocket; rejects responses from other tabs/connections.
  • view_game_canvas_screenshot now works under render_v2: wired captureScreenshot through the worker RPC, captures inside the render frame (WebGPU texture lifetime), bumped size limit (512KB→2MB) and timeout (500ms→2s). Switched final encode from transferToImageBitmap to convertToBlob so taking a screenshot no longer freezes the renderer.
  • Trimmed run_script and run_ui_script tool descriptions; removed duplicated API docs and don't-lists in favor of taste/footgun notes.
  • run_script result shape collapsed to { newVersion, return, logs? } / { ok: false, error, logs? }; surfaces all log levels with data args.
  • New wisp tools terminate_wisp and list_active_wisps (in wisp.ts, no longer monkey-patched in server.ts); 4-char hex wisp IDs; terminated wisps render as "Wisp terminated" in the footer.
  • Misc Savi-side polish: grep merges adjacent matches; inspect_versions summary-only mode; get_game_pulse resolves userId at invocation; str_replace_editor view shows line numbers; debug gizmo renders text tool results as pre-wrapped text; interpreter preserves TomeTerrainAnchor on spec-driven position updates.
  • Screen flash routed through DOM UI sink/transport (was ECS-only, broken in worker).
  • Object-motion juice now restores base position/scale on effect end.
  • Voxel structure mark bounds derived from template build() output.
  • SPAWN_AGENT.md is now the single source of truth for CLAUDE.md / AGENTS.md; generated by scripts/generate-agent-docs.ts. Stale AGENTS/engine/{component-mixin-timing,live-reload}.md removed.
  • Rewrote terrain streaming as non-replicated, client-driven. Chunk components flipped from replicate: "aoi" to replicate: "never"; clients now build their own chunks from the terrain definition instead of waiting on snap frames. Removes terrain entities from the replication budget entirely.
  • Split the monolithic terrain/systems.ts (~4800 LOC) into server-terrain-system.ts, client-terrain-system.ts, streaming.ts, and terrain-systems-shared.ts. Server runs request/ingest/streaming/rescue; client runs its own streaming + ingest + collider flush + mark-liquid + rescue.
  • Added voxel terrain pipeline: per-layer textures, packed layer-texture sharing across LODs, vertex colors, async texture loading, and AO. New terrain-tile-service.ts (render_v2) is the LOD/tile authority.
  • LOD transitions no longer use dithered noise — replaced with stable cross-fade so seams stop crawling at distance.
  • Voxel chunk builds: numeric greedy meshing, boundary cache, SharedArrayBuffer result slots for zero-copy worker→main transfer, deadline raised to 2000ms, deadline-failure recovery, and stale-voxel-lookup fix.
  • Server voxel builds capped to a 3×3 chunk authority window with retry backoff so cold starts and large worlds don't stall the tick.
  • Unified chunk entity ID prefixes under terrain/stream/…; cross-prefix despawn fix prevents leaked chunk entities.
  • 2D top-down places now skip terrain mesh/collider work entirely (heightmap startup gate + ocean shoreline restore).
  • Material rebuild thrashing eliminated; weight-texture checkerboard artifact fixed; sphere terrain positioning corrected; stale colliders invalidated with fallback anchors.
  • Removed SkipReplication component; terrain entities now protected from generic despawn paths instead.
  • F3 Advanced panel wired through worker-host with chunk-mesh lifecycle tracking.
  • Fixed _f32 ReferenceError when terrain generator scripts use float literals.
  • Script-facing API (api.getTerrainHeight, getTerrainNormal, getTerrainMaterial, getVoxelMaterial, isVoxelSolid, raycastVoxel, setVoxel, setVoxelState) is unchanged.
  • Internal: split monolithic AppearanceIntent ECS component into per-aspect Draw* components (DrawModel, DrawPrimitive, DrawSprite, DrawText, DrawSign, DrawMaterial, DrawVisibility, DrawInterpolation, DrawLight, DrawAnimated3DCharacter, TomeLayout). Public ObjectAPI.getProperty / setProperty keys (visible, model, primitive, material, sprite, text, sign, layout, animated3DCharacter, light) and their value shapes are preserved — scripts read and write the same things they did before.
  • New interpolation object property: { teleportThreshold: number } | null (or false to disable). Controls per-entity render-tick smoothing; sets DrawInterpolation.
  • New GameSpec.assets.metadata field (Record<string, AssetMetadata>) — engine-managed cache of CDN model bounds. New AssetMetadata type and patchAssets ScriptMutation kind for engine-internal writers.
  • Camera API (createCameraAPI) reads/writes FeetPosition + Rotation directly instead of InterpTransform. Public getProperty("feetPosition" | "rotation"), setProperty, lookAt, getControlTarget, and query shapes unchanged.
  • queryWorld now returns results sorted by entity id on both the index and full-scan paths (previously only the index path sorted). Iteration order in scripts that loop api.query() is now deterministic across the two paths.
  • Type re-anchoring (no runtime change, no spec-shape change): MaterialOverridesSpec now derives from engine DrawMaterialOverrides; SignSpec and ObjectProperties.text now derive from AppearanceSignValue / AppearanceTextValue; ObjectProperties.model is spelled string | { id: string; animation?: DrawAnimationValue }; ObjectProperties.sprite is spelled inline with the same fields.
  • Schema cleanup: removed unused exports RectangleSplineShapeSchema (use SplineShapeSchema) and TerrainMarkSchema (use HeightmapTerrainMarkSchema). Voxel structure mark bounds is now optional and auto-derived from build() output when a generator is supplied.

pinned

what spawn isstart herefrequently asked questionsfaqthe spawn betthe bet

updates

Atelierengine v4.63 daysSurface Tensionengine v4.51 weekSolidengine v4.42 weeksGroovyengine v4.33 weeksContinuumengine v4.23 weeksFoundationsengine v4.1May 4, 2026Genesisengine v0.1April 29, 2026
← All posts