Skip to content

fix: use b.graph.host for Zig 0.15.2 in iOS build template - #4

Merged
apotema merged 2 commits into
mainfrom
fix/ios-build-zig015
Apr 13, 2026
Merged

fix: use b.graph.host for Zig 0.15.2 in iOS build template#4
apotema merged 2 commits into
mainfrom
fix/ios-build-zig015

Conversation

@apotema

@apotema apotema commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix b.hostb.graph.host in the iOS build template (renamed in Zig 0.15)

Test plan

  • zig build test — all tests pass
  • labelle ios build — blocked by Zig 0.15.2 + macOS Tahoe SDK issue (separate problem)

@cursor

cursor Bot commented Apr 13, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Low risk: small compatibility fixes for Zig 0.15.x that only affect generated profiling pointer casts and iOS build host-arch detection.

Overview
Adjusts generated main.zig lifecycle code to @alignCast profiling structs before @ptrCast, avoiding misaligned pointer casts in debug profiling paths.

Updates the iOS build.zig template to use b.graph.host (replacing b.host) when selecting the simulator target based on host architecture, matching Zig 0.15 API changes.

Reviewed by Cursor Bugbot for commit 1a73682. Bugbot is set up for automated code reviews on this repo. Configure here.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the build.zig template to access the host architecture via b.graph.host instead of b.host, aligning the code with the current Zig build system API. I have no feedback to provide.

@apotema
apotema merged commit 4827aa7 into main Apr 13, 2026
3 checks passed
@apotema
apotema deleted the fix/ios-build-zig015 branch April 13, 2026 15:17
apotema added a commit that referenced this pull request May 23, 2026
)

Phase 4 of RFC-PLUGIN-EVENTS — labelle-assembler#175. Closes the
runtime gap that left phase 3's emitted FlowEventHandler structs idle:
they had no receiver in the GameHooks tuple, so MergeHooks.emit
never reached them.

Wiring:

  1. ScriptEntry gains has_event_handler: bool (default false).
  2. flow_scanner flips it true when the parsed flow is a new-form
     OnEvent (flow.event.OnEvent.name != null) — the gate
     flow-codegen 1182a80 uses to emit pub const FlowEventHandler.
     Lifecycle flows (OnCreate/OnUpdate/OnDestroy/OnCall) and legacy
     OnEvent (module+callback) stay false and are skipped.
  3. main_zig.generateMainZigFromTemplate iterates script_entries in
     scanner-sorted order (O3 — flow_scanner already does the per-flow
     numeric-prefix-then-alphabetical sort) and:
     - appends *@import("scripts/<rel_path>").FlowEventHandler to
       the GameHooks = engine.MergeHooks(...) receiver-type tuple
       (game_hooks_block, after the existing hook entries).
     - materialises var <ident>_flow_handler = .{}; in
       hooks_init_block and appends &<ident>_flow_handler to the
       receivers init tuple.
  4. game_ptr injection is the engine's existing setHooks loop
     (labelle-engine/src/game.zig:419-429): it walks the receivers
     tuple and writes @ptrCast(self) into any receiver that has a
     game_ptr field — FlowEventHandler declares one, so the walk
     reaches it unchanged.

Inline @import in the GameHooks tuple (rather than borrowing the
AllScripts alias) because AllScripts is declared after GameHooks in
the engine template — no new template slot needed.

Regression guard for projects with no hooks/ and no new-form OnEvent
flows: GameHooks stays struct{} verbatim and var hooks = GameHooks{};
stays in hooks_init. The branch is gated on
hook_names.len + flow_handler_count, so every shipped game without
new-form events keeps its exact v1 shape.

Verification:

  - zig build test in labelle-assembler: 453/457 (4 skipped),
    +4 new tests pinning the marker + codegen contract.
  - bouncing-ball end-to-end regen + zig build: clean. Generated
    main.zig now contains:
      const GameHooks = engine.MergeHooks(AllHookPayloads, .{ *@import("scripts/flows/hit_counter.zig").FlowEventHandler, });
      ...
      var flows_s_hit_u_counter_flow_handler: @import("scripts/flows/hit_counter.zig").FlowEventHandler = .{};
      var hooks = GameHooks{ .receivers = .{ &flows_s_hit_u_counter_flow_handler, } };
      g.setHooks(&hooks);  // injects game_ptr
  - bouncing-ball/tests/runtime_dispatch_test.zig (3/3 pass):
    constructs Game + hooks the same way main() does, emits three
    .box2d__collision_begin payloads, dispatchEvents, asserts
    hits.currentTotal() == baseline + 3. Also asserts game_ptr is
    set on the handler post-setHooks, and that variants the handler
    doesn't subscribe to (.collision_end / .sensor_enter) don't
    increment the counter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
