366d916e9d
Convert all four consumers to accept pre-routed [L|None, R|None]: - finger_strike: remove order_hands_by_cx + _slot_hands; step() iterates enumerate(slotted), None slot resets state / synthesizes release edges in-place. - hand_features: step(hands, chirality=None) delegates to route_hands; speed buffer indexed by slot, not cx order. - action_head_pub: _pick_hands returns chirality; _emit_fingers/ _emit_pinch call route_hands with HAND_SWAP_LR/HAND_NEAR_MIN; _emit_hands plumbs chirality to HandFeatureExtractor. - renderer: panels block replaced with route_hands call; persistence gate + hand_plausible mask applied before routing so a ghost hand cannot steal a slot; HAND_SWAP_LR read at init. Test updates: [hand, None] slot lists in test_finger_strike; _hand() fixture in test_action_head_finger_emit gains realistic size so near_min=0.10 gate passes.
data_only_viz
Python + Metal visualiser for the AV-Live data-only patch.
Hand features OSC
/pose/hands [pid=0, lx, ly, lopen, lspeed, rx, ry, ropen, rspeed, dist]
- Emitted once per pipeline tick by
pose_bridge.send_handsto sclang:57121. - L = leftmost hand on screen, R = rightmost; absent hand -> its 4 floats are 0.
- Ranges: positions/openness/dist in [0,1]; speed >= 0 (normalized units/frame).
- Consumed by SuperCollider
~handFeat(data_only) and the Metal renderer uniforms. - Multi-person is not yet carried (pid is always 0).