DoorStateMachine을 build_scene_mem_v2.py에 feature-flag로 배선 — manifest 없으면 legacy path byte-identical, 있으면 scenario-scoped per-door re-groundingmanual_subtasks baked door step이 DoorStateMachine과 중복 emit → strip + reindex_setup_skips_open 정적 게이트 완전 제거 → interior access fixture는 항상 before:open 선언, DoorStateMachine이 runtime에 emit/suppress 결정required_door_states non-empty 19개 auto-derivedB1 eval 시각 뷰어에서 사용자가 직접 확인한 두 결함 중 "닫힌 fixture 앞 암묵 Open 누락"이 scene-mem v2 데이터 전체에 퍼져 있었다. 근본 원인은 fine-subtask 분해가 episode별 실제 초기 door 상태에 grounding되지 않고, robocasa-365 소스의 정적 AST에만 의존하기 때문이다. 즉 cabinet이 닫혀 있어도 열어 있어도 같은 분해 시퀀스를 emit했다.
Task 3에서 DoorStateMachine(scenario-scoped, 양방향, initial=closed→Open+Close / initial=open→suppress Open emit)을 구현했고, Task 4는 이것을 실제 분해 파이프라인에 연결하는 것이다. 두 가지 정확성 버그(P1/P2)가 독립 source-verification 과정에서 추가로 발견되어 함께 수정했다.
build_scene_mem_v2.py: top-level from scripts.door_state_machine import DoorStateMachine 추가_seed_machine(manifest_scenario): fixtures map → DoorStateMachine. undetermined door는 seed 제외 → KeyError loud fallback(silent default 절대 금지)resolve_subtasks_with_doors(...): legacy resolve_subtasks로 base list 생성 후 door step interleave. required_door_states 없으면 base 그대로(gate inert)--init-state-manifest arg 추가: main → jobs tuple → process_scenario → _try_build → build_turns_for_test까지 manifest_scenario 스레딩 완료extract_composite_subtasks.py: --emit-door-annotations argparse 추가. 플래그 없으면 default path byte-identicalresolve_subtasks_with_doors에서 is_target=False로 base resolve — eval_spec target answer는 final-query site(line 1119)에서 별도 처리(legacy 유지). (2) before_step_idx: k 삽입 위치가 base[max(0,k-1)] 앞 — 스펙 테스트가 before_step_idx:1을 base[0] 앞(맨 앞)에 기대해 legacy 해석 채택.
manual_subtasks.json 일부 task의 subtask_templates에 baked "Open/Close the … door" 스텝이 있거나 conditional_steps에 door 스텝이 있을 때, --emit-door-annotations 시 DoorStateMachine이 추가로 emit → duplicate. 영향 task: CoolBakedCake, FoodCleanup, LoadDishwasher, MakeFruitBowl, MicrowaveCorrectMeal, ReturnWashingSupplies, SpicyMarinade, SteamInMicrowave.
수정: build_task_subtasks의 strip pass를 manual entry에도 적용. re.match(r"(?i)^(open|close) the .+ door", s)로 baked door step 제거. keep_mask로 stripped list 생성, _reindex_before_step_idx(reqs, keep_mask)로 before_step_idx 재계산. 예외: close-only task(PrepareSoupServing)는 sole template이 door step이라 strip 시 빈 list → if new_tmpls: guard로 보존.
derive_required_door_states의 _setup_skips_open gate가 setup에서 문을 여는 task 또는 setup이 없는 task(MakeFruitBowl)에 before:open을 emit하지 않았다. Cabinet이 실제로 닫혀 있는 episode에서 interior access 전 Open이 생략되는 silent-wrong.
수정: _setup_skips_open gate 완전 제거. 대신 baked_templates를 regex로 스캔 → from/in/inside/into/on/on a shelf in <fw> 패턴의 첫 match index가 before_step_idx. DoorStateMachine이 runtime에 emit(initial=closed)/suppress(initial=open) 결정 → 항상 before:open 선언해도 safe.
새 원칙(before_step_idx = baked list 기준, strip+reindex가 stripped list로 변환) 아래 11개 task required_door_states 재작성:
| Task | before_step_idx | 근거 |
|---|---|---|
| SteamInMicrowave | 4 | "Place the bowl inside the microwave" |
| MicrowaveCorrectMeal | 2 | "Place the {obj} inside the microwave" |
| FoodCleanup | 2 | "Place the {obj} in the cabinet" |
| LoadDishwasher | 2 | "Place the cup in the dishwasher" |
| MakeFruitBowl | 2 | "Pick up the {obj} from the cabinet" |
| ReturnWashingSupplies | 2 | "Place the {obj} in the cabinet" |
| CoolBakedCake | 1 | "Pick up the cake from the oven" |
| SpicyMarinade | 2 | baked idx=1 기준 |
| PrepareCoffee, SetUpCuttingStation, YogurtDelightPrep | as-is | 기존 correct |
test_decomposition_regrounding.py에 4개 추가: no_duplicate_close_when_initial_open, no_duplicate_open_and_close_when_initial_closed, manual_eleven_get_required_door_states(P1 regression guard 추가)data/task_subtasks.json 무변경 확인)--emit-door-annotations validator: VALID — 162 tasksrequired_door_states non-empty = 19개 (19 auto + 0 manual, Task 5a에서 11 manual 추가예정). 예: ToastBaguette→[toaster oven door after_all closed], RestockBowls→[cabinet before_step_idx:1 open + cabinet after_all closed], MoveFridgeToFreezer→[freezer/fridge door after_all closed]ImportError: cannot import name 'resolve_subtasks_with_doors' 예상대로 fail. Step 5 green: 3/3 regrounding passed. 스펙 결함(존재하지 않는 parsed 키) 자동 탐지 — real ClassElements 신호로 adapt.
C2 door state machine이 분해에 실제로 연결됐다. 19 composite task가 scenario별 door re-grounding 대상이 됐고, manifest 없을 때는 legacy path byte-identical — baseline 재현 보장·비파괴.
P1/P2 두 silent-wrong 버그는 TDD red 단계가 아닌 별도 source-verification에서 발견됐다. "silent-wrong 절대 금지" 원칙이 구현 레벨(loud KeyError)에선 잘 작동하지만, 파이프라인 의미상 wrong(중복 emit / Open 누락)은 end-to-end spot-check가 필수임을 재확인.
_setup_skips_open을 gate로 유지하는 대신 완전 철거한 이유 — 실제 door state를 아는 DoorStateMachine이 runtime에 emit/suppress를 결정하는 게 더 정확하고, 정적 gate가 edge case(MakeFruitBowl)를 놓치는 패턴이 반복됐기 때문. "oracle-driven emit, static-gate 제거"가 Sub-project A의 핵심 원칙이다.
ep_meta['lang'] f-string) task는 AST 추출 불가 → required_door_states=[]. Task 5에서 manual 27개 door 의미를 명시적 required_door_states로 추가할지 결정.tgt_idx, resolved_target_idx)가 base seq 기준 계산 — manifest 생성(별도 cluster job) 후 Task 5에서 실측 필요.