# 중요 설계 결정 사항

> **절대 규칙: LLM에 전달하는 데이터(시나리오 텍스트, 씬 텍스트, description 등)를 코드에서 절대 자르지 마라 ([:400], [:500] 등 금지). 어떤 경우에도 원문 전체를 전달할 것. 이전에 여러 곳에서 [:300]~[:600]으로 자르는 버그가 있었으며 전부 제거함. 앞으로도 절대 추가 금지.**

> **Gemini 3.1 Pro는 긴 문맥 분석에 GPT-5.4보다 우수.** outlook_extraction(119씬) 테스트: GPT-5.4는 44씬 누락(35% 손실), Gemini Pro는 실질 0개 누락. scene_director, outlook_extraction 등 전체 씬을 한번에 분석하는 단계는 반드시 Gemini Pro 사용.

## 1. scene_director — 전체 씬 일괄 분석 (씬별 X)

scene_director는 **반드시 전체 시나리오의 모든 씬을 한번에** LLM에 전달해야 한다.

**이유**: 씬별로 개별 분석하면 앞쪽 씬의 맥락(접속/빙의 상태)을 알 수 없어서
소울라이드/빙의/원격접속 중인 인물을 물리적 존재로 오판함.

**사례**: 씬#3에서 강의원이 동녘 몸에 소울라이드 접속 → 씬#8에서 강의원이 대사를 함 →
씬#8만 보면 강의원이 물리적으로 있는 것처럼 보이지만, 실제로는 동녘의 몸을 빌려 말하는 것.

## 2. 인물 대사/행동 ≠ 물리적 존재

대사를 하거나 행동이 묘사되더라도 물리적 존재를 단정하면 안 된다.
- 빙의/접속 중인 인물이 다른 몸으로 대사하는 경우
- "(인물A(인물B))" 표기 — 괄호 안은 원격 조종자
- 몽타주에서 번갈아 보이는 것은 편집 기법

## 3. visual_world_rules — 자동 추출, 하드코딩 금지

entity_style 단계에서 시나리오 전문을 읽고 시각적 세계관 규칙을 자동 추출.
작품 고유명사나 특정 시나리오에만 해당하는 내용을 프롬프트에 하드코딩하면 안 된다.

## 4. 아웃룩 3단계 분리

1. 얼굴 참조 (1:1) — 인물 정체성
2. 아웃룩 단독 (1:1) — 의상만, 얼굴 없이 (마네킹 스타일)
3. 합성 (16:9) — 얼굴 ref + 아웃룩 ref → 전신

같은 아웃룩을 여러 인물이 공유 가능 (유니폼 등).

## 5. 씬 이미지 참조 — 합성 우선

_resolve_refs_for_prompt에서:
1. composite 합성 이미지 우선 (1장)
2. fallback: 얼굴 + 아웃룩 단독 분리 (2장)
3. _used_ref_ids에 composite_key + char_id + outlook_id 모두 등록 (중복 방지)

## 6. 씬별 요소 필터링

scene_detail에 전체 요소 전달 금지.
scene_director가 PRESENT 판정한 인물 + 씬 텍스트에 등장하는 배경/소품만 전달.

## 7. prompt_used 형식

- 얼굴: 한국어 설명 텍스트
- 아웃룩 단독: `[outfit:{outlook_id}] 설명`
- 합성: `[composite:{char_id}:{outlook_id}] 인물+아웃룩명`
- 레거시: `[outlook_id:{outlook_id}] 설명` (이전 코드 호환)

pipeline_gate에서 두 형식 모두 매칭해야 함.

## 8. scene_director는 반드시 Gemini 3.1 Pro

GPT-5.4는 소울라이드/빙의 상황에서 조종자를 물리적 존재로 오분류.
Gemini Pro는 director_notes 기반으로 정확 판단 (씬3,4에서 김의원 제거 성공).

- step_manifest의 scene_director default_model = `gemini-pro`
- director_notes (visual_world_rules에서 추출한 판단 기준)를 반드시 전달
- visual_world_rules 전체가 아닌 `director_notes` 항목만 전달

## 9. visual_world_rules에 director_notes 포함

