Skip to content

`PhaseStateMachine`

PhaseStateMachine is the internal state machine MovementTracker uses to track which phase of an movement the user is currently in. It runs in one of two modes depending on the loaded Movement:

  • Rule-based mode (phaseConfigs populated, positions empty) , the default and only mode produced by PoseFlow Studio. Phase transitions fire when the authored condition gates on each phase evaluate to true against the current per-frame tracking values.
  • Vector-matching mode (Movement.positions.isNotEmpty), legacy movements built from a recorded marker pass. A small PCA index classifies the current pose against named reference positions; phase transitions fire when the user traverses the authored sequence of positions.

You rarely instantiate PhaseStateMachine directly, MovementTracker owns it. This page documents the surfaces that leak out of the tracker so consumers can render phase-aware UIs.

What you see through the tracker

final result = tracker.processFrame(pose);
// result.phaseId, current phase id, null until the first transition

Plus the rep stream:

tracker.onRepCompleted.listen((event) {
// fires when the phase machine completes a rep per the loaded
// movement's RepDetectionConfig
});

Mode selection

The mode is picked once at tracker.load(movement) time and held for the session:

final movement = Movement.fromJsonString(json);
tracker.load(movement);
// If movement.positions.isNotEmpty → vector-matching mode
// Otherwise → rule-based mode

Switching modes mid-session requires unload + reload with a different movement.

Rule-based mode

Each PhaseConfig carries a list of PhaseConditions (membership gates: “current tracking-point values must fall inside these bands”). On every frame, the state machine evaluates the conditions of the current phase’s authored transitions; the first satisfied transition wins, and the machine moves to that transition’s toPhase.

PhaseConfig.duration (optional minimum-time-in-phase) holds the machine in place, even if the next transition’s conditions are satisfied, the machine waits the duration before firing.

PhaseCondition and PhaseConfig are the per-phase authoring surfaces.

Vector-matching mode

Reference positions are loaded into a small in-memory PCA index. On every frame, the current pose’s landmark vector is projected and the nearest reference is returned. The state machine fires a transition whenever the nearest-position label changes.

The PCA index lives inside the runtime; it’s built fresh each time a movement is loaded. Lookups are ~300K matches/sec on a modern phone , this mode is fast.

Rep boundaries

The state machine fires RepCompletedEvent when the loaded movement’s RepDetectionConfig says a rep is done:

StrategyRep boundary
cycleThe user re-enters primaryPhase.
sequenceThe user has traversed every phase in phases in order.
compoundThe user has traversed any sequence in sequences.
holddurationIncrement ms have elapsed while inside primaryPhase.

The fired event carries the per-dimension RepQuality breakdown (form / ROM / tempo / stability), see TrackingResult.

Why this is internal

PhaseStateMachine does not have a stable public constructor, the tracker creates it from a loaded Movement, owns its lifecycle, and funnels events back to consumers. The internal API surface evolves without back-compat guarantees; trying to instantiate one by hand will break across minor versions.

If you need a deterministic test harness for phase logic, drive a real MovementTracker with synthetic Pose fixtures and assert against tracker.onRepCompleted and result.phaseId. See Rep counting parity for the test patterns the SDK itself uses.