# 검색 그라운딩 씨드 + 콘티 이후 3단계 고정 — production 설계

작성 2026-07-28. **실험 승격 설계**(감독 말판 보드 실험 6~13회차 실측 근거).
사용자 지시로 실험을 끝내고 production 배선으로 넘어간다("이제 본게임").

## 1. 사용자 지시 원문

> "야외 구조물(건물이나 규모가 있는 모든 복잡한 것들)은 배경 생성시에
> (씨드 생성시에) 모두 검색 기반 + VLM 으로 하게 수정하고 콘티 관련 부분도 /
> 콘티 생성 단계 다음 부터는 모두 배경 삽입 → 중요한 VLM 읽고 엔티티를 어찌
> 넣을지 설명 추가 → 최종 샷에 엔티티 삽입 형태로"

## 2. 현행 (실측)

| 대상 | 현행 |
|---|---|
| `outdoor_structure_seed` | **순수 T2I**. 참조 이미지 0, Gemini nb2 멀티롤. 프롬프트=`persistent_site` 검증 서술 + human scale + exterior/interior note + place text + no_people |
| 콘티 (`shot_conti_light` lane) | 플레이트/seed 를 참조로 i2i |
| 콘티 이후 (`bgfirst`) | `build_bgfirst_bg_prompt`(배경) → `build_bgfirst_final_prompt`(엔티티). **2단계** |
| VLM 콘티 판독 | **없다** (`grep` 0건) — 신규 |

## 3. 실험에서 확인된 근거 (그대로 승계)

1. **`web_search` 기본값은 텍스트만**이다. 검색 그라운딩은
   `search_content_types:["image","text"]` 를 켜야 **사진이 컨텍스트로**
   들어간다. 텍스트만이면 산출의 현지성이 눈에 띄게 약하다(표지판이 백색
   무지로 비고, 검색을 8→20회 던지며 헛돈다).
2. **검색을 지휘하는 프롬프트는 시나리오 언어**여야 한다. 영어 지시문 안에서
   "원본어로 검색하라"고 시키는 간접 방식은 관광지·타지역으로 샌다. 언어는
   **씬 원문에서 판정**한다(특정 언어 하드코딩 금지).
3. **회수 사진은 VLM 이 비교해 1장을 고른다.** 지시문에서 관광지를 배제해도
   웹이 그쪽을 밀어올린다(23장 중 다수가 관광 콘텐츠였다). 장마다 개별
   판정이 아니라 **한 번의 비교 선택**이어야 "그 중 하나"가 된다.
4. **참조는 형태 전용**이다. 배치·구도·시점·날씨·시간대는 참조에서 가져오지
   않는다 — 그쪽은 도면/보드와 CAMERA 계약이 소유한다.
5. **VLM 콘티 판독이 방향 반전을 막는다.** 러프 콘티는 선이 성겨 편집 모델이
   빈틈을 자기 해석으로 메운다. 화면 기준 서술을 텍스트로 주면 `placement_kept`
   실패가 PASS 로 돌아섰다.

## 4. 목표 흐름

```
[씨드/배경]  구조물 판정 → 검색어 저작(원본어) → 웹 이미지 검색
             → VLM 비교 선택 1장 → 그 1장을 형태 참조로 seed 생성
[콘티]       같은 선택 실사 계보를 콘티 참조에 물린다
[콘티 이후]  ① 배경 삽입 → ② VLM 이 산출을 읽고 **엔티티를 어떻게 넣을지**
             서술 생성 → ③ 그 서술을 실어 최종 샷에 엔티티 삽입
```

## 5. 대상 집합 — 결정론으로 고정한다 (Codex BLOCKING-1 수용)

**LLM 이 "검색할지 말지"를 판정하지 않는다.** 초안은 seed 대상 안에서 다시
"나라·시대마다 형태가 달라지는 항목만" 고르게 했는데, 그건 새 판정기를 하나
더 만드는 것이고 **복잡 구조물을 조용히 순수 T2I 로 남길 수 있다** — 사용자
원문("모든 복잡한 것들은 모두 검색 기반")을 조용히 어기는 경로다.

