# Rooftop Spatial BG Experiment — plan v2

> standalone 실험 (production pipeline 0 수정). L05 옥탑방 내부만. API-only T2I/I2I + LVM realized-card pass.
>
> v1 → v2 diff: Codex sanity review NEEDS_REVISION 반영. (1) 모델 default 를 OpenAI gpt-image-2 로 정정 (production background 와 일치). (2) shot plate 에 인체/시신 silhouette 도 금지 (환경 흔적만). (3) LVM card schema qualitative 화 + bbox+zone 병행 + confidence rule. (4) fallback 자유 구도 금지. (5) LVM 헬퍼 = `image_validator._call_openai_vision` 직접 import. (6) Open Q 7건 모두 결정 완료.

## 0. 출처 / 절대 규칙

- 대상: project `6cb862d9-590c-4dce-86e6-d10c2977db19` / episode `08ad2cd3-3e96-4d84-808f-869ee628473c` / location `L05 옥탑방 내부` (canon id `3afbc7a8-b919-4431-a691-0a99057a26ca`).
- DB write **0**. checkpoint write **0**. backend/app/ 수정 **0**. import 만 허용.
- API-only. 로컬 ControlNet/ComfyUI/Blender/Diffusers 금지.
- LLM 호출 데이터 자르기 **금지** (CLAUDE.md 절대 규칙) — base/shot prompt + LVM 입력 전문 전달.
- LVM 는 geometry oracle 아님 — visible-only spatial observer/validator.
- **shot plate 단계까지 인체 0** — 시신 silhouette 도 금지. 환경 흔적 (피 자국, 어지러진 가구, 흐트러진 침구) 까지만. (Codex Blocking 2)

## 1. 실험 대상 (Phase A 확정)

14 L05 selected shots: S5_S2, S5_S6, S12_S4, S12_S6, S12_S14, S14_S4, S14_S5, S14_S9, S18_S5, S18_S9, S25_S3, S25_S7, S27_S1, S27_S4.

axis:
- time: morning / day / dusk / night
- state: normal / corpse_marks (피 자국 + 흐트러진 침구, 시체 silhouette 0) / cleaned / empty / vandalized
- sub_space 후보: main_room (거실+식탁) / bedroom / bathroom (욕실 거울) / entry_corner

## 2. 디렉토리 구조

```
scripts_output/rooftop_spatial_bg_experiment/<run_id>/
  space_bible.json
  shot_background_plan.tsv
  prompts/
    base/<space_key>__<camera_slot>.txt
    shot/<scene>_<shot>.txt
  base_plates/<space_key>__<camera_slot>.png
  shot_plates/<scene>_<shot>.png
  realized_spatial_cards/<base|shot>__<key>.json
  final_scene_context_cards/<scene>_<shot>.json
  planned_vs_realized_report.tsv
  cost_log.jsonl
  llm_call_meta.jsonl
  index.html
  run_meta.json
```

`<run_id>` = `<YYYYMMDD_HHMM>_<short_uuid>`.

## 3. CLI / 안전장치

```
backend/scripts/experiment_rooftop_spatial_bg.py
  --run-id <id>                # 명시 안 하면 자동
  --dry-run                    # default — 계획/프롬프트/디렉토리만, API 호출 0
  --generate                   # 실제 image API 호출 허용
  --skip-lvm                   # LVM pass 건너뛰기
  --max-shots N                # default 14
  --max-plates N               # base+shot plate 총량 cap, default 30
  --space-keys main_room,bedroom,bathroom
  --shots S5_S2,S12_S4         # 부분 실행
  --resume <prev_run_id>       # 이미 생성된 plate 재사용
  --estimate-only              # 호출 건수+예상 비용만 출력 후 exit
  --image-backend openai|gemini   # default openai (production 일치). gemini A/B 비교용.
  --allow-fallback-generate    # fallback_unknown slot 도 generate 허용. 기본 OFF (Codex Important 4)
```

Hard guards:
- `--generate` 없으면 image / LVM API 호출 0.
- `--max-plates` 초과 시 abort + 안내.
- `fallback_unknown` slot → 기본 skip (dry artifact 만 생성). `--allow-fallback-generate` 명시해야 generate.
- 중간 실패 시 `run_meta.json` 에 partial state 기록.

## 4. 7 단계 흐름

### Stage 1 — collect (Phase C 일부)
- DB read-only: `scene_still WHERE episode_id AND is_selected AND visible_entities_json LIKE '%L05%'`.
- t2i_variations_json / dependent_scene_id / shot_description / scene_summary / visible_entities_json 수집.
- shot 14건 → `shot_background_plan.tsv` 초안.

