Skip to content

`ScoringProfileInferrer`

ScoringProfileInferrer is a static helper that derives sensible per-dimension scoring weights for a Movement automatically. The output is a ScoringProfile that RepScoringConfig falls back to whenever a weight override is left null.

final profile = ScoringProfileInferrer.infer(movement);
// profile.formWeight, profile.depthWeight, etc., sum to 1.0

You usually don’t call this yourself , MovementTracker calls it on load() and exposes the result via tracker.scoringProfile (ShowcaseTracker.scoringProfile too). Studio’s rep-scoring side panel reads it to render the suggested weights when a trainer first opens the panel.

ScoringProfile shape

class ScoringProfile {
final double formWeight;
final double depthWeight;
final double tempoWeight;
final double stabilityWeight;
final double rangeOfMotionWeight;
final double holdDurationWeight;
}

All six weights are [0, 1] and sum to 1.0 (form always gets at least 30%).

Inference logic

ScoringProfileInferrer.infer(movement) branches on the movement’s RepDetectionStrategy and the authored tracking-point mix:

Hold movements (RepDetectionStrategy.hold)

For static holds (plank, wall-sit, etc.), tempo and depth don’t apply. The default profile is:

DimensionWeight
Form40%
Hold duration35%
Stability25%

Single-phase movements (not a hold)

A movement with < 2 phases gets a form-heavy profile, there’s no phase-traversal depth to score against:

DimensionWeight
Form60%
Stability30%
Hold duration10%

Dynamic movements (2+ phases, cycle / sequence / compound)

The inferrer counts the authored TrackingPoint types and weights each dimension by how relevant it is:

Tracking-point typeBoosts
angleDepth (each angle channel implies a joint angle to score against)
distance / proximity / ratio / positionRange of motion
velocityTempo (presence alone tips tempo to 100% relevance)
stabilityStability

Default tempo relevance when no velocity channel is authored: 0.5 for cycle / sequence strategies (where rep duration is implicitly meaningful), 0.2 for compound.

After collecting raw relevance scores, _normalizeWeights renormalises so the six weights sum to 1.0 with form ≥ 30%.

Fallback

When the inferrer can’t infer (no tracking points, no phases), it returns ScoringProfile.defaultProfile:

DimensionWeight
Form50%
Depth25%
Tempo25%

How RepScoringConfig consumes the profile

RepScoringConfig carries optional *Weight overrides per dimension. When a weight is null, the inferred weight from ScoringProfile is used. This means trainers can:

  • Accept the inferrer’s choices by leaving all weights null.
  • Override one dimension explicitly (e.g. “I want tempo at 40% regardless”).
  • Trim a dimension by toggling its use* flag to false, the remaining active weights renormalise automatically.

When the inferrer is wrong

The inferrer is a heuristic over authored intent, it can’t see the trainer’s actual judgment. If the inferred profile is off:

  • For greenfield authoring, edit the weights in the Studio rep-scoring panel and they get persisted into the .pose.
  • For deployed movements, push a MovementConfig override with a tuned RepScoringConfig, the inferrer is bypassed entirely once explicit weights are set.