# shot-more 재설계 — 한 샷 = 한 찰나, 카메라 플로우 기반

작성일: 2026-04-15
브랜치: `shot-more`
베이스 커밋: `df17084` (scene_consistency + 죽은 인물 배경 통합 직후)

---

## 1. 문제 정의

현재 `shot_extract`는 씬당 샷 수를 의식적으로 줄이면서, LLM이 **연속된 행동을 한 샷에 몰아넣는** 경향을 보인다.

**실패 사례 (S12 민숙 사망 발견 씬)**
- "커튼을 잡고 → 당기고 → 시체를 발견하는" 세 동작이 한 샷 description에 들어감
- scene_detail T2I에서도 이 때문에 "standing rigid and frozen after pulling them open"처럼 여러 시점이 한 프롬프트에 뭉쳐짐
- 결과: 이미지 생성 시 카메라가 "어느 순간"을 찍는지 모호해지고, 자세/구도가 튀거나 혼합됨

**근본 원인**
1. `shot_extract` 프롬프트는 "한 샷 = 한 정지 순간"을 말하지만, **샷 갯수 제한** 때문에 LLM이 규칙을 우회함
2. `shot_staging`은 각 샷의 카메라를 **독립적으로** 결정 — 씬 관통 연속성 개념이 없음
3. `scene_detail`은 구조화 입력을 받지만 **입력 종류가 너무 많아** LLM이 모든 규칙을 동시에 만족시키지 못함

---

## 2. 설계 원칙

1. **한 샷 = 한 찰나 (셔터 1/1000초)**. 시간 슬롯을 극소화. 행동의 before/during/after 중 하나만.
2. **많이 추출, 적게 생성**. shot_extract는 잘게 쪼개고(현재 대비 2~3배), shot_selection이 이미지 생성 대상만 선별. 비선택 샷은 스토리 문맥/카메라 플로우용으로 보존.
3. **씬 단위 카메라 연속 흐름**이 먼저, 샷 단위 구도는 여기서 파생. 독립적 샷 결정 금지.
4. **고정/변화 이분법**은 기존 구조 유지 — 고정은 `scene_consistency`, 변화는 `shot_staging`의 샷별 결과. 단 shot_staging은 "앞 샷과 달라진 축 하나만 강조" 원칙 추가.
5. **비선택 샷도 UI에 표시**. 이미지 없이 설명 카드만 — 사용자가 선택 토글 가능.
6. **전체 하드코딩 금지, 시나리오 의존 프롬프트 금지**.

---

## 3. 파이프라인 변경 (order 기준)

### 현재 (~df17084)
```
15    shot_extract
15.5  shot_selection
16    scene_director
16.5  shot_director           (변형 캐릭터 ID 판정)
17.1  shot_cinematography     (거의 죽은 단계)
18.1  shot_dependency
19.5  shot_staging            (샷별 DP 연출, 독립 결정)
19.7  set_design
19.9  scene_consistency       (씬 내 교차 샷 고정 요소)
20    scene_detail            (최종 t2i_prompt 합성)
20.5  shot_dependency_t2i
```

### 재설계
```
15    shot_extract            [프롬프트 v10 — 찰나 극단화]
15.5  shot_selection          [프롬프트 v2 — "이미지 생성 가치" 기준]
16    scene_director
16.5  shot_director           (유지)
17.05 scene_camera_flow       [NEW — 씬 단위 카메라 연속 흐름, Gemini Pro]
17.1  shot_cinematography     [DEPRECATED — 의존성 끊고 보존만]
18.1  shot_dependency
19.5  shot_staging            [scene_camera_flow 입력 추가]
19.7  set_design
19.9  scene_consistency       (유지)
20    scene_detail            [프롬프트 압축 + 찰나 강화]
20.5  shot_dependency_t2i
```

**신규 1개 (`scene_camera_flow`) + deprecate 1개 (`shot_cinematography`) + 프롬프트 업데이트 5종**.

---

## 4. 신규: `scene_camera_flow`

### 책임
씬 관통 카메라의 연속 이동 경로를 설계하고, 선택된 샷 각각을 이 경로 위의 위치로 매핑한다. 샷 단위 DP 연출(`shot_staging`)은 이 경로를 참조해 개별 카메라를 파생시킨다.

