Robot descriptor (§A2) — control-plane blueprint

소스가 Describe 때 한 번 선언하는 설계도의 §A2 제안형 — 실제 자료구조(JSON) 그대로입니다. channels는 객체의 배열이고, 배열 순서가 곧 values[] 인덱스(우측 // [i]). 필드명은 흰색, 값 색이 출처를 뜻합니다: 청록=소스 메타에서 옴(예: key=실제 컬럼명) / 주황=우리 추론(예: unit). null인 필드(rot_type/group/range)는 객체에서 생략됩니다. (색은 표시용일 뿐 데이터에 *_src 같은 필드는 없습니다.)

핵심 원칙 — 이름은 무손실(verbatim). 채널 이름은 클라이언트가 보낸 컬럼명 그대로 저장됩니다(예: robot0.action.state.joint_positions[3]). SDK가 중간을 깎지 않습니다(.state. 제거 금지) — 원본 복구가 보장돼야 하니까요. observation/action 구분은 이름에 이미 있으니 거기엔 role을 안 씁니다. 대신 role로봇 경계를 실제로 넘었는지로 2가지로 나눕니다 — robot(로봇에서 나온 측정 상태 또는 로봇에 실제 전달된 명령) / aux(그 외 곁다리: 실행 안 된 후보 액션·action_source·inference latency·prev_·success 등). 판별: "이 값이 로봇으로 실제 들어갔거나 로봇에서 나왔나?" 이름만으론 모르니 가치 있는 부가 필드입니다. 그 외 이름으로 복구 불가라 유용한 건 unit·rot_type. group은 선택적 묶음 태그. role·rot_type은 닫힌 집합(proto enum)이라 정해진 값 외엔 거부됩니다(role: robot/aux, rot_type: none/quaternion/euler/rotation_6d). unit은 권장 집합 + 그 외는 경고(단위는 무한하므로). 중복·불필요 채널(예: action.state.*observation.state.*와 동일, 한 명령의 여러 표현)은 권고로 줄이되 강제로 제거하지 않습니다 — 클라이언트가 로깅 목적으로 보낼 수 있으니까요.
rot_type은 3D 방향(orientation) 인코딩 표시입니다 — 사원수/오일러/6D. 1-DOF 관절각 같은 스칼라 각도엔 none(unit=rad만으로 충분), EE roll/pitch/yaw처럼 3채널이 한 방향이면 euler.

  
필드별 필수도
필수name클라이언트 컬럼명 그대로(verbatim). observation/action 구분도 이름에 들어있음
선택role로봇 경계 기준 2분류: robot(로봇에 실제 입출력된 신호) / aux(그 외 곁다리: 미실행 후보 액션·action_source·latency·진단). 닫힌 enum
필수unitrad · m · normalized … 한 벡터에 단위가 섞임
조건부 필수rot_type회전(quaternion/euler/rotation_6d) 채널일 때만. 아니면 none
선택range정규화·검증·이상치 (관측 min/max)
선택groupleft_leg 등 의미 묶음. 휴머노이드 슬라이싱
선택dtypewire는 double. int/bool/enum일 때만

실제 에피소드(프랑카·베가=recorder, G1=LeRobot/GR00T)에서 추출. unit/rot_type은 best-effort 추론이며 실제 계약은 클라이언트가 선언합니다. docs/spec_discrepancies.md §A2.