apotema added a commit that referenced this pull request Jul 1, 2026
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
apotema added a commit that referenced this pull request Jul 1, 2026
* 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
apotema added a commit that referenced this pull request Jul 7, 2026
- #1 escape generated string literals: emit the registry key AND @embedfile
  path through std.zig.fmtString (`{f}`), not raw `{s}` — a backslash/quote
  in an image source or asset_name (e.g. Windows `tiles\terrain.png`) no
  longer produces invalid Zig or a mis-keyed literal.
- #2 same image key → different paths: `img_seen` now maps key→resolved
  embed path; two maps in different dirs both referencing `tiles.png`
  (different files, same runtime key) hard-error instead of silently
  reusing the first's bytes. Same-key-same-path stays a benign dedup.
- #3 external tilesets: detect `<tileset source="*.tsx">` (external, no
  inline <image>) in `collect` and fail loud with the offending .tsx named
  — gfx returns error.ExternalTilesetUnsupported at runtime otherwise.
  Filed assembler#563.
- #4 attribute syntax: `attrValue` now tolerates whitespace around `=` and
  single OR double quotes (`source = "x"`, `source='x'`), still a tight
  scan. Order: locate attr → strip quotes → XML-unescape.

Tests: escaping round-trips to valid Zig (ast-parsed), diff-path collision,
benign same-path dedup, external-tileset error, and the new attr syntaxes.
zig build + test green, goldens byte-identical, fmt clean, files < 1000.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
apotema added a commit that referenced this pull request Jul 7, 2026
…Phase 4) (#560)

* tilemap: scan scenes for Tilemap + embed .tmx and tileset images (T2 Phase 4)

Scan scene JSONC for `Tilemap` components, resolve each `asset_name` to a
`.tmx` under the project's `assets/` dir, and comptime-embed the `.tmx`
plus every tileset `<image source>` it references so labelle-engine
v1.75.0 can decode + render the tilemap.

Engine contract (labelle-engine v1.75.0, game/tilemap_mixin.zig): a single
`Game.addEmbeddedTilemapAsset(name, bytes)` registry is keyed by BOTH the
scene `asset_name` (-> .tmx bytes) AND each tileset's verbatim
`image_source` string (-> image bytes; the engine's `ImageProvider.get`
looks up by that exact string). The generated `init()` now populates it.

- scene_manifest: `SceneManifest.tilemap_assets` — deep-walk the entity
  tree for `Tilemap` components (flat/wrapped/children/bundle shapes).
- tilemap_scan.zig: resolve `asset_name` -> `assets/<asset_name>.tmx`,
  read it, extract `<image source>` refs (tight XML scan), and build a
  deduped flat list of `addEmbeddedTilemapAsset` registrations. Image
  registry key = verbatim `image_source`; @embedfile path = resolved
  relative to the .tmx dir.
- codegen: emit registrations before `setScene` in both lifecycle paths
  (loop `try`, callback `catch @panic`), via the module-level-var pattern
  used by pack_scans. Purely additive — empty for tilemap-free projects.
- Bump default engine_version 1.60.0 -> 1.75.0.

Tests: scan extractor + path convention + collect (tmpDir) + scene
extraction + both emit spellings. Verified e2e: a null-backend fixture
generates registrations before setScene and passes `zig ast-check`.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

* tilemap: re-pin default engine to 1.75.1 (fixes null-backend tilemap compile)

engine 1.75.0's tilemap_runtime failed to compile against any gfx-1.21.0 backend
(generic getTextureInfo seam) — the Examples integration test's null-backend
asset-streaming-smoke build caught it. 1.75.1 (engine#707) hardens supported()
+ derives the texture type from the concrete resolver fn. Brings the fix in
transitively (engine 1.75.1 pins gfx 1.21.0).

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

* tilemap: address codex review (P1 sibling pins + 5 P2 correctness fixes)

P1 — bump core/gfx defaults to match engine 1.75.1's transitive pins so a
fresh `labelle init && build` resolves a compatible set: core 1.24.0, gfx
1.21.0 (read from engine v1.75.1 → gfx v1.21.0 → core v1.24.0 build.zig.zon).

P2 fixes:
- Walker over-match: replace the generic deep scan with a STRUCTURED
  entity-tree walk (`tilemap_scene_scan.zig`) — a `Tilemap` key is only
  collected when it's a direct member of an entity's `components`/`overrides`
  map or a flat-form component key, never nested in another component's data.
- tmx/image key collision: track the `.tmx` and image key spaces separately
  and hard-error (`TilemapKeyCollision`) when a scene `asset_name` collides
  with a tileset `<image source>` in the shared engine registry.
- XML-unescape `<image source>` (&amp; &lt; &gt; &quot; &apos;) before using
  it as the @embedfile path AND the registry key — gfx hands the engine the
  DECODED string.
- Honor project-registered `Tilemap` (engine C2): skip built-in embedding
  when a project component pascal-matches `Tilemap`. Plugin/pack-registered
  Tilemap deferred (fails loud via missing-asset) — filed assembler#562.
- Prefab-borne Tilemaps: not embedded in minimal-T2 — detect + fail loud
  with a clear message instead of a silently broken binary. Filed #561.

Refactor to keep files < 1000 lines: extract the JSON walker into
`tilemap_scene_scan.zig` and the generate-phase policy into
`root/tilemap_phase.zig`.

Tests: walker over-match (nested non-component Tilemap), key collision,
XML-unescape, prefab-scan detection. zig build + test green, fmt clean.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

* tilemap: address codex 2nd-pass review (4 P2 correctness fixes)

- #1 escape generated string literals: emit the registry key AND @embedfile
  path through std.zig.fmtString (`{f}`), not raw `{s}` — a backslash/quote
  in an image source or asset_name (e.g. Windows `tiles\terrain.png`) no
  longer produces invalid Zig or a mis-keyed literal.
- #2 same image key → different paths: `img_seen` now maps key→resolved
  embed path; two maps in different dirs both referencing `tiles.png`
  (different files, same runtime key) hard-error instead of silently
  reusing the first's bytes. Same-key-same-path stays a benign dedup.
- #3 external tilesets: detect `<tileset source="*.tsx">` (external, no
  inline <image>) in `collect` and fail loud with the offending .tsx named
  — gfx returns error.ExternalTilesetUnsupported at runtime otherwise.
  Filed assembler#563.
- #4 attribute syntax: `attrValue` now tolerates whitespace around `=` and
  single OR double quotes (`source = "x"`, `source='x'`), still a tight
  scan. Order: locate attr → strip quotes → XML-unescape.

Tests: escaping round-trips to valid Zig (ast-parsed), diff-path collision,
benign same-path dedup, external-tileset error, and the new attr syntaxes.
zig build + test green, goldens byte-identical, fmt clean, files < 1000.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

* tilemap: address codex 3rd-pass review (3 P2 robustness fixes)

- #1 free TMX buffers per iteration: extract the per-map read+scan into
  `processMap`, so each map's `.tmx` bytes (up to 8 MiB) + extracted image
  slice free at that frame's end instead of accumulating until `collect`
  returns. Registrations/keys are dup'd into `regs` first, so they outlive
  the per-map buffers. Fixes OOM risk for projects with many large maps.
- #2 skip XML comments in the tag scan: `indexOfTagSkippingComments` skips
  `<!-- ... -->` spans, so a commented-out `<image>`/`<tileset source>`
  no longer emits a bogus @embedfile (which could fail the build) or a
  false external-tileset error.
- #3 include pack prefabs in the prefab fail-loud: move the tilemap phase
  after `loadPackScans` and thread `pack_scans` into `collectRegistrations`;
  `failOnPrefabTilemaps` now also walks `<import_prefix>/prefabs/*.jsonc`,
  so a Tilemap in a light-pack prefab aborts with the same #561 message
  instead of shipping a silently-broken binary. Updated #561 body.

Tests: commented-out <image>/<tileset> ignored (real one still found);
existing collect tests exercise per-map frees under testing.allocator.
Verified e2e: pack-prefab Tilemap fails loud; happy-path unchanged +
ast-checks. zig build + test green, goldens byte-identical, fmt clean,
files < 1000.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw

* tilemap: address codex 4th-pass review (#A tileset-scoped scan, #B backslash key/path) + revert unescape per gfx

Grounded in gfx v1.21.0 tilemap/src/root.zig: its TMX parser stores
`image_source` as a RAW dupe of the attribute-value bytes (parseAttributes
reads verbatim between double-quotes; `tileset.image_source = dupe(src)`) —
NO XML-entity decoding, NO separator normalization. The engine's
ImageProvider.get looks up by that raw string, so the registry key must
match it byte-for-byte.

- #A tileset-scoped image scan: `extractImageSources` now only collects
  `<image>` INSIDE a `<tileset>…</tileset>` span. An `<imagelayer><image/>`
  background is ignored — the engine fetches images only for decoded
  TILESETS, so embedding an imagelayer image would require an absent file /
  collide though the runtime never requests it.
- #B Windows backslash: the registry KEY stays the RAW `image_source`
  (backslash intact, matching gfx's lookup); only the @embedfile PATH
  normalizes `\`→`/` (+ existing `.`/`..` collapse) so it resolves to the
  copied asset `assets/tiles/terrain.png`.
- Revert round-2 xmlUnescape: gfx does NOT decode entities, so decoding the
  key was a silent-mismatch bug (engine keys by raw `&amp;…`). Keys/paths
  are now raw; the ambiguous XML-entity-in-path edge is deferred + filed as
  assembler#564.

Tests: imagelayer image ignored (tileset image still found); raw source
preserved (no decode); backslash → raw key + normalized path (unit +
collect e2e). zig build + test green, goldens byte-identical, fmt clean,
files < 1000. Happy-path fixture regenerates identical + ast-checks.

Claude-Session: https://claude.ai/code/session_017pW3ifKf9wgxNg4viy6okw
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant