Index
2026-08-21 schema 1.0 /home/nas_main/taewoongkang/sample/DATASET_sample_v1

SceneMem 데이터셋 샘플 v1

에피소드 5편(1.2 GB) 위에 세 가족 find · restore · resume의 채점 행 36개가 얹혀 있다. 실제 파일을 열어 층 구조 · 필드 · 판정 규칙을 정리하고, 각 가족마다 실제 행 하나를 프레임 그림과 함께 끝까지 따라간다. 마지막에 아직 없는 네 번째 가족 routine의 설계 제안을 같은 편의 실측값으로 붙였다.

5컨텍스트(에피소드)
1.2 GB총 용량
18find 행
11restore 행
7resume 행
85 KBeval/ 전체

layout두 개의 층

데이터셋은 무거운 층(contexts/)가벼운 층(eval/)으로 정확히 갈린다. 용량비가 14,000:1이고, 이게 설계의 핵심이다 — 에피소드는 한 번 만들고 안 고치며, 질의·채점 규칙만 다시 빌드한다.

DATASET_sample_v1/                        1.2 GB
├─ contexts/                            로봇이 실제로 굴러간 에피소드 원본. 질의 규칙 없음
│  ├─ type1/  val_144__2      88 MB     seed < 1000  · 사용자 발화 없음
│  │          val_406__0     289 MB
│  │          val_733__0     346 MB
│  └─ type2/  val_101__1100  299 MB     seed ≥ 1000 · 사용자가 task를 주고 중간에 끊음
│             val_733__1100  206 MB
└─ eval/                                 채점 단위. tools/build_eval.py 생성물 — 손으로 고치면 지워짐
   ├─ index.json                        가족 × 컨텍스트 매트릭스
   ├─ find/     queries.jsonl  stats.json  README.md
   ├─ restore/  queries.jsonl  stats.json  README.md
   ├─ resume/   queries.jsonl  stats.json  (README 없음)
   └─ routine/  — 아직 없다. 설계 제안은 §5b

kind는 둘뿐이고, seed 대역이 가른다

kind영상 안에서쓰는 가족seed이 kind에만 있는 것
type1로봇이 집을 돌며 물건을 옮긴다. 사용자 발화 없음 find restore< 1000 states/final.npz
type2사용자가 반복 유닛 task를 순차로 주고 중간에 세운다 resume≥ 1000 states/cut_*.npz · index.json

README와 실물이 어긋난다. contexts/README.md는 type2 전용 파일로 tasks.json·utterances.json을 적어놨지만 이 배포본에는 둘 다 없다. 같은 내용이 subtasks.jsontasks/cuts/stage 키와 질의 행의 commands[]로 흡수돼 있다. 로더를 파일명 기준으로 짜면 깨진다.

편(에피소드) 한 편의 실제 구성

type1 / val_144__2

meta.json          494 B  house·seed·fps·카메라·지문
subtasks.json      6.7 KB 이벤트 4개 + 세그먼트
traj.h5           53.8 MB 12,778 스텝 전 상태
states/final.npz   2.2 KB 가벼운 스키마
videos/
  exo_camera_1.mp4  13.9 MB  640×480 @25fps
  exo_camera_2.mp4  14.1 MB
  wrist_camera.mp4  10.4 MB

type2 / val_101__1100

meta.json          325 B
subtasks.json     13.0 KB 유닛 10개 + tasks + cuts + stage
traj.h5           37.8 MB 9,265 스텝
states/            11×19 KB cut_00..09 + final
  index.json       태그↔프레임↔시각
videos/           274 MB 동일 3 카메라

같은 states/인데 스키마가 완전히 다르다. type1 final.npznames(25) · pose(25,7) · robot_base_pose(7) · frame 네 배열뿐인 추적 객체 스냅샷(start_state_schema: "v0_track_tail")이고, type2 cut_*.npzqpos(82) · qvel(77) · ctrl(11) · model_geom_* · model_body_*까지 든 MuJoCo 전체 상태 덤프(35개 키)다. type2만 물리 상태까지 정확히 복원된다.


schema세 가족이 공유하는 봉투 11개

가족이 달라도 이 11개는 같은 이름·같은 뜻이다. 36행 전부에 대해 확인했다 — 결손 0. 가족 고유 필드는 중첩 payload 없이 같은 층에 나란히 놓인다.

필드이 샘플의 실제 값
schema_version행 스키마"1.0" (36/36)
familyeval/<family>/와 같다find · restore · resume
kindEVAL_TASKS 레지스트리 키find_target · find_distractor · restore · resume
query_id<family>:<ep>:<local> — 전역 유일find:val_144__2:q00
context데이터셋 루트 상대. 절대경로·심링크 금지contexts/type1/val_144__2
context_frames정책이 보는 닫힌 구간. 자르는 건 읽는 쪽[0, 12777] / [0, 651]
start_statecontext 상대states/final.npz · states/cut_01.npz
query정책에 주는 문장아래 각 가족 표 참조
budget_steps스텝 예산400 ~ 12000
oracle오라클 실행 후 채운다. null = 아직 안 돌림 find null restore null resume 7/7
tags분석 축. 채점에 안 쓴다relocate · shift · twin_merge · r1

캐시 키는 (context, context_frames[1])다. 이 샘플에서 find·restore는 전 행이 편 전체를 본다 — val_144__2 [0,12777] · val_406__0 [0,11436] · val_733__0 [0,13408]. 즉 두 가족의 캐시 키가 자동으로 같아지므로, 러너는 context로 묶어 편당 한 번만 컨텍스트를 로드하면 된다 (find 편당 6.0행, restore 편당 3.67행).


find
판정 — 정답 전부를 한 롤아웃 안에서 한 번씩 든다 (사건 목표, 순서 무관)

에피소드가 끝난 상태(states/final.npz)에서 시작해, 영상 속에서 옮겨졌던 물건을 다시 찾아 집는다. 18행 · 컨텍스트 3 · 정답 21개, 자격 탈락 1건(ambiguous, 탈락률 5.3%).

예시 하나를 끝까지 — find:val_144__2:q00

find_target steps=multi n_candidates=2 twin_nearby=false budget=1500

"Pick up all the bowls I moved earlier"

이 편은 4개 이벤트로 이뤄져 있고, 그중 2개가 bowl을 옮긴다. 그래서 이 한 문장의 정답이 2개다.

frame 020834767888012777
find [0, 12777] 전 구간
침실 서랍장 위에 bowl이 놓여 있고 로봇 팔이 접근 중
frame 120 · exo_camera_1 ev0 시작. bowl₁이 침실 서랍장 위에 있다.
로봇이 bowl을 안락의자 위에 놓은 직후
frame 2082 · ev0 종료 bowl₁이 안락의자로 이동. settled_frame 2107.
에피소드 마지막 프레임, bowl이 식탁 근처 표면 위에 있음
frame 12777 · 최종 상태 bowl₂가 식탁에서 settled_frame 12777로 방금 안착.

이 행의 answers[] 두 개

objxyzroomsurface settledlast_seens_since_seend_base_endscoring
bowl_…_1_0_48.756, 10.135, 0.3654armchair 21076744241.3 s7.24 m pick
bowl_…_2_0_66.155, 2.869, 0.8457diningtable 12777127740.1 s0.65 m pick

두 정답의 기억 난이도가 정반대다 — 하나는 241초 전에 마지막으로 봤고 7.24 m 떨어져 있으며, 다른 하나는 0.1초 전 눈앞(0.65 m)에 있다. 한 행 안에 "기억해야 하는 것"과 "보고 있는 것"이 같이 들어간다.

candidates[]는 근거다 — 각 정답이 어느 이벤트에서 나왔는지(ev0 frames [0,2082] / ev3 frames [8880,12764])를 기록한다.

18행 전체

query_idquerykindsteps twinnscoring
val_144__2:q00Pick up all the bowls I moved earliertargetmulti2pick
val_144__2:q01Pick up the remote control I moved earliertargetsingletwin1pick
val_144__2:q02Pick up the cell phonedistrsingle1pick
val_144__2:q03Pick up the soap bottledistrsingle1nav_only
val_144__2:q04Pick up the potdistrsingle1pick
val_144__2:q05Pick up the spray bottledistrsingle1nav_only
val_144__2:q06Pick up the appledistrsingle1pick
val_406__0:q00Pick up the fork I moved earliertargetsingletwin1pick
val_406__0:q01Pick up the vase I moved earliertargetsingle1pick
val_406__0:q02Pick up the tissue box I moved earliertargetsingle1pick
val_406__0:q03Pick up the bowl I moved earliertargetsingle1pick
val_733__0:q00Pick up the bowl I moved earliertargetsingle1nav_only
val_733__0:q01Pick up all the remote controls I moved earliertargetmulti2pick
val_733__0:q02Pick up the bottle I moved earliertargetsingletwin1pick
val_733__0:q03Pick up the spoondistrsingletwin1pick
val_733__0:q04Pick up the knifedistrsingle1pick
val_733__0:q05Pick up all the forksdistrmultitwin2pick
val_733__0:q06Pick up the vasedistrsingle1pick

find_target vs find_distractor가 문장으로 구분된다. target은 "I moved earlier"가 붙고(영상 속 사건에 근거), distractor는 안 붙는다(처음부터 그 자리에 있던 물건). 9 : 9로 균형. 즉 "기억이 필요한 질의"와 "그냥 탐색이면 되는 질의"가 반씩이고, 이 대조가 기억 효과의 측정축이다.


restore
판정 — 판정 시점에 정답 전부가 각자의 원래 자리 반경(0.15 m) 안에 있다 (상태 목표)

find가 "어디 있나"를 묻는다면 restore는 "원래 어디 있었나"를 묻는다. 찾아서 집고, 되돌려 놓아야 끝난다. 11행 · 컨텍스트 3 · 정답 17개, 같은 컨텍스트를 find와 공유한다.

예시 하나를 끝까지 — restore:val_144__2:q00

restore origin=first steps=single twin_nearby=true budget=5000

"Pick up the remote control and place it in or on where it first was"

