# Phase 4 — `chain_bg_render` Floor Plan Reference Integration Design Spec

> **참조 v3 plan**: `next_session_floor_plan_architecture_implementation.md` (memory)
> **선행 Phase**: P0(aacccd9), 1b(553fea2), 2(04840cc), **3(4eac12c)** 완료
> **현재**: chain_bg_render 이미 gpt-image-2 사용 중 — 모델 전환 불필요

## 1. 목표

`background_mode=floor_plan_anchored` 시 `chain_bg_render`가 **도면 PNG를 추가 reference**로 사용하여 chain background PNG를 생성. 도면이 spatial layout authority 역할을 해 가구/문/공간 배치 정확성 보장 (spike test 6 검증).

또한 `chain_bg_planning`이 도면 prompt를 input context로 받아, planning 단계에서부터 도면 인식한 chain bg prompt 생성.

## 2. 현재 상태 vs 목표 상태

| 항목 | 현재 | Phase 4 후 |
|---|---|---|
| chain_bg_render 모델 | gpt-image-2 (이미 적용) | 변경 없음 |
| chain_bg_render ref 1순위 | parent PNG (자식) / location ref (root) | **floor plan PNG (mode=floor_plan_anchored)** |
| chain_bg_render ref 2순위 | text-only fallback | parent PNG / location ref |
| chain_bg_render ref 3순위 | (없음) | text-only |
| chain_bg_render depends_on | `[background_chain_planning]` | `+ location_floor_plan` |
| chain_bg_planning prompt | 시나리오/샷만 | + 도면 prompt 발췌 (mode=floor_plan_anchored 시) |
| chain_bg_planning depends_on | (변경 없음) | `+ location_floor_plan` (조건부) |

## 3. 핵심 설계 결정

| 결정 | 근거 |
|---|---|
| 도면 PNG를 1순위 ref로 | spike test 6 — 도면이 가장 강력한 spatial authority |
| parent/location ref는 2순위 | 도면 + 부모 모두 활용 (multi-image edit) |
| `OpenAI images.edit` multi-image 사용 | gpt-image-2가 image list 지원 (spike에서 검증됨) |
| mode=off / chain_only는 변경 0건 | 회귀 보장. 기존 단일 ref flow 유지 |
| location_floor_plan을 unconditional depends_on | mode=off 시 not_applicable이지만 step_runner가 satisfied로 인정 (step_runner.py:99) |
| chain_bg_planning prompt 보강 선택적 | 핵심 효과는 chain_bg_render의 ref. planning 보강은 추가 align — Phase 4에 포함 (단순 prepend) |

## 4. 컴포넌트 책임 매트릭스

| 단계 | 입력 | 출력 |
|---|---|---|
| 4-A. floor plan PNG path 로드 | location_floor_plan checkpoint | `Dict[short_id, Path]` |
| 4-B. floor plan prompt text 로드 | location_floor_plan checkpoint | `Dict[short_id, str]` |
| 4-C. chain_bg_planning user_prompt prepend | 4-B의 prompt text | mode=floor_plan_anchored 시만 prepend |
| 4-D. chain_bg_render multi-image edit | 4-A의 PNG + parent/location ref + node prompt | chain bg PNG |

## 5. 데이터 모델 변경

**없음**. 기존 ImageAsset / 체크포인트 구조 그대로. 새 자산/필드 추가 없음.

manifest data only:
- `chain_bg_render` 결과 `nodes[].ref_used` 값에 신규 라벨 추가:
  - 기존: `"parent"`, `"location"`, `"text_only"`
  - 신규: `"floor_plan+parent"`, `"floor_plan+location"`, `"floor_plan_only"`

## 6. mode 매트릭스 (Phase 4 동작)

| background_mode | chain_bg_planning | chain_bg_render | 효과 |
|---|---|---|---|
| `off` (default) | 변경 0건 | 변경 0건 | 회귀 0건 (PID c00bbe19 baseline 일치) |
| `chain_only` | 변경 0건 | 변경 0건 | 기존 동작 (Phase 1b/2 토글과 결합 가능) |
| `floor_plan_anchored` | + 도면 prompt prepend | + 도면 PNG ref | 도면 anchored 풀 architecture |

## 7. 의존성 그래프 변경

```
shot_validator + shot_selection + scene_save + entity_merge + visual_world_rules + scene_director
   ↓
🆕 location_floor_plan (Phase 3, order 21.5, image)
   ↓ (신규 의존성)
background_chain_planning (analysis, order 19.6) ← 의존 추가하려면 order > 21.5 필요 → **22.5** (analysis)
   ↓
background_chain_render (image, order 24.7) ← order 그대로 OK (이미 > location_floor_plan)
```

⚠️ **chain_bg_planning order 19.6 → 22.5 변경 필수** (location_floor_plan 21.5에 의존하므로):
- 22.5 (analysis): last_analysis_order = 22.5
- 모든 image step order > 22.5 필요. 현재 image min = world_guide(22) → **violation**

