---
title: RideForm iOS V0 Torso Motion Logger - Plan
type: feat
date: 2026-10-01
artifact_contract: ce-unified-plan/v1
product_contract_source: supplied-experiment-brief
execution: code
origin: RideForm/docs/requirements/ios-v0-brief.txt
---

# RideForm iOS V0 Torso Motion Logger - Plan

## Goal Capsule

- **Objective:** Let James record and inspect torso movements during a stationary motorcycle drill, then determine how reliably the iPhone distinguishes movement range, timing, repeatability, and smoothness.
- **Means:** A small SwiftUI iPhone app with Core Motion acquisition, a portable Swift analysis package, local session files, CSV export, and sensor diagnostics.
- **Authority:** The supplied brief governs product scope. Requirements below preserve it; technical decisions govern mechanisms within those requirements.
- **Stop condition:** The Definition of Done covers installation, roughly 50 transitions, durable export, reproducible analysis, and readiness for the next watchOS experiment. An inconclusive research result is valid; loss of evidence is not.
- **Landing:** Atomic local commits after verification. Push or shared-history operations require James's authorization. Implementation is a subsequent task; this document does not claim the app exists.

---

## Product Contract

### Summary

Build an inspectable iPhone IMU logger for torso-mounted stationary body-position practice. Preserve original measurements and actual timing, show post-session transitions and candidate metrics, and use the same session and analysis contracts for future Apple Watch recordings.

### Problem Frame

Visual judgment alone cannot establish whether repeated left/right movements have consistent range, timing, or smoothness. The experiment needs measurements that remain useful when definitions of smoothness change. The forthcoming Watch comparison also needs evidence of sampling, noise, drift, and timing limitations rather than assumptions about nominal device capabilities.

A torso IMU measures orientation and acceleration at its mounting location. It does not directly measure lateral distance, hip movement, whole-body posture, or motorcycle-relative motion if the motorcycle moves. Double integration is not a reliable distance measure here. Report tilt and angular excursion explicitly, and leave the distance interpretation as a research limitation.

### Requirements

**Acquisition and timing**

- R1. Use one shared `CMMotionManager`, initially request 100 Hz fused motion, and preserve every delivered measurement with its actual sensor timestamp.
- R2. Store fused attitude Euler angles, quaternion x/y/z/w, rotation matrix, rotation rate, gravity, user acceleration, available magnetic vector/calibration accuracy, and available heading/accuracy with validity and availability information.
- R3. Investigate simultaneous raw accelerometer and gyroscope acquisition; retain separate timestamped streams when the device/API supports them without compromising fused capture.
- R4. Report per-stream count, duration, mean/median Hz, interval minimum/maximum/standard deviation/p95/p99, large-gap counts/percentages, and observable delivery lateness; distinguish inferred missing intervals from known dropped samples.
- R5. Preserve monotonic receipt timing, session boundaries, event timing, clock-domain metadata, and wall-clock anchors sufficient for future platform comparisons without asserting synchronization between devices.

**Calibration and operation**

- R6. Collect a 2–3-second still neutral interval, compute a quaternion reference, and express later orientation relative to that reference without Euler subtraction.
- R7. Support a rigid mounting that need not be perfectly vertical/aligned, document device-to-rider axes, and display relative left/right, forward/back, and meaningful axial rotation.
- R8. Provide sensor status, requested/current measured rate, large calibration/start/stop controls, elapsed time, count, relative angles, and an obvious recording indicator.
- R9. Preserve completed recordings across another recording, navigation, export cancellation, and app relaunch; make interruption or saving failure visible.
- R10. During recording, LEFT/CENTER/RIGHT/GOOD/BAD buttons append timestamped markers without real-time rider judgment.
- R11. An optional audible drill issues CENTER → LEFT → CENTER → RIGHT → CENTER and records each actual cue issuance time.

**Storage and analysis**

- R12. Save a self-contained versioned master session with UUID, platform (`iphone` or `apple_watch`), device/OS/app versions, requested intervals, achieved statistics, calibration, boundaries, all captured streams, and markers/cues.
- R13. Export full-precision machine-readable master data and pandas-friendly CSV through the iOS share sheet.
- R14. Run analysis on saved common models, independently of Core Motion and UIKit; retain unmodified acquisition data and explicit parameters/version for every derived result.
- R15. Calculate and visualize relative orientation, angular velocity, angular acceleration, and user acceleration versus time, with markers/cues and approximate transition boundaries.
- R16. Detect onset, peak, settling, hold, and return, and report direction, starting/ending/peak angle, range, duration, settling time, overshoot, and secondary-correction count/magnitude per transition.
- R17. Investigate angular jerk and present candidate integrated squared jerk, peak/RMS jerk, normalized jerk, high-frequency energy, reversals/corrections, and angular-velocity-profile descriptors with definitions and limitations.
- R18. Report left/right range distributions, repeatability/variance, asymmetry, transition-duration distribution, and smoothness distributions without a single composite score.

**Experiment and portability**

