# Phase 8 — Prompt Redesign + Camera Recommendations

**Date:** 2026-04-30
**Status:** Spec — review pending
**Predecessor:** Phase 7 T19 (production E2E 100% step success, 그러나 결과물 사용자 거부 — 결과 38 PNG 폐기)

## Goal

Phase 7 T19에서 발견된 두 가지 이미지 품질 문제 해결 + 도면-배경 시점 일관성 보장.

**문제 1**: 배경(24장)이 영화 컷 eye-level이 아닌 top-down 도면 모방 형태로 출력됨.
- 근본 원인: `prompts/_base/background_prompt/1.202604292053/system.md` Rule 5의 hardcoded 문구 — `"preserve room layout, furniture positions, and architectural elements from the reference floor plan exactly"`.
- LLM이 이 문구를 그대로 t2i_prompt에 박음 → gpt-image-2가 ref(도면 PNG)를 visual model로 모방 → top-down 출력.

**문제 2**: 도면(14장)이 작품 세계의 architectural style을 반영하지 못하고 generic "다른 나라 건물"처럼 출력됨.
- 근본 원인: floor_plan_prompt + background_prompt system이 `visual_world_rules`의 era/region 같은 cultural cues를 derive해 t2i_prompt에 반영하라는 메타지시가 부재.
- 결과: 한국 옥탑방 시나리오인데 도면/배경이 미국·유럽 아파트 스타일로 출력.

**문제 3 (사전적 보강)**: 같은 sub_location의 여러 배경 PNG 간 카메라 시점이 도면 좌표계와 어긋날 위험.
- 해결: floor_plan_prompt 단계 LLM이 도면 layout을 결정하면서 동시에 각 background plan에 대한 카메라 위치/렌즈/높이를 좌표계 안에서 추천 → background_prompt에 inject.

## 결정사항 (실험 검증 완료)

| # | 결정 | 근거 |
|---|---|---|
| 1 | floor plan PNG ref 유지 | 실험 케이스 B (cinematic prompt + fp ref)가 layout 일관성 + eye-level 영화 컷 모두 만족 |
| 2 | 배경 출력 size **16:9** (1536×864 high) | 사용자 명시. 영화/드라마 컷 본질 |
| 3 | gpt-image-2 web grounding **자동 활용** | 실험 케이스 E (한국 옥탑방 cultural cue 명시) → reality cues가 ref-driven 출력으로 반영 확인 |
| 4 | shot별 카메라 = `floor_plan_prompt` 단계에서 함께 산출 (텍스트 필드만, 도면 PNG에 그리지 않음) | 도면 좌표계를 LLM이 이해하는 시점에 카메라 위치 결정 → 도면-배경 시점 일관성 자동 보장. 새 step 추가 X |
| 5 | 시나리오 의존 단어 hardcode **0** (절대) | 작품 고유명사·지역·주거형태 명시는 visual_world_rules + scene_segments의 LLM derive 결과로만 흘러감 |
| 6 | `background_render.py` 코드 변경 **0** (size default만 변경) | 실험 B로 ref 전략 OK 확인. PromptSanitizer 등 기존 retry/fallback 로직 그대로 |
| 7 | `visual_world_rules` step 변경 **0** | 현 schema에 era + region이 이미 있고, scene_segments는 verbatim 전달됨. 도면/배경 prompt가 이로부터 architectural/cultural cues를 LLM derive 가능 |
| 8 | LLM 입력 정보 **자르지 않음** | 시나리오·씬·shot description 모두 무절단 전달 (CLAUDE.md 절대 규칙) |
| 9 | 도면 출력 size = **1024×1024** (정사각, 확정) | 도면은 architectural diagram이라 정사각이 layout 표현에 자연스러움 |
| 10 | **`background_prompt` t2i_prompt 출력 언어 = scene_segments 원문 언어** (한국 시나리오면 한국어). `floor_plan_prompt` t2i_prompt = 영어 유지 | F 실험에서 100% 한국어 prompt가 한국 옥탑방 reality cue에서 유의미하게 우수 — gpt-image-2 web grounding이 prompt 언어로 검색 query 생성 → 한국 사이트 reference 정밀 활용 |
| 11 | **도면 PNG 안에 가구/문/창문 = 숫자만 (1, 2, 3, ...)**. 영역 라벨(Living Room/Bedroom 등)은 짧은 영어 단어만. JSON에 `numbered_elements[]` 매핑 산출 | (i) gpt-image-2 텍스트 렌더링 부담 0 (ii) `background_prompt` LLM이 번호로 정밀 ref (iii) `camera_recommendations.framing_notes`가 번호 ref로 도면 좌표계 일관성 강화 (iv) fp ref로 image_edit 시 noise ↓ |