리모컨은 이 편에서 두 번 옮겨졌다: 침대 → 변기(ev1) → 식탁(ev2). origin=first맨 처음 자리인 침대를 답으로 요구한다. 중간 경유지인 변기는 오답이고, 그건 other_origins에 따로 기록돼 교차 오답 진단에 쓰인다.

frame 020834767888012777
to (정답) 침대 · [11.925, 9.146, 0.635] · room 4
from (현재) 식탁 · [5.875, 2.923, 0.814] · room 7
침대 위에 놓인 리모컨과 접근하는 로봇 팔
frame 2600 · to — 되돌릴 자리 리모컨의 첫 자리인 침대. origin_frames [0, 2790], 이 자리에서 5.64초 관측됨.
식탁 위에 놓인 리모컨, 로봇 팔이 물러나는 중
frame 8870 · from — 지금 있는 곳 ev2 종료 직전, 식탁에 안착. 이 위치가 곧 find:val_144__2:q01의 정답이다.

이 행의 answers[0] 핵심 수치

필드
d_xy8.679 mfrom→to 변위. 자격 하한 1.0 m
same_room / n_hopsfalse / 2방을 2번 건너야 한다
approachin_reach집는 건 제자리에서 가능 (d_base_end 0.56 m)
r_place / check_quat0.15 m / false판정 반경. 자세(quat)는 안 본다
other_origins[[12.611, 0.833, 0.587]]변기 — 오답으로 걸러야 할 다른 원래 자리
twinsremotecontrol_…_2_0_7 (d_xy 0.518 m)같은 에셋이 52 cm 옆에 있다 — 잘못 집기 쉬움
shares_answer_withfind:val_144__2:q01독립이 아닌 측정임을 명시

11행 전체

query_idstepsorigintwin napproachd_xysame_roomhopsbudget
val_144__2:q00singlefirsttwin1in_reach8.67925000
val_144__2:q01singleprevioustwin1in_reach7.05325000
val_144__2:q02multifirsttwin3navigate4.602same112000
val_406__0:q00singlefirsttwin1navigate1.946same25000
val_406__0:q01singleprevioustwin1navigate8.58925000
val_406__0:q02singlefirst1navigate1.509same15000
val_406__0:q03singlefirst1navigate4.69315000
val_406__0:q04singlefirst1in_reach7.39915000
val_406__0:q05multifirsttwin3navigate1.946same212000
val_733__0:q00singlefirsttwin1in_reach1.177same15000
val_733__0:q01multifirsttwin3navigate14.354112000

대조쌍이 설계돼 있다. origin=first 9행 vs previous 2행이 짝을 이룬다 — val_144__2의 q00(first, 침대)/q01(previous, 변기)은 같은 물체·같은 문장 틀인데 정답만 다르다. "첫 자리"와 "직전 자리"를 구분해서 기억하는지가 그대로 갈린다.

거리 분포가 넓다. d_xy 중앙 4.693 m · p10 1.177 · p90 12.19 · 최대 14.354 m. 가장 가까운 다른 물체까지는 중앙 0.588 m밖에 안 되므로(d_nearest_obj), 0.15 m 판정 반경은 옆 물체 위에 놓는 실수를 잡아낼 만큼 빡빡하다.


resume
판정 — 끊긴 지점에서 남은 유닛(r)만 정확히 마저 한다

type2 편에서만 나온다. 사용자가 반복 유닛 task를 순차로 주고 중간에 세운다. 질의 문장은 7행 전부 동일하고 — 정보는 전부 start_statecommands[]에 있다. 7행 · 컨텍스트 2 · 탈락 0.

"You were in the middle of something. Continue what you were doing."

예시 하나를 끝까지 — resume:val_101__1100:cut_01

press_button cue=bare cut_phase=clean budget=400

이 편의 사용자 지시는 세 개였다:

taskkindutterancestart_framen / done
T1press_buttonPress the button on the dryer three times.13 / 3
T2visit_roomsNavigate to every room in the house.8484 / 4
T3put_in_containerPick up three of the eggs from the dresser and place them into the basket.31693 / 3

cut_01frame 651에서 끊는다. T1의 3번 중 2번을 눌렀고(m=2), 1번 남았다(r=1). 이 시점에 정책이 보는 것은 프레임 0–651과 T1 발화 한 줄뿐이다 — T2·T3는 아직 주어지지도 않았으므로 decoy_tasks로만 들어간다.

frame 084831699264
cut_01 [0, 651] — 정책은 여기까지만 본다

세로 눈금 = 컷 10개(states/index.json). 굵은 눈금 4개가 이 배포본의 질의로 채택됐다.

로봇 팔이 건조기 컨트롤 패널 버튼을 누르고 있음
frame 651 · cut_01 시점 건조기 버튼 2회 완료 직후. done_units에 frame 453, 650이 기록됨. 1회 남음.
침실에서 바구니 위로 팔을 뻗은 로봇
frame 7529 · cut_08 시점 다른 컷 예시 — T3 달걀 3개 중 2개 완료. 남은 1개를 바구니에 넣어야 한다(oracle 1722스텝).

