# Image Generation Pipeline — 이미지 생성 상세

> 2026-04-15 보정: v10codex 기준. 참조 이미지 우선순위 명문화 + shot_dependency_t2i 소비 경로 변경 반영.

## 참조 이미지 우선순위 (결정적)

`scene_image_pipeline` 씬 이미지 생성 시 참조 이미지는 다음 우선순위로 구성한다:

1. **set_design background** (사전 생성된 장소 배경, conditional)
2. **previous shot background** (exact_background 또는 atmosphere_reference)
3. **composite reference** (인물 얼굴 + 아웃룩)
4. **base face reference** (composite 없는 경우 fallback)
5. **prop reference** (소품)
6. **state variant reference** (죽은/부상 인물 상태 variant)

### 소비 경로
- 배경 참조 체크포인트는 `shot_dependency_t2i` 우선, 없으면 `shot_dependency` fallback
- `shot_dependency_t2i`는 자체 manifest에만 저장 (타 step manifest 직접 write 금지)
- `scene_consistency`의 character_state + exact_background 조합 시 state variant 스킵 → 배경에서 유지


## 1. 이미지 생성 흐름 개요

```
ref_image_gen (엔티티별 참조 이미지)
    ↓
composite_image_gen (인물+아웃룩 합성)
    ↓
character_state_variant (죽은/부상 인물 상태 변형)
    ↓
scene_image_pipeline (씬 이미지 생성)
    ├── labeled_refs 구성 (참조 이미지 + 라벨)
    ├── _build_final_scene_prompt (T2I → 최종 영어 프롬프트)
    ├── Gemini Image 생성
    ├── 이미지 검증
    └── 최종 선택
```

## 2. 참조 이미지 시스템 (labeled_refs)

씬 이미지 생성 시 Gemini에 전달되는 참조 이미지 목록:

| 순서 | 유형 | 라벨 패턴 | 용도 |
|------|------|-----------|------|
| 1 | set_design 배경 | `"background set for L01 (base)"` | 사전 생성 배경 참조 |
| 2 | 이전 샷 배경 | `"previous shot at same location (SAME ROOM) — ..."` | 배경 연속성 |
| 3+ | 인물+아웃룩 | `"C01O02 wearing casual outfit"` | 인물 얼굴+의상 |
| N | 소품 | `"P03 object: old photograph"` | 소품 외형 |
| N+1 | state_variant | `"C02O03 — dead state reference"` | 죽은 인물 상태 |

### 참조 이미지 주입 순서 (image_service.py)
1. `set_design_bg_map` → labeled_refs.insert(0, ...)
2. `best_prev_bytes` (이전 샷 이미지) → labeled_refs.append(...)
3. `_resolve_refs_for_prompt()` → 인물/소품/아웃룩 참조

## 3. 배경 참조 라벨 구성 (ref_usage 기반)

```python
if ref_usage == "exact_background":
    # 같은 방 — 배경 그대로
    label = "previous shot at same location (SAME ROOM) — use this background as-is."
    label += f" {ignore_elements}"  # "Ignore the standing woman..."
    label += f" Keep: {', '.join(keep_elements)}"  # "Keep: dead body, blood pool"

elif ref_usage == "atmosphere_reference":
    # 다른 방/앵글 — 분위기만
    label = "previous shot at same location (DIFFERENT ROOM/ANGLE) — use ONLY as style..."

else:
    # fallback
    if has_fixed_char:
        label = "...Ignore standing/moving people. Keep motionless figures..."
    else:
        label = "...Ignore all people..."
```

## 4. _build_final_scene_prompt 라벨 매칭 순서

```python
# 1. "outfit appearance" — 의상 단독
# 2. "wearing" / "outfit" — 합성 이미지
# 3. "face" / "character" — 인물 참조 (라벨 유지)
# 4. "prop" / "object" — 소품
# 5. "previous shot" + "same room" ★ — exact_background (배경 as-is + keep/ignore 보존)
# 6. "previous shot" / "previous scene" — atmosphere 참조
# 7. "background" / "location" — set_design 등 일반 배경
# 8. else — 라벨 그대로 전달
```

**주의**: 5번이 7번보다 먼저 매칭되어야 exact_background의 keep/ignore 지시가 보존됨.

## 5. 죽은 인물 처리 — 3가지 시나리오

### 시나리오 A: 첫 번째 샷 (배경 참조 없음)
```
Shot 1: 시체 최초 등장
  → state_variant 참조 이미지 주입 (dead pose ref)
  → T2I에 C02O03 + 상세 자세 묘사
  → Gemini가 참조 이미지 기반으로 시체 렌더링
```

