Skip to content

`MovementConfig`

MovementConfig is the full tracker-side runtime view of an movement. Movement (the on-disk .pose document) decodes into an MovementConfig via Movement.toExerciseConfig(); consumers can also supply their own MovementConfig as a runtime override when loading a movement, which lets a published config (e.g. a CMS Firestore hotfix) take precedence over the inlined values in the .pose file without re-uploading anything.

tracker.load(
movement,
configOverride: publishedConfig, // overrides movement's inlined values
);

When to use the override

The override path exists for one reason: changing tracker behaviour in production without re-deploying the .pose file. Typical use cases:

  • A trainer notices a movement is rejecting borderline reps and wants to soften the knee-angle band, push a new MovementConfig to Firestore, every consumer picks it up on next load.
  • A new form rule needs to ship to existing users, add it to the published config, no app update required.
  • A bucket-specific AngleBand was authored wrong, fix the band in the published config, leave the original .pose immutable.

For greenfield authoring (creating a new movement in Studio), you don’t need the override. Studio writes the inlined values straight into the .pose file via Movement.fromJson, and tracker loads pick those up automatically.

Shape

class MovementConfig {
final String id;
final String version;
final String name;
final String? description;
final String? category;
final String? difficulty; // 'beginner' / 'intermediate' / 'advanced'
final CameraAngle cameraAngle; // front / side / any
final MovementMetadata metadata;
final LandmarkRequirements landmarks;
final List<TrackingPoint> trackingPoints;
final List<PhaseConfig> phases;
final RepDetectionConfig repDetection;
final List<FormRule> formRules;
final AccessibilityConfig accessibility;
final ScoringConfig scoring;
final RepScoringConfig repScoring;
}

Fields

FieldMeaning
id / version / nameIdentity. version is the schema version, not the movement version.
description, category, difficultyDisplay metadata; the runtime doesn’t act on them.
cameraAngleHigh-level enum (front / side / any) used by the FrameValidator’s view-angle gate. Authored at the movement level, different from the 16-bucket CameraAngleDetector output.
metadataMovementMetadata, author, dates, tags.
landmarksLandmarkRequirements, required / optional landmarks + fallback alternatives. Frame validator uses this.
trackingPointsThe full TrackingPoint list. Per-frame channel evaluations.
phasesThe list of PhaseConfigs the state machine walks.
repDetectionRepDetectionConfig, which strategy to use (cycle / sequence / compound / hold), which phase counts as “rep complete”, optional duration bounds.
formRulesList of FormRules the form service evaluates each frame.
accessibilityAccessibilityConfig, high-contrast, larger text, voice-over preferences. The SDK doesn’t render UI; consumers honour this.
scoringScoringConfig, per-dimension scoring weights for the trainer-facing badge. Different from repScoring (which gates rep validity).
repScoringRepScoringConfig. Defaults to RepScoringConfig.disabled.

Override semantics

When you pass configOverride to MovementTracker.load, the entire config object replaces the one decoded from the movement, there’s no field-level merge. If you only want to override one field, copy the movement’s decoded config first:

final base = movement.toExerciseConfig();
final overridden = base.copyWith(
repScoring: const RepScoringConfig(enabled: true, minQualityScore: 70),
);
tracker.load(movement, configOverride: overridden);

copyWith is the safe path, it preserves every field you don’t explicitly change.

JSON

MovementConfig round-trips via toJson / fromJson. The shape is identical to the config block inside a .pose file (the .pose file is essentially a Movement with an embedded MovementConfig plus the pose-frame data and camera buckets). See the .pose file spec.

Patterns

Publish a config to Firestore

CMS publishes to /movements/{id}/configs/{version} as JSON. Mobile apps fetch the latest config on session start and feed it to the tracker:

final configJson = await firestore
.collection('movements').doc(movementId)
.collection('configs').doc('latest').get();
final config = MovementConfig.fromJson(configJson.data()!);
tracker.load(movement, configOverride: config);

Live-patch a single rule

For trainer-facing tools that need to tweak a rule without restarting the session, prefer MovementTracker.updateRepScoring , it handles the RepScoringConfig slice without forcing a full load. For other slices (form rules, phases), unload and reload with a new override.