오라클이 이미 돌아 있다. oracle: {steps: 196, unit_order: ["t1app_Clothes_Dryer_2"], completed: 1} — 예산 400스텝은 오라클 196스텝의 약 2배로 잡혔다.

7행 전체 — 그리고 "아무것도 하지 말아야 하는" 2행

query_idcut@frametask type nm(완료)r(남음)budgetoracle stepsdecoys
val_101__1100:cut_01651press_button321400196T2, T3
val_101__1100:cut_02848press_button3304000T2, T3
val_101__1100:cut_052615visit_rooms431829553T1, T3
val_101__1100:cut_087529put_in_container32125831722T1, T2
val_733__1100:cut_01591press_button321400196T2, T3
val_733__1100:cut_02788press_button3304000T2, T3
val_733__1100:cut_053143visit_rooms431936624T1, T3

r=0 2행이 이 가족의 함정이다. stats.jsonby_r: {0: 2, 1: 5}가 그것이다. 두 행 모두 컷 시점에 해당 task가 이미 n번 다 채워져 있고, 오라클도 steps 0 · completed 0이다. 즉 "계속하라"는 말에 반사적으로 한 번 더 누르면 틀린다. 기억 없이 문장만 따라가는 정책을 잡아내는 음성 대조군이다.


설계 제안 이 배포본에 없는 네 번째 가족이다. eval/routine/ 디렉터리도, 행 하나도 아직 없다. 아래 스키마와 질의는 제안이고, 근거는 실측이다 — 인용한 이벤트·표면·좌표는 전부 val_406__0의 실제 값이다.
routine (D · 하위 3종)
판정 — 홀드아웃 물체가 규칙이 가리키는 표면 클래스의 어느 인스턴스 위에든 반경 안에 있다 (상태 목표)

find/restore/resume은 전부 특정 사건을 묻는다 — 그 물건, 그 자리, 그 컷. routine은 다르다: 여러 사건에 공통으로 흐르는 규칙을 유도해서, 본 적 없는 물건에 적용하라고 한다. taxonomy 하위 3종은 D1 배치 규칙 · D2 절차 규칙 · D3 금지이고, 아래는 그중 D1이다.

근거 — 이 편에는 이미 규칙이 들어 있다

val_406__0의 이벤트 5개가 물건을 놓은 곳을 표면 클래스로 세면 이렇게 된다. 아무도 의도하지 않았는데 3/5가 같은 클래스로 간다.

ev물건출처 → 목적지목적지 표면 (실제 body name)room클래스
0fork조리대 → 서랍장chestofdrawers_bcf35268…_1_0_77chestofdrawers
1vaseTV장 → 서랍장chestofdrawers_bcf35268…_1_0_77chestofdrawers
2fork서랍장 → 식탁diningtable_f113cf7f…_2_0_66diningtable
3tissue boxTV장 → 서랍장chestofdrawers_10acc45c…_1_0_44chestofdrawers
4bowl서랍장 → 세면대sink_07e796f3…_1_0_55sink
로봇이 나무 서랍장 위에 포크를 내려놓은 직후
frame 2170 · ev0 fork → 거실 서랍장. bcf35268…
같은 나무 서랍장 위에 화병을 놓는 로봇, 신문이 그대로 놓여 있음
frame 3870 · ev1 vase → 같은 서랍장. 신문 위치로 동일 가구임을 확인.
검은색 서랍장 위에 티슈 상자를 놓는 로봇
frame 9105 · ev3 tissue box → 침실의 다른 서랍장. 10acc45c… — 에셋이 다르다.

이 세 장이 D1의 핵심을 그대로 보여준다. 두 서랍장은 에셋 해시가 다르다(bcf35268… vs 10acc45c…) — 색도 재질도 방도 다르다. 그러니 정답은 좌표도 인스턴스도 아니고 카테고리다. restore가 xyz + r_place 0.15로 한 점을 찍는 것과 판정 방식 자체가 갈린다.

이름이 두 개다. 발화는 “dresser”라고 하는데 body name은 chestofdrawers_*다(subtasks.jsonslots.place_name vs obj 비교). 규칙은 body name의 에셋 클래스로 정의하고, 질의 문장은 사람이 쓰는 낱말로 쓴다 — 이 매핑을 채점기가 들고 있어야 한다.

미끼는 심을 필요가 없다 — 이미 있다

규칙 과제의 난점은 “그럴듯한 오답”을 만드는 것인데, 이 편에서는 저절로 생겼다.

TV 앞 나무 스탠드에서 티슈 상자를 집어 올리는 로봇
frame 7500 · ev3의 pick 구간 로봇이 TV장에서 티슈 상자를 집는 중.
표면출처로목적지로역할
chestofdrawers ×223정답 클래스
stand (TV장)20최상급 미끼
countertop10미끼
diningtable01반례
sink01반례

TV장은 로봇이 두 번이나 앞에 서서 물건을 집은 곳인데 놓은 적은 한 번도 없다. 관측 빈도로만 고르면 반드시 걸린다 — “자주 본 표면”과 “물건을 두는 표면”을 구분해야 통과한다.

질의 문장을 뭐라고 쓸 것인가

