Index
2026-09-11 — Progress / Showcase

스크립트 one_leg 정책 v2 — 영상으로 보는 방법과 결과

FurnitureBench (FurnitureSim, low randomness) | LLM 없이 손으로 쓴 폐루프 정책 | 기술 상세는 부모 리포트

TL;DR

0.983
v2g 최종 (59/60)
0.47
원본 stock (14/30)
550
성공 중앙값 step
$0
API 비용
8회
30~60 ep 반복

1 원본 스크립트와 무엇이 다른가

원본(leg.py의 Leg.fsm_step)은 열린 루프 안무다. "팔을 여기로"를 단계별로 실행하고, 각 단계는 목표에 도달하거나 25 step이 지나면 맞든 틀리든 다음으로 넘어간다. 놓는 순간도 팔이 어디 있든 insert_wait 10 step 뒤 무조건 놓고, 놓기가 빗나가도 나사 단계를 600 step까지 반복한다. 파지 자세가 월드 고정이라 다리 yaw가 틀어지면 빈손을 쥔다.

v2(leg_v2.py의 LegV2(Leg))는 운반 안무는 원본을 재사용하고, 파지와 호버 이후를 다리 측정 기반 폐루프로 교체했다. 매 step 계산하는 양은 다리 나사 끝점이 구멍 입구에서 벗어난 수평 거리, 다리 기울기, 입구 안으로 들어간 깊이, 그리고 조립 자세 대비 축 회전이다.

파지
다리 축 yaw를 보고 잡는 방향을 맞춤. 파지 폭 < 2 cm면 빈손으로 판정해 재시도(≤2회), 실패 시 top-down 파지로 전환.
정렬
입구 +10 mm에서 끝점의 수평 오차를 서보(env가 delta를 2배 하므로 오차의 절반을 명령). OSC가 목표 2 mm 앞에서 멈추는 정상상태 오차가 있어 적분항 추가. 기울기도 3°/step로 보정. 1 mm 이내 5 step 연속 & 기울기 < 4°일 때만 하강.
하강·놓기
6 mm/step로 빠르게(림 접촉 시간 최소화), 입구 안 −2 mm에 3 step 연속 있을 때만 놓기. 림에서 튕기면(10 step 동안 입구 ±4 mm) 물러나 재정렬.
판정
놓은 8 step 뒤 끝점 기준으로 안착 여부 판정(수평 < 7 mm, 깊이 > 6 mm, 기울기 < 25°). 실패면 재파지·재배치.
나사
다리 위 7.5 cm까지 현재 자세로 수직 상승 → top-down으로 회전 → 수직 하강 → 닫기 → 나사 방향 −95° 회전 + 5 mm 누름 → 놓기. 매 사이클 진행량·기울기·회전 정체를 검사.
가드
관절각으로 손목 감김을 검출해 감긴 채 큰 자세 변경을 명령하지 않음, 하강이 10 step 동안 안 내려가면 들어 재정렬, 들고 있는 채로 복구할 땐 떨어뜨리지 않고 내려놓기, 다리가 바닥으로 떨어지면 즉시 포기.

2 어떻게 0.98까지 갔나 — 반복과 두 가지 발견

30 episode를 돌리고, 실패한 episode의 trace를 step 단위로 나누어(tmp/split_eps.py) 원인을 하나 특정하고, 그것만 고치고 다시 30 episode. 이 루프를 v2 → v2g까지 8번 돌렸다. 물리가 run마다 비결정적이어서(같은 seed도 ep0 외에는 갈림) 버전 비교는 60 episode 기준이고 ±2/60은 잡음이다.

