Skip to content

iOS: asset-path + device-limit fixes, touch-slot API, target docs#89

Merged
proggeramlug merged 5 commits into
mainfrom
ios/touch-slots-and-asset-paths
Jul 11, 2026
Merged

iOS: asset-path + device-limit fixes, touch-slot API, target docs#89
proggeramlug merged 5 commits into
mainfrom
ios/touch-slots-and-asset-paths

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Everything needed to bring a full 3D game up on a physical iPhone. Found by porting the shooter to iOS; it now runs on device at ~50 fps, and these are the engine-side blockers that were in the way.

Three of the five are real bugs, one of which was taking the shooter down on macOS as well.

fix(ios): resolve the asset path in compile_material_from_file

Every asset loader routes its path through the platform's resolve_path() hook — on iOS the CWD is not the app bundle, so a relative path resolves against nothing. compile_material_from_file was the single loader that didn't, and passed the raw path straight to the filesystem.

That works on the desktop hosts, where the CWD happens to be the asset root, and fails on device. The symptom is a scene that renders with every from-file material silently dropped — in the shooter, no water at all — with one canonicalize "assets/materials/water.wgsl": No such file line to explain it.

FFIs that write a file (take_screenshot, dump_shadow_map) keep the raw path deliberately: the bundle is read-only.

fix(render): request the adapter's sampled-texture limits, not wgpu's defaults

A user material's fragment stage binds 19 sampled textures once the scene/GI inputs sit alongside the material's own maps. wgpu's default cap is 16.

Device creation asked for Limits::default() and only fell back to the adapter's real limits when request_device failed. On macOS it doesn't fail — the device is created happily at 16 — so the shortfall surfaced later and further away, as an abort inside create_pipeline_layout('user_material'):

Too many bindings of type SampledTextures in Stage FRAGMENT, limit is 16, count was 19

The shooter could not open on macOS at all; this is not iOS-specific and reproduces on a clean main.

iOS was only ever passing by luck: its first request_device fails on an unrelated limit (max_inter_stage_shader_variables, 15 vs a requested 16), so it always retried with adapter.limits() and picked up the texture headroom as a side effect.

fix(ios): declare the c++ runtime in the native-library manifest

Jolt is C++ and bloom-ios is a staticlib, so Perry performs the final link and reads libs from the manifest. macOS, tvOS and visionOS all declare c++; iOS was the one Apple target that didn't, so any physics-using game failed the link on undefined std::__1 symbols.

feat(input): add isTouchActive / getMaxTouchPoints

Touch points are addressed by slot, but getTouchCount() returns the number of live fingers — and the two diverge the moment a finger lifts out of order. Hold two fingers, lift the first, and the live finger sits at slot 1 while the count reads 1.

So the obvious loop:

for (let i = 0; i < getTouchCount(); i++)   // ← wrong

reads slot 0 — released, but still carrying the coordinates it had when it left the glass — as though it were live. It presents as a finger frozen in place: a virtual stick that stays deflected and keeps walking the player after the thumb is gone. src/mobile's VirtualJoystick has this shape today, and the games that hand-roll their touch input inherited it.

There was no way to tell from TypeScript which slots were live (getTouchPointCount() just aliases getTouchCount()). The correct scan is now expressible:

for (let i = 0; i < getMaxTouchPoints(); i++) {
  if (!isTouchActive(i)) continue;
  ...
}

Added to the shared define_core_ffi! macro so every platform on it picks them up; watchOS and web hand-roll their FFI and get explicit implementations.

docs: add docs/ios-target.md

iOS is the engine's only App Store platform and had no target doc, while watchOS and web both do — the only build instruction anywhere in the repo was the text of a panic! string. Covers the mandatory ios-game-loop feature and the UIApplicationMain handshake, asset bundling and the resolve-path contract, the touch-slot rule above, and the known gaps (no CI build for iOS, no gamepad, EN-024).

Testing

  • tools/validate-ffi.js: 0 failures, 0 warnings across all 7 platforms (415 manifest functions).
  • Built and run on a physical iPhone 16 Pro (arm64, iOS 27): launches, renders, water present, touch controls tracking correctly, ~50 fps.
  • Built and run on macOS — which crashed on launch before this branch and now opens.

Summary by CodeRabbit

  • New Features

    • Added touch-input helpers to identify active touch slots and query the maximum supported touch points.
    • Exposed the new touch APIs across supported native and web targets.
    • Improved asset path resolution for iOS bundle-based deployments.
    • Added iOS build, deployment, rendering, input, and known-issue documentation.
  • Bug Fixes

    • Improved graphics adapter limit handling to support devices with higher texture and sampler capabilities.

Every asset-loading FFI routes its path through the platform's
resolve_path() hook, which on iOS prepends the app bundle's resource
path — relative paths do not resolve against the working directory
there, because the CWD is not the bundle.

compile_material_from_file was the one loader that didn't, passing the
raw relative path straight to the filesystem. That works on the desktop
hosts, where the CWD happens to be the asset root, and fails on device.

The symptom is a scene that renders with every from-file material
silently dropped — in the shooter that meant no water at all, and
terrain/glass/muzzle-flash falling back to a default — with nothing but
a `canonicalize "assets/materials/water.wgsl": No such file` line on the
console to explain it.

