v0.12.1
Sprite Tiling
The Sprite component now supports tileSize and tileSpacing for repeating a texture across a larger area:
- Set
tileSizeto the desired tile dimensions (e.g.{x: 64, y: 64}) — the texture repeats to fill the spritesize - Set
tileSpacingto add gaps between tiles (e.g.{x: 4, y: 4}) — appears in the Inspector only whentileSizeis non-zero - Both modes emit N×M quads auto-merged into a single draw call via the batch system
- Respects sprite flip and pivot settings
MCP on Startup Page
MCP tools are now available before opening a project:
- Bridge server starts in Tauri
setup()before the frontend loads - New MCP tools on the startup page:
get_editor_status,list_recent_projects,list_examples,open_project,create_from_example - New MCP build tools:
build_project(trigger playable/wechat build) andlist_build_configs(query available configs) LauncherBridgehandles project management tools; editor-mode tools lazy-discover the editor on first call
Unified Script Compilation
Script compilation is now handled by a single ScriptCompiler class, merging two previously divergent VirtualFS plugins and SDK resolution strategies:
- Play Mode uses incremental compilation via esbuild context
- Build export uses one-shot compilation
- Circular dependency between
ScriptLoaderandScriptCompilerbroken by extractingscriptDiscovery.ts
SDK Core Decomposition
App Slimdown
app.ts reduced from 1090 to 729 lines by extracting the camera system and math utilities:
- CameraPlugin (
camera/CameraPlugin.ts): multi-camera collection, ortho/perspective projection, Canvas scale mode adaptation, and the render system are now a standalone plugin - mat4 (
math/mat4.ts):ortho,perspective,invertTranslation,multiplyextracted as pure functions with pooled Float32Array buffers createWebApp()is now a pure assembly function — plugin registration only, no inline system definitions
World Facade Refactoring
world.ts reduced from 1101 to 674 lines by splitting into 5 focused internal classes:
| Module | Responsibility |
|---|---|
ecs/BuiltinBridge | C++ Registry method cache, ptr layout direct reads/writes, color format conversion |
ecs/ScriptStorage | TypeScript Map-based component storage, entity-component tracking |
ecs/NameIndex | Bidirectional name-to-entity lookup |
ecs/ChangeTracker | Tick-based added/changed/removed detection |
ecs/QueryCache | Cached entity queries with component-aware invalidation |
All public World API signatures remain identical — the split is internal (Facade pattern).
AppContext Replaces globalThis
Three globalThis.__esengine_* implicit coupling points replaced with an explicit AppContext:
getDefaultContext().componentRegistryreplacesglobalThis.__esengine_componentRegistrygetDefaultContext().pendingSystemsreplacesglobalThis.__esengine_pendingSystemsgetDefaultContext().editorBridgereplacesglobalThis.__esengine_registerComponent
Enables multi-app instance isolation and proper test cleanup via setDefaultContext().
Component-Aware Query Cache
The query cache previously invalidated all cached queries on any entity change. Now each cache entry tracks which component types it depends on, and only invalidates when a relevant component changes.
Editor Architecture
EditorEventBus
A new typed pub-sub event bus replaces 6 manual listener sets on EditorStore:
- Immediate delivery via
emit()for structural changes - Batched delivery via
emitBatched()usingrequestAnimationFrame— same-type events within a frame are merged (last-write-wins) - Events:
selection:changed,scene:synced,property:changed,hierarchy:changed,entity:lifecycle,component:changed,visibility:changed,tiletool:changed,gizmo:requested
Store Decomposition
EditorStore split into focused stores:
- SelectionStore: entity/asset selection state, focus tracking
- TileToolStore: tile brush tool, stamp, flip state
- EditorStore (slimmed): scene data, command history, world transform cache — notifications routed through EventBus
All stores registered in the IoC container with typed tokens.
IncrementalSync
Property edits in the editor now use field-level WASM direct writes instead of full component re-adds:
- Utilizes
PTR_LAYOUTSto write individual fields directly toHEAPF32/HEAPU32/HEAPU8 - Transform drag operations write only
position.x/y/z(3 floats) instead of the full 20+ field component - Falls back to full component sync for components without ptr layouts
Build Pipeline
- WASM build cache hash validation: cache now computes a hash of all C++/HPP source files and
CMakeLists.txt, invalidating on source changes instead of only checking output existence - Engine source resolution: dev mode now prefers the live project root
src/esengine/over the stale toolchain copy, ensuring new components are included in builds - Toolchain manifest: added yoga library to
toolchain.manifest.jsonfor UILayoutSystem flex support - Playable build fix: replaced hardcoded
COMPONENT_TO_PLUGINmapping (missing 10+ components) with SDK’suiPluginsarray as single source of truth
Bug Fixes
- Fixed
Assets.baseUrlnot propagating toHttpBackendforloadTexture,loadMaterial,loadFont,loadSpineand all Loader-based methods — onlyfetchJsonworked previously - Fixed
Assets.fetchJson/fetchText/fetchBinarynot resolving relative paths throughbaseUrl, causing Play Mode JSON loading to fail in Tauri - Fixed
HttpBackend.resolveUrlonly recognizinghttp:///https://— now handlesasset://,blob://, and other URI schemes - Fixed
UIRenderOrderPluginrequiringCollectionViewplugin to be installed — execution order is already guaranteed by system-levelrunBefore/runAfter - Fixed WASM toolchain sync copying root
CMakeLists.txtwhich references unavailable yogacore sources - Fixed stale WASM builds when new components are added to
WebBindings.generated.cpp - Fixed Assets API documentation referencing non-existent
loadJson/loadText/loadBinarymethods (actual API isfetchJson/fetchText/fetchBinary) - Fixed Sprite seamless tiling (
tileSpacing = 0) rendering only a single tile — textures default toClampToEdge, now uses quad-based tiling for both modes - Fixed Inspector
visibleWhen/hiddenWhenrules not updating dynamically when controlling property changes — previously required deselecting and reselecting the entity - Removed
worldPosition,worldRotation,worldScalefrom Transform Inspector — these are system-computed read-only values