A robot_type of so100/so101 is treated as wrong when the joint dim isn't a
multiple of 6, or (when names are present) the first 6 joints don't match the
canonical SO set. Such datasets are migrated structurally to v3.0 with joints
left untouched and robot_type relabeled 'unknown', instead of being skipped or
degrees-converted on a false assumption.
Some datasets store task-space end-effector pose (names like ee_x/ee_roll or
x/y/z) instead of joint angles; the degrees mapping is meaningless there. Detect
via feature names and skip them entirely (no conversion, no upload) rather than
migrating a mislabeled arm.
The manifest robot_type distribution has 'bi_so100_follower', which the trailing
underscore in 'bi_so_' missed. Drop it to 'bi_so' to catch all bimanual SO
variants (bi_so_follower, bi_so100_follower, ...) with no false positives.
Broaden SO_PREFIXES to (so100, so101, so_, bi_so_) so bimanual two-arm datasets
(robot_type 'bi_so_follower', 12-dim) are recognized as SO and get the degrees
conversion, instead of being skipped as non_so. so_ boundary avoids matching
stray names like 'sofa'.
Split classify() into reusable pieces: is_so_robot_type() for the robot_type
name test, and encoding_from_bounds() as the single source of truth for the
degrees_old/degrees_new/normalized/radians decision from per-joint min/max
(layout-agnostic, so v2.1 episodes_stats and v3.0 stats.json both feed it).