### Stage 2 — space bible (Phase C)
- L05 의 sub_space 4 후보 자동 분류 (룰):
  - "욕실|거울|화장실" → bathroom
  - "침실|침대|방 안.*시신|방 안.*깨끗" → bedroom
  - "현관|진입|문 안쪽" → entry_corner
  - "거실|식탁|싱크대|식사 공간" → main_room
  - 다중 매칭 → 우선순위 **bathroom > bedroom > entry_corner > main_room** (좁은 공간 우선, Codex Open Q 4 OK)
  - 매칭 0 → `manual_review_needed=true`
- 각 sub_space 의 fixed geometry (벽/문/창/가구) — entity_canon L05 description + 기존 chain_bg 8개 prompt 텍스트 키워드 추출. 자동 추출 신뢰 낮으면 sub_space 별 `confidence=low` + `manual_review`.
- camera_slot 정의: (space_key, slot_label, slot_description) 튜플. **자유 구도 X** — slot pool 미스 시 fallback_unknown 처리.

### Stage 3 — camera slot 배정 (Phase C)
- 각 shot → (space_key, camera_slot, state, time) 배정.
- shot_description 의 framing (wide/medium/close) + visible 요소 (식탁/거울/벽/흐트러진 침구) 로 slot 매칭.
- 매칭 0 → `slot=fallback_unknown` + `manual_review`. **자유 구도 자동 생성 금지** (Codex Important 4).

### Stage 4 — base plate (Phase D)
- (space_key, camera_slot) unique 조합만 base plate 1장 (T2I).
- prompt: 사람/캐릭터/동물/이름 **0**. 공간만. 가구 = Stage 2 bible 고정. state=normal/empty/daytime 통일.
- **Backend default = OpenAI gpt-image-2** (`background_render.py:108` 패턴 — `client.images.generate(model="gpt-image-2", ...)`) — production background 와 일치 (Codex Blocking 1).
- Gemini 는 `--image-backend gemini` 명시 시 A/B 비교용.

### Stage 5 — shot plate (Phase E)
- 각 shot 의 state/time 만 base plate 에 I2I/edit 로 적용.
- prompt: base = "이 공간을 그대로 유지" + delta = state/time/lighting overlay.
- **인체 silhouette / 시체 0**. 환경 흔적까지만 (피 자국 = dark stain, 흐트러진 침구 = disturbed bedding, 어지러진 가구 = scattered furniture). (Codex Blocking 2)
- **Backend default = OpenAI gpt-image-2 edit** (`background_render.py:89,99` 패턴 — `client.images.edit(model="gpt-image-2", image=[base_plate_bytes], prompt=...)`).
- lineage: 어느 base plate 가 source 인지 prompts/shot/<scene>_<shot>.txt 첫 줄에 명시.

### Stage 6 — LVM pass (Phase F)
- Helper: `backend/app/modules/image_validator._call_openai_vision` 직접 import (Codex Important 5 — `ImageReviewService` 는 image_asset review 용이라 과함).
- 모델: production 와 동일 (`_call_openai_vision` 내부에서 결정 — `gpt-5` vision 류).
- 입력: 각 plate (base/shot) + strict instruction "이미지에 보이는 것만 evidence 로 기술하라. 추측 금지. 안 보이면 'not visible'."
- 출력 schema (`realized_spatial_card.json`) — qualitative 화:
  ```json
  {
    "plate_id": "...",
    "plate_kind": "base|shot",
    "space_key": "main_room",
    "camera_slot": "eye_level_wide",
    "fixed_objects": [
      {
        "label": "식탁",
        "bbox_xyxy_norm": [0.32,0.55,0.61,0.78],
        "zone_label": "midground_center",
        "confidence": 0.82,
        "confidence_band": "trusted"
      }
    ],
    "depth_zones": {
      "foreground": "floor area near camera",
      "midground": "bed and walking space",
      "background": "back wall and window"
    },
    "camera_impression": {
      "view_text": "from room entrance toward back wall",
      "eye_level_qualitative": "eye_level",
      "framing_qualitative": "wide"
    },
    "placement_zones": [
      {
        "label": "식탁 앞 빈 공간",
        "bbox_xyxy_norm": [0.35,0.78,0.65,0.95],
        "zone_label": "foreground_center",
        "usable_for": "standing character | small prop"
      }
    ],
    "not_visible": ["창문 너머 풍경", "방의 정확한 깊이"],
    "overall_confidence": 0.7,
    "overall_confidence_band": "trusted"
  }
  ```
- **삭제** (Codex Important 1): `estimated_eye_height_m`, `lens_text=35mm` 같은 metric — LVM 못 함. `camera_impression` 의 qualitative tag 만.
- **bbox + zone 병행** (Codex Important 2 / Open Q 5): `bbox_xyxy_norm` (coarse evidence) + `zone_label` (foreground/midground/background × left/center/right 9칸).
- **confidence rule** (Codex Important 3): `band = trusted` (>=0.75) / `weak` (0.5~0.75) / `unknown` (<0.5).
- **negative evidence** (Codex Open Q 6): "not_visible" 리스트 = "이 이미지엔 안 보임" 의 뜻. "실제로 존재하지 않음" 아님.
- mismatch: planned (Stage 2-3) vs realized — fixed_objects 누락 / camera direction drift / sub_space 다르게 그려짐. `planned_vs_realized_report.tsv` 에 row 당 mismatch 사유.
- retry 정책: major mismatch (wrong sub_space) → 같은 plate **1회만** 재생성. 두 번째도 실패 시 `unresolved` 표기 후 진행. 무한 retry 금지.

