`MovementTracker`
The PoseFlow runtime. One instance per session. Wires
PhaseStateMachine (rep counting + phase
transitions), FormServiceV2 (rule-based form analysis), and
per-frame tracking-value computation (angles, distances, ratios,
positions, velocity, stability) for a single loaded
Movement.
import 'package:pose_flow/pose_flow.dart';
final tracker = MovementTracker.withPreset(MovementTrackerPreset.standard);tracker.load(movement);
tracker.onRepCompleted.listen((event) { debugPrint('Rep ${event.repNumber}: ${event.quality.grade.displayName}');});
// Per-frame:final result = tracker.processFrame(pose);Lifecycle
MovementTracker() │ vtracker.load(movement, configOverride: cfg) │ vtracker.processFrame(pose) → TrackingResult (every frame) │ vonRepCompleted stream (on rep)onFeedback stream (on form-rule violation) │ vreset() (start a new set)dispose() (tear down)Constructors
MovementTracker({MovementTrackerConfig? config});MovementTracker.withPreset(MovementTrackerPreset preset);MovementTrackerPreset values: casual, standard, strict,
competition. Each tweaks the underlying MovementTrackerConfig ,
rep-scoring gate floor, real-time feedback emission.
Loading a movement
tracker.load(movement, configOverride: cfg);| Param | Notes |
|---|---|
movement | The Movement decoded from a .pose file. |
configOverride | Optional published MovementConfig that swaps in different trackingPoints / phases / formRules from external storage (e.g. a Firestore hotfix on a deployed movement). When null, the movement’s inline lists drive the tracker. |
Behavioural notes
- The previous baseline snapshots (for
relativeToBaselinetracking points) are cleared. - Rolling sample history (velocity + stability) is cleared.
- The bucket smoother is cleared so the previous session’s committed bucket doesn’t bleed in.
load()callsreset()internally, rep state resets too.
Per-frame processing
final result = tracker.processFrame(pose);Returns TrackingResult
synchronously. The result also carries result.pose so downstream
consumers can read landmarks without re-running pose detection.
Streams
Stream<RepCompletedEvent> get onRepCompleted;Stream<RepQuality> get onRepQuality;Stream<FormFeedbackEvent> get onFeedback;All broadcast streams, subscribe from any number of listeners.
Reading state
int get repCount; // current countdouble get formScore; // current form score (0-100)String? get currentPhaseId; // phase machine stateMovementTrackerConfig get config;Movement? get movement; // currently loadedbool get isMovementLoaded;int get frameCount;RepScoringConfig get repScoring; // active scoring gateScoringProfile get scoringProfile; // inferred profile for this movementMutating state
void updateRepScoring(RepScoringConfig cfg); // live-tune the quality gatevoid reset(); // clear state, keep loaded movementvoid dispose(); // release resources, close streamsupdateRepScoring is what the Studio’s live-tuning panel uses to
react to slider input without restarting the session.
Configuration
MovementTrackerConfig has four knobs:
| Field | Default | Notes |
|---|---|---|
useFormRules | true | Disable form analysis entirely (reps still count). |
realtimeFeedback | true | Emit feedback events per frame. Set false for headless rep counting. |
useRepScoringGate | true | Whether the minimum-quality floor gates rep counting. When false, every rep counts regardless of quality. |
minRepQualityScore | 50.0 | Default minimum quality score when the movement doesn’t publish its own gate. |
The presets pre-tune these:
| Preset | Real-time feedback? | Gate? | Floor |
|---|---|---|---|
casual | off | off | 0 |
standard | on | on | 50 |
strict | on | on | 70 |
competition | on | on | 80 |
Movements that publish their own RepScoringConfig always win, the
preset’s floor is only the default for movements that don’t.
Performance
- Per-frame work in the tracker: ~3 ms on a 2023 phone. Dominated by the pose engine upstream, not the tracker itself.
- Loading a
Movement: ~5 ms. - Memory: tracker state is small (<100 KB); the dominant cost is the loaded movement itself.
Read next
TrackedMovementView, the drop-in widget that wrapsMovementTracker.Movement, the loaded document.RepCompletedEvent.FormFeedbackEvent.- Rep counting parity.