routine에서 가장 위험한 설계 지점이다. 문장 하나로 이 가족은 메모리 과제가 되기도 하고 상식 퀴즈가 되기도 한다. 문장은 아래 세 가지를 동시에 만족해야 한다.

#요구안 지키면
1규칙의 축을 지정한다 — 방인가, 가구 종류인가, 서랍 안인가 정책이 무엇을 유도해야 할지 몰라 ambiguous 탈락
2규칙의 값은 절대 주지 않는다 — “서랍장”이라는 낱말이 문장에 없어야 한다 기억 없이 풀린다 → perception task로 강등 (규칙 1 위반)
3일반성을 표시한다usually / kind of 같은 반복·범주 신호 “내가 마지막에 놓은 곳”으로 읽혀 단일 사건 조회가 된다 → find/restore와 구분 소실

다른 가족과 나란히 놓으면 자리가 보인다

가족template뒷절이 가리키는 것정답의 정체
restorePickAndPlace(restore) … place it in or on where it first was물체의 과거 → 좌표 1점
restorePickAndPlace(restore) … place it in or on where it was just before물체의 직전 → 좌표 1점
routinePickAndPlace(routine) … place it in or on the kind of place where things usually go in this house 의 규칙 → 표면 클래스

뒷절 슬롯 하나만 갈아끼우면 된다 — PickAndPlace 템플릿을 그대로 쓰므로 MolmoSpaces 학습 분포에서 벗어나지 않는다. restore가 물체의 이력을 묻는 자리에 routine은 집의 규칙을 넣는다.

하위 3종의 문장

하위질의 문장축을 지정하는 부분
D1
배치
Pick up the mug and place it in or on
the kind of place where things usually go in this house
the kind of place = 표면 클래스
usually = 반복/규칙 신호
D2
절차
Put one more egg in the basket,
the way the others were done in this house
the way = 순서
the others = 앞선 유닛 참조
D3
금지
Put the mug down somewhere it can stay —
somewhere this house would put it
would put it = 허용/금지 축
금지 표면은 문장에 안 나온다

D3의 A5 충돌을 문장으로 푼다. “금지”를 “안 하기”로 내면 규칙을 지켜서 안 한 것과 그냥 못 한 것이 구분되지 않아 공리 A5(기권도 정답)와 부딪힌다. 위처럼 “금지된 곳을 피해서 놓기”로 내면 양성 판정이 된다 — 반경 안에 놓을 곳이 여럿이고 그중 하나가 이 집에서 물건을 절대 두지 않는 표면이면, 판정은 “놓았고 & 금지 표면이 아니다”로 끝난다. 기권과 명확히 갈린다.

⚠ 최대 위험 — 상식 누출 (공리 A1)

이 가족은 find_distractor가 이미 걸렸던 함정을 훨씬 크게 밟는다. 파일럿 실측에서 find_distractor의 blind 정확도가 0.75였다 — 비누는 욕실, 냄비는 주방이라는 상식만으로 풀렸다는 뜻이다. “머그를 치워라”는 그보다 더 심하다. 방어 없이 내면 blind가 규칙을 안 보고도 맞힌다.

#방어구체적으로
1반사실 짝 (공리 A6) 문장은 집마다 똑같이 두고 정답만 다르게 저작한다. 같은 “머그를 이 집 규칙대로 놓아라”가 house A에서는 chestofdrawers, house B에서는 countertop, house C에서는 shelf. 최소 2집을 짝으로 묶어 둘 다 맞아야 득점 — resume이 7행 전부 같은 문장인 것과 같은 원리다.
2반상식 배치를 섞는다 규칙 값의 일정 비율을 상식과 어긋나게 저작한다. 마침 이 편이 그 예다 — 포크를 서랍장에 올린다(ev0). 상식으로는 주방 서랍이다.
3blind control@chance 고정 후보 표면 클래스가 K개면 blind 기대값은 1/K다. 편마다 K를 세어 n_surface_classes로 행에 기록하고, blind가 1/K를 유의하게 넘으면 그 편을 탈락시킨다.

홀드아웃 물체 선정 규칙도 문장이 정한다. 홀드아웃은 규칙 근거에 안 나온 카테고리여야 한다. 근거가 fork·vase·tissue box이므로 홀드아웃이 또 fork면 “같은 물체를 어디 뒀더라”(= find)로 풀려버린다. mug는 세 카테고리 어디에도 없고 컨텍스트 동안 한 번도 안 움직였다 — 그래서 골랐다.

질의 행 초안

공통 봉투 11필드는 그대로 두고, 고유부만 얹는다(중첩 payload 없음, 다른 가족과 같은 규약). 주황 표시가 저작해야 하는 부분이고 나머지는 실측값이다.