### Stage 7 — final scene context card (Phase G)
- 각 shot 의 `final_scene_context_cards/<scene>_<shot>.json`:
  - `source_base_plate_path`, `source_shot_plate_path`
  - realized_spatial_card 발췌 (placement_zones / camera_impression / fixed_objects trusted only)
  - 미래 final scene 생성 시 reference 로 쓸 instruction text (예: "S12_S6 generation: use shot_plate `12_6.png` as exact_background reference. 인물 C04(민숙) 시신을 placement_zones[label='침대 옆 빈 바닥'] 에 배치. lighting=night.")
- `index.html`: base plate / shot plate / realized card 요약 / context card 한 페이지 contact sheet.

## 5. 모델 선택 (확정, Codex Open Q 1)

| 단계 | default | optional A/B |
|---|---|---|
| base plate T2I | **OpenAI gpt-image-2** (`client.images.generate`) | gemini (`--image-backend gemini`) |
| shot plate I2I | **OpenAI gpt-image-2 edit** (`client.images.edit`) | gemini i2i (`--image-backend gemini`) |
| LVM verify | **`image_validator._call_openai_vision`** (gpt-5 vision 류) | — |

(production background 와 일치, 1ref/multi-ref 모두 OpenAI edit 경로 사용.)

## 6. import 범위 (확정, Codex Open Q 7)

OK to import:
- `backend.app.core.database.SessionLocal` (DB read-only)
- `backend.app.models.*` (ORM, read-only)
- `backend.app.modules.image_validator._call_openai_vision` (LVM)
- (선택) `backend.app.modules.llm.image_tracer.trace_image_call` 로 일관 로깅
- OpenAI client 는 thin local wrapper 로 직접 (`from openai import OpenAI; client = OpenAI(api_key=...)`) — production `_resolve_openai_client` 까진 import 안 함, 실험 격리

비 import:
- `ImageReviewService` (Codex Important 5 — 과함)
- `scene_image_pipeline`, `scene_generation_coordinator` 등 production orchestration
- 어떤 step / service 의 write 경로도 import 금지

## 7. 비용 estimate (대략)

- base plate: sub_space 4 × camera_slot 평균 2 = ~8장 (T2I, gpt-image-2)
- shot plate: 14장 (I2I, gpt-image-2 edit)
- LVM: (8 + 14) × 1 call = 22 call (gpt-5 vision)
- retry: 5장 (보수적)

단가 가정:
- gpt-image-2 standard: ~$0.04/img → image 호출 32 × $0.04 = ~$1.28
- gpt-5 vision: ~$0.02/call × 22 = ~$0.44

총 약 $1.7 미만. `--estimate-only` 로 실호출 전 확정 비용 출력.

## 8. Out of scope (이번 실험 안 함)

- 다른 location (L01~L29 중 L05 외) 실험.
- final scene 이미지 실제 생성.
- production pipeline 수정 PR.
- DB schema 변경.
- frontend UI.
- evaluation 자동 점수 (mismatch report 는 사람이 읽고 판단).
- 시신 silhouette / 인체 plate (Codex Blocking 2 — 이번 실험은 plate 단계 인체 0).
- ImageReviewService 통합 (실험 격리).

## 9. v1→v2 변경 요약 (검토 편의)

| 영역 | v1 | v2 |
|---|---|---|
| base/shot model | Gemini default | **OpenAI gpt-image-2 default** (Blocking 1) |
| shot plate 인체 | 시체 silhouette 까지 허용 검토 | **silhouette 도 0, 환경 흔적만** (Blocking 2) |
| LVM card | `estimated_eye_height_m`, `lens_text=35mm` 포함 | **qualitative `camera_impression` 만** (Important 1) |
| bbox | xyxy_norm 단독 | **xyxy_norm + zone_label 병행** (Important 2) |
| confidence | overall_confidence 단일 | **trusted/weak/unknown 3 band rule** (Important 3) |
| fallback slot | 자유 구도 자동 생성 검토 | **default skip, `--allow-fallback-generate` 명시 필요** (Important 4) |
| LVM helper | `ImageReviewService` 후보 | **`image_validator._call_openai_vision` 직접** (Important 5) |
| CLI option | — | **`--image-backend`, `--allow-fallback-generate` 추가** |
| Open Q | 7개 미결 | **7개 모두 결정 + 문서화** |