visual_world_rules 추출 시 `director_notes` 배열을 함께 추출한다.
- 시나리오 고유의 "물리적 존재 판단 기준"을 LLM이 자동 도출
- 범용 예시가 아닌 작품 설정 기반 판단 원칙
- scene_director에만 전달 (scene_detail 등에는 불필요)

## 10. scene_still sync는 UPSERT (DELETE→INSERT 금지)

sync가 scene_still을 매번 삭제→재생성하면 새 UUID가 발급되어
이미지의 still_id 참조가 깨진다 (still_id = NULL).
still_index 기준으로 기존 ID를 보존하며 UPDATE, 새 씬만 INSERT.

## 11. entities 딕셔너리에 short_id 반드시 포함

image_service.py에서 entities 로드 시 `short_id` 필드 누락하면
entity_lookup → _sid_to_uuid 매핑 실패 → 합성 이미지 매칭 안 됨
→ 인물 참조 이미지 누락 → 엉뚱한 얼굴 생성.

## 12. T2I에서 복합 ID(C08O09) LLM에게 요청 금지

LLM이 복합 ID를 혼동한다 (C08O01을 동녘+검은의전정장으로 잘못 조합).
인물과 아웃룩을 **따로** 받아서 코드에서 조합해야 한다.
scene_detail 프롬프트에 씬별 인물+아웃룩 매핑을 명시적으로 전달.

## 13. 이미지 자동 생성 — 마지막 N개 T2I + 앵글 1개

scene_variation_count = T2I 생성 개수 (기본 2).
프롬프트가 몇 개든 기존 이미지가 몇 개든,
항상 마지막 N개 프롬프트로 T2I 생성 + fal.ai 앵글 1개.

## 14. SRD EP1 분석 점검표

SRD Part 1 에피소드 분석 후 반드시 확인할 항목:

- □ 씬 갯수 — 37 이상 (gemini-flash 세그먼테이션 기준)
- □ 요소 — 인물 13~16, 배경 3~7, 소품 5~10 적정 범위
- □ 아웃룩 — 10~18개, 전 씬 배정, VE에 없는 인물에 배정 안 됨
- □ 프롬프트 — 정확히 N개 (scene_variation_count 기준)
- □ 프롬프트 내 엔티티 — director VE만 사용, VE 위반 0건
- □ 인물 누락 — VE 인물이 프롬프트에 전부 포함
- □ 의원 오분류 — 씬3~8(배틀로얄/헬기/야산)에 강의원/김의원 없는지
- □ still_frame_prompt — VE 위반 없는지
- □ director_notes — 추출됐는지, 소울라이드 규칙 포함
- □ scene_summary — 전체 씬 요약 있는지

## 15. 씬 세그먼테이션 — LLM 파서 정의 + 코드 실행 방식

LLM에 시나리오 텍스트를 직접 분석시켜 씬 헤딩을 찾게 하면 매번 결과가 다름 (31~57씬).
대신 LLM에게 **Pydantic/regex 파서를 정의**하게 하고, 코드에서 실행하면 결정적(deterministic).

**흐름:**
1. LLM(gemini-flash 추천)에게 텍스트 앞부분(~6000자) + "씬 헤딩 regex 만들어라" 요청
2. 받은 regex를 re.compile → 전체 텍스트에 finditer 실행
3. 에러 또는 예상 대비 50% 미만 → 에러 메시지 포함 retry (최대 3회)

**주의:**
- PDF 추출 텍스트는 줄바꿈 없이 붙어있을 수 있음
- re.MULTILINE 사용 금지, 가변 길이 lookbehind 금지
- text_cleanup 제거됨 — PDF 원본 직접 사용

**모델 비교 (미아 시나리오 기준):**
- gpt-mini: 3차까지 실패 (최대 5씬)
- gpt-5.4: 2차(retry)에서 99씬 성공
- gemini-flash: 1차에서 바로 99씬 성공 ← 추천

## 16. 소품 추출 기준

