# U3 independent verification

Audited frozen U3 revision `b241573443a76529b1bbcf99064bbb7d15c2d1e8`, 2026-10-01. This audit wrote only this report and `/tmp/rideform-u3-independent/`; it made no production edits, commits, or device interactions. Read CLAUDE, requirements brief, U3/R6–R7/KTD5–KTD6/AE1, coordinate documentation, math/solver/controller, and existing evidence/tests.

## Result

Independent synthetic math passes. One actionable controller retry defect remains; physical axis/sign and repeat-neutral verification remain human gates.

### P2: Moving neutral is permanently treated as insufficient duration

`App/CalibrationController.swift:23` erases `neutralCoverage`'s failure with `try?`; line 63 reports every failed check as `tooShort` without leaving `.neutral`. Neutral samples only append, so a sample that exceeds a stillness threshold permanently prevents advancement even after the rider settles.

Concrete executable reproduction: start from a successful calibration, call `begin(recording: false)`, ingest 251 samples at timestamps `0...2.5` seconds with the same valid neutral quaternion. Set only sample zero's gyro to `(0.09, 0, 0)` rad/s; every later gyro/acceleration is zero. Call `advance()`.

- Expected: report `movingNeutral` and expose a retry/restart path; retain the prior successful calibration. This follows the documented moving-setup retry contract.
- Actual: returns false, stage remains `neutral`, error is `tooShort`, Record remains blocked. Appending another 250 stationary samples through timestamp 5.0 seconds produces the same result and prompt, `Hold still in neutral for 2.5 seconds.` Prior calibration is correctly retained, and explicit `cancel()` recovers.

Preserve the specific neutral-validation error and move failed motion/invalid evidence to retry, or deliberately restart the stillness collection with visible feedback. Waiting for additional samples alone cannot solve this. The reproduction calls the public controller API directly; U5 UI is not yet implemented. `/tmp/rideform-u3-independent/controller.log` records the observed values.

## Independent fixture provenance

`/tmp/rideform-u3-independent/generate.py` uses Python standard-library matrix products and trigonometry; it never calls OrientationMath. Random seed is 7351. It defines `R = Rz(z) Ry(y) Rx(x)`, a noncommuting neutral `N = Rz(1.02) Ry(-0.47) Rx(0.59)`, and three device-to-rider bases: documented standard mounting, `Rz(0.73) Ry(-0.61) Rx(0.48)`, and `Rz(-1.19) Ry(0.37) Rx(-0.81)`. Captured device-to-reference matrices are constructed as `N Bᵀ R B` and converted to quaternions independently with the matrix trace/diagonal algorithm.

The 483 fixture cases cover neutral, ±20° lateral, ±15° forward, ±20° axial, mixed XYZ rotation, ±179° and 180° yaw, and 150 random nonsingular mixed rotations per mount. Neutral samples alternate q/−q. Guided LEFT/FORWARD samples independently identify each mounting basis. Expected display angles are `(-x, y, z)` modulo 2π. Expected rider vectors are independently chosen `(0.23, -0.41, 0.67)` rad/s and `(-0.071, 0.092, 0.035)` g; device input vectors are calculated with `(Bᵀ R B)ᵀ Bᵀ` so the full current-device-to-neutral-to-rider transport is tested.

`main.swift` loads `fixtures.json` and invokes the real core. Each case is checked with q and −q and repeated using conjugated captured quaternions with the verified conjugate adapter convention: 966 identity plus 966 conjugate evaluations. Calibration is encoded and decoded through its real Codable representation, compared exactly, and used to reproduce each live result. The standard-mount solver also accepts the independently generated documented-mount gestures.

Numerical results from `math.log`:

| Measure | Maximum error | Required tolerance |
| --- | ---: | ---: |
| Display angle, wrapped component | 9.456324612244771e−14 rad | 1e−12 rad |
| Gyro/acceleration component | 1.5681900222830336e−14 | 1e−12 in respective units |
| Quaternion `1 − abs(dot(actual, expected))` | 2.220446049250313e−16 | 1e−12 |
| Live versus reloaded display values | 0 | Exact |

Source SHA-256: OrientationMath `c1851dc3572bbdb7ffc0387a3e3f8af12edbdd40e9fd8baa53cc16ca6cb947cc`; Calibration solver `5c03c36a9f098bbe679008770438a16e2e675aa4a2aeb9b1ac9deeecf4783115`; controller `fde3f89352c33a0ce97528953a4c0ba0adb56434030da910a7e74175d3663400`. Fixture SHA-256 is `c8915c4d8c5ac5579aee70878c1114f31425511a985a1e5ebd83945ab45fa5a5`; generator SHA-256 is `35df484d2f6c1a663425e12a550d4e7035bbf53eec7bfe0a1e8242667a185458`.

## Controller and coordinate review

The standalone controller probe compiles the actual `App/CalibrationController.swift` with exact value-type definitions of `CapturedRecord` and `CaptureSummary`; it does not instantiate MotionCapture. It verifies short neutral cannot advance, idle finish cannot succeed, neutral→left→forward→awaitingConvention→ready phases, Record blocked throughout setup, setup rejected while recording, collinear replacement rejected into retry, exact retention of the prior calibration, and cancel recovery. The moving-neutral failure above was independently reproduced.

The standard basis maps device +Z→rider +X, device +X→rider +Y, and device +Y→rider +Z; its determinant is +1. Its generated left gesture is negative device Z and forward gesture positive device X. Synthetic signed results agree with the documented right-handed rider basis and separate lateral display sign.

Coordinate documentation explicitly names decomposition `Rz * Ry * Rx`, pitch ±90° singularities, ±180° wrap/axis ambiguity, mounting-change refit, and yaw drift. The nonsingular fixture tolerance does not apply at exact gimbal lock. The audit establishes algebraic consistency under the supplied verified adapter convention; it does not establish Apple's physical attitude direction or actual phone mounting signs. Hardware neutral/left/right/forward/back/axial checks, repeat-neutral noise, and drift remain pending.

## Reproduction

All scripts, fixture JSON, binaries and logs are under `/tmp/rideform-u3-independent/`. Build with `DEVELOPER_DIR=/Applications/Xcode-beta.app/Contents/Developer`; use `xcrun swiftc -module-cache-path /tmp/rideform-u3-independent/cache` to avoid the default cache directory denied by the workspace sandbox. Emit RideFormCore from Models.swift, OrientationMath.swift and Calibration.swift with `-emit-library -emit-module -module-name RideFormCore`; compile each probe using `-I`, `-L`, and `-lRideFormCore` pointing to the temporary directory. Run with `DYLD_LIBRARY_PATH=/tmp/rideform-u3-independent`.