## 변경 범위

### A. Prompt 신규 버전 (덮어쓰기 금지 — 새 디렉토리)

#### A1. `prompts/_base/floor_plan_prompt/<new>/`

기존 `1.202604292033/` 유지, 새 버전 디렉토리(예: `2.202604301200/`) 생성.

**system.md — 추가/변경 메타지시:**
1. 기존 Rule 1~5 유지 (top-down architectural diagram, anchor key elements, plot-critical, English only, no proper nouns).
2. **신규 Rule (cultural cues derive)**: `visual_world_rules`의 `era` + `region` + `description` 필드 + `scene_segments`(원문)를 분석해서 도면이 반영해야 할 architectural style/period/cultural cues를 derive. 이 cues를 t2i_prompt에 영어로 반영 (e.g., 시대·지역에 맞는 wall finish/window frame/floor type/lighting fixture). hardcoded 단어 금지 — LLM이 visual_world_rules + scene_segments에서 추출한 cues여야 함.
3. **신규 Rule (numbered markers — 도면 안 텍스트 최소화)**: t2i_prompt가 다음을 명시: 도면 안의 가구·문·창문·고정 소품·plot-critical 시각 장치는 **번호 marker만 (1, 2, 3, …)** 으로 표시 — text label 금지. 영역 라벨(Living Room, Bedroom, Rooftop 등)은 **짧은 영어 단어**만 도면 안 큰 글자로 허용. clean line work + numbered markers + minimal area labels의 정사각 architectural diagram.
4. **신규 Rule (numbered_elements 산출)**: 도면에 배치한 모든 번호에 대해 schema의 `numbered_elements[]` 채움. 각 entry: `number` (정수), `label` (영어 짧은 phrase, e.g., "wardrobe", "sliding window", "main entrance door"), `category` (enum: `furniture`, `opening`, `prop`, `plot_device`, `area`), `position_hint` (도면 좌표 기준 자연어, e.g., "north wall, far end of living_room"). 영역 라벨도 area 카테고리로 entry 생성.
5. **신규 Rule (camera_recommendations)**: master_plan input의 `backgrounds[]` 중 이 floor plan을 ref하는 모든 entry에 대해, 도면 좌표계 안에서 카메라 추천을 schema의 `camera_recommendations[]`에 산출. 각 entry: `bg_id`, `sub_location`, `camera_position` (numbered_elements 번호 ref 활용 자연어, e.g., "near number 1 (entrance), facing diagonally toward number 2 (wardrobe) and number 3 (sliding window)"), `camera_height` (예: "eye-level standing ~1.6m"), `lens_hint` (예: "35mm wide angle"), `framing_notes` (선택, 번호 ref로 프레임 구성 — e.g., "include numbers 2 and 3 prominently, leave number 4 (low table) in foreground"). shot 수가 많아도 bg_id 단위로 1개 추천 산출 (같은 bg_id의 모든 shot은 같은 카메라).

**schema.json — 필드 추가:**
```json
{
  "type": "object",
  "properties": {
    "fp_id": {"type": "string", "pattern": "^[a-z0-9][a-z0-9_]*$"},
    "t2i_prompt": {"type": "string", "minLength": 30},
    "key_elements": {"type": "array", "items": {"type": "string"}},
    "numbered_elements": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "number": {"type": "integer", "minimum": 1},
          "label": {"type": "string", "minLength": 2},
          "category": {"type": "string", "enum": ["furniture", "opening", "prop", "plot_device", "area"]},
          "position_hint": {"type": "string", "minLength": 5}
        },
        "required": ["number", "label", "category", "position_hint"],
        "additionalProperties": false
      }
    },
    "camera_recommendations": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "bg_id": {"type": "string", "pattern": "^[a-z0-9][a-z0-9_]*$"},
          "sub_location": {"type": "string", "pattern": "^[a-z0-9][a-z0-9_]*$"},
          "camera_position": {"type": "string", "minLength": 10},
          "camera_height": {"type": "string", "minLength": 5},
          "lens_hint": {"type": "string", "minLength": 3},
          "framing_notes": {"type": "string"}
        },
        "required": ["bg_id", "sub_location", "camera_position", "camera_height", "lens_hint"],
        "additionalProperties": false
      }
    }
  },
  "required": ["fp_id", "t2i_prompt", "key_elements", "numbered_elements", "camera_recommendations"],
  "additionalProperties": false
}
```

