Skip to content

Form feedback

How form cues you author in the Studio flow through to the consumer app’s UX.

What you author

Every condition row in the Conditions panel has an optional cue editor:

  • Cue text: short string the host UI surfaces.
  • Trigger: onViolation (default), onCompliance, or always.
  • Severity: info / warning / error.

Leave the cue text blank and the condition still gates the phase transition + contributes to the form score, just without an explicit message.

What happens at runtime

When the form service evaluates a rule and its trigger fires, the tracker emits a FormFeedbackEvent on onFeedback:

class FormFeedbackEvent {
final String ruleId;
final String trackingPoint;
final double currentValue;
final String message; // your authored cue text
final FormRuleSeverity severity;
final FeedbackTrigger trigger;
final String? audioCue;
final String? hapticPattern;
}

The host UI subscribes to that stream and decides what to do , toast, audio cue, haptic burst, score badge, post-rep summary.

In the Studio test player

The Studio surfaces feedback as a toast deck at the top of the camera view (just below the top bar). Each event renders as a short pill with the cue text, an icon for the severity, and an auto-dismiss timer (~2.8s). At most 3 toasts stack at once; newest pushes oldest off.

Colour signals severity:

  • error → red
  • warning → amber
  • info → blue (primary tint)

Filtering during authoring

While testing in the Studio, you can filter the toast deck:

  1. Open the Overlays panel.
  2. Find the Form feedback section.
  3. Show feedback toasts master switch + per-severity filter chips.

Hide info if you only want to see real issues during testing. Turn the master switch off entirely when you just want to verify rep counts.

This is a Studio-side display filter only, it doesn’t change what the .pose file emits at runtime in the consumer app.

Throttling

The form service throttles rapid-fire events. A rule won’t re-fire within 800ms of its last event for the same trigger. So a wobbly knee during the down phase doesn’t generate 20 cues per second.

Authoring tips

  • Use onViolation for corrective cues: “Don’t let your knees collapse inward.”
  • Use onCompliance for positive reinforcement: “Great depth!”. Sparingly, too much praise becomes noise.
  • Reserve error severity for catastrophic form: knee buckling under load, back rounding hard. warning is the right default for most cues.
  • Use info for measurement readouts: “Stance: 0.45”. Trigger always + severity info gives the host a stream of values to render as a chart or live label without alarming the user.
  • Keep cue text under 40 chars: the toast renders on one line; longer text truncates.

Audio + haptic cues

The FormFeedback payload optionally carries audioCue and hapticPattern strings. These are asset reference identifiers that the host resolves, the Studio doesn’t bundle audio or haptic assets.

A typical wiring:

tracker.onFeedback.listen((event) {
// Toast
showToast(event.message, severity: event.severity);
// Audio cue
if (event.audioCue != null) {
audioPlayer.play(event.audioCue);
}
// Haptic
if (event.hapticPattern != null) {
haptics.play(event.hapticPattern);
}
});

The Studio doesn’t currently expose audio/haptic fields in the cue editor, they ship in the .pose file when authored programmatically (via the converter script’s preset mapping or by hand). A future Studio release will add them to the cue editor.

Per-bucket cues

When you author bucket-specific bands (see camera buckets), the same condition can fire different cues per bucket implicitly because the bands are different. The cue TEXT stays the same across buckets, the cue fires whenever the bucket-appropriate band is violated.

If you need different cue text per bucket, author multiple conditions with the same channel + different appliesToPhase scopes that map to different bucket-overrides. Rare in practice.

Suppressing form at the host level

Some UXs render only the rep count and intentionally hide form feedback, competitive flows, headless scoring backends, minimalist in-game overlays. The tracker still emits every form event for the loaded movement; your host code just ignores them.

This is a host-side UX policy, not a Studio-side concern. Studio always emits the form rules, it’s up to the host whether to surface them.