> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Motion State Model Card

> The bundled Linear SVC motion-state classifier — inputs, classes, runtime, and known limits

When `BehaviorConfig.enableMotionLite = true`, the SDK runs a small classifier on accelerometer features to predict the user's gross motion state. This page is the model card.

## Identity

| Attribute      | Value                                                                                 |
| -------------- | ------------------------------------------------------------------------------------- |
| Family         | Linear Support Vector Classifier (One-vs-Rest)                                        |
| Format         | ONNX                                                                                  |
| Asset path     | `assets/models/linear_svc_model.onnx` (Android) / bundled equivalents on iOS and Dart |
| Runtime        | ONNX Runtime (`ai.onnxruntime` on Android, native `onnxruntime` framework on iOS)     |
| Class count    | 4                                                                                     |
| Latency target | \< 5 ms per inference on a modern mid-range device                                    |
| Distribution   | Bundled in the SDK; no network calls; no over-the-air updates                         |

## Classes

```json theme={null}
["LAYING", "MOVING", "SITTING", "STANDING"]
```

These are the only labels the SDK emits today. The class list is loaded from the model's label asset on init; mismatches against the model's output dimension cause init to fail loudly.

## Inputs

The classifier consumes a fixed-order feature vector derived from a windowed accelerometer buffer:

* 3-axis acceleration in m/s² (gravity included).
* Sampling rate: native motion collector at the platform's max — typically 50 Hz.
* Window: a rolling buffer the SDK aggregates into derived features.

The exact set of derived features (statistical moments, jerk magnitudes, axis correlations, gravity-removed components, etc.) is owned by the SDK's `MotionFeatureExtractor` — refer to source for the authoritative list.

## Outputs

```dart theme={null}
class MotionState {
  final String majorState;                  // one of the four labels
  final double majorStatePct;               // probability mass on major state
  final Map<String, double> perStateConfidence; // class probabilities
}
```

`majorStatePct` comes from the model's calibration head (a softmax-style normalisation since the underlying SVC produces decision scores rather than probabilities). Treat it as a heuristic, not a calibrated posterior.

## Training context

* The model file is shipped as ONNX, exported from `scikit-learn` (Linear SVC with one-vs-rest).
* Training data, train/test splits, fairness metrics, and demographic coverage are not documented in the SDK source. Treat the classifier as a fast best-effort component, not a clinical sensor.

When you need stronger guarantees, switch to the runtime's on-device motion classifier by setting `emitRawMotionSamples = true` and forwarding samples to the Synheart runtime via `pushAccel`.

## Runtime considerations

| Concern       | Behavior                                                                                                                          |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Thread        | Inference runs on a background dispatcher. Results delivered back on the main isolate / main queue.                               |
| Battery       | Continuous 50 Hz collection is the primary cost — much higher than the inference itself. Disable when not needed.                 |
| Memory        | Model bytes loaded once on `loadModel()`; ORT session retained for the SDK's lifetime.                                            |
| Cold start    | First inference includes ORT environment + session init; subsequent inferences are pure compute.                                  |
| Failure modes | If the model fails to load (asset missing, ORT init error), the SDK logs and `motionState` is permanently `null` for the session. |

## When the model is and is not loaded

| Condition                                                 | Loaded?                                                                             |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `BehaviorConfig.enableMotionLite = false` (default)       | No — collectors don't run, no inference.                                            |
| `enableMotionLite = true`                                 | Loaded lazily on `initialize()`.                                                    |
| `emitRawMotionSamples = true && enableMotionLite = false` | Not loaded — the SDK only forwards raw samples to the host.                         |
| Both `true`                                               | Loaded; the SDK's classifier and the host-side runtime classifier run side-by-side. |

## Known limits

* **No gait variants.** "MOVING" is a single class; the SDK does not distinguish walking, running, cycling, or vehicle motion.
* **Posture vs activity.** "SITTING" vs "STANDING" relies on gravity decomposition, which is sensitive to phone orientation. A phone flat on a desk reads differently from one in a pocket.
* **No biometric calibration.** The classifier does not adapt per user.
* **No location or transport context.** Inputs are accelerometer-only; "MOVING" inside a vehicle is detected the same as walking.
* **Charging stationary cases.** A phone left charging on a flat surface typically classifies as "LAYING".
* **No confidence threshold.** The SDK returns whichever class has the highest score; downstream consumers should apply their own threshold using `majorStatePct` if they want to drop low-confidence predictions.

## Migration path

The runtime's `MotionStateHead` is the long-term replacement. Apps consuming `synheart-core` should already prefer:

1. Set `BehaviorConfig.emitRawMotionSamples = true`.
2. Set `BehaviorConfig.enableMotionLite = false`.
3. Read motion state from the runtime's HSI emit.

The SDK's Linear SVC remains for direct consumers and for the migration window.

## Related

* [Behavior Overview](/synheart-behavior/overview) — `enableMotionLite` and `emitRawMotionSamples` config.
* [Metric Definitions](/synheart-behavior/metric-definitions) — the other 12 metrics, separate from motion state.
