# Saved-motion analysis

`MotionAnalyzer.analyze(session:configuration:)` accepts the saved portable common model and returns a Codable, Equatable `AnalysisResult`. It requires a verified calibration and captured-to-canonical convention. Nonfinite vectors, duplicate/decreasing/nonfinite times, and invalid configuration are rejected; the supplied master is never changed. Empty motion is an explicit quality flag. There is no Core Motion, UIKit, or Charts dependency.

`MotionAnalyzer.defaultConfiguration` selects `rideform-analysis-2`. This version supersedes v1 sidecars after fixing symlink master isolation, even-grid Nyquist energy, and incomplete-leg onset uncertainty. Returned configuration records the resolved defaults, implemented formulas, actual onset/correction thresholds, and algorithm version. Results include input session UUID/SHA256, original event markers, stationary speed median/MAD and angle MAD, both traces, phases, candidate metrics, distributions, and quality flags. The canonical sorted-key SHA256 includes the entire common session, including calibration/events/raw streams. It is a semantic input fingerprint, not a hash of arbitrary JSON whitespace.

`analyzeAndSave(session:configuration:masterURL:sidecarURL:)` writes an atomic `DerivedSidecar<AnalysisResult>` outside the master directory and rejects paths within that master (including resolved symlinks). Store derived files in a separate directory, for example `Documents/Derived/<session UUID>/<configuration hash>.json`. Sidecar identity includes sorted configuration SHA256 and input SHA256. Reanalysis replaces only the chosen derived sidecar. The immutable master and its archive are untouched.

Path checks resolve existing symlink components before appending absent descendants, including relative/chained links. Existing directory identity checks cover alternate casing on case-insensitive volumes. The writer uses the canonical output path and rechecks after creating parent directories. Symlink loops and non-file URLs are rejected as `invalidSidecarPath`.

## Traces, frames, and units

`AnalysisPoint.timestamp` is the saved sensor timestamp; `sequence` is the acquired sequence. Segment IDs change at gaps. Angles are relative intrinsic XYZ decomposition (`Rz * Ry * Rx`), with lateral display `-X`, forward `+Y`, axial left `+Z`, in radians. Decomposition near pitch ±90° is flagged above 85° and remains unsuitable for lateral interpretation there. Angles retain the quaternion decomposition's branch limits.

Angular velocity is fused gyro rotated through current relative orientation and the calibrated basis into the **fixed neutral rider frame**, in rad/s. It is not a derivative of the displayed Euler angle. Angular acceleration and jerk differentiate those fixed-frame vector components, in rad/s² and rad/s³. User acceleration is transformed into that same fixed frame, in Core Motion g units. Acquisition values remain in the saved session.

`rawTrace` means unfiltered derived frame transforms and derivatives; it does not relabel derived values as measured values. Raw derivatives fit a quadratic to the nearest three actual timestamps within each segment and evaluate its derivative at the central timestamp, including one-sided edge fits. Angular jerk repeats this derivative on acceleration. Fewer than three usable neighbors or a singular fit produces nil, including short segments. No derivative or integration bridges a gap. Gap threshold is `min(maximumGapSeconds, gapMultiplier × median positive interval)`; defaults are 0.1 s and 2.5.

`configuredTrace` equals raw unless `filterMethod = localPolynomial`. Local polynomial least squares uses centered actual-time windows (default 0.12 s, degree 2; degree 3 supported), separately per segment. The window fits angles, gyro, and user acceleration; velocity derivatives use timestamped polynomial fits and jerk differentiates the resulting acceleration. `filterApplied` identifies usable fits. An insufficient window retains the unfiltered plotted measurement, leaves unavailable derivatives nil, and adds `filter_window_insufficient`; it never silently expands across gaps. Smoothing can suppress corrections and change phase boundaries. Derivatives amplify noise. Compare both traces, disclose settings, and retain the original acquisition.

## Inspectable transitions

The detector processes chronological samples per segment with pending onset → active movement → candidate settling → confirmed hold. A speed run must exceed `max(5°/s, stationary lateral speed median + 6 MAD)` in one signed direction for at least 0.10 s. The boundary is the first sample of that confirmed run. A hold requires lateral speed at most 2°/s and angle range at most 1° throughout a 0.40 s dwell. If the range is exceeded, the dwell restarts at the observed sample.

Each transition exposes onset, maximum directed excursion (peak), start of final low-speed dwell (end of deceleration), and confirmed hold timestamps. Duration is onset to end of deceleration; settling time is end of deceleration to hold confirmation. The settled target is the median angle over that final dwell. These boundaries are algorithmic estimates within observed samples. Event markers are annotations, not ground truth.

