Repository navigation
v0.4.0
Pre-release
⚠️ Withdrawn on Windows — marked pre-releaseWindows builds of 0.4.0 have a serious bug: after the first project you open, every
subsequent Open Project silently loads the previous project's code. A stale Vite server
keeps port 5173, the new one dies on startup, and the editor then reports one project while
serving another — so edits can be saved against the wrong project's tree.This release is marked pre-release so the auto-updater holds installs at v0.3.8, which is
unaffected. Windows users should stay on or roll back to v0.3.8 until 0.4.1.macOS is not affected — the regression is in the Windows-only process-kill path.
A fix is in progress and will ship as 0.4.1.
A large release — a whole new media subsystem, variable fonts, and a sustained push on low-end device performance.
✨ Features
Video is a first-class asset. .mp4 registers as an asset kind with its own converter, settings source-of-truth and Inspector. A VideoPlayer can drive a 3D screen, a 2D PixiJS sprite, a UI node, or a fullscreen cutscene overlay that can't softlock on skip. Clips play from the bundle or download from a remote host into a resumable LRU cache; playback couples to timeScale, emits events, and answers to declarative video.* actions. A Timeline video track lets a Director sequence a cutscene. The new video-demo project tours every surface.
Variable fonts. The bake can finally pick a weight — axis instancing at build time, and a dynamic path that honours the sidecar's axes, size, pxRange and charset. The Font Inspector offers a font's real variation axes and stops offering what it can't honour. Every bundled engine font is now GUID-bearing.
Quality tiers and a boot ramp probe. A device capability probe keyed per platform, a tier resolver with a change policy, and the tier exposed in Project Settings with a live preview in the debug menu. At boot, a ramp probe measures throughput and caches its verdict; iOS skips measuring entirely and resolves from the model id. Validated against five real phones, which said the population is three bands.
A frame-time profiler. Marker hierarchy with aggregation, GPU timestamp queries that split rest-time into GPU-busy vs idle, counters, frame capture and scrub exported as plain JSON, and a Profiler panel in the editor — plus an agent-facing surface.
Rendering. Per-layer light masks (measured 7–12× on a low-end Android), LOD chains, BlobShadow as a grounding cue for tiers that can't afford real shadows, and GPU context-loss recovery that rebuilds the renderer in both viewports.
Shadows you can author. The directional shadow box now follows the view — or centres on an entity you nominate — so a walking subject stops losing its shadow. Rigged characters cast and receive properly, and shadow flags are authorable.
Haptics. A new engine subsystem with native provisioning, a HapticSettings trait and declarative haptics.* actions.
Building for devices. Native targets scaffold from the CLI or the editor, and the Build menu now picks the iOS/Android target and the specific device, starting the build from the selection. Shipped floors raised to iOS 16.4 / Android 12, with build.iosMinVersion driving both the JS target and the Xcode deployment target.
🐛 Fixes
- Assets — renaming, moving or deleting an asset forked it: the bound editor kept writing to the old path, and autosave wrote a deleted file back.
- Prefabs — prefab-edit lost its session on exit, on save and on Play/Stop; a prefab serialized blank asset refs; 2D prefabs previewed blank; a Transform-only group got no 2D gizmo, so it couldn't be dragged.
- Text — MTSDF glow was inert, outline flooded the quad, and shadow read neighbouring glyphs. GLSL float literals rounded to 1dp rendered glyphs as NaN; the msdf wasm never shipped at all; a dynamic font could rasterize another font's outlines; and a missing asset behind the worker handshake hung the whole game rather than failing.
- Scenes — a stopped editor baked runtime state into authored entities, and system-spawned entities were saved as authored content. Asset refs held on game traits and introduced by prefab-instance overrides are now collected.
- Skinning —
removeBonerenumbered the skeleton but not the parts, and a vertex bound only to the deleted bone collapsed to the origin. The weight brush is a Skin-panel gesture, not a SceneView one. - Builds — a build step's grandchildren outlived the abort meant to kill them; the shutdown path SIGKILLed a child in the same tick it SIGTERMed it; stopping Vite on Windows now takes its build tree with it. Builds serialize server-side behind a lock.
- Config privacy — private build fields (Apple Team ID, device ids, deploy targets) moved out of the committed project config, and out of the shipped bundle.
- Editor chrome — audio/texture/environment inspectors misreported an off-list setting, the Game toolbar wrapped out of its own bar, and Ctrl+C on macOS jumped the selection instead of doing nothing.
⚡ Performance
Sustained work on a low-end Android target, all measured on real hardware rather than estimated: suppressing IBL on the low tier took one scene from 20 → 44 fps; welding static visuals cut 709 scene objects to 55; a field floor was spending 184k triangles on flat ground. Transform propagation no longer pays full price twice a frame on a frozen scene, dynamic fonts seed from their bake and only generate on a miss, and the local test gate dropped from 245s to ~110s.
Two things were measured and declined: draw-call batching (works — 235 calls → 38 — and buys nothing; ships default off) and pointer prediction (the ON state was worse than OFF).
📝 Other notable
- ECS gained
spawnEntity/destroyEntity, making the index pairing impossible to forget. - Android honours
capacitor.statusBarHiddenfor immersive fullscreen. - Device tooling can pick which phone and claim it machine-wide, write as well as read, and record an input watch so a missed gesture leaves evidence.
Full Changelog: v0.3.8...v0.4.0