# W22 설계 — Production 야외 라인 이식: 장소 캐논 + 샷 grounding + nb2 직행 합성

- 작성: 2026-07-09 (Claude, 설계 초안) / **합의: 2026-07-10 사용자 "권고대로 진행" — §7 쟁점 6건 전부 권고안 확정**(①plate 처음부터 skip=A ②scene_detail t2i 미사용·9절 대체 ③씬 전문 grounding 주입+moment_context_en ④concept 1콜 축약=A ⑤nb2 N롤 variation ⑥캐논 단위=building_group)
- 근거 실험: `scratchpad/forest_exp/` s29(장소 캐논 역방향 체인) → s30(stage_ground) → s33(nb2 직행 합성, 프롬프트 계약 SOT). 사용자 채택(07-09 저녁): 4방식 비교(s30 플레이트=정확·경직 / s31 자유 마네킹=장소·광원 표류 / s32 접지 마네킹=정확+연출 / **s33 직행=최저비용·nb2 우세**) 중 s33.
- 판정 지식(관통 필수): 직행은 **nb2 전용**(i2 는 참조 사진 광원에 끌려가 밤→낮 위반) / nb2 프롬프트 **ID-free**(마커 코드 금지, name_en 치환) / **참조 관할 라벨 분리**(실사=룩 SOT, 맵=배치 전용·작도 금지) / **발명 경계 + 제작자 정정 관통**.

---

## 1. 목표와 비목표

**목표**: 야외(실외) 샷의 스틸 생성 라인을 다음 3단 체인으로 대체한다.

```
① outdoor_place_spec   (analysis)  씬 원문 → 마커 스펙(존/마커/배치 서술, evidence-bound)
② outdoor_place_canon  (image)     스펙 → 검증 실사 마스터(룩 SOT) + FP 스타일 탑다운 맵(배치 SOT)
③ outdoor_shot_grounding (analysis) 맵+스펙+샷 → 샷별 존/앵커/카메라 배치 판단
④ 스틸 합성 접점        (image)     기존 scene_image_pipeline 에서: 직행 프롬프트 결정론 조립
                                    + ref = [실사 마스터, 맵] + passport → nb2 1콜 (중간 plate 0)
```

**비목표(유지)**:
- 실내 라인 전부 유지: Phase 7 FP 체인(background_master_plan → floor_plan_prompt → floor_plan_render → background_prompt → background_render), building_fp_link, 실내 pose 계열.
- passport/VE 기반 캐릭터 참조 조립(`SceneReferenceService`) 그대로 재사용.
- 기존 모듈 삭제 금지 — 대체되는 야외 스택은 flag 로 **우회**만 한다(아래 §4).

**분기 원천**: `background_classify` 산출 `building_groups[].members[].is_indoor == false` (기존 그대로, 유일 원천).

---

## 2. 신규 스텝 상세

공통: 전 스텝 신 master flag `outdoor_direct_compose_enabled`(default False) 게이트. 프롬프트는 팩 규약(`prompts/_base/<module>/<version>/`) 채택(outdoor_site_layout 의 인라인 방식은 답습하지 않음). LLM 스텝 2곳(①③)에 `project_corrections_block()` 주입(기존 4사이트 관용 패턴). LLM 입력 자르기 금지.

### 2.1 ① `outdoor_place_spec` (analysis) — 마커 스펙 저작

실험의 `forest_map_spec_v1`(s27 spatial_evidence_graph 유래)을 production 스텝으로 승격. **설계 쟁점 "마커 스펙 저작 주체"에 대한 답 = 신규 LLM 스텝.**