{
  // ── 공통 봉투 11 (다른 가족과 동일) ──
  "schema_version": "1.0",
  "family": "routine",
  "kind": "routine_placement",          // D1. D2=routine_procedure, D3=routine_prohibition
  "query_id": "routine:val_406__0:q00",
  "context": "contexts/type1/val_406__0",
  "context_frames": [0, 11436],
  "start_state": "states/final.npz",
  "query": "Pick up the mug and place it in or on the kind of place
            where things usually go in this house",
  "budget_steps": 5000,
  "oracle": null,
  "tags": ["placement_rule"],

  // ── routine 고유부 ──
  "template": "PickAndPlace(routine)",        // restore와 같은 틀. 뒷절 슬롯만 우리 것
  "rule": { "type": "surface_class", "value": "chestofdrawers",
            "utterance_word": "dresser" },       // body name ↔ 사람 낱말
  "n_surface_classes": 6,                       // blind control@chance = 1/6 ≈ 0.167
  "counterfactual_pair": "routine:val_733__0:q00", // 공리 A6 — 같은 문장, 다른 정답
  "n_evidence": 3,
  "evidence": [                                  // 규칙을 세운 사건들 (근거)
    { "event": 0, "obj": "fork_a17d780c…_1_0_6",       "frames": [1782, 2181],
      "surface": "chestofdrawers_bcf35268…_1_0_7", "room": 7 },
    { "event": 1, "obj": "vase_939cc49c…_1_0_7",       "frames": [3483, 3882],
      "surface": "chestofdrawers_bcf35268…_1_0_7", "room": 7 },
    { "event": 3, "obj": "tissuepaper_1f28299e…_1_0_7", "frames": [8717, 9116],
      "surface": "chestofdrawers_10acc45c…_1_0_4", "room": 4 } ],
  "counterexamples": [                           // 규칙을 안 따른 사건 — 자격 판정에 쓴다
    { "event": 2, "surface": "diningtable_f113cf7f…_2_0_6" },
    { "event": 4, "surface": "sink_07e796f3…_1_0_5" } ],

  "holdout": {                                   // 규칙을 본 적 없는 물체에 적용
    "obj": "mug_ac3a060b…_1_0_6",
    "xyz": [0.870, 3.139, 0.804], "room": 6,
    "moved_during_context": false,               // ← 어떤 이벤트에도 안 나온다 (실측)
    "observed": true, "seen_s": <측정 필요> },

  "answers": [ {
    "obj": "mug_ac3a060b…_1_0_6",
    "to_class": "chestofdrawers",
    "any_instance_ok": true,                    // ← restore와 갈리는 지점
    "instances": [
      { "surface": "chestofdrawers_bcf35268…_1_0_7", "room": 7,
        "xyz": [9.129, 3.801, 0.707], "reach_min": 0.500 },
      { "surface": "chestofdrawers_10acc45c…_1_0_4", "room": 4,
        "xyz": [9.017, 8.507, 0.771], "reach_min": 0.650 } ],
    "r_place": 0.15, "check_quat": false,
    "d_xy_min": 8.3                             // mug → 가장 가까운 서랍장
  } ],

  "distractors": [                               // 채점 안 함. 오답 진단용 (실측 집계)
    { "surface": "stand_6bc09b7e…_1_0_7", "room": 7,
      "src_count": 2, "dst_count": 0, "role": "source_only" },
    { "surface": "countertop_d89e05e8…_1_0_6", "room": 6,
      "src_count": 1, "dst_count": 0, "role": "source_only" } ]
}

왜 편당 최대 1행인가

frame 0218238836565911711436
routine 근거 3 + 홀드아웃 1 = 편 하나를 다 쓴다

규칙 하나에 사건 3개가 들어간다. 편의 이벤트 예산이 4~5개이므로 두 번째 규칙을 심을 자리가 남지 않는다 — find가 편당 4.8행, restore가 1.9행을 뽑는 것과 구조가 다르다. 이것이 routine을 편당 최대 1행으로 묶는 구조적 상한이고, 총량 추정 ~90행(D1 40 · D2 30 · D3 20)의 근거다.

이 편을 그대로 쓸 수는 없다. 3/5는 “그런 경향”이지 규칙이 아니다 — 식탁 1회·세면대 1회라는 반례가 있어서, 정책이 “서랍장”을 못 맞혀도 틀렸다고 하기 어렵다. 실제 D1 편은 배치 4/4를 규칙으로 고정하고 홀드아웃을 따로 두도록 저작해야 한다. 그러면 그 편의 이벤트가 “같은 곳에 놓기”로 채워지므로 목적지 다양성이 떨어지고, 그 편에서 나오는 find/restore 행이 줄어든다. routine은 순증분이 아니다.

남은 두 종은 어디에 붙나

하위규칙의 형태붙일 편판정가장 큰 난점
D1 배치“물건은 X 클래스 위에 둔다” type1홀드아웃이 X 인스턴스 반경 안반례가 섞이면 규칙이 약해진다 (위 사례)
D2 절차“X 하기 전에 Y를 먼저 한다” type2 — 이미 반복 유닛 구조
press ×3 · visit ×4 · put ×3
홀드아웃 유닛에서 순서가 지켜짐순서 제약이 있는 task 조합이 필요
D3 금지“여기엔 절대 두지 않는다” type1안 하는 것이 정답 (음성 판정) “규칙을 지킨 것”과 “그냥 안 한 것”이 구분 안 된다

