# 2026-08-07 육안 판정 25건 — 카테고리별 근본 원인과 재생성 설계

대상 실행: project `e716bafb-24bb-42b7-aea0-fdb383844ee8` /
episode `d6a9aa85-b75e-400c-980c-4ee7e876a15b` (C 실행, 2026-08-05~06, 스틸 255장).

이 문서는 개별 샷 수정안이 아니다. 지목 25건을 원인 층으로 묶고, 다시 그리기
전에 무엇을 고쳐야 하는지와 어떤 순서로 검증할지를 정한다.

작성 근거는 전부 실측이다 — 코드는 file:line, 산출은 `records.json` 과 이미지
실물, 체크포인트는 manifest 대조. 유료 호출 0으로 얻었다.

## 0. 30초 요약

지목 25건은 세 그룹으로 갈린다.

1. **이미 고쳤는데 그 뒤로 다시 안 그린 것** — 폰 부유(팩 v13 손 예외, 미커밋),
   S93sh5 인물(팩 v14 배타 조항 제거, 미커밋), 선정 오류·지폐 국적·룸미러
   광학(judge 팩 v6, 커밋됨). 팩 v6 는 8/6 22:00 이고 문제의 산출은 09:16
   생성이다 — **팩을 못 받았다.**
2. **아직 안 고친 것** — i2i 수정이 참조를 못 본다, 인물 삽입이 기하를 다시
   그린다, 구도·연기·조명 절이 한 번도 안 실렸다.
3. **새로 발견** — 샷 텍스트가 정지 스틸로 성립하지 않는 "허공에 떠 있는"
   순간을 지목한다. `shot_validator` 가 977샷 중 55%를 고치면서 의미를 바꾼다.

따라서 순서는 **고칠 것을 전부 고친 뒤 한 번에 다시 그린다.** 지금 다시 그리면
두 번 그리게 된다.

## 1. 축별 원인 — 확정된 것

### A. 자동차 내부 (S13sh3·S13sh4·S15sh3·S15sh5·S18sh1·S18sh5)

원인이 **두 갈래**다. 초기 진단("prev 경로에 공간 계약이 없다")은 절반만 맞았다.

**(a) 계약 결손 — prev 경로 4샷**

`shot_conti_light.py:96` `conti_targets` 는 `person_visible and not prev` 다.
prev 샷은 콘티를 받지 못한다. 그리고 `still_recipe_camera_frame_enabled` 가
기본 False(`config.py:742`)라 구도 절도 없다. 실측: `CAMERA/FRAME` 절
**0/255**. prev 경로는 전체의 **49%(126샷)** 다.

이 샷들이 받는 공간 정보는 LOCATION 한 문장과 이전 샷 사진 한 장뿐이다.
운전석이 어느 쪽인지, 카메라가 어디 있는지, 룸미러가 어디 붙는지가 어디에도
없다.

**(b) 권위 상실 — bgfirst 경로 2샷 (더 직접적)**

S13sh3 의 배경 산출 `S13sh3__bgfirst_bg.png` 는 **핸들 림 1개, 좌핸들, 좌석
배치가 정확하다.** 그런데 인물 삽입 후 `S13sh3_a.png` 에서 **림이 이중이 되고
좌석 관계가 흐트러진다.**

프롬프트가 그렇게 시키고 있다.

- `still_recipe/*/sketch_label.md` = `LAYOUT SKETCH (people placement only)`
- `still_recipe/7.202607201550/stage_head.md` = `… The SECOND attached image
  (LAYOUT SKETCH) tells you ONLY where the people go … **Ignore the sketch's
  background lines.**`

콘티에 기하가 정확히 그려져 있어도 인물 삽입 단계가 그 선을 무시하라고 명시
지시한다. 게다가 인물 삽입은 마스크 없는 full-frame 편집이라 "배경을 EXACTLY
유지하라"는 지시에도 배경이 다시 그려진다.

**복잡 구조물 판별이 야외 전용이다.** `outdoor_lane_plan` 의
`site.has_complex_structure`(팩 v4)는 장소 단위 선판정이지만 야외 경로에만
실린다. 자동차 6샷 중 `lane_policy` 가 붙은 것은 S15sh3·S15sh5 둘뿐이고
나머지 4샷은 `None` 이다 — 같은 차량 실내인데 판별이 갈린다.

### B. i2i 수정이 개악 (10건)

**지배적 기전 — 판정과 수정의 입력이 다르다.**

`multiroll_select.py:525` critique 는 `labeled_refs` 전체(캐릭터 참조·이전 샷·
배경)를 보고 지적한다. `:614` fix 는 `[(fix_label, orig)]` — **선정 원본 한
장만** 받는다. 지적 내용은 텍스트로만 남는다.

| 샷 | critique 지적 | 참조 없이 실행한 결과 |
|---|---|---|
| S73sh4 | "녹색 비니를 캐릭터 참조와 정확히 일치시켜라" | **둘 다 비니** |
| S74sh8 | "어머니를 딸로 교체하라" | 옷이 전부 달라짐 |
| S98sh6 | "긴 흰머리를 가지런히 묶어라" | 머리 스타일이 달라짐 |
| S57sh3 | "비석 글씨를 이전 샷의 형태로" | 이전 샷을 못 봐서 못 고침 |

**검증 부재.** C 실행에서 fix 223건 전부 `fix_rejudge` 없음. 현재
`multiroll_fix_rejudge_enabled` 는 True 로 승격됐다(`config.py:811`).

**그러나 재판정만으로는 부족하다.** 오프라인 재판정 223쌍(`artifact/
20260806_수정본_재판정/rejudge_result.json`)에서 사용자가 개악으로 지목한
10건 중 **재판정이 원본 승으로 맞춘 것은 5건**이다. 좌우 뒤집기에서 불일치한
것이 4건. (단 이 측정은 judge 팩 v6 **이전** 기준이다 — 2단계에서 재측정한다.)

`:634` `combine_flip_verdicts(fwd, rev, labels2, ["B","A"])` 는 동점 시 수정본을
우선한다. 수정의 목적이 "지적된 것만 고치고 나머지는 보존"이라면 동점은 원본이
안전하다.

### C. 선정 판정 오류 (S104sh7)

판정문은 A(7점) "등 뒤를 향해 허공으로 몸을 날리는 핵심 샷 연출을 지시대로
정확히 구현", B(3점) "인물들이 카메라를 등지고 있어 … 피 묻은 의상이 다른
인물에게 중복 생성"이다.

실물에서 A 는 몸이 지면과 거의 평행하게 떠 있고 B 는 무릎이 굽은 실제 도약
자세다. 그리고 도망자가 카메라를 등지는 것은 역할상 요구사항이지 감점 사유가
아니다 — 역할과 이동 벡터를 잘못 매핑했다.

**판정 팩 v6(`multiroll_judge/6.202608062200`, 8/6 22:00)은 이 축을 이미
갖췄다** — `hard_violations` 1번에 "physically impossible anatomy or staging",
그리고 DIRECTION 축이 "어디를 겨냥하는지 이름을 대라"를 강제한다. S104sh7 은
8/6 09:16 생성이라 v6 를 받지 못했다.

### D. 표현·해석 결함 — 상류에서 의미가 바뀐다

**S88sh3 (차렷 마네킹).** `shot_validator` 가 의미를 바꿨다.

```
shot_extract   : "… 배를 향해 차갑게 서 있는 전경"
shot_validator : "… 배를 향해 고정된 자세로 서 있는 전경"
validator_reason: "'점점 가까워진'이라는 시간 경과 표현을 제거하고
                   한 순간의 상태로 재작성했습니다."
```

시간 표현만 지우겠다고 기록해 놓고 태도("차갑게")까지 자세("고정된 자세로")로
바꿨다. 하류 `shot_staging` 이 이를 증폭한다.

규모: validator 가 **977샷 중 55%(541)의 문구를 고쳤고, 10%(106)에서
`characters` 원소를 늘렸다**(S88sh3 은 `강영란(흰 머리 여자)` 한 사람이
`강영란` + `흰 머리 여자` 둘로 쪼개졌다).

**S79sh1 (조명 기구).** shot_director 의 "밝은 조명이 켜진 선착장"을
shot_staging 이 `key_bg_element` = 선착장 조명, state = On and bright 라는
**물체**로 승격한다. 조명 조건이 소품이 됐다.

**S93sh5 (강영란).** 샷 텍스트는 "**강영란**(흰 머리 여자)의 찰나"인데
프롬프트의 PEOPLE 절은 `must be one of: 김지후(강수리영) … never anyone
else` 다. **샷 텍스트가 지명한 인물을 프롬프트가 금지했다.** 이 배타 조항이
**126샷(49%)** 에 나갔다. 팩 v14 `people_clause_scene`(미커밋)이 이미 제거했다
— "그 목록은 씬 단위이지 이 프레임의 배역표가 아니다 … 샷 텍스트가 목록에 없는
인물을 지명하면 그 사람을 그려라."

**S57sh3 (비석 글자).** `No text, captions, watermarks or annotations
anywhere.` 금지절과 "이전 샷의 비석 글자를 유지하라"가 충돌한다. 게다가 fix 가
이전 샷을 못 보므로 고칠 수도 없다(B축과 같은 뿌리).

### E. 휴대폰 — 코드가 만든 모순

bg_only 샷 33개 중 **27개(82%)** 가 두 절을 동시에 받았다.

- 구 `no_people_clause`: `no living person **or any body part** appears in frame`
- `CARRIED STATE (persist exactly …)`

신체 일부를 금지하면서 소지품은 유지하라고 하니 폰만 남는다. S42sh4 가 그중
하나다(샷 텍스트 = "정인우의 핸드폰 화면에 … 지도앱이 뜬 찰나").

**팩 v13(`still_recipe/13.202608062330/no_people_clause.md`, 미커밋)이 이미
봉합했다** — "물건을 쥔 손은 프레임에 들어간다 … 손 없이 떠 있는 물체가
피하려던 사람보다 더 나쁜 실패다 — 손을 그려라."

화면에 주유기가 비치는 것은 별개다. critique 가 "주유 호스 복원"을 지시했고
마스크 없는 full-frame fix 가 폰 화면 위에 겹쳐 그렸으며, 재판정이 없어 그대로
확정됐다.

### F. 지폐 국적 (S62sh4)

판정 팩 v6 의 ENTITIES 축이 이미 대응한다 — "A banknote has a country and a
denomination … check them one by one." v6 이전 판정이다.

### 새로 발견 — "허공에 떠 있는" 계열

샷 텍스트 자체가 정지 스틸로 성립하지 않는 순간을 지목한다.

| 샷 | 샷 텍스트 | 사용자 판정 |
|---|---|---|
| S1sh7 | "문 안쪽을 향해 몸을 던져 허공에 떠 있는 뒷모습" | 들어가는지 나가는지 이상 |
| S2sh5 | "몸이 문밖 어둠 속을 향해 강하게 꺾인 채 허공에 떠 있는 mid-action" | 코미디 슬랩스틱 |
| S93sh8 | "반쯤 떨어져 내리며 허공에 떠 있는 … 시체" | 사람이 서로 붙은 것 같다 |
| S104sh7 | (판정이 고른 A 후보의 수평 부유) | B 가 훨씬 좋다 |

이미지 모델은 "허공에 떠 있는"을 문자 그대로 부유로 그린다. 지면·손·충격원
같은 지지 근거가 프롬프트에 없다.

## 2. 기각한 추론 — 기록

- **"7월이 좋았던 건 구도·조명·연기 절이 켜져 있어서"** → 아니다. 7월 실행(A,
  `475e5694…`)도 **0/237** 로 똑같이 안 나갔다. 회귀가 아니라 처음부터
  미투입이다.
- **"A축은 prev 경로 계약 결손이다"** → 절반이다. bgfirst 경로 샷은 배경 기하가
  정확한데 인물 삽입이 망가뜨린다. 구도 절을 켜는 것만으로는 이 축에 안 듣는다.
- **"S42sh4 는 VE 결손 때문"** → 아니다. 이름 있는 인물이 아니라 물체를 지지하는
  일반 손이 필요한 샷이고, 구 no-people 절이 그 손을 금지했다.

## 3. 실행 설계

### 1단계 — 유료 0. 다시 그리기 전에 전부 고친다

| # | 수정 | 대상 |
|---|---|---|
| 1 | fix 에 critique 가 본 참조를 전부 넘긴다 | `multiroll_select.py:614` |
| 2 | 재판정 동점 → 원본 유지 | `multiroll_select.py:634` |
| 3 | 재판정에 "지시하지 않은 것이 바뀌었나" 축 추가 | `multiroll_judge` fix_rejudge 헤더 |
| 4 | `has_complex_structure` 판정을 실내 장소로 확장 (LLM 판정, 장소 단위) | lane_plan 계약 |
| 5 | 그 장소의 샷은 인물 삽입에서도 기하가 SOT — "배경선 무시" 철회 | `stage_head`·`sketch_label` |
| 6 | prev 샷에도 콘티 부착 | `shot_conti_light.py:96` |
| 7 | 구도·연기·조명 절 ON | `config.py:742·748·754` |
| 8 | validator — 시간 표현만 지우고 태도·행동은 보존 | `shot_validator` 팩 |
| 9 | staging — 조명 조건과 등기구를 분리 | `shot_staging` 팩 |
| 10 | 공중 동작에 지면·손·충격원 중 하나를 요구 | `still_recipe` 팩 |

**5번과 6번의 관계**: 두 갈래를 각각 막는다. 6번은 콘티가 아예 없는 prev 경로에
기하를 **공급**하고, 5번은 콘티가 있는데도 무시당하는 bgfirst 경로에서 그 기하의
**권위를 지킨다**. 하나만으로는 지목 6샷의 절반씩만 덮인다.

**10번은 금지가 아니라 재료다.** "허공에 떠 있는을 그리지 말라"로 쓰면 모델에게
남는 재료가 줄어 더 나쁜 답이 나온다(2026-08-03 실측). 무엇을 그릴지를 주는
형태 — 발이 딛는 지면, 붙잡은 손, 밀어낸 충격원 중 하나가 화면에 보이게 —
로 쓴다.

**4번의 판별 기준(사용자 확정)**: 좁은 실내 **중 복잡한 구조일 때만**. 가구
없는 방 같은 단순한 공간은 제외한다. 판별은 LLM 이 하고 코드는 스키마·라우팅만
맡는다(어휘·개수 휴리스틱 금지). 장소 단위 선판정인 이유는 lane_plan v4 의
선례다 — "샷 단위 판단은 흔들린다."

**7번 주의**: 플래그를 켜는 것은 사용자 합의를 받았다. 다른 플래그는 임의로
건드리지 않는다.

### 1단계 실행 결과 (2026-08-07, 유닛 1,488 통과)

10건 전부 + 사용자 추가 지시 1건을 구현했다. 팩 3개를 새로 발행했다 —
`multiroll_judge/7` · `still_recipe/15` · `shot_ref_classify/4` ·
`shot_validator/7`.

**사용자 추가 지시 — 손에 든 물건은 손까지 그려질 수 있어야 한다.**
`bgonly_system` 이 "손·손목만 보여도 등장"이라 하면서 동시에 "화면 '속'
이미지만 보이는 샷은 제외"라 해서, **화면을 든 손과 화면 속 이미지가 한
판정에 붙어 있었다.** 분류 팩 v4 에 `handled_by` 를 신설해 둘을 따로 묻는다.
값이 있으면 bg_only 샷이라도 ①그 사람의 참조 **한 장만** 손 전용 라벨로
싣고(전신·의상은 끌어들이지 않는다) ②`THE HAND THAT IS DOING THIS` 절로
손의 정체를 못 박는다. 이름이 맞는 참조가 없으면 아무것도 싣지 않는다 —
엉뚱한 사람의 손보다 낫다.

**4번 판별의 자리.** 새 스텝을 만들지 않고 `shot_ref_classify` 의
`shot_place` 번들에 `confined_structure` 를 얹었다. 그 번들은 이미 샷마다
서브공간과 interior/exterior 를 판정하므로 "이 서브공간이 좁고 복잡한 구조물
안인가"는 자연스러운 후속 물음이고, 같은 팩 승격이라 재실행이 한 번으로 끝난다.
판정 기준은 **좁음 + 실내 + 겹쳐 놓인 조작 장치** 셋이 모두 참일 때만이다 —
가구 없는 방·넓은 홀·복도는 거짓(사용자 확정).

### Codex 리뷰 반영 (BLOCKING 2 · HIGH 4, 전건 재검증 후 수용)

| 지적 | 재검증 | 조치 |
|---|---|---|
| geom 경로가 production 도달 불가 | 맞음 — 호출 0건 | 판별 신설 + 3곳 배선. `conti_targets` 는 **분류를 직접 읽게** 해 배선 누락 자체를 불가능하게 |
| `all_candidates_fail` 을 caller 가 버린다 | 맞음 — `normalize_flip_verdict` 가 3키만 남기고 소비처 0 | `readings` 와 함께 보존·typed 기록. 정순·역순 **둘 다** 선언 시에만 |
| 정책 stamp 미승격 | 지적 시점엔 이미 수정됨 | `fix_rejudge_v2_orig_on_tie` |
| judge 팩 전역 승격이 씨드·배경까지 stale | 맞음 — 두 스텝이 기본 selector 를 지문에 접음 | 전역은 v6 로 되돌리고 `STILL_JUDGE_PACK_VERSION="7"` 신설 |
| `_load_optional` 이 결손을 삼킴 | 맞음 | 도입 팩 이상에서는 fail-closed |
| `camera_frame` 이 outer hash 에 없음 | 맞음 | 추가 + `recipe_geom_authority_pack` |

★교훈: **인자를 만든 것과 그 인자가 프로덕션에 닿는 것은 다르다.** 순수 함수
테스트는 전부 초록인데 실행 경로는 기본값만 탔다. 이후 이런 축은 호출부를
소스로 핀하는 테스트를 함께 둔다.

### Codex 3차 리뷰 (BLOCKING 1 · HIGH 2, 전건 재검증 후 수용)

| 지적 | 재검증 | 조치 |
|---|---|---|
| `invalidate_downstream` 이 tombstone 을 안 남겨 archive 복원이 stale 을 부활 | 맞음 — `clear_checkpoint` 는 marker 를 쓰는데 **상류 force 가 하류를 지우는 경로만** 빠졌다 | 같은 marker 를 남기고, `EntityT2iStep` 도 현재 대상 밖 완료분을 폐기 — **두 층 다** |
| resume 경로에서 `needs_reshoot` 누락 | 맞음 — fresh 두 곳에만 넣었다 | 재개 두 경로에 추가, 테스트로 잠금 |
| optional stem 도입 버전이 문자열 비교 | 맞음 — `"10" >= "7"` 은 False | `_selector_num` 으로 숫자 비교 |

★**이것이 내가 겪은 `entity_t2i` 오염의 진짜 기전이다.** 나는 "아카이브 복원과
새 산출의 경합"으로 봤는데 **경합이 아니라 결정론적 stale 부활**이었다 —
상류 force → 하류 CP 삭제(표식 없음) → 다음 로드가 archive 복원 →
`EntityT2iStep` 이 현재 대상과 대조 없이 전부 `done` →
`failed_count = total - len(done)` 음수(154/97/-57) → 그 partial 이 entity
sync 의 stale cleanup 을 막아 UniqueViolation → 파이프라인 20분 정지.
**원인을 "타이밍 문제"로 뭉뚱그리면 재발을 못 막는다.**

### shot_staging v19 검증 (2026-08-07)

`lighting_mood`(빛의 성질)와 `key_bg_elements`(화면에 보이는 물건)의 관할을
갈랐다. 재실행 산출 S79sh1:

- `lighting_mood`: "Bright nighttime illumination … **without placing any
  lighting fixture in frame**"
- `key_bg_elements`: 어선 · 부두 — **조명 기구 없음**

이전에는 선착장 조명이 `key_bg_element`(상태=켜져 밝음)로 올라가 완성본에
촬영용 투광등이 화면 한가운데 섰다. 상류에서 끊겼다.