### 의존성
- `depends_on`: `shot_selection`, `scene_director`, `shot_extract`, `beat_extract`
- `fan_out`: False (씬 단위)
- `order`: 17.05
- `default_model`: `gemini-pro`
- `applicability`: `always`

### 입력
- 선택된 샷 리스트 (description, based_on_beat, characters)
- 씬 텍스트 원문
- scene_director 결과 (씬 엔티티 배정)
- beats (상태 변화 축)

### 출력 스키마 (개요)
```json
{
  "scenes": [
    {
      "scene_index": 12,
      "flow_summary": "한국어 1~2문장 — 씬 관통 카메라의 전체 이동 묘사",
      "flow_stages": [
        {
          "stage_index": 1,
          "stage_label": "establishing",
          "camera_position": "English — 카메라의 물리적 위치/높이/거리",
          "camera_motion": "static|pan|tilt|dolly_in|dolly_out|track|handheld|crane",
          "visual_focus": "English — 이 단계에서 카메라가 바라보는 대상",
          "transition_to_next": "English — 다음 단계로 어떻게 이어지는가 (컷/이동/회전)"
        }
      ],
      "shot_assignments": [
        {
          "shot_index": 1,
          "stage_index": 1,
          "flow_position": "start|mid|end|transition",
          "inherits": ["camera_position", "visual_focus"],
          "deviation_note": "이 샷만의 미세 조정 — 없으면 빈 문자열"
        }
      ]
    }
  ]
}
```

### 프롬프트 방향
- "당신은 영화 촬영 감독입니다. 씬 전체를 관통하는 카메라의 연속 움직임을 설계하세요."
- 단일 샷 설계 금지, 반드시 "이동 경로" 관점
- 각 단계는 다음 단계로 **어떻게 이어지는지** 필수 명시
- 선택된 샷을 경로 위에 배정 (stage_index + flow_position)
- 엔티티 ID 금지, 보통명사 + 인물 이름

### `shot_staging`의 변화
- 입력에 `scene_camera_flow` 체크포인트 추가
- 프롬프트 규칙 추가: "이 샷의 카메라는 scene_camera_flow의 stage_[N]에서 파생됩니다. 독립적으로 결정하지 말고 플로우의 camera_position과 visual_focus를 계승하되, 이 샷만의 미세 조정만 덧붙이세요."
- 추가 규칙: "앞 샷과 달라진 축이 무엇인지 명시하고, 그 축 하나만 강조하세요."

---

## 5. 프롬프트 변경

### 5.1 `shot_extract` v10

**핵심 변경**
- 샷 갯수 상한 제거 (기존: 프롬프트에는 없지만 LLM이 학습된 경향)
- "한 beat 안에서도 여러 정지 순간이 시각적으로 구분되면 각각 별개 shot으로 추출"
- 연속 동작 금지 규칙을 **예시 5개 이상**으로 강화 (나쁜 예 / 좋은 예)
- 시간 슬롯 극소화 문구: "이 샷이 담는 시간은 셔터가 열린 1/1000초. 그 전후 1초는 다른 샷."
- before/during/after 분해 권장 예시 추가
- "설명에 'and then', '~하고 나서', '~하자', '~한 뒤', '~하며' 등의 시간 연결어 절대 금지"

**예시 추가 (일반화)**
```
나쁜 예: "A가 커튼을 당기며 시체를 발견하는 순간"  (당김 + 발견 = 2순간)
좋은 예 1: "A의 손이 커튼 자락을 움켜쥔 순간, 팔은 아직 뻗기 전"
좋은 예 2: "커튼이 반쯤 열린 상태에서 A의 시선이 침대 쪽으로 향하는 순간"
좋은 예 3: "커튼이 완전히 열린 뒤, A가 시체를 본 직후 숨이 멎은 순간"
```

### 5.2 `shot_selection` v2

**핵심 변경**
- 기준 재정의: "서사적/시각적 중요도" → "**이미지로 생성할 가치가 있는가**"
- 판단 기준 추가:
  1. 이 순간을 이미지로 남겨야 독자가 씬을 이해할 수 있는가
  2. 카메라 플로우의 핵심 전환점인가 (start/end/중대 전환)
  3. 다른 선택된 샷과 시각적으로 충분히 구별되는가 (중복 회피)
  4. 연속된 미세 순간 중 대표 1개만