- **단위**: building_group 중 실외 멤버가 1개 이상인 그룹당 1스펙(그룹=하나의 "촬영 부지"). 실험의 "옥탑방 property"(골목/마당/계단/옥상 4존)에 상응.
- **입력**: 해당 그룹 멤버 loc 이 등장하는 **씬 원문 전체**(전문, 청킹 금지) + shot_extract/shot_staging 의 해당 샷 산출 + entity(배경 요소) 상세 + visual_world_rules(물리 사실) + 제작자 정정.
- **모델**: gpt (GPT-5.5, 텍스트 분석 주력 관례).
- **출력 스키마** (실험 스키마 그대로 승격):
  ```json
  {
    "layout_narration_en": "...",
    "zone_labels_en": ["...", ...],
    "items": [{
      "code": "P1",            // 대문자 1 + 숫자 1, 그룹 내 비중복 (코드 assert)
      "kind": "stair|gate|yard|window|...",
      "name_en": "...",         // ID-free 치환용 서술명
      "placement_en": "...",    // 작도 문장
      "inferred": false,
      "evidence": {"scene_id": "...", "quote_ko": "..."}   // evidence-bound, inferred=true 시 생략 가능
    }]
  }
  ```
- **계약 조항**(실험 검증분 이식): evidence-bound(확정=원문 재진술 인용 / 추정=inferred 표시 / 무근거 발명 금지), 정정 최우선, 수치 발명 금지, 시나리오 중립(코드에 작품 고유명사 0 — 전부 LLM 저작 데이터).
- **persist**: 스텝 checkpoint (`data.groups[group_id].spec`). 결정론 검증=코드: 코드 형식·비중복·enum 무결성 assert.

### 2.2 ② `outdoor_place_canon` (image) — 장소 캐논 자산 생성 (s29 역방향 체인)

그룹당 자산 2장: **실사 항공 마스터**(룩 SOT) + **탑다운 사이트플랜 맵**(배치 SOT).

- **체인**(s29_top_v2 계약 그대로): (a) nb2 무참조 3롤(1:1, 프롬프트=LAYOUT 서사+ELEMENTS ID-free 범례+SCALE&MASSING+NO_ANNOTATION+세계 사실) → (b) GPT+Gemini VLM 0-10 이중판정, **합산 최댓값·동점 시 Gemini ranking 우선** → (c) 양 모델 결함 critique concat → 결함 있으면 nb2 i2i 1콜 수정(결함 0이면 생략) → (d) **gpt 재투영 맵** 1콜(참조=[수정 실사, 스타일 FP], VERTICAL_NOTE=순수 평면 강제, 맵에는 코드 포함 마커 범례).
- **generic 화**: 실험 하드코딩(3층 빌라/8-10m/철제 계단/한국 동네) 제거 — SCALE&MASSING 은 ①스펙 layout_narration + floor_plan_prompt v10 scale_guideline 상당의 스텝 산출 + 정정에서 조립. 스타일 FP 참조 = 해당 프로젝트 floor_plan_render 완료 산출 중 1장(결정론 선택: 첫 completed fp). **FP 없는 프로젝트(전부 야외)** = 스타일 참조 생략, STYLE_MAP 텍스트 계약만으로 작도(s27 검증 경로).
- **persist**: `location_aerial` 선례 그대로 — 파일 `images/background_chain/`, ImageAsset **UPSERT**, 신 pipeline_role 2종: `outdoor_canon_photo` / `outdoor_canon_map` (asset_type 후보 `place_canon`). generation_call_id·중간물(후보 3롤/수정 전)은 `is_intermediate`+`disposition` 메타로 보존(alembic 008/009 패턴).
- **트리거/order**: floor_plan_render(21.55) 이후, 스틸 합성 전 image 스텝. 후보 order 21.75 부근(§5 표).

### 2.3 ③ `outdoor_shot_grounding` (analysis) — 샷 grounding (s30 stage_ground 계약)

- **대상**: selected shots 중 야외 샷(그룹 멤버 loc 소속).
- **입력 4파트**(s30 계약 그대로): SITE PLAN 맵 png + MARKER LEGEND(코드 포함, 여기까지는 VLM 내부용이라 코드 허용) + SHOT(샷 원문=shot_extract description + staging + 정정) + BACKGROUND CONCEPT(선행 gpt 구상 콜 — s30 concept 스테이지 포함 여부는 쟁점 §7-6).
- **모델**: gpt (VLM 판독).
- **출력 스키마**(동적 enum — ①스펙에서 생성): `{map_zone(enum=zone_labels_en), anchor_markers[](enum=codes), camera_position_en, look_direction_en, in_frame_en, rationale_ko}` + **`moment_context_en`**(신설, §7-5 씬 컨텍스트 답안).
- **persist**: 스텝 checkpoint (`data.shots["<si>_<shi>"].ground`). scene_still UPSERT 원칙과 무관(스틸 테이블 안 건드림).

