`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.0You 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:
| Dimension | Weight |
|---|---|
| Form | 40% |
| Hold duration | 35% |
| Stability | 25% |
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:
| Dimension | Weight |
|---|---|
| Form | 60% |
| Stability | 30% |
| Hold duration | 10% |
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 type | Boosts |
|---|---|
angle | Depth (each angle channel implies a joint angle to score against) |
distance / proximity / ratio / position | Range of motion |
velocity | Tempo (presence alone tips tempo to 100% relevance) |
stability | Stability |
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:
| Dimension | Weight |
|---|---|
| Form | 50% |
| Depth | 25% |
| Tempo | 25% |
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 tofalse, 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
MovementConfigoverride with a tunedRepScoringConfig, the inferrer is bypassed entirely once explicit weights are set.
Read next
RepScoringConfig, the consumer of the profile.MovementConfig, published overrides that bypass the inferrer.TrackingPoint, the type mix the inferrer reads.