- **검색 필수 대상 = authoritative lane plan 의 `structure_plate` 바인딩
  그룹, exact parity.** 이 집합의 entry 가 누락/failed/non-ok 면 소비자가
  **fail-closed** 한다.
- `outdoor_seed_all_groups_enabled` 로 딸려 오는 비복잡 그룹은 **검색 계약
  밖**임을 명시한다(`collect_lane2_groups(all_groups=True)` 는 lane status=ok
  인 모든 야외 그룹을 주므로 복잡 구조물 집합과 동치가 아니다).
- LLM 의 역할은 **원본어 검색 지시문 저작 하나**다.
- 코드에 시설명·지역명을 박지 않는다(시나리오 의존 코딩 금지).

### 5.1 검색 입력의 관할 분리 (Codex HIGH 수용)

검색 **의미**는 `persistent place spec` + `world facts` 에서만 나온다.
**씬 원문은 `source_language` 판정에만 쓰이고**, 인물·사건·임시 사물·배치·
작품 고유명사를 검색 의미에 기여시키지 않는다. 이 문장을 저작 system 계약과
validator 양쪽에 넣는다 — "작품 고유명사 금지"만으로는 부족하다(실험 하네스는
원문을 별도 블록으로 격리했는데, production 스펙 초안에서 그 격리가 빠졌다).

## 6. 신설 단계 계약

### 6.1 검색 지시문 저작 (원본어)

입력 = world facts(지역·시대) + 장소 항목 + **씬 원문 전문**(자르지 않는다).
출력 = `source_language` / `search_directive_native` / `search_terms_native`.
지시문은 **전부 원본어**로 쓰고, 관광 명소·여행 안내/예약 페이지·장식용·
예술 작품형을 배제하며, 사진에서 읽을 것(구조·비례·재료·색·부속·표지 배치)과
가져오지 말 것(배치·구도·날씨·시간대·주변)을 명시한다. 작품 고유명사 금지.

### 6.2 검색 → VLM 비교 선택

`responses.create(tools=[web_search(search_content_types=["image","text"],
image_settings={max_results,caption})], include=["web_search_call.results"])`
로 사진을 회수 → 다운로드(원본 URL 실패 시 썸네일 대체) → **비교 선택 1회**.

판정 계약: 그 종류의 시설인가 / 그 지역·시대인가 / 관광·랜드마크·장식·
일회성 디자인이 아닌가 / 실사진인가 / 형태를 읽을 만큼 크고 선명한가.
★**주변 환경·날씨·시간대·앵글은 판정에서 제외**(형태 전용 참조이므로).
후보마다 한 줄 한국어 사유 + 선택 번호. 쓸 만한 후보 0 = fail-closed.

### 6.3 씨드 생성

기존 `build_structure_seed_prompt` 의 절 구성을 유지하고 **참조 첨부 경로**와
FORM-ONLY 계약절을 추가한다. 즉 T2I → i2i 로 바뀌지만 텍스트 계약은 승계.

### 6.3.1 권위 정의 — dual SOT 방지 (Codex BLOCKING-3 수용)

| 층 | 지위 | 관할 |
|---|---|---|
| 생성된 **seed** | 이 작품 구조물의 **IDENTITY/LOOK SOT** | 이 구조물이 어떻게 생겼는가 |
| 선택된 **웹 사진(form_ref)** | seed 생성 입력의 **FORM REFERENCE** | 그 종류의 일반적 구조·비례·재료·부속 관습 |