- 비선택 샷은 **맥락/플로우 설계에 활용** — "버리는 것 아님" 명시
- 선택 개수는 비율 기반 (예: 전체 샷의 30~50%), 고정 N이 아님

### 5.3 `shot_staging` v6

- 입력 섹션에 "[씬 카메라 플로우]" 블록 추가
- "독립 카메라 결정 금지" 규칙
- "이 샷이 앞 샷과 달라진 축 하나를 명시하세요 (`delta_axis`)"
- 나머지(character_angles/gaze_target/perception_mode 등)는 유지

### 5.4 `scene_detail` 프롬프트 압축

- 현재 user_prompt 구성 순서가 "summary → shot → beat → dep → 순간고정 → 고정요소 → 엔티티 → 아웃룩 → 촬영감독 → 인물각도 → 변형관계 → 규칙 → 씬텍스트"로 11블록. LLM이 혼란
- 재구성 (우선순위):
  1. **순간 고정 1문장** (이 샷은 단일 찰나)
  2. **Shot description + beat**
  3. **shot_staging (camera/character_angles)** — 가장 중요한 시각 지시
  4. **scene_consistency 고정 요소** — 수정 금지
  5. **엔티티/아웃룩** — 사용 가능 ID
  6. **규칙 (bare ID만)**
  7. **씬 텍스트 원문** (최하단)
- 삭제: "촬영 감독 추천 기법" 레거시 블록 (shot_cinematography deprecated)
- 강화: "t2i_prompt에 2개 이상의 동작이 들어가면 안 됨. 'and then', 'after', 'while ~ing' 금지."

### 5.5 `beat_extract`

- **변경 없음**. beat가 거칠어 샷이 거칠다고 단정하기 전에 shot_extract 단독 개선 효과를 먼저 측정.
- 2단계 필요 시 측정 후 검토.

---

## 6. DB 스키마 변경

### `scene_still` 테이블
추가 컬럼:
- `is_selected BOOLEAN DEFAULT TRUE` — shot_selection 결과
- `image_generated BOOLEAN DEFAULT FALSE` — 이미지 파이프라인 완료 여부
- `status` (기존): `pending|completed|stale` — `stale`은 force 재실행 시 이전 레코드에 부여

**마이그레이션**
- Alembic: 두 컬럼 추가
- 기존 데이터: `is_selected=TRUE`, `image_generated=(image_asset에 row 있으면 TRUE, 아니면 FALSE)`

### `_sync_checkpoints_to_db` 변경
- **전체 shot을 DB에 저장** (현재는 선택된 것만)
- 선택/비선택은 `is_selected` 플래그로 구분
- force 재실행 시 이전 shot_index와 겹치지 않는 row → `status='stale'` 표시 (hard delete 아님, 생성된 이미지 파일 보존)
- `image_asset` 연결은 is_selected=TRUE인 row만

---

## 7. API 변경

### 엔드포인트
- `GET /api/v1/projects/{pid}/episodes/{eid}/scene_stills` (또는 기존 경로)
  - 쿼리: `include_unselected=true|false` (기본 `true`)
  - 응답: `is_selected`, `image_generated`, `status` 포함
- `POST /api/v1/.../shot_selection/toggle` — 기존 toggle 로직 재활용, 하지만 DB에도 is_selected 반영

---

## 8. Frontend 변경

### `SceneVariationCard.tsx`
- prop에 `is_selected: boolean`, `image_generated: boolean` 추가
- `is_selected=false`일 때:
  - 카드 전체 `opacity: 0.5`, `border: dashed`
  - 이미지 영역: "이미지 생성 안 함" 플레이스홀더
  - T2I 프롬프트 영역 숨김 또는 접힘
  - 편집 비활성화 (읽기 전용)
- 우상단에 선택 토글 버튼: "선택됨 ✓" / "선택 안 됨 +"