- 3씬 이상 반복 등장
- 캐릭터가 착용/사용/탑승하는 것만
- 부속 소품은 본체에 통합 (아머+헬멧→아머)
- 일반 물건, 배경 시설, UI 화면, 의류, 상황 묘사 제외
- 에피소드당 5~10개 목표

## 17. T2I 단일 스틸컷 원칙

하나의 t2i_prompt = 한 순간, 한 시공간만. 3가지 고려:

1. **인물 변형**: 변신/변형 캐릭터는 선택한 순간의 형태 ID만 사용 (변형 전이면 원본만, 후면 변형만). 이름 기반 자동 감지 (괄호 앞 base name 그룹핑).
2. **서브씬**: 몽타주, 회상, 꿈, 상상, 인서트, 인터컷, 화면 속 영상, 내레이션 오버, 평행 액션 등 씬 안에 시공간이 다른 장면이 섞여 있으면 하나만 선택.
3. **전체 ≠ 한 컷**: scene_director가 씬에 배정한 전체 인물을 한 스틸컷에 다 넣을 필요 없음. 인물이 빠져도 OK, 없는 ID만 추가하지 않으면 됨.

## 18. entity_merge — 타입 간 중복 요소 병합

entity_extract 3종 완료 후, entity_detail 전에 실행.
같은 대상이 배경(location)과 소품(prop)에 동시 등록되는 경우 LLM(GPT-5.4)이 판별하여 제거.
입력: visual_world_rules + scene_summary + 전체 요소 목록(description 전문).
출력: 제거할 short_id 목록.

## 19. outlook EntityCanon ID 보존 (UPSERT)

`_sync_checkpoints_to_db`에서 outlook EntityCanon을 매번 DELETE+재생성하면 UUID가 바뀌어
ImageAsset.entity_id 참조가 깨진다. short_id 우선, name fallback으로 기존 ID를 유지하는 UPSERT 방식.
이건 scene_still의 UPSERT(#10)와 같은 원칙: 외부 참조가 있는 레코드의 ID는 보존.

## 20. 인물 T2I에 국적/인종 명기

entity_t2i 프롬프트에서 인간형 인물은 국적(Korean, American 등) 또는 인종(East Asian, Caucasian 등)을 명기.
비인간(요괴, 로봇 등)은 명기하지 않음. 이름 + 세계관 설정에서 LLM이 판단.
이 규칙 없으면 Gemini가 기본 서양인으로 생성함.

## 21. PDF 텍스트 추출 — PyMuPDF 사용

pypdf는 PDF마다 추출 품질이 다름 (요괴전: 단어마다 줄바꿈, 문 로스트: 공백 없이 붙음).
PyMuPDF(`pymupdf`)로 교체하면 모든 시나리오에서 깨끗한 텍스트 추출.
씬 세그먼테이션 regex의 false positive 문제도 PyMuPDF 텍스트에서 해결됨.

## 22. text_cleanup — 샘플 기반 regex (전문 LLM 금지)

PDF 추출 텍스트에서 워터마크/페이지 번호 등을 제거할 때, LLM에 전문을 보내지 않는다.
**5000자 샘플**을 gemini-flash에 보내 regex 치환 규칙을 받고, 코드로 전체 텍스트에 적용.
비용 최소, 원문 변형 없음, re.MULTILINE 사용.

## 23. entity_all 씬 묶음 체이닝 추출

entity_all_* 단계에서 전문 1회 호출 대신 **씬을 3000자 이하로 묶어** 순차 요청.
이전 결과를 체이닝하여 중복 없이 누적, 앞쪽 씬 2000자를 참조로 제공.
모델: gemini-lite (빠르고 저렴).

- 전문 1회: GPT-5.4 34~39명 (비결정적)
- 체이닝: gemini-lite 41명 (v20과 동일, 10회 호출)
- scene_count는 번들 간 누적 합산

## 24. scene_detail T2I 인물 수 제한

하나의 t2i_prompt에 인물 ID(C##O##)는 **최대 5명**까지.
나머지는 보통명사로 배경 표현 ("surrounded by crew members" 등).
대사가 있는 인물은 되도록 ID로 포함.
프롬프트: `prompts/_base/scene_extractor_v2/12.202603271830/turn_scene_detail.md`