**콘티는 seed 만 직참조한다.** form_ref 는 콘티에 붙이지 않고 **transitive
lineage** 로만 남는다. 콘티가 사진을 또 받으면 "사진 속 실제 장소"와 "작품
구조물"이 두 권위가 되어, 사용자가 지적했던 발명·불일치가 다른 형태로
재발한다. 사용자 지시의 "콘티 관련 부분도"는 **그라운딩 계보가 콘티까지
내려간다**는 뜻으로 구현한다(seed 를 통해 전달).

ImageAsset lineage: `structure_seed.input_image_ids` ∋ form_ref asset.
`conti.input_image_ids` ∋ seed asset (form_ref 는 미포함 — prompt refs 와
lineage 가 정확히 일치해야 한다).

### 6.4 콘티 이후 3단계 — 판독은 "검증 후 서술" (Codex BLOCKING-4 수용)

초안은 판독 계약을 "텍스트와 어긋나면 그림 쪽을 적는다"로 썼는데, 그러면
**① 배경 삽입이 방향·배치를 망가뜨린 경우 판독이 그 오류를 충실히 서술하고
③이 그대로 보존한다.** 실험에는 `placement_kept` 게이트 축이 있어 걸렀는데,
production 에 그대로 옮기면 그 축을 잃는다.

- **① 배경 삽입** — 기존 `build_bgfirst_bg_prompt` 승계.
- **② VLM 판독 (신설, 1회 호출·2역할)**
  1. 입력 = 원본 콘티 + 배경 삽입 산출 + **기대 엔티티 slot roster**.
  2. 먼저 콘티→배경 **보존 판정**(`placement_kept` + 위반 목록).
  3. `false` → 판독을 **소비하지 않고** ① 교정 재시도, 상한 소진 시
     fail-closed.
  4. `true` 일 때만 **배경 삽입 산출의 픽셀 기준**으로 엔티티 배치 서술을
     소비한다.
- **③ 엔티티 삽입** — ②의 결정론 직렬화 블록을 실어 최종 샷 생성.

### 6.4.1 판독 대상·스키마 (Codex BLOCKING-5 수용)

대상은 "인물 있는 샷"이 아니라 **최종 첨부 엔티티 참조가 1개 이상인 샷**이다
(사용자 원문이 "엔티티"다). 엔티티 0 = `not_applicable` 로 두고 호출하지
않는다.

스키마는 **character placement** 와 **prop placement** 로 나눈다.
- 기대 slot 집합과 **exact parity**(누락·중복·추가 0)를 검증한다.
- carried prop 은 **어느 인물의 어느 손/신체에 붙는지** 관계를 갖는다.
- 산출은 prose blob 이 아니라 **구조화 JSON → 결정론 직렬화**로 프롬프트에
  싣는다(hash·재개·provenance 안정).

### 6.5 외부 이미지 취득 경계 (Codex HIGH 수용)

검색 URL 은 **외부 입력**이다. production 에 SSRF·대용량·위장 파일 경로를
열지 않도록: https 전용 / **redirect 후** private·loopback·link-local 차단 /
timeout·최대 바이트·content-type 검사 / 실제 image decode 성공 / 픽셀 상한 /
SVG·HTML 거부 / 안전한 PNG 재인코딩 후 프로젝트 경로 저장. 원본 실패 →
thumbnail 대체에도 **같은 정책**을 적용한다.

## 7. 격리·안전

- 기존 모듈 삭제 금지 — 신규 모듈 추가 + 플래그로 배선.
- 프롬프트 팩은 **새 버전 디렉터리**로 발행하고 selector 배선까지 한 단위로
  처리한다(미배선 팩 발행 금지 — 과거 2건 실측).
- `config_hash` 에 신규 계약(팩 버전·검색 계약·모델)을 편입해야 재실행이
  유도된다.
- 검색·선택 산출(후보 전량·판정 원문·선택 사유·출처 URL)은 감사 기록으로
  남긴다. 이미지 파일은 프로젝트 루트 안(경로 CHECK 제약).

## 8. 미결 5건 — 확정 (2026-07-28)

