Skip to content

`RepScoringConfig`

A per-movement opt-in gate that controls whether form scoring affects rep validity. By default (RepScoringConfig.disabled) the gate is off and a rep counts the moment its phase sequence completes, the tracker does no anti-cheat rejection based on tempo, depth, or form rules. Trainers opt in per movement by setting enabled: true and choosing which dimensions drive the overall score.

When the gate is on, MovementTracker still fires onRepCompleted for every rep (so consumers can render “that rep didn’t count” feedback), but RepQuality.shouldCount flips to false for reps below minQualityScore.

Shape

class RepScoringConfig {
final bool enabled; // master switch
final double minQualityScore; // 0–100 floor
// Dimension toggles
final bool useForm;
final bool useTempo;
final bool useDepth;
final bool useStability;
final bool useRangeOfMotion;
final bool useHoldDuration;
// Optional weight overrides, null = use the inferred weight
final double? formWeight;
final double? tempoWeight;
final double? depthWeight;
final double? stabilityWeight;
final double? rangeOfMotionWeight;
final double? holdDurationWeight;
}

Fields

FieldDefaultMeaning
enabledfalseMaster switch. When false, reps count purely on phase traversal, the score is reported but doesn’t gate validity.
minQualityScore50.0Minimum overall score (0–100) required to count a rep when enabled: true. Ignored when disabled.
useFormtrueWhether rule-based form violations contribute.
useTempotrueWhether rep tempo (duration) contributes.
useDepthtrueWhether depth (phases visited per rep) contributes.
useStabilitytrueWhether stability (frame-to-frame jitter) contributes.
useRangeOfMotiontrueWhether total angular travel per rep contributes.
useHoldDurationtrueWhether held-position duration contributes (for static-hold movements).
formWeight, tempoWeight, etc.nullManual weight overrides. null falls back to the inferred weight from ScoringProfileInferrer.

Toggling a use* flag to false removes that dimension from the weighted sum entirely, the remaining active weights are renormalised to sum to 1.0.

Defaults

const disabled = RepScoringConfig();
// enabled: false, all dimensions on, minQualityScore: 50, no weight overrides

RepScoringConfig.disabled is the static “off” value. Every movement inherits this unless the trainer explicitly opts in via the Studio’s rep-scoring panel.

JSON

RepScoringConfig round-trips via toJson / fromJson. The JSON form omits null weight overrides for compactness:

{
"enabled": true,
"minQualityScore": 65,
"useForm": true,
"useTempo": true,
"useDepth": true,
"useStability": true,
"useRangeOfMotion": false,
"useHoldDuration": true,
"formWeight": 0.4,
"tempoWeight": 0.3
}

Persisted alongside the movement definition inside the .pose file. All rep-counting paths (Studio preview, your consumer app) honour the same config , an authoring change in Studio flows through every consumer.

Patterns

Default, anti-cheat off

final tracker = MovementTracker.withPreset(MovementTrackerPreset.standard);
tracker.load(movement); // RepScoringConfig.disabled applies by default

Opt in to anti-cheat per-movement

tracker.updateRepScoring(const RepScoringConfig(
enabled: true,
minQualityScore: 70,
// form + tempo + ROM + stability all active by default
));

MovementTracker.updateRepScoring (also available on ShowcaseTracker) takes effect on the next rep, no session restart required.

Watch what the gate is doing

Subscribe to TrackingResult.pipeline to surface the gate state in a trainer-facing UI:

final snap = result.pipeline;
if (snap.scoringGate.enabled && !snap.lastRepCleared) {
// last rep didn't clear the gate
}

This is what the Studio’s rep-scoring side-panel uses to flash a “rejected” indicator on reps the gate silently dropped.

Trim a dimension

A side-rest movement where tempo doesn’t matter:

const RepScoringConfig(
enabled: true,
minQualityScore: 60,
useTempo: false, // ignore duration
useHoldDuration: false,
);

The remaining dimensions (form / depth / stability / ROM) renormalise their weights automatically.