Device attitude simulation

Contract and panel

vphoned capability motion_attitude exposes motion.attitude.get, motion.attitude.set {roll,pitch,yaw,enabled?} and motion.attitude.clear. Set requires finite JSON numbers: roll/yaw in [-180,180] degrees, pitch in [-90,90]. Booleans, strings, missing/non-finite/out-of-range angles are rejected. enabled must be a Boolean and defaults to true. Clear disables and zeros; set with enabled=false retains angles.

Features → Motion Sensors → Device Attitude (Chinese: 功能 → 运动传感器 → 设备姿态) offers immediate edits, a simulation checkbox and zero reset preserving the checkbox. The gyro panel is in the same submenu. Both share the serialized/coalesced writer and retry/reconnect model. UI/RPC use degrees; Core Motion uses radians. Zero is face-up, gravity (0,0,-1) g. Pitch is about X, roll about Y, yaw about Z: R = Rz(yaw) Rx(pitch) Ry(roll). Pitch ±90° makes Euler roll/yaw singular; quaternion/matrix remain well-defined.

This is a stationary pose, independent of raw gyro simulation. Device motion supplies attitude, corresponding gravity, zero rotation rate/user acceleration, an uncalibrated zero magnetic field and invalid heading (-1). Changing angles does not synthesize angular velocity or UIKit/display orientation changes.

Injection and transport

Guest System declares system-apps-cfw-attitude, selected by both presets on iOS 18+. It adds /usr/lib/libvphoneattitude.dylib. SystemHook loads it into apps before the optional tweak loader; no bootstrap or new daemon is required. The attitude loader excludes /System/Library/ processes and SpringBoard copies, even though their executable paths can contain .app/. The dylib enforces the same exclusion before registering notifications or replacing methods, so an older SystemHook cannot activate the hook in those services after a live library update. Safe-mode/injection-disable flags still apply. No system executable, kernel or DSC bytes change. This intercepts the app's public Core Motion boundary; it does not claim that raw gyro HID events are fused attitude samples.

All four CMMotionManager device-motion start methods, stop, interval, polling, availability, active state and reference-frame getters are covered. Only XArbitraryZVertical is simulated/advertised. Corrected/magnetic/true-north requests keep the native path. Enable simulation before launching an app whose availability check would otherwise prevent subscription.

Per-manager timers honor intervals at 1–200 Hz (unset: 60 Hz). Callbacks run on the app's NSOperationQueue, outside session locks. At most one simulated delivery is queued; a suspended queue cannot accumulate samples. Generations discard deliveries after stop/restart/mode changes. Samples support independent copies, secure coding and relative quaternion multiplication. Weak timer captures and cancellation prevent a timer retaining a released manager. Enabling/disabling an existing subscription switches between simulation and retained native implementations, including native availability when disabled. Native stop is forwarded only for a subscription previously forwarded to native start. A new or purely simulated subscription never initializes native Core Motion by stopping it; repeated stops remain local after native shutdown.

Lock order

The original app gate matched every path containing .app/, so SpringBoard (/System/Library/CoreServices/SpringBoard.app/SpringBoard) loaded the hook too. The respring fix described below now excludes system UI services; the lock-order rule still applies to regular app consumers. Core Motion's -[CMMotionManager setDeviceMotionUpdateInterval:] (and other methods) hop synchronously to its com.apple.CoreMotion.MotionThread, and blocks on that thread call the public getters, such as isDeviceMotionActive, on the same manager. The first version held the session lock around the native setter and took it again in the getters. On iOS 26.6.2 that froze SpringBoard on every boot after data migration: the main thread builds the Home Screen's parallax (SBRootFolderView _updateParallaxSettings → _UIMotionEffectCoreMotionEventProvider init), which sets the interval, while the MotionThread waits in VPhoneActive. The screen stayed black, Setup.app was killed by its 20 s launch watchdog waiting for SpringBoard, any vphoned call that needs SpringBoard (screen.unlock, apps.foreground, a respring) hung, and template setup boots failed at setup-skip. 27.0's setter has the same synchronous hop, so the order matters there as well even though no 27.0 boot was seen to hang.

The rule since: no hooked getter waits for a lock that a caller of a native method may hold. active, frame and latest are atomic and the getters read them without the session lock; the setter calls the native method before it takes the lock; sessions are found through the associated object and created under a private os_unfair_lock, never @synchronized on the manager. AttitudeLockOrderTests (part of make test-attitude) replaces the native setter, start and stop with stand-ins that wait for a second thread calling the getters, and fails without the rule.

Configuration transport