- R19. Diagnostics records about 30 seconds stationary followed by controlled movements and reports noise, sampling, drift, and slow/rapid movement response.
- R20. Use common `MotionSample`, `MotionStream`, `Calibration`, `MotionSession`, `EventMarker`, `Transition`, and `MotionAnalyzer` concepts reusable on watchOS.
- R21. Preserve evidence for comparing frequency/jitter, apparent latency, stationary noise, slow/rapid sensitivity, drift, onset, peak angular velocity, range, smoothness, and repeatability across torso-mounted iPhone and Apple Watch Ultra 2.
- R22. Validate V0 on James's iPhone 16 Pro with roughly 50 deliberate left/right/center transitions, followed by inspection and export.

### Scope Boundaries

V0 includes acquisition, calibration, markers/cues, durable sessions, review, export, candidate analysis, diagnostics, and documentation. Accounts, cloud sync, subscriptions, social features, GPS/maps/track databases, helmet Bluetooth feedback, elaborate design, and real-time coaching are outside scope.

#### Deferred to Follow-Up Work

Add the watchOS target, Watch transfer/UI, valid Watch runtime strategy, and paired-device collection next. Automated cross-device clock alignment and claims of absolute physical sensor latency require additional experiment design. Common format and timing support are V0 requirements, not deferred.

Considered and not built: distance reconstruction from acceleration (unvalidated drift), automatic sensor-rate optimization (measure 100 Hz first), a generalized plugin architecture (two platform adapters suffice), and automatic riding-quality scoring (no validated target). Reconsider only with a demonstrated need and an observable validation target.

### Acceptance Examples

- AE1. Given an obliquely mounted rigid phone and a completed neutral/axis setup, moving left gives the same signed rider-axis interpretation as the documented standard mounting. Covers R6–R7.
- AE2. Given an irregular stream with a long gap, saving and reopening preserves timestamps/counts; analysis marks the gap and does not fabricate captured samples. Covers R1–R5, R12–R14.
- AE3. Given a completed session, starting another recording or cancelling share leaves the first session accessible after relaunch. Covers R9, R13.
- AE4. Given a sequence of outbound, hold, and return movements, review identifies the phases or marks them uncertain, and shows candidate metrics with their settings. Covers R15–R18.
- AE5. Given an unsupported optional sensor field or stream, the session records the reason and the available data remains usable. Covers R2–R3, R12, R21.

---

## Planning Contract

### Current State and Assumptions

RideForm is an empty directory within the `projects` monorepo. There is no existing app, package, test suite, deployment target, or project-specific convention to extend. All implementation paths below are proposed additions and are relative to the monorepo root; the plan and original brief live under `RideForm/docs/` to keep this independent project self-contained.

Unconfirmed implementation defaults: use iOS 17+ and Swift Charts, an ordinary checked-in Xcode project, and a local Swift package supporting iOS/watchOS/macOS. Choose a deployment target supported by James's installed iPhone OS and Xcode; do not raise it just to access an optional field. Signing team, installed SDK/OS versions, and physical mounting are execution prerequisites to inspect before device installation, not reasons to postpone package/schema work.

No iPhone-specific second-brain motion learning was found. The existing specification-first learning supports making the recording state machine and data contract explicit; it does not justify introducing a formal-verification framework.

### Key Technical Decisions

