Repository navigation
feat: absorb backends, ecs adapters, and gui plugins from labelle-cli - #7
Conversation
Stage 1 of extracting backends from labelle-cli (see labelle-toolkit/labelle-cli#147 and #5). raylib moves here; the four remaining backends stay in labelle-cli until their own stages. Adds a new assembler cache slot at ~/.labelle/packages/assembler/{ver}/ alongside the existing cli/ slot. resolveBundledPackage tries the assembler slot first and falls back to the cli slot for backends not yet migrated, so the two halves of the migration compose. The resolver takes both cli_version and assembler_version so that local: path overrides can point each slot at a different sibling repo during dev (e.g. labelle-cli vs labelle-assembler). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stage 2 of #147 / #5. Moves the sokol backend (desktop + mobile + wasm templates, Android/iOS lifecycle glue, stb_image vendored sources, standalone example) into the assembler alongside raylib. No resolver changes — the Stage 1 plumbing handles this automatically. The three remaining backends (bgfx, sdl, wgpu) still live in labelle-cli and resolveBundledPackage falls back to the cli cache slot for them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
resolveAssemblerPackage routes local: versions through resolveLocalPath, which logs "local path '...' does not exist" on misses. During the staged backend migration every un-migrated backend (bgfx/sdl/wgpu today) triggers that warning before the CLI-slot fallback succeeds, polluting the output. Replace the probe with a silent helper that computes the candidate path inline and only returns it when the directory exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
With Stage 5 of the backend migration complete, all five backends live in labelle-assembler/backends/. The CLI-slot fallback in resolveBundledPackage and the probeBundledSlot helper that silenced it are both dead for backends. Remove them — resolveBundledPackage now just picks the right version key (assembler_version orelse cli_version) and calls resolveAssemblerPackage directly. Also drops the "backends" entry from populateCliCache and fetchCliPackages so the CLI cache slot stops trying to symlink/copy a directory that no longer exists in labelle-cli. ECS and GUI remain in the CLI slot until their own stages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stage 7 of #147 / #5. Moves zig-ecs, zflecs, and mr-ecs out of labelle-cli/ecs/ into this repo. Flips the two call sites (build_files and deps_linker) from resolveCliPackage to resolveBundledPackage so ECS picks up from the assembler cache slot like backends. Adds the standalone zig-ecs adapter test step (cd ecs/zig-ecs && zig build test) to this repo's CI — it previously lived in labelle-cli's CI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stage 8 of #147 / #5 — the last content move. Brings the five GUI plugins (clay, raylib-imgui, simple-raylib, simple-sokol, sokol-imgui) into this repo. The raylib-imgui and sokol-imgui build.zig files reach into the sokol/raylib backend src/; their relative paths go from ../../../labelle-assembler/backends/... back to the natural ../../backends/... since both now live under the same repo root. With gui moved, the labelle-cli companion directory has no content left. Removes the entire dead cli-slot machinery from cache.zig: resolveCliPackage, populateCliCache, fetchCliPackages, isCliCached, and the validateCache check for cli packages. Also drops the matching re-exports from root.zig. ECS and GUI in resolveBundledPackage still accept cli_version as a fallback key — kept for the case where cfg.assembler_version is null (production ships the two versions in lockstep). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR SummaryMedium Risk Overview Adds substantial new backend implementations and demo/example projects (notably a full Updates CI to run the additional Reviewed by Cursor Bugbot for commit aa69c1b. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Code Review
This pull request introduces significant updates to the backend and ECS adapter infrastructure, including the addition of new backends (bgfx, raylib, sdl, sokol, wgpu) and ECS adapters (mr-ecs, zig-ecs). The changes also include a migration from CLI-bundled packages to an assembler-based cache system. My review identified several critical issues: hardcoded paths in the SDL build file, platform-specific window handle logic in the bgfx backend, memory leaks in the sokol audio backend, inefficient view implementation in the zig-ecs adapter, and potential integer overflow vulnerabilities in the wgpu audio WAV parser.
| const sdl_include = std.Build.LazyPath{ .cwd_relative = "/opt/homebrew/include" }; | ||
| const sdl_lib = std.Build.LazyPath{ .cwd_relative = "/opt/homebrew/lib" }; |
There was a problem hiding this comment.
The include and library paths for SDL2 are hardcoded to a specific macOS Homebrew location (/opt/homebrew/...). This will cause build failures on Linux, Windows, Intel-based Macs (which use /usr/local), or any system where Homebrew is installed in a non-standard location. Consider using b.dependency to manage SDL2 or at least providing a way to override these paths via build options.
| init.resolution.height = @intCast(height); | ||
| init.resolution.reset = 0x00000080; // BGFX_RESET_VSYNC | ||
| init.platformData.ndt = null; | ||
| init.platformData.nwh = glfw.getCocoaWindow(win); |
There was a problem hiding this comment.
The native window handle is hardcoded to use glfw.getCocoaWindow(win), which is specific to macOS. This will prevent the code from compiling or running correctly on Windows or Linux. You should use a conditional check based on builtin.target.os.tag to retrieve the appropriate native handle for the target platform.
init.platformData.nwh = switch (builtin.target.os.tag) {
.macos => glfw.getCocoaWindow(win),
.windows => glfw.getWin32Window(win),
.linux => glfw.getX11Window(win),
else => null,
};
| // of sample memory happens at shutdown. | ||
| sounds[id] = null; | ||
| } | ||
| } |
There was a problem hiding this comment.
This line causes a memory leak. By setting the sound slot to null without freeing the underlying samples buffer, the reference to the allocated memory is lost. The deinit function (lines 81-86) only iterates over non-null slots, so it will not be able to reclaim this memory later. Even if a race condition exists with the audio thread, you must track these allocations (e.g., in a 'pending free' list) to ensure they are eventually deallocated. The same issue exists in unloadMusic at line 437.
| pub fn view(self: *Self, comptime includes: anytype, comptime excludes: anytype) View(includes, excludes) { | ||
| var result: std.ArrayListUnmanaged(Entity) = .{}; | ||
|
|
||
| if (includes.len == 1 and excludes.len == 0) { | ||
| const basic = self.inner.basicView(includes[0]); | ||
| var iter = basic.entityIterator(); | ||
| while (iter.next()) |internal| { | ||
| result.append(self.inner.allocator, toExternal(internal)) catch @panic("OOM"); | ||
| } | ||
| } else { | ||
| var multi = self.inner.view(includes, excludes); | ||
| var iter = multi.entityIterator(); | ||
| while (iter.next()) |internal| { | ||
| result.append(self.inner.allocator, toExternal(internal)) catch @panic("OOM"); | ||
| } | ||
| } | ||
|
|
||
| return .{ | ||
| .entities = result.toOwnedSlice(self.inner.allocator) catch @panic("OOM"), | ||
| .allocator = self.inner.allocator, | ||
| }; | ||
| } |
There was a problem hiding this comment.
The view implementation is extremely inefficient for a game engine. It performs a full O(N) scan of all entities and allocates a new ArrayList (materialized as a slice) every time it is called. In a typical ECS usage pattern, views are created every frame, leading to significant performance degradation and memory pressure. This should be refactored to wrap the underlying zig-ecs view iterators directly, avoiding allocations and leveraging the ECS's optimized query structures.
| if (fmt_found and data_found) break; | ||
|
|
||
| // Advance to next chunk (chunks are 2-byte aligned) | ||
| pos = chunk_data_start + chunk_size; |
There was a problem hiding this comment.
Potential integer overflow and out-of-bounds logic in the WAV chunk parser. If a corrupted WAV file provides a very large chunk_size, the addition chunk_data_start + chunk_size can overflow usize (especially on 32-bit platforms). This could lead to an infinite loop or incorrect memory access in subsequent iterations. Validate that the chunk fits within the bytes_read bounds before advancing pos.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 530c2c4. Configure here.
| init.resolution.height = @intCast(height); | ||
| init.resolution.reset = 0x00000080; // BGFX_RESET_VSYNC | ||
| init.platformData.ndt = null; | ||
| init.platformData.nwh = glfw.getCocoaWindow(win); |
There was a problem hiding this comment.
bgfx window hardcodes macOS-only native window handle
High Severity
glfw.getCocoaWindow(win) is unconditionally called to obtain the native window handle, but this function only exists on macOS. On Linux or Windows, a different GLFW function is needed (e.g., getX11Window, getWin32Window). The builtin import on line 3 is present but unused, suggesting platform-conditional logic was intended but never implemented. This makes the entire bgfx backend unusable on non-macOS platforms.
Reviewed by Cursor Bugbot for commit 530c2c4. Configure here.
| sounds[id] = snd; | ||
| next_sound_id += 1; | ||
| return id; | ||
| } |
There was a problem hiding this comment.
Raylib audio never recycles freed sound/music slots
Medium Severity
Unlike the bgfx and SDL audio backends which both implement findFreeSoundSlot()/findFreeMusicSlot() to scan for previously-freed null slots, the raylib loadSound and loadMusic only ever increment next_sound_id/next_music_id. After repeated load/unload cycles, IDs will be exhausted even though freed slots exist, causing all subsequent loads to return 0.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 530c2c4. Configure here.
| const optimize = b.standardOptimizeOption(.{}); | ||
|
|
||
| const sdl_include = std.Build.LazyPath{ .cwd_relative = "/opt/homebrew/include" }; | ||
| const sdl_lib = std.Build.LazyPath{ .cwd_relative = "/opt/homebrew/lib" }; |
There was a problem hiding this comment.
SDL build hardcodes macOS Homebrew library paths
Medium Severity
The SDL include and library paths are hardcoded to /opt/homebrew/include and /opt/homebrew/lib, which are specific to Apple Silicon macOS with Homebrew. This breaks builds on Linux, Windows, and even Intel Macs where Homebrew installs to /usr/local. These paths are baked into the module definitions and propagated to all consumers of the SDL backend.
Reviewed by Cursor Bugbot for commit 530c2c4. Configure here.
The docker-build and versions-integration jobs invoke the CLI end-to-end with init + generate. With backends/ecs/gui moved to labelle-assembler, the default assembler_version that `labelle init` writes (v0.2.0) points to a released binary that pre-dates this refactor and still looks for backends in labelle-cli — so the test project fails to generate with "template not found". Fix both jobs by checking out labelle-assembler as a sibling, building it in-place, pointing LABELLE_ASSEMBLER at the fresh binary, and passing --assembler-version="local:../../labelle-assembler" to labelle init so the generated project.labelle resolves backends against the sibling repo. The checkout is temporarily pinned to the paired feat/absorb-backends branch — revert to main once labelle-toolkit/labelle-assembler#7 merges. The three test project.labelle fixtures (plugin-manifest-test, gui-plugin-test, nuklear-plugin-test) also gain the assembler_version local: override so they resolve correctly in monorepo dev. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR completes the migration of assembler-wired runtime content (backends, ECS adapters, GUI plugins) into labelle-assembler, and updates cache resolution to use a new ~/.labelle/packages/assembler/<version>/ slot (with transitional version-key handling via assembler_version).
Changes:
- Reworked cache APIs from CLI-bundled packages to assembler-bundled packages (
resolveAssemblerPackage/resolveBundledPackage,populateAssemblerCache,fetchAssemblerPackages). - Updated generator/linker code paths to resolve bundled runtime content from the assembler cache slot.
- Added the migrated backend/ECS/GUI package sources (plus examples) and expanded CI to run zig-ecs adapter tests.
Reviewed changes
Copilot reviewed 84 out of 87 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/root.zig | Re-exports updated cache APIs and resolves backend templates via bundled-package resolver. |
| src/deps_linker.zig | Links backend/ECS deps using resolveBundledPackage instead of CLI cache. |
| src/cache.zig | Introduces assembler cache slot resolution, population, and fetching; updates cache validation. |
| src/build_files.zig | Uses bundled-package resolver for backend/ECS path generation in fallback zon generation. |
| gui/sokol-imgui/src/adapter.zig | Adds sokol Dear ImGui GUI adapter module. |
| gui/sokol-imgui/build.zig.zon | Declares sokol-imgui GUI plugin package metadata/deps. |
| gui/sokol-imgui/build.zig | Builds sokol-imgui module and re-exports sokol backend modules/artifact. |
| gui/simple-sokol/src/adapter.zig | Adds simple immediate-mode GUI backend for sokol. |
| gui/simple-sokol/build.zig.zon | Declares simple-sokol GUI plugin package metadata/deps. |
| gui/simple-sokol/build.zig | Builds simple-sokol GUI module. |
| gui/simple-raylib/src/adapter.zig | Adds simple immediate-mode GUI backend for raylib. |
| gui/simple-raylib/build.zig.zon | Declares simple-raylib GUI plugin package metadata/deps. |
| gui/simple-raylib/build.zig | Builds simple-raylib GUI module. |
| gui/raylib-imgui/src/adapter.zig | Adds raylib Dear ImGui GUI adapter module. |
| gui/raylib-imgui/build.zig.zon | Declares raylib-imgui GUI plugin package metadata/deps. |
| gui/raylib-imgui/build.zig | Builds rlImGui bridge C++ static lib + GUI module; re-exports raylib backend modules/artifact. |
| gui/clay/src/adapter.zig | Adds Clay GUI adapter module. |
| gui/clay/build.zig.zon | Declares clay GUI plugin package metadata/deps. |
| gui/clay/build.zig | Builds clay GUI module. |
| ecs/zig-ecs/test/adapter_test.zig | Adds zig-ecs adapter tests. |
| ecs/zig-ecs/src/adapter.zig | Adds zig-ecs adapter implementation for engine ECS contract. |
| ecs/zig-ecs/build.zig.zon | Declares zig-ecs adapter package metadata/deps. |
| ecs/zig-ecs/build.zig | Builds zig-ecs adapter module and test step. |
| ecs/zflecs/src/adapter.zig | Adds zflecs adapter implementation. |
| ecs/zflecs/build.zig.zon | Declares zflecs adapter package metadata/deps. |
| ecs/zflecs/build.zig | Builds zflecs adapter module. |
| ecs/mr-ecs/src/adapter.zig | Adds mr-ecs adapter implementation. |
| ecs/mr-ecs/build.zig.zon | Declares mr-ecs adapter package metadata/deps. |
| ecs/mr-ecs/build.zig | Builds mr-ecs adapter module. |
| backends/wgpu/templates/desktop.txt | Adds wgpu backend lifecycle template. |
| backends/wgpu/src/window.zig | Adds wgpu window module skeleton (GLFW-based). |
| backends/wgpu/src/input.zig | Adds wgpu input module (GLFW-based). |
| backends/wgpu/src/audio.zig | Adds wgpu audio module (software WAV mixer). |
| backends/wgpu/example/build.zig.zon | Adds wgpu backend demo package metadata. |
| backends/wgpu/example/build.zig | Adds wgpu backend demo build script. |
| backends/wgpu/build.zig.zon | Declares wgpu backend package metadata/deps. |
| backends/wgpu/build.zig | Builds wgpu backend modules and re-exports native artifacts. |
| backends/sokol/templates/mobile.txt | Adds sokol mobile lifecycle template. |
| backends/sokol/templates/desktop.txt | Adds sokol desktop lifecycle template. |
| backends/sokol/src/window.zig | Adds sokol window module. |
| backends/sokol/src/stb_image_impl.c | Adds stb_image implementation for sokol gfx. |
| backends/sokol/src/input.zig | Adds sokol input module. |
| backends/sokol/src/audio.zig | Adds sokol audio module (saudio mixer). |
| backends/sokol/example/build.zig.zon | Adds sokol backend demo package metadata. |
| backends/sokol/example/build.zig | Adds sokol backend demo build script. |
| backends/sokol/example/assets/characters_frames.zon | Adds sokol demo sprite atlas frame data. |
| backends/sokol/example/assets/characters_animations.zon | Adds sokol demo animation data. |
| backends/sokol/build.zig.zon | Declares sokol backend package metadata/deps. |
| backends/sokol/build.zig | Builds sokol backend modules and re-exports native artifact + emscripten helpers. |
| backends/sdl/templates/desktop.txt | Adds SDL backend lifecycle template. |
| backends/sdl/src/window.zig | Adds SDL window module. |
| backends/sdl/src/sdl.zig | Adds shared SDL2 @cImport module. |
| backends/sdl/src/input.zig | Adds SDL input module. |
| backends/sdl/src/audio.zig | Adds SDL audio module (SDL_mixer). |
| backends/sdl/example/build.zig.zon | Adds SDL backend demo package metadata. |
| backends/sdl/example/build.zig | Adds SDL backend demo build script. |
| backends/sdl/build.zig.zon | Declares SDL backend package metadata. |
| backends/sdl/build.zig | Builds SDL backend modules and shared SDL import module. |
| backends/raylib/templates/wasm.txt | Adds raylib WASM lifecycle template. |
| backends/raylib/templates/desktop.txt | Adds raylib desktop lifecycle template. |
| backends/raylib/src/window.zig | Adds raylib window module. |
| backends/raylib/src/input.zig | Adds raylib input module. |
| backends/raylib/src/gfx.zig | Adds raylib gfx module. |
| backends/raylib/src/audio.zig | Adds raylib audio module. |
| backends/raylib/build.zig.zon | Declares raylib backend package metadata/deps. |
| backends/raylib/build.zig | Builds raylib backend modules and re-exports native artifact + emsdk helpers. |
| backends/bgfx/templates/desktop.txt | Adds bgfx backend lifecycle template. |
| backends/bgfx/src/window.zig | Adds bgfx window module (GLFW + bgfx). |
| backends/bgfx/src/input.zig | Adds bgfx input module (GLFW-based). |
| backends/bgfx/src/audio.zig | Adds bgfx audio module (software WAV mixer). |
| backends/bgfx/example/main.zig | Adds bgfx backend demo program. |
| backends/bgfx/example/build.zig.zon | Adds bgfx backend demo package metadata. |
| backends/bgfx/example/build.zig | Adds bgfx backend demo build script. |
| backends/bgfx/build.zig.zon | Declares bgfx backend package metadata/deps. |
| backends/bgfx/build.zig | Builds bgfx backend modules and re-exports native artifacts. |
| .github/workflows/ci.yml | Adds CI step to run zig-ecs adapter tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Assembler-bundled packages (backends, ecs, gui) | ||
| if (!try isAssemblerCached(allocator, cfg.labelle_version)) { | ||
| try missing.append(allocator, try std.fmt.allocPrint(allocator, "assembler {s}", .{cfg.labelle_version})); | ||
| } |
There was a problem hiding this comment.
validateCache checks the assembler-bundled cache slot using cfg.labelle_version only. When cfg.assembler_version is set to a different version, this will report the wrong cache state (and can block generation even though resolveBundledPackage will look under assembler_version). Consider using (cfg.assembler_version orelse cfg.labelle_version) for the assembler cache check and for the missing-package message.
Addresses PR #7 review feedback from Cursor Bugbot (high severity) and Copilot. validateCache was always keying the assembler-slot check on cfg.labelle_version, but ensureCache in labelle-cli uses the correct selection logic: cfg.assembler_version orelse cfg.labelle_version. When a project pins assembler_version to a value different from labelle_version, validateCache would probe the wrong cache slot and report the wrong package as missing (and ensureCache would then fetch the right one anyway, so runtime still works — but the reported state lied). Match ensureCache's selection so both agree. Also drops a stale comment in loadBackendTemplate that still described the old "prefer assembler, fall back to CLI" dual-slot probe — the fallback was removed in the Stage 6 cleanup (commit 83ef543). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0.3.0 is the first assembler release to bundle backends, ecs adapters, and gui plugins (see labelle-toolkit/labelle-assembler#7). New projects created via \`labelle init\` after this commit will pin to 0.3.0 and auto-download the right binary on first generate. Safe for CI on this branch because both docker-build and versions-integration jobs pass \`--assembler-version="local:..."\` to \`labelle init\`, which overrides the default. Follow-up after labelle-assembler#7 merges: - Cut labelle-assembler v0.3.0 release on GitHub - Revert the CI assembler checkout pin from feat/absorb-backends to main (commit 3f5bc3c) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
labelle-toolkit/labelle-assembler#7 is merged and v0.4.0 is tagged with the absorbed runtime content (backends, ecs, gui). This commit: 1. Drops the temporary feat/absorb-backends pin on the assembler checkout in ci.yml — CI now resolves against the default branch (main), which has the merged content. 2. Updates DEFAULT_ASSEMBLER_VERSION from 0.3.0 to 0.4.0. The 0.3.0 bump in commit 6526383 was wrong: v0.3.0 already existed before this refactor (the "animations directory scanning" release) and does not contain the absorbed packages. v0.4.0 is the first release that does. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
chatgpt-codex raised 8 findings on PR #459 not covered by the #456 round. Verified each against the real code and folded the valid ones in: - root_build_deps now carry required resolution (url+hash/path/builtin); emsdk is a pinned template section, not name-synthesizable (#2) - bgfx-android android_app extra module requires root_alias="backend_app" (#3) - hookless mobile uses an assembler-owned default resolve_target; the backend-agnostic resolver means .resolved does not force a hook (#4) - build_hook must be a dedicated backend.hook.zig, not the provider build.zig (top-level @import("sokol") re-exports don't resolve in the root package) (#5) - android_target_sdk is required for Android; post_wire panics instead of the silent orelse 34 fallback (#6) - golden gate strengthened for hook-bearing cells: snapshot hook source and/or run the hook against a fixture *std.Build (#7) - carried v1 .capabilities forward into the v2 schema so opting into v2 doesn't bypass capability negotiation (.id was already present) (#8) Finding #1 (dep-option removal) was already resolved by ea24373 (base = universal options, per-platform = appends, no subtractive form) — recorded, not re-edited. Added a "PR #459 corrections" section documenting each. Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
* docs(#453): fold PR #456 review findings into manifest-v2 design Revise the build-graph manifest v2 design doc to address the substantive coderabbitai/chatgpt-codex findings that PR #456 merged without incorporating, so the #453 item-3 implementation does not inherit the design flaws. Central corrections (all verified against the real code): - Dependency options are declarative (DepOption name + closed ValueSource predicate set), NOT a runtime pre_wire hook returning []Flag — a b.dependency options literal needs comptime-known field names. pre_wire/DependencyOptions deleted. - Target selection is a pre-dependency resolve_target phase (iOS device/sim + SDK, Android ABI) resolved from -Ddevice/-Demulator/-Dandroid_arch + host, not a static .triple; iOS SDK now computed before plugin b.dependency calls. - Core-diamond walk carries a gfx_mod singleton so it preserves engine->gfx. - Header-first bounded version parse (v1 stays readable, > SUPPORTED rejected). - Preserve backend_* import aliases; per-platform loop_style/artifacts/link_libc; root_build_deps for the emsdk wasm hook; android_target_sdk into HookContext. - Hook reframed as trusted build code (not mechanically sandboxable). - Byte-identical gate -> one desktop anchor + golden snapshots; packager PR moved before Android/wasm conversions. Docs-only. Adds a "Review corrections (PR #456)" summary section. Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw * docs(#453): specify dep_options merge semantics + fix code-span spacing Address CodeRabbit findings on PR #459. Major — dep_options merge is now defined precisely: a comptime, name-keyed fold of per-platform entries over the base (override on collision, append otherwise), with NO subtractive form (an empty per-platform list inherits the base unchanged). Grounded in the v1 paramValue/param_names mechanism (manifest_splice.zig): the merge is a codegen-time operation on the NAME set, and because each platform's b.dependency literal is emitted independently, a name absent from a platform's folded set is simply never written. Corrected the sokol worked example to match build_zig.txt ground truth (:94/:124/:535/:763): with_imgui is the only base option (common to all four platforms); gamepad_* is a desktop-only append; dont_link_system_libs is an ios/android append (android's was missing). This dissolves the empty-wasm-list "drop" conflict — wasm forwards only with_imgui because gamepad_* was never in the base, not by removing it. Updated the "Review corrections" section to note the clarification. Minor — fixed inline code spans that wrapped across lines (MD038): the version gate spans (`< 1 or > SUPPORTED`, `2 <= v <= SUPPORTED_MANIFEST_VERSION`, `v > SUPPORTED`) and the `switch (target.result.os.tag)` span now sit on single lines. Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw * docs(#453): fold PR #459 review findings into manifest-v2 design chatgpt-codex raised 8 findings on PR #459 not covered by the #456 round. Verified each against the real code and folded the valid ones in: - root_build_deps now carry required resolution (url+hash/path/builtin); emsdk is a pinned template section, not name-synthesizable (#2) - bgfx-android android_app extra module requires root_alias="backend_app" (#3) - hookless mobile uses an assembler-owned default resolve_target; the backend-agnostic resolver means .resolved does not force a hook (#4) - build_hook must be a dedicated backend.hook.zig, not the provider build.zig (top-level @import("sokol") re-exports don't resolve in the root package) (#5) - android_target_sdk is required for Android; post_wire panics instead of the silent orelse 34 fallback (#6) - golden gate strengthened for hook-bearing cells: snapshot hook source and/or run the hook against a fixture *std.Build (#7) - carried v1 .capabilities forward into the v2 schema so opting into v2 doesn't bypass capability negotiation (.id was already present) (#8) Finding #1 (dep-option removal) was already resolved by ea24373 (base = universal options, per-platform = appends, no subtractive form) — recorded, not re-edited. Added a "PR #459 corrections" section documenting each. Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw


Summary
Implements the assembler side of #5 (and labelle-toolkit/labelle-cli#147). All runtime content that the assembler wires into generated
build.zigfiles — the five backends, the three ECS adapters, and the five GUI plugins — now lives in this repo instead of labelle-cli.Paired with labelle-toolkit/labelle-cli#??? (feat/eject-backends) which removes the same content from there.
What moved
Cache layout change
Introduces a new cache slot
~/.labelle/packages/assembler/<version>/alongside the existingcli/<version>/slot. During the staged migration,resolveBundledPackagefalls back from the assembler slot to the cli slot so each stage could land independently. The final stage (#8) removes the dead cli-slot machinery entirely since nothing bundled lives there anymore.The resolver takes both
cli_versionandassembler_versionsolocal:path overrides can point each slot at a different sibling repo during dev.Commits (staged for review)
feat: absorb raylib backend— Stage 1 plumbing + first backendfeat: absorb sokol backend— Stage 2refactor: silence assembler-slot probe— suppresses spurious warnings during migrationfeat: absorb bgfx backend— Stage 3feat: absorb sdl backend— Stage 4feat: absorb wgpu backend— Stage 5 (last backend)refactor: drop dead backend fallback— Stage 6 cleanupfeat: absorb ecs adapters— Stage 7feat: absorb gui plugins + drop dead cli cache slot— Stage 8 (final)Test plan
zig build testpasses on this branchcd ecs/zig-ecs && zig build testpasses (CI step added)examples/{raylib,sokol,bgfx,wgpu}in labelle-cli withLABELLE_ASSEMBLERpointing at this branch's binary, verify.labelle/deps/labelle-*/src/gfx.ziginodes trace back tobackends/in this repoCloses #5.
Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com