# Stage D: 야외 3레인 공통 병합 (lane pipe → production) Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Stage A(레인 판정)·B(레인1 마커 맵)·C'(레인2 seed→SITE PLAN, v4 canary 확정)를 production 파이프라인에 배선 — 야외 레인 샷의 콘티=마커 스케치, 스틸 참조=[스케치(+seed)]+엔티티(+prev), 실내·flag OFF 경로 byte-identical.

**Architecture:** 신규 flag `outdoor_lane_pipe_enabled`(default OFF, `outdoor_lane_plan_enabled` 선행 필수). 신규 step `outdoor_structure_seed`(order 21.90)가 레인2(structure_plate) 세그먼트 보유 그룹마다 seed 멀티롤+SITE PLAN(gpt edit, canon map_prompt 재사용)을 생산. `shot_conti_light_step`이 lane 바인딩 샷을 마커 파이프(geometry→PIL→sketch v3+leakage)로 분기해 CP `lane_conti`로 영속. `still_recipe_service`가 lane 샷 참조를 [스케치(+seed 레인2)]+엔티티(+prev=classify 판정)로 조립(플레이트/photo canon 0).

**Tech Stack:** 기존 primitives 재사용 — outdoor_marker_map(geometry/PIL/스케치/leakage), outdoor_structure_seed(build_structure_seed_prompt v3), outdoor_place_canon(build_map_prompt=SITE PLAN 계약), multiroll_select/multiroll_gemini, shot_conti_light(sidecar 지문/stale archive/asset 등록), still_recipe.

## Global Constraints

- flag OFF = 전 경로 byte-identical (config_hash 미변, 실내 무변)
- 시나리오 고유명/증상 어휘 하드코딩 0, substring 의미 판단 0
- 프롬프트 팩 덮어쓰기 금지 — marker_map_sketch **v3 신규 디렉토리**
- LLM 입력(씬 전문 등) 절대 자르지 않기
- 기존 모듈 삭제 금지 — build_aerial_map_prompt 는 deprecated 표기만
- Codex 누적 배선 조건(A/B/C): ①팩·모델 config_hash 스탬프(ON시만) ②persisted lane plan 소비 직전 validate_lane_plan 재실행(fail-closed) ③camera_direction 결손 fail-closed ④prev 근사→**classify prev 판정(배경 동일성 SOT) 재사용** ⑤map/conti/photo-canon 참조 0 + lineage 검증 ⑥seed 입력 SOT=outdoor_place_spec persistent_site 검증 서술(raw entity_merge 아님)+excluded_transient negative contract

---

### Task D1: marker_map_sketch v3 팩 — seed 룩 조항 정식화

**Files:**
- Create: `prompts/_base/marker_map_sketch/3.<ts>/sketch_head.md` (v2 복제+SEED LOOK 조항), `leakage_judge.md` (v2 동일)
- Modify: `backend/app/modules/pipeline/outdoor_marker_map.py` (SKETCH_PACK_VERSION_MAP "3"+`build_marker_sketch_prompt(seed_look_attached: bool=False)` — v3에서만 조항 조립, v1/v2 시그니처 불변)
- Test: `backend/tests/pipeline/test_outdoor_marker_map.py`

**Interfaces:**
- Produces: `build_marker_sketch_prompt(..., seed_look_attached=False, prompt_version="3")` — seed_look_attached=True 시 "SECOND attached image = STRUCTURE LOOK authority(형태·비율·개구부·실루엣), FIRST map = 배치·카메라 SOT" 조항 포함(v4 canary SKETCH_SEED_CLAUSE 정식화). v1/v2 selector 로 조립 시 조항 0(불변).
- 테스트: v3+seed_look_attached=True 조항 존재 / False·v2 조항 부재 / 미지 selector ValueError.

### Task D2: outdoor_structure_seed_step — 레인2 그룹 seed+SITE PLAN

**Files:**
- Create: `backend/app/core/steps/outdoor_structure_seed_step.py`
- Modify: `backend/app/core/config.py`(`outdoor_lane_pipe_enabled: bool = False`), `backend/app/core/applicability.py`(`_if_outdoor_lane_pipe`), `backend/app/core/step_manifest.py`(order 21.90), `backend/app/core/steps/__init__.py`
- Modify: `backend/app/modules/pipeline/outdoor_structure_seed.py` — `derive_seed_inputs(spec, lane_plan, group_id) -> {structure_desc, interior_note_en, excluded_transient_en}`: structure_desc=spec persistent_site 검증 서술 조인(SOT), excluded_transient=negative contract 절, interior=같은 building 그룹 실내 loc 서술(규모 SOT). `build_structure_seed_prompt`에 `excluded_transient_en:str=""` 인자(v3 조립 시 "EXCLUDED (never depict): ..." 절, 기본 불변).
- Test: `backend/tests/pipeline/test_outdoor_structure_seed.py`(derive/negative 절), `backend/tests/core/test_outdoor_structure_seed_step.py`

