Sensors baseline: hinge, pose, motion and device identity
Domain sensors of the iPhone Duo simulator baseline (iPhone19,4, iOS 27.1
24A94425, Xcode 27.1). Simulator duo-baseline-sensors
(C0EF5D72-68AE-4F00-8594-10039EADA1AC, a clone of duo-baseline-harness).
Records: sensors.json (README "Record format", plus two extra fields per
record: group, the probe expression it came from, and sim_note, set when
the value is synthetic or sim-only). Raw transcripts: raw/sensors/snap/.
Files
Snapshot order (history matters)
full = SpringBoard (pose, coremotion, uikit, cmtables, identity, cmquery),
backboardd (hid), Preferences (coremotion, uikit, cmtables, identity, cmquery).
light drops cmtables, identity and cmquery (posture-independent; checked
identical in 00–03). Every posture.zsh run verified (exit 0).
orientation in each record is the scene's interface orientation, as the
README defines it (SpringBoard: the primary window scene; Preferences: its
own window scene; backboardd and the posture-independent groups take
SpringBoard's). The device orientation passed to the display tool is in the
posture label. They differ on the inner panel, which is mounted a quarter
turn from the cover: Preferences on the inner reports interface orientation
4 (landscapeRight) for a portrait device, 1 for device landscapeLeft and 2
for device landscapeRight; on the cover it stays 1 (Preferences and the home
screen do not rotate on the cover). SpringBoard's primary scene is SB-main
(1) when closed and com.apple.springboard.bringup when open. The bringup
scene's interface orientation depends on history: it stayed 1 through the
portrait book/open/closed runs (00–09, including book landscapeLeft), became
2 with a landscapeRight device (10, 12), and after the landscape runs it was
4 for open portrait (13) and stayed 4 on the dark inner panel after
closing (14). So open portrait reads SpringBoard orientation 1 in
open(after-book) but 4 in open(portrait,after-open); compare against the
VM with the same history.
What is synthetic (do not align the VM to these)
The simulator has no hinge, accelerometer, gyro or magnetometer. Three different sources answer, and only one of them follows the posture driver:
- SpringBoard's display tool (follows the posture).
posture.zshrunsSBDisplayToolServiceCoordinator, which pushesSBFSyntheticCMAngle/SBFSyntheticMagicPoseEventthroughSBFDevicePoseProvider. Everything downstream of the provider follows the posture:currentPose(hinge state/position/regions,cmAngle,cmMagicPose,uiHingeStatus, cover/inner orientation), theFBSSceneSettingshinge fields SpringBoard sends to scenes, the scene hinge client components, Preferences'_UIHingeState,UIDevice orientation, and SpringBoard'scurrentHingePoseStateForTesting. These are the values the VM is meant to reproduce, but their CoreMotion-shaped fields are synthetic:cmMagicPose.hingeStateis 3 (OpenFlat) after every swap, closed included, andcmAngle.timestamponly becomes non-zero once the tool has run.sim_notesays "synthetic: written by SpringBoard's display tool sweep". - CoreMotion / locationd (frozen at the boot value). In the sim,
locationd's
CMDeviceStateRelayManagerpublishes two virtual HID services into backboardd:CoreMotion_DeviceStateRelay_Crane(usage page 0xFF0C, usage 0x21, the hinge angle;ReportInterval10000, event mask bit 0x2C) andCoreMotion_DeviceStateRelay_DeviceState(0xFF0C / 0x22, vendor defined). Their last events, the device state CoreMotion hands out (propertyA1 portrait,propertyB1 closed, timestamp 319060.797), and the device state SpringBoard's ownCMMagicPoseManagerreceived are the same in every snapshot: closed, 0°, portrait, timestamps unchanged. Nothing the posture driver does reaches CoreMotion or HID.sim_notesays "sim has no hardware backing". - Host / CoreSimulator (sim-only).
sysctl hw.modelMac17,9,hw.targetJ714sAP,kern.*(host macOS 27.0.1), theSIMULATOR_*environment,UIDevice name(the sim's name), and MobileGestalt answers that come from the host (ProductTypeDesc*Mac17,9,HWModelDescription*J714sAP,HardwarePlatformt6050,DeviceMemorySize64 GB,CPU*,IsSimulatortrue,device-name"iPhone Simulator") or from the device type'scapabilities.plist(screen, artwork, DeviceSupports* entries it lists). These do not exist in this form on the VM.
SpringBoard's pose path (source chain)
-[SpringBoard devicePoseProvider]is oneSBFDevicePoseProvider; the same instance sits inSBDisplayToolService,SBDisplayToolServiceCoordinator,SBDisplayContentModeCoordinator(also itslikelyTransitionDelegate),SBHingeMotionUnintendedInteractionMonitorandSBAmbientPresentationCoordinator(raw/sensors/discovery/springboard-selectors.txt).- Its inputs:
_cmAngleManager(aCMAngleManager,isAngleActiveYES, interval 0.01 s),_cmMagicPoseManager(its ownCMMagicPoseManager, not+sharedManager; itsCMDeviceStateManageris registered for notifications and holds the boot device state),_accelerometernil, and the display tool, which overwrites the pose. Its heartbeat source is theSB-mainscene. - Observers (record
SBFDevicePoseProvider._observers): scene region updaters,SBDisplayContentModeCoordinator,SBFDisplayContentModeResolver,SBHomeScreenController, twoSBTraitsEmbeddedDisplayPipelineManager,SBBiometricOcclusionMonitor×2,AMAmbientPresentationTriggerManager, the UI scene controllers, and SpringBoard itself. currentPose.internal.isOnVMis NO in the sim (worth comparing: the provider has a VM flag),isDisplaySupport3SetNO,hingeRegionsAreDefaultYES,usesStrictCoreMotionNO,invertHingeAnglesNO,flipState.isAvailableNO,continuousFoldEnabledYES, transition style 2.- Boundaries (
SBFDevicePosePTSettings): opens at 5°, closes at 175°, flat at 175°; swap degrees 85 (continuous live), 20/85 (snapshot open/close), 85 (fade).settings.closedToPartiallyOpenBoundarymoves with history: 85 at boot, 20 after a sweep that ended open. - Hinge regions move with history (provider description): at boot
closedis 0.00→0.47; after book 0.00→0.28 (partiallyOpen 0.28→0.97, toClosed 0.56→0.03); after a fold from open or book 0.00→0.11. Open is the same from either path.
uiHingeStatus.magicPose uses the tabletop pose numbering (3
PartiallyOpenPortrait, 5 PartiallyOpenLandscapeLeft), not CoreMotion's hinge
state. UIDevice orientation (SpringBoard and Preferences alike) is the lit
panel's device orientation: closed landscapeLeft → 4 (the cover's), book
landscapeLeft → 3 (the inner's).
App side (Preferences)
_UISceneHingeStateClientComponentexists on every scene but holds no state until something observes it (wantsSceneHingeState: NO,hingeStatenil).probes/sensors-app-hinge-register.lldbregisters one no-op observer per scene, the wayUIHingeInteractiondoes: a runtime classVPSensorsHingeObserverwhose callback IMP is-[NSObject self], kept alive by an associated object onUIApplication(no block escapes). After that the component reports, per posture: closedstatus1 / angle 0, bookstatus3 /magicPose3 / angle 100, openstatus4 / angle 180. Note the numbering differs from SpringBoard'suiHingeStatus.status(1/2/3).- Preferences'
FBSSceneSettingshinge fields (hingeAngle,hingeStatus,magicPose, …) stay 0 in every posture; the state arrives insceneHingeStateinstead. UIApplication currentHingePoseStateForTestingis {0,0,0,0} in Preferences in every posture (it is SpringBoard's testing state; in SpringBoard it follows the pose, e.g. {3,3,3,0} for closed landscapeLeft).- CoreMotion in the app: same frozen device state as SpringBoard;
queryDeviceStateBlockingreturns nil at once in both processes.
HID (backboardd)
[[BKHIDSystem sharedInstance] IOHIDServicesMatching:nil]lists 15 services (an empty dictionary matches none;IOHIDEventSystemCopyServiceson its_HIDEventSystemgives the same 15). Two are CoreMotion's virtual relays (TransportVirtual,VendorID/ProductID0xFF00):CoreMotion_DeviceStateRelay_Crane,PrimaryUsagePage0xFF0C,PrimaryUsage0x21 — the same usage pair the VM's Hinge dylib publishes.IOHIDServiceCopyEvent(svc, 0x2C)returns a hinge-angle event:AngleDegrees0,MechanicalAngleDegrees0, velocity NaN,StateClosed, phase Changed, with a vendor-defined child (usage 0x80, 21 bytes).CoreMotion_DeviceStateRelay_DeviceState, 0xFF0C / 0x22: last event is vendor defined, data02 01 01 00 01 00 00 01.
- Both events carry the same timestamps (mach 7657459129569 / …269) in every
snapshot: the posture driver produces no HID hinge event; it bypasses
HID and CoreMotion entirely and writes SpringBoard's pose provider. On the
VM, which injects
IOHIDEventCreateHingeAngleEventinto a 0xFF0C/0x21 virtual service, the event path is real; in the sim it only carries the boot value. - No
BK*/Sim*/HID*class with "hinge" in its name is loaded in backboardd;BKHIDEventProcessorRegistryhas no hinge processor (list inBKHIDEventProcessorRegistry.eventProcessors).HIDVirtualEventServiceexists.-[BKHIDSystem systemEventOfType:0x2C …]returns the DeviceState relay's vendor event, not a hinge event. - The rest are the simulator's HID services (SimulatorHID: digitizers 0x0D/0x04
for each display, keyboard, buttons, game controller, Indigo
0xacefade…services); full properties inIOHIDService.properties.
UIDevice
Every zero-argument getter of UIDevice (instance and class, from
class_copyMethodList) is recorded; serial/UDID/vendor-identifier getters,
allocators and _tapticEngine are recorded as skipped. Highlights:
model/localizedModel "iPhone", systemName "iOS", systemVersion "27.1",
userInterfaceIdiom 0, sf_productType "iPhone19,4", _hasTouchPad YES,
_supportsForceTouch NO, _nativeScreenGamut 1, _feedbackSupportLevel 0,
batteryLevel -1, +_hasHomeButton NO. There is no hinge, tabletop,
posture or fold getter on UIDevice in iOS 27.1: hingeState,
tabletopPose, _hingeState, hingeAngle, isFoldable, _isFoldable,
foldState, posture, _userInterfaceIdiom and _deviceOrientation all
fail respondsToSelector: (recorded with that error).
+[UIDevice currentDeviceOrientationAllowingAmbiguous:YES] is 0 in SpringBoard
in every posture, while in Preferences it follows the lit panel's device
orientation like -orientation (1 portrait, 3/4 for book landscape).
CoreMotion mapping tables (posture-independent)
Identical in every full snapshot, in SpringBoard and Preferences.
+[CMMagicPoseManager orientationForMagicPose:deviceSurface:] equals
+[CMDeviceStateEvent propertyAForConfigType:surface propertyA:orientation propertyB:hinge propertyC:tabletop] for every input (checked row by row;
createDeviceStateEventFromMagicPoseEvent: copies orientation, hinge state
and tabletop pose unchanged). In the sim sharedManager exists, unlike the
VM. Rows: result for device orientation A = 0..7 (0 Ambiguous, 1 Portrait,
2 UpsideDown, 3 LandscapeLeft, 4 LandscapeRight, 5 FaceUp, 6 FaceDown, 7
invalid). All rows not listed are 0 (ambiguous).
Against FoldDisplay's hard-coded mapping (VM, commit bb690532): B 1 → cover takes A, inner ambiguous: matches. B 2/3 → inner takes A, cover takes A with L/R and face up/down swapped: matches for tabletop 0 (and 11). B 4 and others → ambiguous: matches. Not covered by the VM's mapping: a non-zero tabletop pose with B 2/3 replaces A with a constant (table above), and orientation 7 maps to 0 when open. Surface 0 behaves like surface 1.
String tables (stringFor… records): magic pose hinge state 0 Ambiguous, 1
Closed, 2 PartiallyOpen, 3 OpenFlat, 4 OpenAmbiguous, 5 OpenTransitioning;
tabletop 1 ClosedLandscapeLeft, 2 ClosedLandscapeRight, 3
PartiallyOpenPortrait, 4 …PortraitUpsideDown, 5 …LandscapeLeft, 6
…LandscapeRight, 7 PartiallyOpenFaceDown, 8 OpenFlatFaceDown, 9
OpenFlatFaceUp, 10 OpenFlatPortrait; device state properties are named
ParameterA… (B: 9 = Max, C: 11 = Max).
Identity (MobileGestalt, sysctl, environment)
527 MGCopyAnswer keys (list: raw/sensors/identity/mg-keys-queried.txt,
taken from the strings of the sim's libMobileGestalt.dylib filtered to
screen/fold/device-class/layout/identity names, plus every key in the device
type's capabilities.plist, plus a few guessed fold keys that come back nil).
Values are identical in SpringBoard and Preferences and in every posture.
Selected answers: ProductType/HWModelStr/SubProductType/
ThinningProductType iPhone19,4; DeviceClass iPhone, DeviceClassNumber 1;
ModelNumber/RegulatoryModelNumber A3447; DeviceVariant A;
marketing-name "iPhone Duo"; HomeButtonType 2; DeviceCornerRadius 59;
HasThinBezel, IsLargeFormatPhone, DeviceSupportsDynamicIsland,
DeviceSupportsAlwaysOnDisplay(+Flipbook, AlwaysOnTime) true;
DeviceSupportsEnhancedMultitasking and …SingleDisplayEnhancedMultitasking
false; main-screen-width/height/scale/pitch 1398/2034/3/460 (the cover);
ScreenCaptureWidth/Height 1398/2034; ArtworkTraits phone, subtype 2034,
scale 3, P3, "iPhone Duo", APPLE11; DisplayExtendedProperties two entries:
cover (DeviceSurfaceType 1, rotation 0, corners 8,8,59,59) and inner
(DeviceSurfaceType 2, rotation 270, corners 55×4);
MGGetLogicalDeviceDisplayCount 2. Nil in the sim: DeviceSurfaceType,
main-screen-class, main-screen-orientation, MainScreenClass,
MainScreenScale, MainDisplayRotation, DisplayBootRotation,
MainScreenCanvasSizes, ScreenDimensions(Capability), idiom, subtype,
HasPartiallyOccludedDisplay, OLEDDisplay, DisplayMaxNits, and every
fold/hinge-named key tried. MGCopyAnswerForDisplayAtIndex returns nil for
all ten kMGDisplayIndexedQuery* keys at indices 0..2 (the keys are
self-pointers; both call forms tried, raw/sensors/discovery/mg-display-index.txt).
Missing, nil or errored
UIDevicehas nohingeState,tabletopPose,_hingeState,hingeAngle,isFoldable,_isFoldable,foldState,posture,_userInterfaceIdiom,_deviceOrientation.SBFDevicePoseProvider._accelerometernil.+[CMFactoryAngleManager isAvailable]raises SIGABRT in SpringBoard (raw/sensors/test/bisect.txt); recorded as not called.-[CMDeviceStateManager queryDeviceStateBlocking]nil in both processes.SBFDevicePoseDefaults lastKnownCMAngleDictionary/lastKnownCMMagicPoseDictionarynil.CMMotionManageraccelerometer/gyro/magnetometer/deviceMotion unavailable,availableAttitudeReferenceFrames0 (yet MGaccelerometer,magnetometertrue from capabilities.plist).- Scene hinge client components on SpringBoard's system-aperture scenes: nil state; on the app scene: nil until an observer registers.
- MobileGestalt: see the nil list above; 249 of 527 keys are nil.
Harness notes and workarounds
- lldb expressions here reject messages to
idand@try; locals are typedNSObject *, chained KVC is cast at each step, scalar ivars are read throughclass_getInstanceVariable+ offset (KVC on CoreMotion internals aborted),strrchr/strstr/isnan/isinfdo not link (use NSString and comparisons). A sanitizer turns NaN/Inf into strings beforeNSJSONSerialization(the hinge HID event's velocity is NaN). IOHIDServicesMatching:@{}matches nothing; pass nil.posture.zshdefaultsPOSTURE_OUTto the sharedraw/posture; the runner setsraw/sensors/posture.SpringBoardresponds todevicePoseProviderbut not to_displayToolService(that is only an ivar), sorespondsToSelector:must test the former to detect SpringBoard.- App scene persistent identifiers are UUIDs (Preferences' was
20901331-…for the whole run); join app-scene records on the display identity in thecall(on Main/on Unknown.main[3]), not the UUID.