### 2.4 ④ 스틸 합성 접점 — nb2 직행 (s33 계약 SOT)

신규 스텝이 아니라 **기존 `scene_image_pipeline`(order 25) 내 분기**. 접점 = `scene_generation_coordinator.build_scene_attached_refs` step 7 의 5a 분기.

- **조건**: flag on AND 해당 샷 야외 AND 캐논 자산 2장 + grounding 존재 → **직행 모드**. 하나라도 결측 → 기존 분기(5a~5e) 자연 fallback(plan-missing degrade 선례).
- **참조 조립**(라벨 관할 분리, s33 원문):
  - index 0: `LOCATION PHOTOGRAPH — the same property; sole source of how everything looks.` = 실사 마스터
  - index 1: `SITE PLAN — layout source only; never draw this drawing or its markers.` = 맵
  - 이후: passport(기존 `resolve_refs_for_prompt_set` 그대로, state variant 스왑 포함)
  - 4-list(labeled_refs/attached_meta/ref_roles/ref_role_metadata) length-parity 불변식 준수. `input_image_ids` 에 캐논 자산 UUID 2건 stamp(lineage).
- **프롬프트**: **LLM 콜 없이 결정론 조립** — s33 `_prompt` 9절 그대로 이식:
  1. DIRECT_HEAD ("Create ONE FINAL photorealistic live-action film still … ON LOCATION …")
  2. SPOT(fixed) — grounding 의 zone+anchor_markers 를 **name_en 으로 치환**(ID-free)한 place_desc
  3. SHOT TEXT(authoritative, Korean) — scene_heading + shot description + people in shot (+ `context:` = moment_context_en, §7-5)
  4. FREE_CAMERA (구도 지시 의도적 0 — 카메라 t2i 재량)
  5. CHARACTERS (참조 인물만 등장 / 없으면 무인물 절)
  6. TIME_LOCK (샷 텍스트의 시간·광원 하드 제약, 무근거 날씨·무드 금지)
  7. WORLD FACTS — 하드코딩 금지: visual_world_rules 물리 사실 + **제작자 정정 전문** 데이터 주입
  8. REF_NOTE (참조 관할 3종 선언)
  9. NO_ANNOTATION (주석 봉인)
- **엔진**: **nb2 전용**(`settings.gemini_image_model`) — i2 직행 금지(참조 광원 우세로 TIME_LOCK 파괴, s33/s31 재현 판정).
- **T2I 단일 스틸컷 원칙 준수**: 한 순간·한 시공간(SHOT TEXT=한 샷), 인물=샷 확정 인물만(VE 계열 데이터), 복합 ID 없음(코드 조합).

---

## 3. 데이터 흐름(요약)

```
background_classify ──is_indoor=false──▶ [그룹 G: 실외 멤버]
      │                                        │
      ▼                                        ▼
(실내: 기존 Phase 7 FP 체인 그대로)   ① outdoor_place_spec (씬 전문+staging+정정 → 마커 스펙)
                                               │
                                               ▼
                                      ② outdoor_place_canon (3롤→이중판정→수정→맵 재투영)
                                               │  ImageAsset: outdoor_canon_photo / outdoor_canon_map
                                               ▼
                                      ③ outdoor_shot_grounding (샷별 zone/anchor/camera/moment_context)
                                               │
                                               ▼
scene_image_pipeline ──야외+flag──▶ ④ 직행: 9절 조립 + [실사,맵]+passport → nb2 1콜 → scene_still
                    └─그 외/결측──▶ 기존 5a~5e 분기 그대로
```

---

## 4. 기존 라인과의 관계 (쟁점 1 답안)