### 시나리오 B: 후속 샷 (exact_background + scene_consistency)
```
Shot 2: 같은 씬, 같은 방
  → dep_detail_map: ref_usage="exact_background"
  → _shots_with_fixed_char: (12, 2) 확인
  → best_prev_bytes 존재 확인
  → state_variant 스킵 (배경에 이미 시체 포함)
  → _skip_char_sids에 C02 추가
  → 배경 라벨: "Keep: the dead middle-aged woman slumped against the wall"
  → Gemini가 배경에서 시체 유지, 새 인물만 추가
```

### 시나리오 C: 후속 샷이지만 배경 이미지 없음
```
Shot 2: exact_background이지만 best_prev_bytes=None (Shot 1 생성 실패)
  → best_prev_bytes 조건 미충족
  → state_variant 유지 (기존 방식으로 처리)
  → 시체 소실 방지
```

## 6. 인물 참조 스킵 로직

```python
_skip_char_sids = set()      # 얼굴 참조 스킵
_state_variant_sids = {}     # char_sid → {key, state}

# 1) back_to_camera / over_shoulder → 얼굴 스킵
# 2) gaze_target: dead/severely_injured/unconscious
#    → state_variant 있으면 제공, 없으면 스킵
# 3) gaze_target: closed → 얼굴 스킵
# 4) exact_background + fixed_char → state_variant 제거, 배경으로 대체
```

## 7. _resolve_refs_for_prompt 흐름

```python
def _resolve_refs_for_prompt(t2i_prompt, visible_entities, scene_ref_image_map, 
                              entity_lookup, skip_char_sids, state_variant_sids):
    # T2I에서 C##O## 패턴 스캔
    for match in re.finditer(r'(C\d{2,3})(O\d{2,3})', t2i_prompt):
        char_sid = match.group(1)
        
        # 1) state_variant 우선 (죽은 인물 참조)
        if char_sid in state_variant_sids:
            labeled_refs.append(("C02O03 — dead state reference", variant_bytes))
            continue
        
        # 2) skip (back_to_camera, closed 등)
        if char_sid in skip_char_sids:
            continue
        
        # 3) 정상: composite ref → face ref fallback
        labeled_refs.append(("C01O02 wearing ...", composite_bytes))
```

## 8. Image N 인덱싱

labeled_refs에 번호를 부여하고, T2I의 `C##O##`을 `"the character from Image N"`으로 치환.

```python
# T2I: "C01O02 stands in the left foreground"
# → "the character from Image 3 stands in the left foreground"
```

이렇게 Gemini가 어떤 참조 이미지가 어떤 인물인지 정확히 알 수 있음.

## 9. scene_image_pipeline sub_steps

| Sub-step | 모델 | 역할 |
|----------|------|------|
| prompt_translation | gpt-mini | T2I 한국어→영어 변환 + 참조 이미지 역할 지시 |
| scene_t2i_gen | gemini-image | 이미지 생성 (참조 이미지 + 변환된 프롬프트) |
| scene_t2i_validation | gpt | 생성 이미지 품질 검증 (score + pass/fail) |
| prompt_sanitize | gpt | 안전 필터 회피용 프롬프트 순화 |
| angle_recommend | gpt | 카메라 앵글 추천 |
| fal_angle_apply | fal-ai | fal.ai API로 앵글 적용 (FAL_AI_ENABLED 시) |
| final_select | gpt | 최종 이미지 선택 |

## 10. 안전 필터 대응

### scene_consistency 레벨
```
1차: Gemini Pro (원본 텍스트)
2차: Gemini Pro (순화 텍스트 + 영화 촬영 프레이밍)
3차: GPT fallback (순화 텍스트)
```

### 순화 매핑 (한국어 + 영어)
```python
_SAFETY_REPLACEMENTS_KO = [
    ("피묻은", "붉은 물감이 묻은"),
    ("시신", "쓰러진 인물"),
    ("죽은 ", "세상을 떠난 "),
    ...
]
_SAFETY_REPLACEMENTS_EN = [
    (r"\bblood[- ]?stained\b", "dark-red-stained"),
    (r"\bcorpse\b", "motionless figure"),
    ...
]
```

### scene_image_pipeline 레벨
- Gemini 생성 시 SAFETY 차단 → prompt_sanitize로 순화 후 재시도
- 최대 3회 시도