**user_template.md — 변경:** 기존 inputs(`fp_id`, `sub_location`, `scope`, `backgrounds_block`, `shots_block`, `scene_segments_block`, `visual_world_rules`)에 변경 없음. `backgrounds_block`은 이미 master_plan의 `backgrounds[]` 항목 (bg_id + sub_location + state_label + applies_to_shots) 포함하므로 LLM이 camera_recommendations 산출 가능.

#### A2. `prompts/_base/background_prompt/<new>/`

기존 `1.202604292053/` 유지, 새 버전 디렉토리(예: `2.202604301200/`) 생성.

**system.md — Rule 1·5 재작성 + 신규 Rule 추가:**

1. **(재작성) Output language**: Write t2i_prompt in the **SAME language as the source `scene_segments` + `visual_world_rules`** (e.g., 한국 시나리오 → 한국어 t2i_prompt). 식별자(`bg_id`, `sub_location`, `state_label`)와 schema의 다른 필드는 ASCII snake_case 그대로. t2i_prompt **본문**만 source language로 작성 — gpt-image-2 web grounding이 native language reference를 검색하도록 유도.
2. (기존) No proper nouns from the work — character names, place names from input scenes 사용 금지. 일반 묘사어만.
3. (기존) NO people, NO faces, NO blood-on-corpses depicted. Background only — empty space, props, atmosphere.
4. (기존) Plot-critical visual devices MUST be in the prompt (drawn curtain, broken window, scattered debris, etc.).
5. **(재작성) Image purpose**: t2i_prompt MUST describe a **cinematic eye-level photograph** at standing human height (~1.6m), framed as a film/drama still. The output is a photographic interior shot — **strictly NOT a top-down view, NOT a floor plan, NOT an architectural diagram**. State this purpose explicitly in t2i_prompt (source language).
6. **(재작성) Reference role**: `floor_plan_path`은 layout source ONLY — 어떤 가구·문·창문이 있고 어떤 상대 위치인지 이해하는 용도. Do NOT instruct the model to "preserve from the floor plan exactly" or to replicate its top-down perspective. `prior_bg_paths`는 lighting/material/style consistency용으로 명시.
7. **(신규) Camera derivation**: 입력으로 받은 `camera_recommendations[bg_id]`가 있으면 그 `camera_position` + `camera_height` + `lens_hint` + `framing_notes`를 t2i_prompt에 source language 자연어로 반영. `camera_position`/`framing_notes` 안의 `numbered_elements` 번호 ref는 `numbered_elements` input과 매칭해서 자연어 phrase로 변환 (예: "number 2 (wardrobe)" → 한국어 "옷장"). 없으면 scene_segments + applies_to_shots의 shot description으로부터 카메라 시점을 derive.
8. **(신규) Cultural/architectural cues**: `visual_world_rules`의 era/region/description + `scene_segments`에서 architectural style/period/material/lighting cues를 derive해서 t2i_prompt에 source language로 명시. 이렇게 reality cues가 들어가면 gpt-image-2의 web grounding이 자동으로 작동해서 reference 기반 출력이 됨. hardcoded 단어 금지 (작품 고유명사 X) — LLM이 visual_world_rules + scene_segments로부터 추출한 cues여야 함.
9. **(신규) Aspect ratio cue**: t2i_prompt 끝에 "16:9 시네마틱 화면비" 류의 framing 힌트 source language로 명시 (실제 size는 코드가 1536×864로 호출).
10. (기존) State variation: state_label drives lighting/mood/decor (source language로 묘사).

**schema.json — 변경 없음** (기존 `bg_id`, `t2i_prompt`, `ref_guide`, `shot_guides[]` 그대로).

