`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 stringtoJson 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 orderint get totalFrames; // sum of frames across positionsbool get isRuleBased; // positions.isEmpty && phaseConfigs.isNotEmptybool get hasReferenceFrames; // positions.isNotEmptyCopy
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.