- KTD1. **Separate the reusable core from the platform adapter.** A local `RideFormCore` Swift package owns value models, math, serialization, analysis, and diagnostics. The iOS target owns Core Motion mapping, UI, audio, lifecycle, and sharing. Use Apple frameworks before adding dependencies; custom code is justified for this experiment's session contract, timestamp treatment, and rider-axis analysis. Governs R14, R20.
- KTD2. **Use callbacks off the main queue.** Request 0.01-second device-motion intervals, select supported `xArbitraryZVertical`, and record the selected frame. Use one serial sensor operation queue, copy Core Motion values into immutable records, and feed a serialized writer. Throttle only UI refresh (about 10 Hz), never acquisition. Hardware caps are expected and reported; unsupported required motion prevents Record with a clear status. Apple advises against the main operation queue for high-rate updates. Governs R1–R4, R8. [CMMotionManager](https://developer.apple.com/documentation/coremotion/cmmotionmanager), [callback API](https://developer.apple.com/documentation/coremotion/cmmotionmanager/startdevicemotionupdates(using:to:withhandler:)), [update interval](https://developer.apple.com/documentation/coremotion/cmmotionmanager/devicemotionupdateinterval).
- KTD3. **Optional means explicitly unavailable, not zero.** Preserve readings plus raw validity codes when exposed; mark heading invalid for the chosen arbitrary frame. Current Apple documentation lists `headingAccuracy` with iOS/watchOS 27 availability, so inspect installed SDK declarations and use a compile-time-supported, runtime-gated access path; older SDKs export unavailable status. Do not add location permission or a compass-based calibration for this experiment. Governs R2, R12. [CMDeviceMotion](https://developer.apple.com/documentation/coremotion/cmdevicemotion), [heading](https://developer.apple.com/documentation/coremotion/cmdevicemotion/heading), [headingAccuracy](https://developer.apple.com/documentation/coremotion/cmdevicemotion/headingaccuracy).
- KTD4. **Keep clock domains explicit.** Sensor time is seconds since boot. Store callback/event uptime on the same documented host clock, plus wall-clock/uptime anchors at start/end and interruption. Persist clock names, units, and sensor-to-host mapping checks; never subtract unrelated clock domains. Receipt minus mapped sensor time measures delivery age, not physical sensor latency. Cue records contain scheduled time and actual audio-dispatch time; audible onset latency remains unmeasured unless separately instrumented. Governs R5, R10–R11, R21. [CMLogItem timestamp](https://developer.apple.com/documentation/coremotion/cmlogitem/timestamp).
- KTD5. **Separate neutral from rider-axis identification.** Stillness gives a reference rotation but cannot infer arbitrary phone-to-torso yaw or which motion the rider calls left. Use quaternion hemisphere alignment and a normalized multi-sample mean, with motion dispersion/stillness checks. Keep the old calibration until a new one succeeds. After neutral, guide one deliberate left lean and one forward lean to establish/verify a fixed orthonormal rider basis; reject near-collinear gestures and show retry. Save those gesture samples and the fitted device-to-rider rotation. Provide a documented standard-mount shortcut with a sign check. This minimal setup supports R7 without pretending a stationary quaternion encodes mounting direction. No recalibration during recording.
- KTD6. **Make rotation conventions executable.** Internally use radians, rad/s, rad/s², rad/s³, seconds, acceleration in Core Motion g units, and magnetic field in µT. Export units in field names/schema; display angles in degrees. Define right-handed rider axes: +X forward, +Y left, +Z up. Left tilt is negative rotation about +X, displayed as a positive leftward angle; forward tilt is positive rotation about +Y; axial left turn is positive about +Z. Keep this display sign separate from the right-handed vector components. Treat quaternion as an active device-to-reference rotation in the core. Verify the adapter against Apple's matrix/attitude convention rather than guessing its inverse. Relative device rotation is reference-neutral inverse times current rotation; conjugate by the fixed device-to-rider basis. Rotate angular velocity and acceleration into the same declared frame before differentiation. Use quaternion-derived decomposed angles for plots with documented order/singularity limits, and SO(3) rotation vectors for calibration/detection where appropriate. Governs R6–R7, R14–R17. [Processed motion](https://developer.apple.com/documentation/coremotion/getting-processed-device-motion-data), [acceleration units](https://developer.apple.com/documentation/coremotion/cmacceleration).
- KTD7. **Save a versioned session bundle.** Master bundle contains `manifest.json`, `motion.jsonl`, optional `accelerometer.jsonl`/`gyroscope.jsonl`, `events.jsonl`, and calibration samples. Include schema/app/analysis versions, capability reasons, stream counts/units/frames, and requested/observed rates. Append batches during recording, flush checkpoints, and finalize manifest atomically after draining accepted samples. Keep incomplete bundles discoverable after interruption; label them incomplete rather than silently declaring success. Completed files are immutable. No UI feature deletes sessions in V0. Encode Double values with round-trip precision and reject nonfinite values explicitly with a diagnostic record. Governs R9, R12–R14.
- KTD8. **Define sampling statistics once.** For N valid ordered times, duration is last minus first; mean Hz is (N−1)/duration and median Hz is 1/median(interval). Report interval population SD and nearest-rank p95/p99. N<2 gives unavailable rate statistics. Preserve original order/data but flag nonpositive intervals; exclude those from interval summaries with counts. Flag unusually large gaps above 2.5× the stream median interval; report threshold, count, and percent of positive intervals. Record delivery-age statistics separately after clock mapping validation. Estimated missing intervals are clearly labeled estimates; timestamp gaps cannot prove a hardware drop count. Governs R4–R5.
- KTD9. **Compare fused-only and fused-plus-raw on device.** Same-manager raw callbacks are separate streams with independent timestamps/requested intervals. Run three matched 30-second stationary and movement captures in each mode with UI/writer active. Record achieved rates, p99 gaps, callback delivery age, backlog, and stationary noise. As an initial engineering guardrail, accept combined mode if fused mean Hz decreases at most 5%, p99 interval increases at most 20%, and writer loss count remains zero versus matched fused-only runs. These thresholds are unvalidated defaults, not Apple guarantees or research truth. If combined mode fails, keep fused capture primary, document results, and retain raw-only diagnostic acquisition for investigating its additional information. Revisit the conclusion if variance overlaps the threshold. Governs R3, R19, R21.
- KTD10. **Derive analysis without rewriting evidence.** Version an `AnalysisConfiguration` containing gap rules, derivative window, filter parameters, transition thresholds, and metric formulas. Use actual irregular intervals for finite differences and trapezoidal integration. Split at gaps, never differentiate across them. Provide unfiltered results plus an explicit derived smoothing option: local polynomial fitting in timestamped windows. No filtering in acquisition. Uniform resampling is permitted only in a separate derived spectral trace with its grid/interpolation/band limits disclosed; never serialize it as measured data. Governs R14–R17.
- KTD11. **Detect phases with an inspectable state machine.** Onset requires angular speed above a threshold for a minimum time; settling requires low speed and low angle variation for a dwell; hold follows settling; return moves toward neutral. Use stationary noise to inform thresholds and persist numeric values. Initial defaults: onset max(5°/s, stationary speed median + 6 MAD), minimum onset 0.10 s, settle speed 2°/s, angle variation 1°, settle dwell 0.40 s, neutral band 3°. These are tunable hypotheses. Detect peak before settling, link return legs to outbound legs, and support direct left-to-right transitions. Direction is signed movement along the verified rider lateral coordinate, with neutral-to-left/right versus return classifications separated. A marker is an annotation, not ground truth. Incomplete/truncated/gap-crossing events carry quality flags and unavailable metrics rather than invented endpoints. Governs R15–R18.
- KTD12. **Foreground iPhone capture; no background entitlement workaround.** Keep the display awake during active recording and restore idle-timer behavior on stop. On becoming inactive/backgrounded, stop cues and acquisition, drain the writer, and save an interrupted session. Add motion usage description and inspect authorization behavior for the SDK/APIs used; do not assume every Core Motion API prompts identically. Future Watch torso capture must investigate screen-off, off-wrist detection, runtime eligibility, and expiration on Ultra 2. Extended runtime types must match actual use; do not misuse a workout or therapy category merely for runtime. Batch APIs are a future candidate, not a V0 guarantee of sample rate or latency. Governs R8–R9, R20–R21. [Motion usage description](https://developer.apple.com/documentation/bundleresources/information-property-list/nsmotionusagedescription), [Watch extended runtime](https://developer.apple.com/documentation/watchkit/using-extended-runtime-sessions), [batched sensors](https://developer.apple.com/documentation/coremotion/cmbatchedsensormanager).

### High-Level Technical Design

```mermaid
flowchart TB
  Sensors[One Core Motion manager] --> Adapter[iOS adapter and clock mapping]
  Adapter --> Writer[Serialized session writer]
  Controls[Controls and cue dispatch] --> Writer
  Adapter --> Live[Throttled live readout]
  Writer --> Bundle[Immutable completed session bundle]
  Bundle --> Analyzer[Portable MotionAnalyzer]
  Analyzer --> Review[SwiftUI charts and transition review]
  Bundle --> Export[Master bundle and CSV share]
  Analyzer --> Export
  Watch[Future Watch adapter] -.-> Writer
```

```mermaid
stateDiagram-v2
  [*] --> Uncalibrated
  Uncalibrated --> Calibrating: neutral plus axis setup
  Calibrating --> Ready: validated
  Calibrating --> Uncalibrated: retry
  Ready --> Recording: start
  Recording --> Finalizing: stop or interrupt
  Finalizing --> Saved: writer drained and finalized
  Finalizing --> SaveError: failure
  SaveError --> Finalizing: retry preserving bundle
  Saved --> Ready: next session
```

Successful recalibration replaces the prior reference from Ready; failed recalibration returns to Ready with the prior reference. UI availability derives from this state, not independent booleans. A SaveError cannot start a new session over the unsaved one.

```mermaid
flowchart TB
  Read[Read saved session and configuration] --> Validate[Validate times, frames, and gaps]
  Validate --> Relative[Relative rider orientation and vector transforms]
  Relative --> Derive[Irregular-time derivatives and optional derived filtering]
  Derive --> Phases[Onset, peak, settle, hold, return]
  Phases --> Metrics[Per-transition metrics and quality flags]
  Metrics --> Aggregate[Matched left/right distributions]
```

### Metric Definitions and Limits

Use transformed fused gyro for angular velocity; do not call it the derivative of an Euler plot. Angular acceleration is its timestamp-aware derivative, and angular jerk is the derivative of that acceleration in the declared frame. Frame choice, smoothing, derivative windows, and gaps affect each result.

For transition interval T and scalar lateral jerk j(t), report integral of j² dt (rad²/s⁵), maximum |j| (rad/s³), and RMS jerk √(integral(j²)/T). Normalized squared jerk is T⁵/A² × integral(j²), with A the angular excursion in radians; return unavailable for near-zero excursion. The vector-norm variants are distinct named metrics, not interchangeable with lateral metrics.

High-frequency energy is a derived spectral ratio above an explicit cutoff (initially 5 Hz) to analyzed-band energy, using a documented uniform grid, window, and Nyquist limit. Mark it unavailable for short intervals, large gaps, or inadequate effective sampling. It proxies high-frequency corrections/noise, not rider skill. Reversals require minimum duration and excursion above configured noise thresholds; exclude the intentional return leg. Profile descriptors include peak count, peak timing fraction, and acceleration/deceleration duration, without ranking a single ideal shape.

Transition duration is onset to end of deceleration before the confirmed settled dwell. Settling time is that end to confirmed stable hold; peak time remains separate. Overshoot is signed excursion beyond the settled target in movement direction. Secondary corrections are reversals after the primary peak and before stable hold, with individual magnitudes. If no settled target exists, overshoot/settling are unavailable. Save all boundary timestamps and definitions.

For session summaries, report sample sizes, individual values, median/IQR, mean/SD, and variance by comparable outbound/return and left/right groups. Left/right signed difference compares magnitude means; normalized asymmetry is (left−right)/((left+right)/2), unavailable near zero or without both sides. Repeated trials vary in intent, so differences describe this dataset rather than proving rider impairment.

### Output Structure

```text
RideForm/
  RideForm.xcodeproj/
  App/                         SwiftUI, sensor adapter, clock, cues, sharing
  Packages/RideFormCore/
    Package.swift
    Sources/RideFormCore/       models, math, persistence, analysis, diagnostics
    Tests/RideFormCoreTests/    fixtures and deterministic contract tests
  Tests/RideFormAppTests/       adapter, writer integration, lifecycle
  Tests/RideFormUITests/        real UI workflow with deterministic motion input
  docs/requirements/           original brief
  docs/plans/                  implementation plan
  docs/                        schema, axes, metrics, protocol, validation evidence
```

---

## Implementation Units

### U1. Establish the common model and build boundaries

**Goal:** A buildable iOS shell and portable tested session contract. **Requirements:** R12, R14, R20. **Dependencies:** None.

**Files:** `RideForm/RideForm.xcodeproj/`, `RideForm/App/RideFormApp.swift`, `RideForm/App/Info.plist`, `RideForm/Packages/RideFormCore/Package.swift`, `RideForm/Packages/RideFormCore/Sources/RideFormCore/Models.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/SessionSchemaTests.swift`, `RideForm/docs/session-schema.md`, `RideForm/CLAUDE.md`, symlink `RideForm/AGENTS.md`, `RideForm/docs/observations.md`.

**Approach:** Apply KTD1/KTD3/KTD4/KTD7. Define tagged independent streams and optional capability states. Store calibration inputs, sensor timestamps, receipt timestamps, markers, and configuration/version fields. Document exact axes/units and unknown-version behavior. No project generator dependency is necessary.

**Patterns:** Apple value types and Swift Codable; root testing/symlink conventions. **Execution note:** Model and serialization logic start with failing deterministic contract tests.

**Test scenarios:**

1. Encode/decode iPhone and synthetic Watch sessions with identical data; analysis-facing fields remain identical except platform/device metadata.
2. Round-trip difficult Double values and independent asynchronous timestamps exactly; no rounding or stream joining.
3. Empty optional streams differ from unavailable streams; unknown schema version yields an explicit compatibility error.
4. Nonfinite values produce explicit diagnostic handling rather than silent substitution.

**Verification:** Core builds/tests on macOS without UIKit/Core Motion; simulator app builds with the package dependency.

### U2. Acquire fused motion, clocks, and sampling diagnostics

**Goal:** Preserve actual sensor delivery and quantify sampling behavior. **Requirements:** R1–R5, R19, R21. **Dependencies:** U1.

**Files:** `RideForm/App/MotionCapture.swift`, `RideForm/App/SessionClock.swift`, `RideForm/Packages/RideFormCore/Sources/RideFormCore/SamplingStatistics.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/SamplingStatisticsTests.swift`, `RideForm/Tests/RideFormAppTests/MotionCaptureTests.swift`, `RideForm/docs/sensor-api-decisions.md`.

**Approach:** Implement KTD2–KTD4/KTD8/KTD9. Record acquisition errors and stream capabilities. Keep a fake motion source at the adapter boundary for workflow tests, visibly unavailable in production mode. Inspect actual SDK availability before optional access. Raw-stream investigation is a required device gate, not an assumption that streams are synchronized.

**Test scenarios:**

1. Eleven sensor samples at 0.01-second spacing report 100 Hz over 0.10 s, with correct interval summaries.
2. Irregular, duplicate, backward, empty, and one-sample timestamps preserve evidence and produce defined statistics/flags.
3. Delayed callbacks leave sensor-time intervals unchanged but increase measured delivery age after clock-domain validation.
4. Fused/raw callbacks with different offsets retain independent stream counts/times; missing optional sensors record reasons.
5. Stop races with a callback; the declared stop boundary and accepted sample count reconcile with persisted output.

**Verification:** Real iPhone fused-only/combined comparison in KTD9; saved timestamps, counts, errors, and timing summaries agree with independent file calculations.

### U3. Calibrate neutral and establish rider coordinates

**Goal:** Meaningful relative angles under oblique rigid mounting. **Requirements:** R6–R7. **Dependencies:** U1, U2.

**Files:** `RideForm/Packages/RideFormCore/Sources/RideFormCore/OrientationMath.swift`, `RideForm/Packages/RideFormCore/Sources/RideFormCore/Calibration.swift`, `RideForm/App/CalibrationController.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/CalibrationTests.swift`, `RideForm/Tests/RideFormAppTests/CalibrationIntegrationTests.swift`, `RideForm/docs/coordinate-systems.md`.

**Approach:** Implement KTD5/KTD6. Use 2.5 seconds of neutral samples, store dispersion and actual duration, and then fit/verify rider axes from guided gestures. Numeric stillness/rejection settings belong in saved calibration configuration. Document singularities and mounting-change invalidation. Do not hide yaw drift behind recalibration.

**Test scenarios:**

1. Quaternion q and −q average to the same orientation; angles crossing ±π avoid subtraction discontinuities.
2. Known ±20° lateral, ±15° forward, and axial rotations under several oblique mount transforms recover signs/ranges within 0.1° in noiseless fixtures.
3. Motion during neutral and collinear setup gestures reject calibration with retry; prior valid calibration survives.
4. Adapter attitude/matrix/quaternion agree on physical left/forward sign checks; saved/reloaded calibration reproduces live values.
5. Empty or too-short calibration cannot enable Record; recalibration is unavailable while recording.

**Verification:** Covers AE1; fixture proof plus real-device neutral/left/right/forward/back sign and repeat-neutral check.

### U4. Preserve sessions and export the exact evidence

**Goal:** Recordings remain inspectable after stop, interruption, and share cancellation. **Requirements:** R9, R12–R14. **Dependencies:** U1, U2, U3.

**Files:** `RideForm/Packages/RideFormCore/Sources/RideFormCore/SessionIO.swift`, `RideForm/Packages/RideFormCore/Sources/RideFormCore/CSVExport.swift`, `RideForm/App/SessionRepository.swift`, `RideForm/App/ShareExporter.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/SessionIOTests.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/CSVExportTests.swift`, `RideForm/Tests/RideFormAppTests/RecordingPersistenceTests.swift`.

**Approach:** Apply KTD7. Keep writer ownership serial, checkpoint during capture, persist sequence/counts, drain before finalization, and finalize atomically. Export one master archive plus separate per-stream/event/calibration CSVs and optional derived-analysis CSVs. CSV column names declare frames/units; do not align raw streams by row number. Temporary share files remain separate from the master.

**Test scenarios:**

1. Covers AE2: irregular timestamps and all sensor fields survive capture → disk → reload → CSV with counts and precision intact.
2. Covers AE3: two sessions plus cancelled export and relaunch preserve both masters.
3. Simulated write failure leaves discoverable incomplete data, reports SaveError, and allows retry without overwriting a completed session.
4. Truncated final JSONL record is diagnosed; complete prior records remain recoverable with incomplete status.
5. Buffered stop drains all accepted rows before the manifest counts are finalized; stale derived output cannot masquerade as current configuration.

**Verification:** Share to Files/AirDrop, reopen outside the app, compare master versus CSV stream counts/timestamps/values, and parse CSV using pandas or Python's CSV reader.

### U5. Deliver the recording workflow and timed cues

**Goal:** Operate the torso experiment with large readable controls. **Requirements:** R8–R11. **Dependencies:** U2, U3, U4.

**Files:** `RideForm/App/RecordingController.swift`, `RideForm/App/RecordingView.swift`, `RideForm/App/SessionListView.swift`, `RideForm/App/CuePlayer.swift`, `RideForm/Tests/RideFormAppTests/RecordingLifecycleTests.swift`, `RideForm/Tests/RideFormAppTests/CueTimingTests.swift`, `RideForm/Tests/RideFormUITests/RecordingWorkflowTests.swift`.

**Approach:** Implement the recording state machine and KTD4/KTD12. Show requested versus rolling observed rate and all requested controls/status. Use accessible identifiers, high-contrast recording indication, large targets, and Dynamic Type. Marker taps write events immediately; audio uses actual dispatch timestamps. Cancel pending cues on stop/interruption. Show the saved session before permitting another recording; use session list for reopening. GOOD/BAD remain user annotations.

**Test scenarios:**

1. Uncalibrated → calibration → ready → recording → saved; status, sample count, elapsed time, and button availability follow state.
2. Every marker tap writes one correctly labeled monotonic event within the recording interval.
3. A deliberately delayed cue writes actual rather than scheduled time; stopping cancels later cues and records cancellation/interruption.
4. Backgrounding during capture saves an interrupted session, restores idle behavior, and resumes into review without silent continued recording.
5. Covers AE3 through actual UI persistence/export cancellation; double taps cannot start two sessions or double-finalize one.

**Verification:** Simulator UI workflow with known motion input, followed by real-device calibration/record/mark/cue/stop/share. Inspect resulting event files rather than merely button presence.

### U6. Analyze transitions and candidate smoothness reproducibly

**Goal:** Answer the research questions using saved data. **Requirements:** R14–R18, R20–R21. **Dependencies:** U1, U3, U4.

**Files:** `RideForm/Packages/RideFormCore/Sources/RideFormCore/MotionAnalyzer.swift`, `RideForm/Packages/RideFormCore/Sources/RideFormCore/TransitionDetector.swift`, `RideForm/Packages/RideFormCore/Sources/RideFormCore/SmoothnessMetrics.swift`, `RideForm/Packages/RideFormCore/Sources/RideFormCore/SessionSummary.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/AnalysisTests.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/TransitionTests.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/SmoothnessTests.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/Fixtures/`, `RideForm/docs/analysis-metrics.md`.

**Approach:** Implement KTD6/KTD10/KTD11 and Metric Definitions. Use saved sessions only. Save analysis outputs alongside input identity/configuration so reruns are attributable. Comments explain noise sensitivity, smoothing/resampling effects, and why candidate smoothness is not validated riding quality. Outbound and return grouping remains explicit.

**Test scenarios:**

1. Analytic irregular-time constant-velocity and polynomial angular motion yield known acceleration/jerk/integrals within stated numerical tolerances.
2. Covers AE4: synthetic neutral → left → hold → center → right produces known onset/peak/settle/hold/return boundaries within one sample interval plus configured dwell.
3. Add overshoot and two secondary corrections; report their count/magnitude without counting intentional return as a correction.
4. Gaps, noise-only stationary data, aborted movements, direct left-to-right, and no stable hold yield defined phases/quality flags without invented metrics.
5. Same saved data/configuration reproduces every result; analysis leaves stream hashes/counts unchanged.
6. Known asymmetric left/right fixtures give expected range variance and normalized asymmetry; missing side/near-zero excursion gives unavailable values.
7. Added high-frequency perturbation changes disclosed spectral/jerk metrics; differing movement duration/amplitude exposes normalization effects without forcing a universal ranking.

**Verification:** Deterministic package tests with numeric expected values, plus independently recomputed representative real-session metrics using exported data. Add Algorithm Notes with units/invariants/test commands to project CLAUDE.md.

### U7. Review sessions and run sensor diagnostics

**Goal:** Make saved evidence and analysis limitations visible. **Requirements:** R15–R19, R21. **Dependencies:** U5, U6.

**Files:** `RideForm/App/SessionReviewView.swift`, `RideForm/App/DiagnosticsView.swift`, `RideForm/Packages/RideFormCore/Sources/RideFormCore/SensorDiagnostics.swift`, `RideForm/Packages/RideFormCore/Tests/RideFormCoreTests/DiagnosticsTests.swift`, `RideForm/Tests/RideFormUITests/SessionReviewTests.swift`, `RideForm/docs/diagnostic-protocol.md`.

**Approach:** Use Swift Charts for synchronized angle/velocity/angular-acceleration/user-acceleration timelines, marker/cue overlays, phase shading, and a per-transition table. Show distributions and sampling statistics with sample sizes/quality flags. Plot decimation is display-only and preserves extrema/gaps; statistics use full saved data. Diagnostics marks 30-second stationary phase, then slow and rapid left/right/forward/return drills. Compute gyro/user-acceleration mean/SD/RMS, gravity norm, quaternion-angle dispersion, and drift slope/end-start change from stationary data. Preserve protocol timing and movement markers.

**Test scenarios:**

1. Known fixture data appears on each requested chart with correct units, overlays, transitions, and summaries; gap shading survives display decimation.
2. Stationary diagnostics with a known bias/noise/drift yields expected statistics, excluding movement phase.
3. Unknown/insufficient data shows unavailable diagnostics/metrics rather than zero noise or perfect smoothness.
4. Reopen a saved session and change derived filter settings; visible results and exported configuration agree while captured data stays unchanged.

**Verification:** Narrow rendered/UI check against fixture counts and values, plus real iPhone 30-second diagnostic and controlled movement capture with exported results.

### U8. Execute the garage acceptance protocol and document Watch readiness

**Goal:** A usable V0 with a defensible research dataset. **Requirements:** R3, R19–R22, all acceptance examples. **Dependencies:** U1–U7.

**Files:** `RideForm/docs/garage-experiment.md`, `RideForm/docs/watch-readiness.md`, `RideForm/docs/validation/ios-v0-acceptance.md`, `RideForm/README.md`, `RideForm/Tests/RideFormAppTests/EndToEndSessionTests.swift`.

**Approach:** Install on iPhone 16 Pro; record OS/app/build versions and mounting/protocol. Perform stationary diagnostics, axis/sign checks, and approximately 50 deliberate transitions with slow/simple and intentional correction subsets, neutral holds, and at least two repeat blocks. Use a manually labeled subset or independently observed video timestamps as comparison evidence, not cue time as movement truth. Inspect detection misses/false positives, range variability, timing error, and candidate metrics against those labels. Keep raw data even when the research conclusion is inconclusive. Document Watch adapter fields, runtime/SDK checks, absence of a shared boot clock, and next experiment's matching protocol.

**Test scenarios:**

1. Capture → stop → relaunch → review → export → independent reload preserves the exact session and analysis inputs.
2. Offline review can account for all roughly 50 deliberate movements, including explicit uncertain/missed detections rather than hiding them.
3. Synthetic Watch-format fixture runs through the same core pipeline without UI/platform dependencies.
4. Exported stationary/drill data supports every comparison named in R21 or explicitly identifies the unmeasured physical-latency limitation in KTD4.

**Verification:** Acceptance report records observed counts, rates/gaps, comparison errors, variance and candidate metric limitations, artifact identifiers, and remaining findings. The real device trial cannot be replaced by simulator success. James performs physical mounting/riding actions; the implementer verifies files and analysis evidence.

---

## Verification Contract

Commands below are proposed verification surfaces to establish in U1, not commands already present or run. Exact simulator name/OS and signing destination must come from installed Xcode/device state.

| Gate | Proposed command or evidence | Covers |
|---|---|---|
| Portable core | `swift test --package-path RideForm/Packages/RideFormCore` | U1, U3, U4, U6, U7 |
| iOS build | `xcodebuild -project RideForm/RideForm.xcodeproj -scheme RideForm -destination 'generic/platform=iOS Simulator' build` | U1–U7 |
| App/UI tests | `xcodebuild -project RideForm/RideForm.xcodeproj -scheme RideForm -destination '<installed iPhone simulator destination>' test` | U2–U5, U7 |
| Persistence/export | Independently parse exported archive/CSV; compare every stream count, timestamp, selected fields and numeric round-trip | U4, U8 |
| Device acquisition | Matched fused-only/combined runs, axis/sign checks, SDK capability notes, real interruption/share tests | U2, U3, U5 |
| Research acceptance | 30-second diagnostics and roughly 50-transition iPhone 16 Pro trial with independently labeled subset | U7, U8 |
| Watch portability | Core has no UIKit/Core Motion dependency; Watch-platform fixture uses identical analysis | U1, U6, U8 |

Test deterministic logic first with an observed failing case before production change. Use Given/When/Then for lifecycle workflows. Timing/precision tests must compare actual persisted/generated artifacts; a mock callback or green encoder test alone cannot prove delivery. Coordinate math needs fixed analytic fixtures and physical sign checks. Analyze hardware capability only from device evidence; do not infer that a requested rate was achieved.

Hardware/data-quality thresholds (initial experimental defaults): zero unexplained writer loss, no silent session overwrite, reported rather than hidden sensor gaps, and exact master reload of valid captured doubles/timestamps. Detection accuracy and meaningful smoothness discrimination remain research outcomes to measure; a favorable answer is not a release gate. Persist the observation and limitations if noise, mounting, or the detector prevents a reliable conclusion.

### Agentic Version

1. **Simple automation (V0):** Recompute diagnostics/analysis after stop and generate exports. Objective metric: zero unexplained record loss and exact reproduced results for a given input/configuration. Measure counts, round-trip values, hashes, and numeric fixture checks. Actions are parsing, calculation, reporting. Stop after one analysis pass; invalid input yields quality flags, preserving the master.
2. **Semi-agentic follow-up:** Suggest alternate filter/detection settings from saved labeled sessions. Objective metric: onset/phase error and missed/false-transition rate on held-out labels. Mechanism: a fixed labeled corpus and reported error per setting. Action space: bounded versioned parameter candidates. Stop on a declared local compute/candidate budget; keep the previous accepted configuration and require human acceptance. Smoothness metric agreement with labels is a proxy for visual movement quality, not a truth about rider skill.
3. **Fully agentic follow-up, not ready to run:** Tune candidate analysis automatically only after enough independent labels and an agreed objective/holdout protocol exist. Use the same measurable detection errors, bounded action space, explicit budget/diminishing-return stop, and rollback on held-out regression. Missing today: validated riding-quality/smoothness ground truth, optimization objective weights, and the tuning budget. No unattended coaching or optimization loop is authorized in V0.

---

## Definition of Done

- All R1–R22 are realized and every unit's test scenarios have recorded evidence; optional API limitations follow AE5 rather than invented readings.
- The app installs on iPhone 16 Pro, completes the U8 garage workflow, saves every accepted record, reopens after relaunch, and exports through Files/AirDrop.
- Master schema includes every requested sample/metadata field or explicit unsupported/invalid status, and independently validated CSV preserves the master evidence.
- Relative coordinates pass synthetic mount-invariance and physical sign checks; calibration samples/axis mapping are preserved for reproduction.
- All requested timelines, phases, transition descriptors, candidate metrics, session summaries, and diagnostics appear with units/configuration/limitations.
- The research report answers what the dataset supports about range, timing, repeatability, and smoothness, including inconclusive results and measurement limits.
- The next task can add a Watch adapter/target using the existing common model/analyzer; Watch hardware rate/runtime/clock assumptions are explicitly unproven.
- Project documentation records setup, permissions, sampling/axis/schema/metric definitions, verification commands, experiment protocol, and API limitations. Useful reproducible learnings go to project solutions or second brain.
- No background jobs/services or abandoned experimental source remain without an explained disposition. Preserve files unless James authorizes deletion. Run git status; commit verified task work atomically and identify unrelated changes separately.

---

## Sources and API Recheck Points

Source of truth: `RideForm/docs/requirements/ios-v0-brief.txt`, copied verbatim from the attachment. Apple documentation checked 2026-10-01; recheck installed SDK headers, availability, selected frame, permissions, clock conversion, and device behavior before implementation claims.

The cited Apple APIs supply the framework/runtime contract. They do not establish the iPhone 16 Pro or Ultra 2's achieved rate, raw-stream coexistence impact, fusion latency, off-wrist behavior, or noise. U2/U8 provide that evidence. The Watch experiment must compare equivalent torso locations and account for non-identical mounts; simultaneous capture alone does not synchronize clocks.