Neutral band is 3°. Movement from a held nonneutral side toward neutral is a return; it links to the preceding outbound within the same continuous segment. A movement ending beyond neutral on the opposite side is `directCross` and carries a quality flag. Direct crossings are inspectable but excluded from comparable outbound/return summaries. Returns retain their original outbound side for left/right comparison. Group IDs and transition IDs are deterministic integers, not random UUIDs.

Overshoot is `max(0, movementSign × (peakAngle − settledTarget))`, so left/right and return legs use their movement direction consistently. Secondary corrections are angle-extrema runs after the primary peak and before the final settled dwell. Each must last 0.05 s and exceed `max(1°, 6 × stationary lateral angle MAD)`. Individual magnitudes and timestamps are retained. The initial correction away from an overshot primary peak is included; intentional return after confirmed hold is a separate leg.

Missing hold yields nil duration, settling, endpoint/overshoot, and unavailable interval metrics; the observed peak remains available with uncertainty. Flags include `gap_interrupted`, `truncated`, `onset_unconfirmed`, `onset_at_segment_boundary`, `no_stable_hold`, `unlinked_return`, and `direct_crossing`. No endpoint is invented across missing evidence. A post-gap movement already active at the segment start receives a boundary flag. Gradual movements below the onset threshold may be missed; low-speed intentional pauses may be interpreted as holds. These are tunable research hypotheses requiring labeled device trials.

## Candidate metrics

For a complete transition interval T (onset through end of deceleration), lateral jerk is `−angularJerk.x`. With actual saved intervals, trapezoidal integration yields:

| Metric | Definition | Units |
|---|---|---|
| Integrated squared lateral jerk | ∫j²dt | rad²/s⁵ |
| Peak lateral jerk | max absolute j | rad/s³ |
| RMS lateral jerk | √(∫j²dt/T) | rad/s³ |
| Normalized squared lateral jerk | T⁵/A² × ∫j²dt | dimensionless |

A is max−min lateral angle over the movement interval. Near-zero A (≤1e−8 rad), missing derivatives, gaps, incomplete endpoints, or too-short intervals produce an explicit `CandidateMetric.unavailableReason`. Vector-norm variants are not provided; scalar lateral and vector metrics must never be interchanged.

The spectral candidate uses lateral **angular velocity**, a separate derived uniform grid at 50 Hz, piecewise linear interpolation of actual times, mean removal and Hann window (rectangular available through `windowHann < 0.5`). `SpectralResult` retains the derived grid/time/value trace and metadata. DFT band limit is min(grid Nyquist, 0.5/maximum actual interval), conservatively exposing ineffective irregular sampling. Ratio is non-DC energy above 5 Hz divided by energy in that analyzed band. Minimum interval length is 1 s; gaps, band limit at/below cutoff, zero band energy, and grids over 4096 points are unavailable with reasons. No spectral grid is serialized as captured motion. Filtering, interpolation and window leakage affect this proxy; high-frequency noise can raise it.

The one-sided real-signal energy includes the even-length Nyquist bin once; conjugate-paired bins contribute twice. A relative 1e−12 band-limit tolerance avoids dropping a Nyquist bin through timestamp roundoff. This changes v1 results where Nyquist energy is material.

Velocity profile uses absolute lateral gyro speed: interior local maxima above the effective onset threshold, maximum speed, its timing fraction, start-to-maximum duration, and maximum-to-end duration. These describe a profile, without assuming an ideal shape. For incomplete legs, observed profile values remain inspectable alongside transition quality flags.

## Dataset summaries and validation limits

Four comparable groups separate outbound/return and left/right. Only confirmed holds without quality flags enter them. Range, duration, integrated/normalized/peak/RMS jerk, and spectral ratio retain individual values and each metric's own sample count, median, IQR (linearly interpolated quantiles), mean, sample SD and sample variance. Sample variance/SD are nil below two trials. Empty distributions are explicit, with zero count and nil statistics.

Range asymmetry compares magnitude means: signed difference left−right, normalized `(left−right)/((left+right)/2)`. Missing sides and near-zero denominator produce nil normalized asymmetry. Returns and outbound are never pooled. No composite score is created, and no metric has been validated as rider quality, impairment, lateral displacement, or motorcycle-relative movement.

Deterministic fixtures validate timestamp math, known phase boundaries, correction counting, gaps, saved identity, filters, spectra, duration/amplitude normalization, and asymmetric summaries. Real mounting/sign checks, independently labeled motions, matched raw/fused acquisitions, and roughly 50 garage transitions remain human verification gates. Real-session exported metric recomputation is pending device data.