해결책 두 가지:
- (A) **chain_bg_planning unconditional 의존 X — 코드 내부에서 floor_plan checkpoint 직접 로드** (manifest depends_on에 추가 안 함). order 19.6 유지.
  - 단점: cascade invalidation 안 함 (location_floor_plan force 시 chain_bg_planning 자동 재실행 안 됨).
- (B) chain_bg_planning을 image category로 변경 (planning이지만 image asset 생성 단계로 분류).
  - 단점: 의미적으로 어색함. world_guide와 동일 패턴이 아님.
- (C) `world_guide` order 22 → 23 이동 + chain_bg_planning order 22.5.
  - 단점: 영향 범위 넓음.

**추천**: **(A)** — chain_bg_planning은 manifest depends_on을 그대로 두고, 코드 내부에서 location_floor_plan checkpoint를 best-effort 로드. mode=off / chain_only 시 floor_plan checkpoint가 없거나 빈 결과 → 기존 동작. 토글 분기로 충분.

cascade invalidation은 사용자가 force할 때 명시적으로 chain_bg_planning + chain_bg_render도 함께 force. 이는 Phase 6 E2E 검증에서 다룰 사항.

→ **manifest depends_on 변경**:
- `background_chain_planning`: 변경 없음 (order 19.6, 코드 내 best-effort 로드)
- `background_chain_render`: `+ location_floor_plan` 추가 (order 24.7 그대로 OK)

## 8. 실행 흐름 (코드 변경 영역)

### 8.1 `BackgroundChainRenderStep._execute`

```
기존: planning_data + location_ref_paths 로드
신규:
  + floor_plan_paths: Dict[short_id, Path] 로드 (location_floor_plan checkpoint)
  + run_background_chain_render(..., floor_plan_paths=floor_plan_paths)
```

### 8.2 `run_background_chain_render` / `render_one_location`

```python
def render_one_location(
    location_id: str,
    ...,
    floor_plan_path: Optional[Path] = None,  # 신규
    ...
):
    for node_id in execution_order:
        ...
        ref_paths = []
        if floor_plan_path and floor_plan_path.exists():
            ref_paths.append(floor_plan_path)  # 1순위
        if parent_id and parent_rendered:
            ref_paths.append(parent_rendered)  # 2순위
        elif location_ref:
            ref_paths.append(location_ref)
        # text_only: ref_paths == []
```

### 8.3 `render_node_image` — multi-image edit 지원

```python
# 기존: image=f (single file)
# 신규: image=[f1, f2, ...] (multi-image, gpt-image-2 multi-edit)
if len(ref_paths) >= 2:
    # OpenAI gpt-image-2 multi-image edit
    files = [open(p, "rb") for p in ref_paths]
    resp = client.images.edit(model="gpt-image-2", image=files, prompt=..., ...)
elif len(ref_paths) == 1:
    # 기존 단일 ref
    resp = client.images.edit(model="gpt-image-2", image=open(ref_paths[0], "rb"), prompt=..., ...)
else:
    # text_only
    resp = client.images.generate(...)
```

### 8.4 `BackgroundChainPlanningStep._execute` — best-effort floor plan prompt prepend

```python
floor_plan_prompts: Dict[short_id, str] = self._load_floor_plan_prompts()  # best-effort
# user_prompt 빌드 시:
if location_id in floor_plan_prompts and floor_plan_prompts[location_id]:
    user_prompt = (
        "[FLOOR PLAN — spatial layout authority]\n"
        + floor_plan_prompts[location_id]
        + "\n\n[CHAIN BG PLANNING TASK]\n"
        + base_user_prompt
    )
```

mode=off / chain_only 시 floor_plan_prompts == {} → 기존 동작.

## 9. Multi-image edit 검증

OpenAI `gpt-image-2` multi-image edit:
- spike test 6 `experiment_chain_bg_floorplan_v3_tworoom.py` 에서 검증 (variant F/G)
- API: `client.images.edit(model="gpt-image-2", image=[file_obj_1, file_obj_2, ...], ...)`
- 도면 + parent + (선택) entity refs 까지 가능

본 Phase 4 범위:
- chain_bg_render: 도면 + parent (또는 location ref) → 최대 2개 image
- entity refs는 Phase 5 (scene_image_pipeline)에서 추가

## 10. Fallback 매트릭스

| 상황 | 동작 |
|---|---|
| floor plan checkpoint 없음 (mode=off) | floor_plan_paths={} → 기존 단일 ref flow |
| floor plan checkpoint 있지만 status=failed | 해당 location의 floor_plan_path=None → 기존 flow |
| floor plan PNG 파일 미존재 (disk 이슈) | path.exists() check fail → 기존 flow + warning |
| multi-image edit 실패 (API 에러) | retry → 최종 실패 시 단일 ref(parent only)로 fallback |
| moderation block | 기존 sanitizer retry 로직 그대로 |

## 11. 회귀 보장 3중

1. `background_mode = "off"` default → location_floor_plan not_applicable → floor_plan_paths={} → chain_bg_render 기존 단일 ref flow
2. `chain_only` mode도 동일 (floor plan checkpoint 미생성)
3. floor_plan_paths 비어있으면 chain_bg_planning prompt prepend skip