**user_template.md — 신규 inject 블록:**
```
## Background spec
bg_id: {bg_id}
loc_id: {loc_id}
sub_location: {sub_location}
state_label: {state_label}

## References
floor_plan: {floor_plan_path_block}
prior backgrounds: {prior_bg_paths_block}

## Floor plan numbered elements (from floor_plan_prompt)
{numbered_elements_block}

## Camera recommendation (from floor_plan_prompt)
{camera_recommendation_block}

## Applies to shots
{applies_to_shots_block}

## Scene segments (verbatim)
{scene_segments_block}

## visual_world_rules
{visual_world_rules}
```

- `{numbered_elements_block}`: floor_plan_prompt 결과의 `numbered_elements[]` 전체를 자연어 블록으로 직렬화 (한 줄 1 entry: `"<number>. <label> [<category>] — <position_hint>"`). LLM이 t2i_prompt 작성 시 번호 ref를 자연어로 변환하는 dictionary 역할.
- `{camera_recommendation_block}`: floor_plan_prompt 결과의 `camera_recommendations[]` 중 현재 bg_id에 매칭되는 entry를 자연어 블록으로 직렬화. 매칭 없으면 빈 문자열(또는 "(none — derive from shot descriptions)" 안내).

### B. 코드 변경

#### B1. `app/modules/pipeline/floor_plan_prompt.py`
- LLM 호출 후 결과 schema에 `numbered_elements[]` + `camera_recommendations[]` 포함 → 데이터 클래스/dict 그대로 보존 후 step에 반환.
- 검증: `camera_recommendations[].bg_id`가 master_plan의 `backgrounds[].bg_id` 중 하나여야 함, `numbered_elements[].number`가 unique이어야 함.

#### B2. `app/core/steps/floor_plan_prompt_step.py`
- 체크포인트 `data.fp_prompts[fp_id]`에 `numbered_elements` + `camera_recommendations` 보존.
- prompt version PROMPT_VERSION 갱신 (manifest config_hash invalidate).

#### B3. `app/modules/pipeline/background_prompt.py`
- 입력 인자에 `numbered_elements: Optional[List[dict]]` + `camera_recommendations: Optional[Dict[str, dict]]` (bg_id → entry) 추가.
- user_template inject 시 `{numbered_elements_block}` + `{camera_recommendation_block}` 자연어 직렬화 (없으면 가이드 텍스트).
- t2i_prompt non-ASCII 허용 (Rule 1 변경에 따라 ASCII validation 제거 — 식별자만 ASCII 검증 유지).

#### B4. `app/core/steps/background_prompt_step.py`
- floor_plan_prompt 체크포인트 로드 시 fp_id별 `numbered_elements` + `camera_recommendations` flat dict (bg_id → entry) 구성. background는 `depends_on_fp[0]`으로 fp_id 매핑.
- background_prompt 호출 시 inject.
- prompt version PROMPT_VERSION 갱신.

#### B5. `app/modules/pipeline/background_render.py`
- 기본 `size` 인자: `"1024x1024"` → `"1536x864"` 변경 (코드 흐름 변경 X).
- (선택) `size` 인자를 step에서 명시 전달 — manifest/config 통해.

#### B6. `app/core/steps/background_render_step.py`
- `render_one_background` 호출 시 size 인자 명시 또는 default 활용.

#### B7. `app/modules/pipeline/floor_plan_render.py` + step
- 도면 size: 정사각 1024×1024 유지. **변경 없음**. (Q1 사용자 확인 후 변경 가능)

#### B8. `app/core/step_manifest.py`
- step order/depends_on 변경 없음. PROMPT_VERSION 변경만으로 invalidate.

### C. 변경 없는 영역 (확인)

- `prompts/_base/visual_world_rules/3.202604161200/` — 그대로
- `prompts/_base/background_master_plan/1.202604292000/` — 그대로 (master plan 산출 schema 충분)
- `prompts/_base/background_classify/3.202604300520/` — 그대로
- `app/core/steps/background_classify_step.py` / `background_master_plan_step.py` — 그대로
- step depends_on / order — 그대로
- ImageAsset UPSERT 로직 — 그대로 (단, B9 참고)