버전성공고친 것
stock (원본)14/30 @600, 17/30 @1500—
v222/30폐루프 정렬·하강·판정, yaw 인식 파지, 적분항
v2b26/30상판 밀기 전처리 생략(다리를 쓸어 떨어뜨림 3/30), 바닥 낙하 즉시 포기
v2c56/60stock pre_grasp(측면 자세에서 이동+회전 동시 → 다리 쓸어냄) → 수직 상승·top-down·하강 3단계, 비틀기 정체 감지
v2d / v2e28/30 / 27/30관절각 기반 감김 검출과 되감기, 하강 정체 가드, 놓기 3 step 연속 조건, 기울어진 안착엔 누르기 전용 사이클, nudge 제거
v2f57/60재파지 heading 0 고정(감긴 손목에 큰 회전 명령 금지), 세우기↔정렬 루프 상한, 들어올리기 전 기운 방향으로 1.2 cm 비켜서기
v2g 최종59/60 (30/30 + 29/30)나사 방향으로만 최대 회전(역회전 금지), 바닥 도달 후 판정까지 추가 회전, 복구 시 다리 위 7.5 cm까지 상승 후 회전, 들고 있는 채 복구는 내려놓기

발견 ① 손목 감김 (wrist wind-up)

상태 전이마다 관절각을 기록해 보니, 원본의 다리 세우기 90° 회전(match_leg_ori)이 손목 특이점(q6 = π, 5·7축 정렬)을 지나면서 (q5, q7)이 episode마다 무작위로 갈렸다: 정상(q5≈0, q7≈1) 13 / 부분 감김 26 / 완전 감김(q7이 한계 2.9에 고정) 21 (60 ep). 감긴 손목으로 다리 근처에서 큰 자세 변경을 명령하면 OSC가 베이스·팔꿈치를 재배치하며 다리를 쳐낸다 — v2b~v2f의 거의 모든 실패가 이 배경이었다.

시도했지만 안 된 것: 세우기를 1.5°/step로 느리게(→ 매번 같은 감김 상태로 수렴), 공중에서 되감기(→ 다시 특이점을 지나야 함), 의도적으로 완전히 감기, 손을 180° 돌려 잡기(23/30). 된 것: 회전은 다리 위 7.5 cm에서만, 회전이 필요 없는 heading 0 목표, 짧은 예산, 그리고 감긴 상태에서 top-down 명령을 주면 대부분 스스로 풀린다는 관찰의 활용.

발견 ② 구멍은 실제 나사산

다리 축 회전을 trace에 추가하니 조립 판정(is_similar_rot, 축별 cos ≥ 0.94)이 축 회전 ±20°도 요구하고, 회전과 하강이 결합돼 있었다: −95° 회전 = 2.3 mm 하강, +95° = 2.3 mm 상승, 회전 없는 누르기 = 0 mm. "누르기가 다리를 넣는다"는 이전 해석은 틀렸다. 그래서 매 사이클 나사 방향으로만 최대 회전하고(관절 여유가 없으면 짧게, 절대 역회전 않음), 바닥에 닿았는데 판정이 안 나면 축 회전이 맞을 때까지 몇 사이클 더 돈다.

3 영상

전면 카메라(960×540) + 우상단 손목 카메라 인셋, 하단 오버레이는 정책·episode·step·FSM 상태·다리 기울기. 재생 속도 1.5×(정책은 10 Hz). 녹화: scripts/record_scripted_video.py, job 119213.

이번 녹화(job 119213, 각 seed 3 ep)의 성공률은 v2 8/9, 원본 3/6. 아래는 그중 대표 사례 5개.

v2 성공 seed 900 ep2, 483 step. 깨끗한 안착(기울기 8°) 뒤 나사 사이클 4회. 정렬 → 하강 → 놓기 → 다리 위로 수직 상승 → top-down 재파지 → −95° 회전을 반복하는 기본 흐름.
v2 성공 (기울어진 안착 처리) seed 800 ep1, 565 step. 놓은 뒤 16.8°로 기울어 안착 → 회전 없이 닫고 누르는 사이클 3회로 다리를 세운 뒤(19° → 14°) 정상 나사 사이클. 22.7° 어트랙터 근처 사례가 살아나는 방식.
v2 실패 (잔여 유형) seed 300 ep1, 1500 step 타임아웃. 놓은 뒤 30.2°로 기울어 MISSED → 재파지·재배치 시도 → 다리가 60°로 넘어져 복구 실패. §4의 어트랙터 안착이 25°를 넘긴 드문 경우.
원본 stock 성공 seed 300 ep1, 503 step. 열린 루프 안무가 맞아떨어진 경우. v2와 비교하면 놓기 뒤 검사 없이 곧장 나사 단계로 간다.
원본 stock 실패 seed 300 ep0, 1500 step 타임아웃. 원본의 전형적 실패: 단계 시간초과가 누적돼 놓기가 빗나가도 확인 없이 나사 단계를 반복한다.