D2가 가장 싸다. type2는 이미 같은 유닛을 3~4회 반복하는 구조라 규칙을 새로 심을 필요 없이 순서 제약만 얹으면 된다. 반대로 D3가 가장 비싸다 — 음성 판정은 “예산 안에서 다른 유효한 행동을 했는가”라는 부가 조건 없이는 기권과 구분되지 않는다. 이건 공리 A5(기권도 정답)와 정면으로 부딪히므로 별도 설계가 필요하다.


raw원본 파일 안에는 무엇이 있나

traj.h5 — 프레임 단위 전 상태

영상 프레임 수와 정확히 1:1이다 (val_144__2: 12,778 프레임 = 12,778 스텝 = mp4 nb_frames). 가변 길이 항목은 uint8 패딩 버퍼로 들어 있다.

traj_0/
  obs/extra/
    tracked_obj_pose      (12778, 25, 7)  float32   ← 추적 객체 25개의 pose 궤적
    tracked_obj_pixels    (12778, 25, 3)  int32     ← 화면상 픽셀 위치 (가시성 판정)
    robot_base_pose       (12778, 7)      float32
    tcp_pose              (12778, 7)      float32
    scene_joint_qpos       (12778, 14)     float32   ← fixture 개폐 상태
    policy_phase           (12778,)        int64
    policy_num_retries     (12778,)        int64
    env_states             (12778, 50000)  uint8     ← 직렬화 버퍼
  obs/agent/qpos,qvel      (12778, 2000)   uint8     ← 직렬화 버퍼
  sensor_param/{exo_camera_1, exo_camera_2, wrist_camera}/
    cam2world_gl (…,4,4) · extrinsic_cv (…,3,4) · intrinsic_cv (…,3,3)   float64
  env_states/articulations/panda  (12778, 31)  float32
  rewards · success · terminated · truncated · fail                     (12778,)

sensor_data/ 그룹은 비어 있다 — 픽셀은 mp4로만 배포된다. 카메라 파라미터는 프레임마다 들어 있으므로 3D 재투영은 h5만으로 가능하다.

subtasks.json — 사건의 대본

이벤트 하나가 nav → manip(pick) → carry → manip(place) 네 세그먼트로 쪼개져 있고, 각 세그먼트에 프레임 구간·템플릿·자연어 instruction·프리미티브 목록이 붙는다.

// type1/val_144__2 · subtasks[0]
{ "idx": 0, "kind": "pick_and_place", "frames": [0, 2082], "outcome": "ok",
  "task_description": "Pick up the bowl from the dresser in the bedroom and place it in or on the armchair",
  "slots": { "pickup_name":"bowl", "source":"dresser", "source_room":"bedroom",
             "place_name":"armchair", "place_room":"bedroom" },
  "segments": [
    { "kind":"nav",   "frames":[   0,  257], "template":"NavToObj",           "prims":["nav"] },
    { "kind":"manip", "frames":[ 258,  559], "template":"Pick",               "prims":["approach","grasp","lift"] },
    { "kind":"carry", "frames":[ 560, 1740], "template":"NavToObj",           "prims":["carry","navcarry"] },
    { "kind":"manip", "frames":[1741, 2082], "template":"PickAndPlace(tail)", "prims":["place","release","retreat"] } ],
  "settled_frame": 2107, "moved_m": 4.618, "on_floor": false, "lifted": true, "tags": ["shift"] }

세 편의 이벤트 전체

스텝ev이벤트movedoutcometags
val_144__2127780bowl: 침실 서랍장 → 안락의자4.618okshift
1remote: 침대 → 욕실 변기8.342ok
2remote: 변기 → 거실 식탁7.056okrelocate, twin_merge
3bowl: 주방 조리대 → 거실 식탁8.029ok
val_406__0114370fork: 주방 조리대 → 거실 서랍장7.475ok
1vase: TV장 → 서랍장1.508okshift
2fork: 서랍장 → 주방 식탁8.589okrelocate, twin_merge
3tissue box: TV장 → 침실 서랍장4.694ok
4bowl: 침실 서랍장 → 욕실 세면대7.400ok
val_733__0134090bowl: 주방 조리대 → 거실 카트9.734failed_dropped
1remote: TV장 → 침실 식탁14.355ok
2bowl: 카트 → TV장0.000failed_no_placerelocate, twin_merge
3remote: 침실 의자 → 욕실 변기12.191ok
4bottle: 주방 식탁 → 의자1.227okshift

failure실패가 어떻게 데이터에 남는가

수집 실패를 버리지 않고 채점 난이도를 낮춰서 살린다. 이 샘플에 실제 사례가 3건 있다.

사례 1 — 바닥에 떨어진 bowl (val_733__0 ev0)

수집 정책이 bowl을 카트로 옮기다 떨어뜨렸다(outcome: failed_dropped). 그런데 이 실패가 find:val_733__0:q00의 정답으로 그대로 살아 있다:

{ "obj": "bowl_6befd62f…_1_0_6",
  "xyz": [3.029, 9.75, 0.045],          ← z=4.5cm, 바닥
  "room": null, "surface": null,             ← 어느 가구 위도 아님
  "on_floor": true, "misplaced": "on_floor",
  "settled_frame": 2447, "last_seen_frame": 6949, "s_since_last_seen": 258.4,
  "pickable": false, "pick_fail": {"ik/reach": 480},   ← IK가 480번 실패
  "scoring": "navigate_only" }                ← 집기 채점을 포기하고 접근만 채점

즉 "바닥에 떨어진 물건을 기억해서 그 앞까지 가는가"라는 질의로 재활용됐다. misplaced: on_floor는 stats의 축으로도 노출된다(misplaced: {no: 20, on_floor: 1}).

사례 2 — 팔이 안 닿는 조리대 위 물건 (val_144__2 q03·q05)

soap bottle(z=1.047)과 spray bottle(z=1.064)은 처음부터 그 자리에 있던 distractor인데, 둘 다 pickable: false · pick_fail: {"ik/reach": 480}이라 navigate_only로 내려갔다. 실패 원인이 정책이 아니라 로봇 팔의 도달 한계(고정 베이스 8-D)라서, 집기를 요구하는 것 자체가 불공정하다.

채점 축이 섞여 있다. stats의 scoring: {navigate_only: 3, pick: 18}행이 아니라 정답 21개 기준이다(18+3=21). 러너가 행 단위로 집계하면 숫자가 안 맞는다.

사례 3 — 놓을 자리를 못 찾음 (val_733__0 ev2)

failed_no_place, moved_m: 0.0. 물건이 아예 안 움직였으므로 d_xy 자격 하한 1.0 m를 통과하지 못하고, 이 이벤트에서는 restore 행이 나오지 않았다(val_733__0는 restore 2행뿐).


caveats이 배포본을 쓸 때 걸릴 것들

문서와 실물을 대조하면서 확인한 불일치·함정. 심각도 순.

HIGH
find와 restore는 독립 측정이 아니다

restore/stats.jsonshares_find: {rows: 11, frac: 1.0} — restore 11행 전부가 find 행과 정답을 공유한다. 두 가족 점수를 더하거나 평균 내면 같은 물체를 두 번 세는 셈이다. 행마다 shares_answer_with가 명시돼 있으니 보고 시 반드시 분리하거나 명시해야 한다.

HIGH
find · restore의 오라클이 아직 안 돌았다

29행 전부 oracle: null이고 stats.oracle = {run: 0, ok: null}. 예산(budget_steps 1500 / 5000 / 12000)이 오라클 실측이 아니라 상수로 박혀 있다는 뜻이다 — find는 18행 전부 1500, restore는 single 5000 / multi 12000 두 값뿐.

resume만 7/7 오라클이 채워져 있고, 그쪽 예산은 실측의 약 1.5~2배로 잡혀 있다(196→400, 553→829, 1722→2583).

MED
contexts/README.md가 없는 파일을 가리킨다

type2 전용으로 적힌 tasks.json·utterances.json이 실물에 없다. 내용은 subtasks.jsontasks/cuts/stage와 질의 행의 commands[]에 있다.

MED
restore/README.md의 탈락률 표기가 모순으로 읽힌다

"자격 탈락률은 0.0%다 (merged_multi 4 · multi_capped 1)"인데 dropped.total5다. merge·cap은 자격 탈락이 아니라 행 병합이라 분모에서 빠진 것으로 보이지만, 문장만 봐서는 알 수 없다. find 쪽은 ambiguous 1 → 5.3%로 정상 계산된다.

MED
eval/resume/에 README가 없고 stats 스키마도 다르다

find·restore의 stats.jsonrows / answers / contexts / dropped / axes / by_context 구조인데, resume은 n_query / n_context / n_dropped / by_r / by_type키 이름부터 다르다. 세 가족을 한 파서로 훑을 수 없다.

LOW
answers[].on_floor가 21개 중 11개에만 있다

나머지 10개는 키 자체가 없다. a["on_floor"]로 접근하면 KeyError. a.get("on_floor", False)로 읽거나 misplaced 필드를 쓰는 게 안전하다. 같은 정보를 담은 misplaced는 21/21 존재한다.

LOW
resume 행만 house·seed를 최상위에 든다

find·restore에는 없다. 둘 다 query_id에서 파싱 가능하므로 기능상 문제는 없지만, 공통 봉투 바깥의 필드라 스키마 검증기를 짤 때 걸린다.

러너 체크리스트

  1. 컨텍스트는 항상 편 전체다. 영상·traj.h5에는 컷 이후 프레임이 그대로 들어 있다 — context_frames = [f0, f1]닫힌 구간이고, 자르는 것은 읽는 쪽 책임이다. 특히 resume에서 컷 이후를 흘리면 그게 곧 정답 누설이다.
  2. 캐시 키는 (context, context_frames[1]). find·restore는 이 값이 자동으로 같아지므로 편당 1회 로드로 최대 10행(val_144__2: find 7 + restore 3)을 처리할 수 있다.
  3. tags는 채점에 쓰지 않는다. 분석 축 전용이다.
  4. 경로는 데이터셋 루트 상대. 절대경로·심볼릭 링크 금지.
  5. 판정 기준이 가족마다 다르다. find는 사건 목표(한 롤아웃 안에 전부 한 번씩), restore는 상태 목표(판정 시점 반경 0.15 m), resume은 남은 유닛 수 r.