**동작:** lane plan CP에서 structure_plate 세그먼트 보유 그룹 열거 → 그룹마다 ①seed=nb2 멀티롤 3롤+critique(공통 파이프) ②siteplan=gpt-image-2 edit(canon map_prompt v3 재사용, 참조=[seed_sel], sanitize 1회 재시도). 산출=`{groups: {gid: {seed_path, seed_asset_id, siteplan_path, siteplan_asset_id, records}}}` + intermediate ImageAsset 등록(seed: 참조 0 / siteplan: input=[seed]). CP 소비 직전 validate_lane_plan 재실행. config_hash=structure_seed 팩 v3+canon 팩+nb2/gpt 모델+schema(ON시만 스탬프 불필요 — step 자체가 flag 게이트). applicable=lane_pipe ON+lane plan 존재+대상 그룹>0.

### Task D3: shot_conti_light_step lane 분기 — 마커 파이프 콘티

**Files:**
- Modify: `backend/app/core/steps/shot_conti_light_step.py`(`_run_lane_conti()`, `_config_hash` lane 스탬프, `_register_intermediate_assets` lane 확장), `backend/app/modules/pipeline/shot_conti_light.py`(`register_intermediate_assets` lane_conti 지원)
- Test: `backend/tests/core/test_shot_conti_light_step.py`(기존 파일에 lane 케이스), `backend/tests/pipeline/test_shot_conti_light.py`

**동작(lane_pipe ON일 때만):** lane plan CP 로드→validate_lane_plan 재실행(위반=AppError fail-closed)→lane 바인딩 샷마다: base map=(map_marker→outdoor_place_canon map_png / structure_plate→outdoor_structure_seed siteplan; 결손=AppError step.missing_input)→`run_marker_geometry_shot`(gpt)→`render_marker_map`(PIL)→sketch=gpt edit refs=[control(+seed_sel if structure_plate)] 프롬프트=v3 팩(seed_look_attached=lane2)+camera_direction(shot_staging, **결손=AppError**)→leakage VLM gate(3회 재시도, 실패=샷 failed 격리). lane 샷은 일반 콘티/맵 콘티 대상에서 제외. sidecar 지문(스펙+geometry 입력+base map bytes+팩·모델)+stale archive. CP `data.lane_conti={tag: {image_path, control_path, geometry, lane, base_map_path, seed_path?, leakage, asset_id...}}`. asset 등록: control(input=[base map])→sketch(input=[control(+seed)]). config_hash: ON시 marker geometry 팩+sketch v3 팩+leakage 모델+lane plan EVIDENCE_VALIDATION_VERSION 스탬프.

### Task D4: still_recipe lane 참조 배선

**Files:**
- Modify: `backend/app/services/still_recipe_service.py`, `backend/app/modules/pipeline/still_recipe.py`(`build_still_refs` lane 분기)
- Test: `backend/tests/pipeline/test_still_recipe.py`

**동작:** conti CP `lane_conti`에 있는 샷 → refs=[SKETCH(스케치)+(STRUCTURE LOOK seed, structure_plate만)]+엔티티(+prev=classify 판정 그대로 — usage_en 포함, 레인2 prev=background-only usage 는 classify 저작 usage_en 이 SOT). **플레이트/photo canon/map 참조 0** (partition_lineage_ids 로 input=전체/reference=엔티티만 유지). bg_only lane 샷=스케치 유지+NO PEOPLE 절. lineage: input_image_ids=[sketch_asset, seed_asset?, prev, 엔티티].

### Task D5: fresh-run applicability 배선

**Files:**
- Modify: `backend/app/core/applicability.py` — `outdoor_place_spec`/`outdoor_place_canon` applicable 조건에 `outdoor_lane_pipe_enabled` 포함(기존 map_conti 조건 OR). canon 은 lane_pipe ON 시 **map_marker 세그먼트 보유 그룹만** 대상(structure_plate 전용 그룹 skip — photo canon 누출 방지·비용 절감), lane plan CP 부재 시 전 그룹(기존 동작).
- Test: `backend/tests/core/test_applicability.py`(기존 파일에 케이스 추가)

### Task D6: 결정론 테스트 총정리 + byte-identical 검증

- 신규·수정 테스트 전부 + `tests/unit` 전체 PASS
- flag OFF byte-identical: conti step `_config_hash` OFF 값 불변 테스트, 실내 경로 무변(레인 분기 진입 0) 테스트
- deprecated: `build_aerial_map_prompt` docstring 에 폐기 사유(v4: SITE PLAN=canon map_prompt 재사용) 명기, 신규 소비 0

### Task D7: Codex diff 리뷰 → 반영 → 커밋

### Task D8: 새 프로젝트 fresh full E2E (별도 러너, scratchpad)

- 새 프로젝트 생성(시나리오 동일본 업로드)→전체 파이프라인(flag: lane_plan+lane_pipe+still_recipe v1+plate_multiroll+배경 v14 selector ON — 프로세스 로컬)→배경/콘티/스틸 전수→갤러리(8899, LAN IP)→사용자 육안.

## Self-Review 노트

- prev 정책: canary 의 same-segment/same-cluster 근사는 폐기 — classify prev 판정(배경 동일성 SOT)이 이미 실제 overlap 계약. Codex 조건 ④ 충족.
- seed SOT: raw entity_merge 조인(캔ary)이 아니라 outdoor_place_spec persistent_site 검증 서술+excluded_transient negative contract. Codex 조건 ⑥ 충족.
- 레인2 맵=outdoor_place_canon build_map_prompt 재사용(신규 프롬프트 저작 0) — v4 canary 실증 그대로.
- outdoor_view_cluster(DAG)·outdoor_map_conti·build_aerial_map_prompt=dormant(삭제 금지, 신규 소비 0).