★팩을 만들 때 **최신 판을 먼저 확인할 것.** `ls -v` 의 끝 두 줄만 보고 v9 를
최신으로 오인해 v10 을 만들었는데, 실제 최신은 v18 이었고 번호까지 충돌했다
(기존 `10.202605141617`). v18 에는 내가 모르던 개선("근거 없으면 특정
색·광원·날씨를 지정하지 말 것")이 있어 그대로 실렸다면 **되돌릴 뻔했다.**

### 2단계 — 생성 0, 판정만 (2026-08-07 실행 완료)

도구 `backend/reselect_v6.py` — 심판 4종(Opus·Sol·Gemini·Qwen)에게 **같은 팩·
같은 후보·같은 참조**를 주고 동시에 물었다. 갤러리
`artifact/20260807_선정재판정_v6/`. 32콜, 이미지 생성 0.

**절반이 선택 문제였다.** 팩 v6 만으로 4/8 이 뒤집혔다 — S62sh4(A=달러 →
B=**한국 원화**, 실물 확인) · S18sh5 · S88sh6 · S76sh5.

안 뒤집힌 4건 중 S15sh5 는 넷 다 같은 후보를 골랐고 둘이
`all_candidates_fail` 을 선언했다 — **후보 전체 실패, 생성 문제**다.

**★ 심판은 서로 보완적이고, 현행 조합에 물리 축이 없었다.**

S104sh7 에서 **Gemini 만** "걷는 포즈를 가로로 눕혀 공중에 띄운 물리적으로
불가능한 연출"을 하드 위반으로 걸었고, Opus·Sol·Qwen 셋은 **부유를 한 줄도
쓰지 않고 방향만** 봤다. 그런데 S62sh4 에서는 Gemini 만 틀렸다.

| 심판 | 잘 보는 축 | 놓치는 축 |
|---|---|---|
| Gemini | 물리·자세 성립 | 엔티티 정체(지폐 국적) |
| Opus·Sol | 엔티티·인물 중복·발명 | 물리적 부유 |
| Qwen | 관찰 서술은 상세 | 결론이 흔들린다 |

가설은 **v6 가 물리 불가를 `hard_violations` 예시 목록에만 적고 관찰 강제
축으로 만들지 않았다**는 것이었다. 팩 v7 로 PHYSICS 를 네 번째 축으로 올려
검증했다 — **맞았다.** v6 에서 부유를 언급조차 않던 Sol 이 v7 에서 "확인
가능한 도약 지지점이나 추진 동작이 없다"고 관찰하고 하드 위반으로 걸어
**S104sh7 이 사용자 정답 B 로 뒤집혔다.** S42sh4 도 손 없는 B → 손 보이는
A 로 바뀌었다. 넷 다 이제 물리를 관찰한다.

**미해결 — S62sh4 회귀 1건.** v6 에서 옳게 B(원화)였는데 v7 에서 A(달러)로
돌아갔다(Sol 이 바뀌었다). 표본 1회라 판정 변동과 구분되지 않는다. 반복
측정으로 확인할 것. ★측정 도구 주의: `reselect_v6.py` 는 심판을 항상 합산하는데
프로덕션은 **조건부 이중**(1심이 0.30 이상 앞서면 2심 생략)이다. 8샷 전건에서
두 방식의 승자가 일치함을 확인했지만, 표를 읽을 때 이 차이를 기억할 것.

### 3단계 — 카나리 20샷 → 육안 → 전체

| 묶음 | 샷 |
|---|---|
| 자동차 6 | S13sh3 · S13sh4 · S15sh3 · S15sh5 · S18sh1 · S18sh5 |
| 부유·폰 4 | S42sh4 · S1sh7 · S2sh5 · S93sh8 |
| i2i 개악 5 | S73sh4 · S74sh8 · S98sh6 · S97sh5 · S61sh12 |
| 표현 4 | S93sh5 · S88sh3 · S79sh1 · S57sh3 |
| 판정 1 | S104sh7 |

육안 확인 후 전체 253장. 8·9번은 상류 스텝이라 재실행 범위가 넓다 — 카나리
결과를 보고 전체 실행 시점에 함께 적용한다.

## 4. 검증 계약

- 1단계 각 항목은 유닛으로 잠근다. 현재 1,456건 통과가 기준선이다.
- **TDD 는 deterministic 영역만.** 프롬프트·이미지 품질은 실험 반복과 육안으로
  판정한다. 테스트 통과를 동작 검증으로 보고하지 않는다.
- 2단계 결과는 지목 25건 각각에 대해 "생성/선택/수정 중 어느 층의 문제였나"로
  적는다.
- 3단계는 수정 전 샷과 나란히 놓고 본다. 개악 여부를 대조 없이 판정하지 않는다.

## 4-1. 재실행에서 실제로 겪은 것 (2026-08-07)

**`_config_hash` 를 정의하면 저장 쪽 짝을 맞춰야 한다.** `shot_validator` 에
`_config_hash()` 를 추가했더니 **방금 성공한 실행이 다음 순간 contract drift 로
막혔다** — 전체 재실행이 첫 스텝에서 멈췄다.

- 저장은 `data` 에 `config_hash` 가 없으면
  `compute_config_hash(project_config)` 로 떨어진다(`step_runner.py:388`).
- 검사는 step-local `_config_hash()` 로 한다(`:1533`).
- 짝이 안 맞으면 저장값과 검사값이 영원히 다르다.

짝을 맞추는 방식은 둘이다 — `_execute` 반환에
`"config_hash": self._config_hash()`(40개 스텝) 또는 `save_checkpoint`
override(`image_steps.SceneImagePipelineStep`). ★**이 프로젝트는 같은 결함을
이미 겪고 고쳤다**(image_steps 주석: "저장/비교 비대칭으로 … 원리적으로
작동하지 않았다"). 선례를 보지 않고 같은 유형을 다시 만들었다.

복구는 재실행이 아니라 **CP 의 hash 만 갱신**으로 했다 — 산출(113씬·526샷)은
멀쩡했다. 계산값이 백엔드 로그의 `current` 와 일치함을 먼저 확인하고, 백업 뒤
갱신했다.

**validator 팩 v7 대조 결과 — 지목 4샷 전부 의도대로.**

| 샷 | 이전 | 이후 |
|---|---|---|
| S88sh3 | "**고정된 자세로** 서 있는" | "**차갑게** 서 있는" (사유에 "태도는 그대로 유지함") |
| S1sh7 | "몸을 던져 **허공에 떠 있는**" | "**한쪽 발로 지면을 힘껏 차며** … 몸이 기운" |
| S2sh5 | "**허공에 떠 있는** mid-action" | "무언가에 **당겨져 두 발이 바닥에서 막 떨어지고**" |
| S93sh8 | "반쯤 떨어져 내리며 **허공에 떠 있는**" | "**하반신만 걸친 채** 상체가 꺾여 **매달려 있는**" |

구조 지표는 수정 비율 55%→53%(거의 불변), 두 판의 결과가 다른 shot 60%.
**덜 고치게 만든 게 아니라 다르게 고치게 만들었다.** 60% 중 얼마가 개선인지는
이 수치만으로 말할 수 없다 — LLM 판정의 자연 변동이 섞여 있다.

**판정층은 같은 입력에도 흔들린다.** S104sh7 을 4회 돌려 정답은 1회뿐이었고,
S62sh4 는 개별 판정이 같은데 합의가 B·B·A 로 갈렸다. 확실한 것은 v7 이 넷 다
물리를 **관찰하게** 만든 것까지다. 3롤 선정은 지금 flip 없이 1회이므로
(`judge_flip` 은 A/B 2택1·fix-rejudge 전용) flip 적용이 흔들림을 줄이는지
`backend/judge_variance.py` 로 측정 중이다 — 결정은 그 수치를 보고 한다.

## 5. 재실행 주의

- `mode=force` 는 하류 체크포인트를 지운다. 표적 재실행은 `resume`.
- 스텝 스테일 락은 force 로 회수되지 않는다. 3600s 경과 후 resume 1회.
- `scene_still` sync 는 트리거 스텝 5개 직전에만 돈다 — "실패 → 재개" 순서면
  건너뛴다.
- 백엔드 재기동은 코드를 최신화하면서 **설정도 `.env` 파일 상태로 되돌린다.**
  E2E 전에 기동 시각과 플래그 실효값을 확인한다.

## 6. 이번 세션에서 이미 끝난 것 (재작업 금지)

- `scene_director` 인덱스 드리프트 — 원인 확정·수정·검증 완료(113/113)
- outlook 참조/씬 키 결함 2건 — `segment_key.py` 공용 모듈로 통합
- 표본 8샷 재생성 · 갤러리 `artifact/20260807_scene_director_drift/`
- Qwen 3.8 Max VLM 심판 실험 · 갤러리 `artifact/20260807_qwen38max_vlm/`
- 유닛 1,456건 통과 — 전부 미커밋, 브랜치 `feat/prompt-diet`
