ReviewOS

also looking at this

craft-native/craft

feat(ios): AR moves to Zig — ratchet 18 -> 13

#126
Merged glennmichael123 wants to merge feat/ios-ar into main
5 files +1480 -10
9 days ago

Moves startAR, stopAR, placeARObject, removeARObject and getARPlanes to Zig. Ratchet 18 → 13.

This was the last group whose deferral reason was an effort judgement rather than a wall — "SceneKit node-graph glue; miserable and low-value through objc_msgSend". The node graph turned out not to be the hard part.

The actual risk: three struct returns, not the node graph

Every previously migrated module passes and returns pointers and integers. This one passes and returns structs by value in floating-point registers, three different ways:

TypeUsed forReturn rule on arm64
CGRectUIScreen.main.bounds, -initWithFrame:HFA of 4 doubles → v0–v3
SCNVector3SCNNode.position, eulerAnglesHFA of 3 → v0–v2
simd_float3ARPlaneAnchor.center / .extent16 bytes in v0 alone

The third uses a different rule from the other two. Getting it wrong does not crash — it yields plausible coordinates that ARKit never measured, which is a wrong answer delivered as a right one.

So none of the three is asserted from the headers and hoped for. SceneKit ships on macOS even though ARKit does not, which makes SCNNode, SCNBox and — crucially — simdPosition reachable from a host test. All three round-trip against real objects in zig build test:ios.

The simd_float3 guard was mutation-tested: swapping @Vector(4, f32) for a four-field extern struct makes it read y = 0 and z = 0 while x still agrees, and the test fails exactly there. It bites.

What this cannot verify, and won't pretend to

ARWorldTrackingConfiguration.isSupported is false in the simulator, so startAR refuses there and every line past that guard needs hardware I don't have. The tests cover argument parsing, the reply shapes, the plane JSON, the refusal paths and the three ABI mechanisms. They do not cover a running AR session, and no test in this repository can.

One further limit, written into the module rather than glossed: SceneKitTypes.h declares SCNVector3 as three CGFloats on macOS and three floats on iOS (checked in both SDKs). The alias follows the target, so the host test verifies the mechanism at double width, not the exact iOS layout.

Two details that are easy to miss, and are ported

  • extent contributes x and z, mapped to width and height. A plane's y extent never reaches the page at all.
  • position is a [String: Double] cast of the whole dictionary. It fails as a unit, so {"x":1,"y":"up"} is not "x with a bad y" — it is no position, and the node lands at the default (0, 0, -0.5). A reader that kept x would place the object somewhere Swift never would. Pinned by its own test.

Divergences

  • INVALID_ARGUMENT → INVALID_PARAMETER, and "AR not supported" → PLATFORM_NOT_SUPPORTED. BridgeError carries an enum, not free text — the same trade bridge_mobile_misc.zig and bridge_mobile_watch.zig document.
  • Reply ordering is fixed, not preserved. getARPlanes is the only one of the five Swift does not wrap in DispatchQueue.main.async, so a startAR immediately followed by a getARPlanes has the planes reply first. Both are synchronous here, so replies keep the order the page sent them in.
  • All five carry config.enableAR in the spec — unlike the recorder, where only the start is gated — so all five are added to ios_config.gateFor. The conformance test that asserted gateFor("startAR") == null is inverted rather than deleted: it now asserts the gate is there.

Verification

  • zig build test:ios — 7/7 steps, 1083/1095 passed, 12 skipped, 0 failed.
  • zig build test — 1454/1454 passed. The one failing step is injected_js_test.zig compiling against a local zig-js checkout that is incompatible with the pinned toolchain; it fails identically on main, checked before claiming it.
  • No Swift template change: the five case arms stay as the spec, and Zig answers first — the same arrangement readLocationRecording uses.

5 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.