| 기존 구성 | 처분 | 방법 |
|---|---|---|
| Phase 7 FP 체인(실내) | **유지** | 변경 없음 |
| background_render 의 야외 plate(chain_bg) | **우회** | 직행 모드 샷은 5a 에서 chain_bg 대신 캐논 자산 주입. plate 생성 자체의 야외 skip 여부는 §7-2 |
| outdoor_site_layout (21.76) | **우회** | flag on 시 not_applicable(applicability resolver 분기). 모듈 삭제 금지 — flag off 면 기존 동작 |
| shot_aware_bg 스택(dossier→readback→projection_card→partition→plan)의 야외 분 | **우회** | 동일. 실내 분은 유지 |
| location_aerial | **대체(개념 승계)** | 캐논 실사 마스터가 역할 흡수. persist 패턴(UPSERT/lineage)은 재사용. flag on 시 야외 aerial 생성 skip |
| building_fp_link | **유지** | 실내 접점. 직행 프롬프트에는 미주입(참조 3종 계약 밖) |
| space_set_bg (opt-in) | **독립 유지** | open_air 단일 T2I 선례로만 참조, 상호 배타 flag 운용(동시 on 금지 가드) |
| scene_detail | **유지 + 스틸 소스만 스위치** | 스텝은 그대로 실행(실내·리뷰·VE 하류 의존). 직행 모드 야외 샷의 스틸 프롬프트로는 **미사용**(9절 조립이 대체) — §7-4 |

---

## 5. 스텝 order·applicability 초안

| 스텝 | phase | order(안) | depends_on(핵심) | applicability |
|---|---|---|---|---|
| outdoor_place_spec | analysis | 21.77 | background_classify, shot_staging, entity_detail, visual_world_rules | if_background_mode AND flag AND 실외 loc 존재 |
| outdoor_place_canon | image | 21.78 | outdoor_place_spec (+floor_plan_render soft: 스타일 참조는 있으면 사용) | 동일 |
| outdoor_shot_grounding | analysis | 21.79 | outdoor_place_canon(맵 png), shot 선택/staging | 동일 |
| (접점) scene_image_pipeline | image | 25 (기존) | + outdoor_shot_grounding soft | 기존 |

- floor_plan_render 의존을 soft 로 두는 이유: 전부-야외 프로젝트에서 FP 부재가 캐논 생성을 막으면 안 됨(스타일 참조 생략 경로).
- 재시도: 이미지 콜 max 2회 + sanitize(실험 계약), grounding LLM 은 스키마 위반 시 max_retry=2(기존 beat/shot 관례).

---

## 6. config flag · config_hash · 프롬프트 팩

- **flag**: `outdoor_direct_compose_enabled: bool = False` 1개(master). 세부 flag 분할(스텝별)은 과분할로 비권장 — 실험 채택 체인이 원자적 세트.
- **config_hash**: "활성 경로 + True 일 때만 payload 스탬프" 계약 준수 → **default(off) byte-identical, 기존 프로젝트 checkpoint 무효화 0**. on 전환 시 신규 스텝은 신규 cp 라 충돌 없음. scene_image_pipeline 은 직행 모드 활성 시에만 flag 를 `_config_hash()` 에 스탬프(v10 selector 전환 때 겪은 재렌더 파장을 off 경로에 일으키지 않기 위함).
- **프롬프트 팩**: 신규 모듈 3개 각각 `prompts/_base/outdoor_place_spec/1.2026MMDDHHmm/` 형식 신설(덮어쓰기 금지 규칙 자동 충족). ④의 9절 조립은 LLM 프롬프트가 아니라 t2i 프롬프트 템플릿 — 동일하게 팩으로 관리하되 결정론 조립 함수가 소비.

---

## 7. 설계 쟁점별 제안 (사용자 결정 요청)