4 남은 실패와 한계

5 같은 레시피가 다른 가구에도 바로 되나?

결론: 구조가 같은 부품은 "파라미터만 바꾸면" 이식되지만, 첫 시도에 0.98은 아니다

일반화되는 것은 측정 함수와 검사 로직이다. 부품 끝점·중심·상단 오프셋, 구멍 축, 안착 깊이 같은 기하는 부품별 상수로 빠져 있어(LEG_BOTTOM_LOCAL, LEG_TOP_LOCAL, 운반 높이 5개) 서브클래스가 덮어쓰면 된다. 하지만 성공률을 결정한 두 발견은 팔의 기하(특이점 위치, 관절 한계)와 시뮬 나사산에 관한 것이라 부품이 바뀌면 다시 측정해야 한다.

부품유형이식 난이도근거
lamp 전구, round_table 다리측면 파지 → 세우기 → 구멍 삽입 → −95° 나사 (one_leg와 동형)낮음~중간원본 FSM이 Leg 서브클래스이고 상태 이름도 같다. 기하(half_width 0.0175/0.016, grasp_margin_z 0.053/0.03, 나사 대상이 상판이 아니라 base)만 바꾸면 되나 세우기 자세의 손목 문제는 그대로 따라온다.
desk 다리동형이지만 다리가 3.8 cm 더 길고 4개중간이미 이식 시작: 첫 다리를 3°로 안착시켰지만 top-down 하강 6.8 cm 동안 열린 손가락이 다리 상단에 닿아 12~15° 기울고, 긴 다리를 든 자세에서 손목 감김이 더 심하다. 4개 다리 × 사이클이라 다리당 ≥ 98%가 필요.
round_table base, cabinet top, lamp hood올려놓기(+재파지 나사 / 위치만 판정)중간삽입 대신 "상대 자세 맞춰 놓기" 판정을 새로 써야 함. 나사 사이클 로직은 재사용.
cabinet door슬라이드 삽입, 나사 없음중간진행량 기반 폐루프(끼임 감지·후퇴)가 새로 필요.
stool, chair, drawer원본 스크립트 없음높음상태기계·pre_assemble·타임아웃·화이트리스트까지 신규. chair 다리는 Leg 상속이라 부분 재사용.

현실적인 기대치: 동형 부품은 1~2일(2~4회 반복)에 0.9 근처, 놓기·슬라이드형은 판정 로직 설계부터. 자세한 코드 구조 조사는 claude/260911/research-scripted_policy_survey.md.

6 재현

conda activate furniture-bench # 성공률 (own QOS, 1 GPU, 30 ep ≈ 40 min) sbm "bash scripts/eval_scripted_one_leg.sh v2 30 100 1500" --qos=own --gres=gpu:1 -c 8 --mem 200GB --time 2:30:00 -J scr_v2 sbm "bash scripts/eval_scripted_one_leg.sh stock 30 100 600" --qos=own --gres=gpu:1 -c 8 --mem 200GB --time 2:30:00 -J scr_stock # 디버그(나사 사이클 step별 EE·관절 로그): FURNITURE_SCRIPTED_V2_DEBUG=1 # 영상: sbm "bash scripts/record_scripted_videos.sh" --qos=own --gres=gpu:1 -c 8 --mem 200GB --time 1:30:00 -J scr_videos # 데모 수집에 적용: FURNITURE_SCRIPTED_V2=1 + FURNITURE_SCRIPTED_TIMEOUT=1500 로 collect_scripted.sh

정책 파일 furniture-bench/furniture_bench/furniture/parts/leg_v2.py(2026-09-11 14:08 동결), 작업 로그 claude/260911/progress-scripted_one_leg_v2.md.