Skip to content

`TrackingResult`

The per-frame output of MovementTracker.processFrame. Carries the scalar state your UI cares about (rep count, form score) plus the raw inputs that produced it (the Pose, the per-channel tracking values) so consumers can render without re-running pose detection.

Returned by processFrame and forwarded through TrackedMovementView.onTrackingResult and ShowcaseTracker.frame.value.

Fields

class TrackingResult {
final String? phaseId;
final int repCount;
final double formScore;
final List<String> feedback;
final RepQuality? lastRepQuality;
final bool repJustCompleted;
final Map<String, double> trackingValues;
final RepPipelineSnapshot pipeline;
final Pose? pose;
}
FieldMeaning
phaseIdCurrent phase id from the state machine. null until the first phase transition fires.
repCountNumber of completed reps in this session. Monotonic, reset() zeroes it.
formScoreLive form score, 0–100, rolling weighted average across recent frames.
feedbackList of currently-active feedback messages (the same text FormFeedbackEvent carries, deduplicated for display).
lastRepQualityPer-dimension quality breakdown for the last completed rep. null until the first rep completes. Populated only on the frame repJustCompleted == true, track separately for cumulative quality.
repJustCompletedtrue on the single frame a rep transitions to “done”. Use this to trigger one-shot effects (counter bump, haptic).
trackingValuesAll per-channel scalar values this frame, keyed by TrackingPoint.id. Includes angles, distances, ratios, positions, velocities, and stabilities.
pipelineRepPipelineSnapshot, surfaces scoring-gate state for trainer-facing UIs.
poseThe Pose this result was computed from. Surfaced so PIP overlays and skeleton painters can read landmarks without re-running pose detection. Nullable so synthetic / test results stay cheap to construct.

RepQuality

When lastRepQuality is non-null, it carries the per-dimension breakdown of the rep that just completed:

class RepQuality {
final double score; // 0–100 overall
final RepGrade grade; // perfect / great / good / okay / poor
final double formScore; // form-rule compliance
final double? romScore; // range-of-motion fullness
final double? tempoScore; // movement controlled, not jerky
final double? stabilityScore; // steady, not shaky
final Duration duration;
final List<FormViolation> violations;
}
FieldMeaning
scoreOverall 0–100, weighted across the per-dimension components.
gradeRepGrade enum: perfect (95+), great (85+), good (70+), okay (50+), poor (< 50).
formScoreRule-based compliance, penalties for triggered violations.
romScoreHow fully the rep traversed the authored phase sequence.
tempoScoreWhether the rep stayed in the “controlled” duration band (< 1 s is flagged as momentum; the ideal band is 2–4 s).
stabilityScoreWhether tracking-point values held steady or wobbled.
durationWall-clock duration of the rep.
violationsAll FormViolations that fired during the rep.

shouldCount (a RepQuality getter) returns true when score ≥ 50 , the default threshold the optional RepScoringConfig gate uses unless overridden.

RepPipelineSnapshot

class RepPipelineSnapshot {
final RepScoringConfig scoringGate;
final bool lastRepCleared;
}
FieldMeaning
scoringGateThe currently-active RepScoringConfig. Trainers can see at a glance whether scoring is enabled.
lastRepClearedWhether the previous rep cleared the scoring gate (i.e. would have counted under the active gate). Trainers can spot reps the gate is silently rejecting.

Patterns

Bump a rep counter

StreamBuilder<TrackingResult>(
stream: tracker.results,
builder: (context, snap) {
final reps = snap.data?.repCount ?? 0;
return Text('$reps', style: counterStyle);
},
);

Show the per-rep quality breakdown

lastRepQuality is only non-null on the completion frame. Capture it into your own state when repJustCompleted is true:

RepQuality? _latestQuality;
void _onTrackingResult(TrackingResult r) {
if (r.repJustCompleted && r.lastRepQuality != null) {
setState(() => _latestQuality = r.lastRepQuality);
}
}

Read a specific tracking-point value

final kneeAngle = result.trackingValues['knee_angle'];
if (kneeAngle != null) {
// render
}

The keys are the TrackingPoint.id values authored in your .pose file.