1. **기존 background_chain 관계** — §4 표대로 "야외 우회·실내 유지". ✅제안 확정치.
2. **야외 plate 생성 skip 여부**: 직행 모드에서 background_prompt/render 가 야외 bg 를 여전히 만들면 비용 낭비(사용 안 함). **제안**: flag on 시 야외 bg_id 는 planning 단계에서 skip 마킹(생성 0) — 단, 최소침습을 원하면 1차 wave 에선 생성 유지+미사용, 2차에서 skip. **(A: 처음부터 skip / B: 1차 유지 후 skip — 권고 A)**
3. **캐논 단위** = building_group(실외 멤버 묶음). 단독 실외 loc(거리 등)는 1-loc 그룹. **이의 없으면 확정**.
4. **scene_detail 접점**: 직행 모드 야외 샷은 scene_detail t2i_prompt 미사용(9절 조립 대체), scene_detail 스텝 실행은 유지. 근거: scene_detail t2i 는 구도·배경 서술 과지정 이력(07-07 원인 분석)이라 FREE_CAMERA·SPOT 계약과 충돌. **부작용**: 해당 샷은 t2i_review/VE 위반 검사 경로가 직행 조립엔 그대로 안 걸림 → 직행 조립에 동등한 VE 가드(등장 인물=확정 데이터 코드 조립이라 위반 여지 극소)만 결정론으로 추가. **(동의 여부)**
5. **씬 컨텍스트 주입**(S13_3 "차"→경찰차 소실 사례): **제안** = grounding 스텝 입력에 **해당 씬 원문 전체** 주입(자르기 금지 규칙 부합) + 출력에 `moment_context_en` 1~2문장(evidence-bound, 샷 순간의 세계 사실 보강만·연출 금지) 신설 → 9절 ③에 `context:` 로 주입. t2i 프롬프트 자체에 씬 전문을 넣는 안은 콘티형 단순 원칙 위반이라 비채택. **(동의 여부)**
6. **concept 선행 콜 유지 여부**: s30 은 concept(배경 구상)→ground 2콜이었으나 s33 직행에선 concept 산출이 최종 프롬프트에 안 들어감(SPOT+FREE_CAMERA 만). **제안**: production 은 ground 1콜로 축약(입력에서 BACKGROUND CONCEPT 파트 제거, 씬 원문이 대신 들어감) — 비용·단순성. **(A: 1콜 축약 / B: 실험 그대로 2콜 — 권고 A)**
7. **variation 수**: 실험은 샷당 nb2 1장. production 관례는 scene_variation_count(마지막 N개 T2I). **제안**: 직행도 동일 프롬프트 nb2 N롤(FREE_CAMERA 재량이 자연 variation 생성) — 기존 UI/선택 흐름 유지. **(동의 여부)**

---

## 8. 구현 웨이브(합의 후)

1. **W1**: `outdoor_place_spec` 스텝+팩+결정론 테스트(코드 형식/비중복/enum) → Codex 리뷰
2. **W2**: `outdoor_place_canon` (판정·수정·재투영 체인, asset persist) → canary(단일 그룹) → Codex 리뷰
3. **W3**: `outdoor_shot_grounding` (동적 enum 스키마, moment_context) → canary → Codex 리뷰
4. **W4**: scene_image 접점(9절 조립·ref 교체·fallback·lineage·config_hash 스탬프) + 야외 스택 우회 applicability → Codex 리뷰
5. **E2E**: fresh 프로젝트 full run(flag on) → 갤러리 육안(8897, LAN IP) → 수정 → Codex 리뷰 → 디플로이

검증 방침: 결정론 부분(스키마 assert·조립 함수·분기·hash)만 TDD, LLM/이미지 완성도는 canary+사용자 육안(PASS 카운트로 동작 주장 금지).

---

## 9. 리스크

- **금월도 외 시나리오 일반화**: 실험은 단일 property 중심 — 다수 야외 그룹/광역 장소(바다·포구 등)에서 스펙·캐논 품질 미검증. E2E 에서 최우선 관찰 지점.
- **맵 재투영 품질 의존**: gpt 재투영 1콜 실패 시 grounding 전체가 흔들림 — 재시도 2회+실패 시 해당 그룹 직행 비활성(기존 라인 fallback).
- **nb2 습성**: 마커 코드 노출·라벨 작도 재발 가능 — ID-free 치환을 코드 레벨 assert(프롬프트 내 code 패턴 0 검사)로 봉인.