vphoned serializes publication/readback and persists one dictionary, VPhoneAttitudeConfiguration, in mobile's com.apple.backboardd preferences. It packs angles and enabled in one 64-bit Darwin notification state, com.vphone.motion.attitude.changed. Apps read this atomic state without cross-sandbox preferences/file access. Transport quantizes to 0.001° (maximum 0.0005° error); persisted/RPC values retain input precision. vphoned seeds notifyd at startup; new app readers immediately read the latest state.

provider_installed only reports the dylib's presence. It does not prove a target app loaded the hook or received samples. Check SystemHook's app-PID attitude-hook=... result=loaded log and the actual Core Motion consumer.

Selection and verification

cfw install/update-environment follows selection and records the guest receipt. No-plan legacy VMs do not opt in. Blocking removes the added file; live hash sync replaces installed selected copies only. Relaunch apps after changing the library. A changed SystemHook still requires reboot; no UI daemon is restarted for attitude updates.

Host checks: make -C VPhoneGuestComponents test-attitude tests malformed configuration, packed-state boundaries, quaternion/matrix/gravity, copy, relative attitude and secure coding. Its real host Core Motion runtime test covers all start overloads, polling, callback delivery, live toggle, snapshots, interval setting, bounded backlog and suppression after stop, using a separate Darwin notification name. The shared editor harness zsh VPhoneExecutable/VPhoneVirtualization/Tests/run-gyroscope-tests.sh covers sequencing/retry/reconnect and attitude-specific bounds. Catalogue tests cover version gating, blocking, preset reachability and manifest invariants.

2026-10-08: sample and live hook tests passed, shared editor tests passed, 55 catalogue/model tests in seven suites passed, and the full VPhone Debug bundle built successfully. Bundle admission and strict deep signature verification passed; the built zh-Hans strings include 设备姿态 and 俯仰.

Guest acceptance is manual, as requested: update guest, relaunch a consumer, enable simulation, compare polling/callback Euler radians, quaternion and gravity at zero and single-axis 90° poses. Check reset/disable while subscribed, stopped-manager silence, and relaunch after blocking the patch. Host runtime checks do not establish guest injection/sandbox acceptance or every app's API.

SpringBoard respring investigation (2026-10-09)

The reported black screen followed by the Lock Screen is a SpringBoard crash, not a VM reboot. Reports SpringBoard-2026-10-09-194121.ips and SpringBoard-2026-10-09-194251.ips on iOS 27.0.1 (24A446), cloudOS 26.4, both show EXC_BAD_ACCESS / SIGSEGV at address 0x10 on com.apple.CoreMotion.MotionThread. The main thread waits in native Core Motion from -[VPhoneAttitudeSession stop], called by startWithFrame:queue:handler: → VPhoneStartCallback → UIKit's -[_UIMotionEffectCoreMotionEventProvider startGeneratingEvents]. Both reports list /usr/lib/libvphoneattitude.dylib in SpringBoard.

The .app/ injection gate admitted /System/Library/CoreServices/SpringBoard.app/SpringBoard. Separately, every new simulated subscription called native stop despite never having called native start. The reports establish that this path reached guest Core Motion and crashed its motion thread. The console's CloudKit preferences denial, RunningBoard faults and “No accelerometer” message do not supply the crash stack; the last message is consistent with the sensorless guest.

Fix: exclude system UI services in both loaders and guard native stop with the session's native-start tracking. The sensorless lifecycle harness fails the stops == 0 assertion before that guard and passes after it. It covers stop before start, simulated start/restart/stop, repeated stop, native forwarding of unsupported reference frames, and native-to-simulated switching. Existing real host hook tests still cover callbacks, polling and live enable/disable. The upstream MotionThread lock-order regression test passes alongside the sensorless lifecycle and process-exclusion tests. Injection tests cover SpringBoard, AccessibilityUIServer, relocated SpringBoard, Settings and a container-installed consumer. The lifecycle executable is also run from a /System/Library/-shaped SpringBoard path to verify that the dylib constructor leaves native methods intact. All host attitude/injection checks and 51 catalogue/model tests in six suites passed; the attitude and SystemHook dylibs cross-compiled for iPhoneOS. The full VPhone Debug bundle built successfully, passed bundle admission and strict deep signature verification.

Deployment acceptance remains pending: build and install the new bundle, update the stopped guest's environment and boot it. Confirm the new SpringBoard PID has no attitude-hook=... result=loaded entry, enable/edit/ disable attitude while opening/closing apps and locking/unlocking, and check that no new SpringBoard crash reports appear. Confirm a regular consumer still receives the expected polling and callback poses. Reboot is needed to apply SystemHook; replacing a dylib cannot unhook a process that already loaded it.

ABI sources: Xcode iPhoneOS SDK CMMotionManager.h, CMAttitude.h, CMDeviceMotion.h, system notify.h, Apple CMAttitude and CMDeviceMotion. No Hopper-derived offset or private initializer is used.