### `EpisodeDetail.tsx`
- 씬 내 샷 리스트에 선택된 샷과 비선택 샷을 **모두** 렌더
- 시각 구분: 선택된 샷 사이사이 비선택 샷이 얇은 회색 카드로 끼워짐
- 선택/비선택 토글 시 API 호출 → 하위 체크포인트 invalidate 경고

### 필터
- 상단 토글: "모든 샷 보기" / "선택된 샷만"

---

## 9. 엣지 케이스 / 리스크

| 케이스 | 처리 |
|---|---|
| shot_extract 재실행 시 shot_index 변경 | 이전 scene_still을 `status='stale'`로 표시. 이미지 파일 보존. UI 스킵 |
| 사용자 편집한 샷 (`_user_edited`) | shot_extract force 재실행 시 **경고 후 유지 불가** — 현재 한계 수용. 스냅샷 저장 권장 |
| 샷 수 2~3배 증가 → 토큰 비용 | shot_extract만 증가, 하위는 선택된 샷만. 번들링 유지로 완화 |
| scene_consistency "2+ 샷" 조건이 너무 자주 충족 | 조건은 **선택된 샷 기준** 유지 (현재 코드). 재확인 |
| scene_camera_flow가 선택된 샷 0개인 씬 | 스킵 (빈 결과 기록) |
| shot_cinematography deprecate 후 scene_detail fallback | scene_detail의 staging 없을 때 cine fallback 경로를 **"최소 기본값"으로 대체**. cine 체크포인트가 없어도 동작 |
| 기존 프로젝트 DB | 마이그레이션 시 is_selected=TRUE, image_generated는 image_asset 유무로 판정 |
| 비선택 샷 토글 → 선택 전환 | scene_consistency/shot_staging/scene_detail 등 하위 invalidate 필수 (기존 toggle 로직 재활용) |

---

## 10. 테스트 계획

### Phase 1: shot_extract 단독
- v16 (47cf90e9) / EP1 (483a7759) / S12 집중
- shot_extract force 실행 → 샷 수 2~3배 증가 확인
- 샷 description에 연속 동작 포함 여부 수동 검증

### Phase 2: scene_camera_flow
- force 실행 → S12 플로우 출력 확인
- 단계 수, 단계 전환, shot_assignments 적절성

### Phase 3: shot_staging (플로우 파생)
- force 실행 → 앞뒤 샷 카메라 연속성 확인
- delta_axis 필드 확인

### Phase 4: scene_detail + 이미지
- force 실행 → S12 shot 개수대로 T2I 생성
- 이미지 품질: 단일 순간 포착 / 구도 연속성 / 죽은 인물 고정

### Phase 5: UI
- 선택/비선택 카드 구분 표시
- 토글 동작

### Phase 6: 새 프로젝트 E2E
- 새 프로젝트 생성 → 분석 전체 실행
- 샷 수, 선택률, 이미지 품질 종합 평가

---

## 11. 구현 순서

1. **설계 문서 (이 파일)** ← 현재
2. **shot_extract v10 프롬프트** — 단일 변경, Phase 1 테스트 가능
3. **scene_camera_flow step 신설**
4. **shot_staging 프롬프트에 플로우 반영**
5. **shot_selection v2 프롬프트**
6. **scene_detail 프롬프트 압축**
7. **shot_cinematography deprecate**
8. **DB 마이그레이션 + _sync_checkpoints_to_db 확장**
9. **API 응답 확장**
10. **Frontend 비선택 샷 카드**
11. **Codex + Claude 병렬 리뷰 → 수정**
12. **Phase 1~6 테스트**
13. **커밋 + 메모리 업데이트**

각 단계 종료 후 최소 1회 Codex 리뷰를 거친다.

---

## 12. 결정 로그

- **scene_camera_flow는 신규 step** (shot_cinematography 리퍼포즈 X) — 체크포인트 스키마 호환성/책임 분리
- **shot_cinematography는 deprecate만**, 코드/체크포인트는 보존 — 롤백 가능성
- **beat_extract는 이번 스코프 제외** — shot_extract 단독 효과 먼저 측정
- **shot_index는 stable ID 미도입** — force 재실행 시 soft-stale로 충분
- **선택/비선택 샷 모두 DB 저장** — UI 요구