내 분석으로 결정하고 Codex 의견을 참고로 반영했다(사용자: "codex 는 그냥
의견이라고 생각해"). 4·5 는 Codex 의견이 더 정확해 내 초안을 수정했다.

| # | 확정 | 근거 |
|---|---|---|
| 1 | **search subject 당 winner 정확히 1장**. 첫 production 버전은 그룹 primary subject 1개 = 사진 1장 | "여러 개 중 하나"라는 비교선택 의미가 subject 마다 성립해야 한다. 임의 N장을 한 seed 호출에 동시 첨부하지 않는다 |
| 2 | **독립 선행 스텝** `outdoor_structure_form_reference` | 두 소비자(seed·conti)가 같은 자산을 재사용하고, 검색은 네트워크·비결정·비용 수명이 seed 생성과 다르다 |
| 3 | **필수 대상은 fail-closed**. 네트워크/429/5xx 는 retryable failure. **순수 T2I 자동 degrade 금지** | 조용한 degrade 는 사용자가 지적한 "배경이 이상해"를 그대로 재생산하면서 아무도 모르게 만든다. 운영자 override 가 필요하면 별도 mode+감사+hash 계약으로 |
| 4 | **최종 엔티티 참조 ≥1 인 샷 전부**(초안 "인물 샷만"을 수정) | 사용자 원문이 "엔티티"다. 엔티티 0 = explicit `not_applicable` |
| 5 | **adapter seam 만 둔다. effective request 에 파라미터를 넣지 않는다** | `thinking` 은 이 계정에서 400 Unknown parameter(후보 25종·이미지 모델 4종 전수 실측). 400 받고 몰래 빼서 재호출하는 fallback 은 비용·provenance 를 숨기므로 금지. 공식·실측으로 열리면 contract version bump + config hash drift 로 활성화 |

## 9. hash · 재개 · 팩 계약

- **검색 스텝 `config_hash`**: schema/validation version, 저작 팩+alias/
  physical model, 검색 오케스트레이터 alias/physical model, web tool 계약
  (`search_content_types`·max_results·candidate cap·include), 선택 팩+schema+
  VLM alias/physical model, retry 정책, **safe-download 정책 버전**.
- **그룹별 fingerprint**: persistent spec + world facts + 언어판정 원문 hash
  + 저작 prompt/schema. 결과에 queries / raw result order / candidate bytes
  sha / **verdict 전문** / chosen asset 을 durable persist. resume 은
  fingerprint 일치 **+ 모든 파일·sha 재검증** 때만 0콜 재사용, force 는 검색
  부터 우회.
- **seed/conti per-output fingerprint**: chosen form-ref bytes sha + asset
  lineage + FORM-ONLY 팩. CP entry 에 resolved version 과 ref asset id/sha 를
  기록하고 **소비자가 exact revalidate**.
- **scene image `config_hash`**: readback 팩/schema/계약, VLM alias+physical
  model, preservation 정책·retry 수, final insertion 팩. per-shot readback
  fingerprint = bgfirst 배경 bytes + 원본 콘티 bytes + 기대 엔티티 roster +
  model/schema/prompt 전문.
- **readback 은 top-level manifest 스텝이 아니라 `still_recipe` 내부의
  durable phase** 로 둔다 — 입력 bg 가 같은 shot transaction 에서 생기므로
  `records.json` 에 `entity_readback` phase 로 먼저 영속한 뒤 최종 이미지를
  만든다. 별도 모듈로 추출하되 가짜 ImageAsset 은 만들지 않는다.
- 신규 팩 = query author / selection judge / seed FORM-ONLY / entity readback
  / final insertion. **selector 배선·VERSION_MAP·config hash·핀 테스트가 같은
  커밋**에 들어간다(미배선 팩 발행 금지 — 과거 2건 실측).
- MINOR: `outdoor_structure_seed` manifest label·주석이 이미 제거된
  `+SITE PLAN` 을 아직 말한다 — 이번 schema 변경 때 현행화.