## 12. 변경 파일 (예상 7개)

| 파일 | 변경 |
|---|---|
| `backend/app/core/step_manifest.py` | chain_bg_render depends_on에 `location_floor_plan` 추가 |
| `backend/app/core/steps/background_chain_render_step.py` | `_load_floor_plan_paths` 추가 + `run_background_chain_render` 호출 시 인자 전달 |
| `backend/app/core/steps/background_chain_planning_step.py` | `_load_floor_plan_prompts` 추가 (best-effort) |
| `backend/app/modules/pipeline/background_chain_render.py` | `render_one_location` + `render_node_image`에 multi-image edit + floor_plan_path 인자 |
| `backend/app/modules/pipeline/background_chain_planning.py` | user_prompt 빌드 시 floor_plan_prompts prepend |
| `backend/tests/core/test_phase4_floor_plan_chain.py` | **신규** — 단위 테스트 ~10~12개 |
| `backend/tests/pipeline/test_background_chain_render.py` | 기존 회귀 테스트 + multi-image edit 신규 케이스 |

`prompts/_base/background_chain_render` 와 `prompts/_base/background_chain_planning` 의 프롬프트는 **변경 없음** (input prepend는 user_prompt 코드에서 처리, system.md는 mode-agnostic 유지).

## 13. 테스트 전략

### 단위 테스트 (mock 기반)
- `test_load_floor_plan_paths_when_off_returns_empty` — mode=off 시 빈 dict
- `test_load_floor_plan_paths_skips_failed_status` — status=failed location 제외
- `test_load_floor_plan_paths_skips_missing_file` — disk 파일 없으면 제외
- `test_render_node_image_multi_image_edit` — 2 ref 시 multi-image API 호출
- `test_render_node_image_single_image_fallback` — 1 ref 시 기존 single 호출
- `test_render_one_location_floor_plan_priority` — floor plan + parent 모두 있을 때 floor plan이 1순위
- `test_chain_bg_planning_prepends_floor_plan_prompt` — floor_plan_anchored 모드에서 user_prompt prepend
- `test_chain_bg_planning_no_prepend_when_off` — off 모드에서 prepend 0
- `test_render_one_location_no_floor_plan_uses_legacy_flow` — floor_plan_paths={} 시 기존 동작

### 회귀 테스트 (기존 호환)
- `test_background_chain_render.py` 17개 → 모두 PASS (multi-image이 옵셔널 인자라 기존 호출 깨지지 X)
- Phase 1b/2/3 테스트 → 변경 없음

### 통합 검증 (별도 세션 — Phase 4 e2e)
- 옥탑방 PID로 mode=floor_plan_anchored 실행, chain_bg PNG가 floor plan에 align되는지 시각 확인
- spike variant F/G 결과와 비교

## 14. 비용 변화

| 항목 | 변화 |
|---|---|
| chain_bg_render 모델 | 변경 없음 (이미 gpt-image-2) |
| chain_bg_render 호출 수 | 변경 없음 |
| chain_bg_render 입력 토큰 | 약간 증가 (image 2개 → 단일에서 multi) — image input 비용 증가 추정 ~30% |
| chain_bg_planning 입력 토큰 | floor plan prompt prepend 시 ~1500자 추가 — 미미 |

기존 chain_only mode: 비용 그대로. floor_plan_anchored: chain_bg_render multi-image input으로 비용 ~$0.80 → ~$1.04 / EP 추정.

## 15. Phase 5 / 6 연계

- **Phase 5**: scene_image_pipeline에 같은 패턴 적용 — 도면 + chain_bg + entity refs (3+ image edit)
- **Phase 6**: E2E 5 시나리오 + baseline 비교, mode 전환 cascade 검증

본 Phase 4는 chain_bg_render 단계의 도면 통합만 책임. scene 이미지는 그대로 (Phase 5에서).

## 16. 의사결정 사항 (확정 — 추가 질문 없음)

| 항목 | 결정 |
|---|---|
| floor plan ref 1순위 | YES — 도면이 가장 강력한 spatial authority |
| chain_bg_planning depends_on에 location_floor_plan | NO — best-effort 로드 (manifest 단순화) |
| chain_bg_render depends_on에 location_floor_plan | YES — 명시적 의존, not_applicable이면 skip |
| multi-image vs sequential single | multi-image (gpt-image-2 native) |
| prompt 변경 | 없음 (코드 prepend로 처리) |
| 새 토글 추가 | 없음 (`background_mode`로 충분) |
| order 변경 | 없음 (chain_bg_render 24.7 유지) |

## 17. 진행 위치

| Day | Phase | 상태 |
|---|---|---|
| 1 | P0 | ✅ aacccd9 |
| 3 | 1b | ✅ 553fea2 |
| 4 | 2 | ✅ 04840cc |
| 5~6 | 3 | ✅ 4eac12c |
| **7** | **4** | **🔵 본 design** |
| 8 | 5 | 대기 |
| 9~10 | 6 | 대기 |