### D. 별도 PR (Phase 8과 분리)

- **DB schema fix**: `image_asset.variant_label` 컬럼 `varchar(8)` → `varchar(64)` 또는 `Text`로 확장.
  - 근거: T19 production E2E에서 `night_blood_curtain_drawn` (24자) 같은 state_label 값이 truncation으로 일부 ImageAsset row INSERT 실패.
  - 조치: alembic migration + 단위 테스트.
  - **별도 PR/migration** — Phase 8 prompt redesign과 분리해서 진행.

## 입력/출력 흐름 (요약)

```
visual_world_rules (변경 X)
    ↓ era/region/description/director_notes
background_classify (변경 X)
    ↓ groups (chain_bg / prev_shot_ref)
background_master_plan (변경 X)
    ↓ floor_plans[], backgrounds[], gen_order, applies_to_shots[bg_id]
floor_plan_prompt (★ schema/system 강화)
    ↓ fp_id, t2i_prompt, key_elements, camera_recommendations[bg_id]
floor_plan_render (변경 X)
    ↓ floor_plan PNG (1024×1024)
background_prompt (★ system 재설계 + camera_recommendations inject)
    ↓ bg_id, t2i_prompt (cinematic eye-level + cultural cues + camera derivation), shot_guides
background_render (★ size 16:9)
    ↓ background PNG (1536×864), fp ref 유지
```

## 폐기 대상

- T19 결과 38 PNG (도면 14 + 배경 24) — 재생성 필수
- 기존 prompt 버전 (`1.202604292033`, `1.202604292053`)은 디렉토리 유지 (덮어쓰기 X). manifest는 새 버전 참조.

## 테스트 전략

### 단위 테스트
1. **floor_plan_prompt v2**: camera_recommendations[] 산출 확인 (schema 검증 + master_plan backgrounds[].bg_id 매핑)
2. **background_prompt v2**: camera_recommendations inject 확인 + Rule 5 hardcoded 문구 부재 확인 + cultural cues derive 확인 (regression test on output text)
3. **background_render**: size 1536×864 인자 전달 확인
4. **camera_recommendations 누락 시 fallback**: bg_id 매핑 없을 때 background_prompt가 shot description에서 derive (graceful degradation)

### 통합 테스트
5. integration smoke (Phase 7 T18 패턴 mirror) — Step 3+5+6 빠른 검증

### 회귀
6. 기존 baseline tests (Phase 7 시점 1228 passed) → 회귀 0 유지

### Production E2E
7. PID c00bbe19 EP fe165e3a 재실행 — Step 3+4+5+6만 (master_plan 변경 없으므로 Step 1+2 재사용). 결과 사용자 검증.

## 듀얼 리뷰

- 구현 후 **Codex** + **Claude** 병행 리뷰 (CLAUDE.md feedback_dual_code_review 원칙).
- BLOCKING/IMPORTANT 식별 → fix → re-review → 승인 후 push.

## 명시적으로 하지 않는 것 (out of scope)

- visual_world_rules schema 확장 (이번 Phase에서는 derive 메타지시 강화로 해결, schema 변경 없음)
- background_classify / master_plan prompt 변경
- step_manifest order/depends_on 변경
- background_render.py 의 ref 처리 로직 (B 실험으로 OK 확인)
- ImageAsset UPSERT 로직 (별도 PR — variant_label varchar 확장만)
- scene_consistency 등 다른 phase 작업

## 성공 기준

1. 배경 출력이 cinematic eye-level film still (top-down 도면 모방 0건)
2. 도면/배경 모두 cultural/architectural cues 반영 ("다른 나라 건물처럼" 0건)
3. 같은 sub_location 안에서 카메라 위치가 도면 좌표계와 일관 (camera_recommendations 번호 ref로 명시)
4. 도면 PNG 안 텍스트 = 영역 라벨(짧은 영어) + 번호만 (개별 가구 텍스트 라벨 0건)
5. 배경 t2i_prompt 언어 = scene_segments 원문 언어 (한국 시나리오면 한국어)
6. PROMPT_VERSION 갱신으로 기존 체크포인트 자동 invalidate → 새 결과로 재생성
7. Codex + Claude 듀얼 리뷰 모두 APPROVED
8. 회귀 0 (1228 passed 유지)
