Summary
When /api/mover-control/start transitions a claim to state=streaming, the head should be parked at home (Home anchor) before any subsequent orient updates can land. Currently /start only sets dimmer + colour and starts the engine pump; the head stays at whatever wire DMX was previously on.
Why this matters
/api/mover-control/calibrate-end captures _mover_current_aim_stage(mover) — the head's current stage-frame aim — and uses that as the reference direction the phone's current pose is anchored to. If the head was at a stale pose at calibrate-time (because /start didn't park it), the operator's phone-pose-now is locked to that stale stage direction. Subsequent orient packets are anchored against the wrong reference, and the head appears to "jump" on calibrate-release because the new lock doesn't match where the operator expected the reference to be.
Symptom in the wild: today's Android live-test on fid 17 (2026-05-03) — claim+start brought the head to pan16=53376, tilt16=6272 (a stale aim from a previous test, ≈ stage az=-74°), then calibrate-end-with-phone-steady locked the phone to that stale pose, then the very next orient update moved the head to a "different" pose because the operator was holding the phone where they thought "stage forward" was — but the lock was anchored to az=-74° instead of home. That's the visible "jumped to stage -90X after calibrate" symptom.
Fix
In /api/mover-control/start (or MoverControlEngine.start_streaming), after the claim transitions to state=streaming:
- Compute the head's home aim DMX via
AimSphere.aim_direction(home_az, home_el) for the fixture (uses the new sphere; honours rotation + Home + Secondary).
- Write that pose to the universe buffer for the fixture's pan/tilt channels.
- Wait for the engine to settle (or skip the wait — engine pump will catch up within a frame or two).
- Only then accept incoming orient packets.
Equivalently: the engine's tick loop, when it sees a claim that's state=streaming but calibrated=false, drives the head to home as the streaming reference. Calibrate-end then locks the phone to that home pose. First orient update post-release uses home as the stable reference.
Acceptance
- fid 17 cold-claim +
/start from Android (no calibrate yet): head physically moves to home position (panDmx16=44364, tiltDmx16=0). Wire confirms the writes.
- Hold phone steady through calibrate-hold + release. After calibrate-end,
aim_stage reported by the orchestrator is approximately (0, 1, 0) (stage forward) — i.e., maps to the head's current stage aim, which is now home.
- Subsequent steady-phone orient packets keep
aim_stage ≈ (0, 1, 0) — head stays at home.
- Tilt phone +20° forward (gesture) → head physically tilts toward floor by ~20° (assuming rotation
=[0,0,0] and operator's expected stage convention). No "jump."
- Same test on fid 14 (rotation
[-75, 0, 0]): claim+start parks head at fid 14's home pose (32269, 28298), which is at stage (az=0, el=+75). Calibrate locks phone to that pose. Steady phone keeps head there.
Related
Out of scope
Summary
When
/api/mover-control/starttransitions a claim tostate=streaming, the head should be parked at home (Home anchor) before any subsequent orient updates can land. Currently/startonly sets dimmer + colour and starts the engine pump; the head stays at whatever wire DMX was previously on.Why this matters
/api/mover-control/calibrate-endcaptures_mover_current_aim_stage(mover)— the head's current stage-frame aim — and uses that as the reference direction the phone's current pose is anchored to. If the head was at a stale pose at calibrate-time (because/startdidn't park it), the operator's phone-pose-now is locked to that stale stage direction. Subsequent orient packets are anchored against the wrong reference, and the head appears to "jump" on calibrate-release because the new lock doesn't match where the operator expected the reference to be.Symptom in the wild: today's Android live-test on fid 17 (2026-05-03) — claim+start brought the head to
pan16=53376, tilt16=6272(a stale aim from a previous test, ≈ stage az=-74°), then calibrate-end-with-phone-steady locked the phone to that stale pose, then the very next orient update moved the head to a "different" pose because the operator was holding the phone where they thought "stage forward" was — but the lock was anchored to az=-74° instead of home. That's the visible "jumped to stage -90X after calibrate" symptom.Fix
In
/api/mover-control/start(orMoverControlEngine.start_streaming), after the claim transitions tostate=streaming:AimSphere.aim_direction(home_az, home_el)for the fixture (uses the new sphere; honours rotation + Home + Secondary).Equivalently: the engine's tick loop, when it sees a claim that's
state=streamingbutcalibrated=false, drives the head to home as the streaming reference. Calibrate-end then locks the phone to that home pose. First orient update post-release uses home as the stable reference.Acceptance
/startfrom Android (no calibrate yet): head physically moves to home position(panDmx16=44364, tiltDmx16=0). Wire confirms the writes.aim_stagereported by the orchestrator is approximately(0, 1, 0)(stage forward) — i.e., maps to the head's current stage aim, which is now home.aim_stage ≈ (0, 1, 0)— head stays at home.=[0,0,0]and operator's expected stage convention). No "jump."[-75, 0, 0]): claim+start parks head at fid 14's home pose(32269, 28298), which is at stage(az=0, el=+75). Calibrate locks phone to that pose. Steady phone keeps head there.Related
aim_stagemagnitude.## Angular-aim convention (#783)— the stage convention the parked aim must respect.Out of scope
REMOTE_FORWARD_LOCAL = (1, 0, 0)(Issue A from SPA/Android: orientation indicator drifts independently of physical mover after calibrate #757 diagnostic) — separate work._mover_current_aim_stagelegacy IK paths (Issue B from SPA/Android: orientation indicator drifts independently of physical mover after calibrate #757 diagnostic) — separate work; should land alongside this for cleanest test, but isn't strictly required for this fix's acceptance.