Releases: labelle-toolkit/labelle-engine
v1.49.0
v1.47.0 — flat-form unified loader (v2.0 prep)
First release of the v2.0 foundation. Engine loader now accepts both root-wrapped AND flat-form unified files (RFC #594).
Highlights
- #595 — Loader dual-accepts both shapes.
{ "root": { "components": {...} } }and{ "components": {...} }are equivalent. §B2 enforcement (RFC #560) still fires at the flat top-level. Surprise discovery: most of the loader's helpers (`rootObject`, `fileChildren`, etc.) were already shape-agnostic; only the file-root §B2 gate needed adjusting.
Compatibility
Both forms accepted, no warnings emitted. v2.0 (per engine#592) will remove the root-wrapped path along with the other legacy unified-format aliases.
Pairs with
- labelle-cli v1.44.0 — `labelle audit unification` detects `legacy_root_wrapper`; `labelle migrate unified` auto-fixes all 4 legacy patterns
- labelle-assembler v0.35.0 — pre-build scan also dual-accepts both forms + hybrid rejection
v1.46.0 — runtime helpers + io_helper Mutex fix
Consolidated release containing the work that was originally intended for v1.45.0 plus the fixes that surfaced after.
What changed
- #588 —
engine.ScreenshotRequest+engine.nowNs()env-var helpers for cli#227--screenshotflow. - #589 —
engine.runtime_env+engine.requestedScene()for cli#229--sceneruntime override. - #591 —
fix(io_helper):std.Thread.Mutexwas removed in Zig 0.16. Replaced withstd.atomic.Mutex+ tryLock spin. v1.45.0 shipped this bug; engine's own CI masked it viabuiltin.is_testshort-circuit, but downstream game compilation forced full evaluation and failed. - #590 — bump
zspecv0.9.1 → v0.9.2 to fix the Linux RegistryScanSpec hang (closes #583). Root cause was zspec v0.9.1 never initializingstd.testing.io_instance.
Compatibility
- Additive surface — no breaking changes vs v1.45.0's intended shape.
- v1.45.0 should not be used (broken on non-test builds). Either bump straight to v1.46.0 or pin to commit
8f6e898if you must use the v1.45.0 commit explicitly.
Pairs with
- labelle-assembler v0.33.0 — screenshot template + .initial_prefab + main_zig.zig refactor
- labelle-cli v1.42.0 —
--screenshot,--sceneruntime override,labelle audit unification
v1.45.0 — engine.ScreenshotRequest
Adds engine.requestedScreenshot() env-var reader + engine.nowNs() helper used by labelle-cli's --screenshot flag + assembler's screenshot template plumbing.
What changed
- New
src/screenshot_request.zigexposing:pub const Request = struct { path: [:0]const u8, after_sec: f32 = 0.0 }pub fn parse() ?Request— readsLABELLE_SCREENSHOT_PATH/LABELLE_SCREENSHOT_AFTER_SECvia libcgetenv(Zig 0.16 droppedstd.posix.getenv)pub fn nowNs() i128— wrapsclock_gettime(CLOCK_MONOTONIC)(Zig 0.16 droppedstd.time.nanoTimestamp)
- Re-exported from
src/root.zigasScreenshotRequest,requestedScreenshot,nowNs. - Bugbot finding (uninitialized
Timespec+ discardedclock_gettimereturn) addressed inline (#588).
Pairs with
- labelle-assembler v0.33.0 — template wiring for raylib + sokol backends (PR labelle-toolkit/labelle-assembler#210)
- labelle-cli v1.42.0 —
--screenshot+--afterCLI flags (PR labelle-toolkit/labelle-cli#233)
Compatibility
Additive — no changes to existing engine surface. Older projects that don't set the env vars get a no-op.
labelle-engine v1.44.0 — Unify Scenes and Prefabs (RFC #560)
labelle-engine v1.44.0 — Unify Scenes and Prefabs (RFC #560)
This release lands the unified scene/prefab format from RFC #560. Scenes and prefabs are now the same file format — both have a top-level "root" wrapper, prefab-reference child entries use "overrides" instead of "components", and §B2 enforces that reference-mode entries cannot declare children (authoring belongs in inline mode; instantiating belongs in reference mode).
Highlights
- Unified loader accepts both legacy
{ "entities": [...] }and the new{ "root": { "children": [...] } }shapes - Eager filesystem registry scan with collision detection — idempotent + transactional
- Deep-merge override semantics: shallow struct-field overlay;
nullremoval for components; whole-field replacement for nested struct/list fields - Shared entity-tree walker with cycle detection — used by asset inference, save/load, post-load hook dispatch, gizmo registration
- Standalone
Imagecomponent for entities viaAssetCatalog
Compatibility
Legacy entities / components / assets keys are still accepted (with deprecation warnings). The legacy form will be removed in a future major release.
Cross-repo coordination
This release pairs with:
- labelle-assembler v0.32.0 — pre-build scene scan accepts the unified format + the
.initial_scene→.initial_prefabrename - labelle-box2d v0.3.0 — adopts the new
Game.emit(GameEvents)signature introduced by #578 - labelle-cli v1.41.0 —
--scenewrites.initial_prefab
Projects using box2d must update to box2d v0.3.0 before bumping to engine v1.44.0.
Also in this release
v1.42.0 — Android: hide system bars at launch
Highlights
- Immersive mode now hides the system bars at launch (#567). v1.41.0 installed the immersive hook by chaining sokol's
onWindowFocusChangedfrom the render-threadinitcallback — too late to catch the window's first focus event, so the status + navigation bars stayed visible until the player background+foregrounded the app. The hook is now installed into theonContentRectChangedcallback slot, which sokol leaves unset and the framework fires on the UI thread once the content rect is established at launch. Verified on an Android 14 device:dumpsysreports bothITYPE_STATUS_BARandITYPE_NAVIGATION_BARinvisible ~0.3s after launch with no focus cycle; immersive-sticky re-apply on focus regain still holds.
Requires
- Zig 0.16.0
labelle-assemblerv0.30.0+ — the call must now be emitted intosokol_main()(UI thread), not the render-threadinit. Older assembler versions emit it intoinitand the bars will still stay visible until the first focus cycle.
Refs
PR #567. Builds on the v1.41.0 WindowInsetsController immersive path.
v1.41.0 — Android: hide the navigation bar (WindowInsetsController)
Highlights
- Android immersive mode now hides the navigation bar (#559). v1.40.0's runtime immersive used the legacy
View.setSystemUiVisibility, which on Android 11+ (API 30+) hides the status bar but no longer reliably hides the navigation bar.engine.androidnow usesWindowInsetsController.hide(WindowInsets.Type.systemBars())+setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE)on API 30+ — verified on an Android 14 device:dumpsysreports bothITYPE_STATUS_BARandITYPE_NAVIGATION_BARinvisible, full-height window frame, immersive-sticky re-apply holds across focus cycles. - API 28–29 fallback preserved: when
WindowInsetsControllerisn't available (Android 9–10), it falls back to the legacysetSystemUiVisibilityimmersive-sticky flags — so the project'smin_sdk = 28is fully supported, no regression for old devices.
Requires
- Zig 0.16.0
labelle-assemblerv0.29.0+ (emits theenableImmersiveMode()call).
Refs
PR #559. Supersedes the v1.40.0 immersive path for navigation-bar hiding.
v1.40.0 — Android runtime immersive mode
Highlights
- Android runtime immersive mode (#558) — new
engine.android.enableImmersiveMode(). When a project setsimmersive_mode = true, the running Android game hides both system bars (status + navigation) in immersive-sticky mode via JNI (setSystemUiVisibilitywith the immersive-sticky flag set). The legacy manifestTheme.NoTitleBar.Fullscreenis a no-op on Android 11+ — this is the runtime call that actually works on current devices.- UI-thread requirement solved by chaining the
ANativeActivityCallbacks.onWindowFocusChangedcallback — the framework invokes it on the UI thread with a validJNIEnv, and it doubles as the immersive-sticky re-apply hook on focus regain. - The callback globals are atomic (
std.atomic.Value) — no data race between the render thread (install) and UI thread (hook). - Verified on-device (Android 14 Samsung tablet): game renders fullscreen, no status/navigation bar, survives focus cycles without crashing.
- UI-thread requirement solved by chaining the
Requires
- Zig 0.16.0
labelle-assemblerv0.29.0+ (emits theenableImmersiveMode()call into the generated Androidmain.zigwhenimmersive_modeis set).
Refs
PR #558.
v1.39.0 — Android: drop Bionic-incompatible libc symbols
Highlights
libgame.sonowdlopens on Android. Removed two Bionic-incompatible libc symbol references that made the loader reject the shared object onNativeActivity.onCreate:shm_open/shm_unlink—preview_shm.zigused POSIX shared memory for the editor's Play-in-Editor pixel ring. Bionic doesn't ship those (Android uses ashmem). The PIE consumer never runs on-device, socreateMapping/openMapping/unlinkNamenow returnerror.ShmOpenFailed(or no-op) under a comptimeis_android_abiguard — Zig drops the dead branches and the extern refs vanish from the object file.__errno_location—preview_mode.zigdeclared the glibc errno thunk and called it for any non-macOS target. Bionic uses__errno; the file now branches onbuiltin.target.abi == .android.
Desktop / WASM / iOS code paths are untouched — the new branches only fire on aarch64-linux-android / arm-linux-androideabi.
Verification
flying-platform-labelle on Zig 0.16 with labelle-assembler v0.27.0 + labelle-imgui v0.5.0:
$ nm -D libgame.so | grep -iE 'shm_|errno'
U __errno@LIBC # Bionic's symbol, resolves at runtime
$ labelle android run
labelle: app launched on device
The full game renders on a Samsung Tab — sprite scene, workers, imgui Build menu.
Refs
Unblocks Flying-Platform/flying-platform-labelle#450 (PR #557).
v1.38.0 — FrameCapture trait + InputEvent ring
Minor release shipping the preview pipeline's backend-agnostic frame-capture trait and the editor → game input channel (#140 + #143).
New public API
engine.preview_capture.FrameCapture— vtable +publishFrame(producer, capture, stamp_now)for backend-side frame producers. Replaces ~200 lines of inlined backend-specific code that previously lived in the labelle-assembler codegen templates. Backend authors implement a single trait shape; the engine handles the SHM / IOSurface plumbing.Preview.InputEvent— tagged union (mouse_pos,mouse_button) parsed from editor JSON frames on the existing control socket.Preview.popInputEvent()— game frame loops drain pending events from a 256-slot ring buffer. Overflow drops oldest. Codegen template wires this through to the imgui sokol bridge'ssimgui_add_*_event(labelle-imgui#10) so games receive editor mouse activity in headless preview.PublishError.SizeMismatch—publishFramenow validates slot capacity (width * height * 4≤slot_size - sizeof(SlotTrailer)) and short-circuits with this error before invoking the backend's capture fn. Catches post-initproducer.optsdrift / header tampering.
Bug fixes
io_helper.io()on wasm32-emscripten — returnsstd.Io.failinginstead of instantiatingstd.Io.Threaded. Avoids the upstream Zig 0.16 stdlib bug (ziglang/zig#31849, fixed by PR #31850). Asset / scene load paths on wasm surface a cleanFailederror rather than a compile failure. Remove this gate when the toolkit moves off 0.16.
Pairs with
- labelle-imgui#10 — sokol bridge with
SOKOL_IMGUI_NO_SOKOL_APP+ per-event-type simgui dispatch (mouse / scroll / keyboard / touch / focus / leave). - labelle-assembler#141 — headless preview mode + codegen drains the InputEvent ring + WASM panic-handler workaround.
- labelle-gui#146 — Game View tab forwards mouse over the IOSurface texture to the new
sendMousePos/sendMouseButtonprotocol messages.
Tests: 437/437 pass on macOS + Linux (incl. new publishFrame size-mismatch regression test).