Skip to content

`Movement`

The in-memory representation of a .pose file. The single source of truth for “this movement” across every consumer surface.

class Movement {
final String id;
final String version;
final String name;
final String category;
final CameraAngle cameraAngle;
final int formatVersion;
final List<PosePosition> positions;
final List<PhaseConfig> phaseConfigs;
final List<TrackingPoint> trackingPoints;
final List<FormRule> formRules;
final RepDetectionConfig repDetection;
final RepScoringConfig repScoring;
final Map<String, List<AngleBand>> angleBands;
final Map<String, dynamic> metadata;
final DateTime createdAt;
final MatcherConfig matcherConfig;
}

Construction

Three entry points:

// 1. From a parsed JSON Map (the .pose file shape).
final ex = Movement.fromJson(json);
// 2. From a raw JSON string.
final ex = Movement.fromJsonString(rawString);
// 3. From a recorded reference frames Pose document, for the migration path.
final ex = Movement.fromPose(
pose,
repDetection: rd,
formAnalysis: fa,
repScoring: rs,
);

fromPose is the legacy-import adapter. It takes a recorded reference-frame Pose plus optional sidecar configs (rep detection, form analysis, rep scoring) and builds an in-memory Movement. Doesn’t write to disk. The next save flips the file to a true .pose file. See the .pose file format spec.

Serialisation

final json = ex.toJson(); // Map<String, dynamic>
final raw = ex.toJsonString(); // canonical pretty-printed string

toJson is JSON-stable, keys in deterministic order, no null fields written. You can byte-diff two .pose files to detect authoring drift. Round-trip guarantee: Movement.fromJson(ex.toJson()) == ex.

Convenience accessors

List<String> get positionIds; // every position's id, in order
int get totalFrames; // sum of frames across positions
bool get isRuleBased; // positions.isEmpty && phaseConfigs.isNotEmpty
bool get hasReferenceFrames; // positions.isNotEmpty

Copy

final updated = ex.copyWith(
name: 'Squat (revised)',
formRules: [...ex.formRules, newRule],
);

Standard Freezed-style copyWith, every field is overridable. Useful for inline tuning during authoring sessions.

Key fields

formatVersion

2 for Studio-authored .pose files. Defaults to 1 when omitted on load so files produced by the legacy reference-frame recorder still round-trip cleanly. Always set explicitly to 2 when writing.

cameraAngle

CameraAngle.front / .side / .any. High-level authoring hint, NOT a runtime lens selector, every consumer uses the front-facing camera. The runtime’s bucket detector (see camera buckets) decides which angleBands slice to use for view-dependent measurements.

positions

The recorded reference frames path. Empty for Studio-authored movements. When populated, MovementTracker activates vector matching against these positions via PhaseStateMachine.

phaseConfigs

The rule-based path. Each entry defines one phase + its outgoing transitions. PoseFlow Studio writes these directly. See PhaseConfig.

trackingPoints

Per-frame measurement channels. Each tracking point computes one scalar per frame from its configured landmarks. See TrackingPoint.

formRules

Declarative form analysis. Each rule references a trackingPoint, an operator + threshold, an optional phase scope, and a FormFeedback payload (cue text, audio, haptic). See FormRule.

repDetection

How a rep is counted. See RepDetectionConfig.

repScoring

Optional quality gate. When enabled, a completed rep must meet a minimum quality score to actually increment the counter. See RepScoringConfig.

angleBands

Per-bucket measurement bands for view-dependent channels. Keyed by bucket id; each value is a list of AngleBand entries (one per (channel, phase) for that bucket). Empty for view-invariant movements.

metadata

Map<String, dynamic>, author-defined. Hosts that need to attach managed fields alongside the tracking definition (image url, video url, body parts, muscle groups, supported buckets) typically nest them under a host-namespaced key like metadata.<host>.*. Consumers that don’t care about host-specific fields can ignore the whole metadata object.

Loading into the tracker

Never call MovementTracker.loadMovement directly. Use LoadMovementArgs.from:

LoadMovementArgs.from(
movement: unified,
movementConfig: cfg,
).applyTo(tracker);

This coalesces the inline lists from the .pose file with an optional Firestore-published MovementConfig overlay, then forwards to the tracker with the right flags. See the parity contract.

Reading the on-disk format

See the .pose .pose file spec for the byte-by-byte JSON shape.