FFIs that *write* a file (take_screenshot, dump_shadow_map) keep the raw
path deliberately: the bundle is read-only, and their output belongs
outside it.
Touch points are addressed by slot, but getTouchCount() returns the
number of *live* fingers — and the two spaces diverge the moment a
finger lifts out of order. Hold two fingers, lift the first, and the
live finger sits at slot 1 while the count reads 1.

The obvious loop, `for (i = 0; i < getTouchCount(); i++)`, therefore
reads slot 0 — released, but still carrying the coordinates it had when
it left the glass — as though it were a live touch. It presents as a
finger frozen in place: a virtual stick that stays deflected and keeps
walking the player after the thumb is long gone. src/mobile's
VirtualJoystick has this shape today, and games that hand-roll their
touch input (jump, garden) inherited it.

There was no way to tell from TypeScript which slots were live —
getTouchPointCount() just aliases getTouchCount(). Expose the two
primitives that make a correct scan possible:

    for (let i = 0; i < getMaxTouchPoints(); i++) {
      if (!isTouchActive(i)) continue;
      ...
    }

Added to the shared define_core_ffi! macro, so every platform on it
picks them up; watchOS and web hand-roll their FFI and get explicit
implementations. FFI parity is green on all seven.
Jolt is C++, and bloom-ios is a staticlib — so Perry, not cargo, performs
the final link, and it takes the libraries to link from `libs` in the
manifest. macOS, tvOS and visionOS all declare `c++` there for exactly
this reason; iOS was the one Apple target that didn't, leaving any
physics-using game to fail the link on undefined std::__1 symbols.
… defaults

A user material's fragment stage binds 19 sampled textures once the
scene/GI inputs sit alongside the material's own maps. wgpu's default
max_sampled_textures_per_shader_stage is 16.

Device creation asked for Limits::default() and only fell back to the
adapter's real limits when request_device *failed*. On macOS it doesn't
fail — the device is created happily at 16 — so the shortfall surfaced
later and further away, as an abort inside
create_pipeline_layout('user_material'):

    Too many bindings of type SampledTextures in Stage FRAGMENT,
    limit is 16, count was 19

which took the shooter down on every macOS launch, before a frame.

iOS was only ever passing by luck: its first request_device fails on an
unrelated limit (max_inter_stage_shader_variables, 15 vs a requested 16),
so it always retried with adapter.limits() and picked up the texture
headroom as a side effect.

Ask the adapter for what it actually offers on the limits the material
system depends on, never dropping below wgpu's defaults.
iOS is the engine's only App Store platform and had no target doc, while
watchOS and web both do. The only build instruction anywhere in the repo
was the text of a panic! string in native/ios/src/lib.rs.

Covers the ios-game-loop requirement and the UIApplicationMain handshake,
asset bundling and why every read path must go through resolve_path(),
the touch-slot contract, and the known gaps (no CI build, no gamepad,
EN-024).
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f2c8e36-320a-4e16-a130-47856f16e1a9

📥 Commits

Reviewing files that changed from the base of the PR and between e2b1ba2 and 7a9b0c5.

📒 Files selected for processing (10)
  • docs/ios-target.md
  • native/shared/src/attach.rs
  • native/shared/src/ffi_core/input.rs
  • native/shared/src/ffi_core/models.rs
  • native/shared/src/input.rs
  • native/watchos/src/lib.rs
  • native/web/src/input_ffi.rs
  • package.json
  • src/core/index.ts
  • src/index.ts

📝 Walkthrough

Walkthrough

The change documents the iOS target, resolves iOS asset paths, expands touch-slot APIs across native and TypeScript layers, updates iOS linking, and aligns renderer limits with adapter capabilities.

Changes

iOS and touch support

Layer / File(s) Summary
Touch-slot API contract and bindings
native/shared/src/input.rs, native/shared/src/ffi_core/input.rs, native/watchos/src/lib.rs, native/web/src/input_ffi.rs, package.json, src/core/index.ts, src/index.ts, docs/ios-target.md
Adds active-touch and maximum-touch-point queries across native targets, the ABI, and public TypeScript exports, with documentation for sparse touch-slot iteration.
iOS build and asset integration
docs/ios-target.md, native/shared/src/ffi_core/models.rs, package.json
Documents the static-library build workflow, required game-loop feature, bundle-relative asset loading, and iOS C++ linking.
Renderer limits and platform notes
native/shared/src/attach.rs, docs/ios-target.md
Uses adapter-reported sampled-texture and sampler limits while documenting iOS renderer settings, deployment targeting, and known gaps.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TouchAPI as TypeScript Touch API
  participant NativeFFI as bloom_is_touch_active
  participant InputState
  TouchAPI->>NativeFFI: query touch index
  NativeFFI->>InputState: is_touch_active(index)
  InputState-->>NativeFFI: active boolean
  NativeFFI-->>TouchAPI: 1.0 or 0.0
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main iOS fixes and docs added in this PR.
Description check ✅ Passed It covers the summary and test evidence well, though the Notes for reviewers section is missing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ios/touch-slots-and-asset-paths

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug proggeramlug merged commit 5470d56 into main Jul 11, 2026
10 checks passed
@proggeramlug proggeramlug deleted the ios/touch-slots-and-asset-paths branch July 11, 2026 12:50
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