# G4.4 Continuity Lift Design

본 문서는 G4 spec (`docs/superpowers/specs/2026-05-04-g4-render-prompt-card-strategy.md`)
§7 G4.4 섹션의 detail sub-spec 이다. G4.3 ID-policy lift (commit `51d6c5e`)
의 자연 후속.

- 작성일: 2026-05-05
- 상태: initial draft
- 선행 완료:
  - G4.1 RenderPromptCard bootstrap (5d045f3 + c656ce3 + 86473df)
  - G4.2 background-binding lift (14d14cb — `_card_metadata` envelope-sibling,
    `build_background_binding()` constraint reshape, v17 Background Binding
    compact section, 4 canary scripts, 25+10 tests)
  - G4.3 ID-policy lift (51d6c5e — 109 prose lines → **4 id_policy sub-fields**
    (face_identifiability_rule / body_part_focus_rule / close_framing_face_phrasing /
    reproduction_surface_rule — Override O-9 정정: 기존 carry text "5 sub-fields"
    부정확), v18 ID Policy compact section, 5 canary scripts, 50+18 tests,
    tiktoken Δ-2073)
- 목적: v18 system.md 의 3 continuity-related prose section (약 66 lines —
  Section A "교차 샷 고정 요소 통합 규칙" 12 lines + Section B "앞쪽 참조 샷
  처리 — ref_usage 유형별 지시" 35 lines + Section C "절대 규칙 — 단일 시점"
  19 lines) 을 `continuity_elements_used` field 의 sub-field constraints 로
  lift, system.md 에서 해당 long-form sections 제거 후 compact "Continuity"
  reference section 으로 교체. 4 metric STRICT 0 검증 + tiktoken 실측 token
  delta gate ≤ -<value> (R1+R2 audit 에서 정정).

## Round Override (audit-driven, applied before body)

본문보다 우선 적용. Codex 라운드 감사 + lead/user 결정 결과를 본문 반영 전
fast-path 로 박는다. 본문은 정식 패치에서 동기화하되, 그 전까지 본 표가 진실의 source 다.

### Round 1 — Codex (R1) + Claude (R2) dual audit 결과 (2026-05-05)

R1 verdict: `NEEDS_REVISION` (3B + 3I + 2M + carry checks). R2 verdict: `NEEDS_REVISION` (3B + 5I + 5M).

| ID | Source | 결정 (Override) | 적용 위치 |
|---|---|---|---|
| **O-1** | R1-B2 + R2-B1 | **Token gate threshold = system-only `token_delta = candidate - baseline ≤ -540`** (1차 estimate-based binding gate). Wave 1-B tiktoken 실측이 더 강한 수치 (e.g. -480) 로 revise 가능 — Round 2 Override row 로만 변경, **upward revise 는 re-audit 필요**. system+inject 합산 (≤ -150) = secondary report-only metric (canary JSON 보조 기록 / no gate). | §3.4 + §5.1 + §5.3 |
| **O-2** | R1-B1 + R2-I5 | **§3.3 v19 `## Continuity` compact section line count gate: `wc -l` ≤ 32 lines strict** (binding). 각 bullet 반드시 1 line — 80-col wrap 허용 안 됨 (wrap 줄도 line 1 로 카운트). 통합 테스트 §6.2 `test_v19_continuity_section_line_count_under_32` 신규 추가. **현재 §3.3 본문 (line ~470-508) 가 34 lines 위반 → 본문 32 lines 이내로 재작성**. | §3.3 + §6.2 |
| **O-3** | R1-B3 | **detail_steps.py:1555-1563 zoom_in_detail prose 잔존 검증 완료**: `if has_zoom_in_detail:` 블록 안 9 줄 prose ("[중요 — 확대 샷 원칙 (zoom_in_detail)] / 새 인물·소품·자세 추가 금지 / 인체 부위 초점이면 보통명사 / 환경 동일") 가 v18 system.md 와 별개로 존재. **Wave 2 wiring scope 에 prose 9 줄 삭제 + card.continuity_elements_used.ref_usage_constraints.zoom_in_detail 가 대체** 명시. §2.3 Producer/Consumer matrix + §3.2 + §3.4 + §4 Lift Mapping + §7.2 Wave 2 모두 detail_steps.py:1555-1563 = "Wave 2 삭제 대상" row 추가. token reduction = system 외 별도 인접 계산 (~9 lines × 15 ≈ 135 tokens; Wave 1-B 실측 보정). | §2.3 + §3.2 + §3.4 + §4 + §7.2 |
| **O-4** | R2-B2 | **forward_zoom_targets 는 G4.4 lift 대상 아님 (Option A 채택)**: detail_steps.py:1566-1620 의 forward zoom prose inject 는 별도 mechanism (v19 LLM consumer 영향 0 — 이미 prose 형태). G4.5 prompt slimming pass 에서 G4.1 lossless carry status 재검토. lift_status 에 `continuity_forward_zoom_lifted` 키 **추가 안 함**. §1.2 Non-goal 에 명시. | §1.2 + §2.2 |
| **O-5** | R2-B3 | **assert_continuity 확장 = strict required-keys subset (set superset 검증)**: 신규 sub-field required keys = `EXPECTED_REQUIRED <= returned_keys` (set equality 가 아닌 superset). extra keys 허용 (G4.5 forward-compat). §6.1 unit test row 786 등 모두 `set(returned.keys()) >= EXPECTED_SET` 으로 수정. _card_metadata 는 G4.3 R2-I5 carry 따라 free-form 유지. | §2.2 + §6.1 |
| **O-6** | R1-I1 | **Sub-shape 명명 정정**: "4 new top-level sub-fields" → "**3 new sibling policy dicts (`cross_shot_id_substitution_rule` / `ref_usage_constraints` / `view_consistency`) + extended `constraints` list (G4.1 base 2 → G4.4 7 strings)**". 기존 G4.1 sub-field (fixed_elements / previous_shot_refs / forward_zoom_targets) 는 shape 변경 없음. spec §1.1 / §2.1 / §2.2 / §3.4 / §4 / §10 / §12 모든 "4 new top-level" 문구 정정. | §1.1 + §2.1 + §2.2 + §3.4 + §4 + §10 + §12 |
| **O-7** | R1-I2 + R2 | **Cross-card reference = paired string substring (구조화 dict 미도입)**: `view_consistency.id_policy_cross_ref_for_body_part_focus` (string, 리터럴 substring `"id_policy.body_part_focus_rule"` 포함) + `ref_usage_constraints.zoom_in_detail.body_part_cross_ref` (string, 동일 substring 포함). 단위 테스트 §6.1 +2 rows / 통합 테스트 §6.2 +1 row. Wave 1-A 코드에 maintainer 코멘트 ("rename 시 G4.4 spec patch 필요") 추가. `id_policy.py` builder source 코멘트 sync. | §0 (본 표) + §2.2 + §3.3 + §6.1 + §6.2 + §8.1 |
| **O-8** | R1-I3 | **silent-fallback 잔존 carry**: `backend/app/core/version_registry.py:148-155` 의 `MODULE_VERSIONS.get(..., "0.0.0")` / `_MODULE_INFO.get(..., {})` = G4.1 inheritance, G4.4 scope 외. **G4.5 또는 별도 cleanup PR carry follow-up**. G4.4 신규 코드 (Wave 1-A producer + canary + tests) 는 새로운 silent-fallback 도입 절대 금지. | §8.2 + §11/§12 carry |
| **O-9** | R2-I3 verification | **lift_status keyset 정정**: 실제 G4.3 production = `id_policy` **4 sub-fields** (face_identifiability_rule / body_part_focus_rule / close_framing_face_phrasing / reproduction_surface_rule). carry text "5 sub-fields" 부정확. spec §1 line 14-16 + §2.2 _card_metadata.lift_status block + §11/§12 모두 "5 sub-fields" → "4 sub-fields". G4.3 carry 4 lift_status keys = `id_policy_composite_lifted` / `id_policy_close_framing_face_lifted` / `id_policy_reproduction_surface_lifted` / `rule_h_lifted`. **G4.4 신규 4 lift_status 키**: `continuity_cross_shot_id_substitution_lifted` / `continuity_ref_usage_constraints_lifted` / `continuity_view_consistency_lifted` / `continuity_constraints_extended`. | §1.1 + §2.2 + §11 + §12 |
| **O-10** | R2-I1 | **Lift mapping line range footnote**: §4 footnote — "Section boundaries inclusive 행 = Section A 109-120 / Section B 122-156 / Section C 308-326. Blank lines 121/157/307/327 = 인접 섹션 보존 — Wave 1-B v19 drafting 에서 line-by-line diff 로 결정. 인접 섹션 영향 없도록 보존." | §4 footnote |
| **O-11** | R2-I2 | **Canary multi-ref shot atmosphere_reference 필터**: `g4_4_atmosphere_no_layout_import.py` shot-level filter = `any(ref.ref_usage == 'atmosphere_reference' for ref in previous_shot_refs)`. multi-ref 시 atmosphere check 만 적용. `exact_background` co-presence 시 false-positive baseline 비0 → **P1 follow-up 추적**. 단위 테스트 fixture +1 row in §6.1 — `test_g4_4_view_mixing_canary_multi_ref_shot_filter`. | §5.1 + §5.3 + §6.1 + §7.2 step 17 + §8.1 |
| **O-12** | R2-I4 | **`compute_continuity_snapshot_hash` 동작 명시**: `compute_continuity_snapshot_hash(card) = sha256(json.dumps(card['continuity_elements_used'], sort_keys=True, ensure_ascii=False))[:16]`. `_card_metadata` 는 envelope sibling 이므로 자연 제외 — 추가 strip 불필요. G4.3 `compute_id_policy_snapshot_hash` 와 동일 패턴 (검증 완료). | §5.2 + §9 |
| **O-13** | R1-M1 | **§2.3 Producer/Consumer matrix 구체 wire 명시**: `_collect_card_inputs(snapshot, scene_idx, shot_idx) → build_render_prompt_card(...) → _card_metadata strip → JSON prepend (line ~1500-1610 in detail_steps.py)`. `build_continuity_elements_used()` 위치 = `render_prompt_card.py:676`. consumer = scene_detail user_prompt JSON dict. 누락된 wire = detail_steps.py:1555-1563 prose 잔존 (O-3 carry). | §2.3 |
| **O-14** | R1-M2 | **§6.2 line-count test 임계 도출**: spec §6.2 row "v18 - 50" → "**v18 baseline 587 lines - 32 (compact section gate O-2) - 보수 buffer 5 = 550 lines 미만**". 기존 "v18 - 50" 임의 수치 → 도출식 명시. | §6.2 |
| **O-15** | R2-M1 | **§3.3 ✓/✗ 예시 prose 삭제 footnote**: §2.4 footnote — "✓/✗ 예시 prose 삭제는 G4.3 baseline 통과 패턴 carry (commit 51d6c5e). canary STRICT 0 검증이 LLM 행동 보증." | §2.4 |
| **O-16** | R2-M2 | **§6.1 unit test count 정합**: "총 unit tests: ~38" → "**총 unit tests: ~32-38** (table 33 enumerate, +5 buffer for plan-R3+R4 audit additions)". | §6.1 |
| **O-17** | R2-M3 | **regex view-mixing canary brittleness footnote**: §5.1 row 592 + Wave 3 step 17 + §8.1 — "regex 는 1차 detector 만, baseline 비0 시 P1 follow-up 으로 LLM-validator (gpt-5.4-mini judge) 전환 검토. 의미 판단은 LLM-first." `feedback_no_regex_postprocessing.md` 참조. | §5.1 + §7.2 step 17 + §8.1 |
| **O-18** | R2-M4 | **Wave 5/6 numbering**: §7.2 — Wave 1-A / 1-B / 2 / 3 / 4 / 5 / 6 명시. **Wave 5 = production canary execution (사용자 실행). Wave 6 = single commit + push. test 파일 = Wave 4.** | §7.2 |
| **O-19** | R2-M5 | **Glossary t2i_prompt 영어 가정 footnote**: §9 — "t2i_prompt 영어 가정은 `feedback_t2i_storyboard.md` carry — Korean t2i prompt 도입 시 G4.5 에서 generic-noun 패턴 재검토." | §9 |
| 메타룰 | (G4.2/G4.3 carry) | — | Round 1 Override 결정은 body 본문보다 우선. Round 2 (예정) 추가 row 가 없으면 Round 1 결정 유지. body 와 Override 충돌 시 Override 우선 — body 동기화 필수. |

### Round 2 — (예정, R3 audit 후)

Wave 1-4 dispatch 후 dual review fix loop 진행 시 추가 발견 사항 row 추가.

### 메타룰 (G4.2 + G4.3 carry)

- Round Override 표 가 §1-§9 본문보다 우선.
- 본 spec drafting 직후 Codex (R1) + Claude code-reviewer (R2) 듀얼 audit 진행.
  결정사항은 본 표에 row 추가 + 본문 inline 동기화 (G4.2 + G4.3 패턴 답습).
- 절대 규칙 위반 (LLM 입력 truncation, 시나리오 의존 고유명사, silent fallback)
  발견 시 BLOCKING 최우선.
- silent fallback 패턴 (`(d or {})` / `(card or {})` / `(pair or {}).get(...)`)
  발견 시 BLOCKING. fail-fast (AppError) 강제 (`feedback_no_silent_fallback.md`).
- regex post-processing 의존 발견 시 IMPORTANT (`feedback_no_regex_postprocessing.md`).
  의미 기반은 LLM validator 또는 prompt 로.

---

## 1. Overview

### 1.1 Why now

G4.1 은 `RenderPromptCard.continuity_elements_used` 의 sub-shape 를 이미
빌드하고 inject 한다. 현재 `continuity_elements_used`
(`render_prompt_card.py:676-744`) 가 carry 하는 field:

- `fixed_elements` (list[dict] — 9 producer 필드 lossless 보존)
- `previous_shot_refs` (list[dict] — `scene_index` / `shot_index` /
  `ref_usage` 3 key per item)
- `forward_zoom_targets` (list[dict] — `scene_index` / `shot_index` /
  `description` 3 key per item)
- `constraints` (list[str] — base 2 string)

그러나 v18 system.md 에는 여전히 3 long-form prose section 이 남아 있다:

- **Section A** (line 109-120, 12 lines): `## 교차 샷 고정 요소 통합 규칙` —
  fixed_elements 의 `(인물이름 = C##)` 매핑이 명시된 경우 보통명사 인물 묘사
  ("An Asian man" / "a woman") 를 해당 C## 으로 대체 + 같은 인물 C## + 보통
  명사 이중 묘사 절대 금지 + 자세/위치/상태 그대로 유지 + 자세 묘사 후 같은
  인물 fixed element 별도 문장 반복 금지 + 1 ✓/✗ 예시 (line 117-120)
- **Section B** (line 122-156, 35 lines): `## 앞쪽 참조 샷 처리 —
  ref_usage 유형별 지시` 3 sub-section:
  - `zoom_in_detail` (line 126-141): 같은 시공간 카메라 이동/확대만, 새 인물
    /소품/배경/자세 추가 금지 + 초점 영역만 구체화 + "the same figure /
    the same room / the same surface" 연속 표현 + 인체 부위 초점이면
    `id_policy.body_part_focus_rule` 적용 + "앞 샷의 특정 영역을 확대한 같은
    사진" 원칙
  - `exact_background` (line 143-149): 같은 방 다른 순간/앵글, 인물·동작 새로
    묘사하되 배경 일관 + ignore/keep 자동 반영 (별도 언급 불필요)
  - `atmosphere_reference` (line 151-156): 다른 공간, 톤만 참고, 가구·벽
    배치 끌어오지 마라
- **Section C** (line 308-326, 19 lines): `## 절대 규칙 — 단일 시점
  (1인칭/3인칭 혼합 금지)` — 3인칭 전신 묘사 + 같은 인물 클로즈업 한 프롬프트
  결합 금지 + "Focus on" = 초점 영역 (시점 전환 X) + 소품 방향 한 방향만 +
  3 ✓/✗ 예시. line 313 references `id_policy.body_part_focus_rule` (G4.3
  cross-card 참조 — body-part Focus 시 C##O## 금지 reminder).

이 세 section 의 역할은 G4.1 이 card 에 deterministic 하게 넣어 주는 continuity
data 의 "어떻게 사용해야 하는가" 를 산문으로 풀어쓴 것이다. LLM 에게 남겨야
하는 것은 "card 의 continuity_elements_used.constraints 가 단일 출처이며 그
reminder 한 단락" 뿐이다.

추가로 **`backend/app/core/steps/detail_steps.py:1555-1563` 안 9 줄 inline
prose** (`if has_zoom_in_detail:` 블록 — "[중요 — 확대 샷 원칙 (zoom_in_detail)]
/ 새 인물·소품·자세 추가 금지 / 인체 부위 초점이면 보통명사 / 환경 동일") 이
v18 system.md 와 별개로 user_prompt 안에 prepend 되고 있음 (Override O-3 검증
완료). G4.4 Wave 2 에서 이 9 줄 inline prose 도 삭제 — card.continuity_elements_used.
ref_usage_constraints.zoom_in_detail 가 동일 데이터 carry 한다.

G4.4 는 그 분리를 완성한다: long prose 제거 (system.md 3 section + detail_steps
inline 1 block) → single compact "Continuity" section 으로 교체. shape 측면
에서는 **3 new sibling policy dicts (`cross_shot_id_substitution_rule` /
`ref_usage_constraints` / `view_consistency`) + extended `constraints` list
(G4.1 base 2 → G4.4 7 strings)** 추가 (Override O-6 명명 정정). 기존 G4.1
sub-field (fixed_elements / previous_shot_refs / forward_zoom_targets) shape
변경 없음. token 감소 quantified + cross-shot fixed-element double-description
rate ≤ baseline 검증 + ref_usage 위반 (zoom_in_detail 에서 새 인물·소품·자세
추가) 0 strict + view-mixing pattern (전신 + 부위 close-up 동일 prompt) 0
strict.

### 1.2 Non-goals (명시)

G4.4 는 다음을 **하지 않는다**:

- background-binding lift (G4.2) — 이미 완료. Rule A/C/E prose 변경 없음.
- ID-policy lift (G4.3) — 이미 완료. v18 의 `## ID Policy` compact section
  은 G4.4 가 변경하지 않음. line 313 의 cross-card reference (`id_policy.
  body_part_focus_rule`) 는 G4.4 view_consistency sub-field 와 1:1 보존만.
- prompt slimming pass (G4.5) — heading count ≤ 10 enforcement 는 G4.5
  까지 보류.
- **forward_zoom_targets prose lift 는 G4.4 scope 아님 (Override O-4 — Option A
  채택)**: detail_steps.py:1566-1620 의 forward zoom prose inject 는 별도
  mechanism 으로 v19 LLM consumer 영향 0 (이미 prose 형태로 inject). G4.4
  lift 대상 아님 — G4.5 prompt slimming pass 에서 G4.1 lossless carry status
  재검토. lift_status 에 `continuity_forward_zoom_lifted` 키 **추가 안 함**.
- Producer side 변경 — `scene_consistency` / `shot_dependency_t2i` /
  `forward_zoom_targets` upstream step 변경 없음. **그러나
  `build_continuity_elements_used()` 내부 로직 변경은 G4.4 의 자연 부산물
  로 in-scope** (G4.2 R2-B2 carry): continuity_elements_used 의 신규 sub-
  constraint 작성 (fixed_elements 별 cross_shot_id_substitution_rule /
  previous_shot_refs 별 ref_usage_constraints / view_consistency dict /
  신규 constraint strings) + `_card_metadata.lift_status.continuity_*_lifted`
  등 신규 rule key 추가.
- `_check_prompts()` / deterministic post-check 변경 없음.
- G3.2 owned sentinel 의 로직 변경 없음 (G4.2 와 무관, G4.4 와도 무관).
- `id_policy` field 의 sub-field 또는 constraint list 변경 없음 (G4.3 영역).
- G4.2 `background_binding.constraints` 변경 없음.
- scene_image_pipeline 또는 합성 로직 변경 없음.
- `continuity_elements_used.fixed_elements[*]` 의 9 producer 필드 (R1-I2
  lossless adapter carry) shape 변경 없음 — sub-field 추가만.

---

## 2. Card Envelope Sub-shape: continuity_elements_used 확장

### 2.1 G4.1 현재 shape (3 sub-fields)

`build_continuity_elements_used()` 가 반환하는 shape
(`render_prompt_card.py:676-744`):

```json
{
  "fixed_elements": [
    {
      "element_id": "FE-S12-3",
      "element_type": "body_full_pose",
      "character_name": "<character display name>",
      "description": "An Asian man stands rigidly, arms at sides",
      "applies_to_shots": [{"scene_index": 12, "shot_index": 4}],
      "source_facts": ["..."],
      "visual_inferences": ["..."],
      "creative_decisions": ["..."],
      "confidence": "high"
    }
  ],
  "previous_shot_refs": [
    {"scene_index": 12, "shot_index": 3, "ref_usage": "zoom_in_detail"}
  ],
  "forward_zoom_targets": [
    {"scene_index": 12, "shot_index": 5, "description": "..."}
  ],
  "constraints": [
    "fixed_elements are continuity inputs derived from prior shots — treat them as binding context, not as source facts to be invented",
    "previous_shot_refs with ref_usage='zoom_in_detail' must preserve the same moment, the same space, and the same lighting as the prior shot"
  ]
}
```

이 shape 는 continuity 의 "raw input" 은 carry 하지만, "어떤 wording 이
ref_usage 별 금지/권장인가" 의 구체 list 는 v18 prose 에만 존재한다. 결과
— 같은 룰의 이중 source (card constraints summary + prose detail) 가 G4.4
lift 대상.

### 2.2 G4.4 sub-shape 확정

G4.4 에서 `continuity_elements_used` 는 기존 G4.1 3 sub-field (fixed_elements /
previous_shot_refs / forward_zoom_targets — shape 변경 없음) 위에 **3 new
sibling policy dicts (`cross_shot_id_substitution_rule` / `ref_usage_constraints`
/ `view_consistency`) + extended `constraints` list (G4.1 base 2 → G4.4
7 strings)** 추가로 구성된다 (Override O-6 명명 정정 — 과거 표현 "4 new
top-level sub-fields" 부정확).

**Design decision (Override O-6 / Round 1 확정)**: 기존 3 sub-field 의
**list[dict] item** 안에 constraints 를 nest 하는 대신 **sibling policy dicts**
3 개로 평면 유지 — `_assert_continuity_elements_used_shape()` 검증 단순 +
canary detector 가 평면 dict.get 만으로 충분.

envelope contract (5 semantic field) 는 변경하지 않고, debug-only sub-field
는 `_card_metadata` 하위로 격리 (G4.2 R1-B1 carry — hash payload 에서 strip).

```json
{
  "schema_version": 1,
  "shot_key": {"scene_index": 12, "shot_index": 4},
  "render_strategy": {"...": "..."},
  "id_policy": {"...": "..."},
  "background_binding": {"...": "..."},
  "continuity_elements_used": {
    "fixed_elements": [
      {
        "element_id": "FE-SXX-N",
        "element_type": "body_full_pose",
        "character_name": "<display name>",
        "description": "An Asian man stands rigidly, arms at sides",
        "applies_to_shots": [{"scene_index": 12, "shot_index": 4}],
        "source_facts": ["..."],
        "visual_inferences": ["..."],
        "creative_decisions": ["..."],
        "confidence": "high"
      }
    ],
    "previous_shot_refs": [
      {"scene_index": 12, "shot_index": 3, "ref_usage": "zoom_in_detail"}
    ],
    "forward_zoom_targets": [
      {"scene_index": 12, "shot_index": 5, "description": "..."}
    ],

    "cross_shot_id_substitution_rule": {
      "applies_when": "fixed_elements[i].character_name maps to a C## allowed in id_policy.allowed_base_entity_ids OR a C##O## in id_policy.allowed_outlook_pairs",
      "substitution": "replace the common-noun person reference inside fixed_elements[i].description (e.g. 'An Asian man' / 'a woman' / 'a figure') with the matched C## or C##O##",
      "double_description_forbidden": "do not describe the same character with both a C##/C##O## and a separate common-noun person reference in the same t2i_prompt — produces phantom extra figures",
      "preserve_pose_unchanged": "fixed_elements[i].description 의 자세/위치/상태 keyword 는 그대로 유지 — 인물 참조 토큰만 교체",
      "no_repeat_after_pose": "이미 t2i_prompt 안에서 같은 인물의 자세를 묘사했다면, 같은 fixed_element description 을 별도 문장으로 반복하지 마라",
      "rationale_summary": "fixed_element description 이 보통명사로 작성되었고 같은 인물의 C## 묘사가 t2i_prompt 에 따로 있으면 모델이 두 인물로 인식해 인원 수가 부풀려진다"
    },

    "ref_usage_constraints": {
      "zoom_in_detail": {
        "scope_summary": "same moment, same space, same primary subject; camera moved/zoomed onto a sub-region only",
        "forbidden_additions": [
          "new persons not in the prior shot",
          "new props not in the prior shot",
          "new background elements not in the prior shot",
          "new poses, gestures, or expressions not in the prior shot",
          "different moments of action"
        ],
        "required_phrasings": [
          "describe only the focused sub-region in concrete terms",
          "explicitly state the same environment (lighting, background) is preserved",
          "use continuity tokens such as 'the same figure', 'the same room', 'the same surface'"
        ],
        "body_part_cross_ref": "id_policy.body_part_focus_rule applies — when the focused sub-region is a body part, use a common noun + demographic descriptor (no C##/C##O##)",
        "rationale_summary": "zoom_in_detail = 앞 샷의 특정 영역을 확대한 '같은 사진'. 새 정보 추가 금지 — 현재 프레임이 보여주는 부분만 정확히 묘사"
      },
      "exact_background": {
        "scope_summary": "same room, possibly different moment or angle; background carries through, persons/actions are described fresh for this shot",
        "permitted_additions": [
          "new persons, poses, expressions, actions for this shot's moment",
          "different camera angle within the same room"
        ],
        "background_consistency_rule": "background elements established in the prior shot must remain consistent (no contradictory walls, doors, furniture)",
        "ignore_keep_handled_elsewhere": "ignore/keep directives are auto-applied via reference image processing — do not echo them inside t2i_prompt",
        "rationale_summary": "같은 공간, 새 순간 — 인물·동작은 이 샷의 instantaneous moment 로 묘사하되 배경은 prior shot 과 일관"
      },
      "atmosphere_reference": {
        "scope_summary": "different room/floor/angle entirely — only mood/tone is shared with the prior shot",
        "forbidden_imports": [
          "furniture layout from the prior shot",
          "wall positions from the prior shot",
          "specific architectural features from the prior shot"
        ],
        "required_handling": "describe the new space's background directly in this shot; carry through only color tone or lighting mood (e.g. 'cool gray ambient' / 'dim warm pool')",
        "rationale_summary": "atmosphere_reference = 분위기 reference 만, 레이아웃 reference 아님. 가구·벽 배치를 끌어오면 모델이 두 공간을 한 공간으로 합성"
      }
    },

    "view_consistency": {
      "framing_scope_options": ["third_person", "close_up"],
      "single_camera_rule": "one t2i_prompt = one camera position. do not combine a third-person full-body description and a close-up of the same character's body part.",
      "third_person_handling": "describe persons with full or partial body in frame; their hands/arms remain attached to their bodies (no isolated hand close-up alongside the full body)",
      "close_up_handling": "isolated body part (hand, wrist, fingers, brow) — do not include the same character's full body in the same prompt",
      "focus_on_is_focus_area_not_view_switch": "the phrasing 'Focus on ...' specifies the area of focus while the camera position remains the same; it is NOT a view switch",
      "id_policy_cross_ref_for_body_part_focus": "id_policy.body_part_focus_rule applies — body-part Focus 시 C##O## 금지 (얼굴 ref 합성 차단)",
      "prop_orientation_rule": "props the character holds must face one direction only — toward camera OR toward character, not both. forbidden contradiction example: 'photo angled partly toward the camera' + 'eyes fixed on the photograph' (양방향 모순)",
      "mixing_forbidden": true,
      "rationale_summary": "single t2i_prompt = single camera shutter at 1/1000s. 시점 혼합은 모델이 두 카메라 합성으로 해석해 인물이 두 번 등장 또는 부위 분리"
    },

    "constraints": [
      "fixed_elements are continuity inputs derived from prior shots — treat them as binding context, not as source facts to be invented",
      "when fixed_elements[i].character_name maps to a C## in id_policy.allowed_base_entity_ids or a C##O## in id_policy.allowed_outlook_pairs, replace the common-noun person reference inside fixed_elements[i].description with that C##/C##O## and do not also write a separate common-noun reference for the same character in the same t2i_prompt",
      "previous_shot_refs with ref_usage='zoom_in_detail' must preserve the same moment, the same space, and the same lighting as the prior shot — no new persons, props, background elements, poses, or moments",
      "previous_shot_refs with ref_usage='exact_background' may freely describe this shot's persons, poses, and moment, but must keep background elements consistent with the prior shot's established room",
      "previous_shot_refs with ref_usage='atmosphere_reference' must describe a new space directly — do not import furniture layout or wall positions from the prior shot, only mood/tone",
      "one t2i_prompt = one camera position — do not combine a third-person full-body description and a close-up of the same character's body part; 'Focus on' specifies the area of focus, not a view switch (id_policy.body_part_focus_rule applies for any body-part Focus)",
      "props held by characters face one direction only — toward camera OR toward character, never both"
    ]
  },
  "asset_requirements": {"...": "..."},
  "_card_metadata": {
    "lift_status": {
      "rule_a_lifted": true,
      "rule_c_lifted": true,
      "rule_e_lifted": true,
      "rule_h_lifted": true,
      "id_policy_composite_lifted": true,
      "id_policy_close_framing_face_lifted": true,
      "id_policy_reproduction_surface_lifted": true,
      "continuity_cross_shot_id_substitution_lifted": true,
      "continuity_ref_usage_constraints_lifted": true,
      "continuity_view_consistency_lifted": true,
      "continuity_constraints_extended": true
    },
    "rule_source": {
      "camera_rule": "A",
      "owned_rule": "C",
      "close_skip_rule": "E",
      "demographic_rule": "H",
      "composite_id_rule": "C## 사용 규칙 — 참조 이미지 연동 (v17:32-64)",
      "close_framing_face_rule": "극단 클로즈업 표현 (v17:66-85)",
      "reproduction_surface_rule": "사진·포스터·화면·거울 속 인물 규칙 (v17:133-146)",
      "cross_shot_id_substitution_rule": "교차 샷 고정 요소 통합 규칙 (v18:109-120)",
      "ref_usage_rule": "앞쪽 참조 샷 처리 — ref_usage 유형별 지시 (v18:122-156)",
      "view_consistency_rule": "절대 규칙 — 단일 시점 (1인칭/3인칭 혼합 금지) (v18:308-326)"
    }
  }
}
```

**`_card_metadata` 신규 sub-field 설명 (G4.2 R1-B1 + G4.3 carry — hash 제외):**

> **Override O-9 정정**: 정확 키명은 `continuity_cross_shot_id_substitution_lifted`
> / `continuity_ref_usage_constraints_lifted` / `continuity_view_consistency_lifted`
> / `continuity_constraints_extended` (4 new keys). spec drafting 초기 표기
> "continuity_cross_shot_id_lifted" / "continuity_ref_usage_lifted" 는 abbrev
> 형태였으며 본 표기로 통일.

- `lift_status.continuity_cross_shot_id_substitution_lifted` (bool, debug-only):
  "교차 샷 고정 요소 통합 규칙" prose 가 card 로 lift 됐음을 version-tracking
  목적으로 기록.
- `lift_status.continuity_ref_usage_constraints_lifted` (bool, debug-only):
  "앞쪽 참조 샷 처리 — ref_usage 유형별 지시" prose 가 lift 완료.
- `lift_status.continuity_view_consistency_lifted` (bool, debug-only): "단일
  시점 (1인칭/3인칭 혼합 금지)" prose 가 lift 완료.
- `lift_status.continuity_constraints_extended` (bool, debug-only): G4.1 base
  2 strings → G4.4 7 strings 확장 완료.
- `rule_source.cross_shot_id_substitution_rule` / `ref_usage_rule` /
  `view_consistency_rule` (str, debug-only): 각 신규 lift 의 v18 prose source
  위치 역추적.

`_card_metadata` 자체는 G4.2 (commit `14d14cb`) 에서 이미
`canonicalize_render_prompt_card()` 가 hash 입력에서 strip + `detail_steps.py`
inject 직전 strip 처리. G4.4 에서는 dict key 추가만 — strip 로직 변경 0.

**continuity_elements_used `_card_metadata.lift_status` matrix (G4.4 —
ref_usage 무관 모두 동일, 모든 shot 에 대해 lift 완료):**

| ref_usage state | continuity_cross_shot_id_substitution_lifted | continuity_ref_usage_constraints_lifted | continuity_view_consistency_lifted | continuity_constraints_extended |
|---|---|---|---|---|
| (no previous_shot_refs) | true | true | true | true |
| `zoom_in_detail` / `exact_background` / `atmosphere_reference` | true | true | true | true |
| (forward_zoom_targets present, no previous_shot_refs) | true | true | true | true |

(G4.2 의 background_binding 은 mode 별로 차등이지만, continuity 는 ref_usage
무관 — 모든 shot 에 동일 적용. matrix 는 single row 로 사실상 충분.)

**continuity_elements_used 신규 sub-field shape 검증 contract (Override O-5
strict required-keys subset + G4.3 R2-I5 carry):**

`assert_card_shape()` 는 envelope contract **7 field 만 strict 검증** 유지
(`schema_version`, `shot_key`, `render_strategy`, `id_policy`,
`background_binding`, `continuity_elements_used`, `asset_requirements`).
**continuity_elements_used 내부 신규 3 policy dicts
(`cross_shot_id_substitution_rule` / `ref_usage_constraints` /
`view_consistency` + 기존 3 sub-field 의 required key 유지) 는 G4.4 strict
검증에 추가 — 누락 시 contract violation**. G4.1 의
`_assert_continuity_elements_used_shape()` (`render_prompt_card.py:1353-1385`)
가 base 3 field (fixed_elements / previous_shot_refs / forward_zoom_targets)
만 검증하므로 G4.4 에서 3 신규 policy dicts 검증 추가 필요.

> **Override O-5 — strict required-keys subset (set superset 검증)**: 신규
> sub-field required keys 는 `EXPECTED_REQUIRED <= returned_keys` (set
> equality 가 아닌 superset). extra keys 허용 — G4.5 forward-compat. 단위
> 테스트도 `assert set(returned.keys()) >= EXPECTED_SET` 으로 통일.

`_card_metadata` 는 G4.2 + G4.3 와 동일 free-form (extra-key 허용, schema 검증
없음).

**Required-key contract per G4.4 sub-field (Override O-5 strict superset):**

| sub-field | required_keys |
|---|---|
| `cross_shot_id_substitution_rule` | `("applies_when", "substitution", "double_description_forbidden", "preserve_pose_unchanged", "no_repeat_after_pose", "rationale_summary")` |
| `ref_usage_constraints` | `("zoom_in_detail", "exact_background", "atmosphere_reference")` (top-level 3 sub-key, 각 sub-key value 는 dict) |
| `ref_usage_constraints.zoom_in_detail` | `("scope_summary", "forbidden_additions", "required_phrasings", "body_part_cross_ref", "rationale_summary")` |
| `ref_usage_constraints.exact_background` | `("scope_summary", "permitted_additions", "background_consistency_rule", "ignore_keep_handled_elsewhere", "rationale_summary")` |
| `ref_usage_constraints.atmosphere_reference` | `("scope_summary", "forbidden_imports", "required_handling", "rationale_summary")` |
| `view_consistency` | `("framing_scope_options", "single_camera_rule", "third_person_handling", "close_up_handling", "focus_on_is_focus_area_not_view_switch", "id_policy_cross_ref_for_body_part_focus", "prop_orientation_rule", "mixing_forbidden", "rationale_summary")` |

ground-truth list/literal counts:

- `ref_usage_constraints.zoom_in_detail.forbidden_additions` = 5 entries
- `ref_usage_constraints.zoom_in_detail.required_phrasings` = 3 entries
- `ref_usage_constraints.exact_background.permitted_additions` = 2 entries
- `ref_usage_constraints.atmosphere_reference.forbidden_imports` = 3 entries
- `view_consistency.framing_scope_options` = 2 entries (`"third_person"`,
  `"close_up"`)
- `view_consistency.mixing_forbidden` = `True` (bool, not string — bool int
  subclass trap carry from G4.3 / G3.2)

### 2.3 Producer / consumer matrix

> **Override O-13 — concrete wire 명시**: `_collect_card_inputs(snapshot,
> scene_idx, shot_idx) → build_render_prompt_card(...) → _card_metadata strip
> → JSON prepend (line ~1500-1610 in detail_steps.py)`. `build_continuity_elements_used()`
> 위치 = `render_prompt_card.py:676`. consumer = scene_detail user_prompt JSON dict.
> 누락된 wire (Wave 2 삭제 대상) = **detail_steps.py:1555-1563 inline prose 9 줄**
> (`if has_zoom_in_detail:` 블록).

| field | producer | consumer (LLM) | consumer (post-parse judge) |
|---|---|---|---|
| `fixed_elements` | `_collect_card_inputs()` ← scene_consistency CP (G4.1 carry) | C## substitution input + binding context | (없음 — fixed_element 자체 검증은 scene_consistency 측) |
| `previous_shot_refs` | `_collect_card_inputs()` ← shot_dependency_t2i CP | ref_usage 별 prompt 작성 분기 입력 | (없음 — ref 데이터 자체 검증) |
| `forward_zoom_targets` | `_collect_card_inputs()` (G4.1 carry) | 이후 shot 으로 zoom 진행 정보 | (현재 t2i_prompt 작성에는 영향 X) — Override O-4 G4.4 lift 대상 아님 |
| `cross_shot_id_substitution_rule` | builder static (`build_continuity_elements_used()` @ render_prompt_card.py:676) | fixed_element description 안 보통명사 인물 → C##/C##O## 치환 + 이중 묘사 금지 | canary `g4_4_double_description.py` (같은 캐릭터에 대해 C##/C##O## + 보통명사 동시 사용 검출) |
| `ref_usage_constraints` | builder static (render_prompt_card.py:676) | ref_usage 별 forbidden/required wording 회피 | canary `g4_4_zoom_in_detail_no_new_entity.py` (zoom_in_detail 인 shot 의 t2i_prompt 가 prior shot 에 없는 entity 도입했는지) + `g4_4_atmosphere_no_layout_import.py` (atmosphere_reference 인 shot 이 furniture/wall layout 표현 import 했는지) |
| `view_consistency` | builder static (render_prompt_card.py:676) | one-camera 원칙 + Focus-on 의미 + prop orientation | canary `g4_4_view_mixing.py` (전신 person 묘사 + 같은 person 의 body-part close-up 같은 prompt 안 동시 등장) |
| `constraints` | builder static (G4.1 base 2 → G4.4 7 strings) | t2i_prompt 작성 시 constraint list 준수 | 위 4 canary 통합 |
| **detail_steps.py:1555-1563 inline prose** (Override O-3) | **현재 = `if has_zoom_in_detail:` 블록 9 줄 inline (`detail_steps.py`)** — **Wave 2 삭제 대상** | (현재) zoom_in_detail 시 user_prompt 안 prepend 9 줄 prose | (Wave 2 후 부재) — `ref_usage_constraints.zoom_in_detail` card sub-dict 가 동일 데이터 carry |

### 2.4 정보 분리 원칙

Card 가 carry 하는 것 vs prompt 에 남는 것의 경계:

| 정보 | G4.4 이후 위치 | 근거 |
|---|---|---|
| 어떤 fixed_element / previous_shot_ref / forward_zoom_target 가 입력되는가 | card `continuity_elements_used.{fixed_elements,previous_shot_refs,forward_zoom_targets}` | deterministic — G4.1 이미 carry |
| C## substitution 적용 조건 (character_name → C## 매핑 lookup) | card `cross_shot_id_substitution_rule.applies_when` | deterministic rule → card 로 |
| 이중 묘사 금지 룰 + 자세 보존 룰 + 반복 금지 룰 | card `cross_shot_id_substitution_rule.{double_description_forbidden,preserve_pose_unchanged,no_repeat_after_pose}` | deterministic 3 sub-rule → card 로 |
| ref_usage 별 forbidden additions list (zoom_in_detail) | card `ref_usage_constraints.zoom_in_detail.forbidden_additions` (5 entries) | deterministic list → card 로 |
| ref_usage 별 required phrasings list (zoom_in_detail) | card `ref_usage_constraints.zoom_in_detail.required_phrasings` (3 entries) | deterministic list → card 로 |
| exact_background 의 permitted additions + consistency rule | card `ref_usage_constraints.exact_background.{permitted_additions,background_consistency_rule}` | deterministic → card 로 |
| atmosphere_reference 의 forbidden imports + required handling | card `ref_usage_constraints.atmosphere_reference.{forbidden_imports,required_handling}` | deterministic → card 로 |
| 시점 혼합 금지 룰 (third_person + close_up 같은 prompt 결합 금지) | card `view_consistency.{single_camera_rule,mixing_forbidden,third_person_handling,close_up_handling}` | deterministic → card 로 |
| Focus-on 이 시점 전환이 아니라는 의미 | card `view_consistency.focus_on_is_focus_area_not_view_switch` | deterministic → card 로 |
| 소품 방향 한 방향만 (양방향 모순 금지) | card `view_consistency.prop_orientation_rule` | deterministic → card 로 |
| body-part Focus 시 C##O## 금지 cross-ref | card `view_consistency.id_policy_cross_ref_for_body_part_focus` (string foreign-ref) + `ref_usage_constraints.zoom_in_detail.body_part_cross_ref` | G4.3 lift 의 body_part_focus_rule 을 string 으로 cross-ref. 실제 룰 구현은 id_policy 측 |
| "왜 fixed_element 보통명사 + C## 동시 사용이 인원 부풀림인가" (모델 인식 설명) | system.md "Continuity" 한 단락 reminder | reasoning prose — card 로 표현 불가 |
| "왜 zoom_in_detail 에 새 entity 추가가 같은 사진 원칙 위반인가" | system.md "Continuity" 한 단락 reminder | reasoning prose — card 로 표현 불가 |
| "왜 시점 혼합이 두 카메라 합성으로 모델이 해석하는가" | system.md "Continuity" 한 단락 reminder | reasoning prose — card 로 표현 불가 |
| 교차 샷 통합 ✓/✗ 예시 1개 (line 117-120) | 삭제 (G4.4 prompt v19) | card constraints 가 대체 |
| ref_usage 3 sub-section 예시들 + ✓/✗ | 삭제 (G4.4 prompt v19) | card `ref_usage_constraints` 가 대체 |
| 시점 혼합 ✓/✗ 예시 6개 | 삭제 (G4.4 prompt v19) | card constraints 가 대체 |

> **Override O-15 — ✓/✗ 예시 prose 삭제 안전성 footnote**: ✓/✗ 예시 prose
> 삭제는 G4.3 baseline 통과 패턴 carry (commit `51d6c5e` — G4.3 도 prose
> 예시 전부 삭제 후 4 metric STRICT 0 통과). canary STRICT 0 검증이 LLM 행동
> 보증 — 예시 prose 의 추가 reasoning 효과 없음. v19 compact section 은
> reasoning summary 만 carry, sub-field 가 single source of truth.

---

## 3. Prompt Change Spec

### 3.1 대상 버전

- **Before**: `prompts/_base/scene_detail/18.202605041549/system.md` (v18,
  587 lines, 3 continuity prose section 포함 + G4.3 ID Policy compact section
  완료)
- **After**: `prompts/_base/scene_detail/19.<TIMESTAMP>/system.md` (v19, 3
  continuity prose sections removed, "Continuity" compact section 1 단락 대체)

새 버전 디렉토리 생성 필수 — 덮어쓰기 금지 (CLAUDE.md
`feedback_prompt_versioning.md`). v18 sealed.

`<TIMESTAMP>` format = `YYYYMMDDHHmm` (e.g. `19.202605051600`) per
`feedback_version_format.md`.

### 3.2 삭제 대상 lines (v18 기준)

| section | lines (v18) | 내용 | 삭제 이유 |
|---|---|---|---|
| `## 교차 샷 고정 요소 통합 규칙` | 109-120 | fixed_elements 의 (인물이름 = C##) 매핑 시 보통명사 인물 묘사 → C## 대체 + 이중 묘사 금지 + 자세 보존 + 반복 금지 + 1 ✓/✗ 예시 = 12 lines | card `cross_shot_id_substitution_rule` + constraints 가 동일 데이터 carry |
| `## 앞쪽 참조 샷 처리 — ref_usage 유형별 지시` | 122-156 | 3 sub-section (zoom_in_detail / exact_background / atmosphere_reference) + 각 forbidden/required + 1 ✓/✗ 예시 류 = 35 lines | card `ref_usage_constraints` + constraints 가 동일 데이터 carry |
| `## 절대 규칙 — 단일 시점 (1인칭/3인칭 혼합 금지)` | 308-326 | 3인칭 + 클로즈업 혼합 금지 + Focus-on 의미 + 소품 방향 + 3 ✓/✗ 예시 = 19 lines. **line 313 의 `id_policy.body_part_focus_rule` cross-ref 는 v19 compact section 안에 1 line 으로 보존** | card `view_consistency` + constraints 가 동일 데이터 carry |
| **Wave 2 — `backend/app/core/steps/detail_steps.py:1555-1563`** (Override O-3) | 1555-1563 | `if has_zoom_in_detail:` 블록 안 9 줄 inline prose ("[중요 — 확대 샷 원칙 (zoom_in_detail)] / 새 인물·소품·자세 추가 금지 / 인체 부위 초점이면 보통명사 / 환경 동일") = 9 lines + trailing `\n\n` | card `ref_usage_constraints.zoom_in_detail` 5 required keys 가 동일 데이터 carry. v19 compact section 의 zoom_in_detail bullet + sub-field 의 forbidden_additions 5 entry / required_phrasings 3 entry / body_part_cross_ref / rationale_summary 가 prose 9 줄 대체. |

총 삭제 대상: **66 lines (system.md) + 9 lines (detail_steps.py inline) = 75 lines** (12 + 35 + 19 + 9).

> **Cross-card reference 처리 (R1+R2 dispatch 시 검증)**: v18 line 313 의
> `**id_policy.body_part_focus_rule 참조 — body-part Focus 시 C##O## 금지
> (얼굴 ref 합성 차단)**` 단편은 v19 compact "Continuity" section 안 한 줄
> reminder 로 보존 — card 의 `view_consistency.id_policy_cross_ref_for_body_part_focus`
> sub-field 로 동일 데이터 carry 되지만 prose 도 reminder 1 line 유지 (G4.3
> `## ID Policy` compact section 과 cross-ref 2-way 일관성 확보).

> **결정 (예정 — R1+R2 audit 후 확정)**: v18 line 308-326 섹션은 G4.4 scope —
> 본 G4.4 가 처분. G4.3 spec §3.2 가 `line 390-407 단일 시점 섹션 자체는
> G4.4 continuity scope` 로 명시 carry. v18 에서 G4.3 가 line 313 만 1 line
> 인플레이스 교체하고 본문 보존했던 부분은 G4.4 가 전체 삭제.

### 3.3 대체 section (v19 신규)

삭제된 3 system.md prose section + 1 detail_steps.py inline prose 자리에
다음 단일 섹션으로 교체. **Override O-2 — `wc -l` ≤ 32 lines strict gate**
(통합 테스트 §6.2 `test_v19_continuity_section_line_count_under_32` 가 강제).
각 bullet 반드시 1 line — 80-col wrap 허용 안 됨 (wrap 줄도 line 1 로 카운트).

```markdown
## Continuity (cross-shot substitution + ref_usage + single-view)

`[RenderPromptCard v1]` 의 `continuity_elements_used` field 가 이 shot 의 연속성 정책을 결정한다 (card 가 primary contract — 충돌 시 card 우선).

**핵심 원칙:**

- `fixed_elements[i].description` 안 보통명사 인물 ("An Asian man" / "a woman" 류) 이 `cross_shot_id_substitution_rule.applies_when` 조건 (character_name → C## 또는 C##O## 매핑이 id_policy 안에 존재) 만족 시 해당 보통명사를 C##/C##O## 로 치환. 같은 인물 C##/C##O## + 보통명사 이중 묘사 금지 (모델이 두 인물로 인식해 인원 부풀림). 자세/위치/상태 keyword 는 보존.
- `previous_shot_refs[i].ref_usage='zoom_in_detail'` 이면 같은 시공간·같은 주요 피사체·카메라 sub-region 확대만 — `ref_usage_constraints.zoom_in_detail.forbidden_additions` (새 인물·소품·배경·자세·다른 순간) 추가 금지. `required_phrasings` (the same figure / the same room / the same surface 류) 사용. body-part Focus 시 `id_policy.body_part_focus_rule` 적용 (보통명사 + demographic descriptor).
- `previous_shot_refs[i].ref_usage='exact_background'` 이면 같은 방 다른 순간/앵글 — 인물·동작은 이 샷의 instantaneous moment 로 새로 묘사, 배경은 prior shot 과 일관 유지. ignore/keep 자동 반영.
- `previous_shot_refs[i].ref_usage='atmosphere_reference'` 이면 다른 공간 — 새 공간 배경 직접 묘사하고 색감·조명 톤만 prior shot 에서 carry. `ref_usage_constraints.atmosphere_reference.forbidden_imports` (가구 배치·벽 위치·architectural feature) 끌어오기 금지.
- `view_consistency.single_camera_rule`: 한 t2i_prompt = 한 카메라 위치. 3인칭 전신 묘사 + 같은 인물의 body-part close-up 한 프롬프트 결합 금지. "Focus on" = 초점 영역 (시점 전환 X). body-part Focus 시 `id_policy.body_part_focus_rule` 참조 — body-part Focus 시 C##O## 금지 (얼굴 ref 합성 차단).
- `view_consistency.prop_orientation_rule`: 인물이 잡은 소품은 한 방향만 — 카메라 향함 OR 인물 향함, 양방향 모순 금지.

**충돌 해결**: 본 섹션 prose 는 reasoning summary; 구체 token list (forbidden_additions / required_phrasings / forbidden_imports) 는 모두 `continuity_elements_used` sub-field 가 single source. prose 가 sub-field 와 충돌하면 sub-field 우선.
```

실제 line 수 (Override O-2 gate): heading 1 + blank 1 + intro 1 + blank 1 + "**핵심 원칙:**" 1 + blank 1 + 6 bullet (각 1 line) + blank 1 + "**충돌 해결**" 1 = **14 lines**. **32 lines 이내 strict** (wrap 발생 시 violation — Wave 1-B draft 작성 시 `wc -l` 자체 점검 필수). multi-line wrap trap 회피 (G4.3 R3R4-B3 carry).

### 3.4 Token 감소 추정

> **R1-M1 carry from G4.2/G4.3**: 본 표의 token 수는 line 수 × 약 15
> tokens/line 의 1차 추정값 (`tiktoken cl100k_base` 미적용). canary 측정
> 단계 (§5.3) 가 `tiktoken cl100k_base` 로 system.md 실측 후 본 표를 갱신.
> exit criteria (§5.1 token delta gate) 는 **실측치 기준으로만 판정**.
>
> **R2-I4 carry from G4.2/G4.3**: `_card_metadata.lift_status.continuity_*_lifted`
> 등 신규 dict key 가 user_prompt inject 시 +0 token 가정은 G4.2 producer-
> side 변경 (`detail_steps.py` inject 직전 strip — 14d14cb) 으로 보존. G4.4
> 에서는 strip 로직 변경 0.

| 항목 | v18 | v19 (예상) | delta |
|---|---|---|---|
| 교차 샷 고정 요소 통합 규칙 section (system.md) | 12 lines ≈ 180 tokens | 0 (삭제) | -180 |
| 앞쪽 참조 샷 처리 — ref_usage 유형별 지시 section (system.md) | 35 lines ≈ 525 tokens | 0 (삭제) | -525 |
| 절대 규칙 — 단일 시점 section (system.md) | 19 lines ≈ 285 tokens | 0 (삭제) | -285 |
| Continuity compact section (system.md, 신규 — Override O-2 ≤ 32 lines) | 0 | 14 lines ≈ 210 tokens (§3.3 enumerate) | +210 |
| **net system.md** | — | — | **-780 tokens** (추정. 실측 = canary §5.3, **Override O-1 binding gate `≤ -540`**) |
| **detail_steps.py:1555-1563 zoom_in_detail inline prose** (Override O-3) | 9 lines ≈ 135 tokens (per-shot prepend, zoom_in_detail shot 만) | 0 (Wave 2 삭제) | **-135 tokens (per zoom_in_detail shot)** — system.md 외 별도 saving |
| card `continuity_elements_used` user_prompt inject | 이미 있음 (G4.1 base 3 sub-field) | 신규 3 policy dicts 추가 (+~280 tokens — `cross_shot_id_substitution_rule` ~70 + `ref_usage_constraints` 3 sub-key dict ~140 + `view_consistency` ~70) + constraints 2 → 7 (+5 strings ~80) | +360 |
| **net 합산 (system + inject)** | — | — | **-420 tokens** (system -780 + inject +360. Override O-1 secondary gate `≤ -150` report-only) |

> **Override O-1 binding token gate**: `token_delta = candidate - baseline ≤ -540` **system-only** (1차 estimate-based binding gate). Wave 1-B tiktoken 실측이 더 강한 수치 (e.g. -480) 로 revise 가능 — Round 2 Override row 로만 변경, **upward revise (e.g. -300) 는 re-audit 필요**. system+inject 합산 (≤ -150) = secondary report-only metric (canary JSON 보조 기록 / no gate).

> **Override O-2 line count gate** (binding): §3.3 compact section `wc -l` ≤ 32 lines strict — 통합 테스트 §6.2 가 강제. 본 §3.4 estimate 의 14 lines 는 §3.3 enumerate 기준; Wave 1-B v19 draft 작성 후 actual `wc -l` 측정.

> **G4.3 baseline reference**: G4.3 token canary delta -2073 (gate -1050)
> 통과 commit 51d6c5e. G4.4 가 G4.3 비교에서 수치적으로 작은 이유: prose
> 분량 (66 system + 9 inline = 75 lines vs G4.3 의 109 lines) + sub-field
> inject 보강 (3 new policy dicts) 동반.

카나리 측정 시 실제 delta 를 `docs/canary/g4_continuity_<timestamp>.json` 에
기록.

---

## 4. Lift Mapping Table

각 v18 prose section → G4.4 card field 1:1 대응:

| Prose source (v18 lines) | v18 prose 역할 | G4.4 card field | constraint key | lift 완료 판정 기준 |
|---|---|---|---|---|
| **Section A — line 109 heading** | section title | (N/A heading 자체 삭제) | (N/A) | v19 system.md heading absence integration test |
| **Section A — line 111-115 (5-line bullet enumeration)** | character_name → C## 매핑 시 보통명사 → C## 대체 + 이중 묘사 금지 + 자세 보존 + 반복 금지 | `cross_shot_id_substitution_rule.applies_when` + `substitution` + `double_description_forbidden` + `preserve_pose_unchanged` + `no_repeat_after_pose` | constraints[1] | unit test `test_continuity_cross_shot_id_substitution_rule_required_keys_exact` + canary `g4_4_double_description.py` |
| **Section A — line 117-120 (1 ✓/✗ 예시)** | 통합 후 자세 묘사 vs 이중 묘사 reject 예시 | (삭제 — card 가 단일 출처 + constraints 가 enumeration) | constraints[1] | integration test v19 missing prose substring |
| **Section B — line 122 heading** | section title | (N/A heading 자체 삭제) | (N/A) | v19 missing heading |
| **Section B — line 124 (introductory sentence)** | "ref_usage 유형에 따라 t2i_prompt 작성 방식 달리" | (삭제 — card 의 3 sub-key 가 분기를 명시) | constraints[2..4] | (N/A) |
| **Section B — line 126-141 (zoom_in_detail sub-section)** | 같은 시공간 카메라 이동/확대만 + forbidden 3 + required 4 + body-part cross-ref + "같은 사진" 원칙 | `ref_usage_constraints.zoom_in_detail` 전체 dict (5 required keys) | constraints[2] | unit test `test_continuity_ref_usage_zoom_in_detail_*` + canary `g4_4_zoom_in_detail_no_new_entity.py` (zoom_in_detail shot 의 t2i_prompt 가 prior shot 에 없는 entity 도입했는지) |
| **Section B — line 143-149 (exact_background sub-section)** | 같은 방 다른 순간/앵글 + 인물·동작 새로 묘사 + 배경 일관 + ignore/keep 자동 처리 | `ref_usage_constraints.exact_background` 전체 dict (5 required keys) | constraints[3] | unit test `test_continuity_ref_usage_exact_background_*` |
| **Section B — line 151-156 (atmosphere_reference sub-section)** | 다른 공간 + 톤만 carry + 가구·벽 import 금지 | `ref_usage_constraints.atmosphere_reference` 전체 dict (4 required keys) | constraints[4] | unit test `test_continuity_ref_usage_atmosphere_reference_*` + canary `g4_4_atmosphere_no_layout_import.py` (atmosphere_reference shot 의 t2i_prompt 가 furniture/wall layout token 검출 == 0) |
| **Section C — line 308 heading** | section title | (N/A heading 자체 삭제) | (N/A) | v19 missing heading |
| **Section C — line 310 (introductory)** | "한 카메라 위치에서 촬영한 것만 묘사" | `view_consistency.single_camera_rule` | constraints[5] | unit test `test_continuity_view_consistency_single_camera_rule` |
| **Section C — line 312 (third-person handling)** | 인물 전신/상반신 + 손/팔 몸에 붙여 묘사 | `view_consistency.third_person_handling` | constraints[5] | unit test |
| **Section C — line 313 (close-up handling + cross-ref)** | 부위 클로즈업 + body_part_focus_rule cross-ref + 보통명사 사용 | `view_consistency.close_up_handling` + `view_consistency.id_policy_cross_ref_for_body_part_focus` (foreign-ref string) | constraints[5] | unit test `test_continuity_view_consistency_id_policy_cross_ref_string_present` (string contains `"id_policy.body_part_focus_rule"`) |
| **Section C — line 314-317 (mixing forbidden + 3 ✓/✗ 예시)** | 전신 + close-up 결합 금지 + 예시 | `view_consistency.mixing_forbidden=true` + `single_camera_rule` | constraints[5] | canary `g4_4_view_mixing.py` (전신 person + 같은 person body-part close-up 동일 prompt 검출 == 0 strict) |
| **Section C — line 319-321 (Focus-on 의미 + 1 ✓/✗ 예시)** | Focus-on = 초점 영역, 시점 전환 X | `view_consistency.focus_on_is_focus_area_not_view_switch` | constraints[5] | unit test |
| **Section C — line 323-325 (prop orientation + 2 ✓/✗ 예시)** | 소품 방향 한 방향만 (양방향 모순 금지) | `view_consistency.prop_orientation_rule` | constraints[6] | unit test + canary 보강 검토 (R3+R4 audit 에서 검증 — prop orientation contradiction detector 가 deterministic regex 가능한지) |
| **detail_steps.py:1555-1563 inline prose** (Override O-3, Wave 2 삭제 대상) | `if has_zoom_in_detail:` 9 줄 inline ("[중요 — 확대 샷 원칙 (zoom_in_detail)] / 새 인물·소품·자세 추가 금지 / 인체 부위 초점이면 보통명사 / 환경 동일") | `ref_usage_constraints.zoom_in_detail` 5 required keys 가 동일 데이터 carry. v19 system.md compact section bullet + sub-dict + cross-ref 가 prose 9 줄 대체. | constraints[2] | integration test `test_detail_steps_no_inline_zoom_in_detail_prose_after_g4_4` (Wave 4 신규) — `detail_steps.py` source string 안 `"확대 샷 원칙"` substring 부재 검증 |

> **Override O-10 footnote — section boundary inclusivity**: Section A
> inclusive 행 = v18 lines 109-120 (12 lines: heading 109 / blank 110 /
> bullets 111-115 / blank 116 / ✓/✗ block 117-120). Section B inclusive 행
> = v18 lines 122-156 (35 lines: heading 122 / blank 123 / intro 124 /
> blank 125 / zoom_in_detail 126-141 / exact_background 143-149 /
> atmosphere_reference 151-156, blank gap 142/150 sub-section 사이). Section
> C inclusive 행 = v18 lines 308-326 (19 lines). **Blank lines 121 / 157 /
> 307 / 327 = 인접 섹션 보존** — Wave 1-B v19 drafting 에서 line-by-line diff
> 로 결정. 인접 섹션 영향 없도록 보존 (트레일링 blank 한 개씩 carry 또는
> drop 결정은 v19 final draft 시 점검).

각 section 에 대해 "card 가 data 를 carry" + "prompt 가 1-paragraph reasoning
제공" 의 분리가 완성되면 lift 완료.

---

## 5. Exit Criteria + Canary Procedure

### 5.1 Exit criteria

G4.4 는 다음 **모두** 충족 시 완료 선언 가능 (정확 수치는 R1+R2 audit 후 확정):

| criterion | 측정 방법 (G4.2/G4.3 deterministic carry) | threshold |
|---|---|---|
| cross-shot fixed-element 이중 묘사 0 | canary script `scripts/canary/g4_4_double_description.py` 가 모든 shot 의 t2i_prompt 에서 fixed_elements[i].character_name → C## 매핑이 존재하는 shot 만 scan, regex `r"\bC\d{2}(?:O\d{2})?\b"` 와 ±50 chars window 안의 generic person noun (`an Asian man` / `a young woman` / `a figure` 등 module-level constant `_CONTINUITY_GENERIC_PERSON_NOUNS` import) 동시 등장 검출 count | **candidate count == 0 strict** (이중 묘사 violation 은 0 강제) |
| zoom_in_detail entity 추가 0 | canary script `scripts/canary/g4_4_zoom_in_detail_no_new_entity.py` 가 ref_usage='zoom_in_detail' 인 shot 의 t2i_prompt 에서 prior shot 의 t2i_prompt 에 부재했던 C##/C##O## 또는 신규 prop short_id (P##) 등장 검출 count | **candidate count == 0 strict** |
| atmosphere_reference layout import 0 | canary script `scripts/canary/g4_4_atmosphere_no_layout_import.py` 가 ref_usage='atmosphere_reference' shot 의 t2i_prompt 에서 module-level `_CONTINUITY_FURNITURE_LAYOUT_TOKENS` (`furniture / wall position / room layout / same chair / same table` 류 — R1+R2 audit 에서 ground-truth list 확정) 검출 count | **candidate count == 0 strict** |
| view-mixing pattern 0 | canary script `scripts/canary/g4_4_view_mixing.py` 가 모든 shot 의 t2i_prompt 에서 같은 인물 ID 의 전신/상반신 묘사 (`stands` / `seated` / `leaning` 류) + 같은 인물의 body-part close-up (`focus on` / `close on` / `tight on` / `detail on` + body-part) 동시 등장 검출 count | **candidate count == 0 strict** |
| system.md token delta (tiktoken cl100k_base 실측) | canary script `scripts/canary/g4_4_token_count.py` 가 v19 system.md 실측 vs v18 baseline 실측 | **`token_delta = candidate - baseline ≤ -540` system-only binding gate (Override O-1)**. system+inject 합산 (≤ -150) = secondary report-only metric (canary JSON 보조 기록 / no gate). Wave 1-B 실측 후 Round 2 Override 로 downward (e.g. -480) revise 가능, **upward revise 는 re-audit 필요**. |
| **v19 compact section line count gate** (Override O-2) | `wc -l` of `## Continuity` heading → 다음 `##` heading 사이 lines | **≤ 32 lines strict** (각 bullet 1 line, wrap 줄도 카운트). 통합 테스트 §6.2 `test_v19_continuity_section_line_count_under_32` 가 강제. |
| pytest G4.4 test suite green | `backend/tests/unit/test_g4_4_*.py` + `backend/tests/integration/test_g4_4_*.py` all pass | 0 failures, 0 regressions |
| 기존 regression suite green | `pytest backend/tests/` | count ≥ G4.3 baseline (= 약 2103+ pass post-G4.3) |

> **strict 0 metric (4종)**: G4.4 의 double-description / zoom_in_detail
> entity 추가 / atmosphere_reference layout import / view-mixing 위반 표현
> 은 baseline 비0 이어도 candidate 0 강제 (G4.2 R1-I4 / R2-B5 strict 패턴
> carry, G4.3 carry). baseline 비0 이면 baseline 도 별 production fix 대상
> 으로 마킹 (G4.4 scope 밖, P1 follow-up).

> **R2-B4 carry from G4.2/G4.3 — metric scope (Override O-11 multi-ref filter
> 추가)**: 본 4 canary 의 측정 대상 shot subset 은 명확히 분리:
> - `g4_4_double_description.py`: fixed_elements[i].character_name → C##
>   매핑이 1+ 존재하는 shot 만. 매핑 자체 미발생 shot 은 분모/numerator 모두
>   제외.
> - `g4_4_zoom_in_detail_no_new_entity.py`: ref_usage='zoom_in_detail' shot
>   만. 다른 ref_usage / no ref shot 은 제외.
> - `g4_4_atmosphere_no_layout_import.py`: **shot-level filter =
>   `any(ref.ref_usage == 'atmosphere_reference' for ref in previous_shot_refs)`**
>   (Override O-11). multi-ref 시 atmosphere check 만 적용 — `exact_background`
>   co-presence 시 false-positive baseline 비0 → P1 follow-up 추적 (`scope crep`
>   별도 issue). 단위 테스트 fixture +1 row in §6.1: `test_g4_4_view_mixing_canary_multi_ref_shot_filter`.
> - `g4_4_view_mixing.py`: 모든 shot. mixing pattern 자체가 의도된 close-up
>   /wide 구분 무관 검출.

> **Override O-17 — regex view-mixing canary brittleness**: regex 는 1차
> detector 만, baseline 비0 시 P1 follow-up 으로 LLM-validator (gpt-5.4-mini
> judge) 전환 검토. 의미 판단은 LLM-first (`feedback_no_regex_postprocessing.md`
> 참조). 본 4 canary 의 regex (ID/trigger 검출) 는 deterministic 패턴만 —
> body-part close-up trigger phrase (`focus on / close on / tight on / detail on`)
> + same-character ID 동시 등장 시에만 trigger. 단순 hand 언급 / 자연스러운
> 부분 묘사는 trigger 아님. R5+R6 fix-loop 또는 production 결과 기반으로
> false-positive 발견 시 LLM-validator 전환.

G4.4 Exit criteria 가 충족되어야 G4.5 (final prompt slimming) 진입 가능.

### 5.2 Baseline 측정 대상 PID / scene set

**Baseline 비교는 baseline / candidate 가 다음 변동 source 모두 동일할 때만
유효** (G4.2 R1-I5 + G4.3 R2-I2 pinning carry):

| pinning field | 값 결정 | 비교 시 enforce |
|---|---|---|
| `pid` | 단일 PID (G4.2 / G4.3 canary 와 동일 권장) | baseline.pid == candidate.pid |
| `scene_index_list` | 정확한 scene_index 정수 list | sorted 비교 동일 |
| `shot_index_list_per_scene` | scene 별 shot_index 정수 list dict | dict 동일 |
| `model_routing` | scene_detail step 의 LLM 모델 (e.g. `gemini-3.1-pro-preview`) | 동일 |
| `prompt_source_mode` | `file` (file-based prompt) vs `db` (DB prompt_template) | 동일 |
| `card_commit_hash` | RenderPromptCard helper / detail_steps 의 git HEAD commit hash 시점 (G4.3 = `51d6c5e`) | baseline / candidate 모두 명시 |
| `continuity_card_snapshot_hash` | continuity_elements_used field hash (loader 입력 변동 차단). G4.4 추가 — **`compute_continuity_snapshot_hash(card) = sha256(json.dumps(card["continuity_elements_used"], sort_keys=True, ensure_ascii=False))[:16]`** (Override O-12 명시 — `_card_metadata` 는 envelope sibling 이므로 자연 제외, 추가 strip 불필요. G4.3 `compute_id_policy_snapshot_hash` 와 동일 패턴 carry — `render_prompt_card.py` 에 helper 신설). | 동일 |

**Baseline scene set 요건** (R1+R2 audit 시 정확 수치 정정):

- fixed_elements[i].character_name → C## 매핑이 1+ 존재하는 shot 최소 5개
  (double-description metric 분모 보장).
- previous_shot_refs[i].ref_usage='zoom_in_detail' shot 최소 2개 (zoom_in_detail
  metric 검증 mandatory).
- previous_shot_refs[i].ref_usage='atmosphere_reference' shot 최소 1개
  (atmosphere layout import metric 검증 mandatory).
- 모두 `scene_index_list` / `shot_index_list_per_scene` 에 명시.

Baseline 측정 실행:
```
# 1. canary script 가 위 pinning field 모두 capture 후 baseline JSON 작성
# 2. v18 (G4.3) 로 force scene_detail 실행 — 명시된 scene/shot 만 force
# 3. 측정: 4 metric per shot + tiktoken cl100k_base system.md 실측 token count
# 4. 저장: docs/canary/g4_continuity_baseline_<timestamp>.json
```

candidate 측정도 동일 pinning. 한 field 라도 다르면 비교 reject — baseline
재측정.

### 5.3 Canary 절차 (G4.2 §5.3 + G4.3 §5.3 carry — 4 metric DRY refactor 권장)

G4.4 canary 는 3 continuity prose 삭제를 광범위 적용 전 1 PID / 1 selected
segment 로 검증한다:

> **DRY refactor 권장 (R1+R2 audit 시 명시 확정)**: G4.2 (4 metric) + G4.3 (5
> metric) + G4.4 (4 metric) 의 13 canary script 가 동일 boilerplate
> (CP load + pinning capture + measurement_failures list + pinned-tuple
> seen_in_cp post-loop validation + sentinel pre-pass + gate fail exit 1) 을
> 가짐. **`scripts/canary/_canary_common.py` helper module 신설 +
> `_validate_scene_preflight()` 공통 함수** 추출 권장. G4.2 R3-I3 carry +
> G4.3 잔여 follow-up #2 carry. 본 권장은 G4.4 in-scope 가 아니라 G4.5 또는
> 별 P1 follow-up 으로 이관.

**Step 1 — Baseline capture (v18)**
```
force scene_detail 실행 (v18 prompt, G4.3 card, 일반 PID)
capture → docs/canary/g4_continuity_baseline_<timestamp>.json
```

JSON schema (G4.2 R1-I3 + R1-I5 pinning + R2-B6 pid 위치 carry + G4.3 R2-I2
hash carry + G4.4 4 metric):
```json
{
  "timestamp": "ISO8601",
  "prompt_version": "18.202605041549",
  "pinning": {
    "pid": "...",
    "scene_index_list": [3, 7, 12, 18, 24],
    "shot_index_list_per_scene": {"3": [1,2], "7": [4], "12": [4], "18": [3], "24": [1,5]},
    "model_routing": "gemini-3.1-pro-preview",
    "prompt_source_mode": "file",
    "card_commit_hash": "51d6c5e",
    "continuity_card_snapshot_hash": "..."
  },
  "scene_set": [
    {
      "scene_index": N,
      "shot_index": M,
      "has_fixed_element_id_mapping": bool,
      "ref_usage": "zoom_in_detail"|"exact_background"|"atmosphere_reference"|null,
      "has_view_mixing_candidates": bool
    }
  ],
  "metrics": {
    "total_shots": N,
    "shots_with_fixed_element_id_mapping": N,
    "zoom_in_detail_shots": N,
    "exact_background_shots": N,
    "atmosphere_reference_shots": N,
    "atmosphere_reference_multi_ref_shots": N,
    "double_description_count": N,
    "double_description_per_shot": {},
    "zoom_in_detail_new_entity_count": N,
    "zoom_in_detail_new_entity_per_shot": {},
    "atmosphere_layout_import_count": N,
    "atmosphere_layout_import_per_shot": {},
    "view_mixing_count": N,
    "view_mixing_per_shot": {},
    "system_prompt_token_count_tiktoken_cl100k": N,
    "system_plus_inject_token_count_tiktoken_cl100k": N
  },
  "measurement_scripts": {
    "double_description": "scripts/canary/g4_4_double_description.py (id-mapping shots only)",
    "zoom_in_detail_no_new_entity": "scripts/canary/g4_4_zoom_in_detail_no_new_entity.py (zoom_in_detail shots only)",
    "atmosphere_no_layout_import": "scripts/canary/g4_4_atmosphere_no_layout_import.py (atmosphere_reference shots only)",
    "view_mixing": "scripts/canary/g4_4_view_mixing.py",
    "token_count": "scripts/canary/g4_4_token_count.py (tiktoken cl100k_base)"
  }
}
```

**Step 2 — Candidate capture (v19)**
```
새 v19 prompt 적용 + 동일 PID 동일 scene_set force 재실행
capture → docs/canary/g4_continuity_candidate_<timestamp>.json
```

동일 JSON schema (`prompt_version: "19.<timestamp>"` 로 변경).

**Step 3 — Comparison**:

선행 조건: `pinning` block 7 field 모두 baseline == candidate 일치 확인 (dict
equality 한 번. 불일치 시 재측정). 그 후 metric 비교:

- `double_description_count`: **candidate == 0 strict**
- `zoom_in_detail_new_entity_count`: **candidate == 0 strict**
- `atmosphere_layout_import_count`: **candidate == 0 strict** (Override O-11
  filter: shot-level `any(ref.ref_usage == 'atmosphere_reference' for ref in
  previous_shot_refs)` — multi-ref shot 도 atmosphere check 적용)
- `view_mixing_count`: **candidate == 0 strict**
- `system_prompt_token_count_tiktoken_cl100k`: **`token_delta = candidate -
  baseline ≤ -540` system-only binding gate (Override O-1)**. 즉 `baseline -
  candidate ≥ 540`. Wave 1-B 실측 후 Round 2 Override 로 downward (e.g. -480)
  revise 가능, **upward revise 는 re-audit 필요**.
- `system_plus_inject_token_count_tiktoken_cl100k`: secondary report-only
  metric (Override O-1). 1차 estimate 합산 delta = -420. **gate 아님** —
  canary JSON 보조 기록만.

**Step 4 — 판정**
- 모두 통과 → G4.4 lift 적용 확정, 3 continuity prose v19 삭제 확정.
- 실패 항목 있음 → constraints sub-field 또는 compact section 문구 수정 후
  재canary.
- canary 가 mix 되면 다음 phase (G4.5) 와 분리 상태 유지
  (`_card_metadata.lift_status` 확인으로 구별 가능).

### 5.4 Canary script 공통 패턴 (G4.2 + G4.3 carry)

모든 4 metric script + token script 는 G4.2 + G4.3 의 silent-absorb 차단 패턴
답습:

- **CP shape**: `scene["render_prompt_card"]["continuity_elements_used"]`
  (NOT 평면 `scene["continuity_elements_used"]`). G4.2 함정 carry —
  `detail_steps.py:2124` authoritative. G4.3 의 `_validate_scene_preflight()`
  helper carry — 4 metric scripts 동일 적용.
- **silent absorb 금지**: `or [] / or {}` 패턴 제거. `data.scenes` /
  `t2i_variations` / `t2i_prompt` 누락은 measurement_failures list 로 기록 후
  exit 1.
- **pinned tuple seen_in_cp post-loop validation**: pinning 의 (si, shi) 가
  CP 에 실제 존재했는지 enumerate 후 검증. 없으면 false STRICT pass 차단.
- **t2i_variations + per-variation t2i_prompt validation**: variation 누락 /
  variation 내부 t2i_prompt 누락 모두 measurement_failures.
- **`_DEFAULT_SENTINEL = object()` fixture pattern** (test 측): fixture 의
  default arg 와 None / [] 구분 필요할 때 sentinel. G4.1 R3-B2 fixture trap
  carry.
- **module-level constants single source**: `_CONTINUITY_GENERIC_PERSON_NOUNS`
  / `_CONTINUITY_FURNITURE_LAYOUT_TOKENS` / `_CONTINUITY_FRAMING_SCOPE_OPTIONS`
  builder + canary + unit test 동일 import. drift 차단 (G4.3 R1-I2/R2-I6
  carry).

---

## 6. Test Scope

### 6.1 Unit tests

파일: `backend/tests/unit/test_g4_4_continuity_lift.py`

| test | 설명 | assert |
|---|---|---|
| `test_continuity_has_cross_shot_id_substitution_rule` | `build_continuity_elements_used()` 반환에 `cross_shot_id_substitution_rule` dict 존재 + 6 required keys | dict + 6 key assert |
| `test_continuity_cross_shot_id_substitution_rule_required_keys_superset` | `cross_shot_id_substitution_rule` 의 6 required key (`applies_when` / `substitution` / `double_description_forbidden` / `preserve_pose_unchanged` / `no_repeat_after_pose` / `rationale_summary`) — **Override O-5 strict superset**: `assert set(returned_keys) >= EXPECTED_SET` (set equality 가 아닌 superset, extra keys 허용 G4.5 forward-compat) | `assert set(returned_keys) >= EXPECTED_SET and EXPECTED_SET <= set(returned_keys)` (substring 검증 금지) |
| `test_continuity_has_ref_usage_constraints` | `ref_usage_constraints` dict + 3 sub-key (`zoom_in_detail` / `exact_background` / `atmosphere_reference`) | dict + 3 sub-key assert |
| `test_continuity_ref_usage_zoom_in_detail_required_keys_superset` | `zoom_in_detail` sub-dict 의 5 required key (`scope_summary` / `forbidden_additions` / `required_phrasings` / `body_part_cross_ref` / `rationale_summary`) — Override O-5 superset | `assert set(returned_keys) >= EXPECTED_SET` |
| `test_continuity_ref_usage_zoom_in_detail_forbidden_additions_count` | `forbidden_additions` list-len == 5 | len assert |
| `test_continuity_ref_usage_zoom_in_detail_required_phrasings_count` | `required_phrasings` list-len == 3 | len assert |
| `test_continuity_ref_usage_zoom_in_detail_body_part_cross_ref_string_present` | `body_part_cross_ref` string 안 `"id_policy.body_part_focus_rule"` substring | substring assert |
| `test_continuity_ref_usage_exact_background_required_keys_superset` | `exact_background` sub-dict 의 5 required key — Override O-5 superset | `assert set(returned_keys) >= EXPECTED_SET` |
| `test_continuity_ref_usage_exact_background_permitted_additions_count` | `permitted_additions` list-len == 2 | len assert |
| `test_continuity_ref_usage_atmosphere_reference_required_keys_superset` | `atmosphere_reference` sub-dict 의 4 required key — Override O-5 superset | `assert set(returned_keys) >= EXPECTED_SET` |
| `test_continuity_ref_usage_atmosphere_reference_forbidden_imports_count` | `forbidden_imports` list-len == 3 | len assert |
| `test_continuity_has_view_consistency` | `view_consistency` dict + 9 required keys | dict + 9 key assert |
| `test_continuity_view_consistency_framing_scope_options_exact` | `framing_scope_options` 가 정확히 `["third_person", "close_up"]` 2 entries | list equality |
| `test_continuity_view_consistency_mixing_forbidden_is_bool_true` | `mixing_forbidden` 가 `True` (bool, NOT bool int subclass — G4.3/G3.2 trap carry: `assert mixing_forbidden is True and type(mixing_forbidden) is bool`) | identity + type assert |
| `test_continuity_view_consistency_id_policy_cross_ref_string_present` | `id_policy_cross_ref_for_body_part_focus` 안 `"id_policy.body_part_focus_rule"` substring (Override O-7 cross-card paired) | substring assert |
| `test_continuity_zoom_in_detail_body_part_cross_ref_string_present` | `ref_usage_constraints.zoom_in_detail.body_part_cross_ref` 안 `"id_policy.body_part_focus_rule"` substring (Override O-7 cross-card paired — view_consistency 와 동일 substring) | substring assert |
| `test_continuity_cross_card_pair_substrings_match` | view_consistency 의 `id_policy_cross_ref_for_body_part_focus` 와 ref_usage_constraints.zoom_in_detail 의 `body_part_cross_ref` 모두 동일 literal `"id_policy.body_part_focus_rule"` substring 보유 (Override O-7 — paired-string substring 검증, 두 필드 sync drift 차단) | both substring asserts together |
| `test_g4_4_view_mixing_canary_multi_ref_shot_filter` | atmosphere_reference + exact_background co-presence multi-ref shot 의 fixture 가 atmosphere_no_layout_import canary 의 shot-level filter (Override O-11: `any(ref.ref_usage == 'atmosphere_reference' for ref in previous_shot_refs)`) 에 포함되는지 검증 | shot included in filter when any ref atmosphere |
| `test_continuity_view_consistency_focus_on_is_focus_area_not_view_switch_string_present` | string 안 `"area of focus"` + `"NOT a view switch"` substring | 2 substring assert |
| `test_continuity_view_consistency_prop_orientation_rule_string_present` | `prop_orientation_rule` 안 `"one direction only"` substring | substring assert |
| `test_continuity_constraints_grew_to_seven_strings` | G4.1 base 2 strings → G4.4 7 strings (fixed binding + cross-shot sub + zoom_in_detail + exact_background + atmosphere_reference + single_camera + prop orientation) | len(constraints) == 7 |
| `test_continuity_constraints_string_1_fixed_binding_carry` | constraints[0] == G4.1 carry string (`"fixed_elements are continuity inputs..."`) | string equality |
| `test_continuity_constraints_string_2_cross_shot_substitution_inline` | constraints[1] 안 `"replace the common-noun person reference"` substring + `"do not also write a separate common-noun"` substring | 2 substring assert |
| `test_continuity_constraints_string_3_zoom_in_detail_inline` | constraints[2] 안 `"ref_usage='zoom_in_detail'"` + `"no new persons, props, background"` substring | 2 substring assert |
| `test_continuity_constraints_string_4_exact_background_inline` | constraints[3] 안 `"ref_usage='exact_background'"` + `"keep background elements consistent"` substring | 2 substring assert |
| `test_continuity_constraints_string_5_atmosphere_reference_inline` | constraints[4] 안 `"ref_usage='atmosphere_reference'"` + `"do not import furniture layout"` substring | 2 substring assert |
| `test_continuity_constraints_string_6_single_camera_inline` | constraints[5] 안 `"one t2i_prompt = one camera position"` + `"id_policy.body_part_focus_rule"` substring | 2 substring assert |
| `test_continuity_constraints_string_7_prop_orientation_inline` | constraints[6] 안 `"one direction only"` substring | substring assert |
| `test_continuity_fixed_elements_none_raises` | `fixed_elements=None` → AppError (G4.1/G4.2/G4.3 silent-absorb ban carry) | pytest.raises |
| `test_continuity_previous_shot_refs_none_raises` | `previous_shot_refs=None` → AppError | pytest.raises |
| `test_continuity_forward_zoom_targets_none_raises` | `forward_zoom_targets=None` → AppError | pytest.raises |
| `test_continuity_fixed_elements_empty_list_valid_with_static_subfields` | `fixed_elements=[]` + `previous_shot_refs=[]` + `forward_zoom_targets=[]` → return shape OK + 4 신규 sub-field 모두 존재 (R2-I4 from G4.3 carry — builder-static 확보) | shape valid + `assert "cross_shot_id_substitution_rule" in result and "ref_usage_constraints" in result and "view_consistency" in result` |
| `test_continuity_4_subfields_independent_of_input_lists` | hardcoded `[]` 입력에도 cross_shot_id_substitution_rule / ref_usage_constraints / view_consistency / constraints 4 모두 builder-static 출력 (Wave subagent hardcoded `[]` fixture trap 회피 — `if visible_entities:` truthy guard 안 배치 절대 금지 carry) | 모든 sub-field deep equality vs ground-truth |
| `test_card_metadata_lift_status_includes_continuity_keys` | `build_render_prompt_card()` 반환의 `_card_metadata.lift_status` 에 `continuity_cross_shot_id_lifted=true` + `continuity_ref_usage_lifted=true` + `continuity_view_consistency_lifted=true` 모두 존재 | 3 key assert |
| `test_card_metadata_rule_source_includes_continuity_keys` | `_card_metadata.rule_source` 에 `cross_shot_id_substitution_rule` / `ref_usage_rule` / `view_consistency_rule` 3 key 존재 | 3 key assert |
| `test_card_metadata_excluded_from_hash_after_g4_4_keys_added` | G4.2 R1-B1 + G4.3 R1-I11 carry — 신규 3 lift_status key 추가에도 hash 변경 0 (`_card_metadata` strip) | hash_before == hash_after |
| `test_card_hash_drifts_on_continuity_constraint_change` | `continuity_elements_used.constraints[5]` 문자열 1 char 변경 → hash drift | hash_A != hash_B |
| `test_card_hash_stable_on_continuity_lift_status_only_change` | `_card_metadata.lift_status.continuity_view_consistency_lifted` 만 toggle → hash 동일 | hash_A == hash_B |
| `test_assert_continuity_shape_validates_cross_shot_id_substitution_rule_shape` | `_assert_continuity_elements_used_shape()` 가 `cross_shot_id_substitution_rule` 누락 시 AppError | pytest.raises |
| `test_assert_continuity_shape_validates_ref_usage_constraints_shape` | `_assert_continuity_elements_used_shape()` 가 `ref_usage_constraints` 누락 또는 3 sub-key 미충족 시 AppError | pytest.raises |
| `test_assert_continuity_shape_validates_view_consistency_shape` | `_assert_continuity_elements_used_shape()` 가 `view_consistency` 누락 시 AppError | pytest.raises |
| `test_assert_continuity_shape_validates_view_consistency_required_keys` | `view_consistency` 의 9 required key 중 1 누락 시 AppError | pytest.raises |

총 unit tests: **~32-38** (Override O-16 정합 — 본 §6.1 table 33 enumerate,
+5 buffer for plan-R3+R4 audit additions). G4.3 31 carry pattern 위에 +7 신규
sub-field shape + ref_usage 3 sub-key 분기 검증 + Override O-7 cross-card 2
신규 row + Override O-11 multi-ref filter 1 신규 row. 정확 count 는 plan
drafting 시 R3+R4 audit 에서 확정.

### 6.2 Integration tests

파일: `backend/tests/integration/test_g4_4_continuity_integration.py`

| test | 설명 | assert |
|---|---|---|
| `test_v19_system_prompt_missing_cross_shot_section` | v19 system.md 를 load 후 `"## 교차 샷 고정 요소 통합 규칙"` heading 없음 | heading not present |
| `test_v19_system_prompt_missing_ref_usage_section` | v19 system.md 에 `"## 앞쪽 참조 샷 처리 — ref_usage 유형별 지시"` heading 없음 | heading not present |
| `test_v19_system_prompt_missing_single_view_section` | v19 system.md 에 `"## 절대 규칙 — 단일 시점"` heading 없음 | heading not present |
| `test_v19_system_prompt_has_continuity_section` | v19 system.md 에 `"## Continuity"` heading 존재 | heading present |
| `test_v19_system_prompt_keeps_id_policy_section` | v19 system.md 에 G4.3 의 `"## ID Policy"` heading **여전히 존재** (G4.4 가 G4.3 영역 변경 0) | heading present |
| `test_v19_system_prompt_keeps_background_binding_section` | v19 system.md 에 G4.2 의 `"## Background Binding"` heading 여전히 존재 | heading present |
| `test_v19_prompt_token_reduction_vs_v18` | v19 system.md 줄 수 < **v18 baseline 587 lines - 32 (compact section gate Override O-2) - 보수 buffer 5 = 550 lines 미만** (Override O-14 도출식). 기존 "v18 - 50" 임의 수치 대체. | `len(v19_lines) < 550` |
| `test_v19_continuity_section_line_count_under_32` | v19 system.md 의 `## Continuity` heading 부터 다음 `##` heading 사이 lines `wc -l` ≤ 32 (Override O-2 binding gate, bullet 1 line 강제, wrap 줄도 카운트) | `assert continuity_section_line_count <= 32` |
| `test_continuity_compact_section_references_id_policy_body_part_focus_rule` | v19 system.md 의 Continuity compact section 안 `"id_policy.body_part_focus_rule"` substring 1+ 등장 (Override O-7 cross-card paired-string substring + G4.3 cross-card reference 보존) | substring present |
| `test_shot_with_fixed_element_id_mapping_carries_substitution_rule` | `_collect_card_inputs()` + `build_render_prompt_card()` + inject 경로 통해 user_prompt 에 `continuity_elements_used.cross_shot_id_substitution_rule` dict 포함 확인 | JSON parse → dict valid |
| `test_zoom_in_detail_shot_card_carries_ref_usage_constraints` | ref_usage='zoom_in_detail' shot 의 path → user_prompt 에 `continuity_elements_used.ref_usage_constraints.zoom_in_detail` 5 required key 모두 포함 | 5 substring in injected JSON |
| `test_atmosphere_reference_shot_card_carries_forbidden_imports` | ref_usage='atmosphere_reference' shot path → user_prompt 에 `forbidden_imports` 3 entry inline 또는 substring | 3 substring in injected JSON |
| `test_view_consistency_carries_id_policy_cross_ref` | 모든 shot path → user_prompt 에 `view_consistency.id_policy_cross_ref_for_body_part_focus` 안 `"id_policy.body_part_focus_rule"` substring | substring in injected JSON |
| `test_detail_steps_no_inline_zoom_in_detail_prose_after_g4_4` | `detail_steps.py` source string 안 `"확대 샷 원칙"` substring 부재 + `"[중요 — 확대 샷 원칙 (zoom_in_detail)]"` substring 부재 (Override O-3 — Wave 2 inline prose 9 줄 삭제 검증) | both substrings absent |
| `test_card_hash_drift_on_continuity_constraint_change_in_verify` | continuity_elements_used.constraints 변경 후 `verify_completion()` 실행 → hash drift detected → status partial | verify marks partial |
| `test_v18_checkpoint_resume_on_v19_escalates_force` | v18 cp (schema_version=7) 를 v19 consumer 로 resume → `_config_hash` mismatch → `mode="force"` escalate (G4.1 의 `test_g4_1_card_consumer_wiring.py::test_step_runner_actually_escalates_to_force_on_v15_resume` carry — G4.4 v19 prompt 가 동일하게 작동) | step_runner.escalate observed |
| `test_g3_2_sentinel_coexists_with_g4_4_continuity` | 임의 shot → card `continuity_elements_used` 신규 sub-field + variation-level `owned_validation` sentinel + `background_binding.constraints` + `id_policy` 4 신규 sub-field 모두 CP 에 존재 | four areas present in CP dict |
| `test_4_way_alignment_post_g4_4_bump` | alignment test runner — v19 prompt dir + detail_steps.py:105 + version_registry.py:34 + version_registry.py:126 4 곳 lockstep 갱신 검증 | 4 locations match |

총 integration tests: **~17-18** (Override O-2 line count test + Override O-3 detail_steps prose absent test 신규 추가).

> **`test_v18_checkpoint_resume_on_v19_escalates_force` (G4.2 + G4.3 carry)**:
> G4.3 의 v17→v18 escalation test 와 같은 escalate path 가 v18→v19 에 자동
> 적용됨을 검증. `SCENE_DETAIL_PROMPT_VERSION` 변경이 `_config_hash` 에
> reflect 되므로 step_runner 자동 force escalate.

### 6.3 Regression tests

G4.4 구현 후 반드시 실행:

```
pytest backend/tests/ -x --timeout=120
```

기준: G4.3 baseline 대비 0 regressions. G4.4 test 추가 후 total count 증가만
허용. 예상 baseline = G4.3 끝 약 2103+ pass + G4.4 ~38 unit + ~16 integration
= 약 2157+ pass.

G3.2 sentinel test suite (`test_g3_2_*.py`) + G4.1 (`test_g4_1_*.py`) + G4.2
(`test_g4_2_*.py`) + G4.3 (`test_g4_3_*.py`) 가 G4.4 이후에도 모두 pass 해야
한다 (continuity_elements_used 외 영역 변경 없음).

### 6.4 alignment test (4-way sync)

`backend/tests/prompts/test_scene_detail_continuity_alignment.py`:

- `test_scene_detail_prompt_version_constant_matches_latest` —
  `SCENE_DETAIL_PROMPT_VERSION` 가 v19 latest 와 일치 (G4.4 가 v18 → v19 bump).
- `test_scene_detail_schema_version_synced_with_manifest` —
  `SCENE_DETAIL_SCHEMA_VERSION` 유지 (= 7) — continuity_elements_used 신규
  sub-field 는 cp shape 변경 아님 (G4.2 R1-I6 carry).
- `test_scene_detail_version_registry_aligned` —
  `version_registry._MODULE_INFO['scene_detail_composer'].prompt_dependency`
  가 `scene_detail/v19` 로 갱신 (G4.3 R3R4-B1 carry: dict access NOT attribute,
  major suffix only).
- `test_scene_detail_module_versions_aligned` —
  `MODULE_VERSIONS["scene_detail_composer"] == "1.19.0"` 검증 (silent fallback
  ban: `MODULE_VERSIONS.get(..., "")` 패턴 금지 — 명시적 키 존재 + 타입
  검증, G4.3 R5-I2 carry).

> **4-way sync constraint (G4.3 R3-I2 carry)**: v19 push 시 다음 4 곳
> lockstep 갱신 필수:
> 1. `prompts/_base/scene_detail/19.<TIMESTAMP>/system.md` 디렉토리 생성
> 2. `backend/app/core/steps/detail_steps.py:105` —
>    `SCENE_DETAIL_PROMPT_VERSION = "19.<TIMESTAMP>"`
> 3. `backend/app/core/version_registry.py:34` —
>    `MODULE_VERSIONS["scene_detail_composer"]` minor bump (1.18.0 → 1.19.0)
> 4. `backend/app/core/version_registry.py:126` —
>    `_MODULE_INFO["scene_detail_composer"].prompt_dependency = "scene_detail/v19"`
>    (`prompt_dependency` 값 = major suffix only `"scene_detail/v19"` —
>    G4.3 R3R4-B1 carry)

---

## 7. G4.x Sequencing

### 7.1 G4.4 entry condition (= G4.3 exit criteria — entry gate split carry)

G4.3 exit criteria (G4.3 spec §5.1):
- G4.3 4 metric STRICT (body-part focus / close-framing face / reproduction
  surface / demographic descriptor) candidate count == 0 OR ratio ≥ baseline
- G4.3 token delta gate `≥ -1050` (실측 `-2073` ✓)
- G4.3 unit + integration test green ✅ (commit `51d6c5e`)
- v18 prompt push ✅ (`prompts/_base/scene_detail/18.202605041549/`)
- 4-way sync ✅ (G4.3 Wave 2 complete)

> **R3-Entry-Note (G4.3 carry — entry gate split)**: Phase 6 G4.3 production
> canary 미실행 시에도 G4.4 spec drafting / R1+R2 audit / plan drafting / Wave
> 1-5 구현 진행 가능. 단, **G4.4 commit gate 는 G4.3 production canary 6
> gate AND G4.4 self-canary 6 gate 둘 다 통과 필수**. canary 결과는 G4.4 v19
> push 직전 검증 — token + pytest gate 는 G4.3 commit 시점에 이미 dry-run
> 통과 (token canary delta -2073 < -1050 ✓), 다른 4 G4.3 gate 는 production
> CP 데이터 필요.

### 7.2 G4.4 작업 순서

**Wave 1-A: render_prompt_card.py 변경**

1. `build_continuity_elements_used()` 에 G4.4 **3 new sibling policy dicts +
   extended constraints list** (Override O-6 명명 정정) 작성 로직 추가 (G4.2
   R2-B2 in-scope carry — `_card_metadata` 작성 / 신규 constraint strings):
   - `cross_shot_id_substitution_rule` dict (6 required keys: applies_when /
     substitution / double_description_forbidden / preserve_pose_unchanged /
     no_repeat_after_pose / rationale_summary)
   - `ref_usage_constraints` dict (3 sub-key: zoom_in_detail /
     exact_background / atmosphere_reference, 각 sub-key 5/5/4 required keys)
   - `view_consistency` dict (9 required keys: framing_scope_options /
     single_camera_rule / third_person_handling / close_up_handling /
     focus_on_is_focus_area_not_view_switch /
     id_policy_cross_ref_for_body_part_focus / prop_orientation_rule /
     mixing_forbidden / rationale_summary)
2. `continuity_elements_used.constraints` G4.1 base 2 → G4.4 7 (1 fixed
   binding + 1 cross-shot substitution + 1 zoom_in_detail + 1 exact_background +
   1 atmosphere_reference + 1 single_camera + 1 prop orientation). ref_usage
   분기 무관 — 4 신규 sub-field 는 builder-static (id_policy 의 perception
   분기와 달리 G4.4 는 모든 shot 동일).
3. `_assert_continuity_elements_used_shape()` (`render_prompt_card.py:1353-1385`)
   에 4 신규 sub-field strict 검증 추가. 누락 시 AppError. G4.3 R2-I5 carry —
   `_card_metadata` 는 free-form.
4. `build_render_prompt_card()` wrapper 에서 `_card_metadata.lift_status` 에
   **4 신규 key** (Override O-9 정확 명):
   `continuity_cross_shot_id_substitution_lifted` /
   `continuity_ref_usage_constraints_lifted` /
   `continuity_view_consistency_lifted` /
   `continuity_constraints_extended` 추가 + `rule_source` 에 3 신규 key
   (`cross_shot_id_substitution_rule` / `ref_usage_rule` /
   `view_consistency_rule`) 추가. 모든 mode 에서 `True` (continuity_elements_used
   는 ref_usage 무관 동일 적용).
5. module-level constants 도입 (G4.3 R1-I2/R2-I6 carry — builder + canary +
   unit test 동일 import):
   - `_CONTINUITY_GENERIC_PERSON_NOUNS` (R1+R2 audit 시 ground-truth list 확정 —
     초기 후보: `("an Asian man", "a young woman", "a figure", "a man",
     "a woman", "an elderly figure", "a child")` 등 generic Asian-default
     placeholders)
   - `_CONTINUITY_FURNITURE_LAYOUT_TOKENS` (R1+R2 ground-truth — 초기 후보:
     `("furniture", "wall position", "room layout", "same chair", "same table",
     "same wall", "same window")`)
   - `_CONTINUITY_FRAMING_SCOPE_OPTIONS = ("third_person", "close_up")` (2
     entries — view_consistency.framing_scope_options ground-truth)
   - `_CONTINUITY_REF_USAGE_VALUES = ("zoom_in_detail", "exact_background",
     "atmosphere_reference")` (3 entries)
   - `_CONTINUITY_ZOOM_FORBIDDEN_ADDITIONS_COUNT = 5` / `_CONTINUITY_ZOOM_REQUIRED_PHRASINGS_COUNT = 3`
     / `_CONTINUITY_EXACT_PERMITTED_ADDITIONS_COUNT = 2` /
     `_CONTINUITY_ATMOSPHERE_FORBIDDEN_IMPORTS_COUNT = 3` (count constants)

**Wave 1-B: v19 prompt 신규 디렉토리**

6. `prompts/_base/scene_detail/19.<TIMESTAMP>/` 디렉토리 생성:
   - `system.md` = v18 system.md - 66 lines [3 continuity prose] + 30-line
     "Continuity" compact section (§3.3).
   - `detail_schema.json` = v18 verbatim copy (cp shape 변경 없음).
   - `user.md` = v18 verbatim copy (변경 없음).
7. v18 sealed (덮어쓰기 금지 — `feedback_prompt_versioning.md`).
8. tiktoken cl100k_base 실측 — Wave 1-B draft 완성 직후 §3.4 estimate vs
   실측 비교 + token gate threshold §5.1 보정 (G4.2 R2-M4 carry).

**Wave 2: detail_steps.py + version_registry 4-way sync + Override O-3 inline prose 삭제**

9. `detail_steps.py:105` —
   `SCENE_DETAIL_PROMPT_VERSION = "18.202605041549"` →
   `"19.<TIMESTAMP>"`. SCENE_DETAIL_SCHEMA_VERSION 유지 (= 7).
10. `detail_steps.py` inject 직전 `_card_metadata` strip 로직 변경 0 (G4.2
    `14d14cb` + G4.3 carry 그대로 유지) — continuity_elements_used 의 본
    sub-field 는 inject 대상 (LLM 이 참조). `_card_metadata` 만 strip.
11. **Override O-3 — `detail_steps.py:1555-1563` inline prose 9 줄 삭제**:
    `if has_zoom_in_detail:` 블록 안 9 줄 prose ("[중요 — 확대 샷 원칙
    (zoom_in_detail)] / 새 인물·소품·자세 추가 금지 / 인체 부위 초점이면
    보통명사 / 환경 동일") 전체 삭제. card 의 `ref_usage_constraints.zoom_in_detail`
    sub-dict 가 동일 데이터 carry. integration test
    `test_detail_steps_no_inline_zoom_in_detail_prose_after_g4_4` (§6.2) 가
    `"확대 샷 원칙"` substring 부재 강제.
12. `version_registry.py:34` — `MODULE_VERSIONS["scene_detail_composer"]`
    `1.18.0` → `1.19.0`.
13. `version_registry.py:126` —
    `_MODULE_INFO["scene_detail_composer"].prompt_dependency =
    "scene_detail/v19"` (G4.3 R3R4-B1 carry: dict access NOT attribute, major
    suffix only). `updated_at = "2026-05-05"` 또는 신규 timestamp.
14. alignment test
    (`backend/tests/prompts/test_scene_detail_continuity_alignment.py`) green
    유지 검증 — silent fallback 제거 패턴 (`MODULE_VERSIONS.get(..., "")` /
    `_MODULE_INFO.get(..., {})` 금지) carry.

**Wave 3: canary scripts 5종 작성**

15. `scripts/canary/g4_4_double_description.py` — fixed_element id-mapping
    shot 만 scan, regex `r"\bC\d{2}(?:O\d{2})?\b"` ±50 chars window 안 module-
    level `_CONTINUITY_GENERIC_PERSON_NOUNS` 동시 등장 검출. **G4.2 + G4.3
    silent-absorb 차단 패턴 carry** (CP shape / measurement_failures /
    pinned tuple seen_in_cp post-loop).
16. `scripts/canary/g4_4_zoom_in_detail_no_new_entity.py` — ref_usage='zoom_in_detail'
    shot 만 scan, prior shot t2i_prompt 의 C##/C##O## set 와 current t2i_prompt
    의 C##/C##O## set diff 검출.
17. `scripts/canary/g4_4_atmosphere_no_layout_import.py` — **shot-level filter
    = `any(ref.ref_usage == 'atmosphere_reference' for ref in previous_shot_refs)`**
    (Override O-11 multi-ref filter — multi-ref shot 시 atmosphere check 만
    적용, exact_background co-presence false-positive 는 P1 follow-up). module-
    level `_CONTINUITY_FURNITURE_LAYOUT_TOKENS` 검출. **Override O-17 — regex
    1차 detector, baseline 비0 시 P1 follow-up 으로 LLM-validator (gpt-5.4-mini
    judge) 전환 검토** (`feedback_no_regex_postprocessing.md`).
18. `scripts/canary/g4_4_view_mixing.py` — 모든 shot scan, 같은 인물 ID 의
    전신 묘사 + body-part close-up 패턴 동시 등장 검출. body-part 검출은
    G4.3 의 `_ID_BODY_PART_TRIGGERS` 4 trigger alternation 재사용 (cross-import).
    **Override O-17 — regex brittleness**: false-positive (e.g. "C01O02 stands
    in profile, hand resting on the table" 자연스러운 부분 묘사) 발견 시
    LLM-validator 전환 follow-up.
19. `scripts/canary/g4_4_token_count.py` — `tiktoken cl100k_base` 로 v19
    system.md 실측 + system+inject 합산 측정 (Override O-1 secondary report-only
    metric). CLI args = pinning 7 fields (G4.2 plan-R2-I4 carry).

**Wave 4: tests (Override O-18 — test 파일 = Wave 4)**

20. `backend/tests/unit/test_g4_4_continuity_lift.py` — ~32-38 unit tests
    (§6.1, Override O-16 정합).
21. `backend/tests/integration/test_g4_4_continuity_integration.py` — ~17
    integration tests (§6.2 — line count gate test + detail_steps prose absent
    test 추가).
22. `backend/tests/prompts/test_scene_detail_continuity_alignment.py` — 4-way
    alignment test (§6.4) + silent fallback 제거 패턴.
23. fixture pattern: `_DEFAULT_SENTINEL = object()` carry (G4.1 R3-B2 / G4.2
    plan-R1-I4 / G4.3 plan-R1-I4 carry — None vs [] vs default 구분).
24. **Wave subagent dispatch 시 hardcoded `[]` fixture trap 회피 명시** (G4.1
    + G4.3 R4-M6 carry): 신규 3 policy dicts (Override O-6 명명) 는 builder-
    static — `if visible_entities:` 또는 `if fixed_elements:` 류 truthy guard
    안 배치 절대 금지. test
    `test_continuity_4_subfields_independent_of_input_lists` 가 강제.

**Wave 5: production canary execution (Override O-18 — 사용자 실행)**

25. Baseline canary capture (v18) — pinning 7 field 명시.
26. Candidate canary capture (v19) — 동일 pinning.
27. 4 metric + token + pytest = 6 gate 판정 (Override O-1 token gate
    `≤ -540` system-only binding + line count `≤ 32` Override O-2).

**Wave 6: single commit + push (Override O-18)**

28. 4 영역 (render_prompt_card.py + detail_steps.py [inline prose 삭제 포함
    Override O-3] + version_registry.py + prompts/_base/scene_detail/19.../)
    + canary scripts 5종 + tests 3 file (unit + integration + alignment) =
    single commit + push.
29. Codex + Claude 듀얼 리뷰 → 수정 → 재push (G4.2 + G4.3 패턴 — 3 iter dual
    review fix loop).

### 7.3 G4.5 entry condition (= G4.4 exit criteria)

G4.5 (final prompt slimming pass) 는 다음 모두 충족 후 진입:

- G4.4 4 metric STRICT (double-description / zoom_in_detail entity 추가 /
  atmosphere_reference layout import / view-mixing) 통과
- G4.4 token delta gate `≤ -540` system-only 통과 (Override O-1 binding)
- G4.4 v19 compact section line count gate `≤ 32 lines` 통과 (Override O-2 binding)
- G4.4 unit + integration test suite green
- v19 prompt push + 4-way sync 완료

G4.5 가 다루는 scope: final prompt slimming pass — G4.2-4 lift 후 잔여 prose
heading count ≤ 10 enforcement + 추가 token 절감 + 미 lift 된 reasoning
prose 통합. G4.4 와 별개 phase.

---

## 7.x Entry gate 명시 (G4.2 + G4.3 carry)

| G4.3 Exit gate | 현재 상태 (2026-05-05) |
|---|---|
| body-part focus pattern 0 STRICT | canary measurement pending (Phase 6 production 사용자 실행) |
| close-framing face forbidden 0 STRICT | canary measurement pending |
| reproduction surface composite ID 0 STRICT | canary measurement pending |
| demographic descriptor present ratio ≥ baseline | canary measurement pending |
| unit + integration green | ✅ (commit `51d6c5e`, 50/50 + 18/18) |
| token delta ≤ -1050 | ✅ dry-run -2073 |
| v18 prompt push | ✅ (`prompts/_base/scene_detail/18.202605041549/`) |
| 4-way sync (registry + alignment test) | ✅ (G4.3 Wave 2 complete) |

> **G4.4 spec drafting 진행 가능 (entry gate split carry from G4.3 R3-I3)** —
> production canary 결과는 G4.4 commit gate 로만 필요 (spec / plan / Wave
> 1-5 작성은 무관). G4.4 commit gate = G4.3 production canary 6 gate AND G4.4
> self-canary 6 gate.

---

## 8. Risk Register

### 8.1 G4.4-unique risks

| risk | mitigation | measurable threshold |
|---|---|---|
| v19 compact section 이 fixed_element substitution 룰 (자세 보존 + 반복 금지) 을 충분히 carry 하지 않아 LLM 이 보통명사 + C## 이중 묘사 출력 | constraints[1] + `cross_shot_id_substitution_rule` 6 sub-key 명시 + canary STRICT 0 | candidate count == 0 |
| zoom_in_detail prose 삭제 후 LLM 이 prior shot 에 없던 entity 추가 출력 | card `ref_usage_constraints.zoom_in_detail.forbidden_additions` 5 entry + constraints[2] + canary STRICT 0 | candidate count == 0 |
| atmosphere_reference prose 삭제 후 LLM 이 prior shot 의 가구 layout import | card `ref_usage_constraints.atmosphere_reference.forbidden_imports` 3 entry + constraints[4] + canary STRICT 0 | candidate count == 0 |
| 시점 혼합 prose 삭제 후 LLM 이 전신 + 같은 인물 body-part close-up 한 prompt 결합 | card `view_consistency.{single_camera_rule,mixing_forbidden,third_person_handling,close_up_handling}` + constraints[5] + canary STRICT 0 | candidate count == 0 |
| `_CONTINUITY_GENERIC_PERSON_NOUNS` ground-truth list 가 actual production prompt 의 generic person noun 다양성을 cover 하지 않음 | R1+R2 audit 에서 ground-truth list 확정 + Section A line 117-120 예시 prose 의 actual 표현 카탈로그 + Wave 5 production canary 에서 baseline scan time 에 missed phrase 자동 발견 시 list 확장 (P1 follow-up 별도 추적) | actual production canary baseline 에 등장한 generic noun 의 ≥ 90% list cover |
| `_CONTINUITY_FURNITURE_LAYOUT_TOKENS` 가 너무 broad 해 atmosphere_reference shot 가 "the dim ambient pool"같은 ambient mood 표현을 false-positive 검출 | R1+R2 audit + R3+R4 plan audit 에서 token list 정정. `furniture` / `wall position` / `same chair` 류 layout-specific 만 포함, ambient/lighting tone 표현 제외 | unit test fixture 가 ambient mood 표현 false-positive 0 검증 |
| view-mixing canary 가 "C01O02 stands in profile, hand resting on the table" 류 자연스러운 부분 묘사를 false-positive 검출 | regex 가 same-character ID + body-part close-up trigger phrase (focus on / close on / tight on / detail on) 둘 다 동일 prompt 안 동시 등장 시에만 trigger. 단순 hand 언급은 trigger 아님. R1+R2 audit 에서 regex 정정 | unit test fixture 가 자연스러운 부분 묘사 false-positive 0 검증 |
| `_card_metadata.lift_status` 신규 4 key (Override O-9 정정) 가 hash 에 의도치 않게 포함되어 hash 가 매 build 마다 변경 | G4.2 R1-B1 + G4.3 carry: `_card_metadata` 전체 가 `canonicalize_render_prompt_card()` 에서 strip. unit test `test_card_metadata_excluded_from_hash_after_g4_4_keys_added` 가 강제 | `_card_metadata` not in canonicalized output (post G4.4 key 추가) |
| canary PID 의 fixed_element id-mapping shot / ref_usage shot 부족 시 metric 분모 0 | baseline / candidate scene set 선정 시 fixed_element id-mapping shot 최소 5개 + zoom_in_detail shot 최소 2개 + atmosphere_reference shot 최소 1개 보장 (§5.2) | scene_set in canary JSON ≥ 5+2+1 분리 |
| canary baseline double-description / zoom_in_detail / atmosphere / view-mixing count 가 비0 인 경우 | strict: candidate == 0 강제. baseline 비0 이면 P1 follow-up 으로 별 추적 (G4.4 scope 밖이지만 production 결함 시그널) | candidate count == 0 mandatory; baseline 비0 면 별 issue 등록 |
| Wave subagent fixture 가 신규 3 policy dicts (Override O-6 명명 정정) 누락한 partial card 사용 → production drift cascade | `_DEFAULT_SENTINEL = object()` fixture pattern + `_make_full_card()` helper 가 항상 3 policy dicts + extended constraints 채움 + `_assert_continuity_elements_used_shape()` strict (G4.3 R2-I5 carry, Override O-5 superset) | unit test `test_assert_continuity_shape_validates_*_shape` 3종 |
| view_consistency 의 cross-card reference (`id_policy.body_part_focus_rule`) 가 G4.3 의 sub-field 와 sync 안 맞아 inconsistent | **Override O-7 paired-string substring**: view_consistency.id_policy_cross_ref_for_body_part_focus + ref_usage_constraints.zoom_in_detail.body_part_cross_ref 둘 다 동일 literal substring 보유. unit test `test_continuity_view_consistency_id_policy_cross_ref_string_present` + `test_continuity_zoom_in_detail_body_part_cross_ref_string_present` + `test_continuity_cross_card_pair_substrings_match` 3 곳 검증. Wave 1-A 코드에 maintainer 코멘트 ("rename 시 G4.4 spec patch 필요"). | all 3 tests pass with same substring |
| ref_usage_constraints 의 nested dict 깊이 (3 sub-key × 4-5 sub-sub-key) 가 `_assert_continuity_elements_used_shape()` 검증 누락 위험 | nested validator helper `_assert_ref_usage_constraints_shape()` 신설 + 3 sub-key 각각 required key set 검증 (Override O-5 superset) + 4 unit test (`test_assert_ref_usage_constraints_*`) | nested shape AppError on missing sub-key |
| **multi-ref shot atmosphere_reference filter false-positive** (Override O-11) | atmosphere_reference + exact_background co-presence shot 에서 atmosphere check 만 적용 — `g4_4_atmosphere_no_layout_import.py` shot-level filter `any(ref.ref_usage == 'atmosphere_reference' for ref in previous_shot_refs)`. exact_background 의 `same wall` 표현이 false-positive 발생 가능 → P1 follow-up 추적 (별 issue) | unit test `test_g4_4_view_mixing_canary_multi_ref_shot_filter` |
| **regex view-mixing canary brittleness** (Override O-17) | regex 는 1차 detector 만, baseline 비0 시 P1 follow-up 으로 LLM-validator (gpt-5.4-mini judge) 전환 검토. 의미 판단은 LLM-first (`feedback_no_regex_postprocessing.md`) | false-positive 발견 시 LLM-validator follow-up issue 등록 |

### 8.2 G4.1+G4.2+G4.3 carry / inherited checks

| inherited risk | carry source | G4.4 enforcement |
|---|---|---|
| v19 로 resume 시 old v18 cp 가 자동 force escalate | G4.1 R1-B1 (`_config_hash` mismatch 검증) | §6.2 `test_v18_checkpoint_resume_on_v19_escalates_force` |
| `_user_edited` reuse path 에서 stored card 와 recompute card hash drift | G4.1 Wave 4 R4 B1/B2/B3 (ctx-driven single source) | G4.1 패턴 그대로 — G4.4 변경 없음 |
| schema_version != 7 result 에서 card check bypass warning step 당 1회 emit | G4.1 cleanup I5 (commit c656ce3) | G4.4 변경 없음 — 동일 동작 |
| `or [] / or {}` silent absorb pattern 차단 | G4.2 iter1 C3 (commit `14d14cb` cycle) + G4.3 R5-B1 | G4.4 canary scripts 5종 + builder 모두 동일 pattern carry. 신규 producer/canary 코드 review 시 `grep -n "or \[\]\|or {}"` 검사 (Trap #1) |
| `_DEFAULT_SENTINEL = object()` fixture pattern 필수 | G4.1 R3-B2 / G4.2 plan-R1-I4 / G4.3 plan-R1-I4 | G4.4 fixture (None / [] / default 셋 명시 구분) — Trap #2 |
| canary CP shape `scene["render_prompt_card"]["continuity_elements_used"]` (NOT 평면) | G4.2 함정 + G4.3 `_validate_scene_preflight()` helper carry | G4.4 4 metric scripts 모두 동일 helper 사용 — Trap #3 |
| pinned tuple seen_in_cp post-loop validation + sentinel pre-pass + gate fail → exit 1 | G4.2 iter2 B1 + G4.3 carry | G4.4 canary scripts 5종 동일 pattern — Trap #4 |
| `_MODULE_INFO` dict access (NOT attribute) + `prompt_dependency = "scene_detail/v19"` (major suffix only) | G4.3 R3R4-B1 | G4.4 alignment test 동일 — Trap #5 |
| token gate signed convention `token_delta = candidate - baseline`, gate `<= -<value>` | G4.3 R4-B4 | §5.1 token gate 수치는 R1+R2 audit 후 확정 — Trap #6 |
| compact section line count 정확 (multi-line wrap trap) — bullet 1줄 강제 | G4.3 R3R4-B3 | §3.3 compact section 30 lines target, Wave 1-B draft 시 line count 자체 점검 — Trap #7 |
| module-level constants single source — builder + canary + unit test 동일 import. drift 차단 | G4.3 R1-I2/R2-I6 | Wave 1-A step 5 — Trap #8 |
| 시나리오 의존 0 — 작품 고유명사 절대 금지. 예시 prose 도 generic Asian man / a young woman / a hand 등 | CLAUDE.md 절대 규칙 + `feedback_no_scenario_names.md` | §2.2 / §3.3 / §6.1 / §9 모든 예시 generic placeholder 사용 — Trap #9 |
| prompt versioning — NEVER overwrite v18, 새 dir `prompts/_base/scene_detail/19.<TIMESTAMP>/` | CLAUDE.md `feedback_prompt_versioning.md` | Wave 1-B step 6-7 — Trap #10 |
| 4-point sync lockstep — prompt dir + detail_steps.py:105 + version_registry.py:34 + version_registry.py:126 | G4.3 R4-I6 | Wave 2 step 9-13 + alignment test — Trap #11 |
| `_card_metadata.rule_source` 키 명세 — namespace 분리되어 top-level field 와 동명 OK | G4.3 R5-I1 | §2.2 _card_metadata 표기 namespace 일관 — Trap #12 |
| alignment test silent fallback 제거 — `MODULE_VERSIONS.get(..., "")` / `_MODULE_INFO.get(..., {})` 금지 — 명시적 키 존재 + 타입 검증 | G4.3 R5-I2 | §6.4 4 alignment test — Trap #13 |
| Wave subagent hardcoded `[]` fixture trap — 4 sub-field 는 builder-static, `if visible_entities:` truthy guard 안 배치 절대 금지 | G4.1 origin / G4.3 R4-M6 | §6.1 `test_continuity_4_subfields_independent_of_input_lists` 강제 — Trap #14 |
| `t2i_variations` + per-variation `t2i_prompt` validation | G4.2 iter2 B2 + iter3 B3 | G4.4 4 metric scripts 모두 검증 |
| LLM 입력 truncation 절대 금지 (`[:400]`, `[:500]` 등) | CLAUDE.md 절대 규칙 | G4.4 canary script 가 t2i_prompt 전문 scan, deepcopy lossless |
| regex post-processing 의존 금지 (`feedback_no_regex_postprocessing.md`) | CLAUDE.md 메모리 | G4.4 canary 의 regex 는 ID/trigger 검출용 deterministic pattern 만, 의미 판단은 module-level constant token list (LLM validator 또는 prompt 우선) |
| **silent-fallback 잔존 carry (Override O-8)** | `backend/app/core/version_registry.py:148-155` 의 `MODULE_VERSIONS.get(..., "0.0.0")` / `_MODULE_INFO.get(..., {})` 잔존 = G4.1 inheritance, G4.4 scope 외 | **G4.5 또는 별도 cleanup PR carry follow-up**. G4.4 신규 코드 (Wave 1-A producer + canary + tests) 는 새로운 silent-fallback 도입 절대 금지 — Trap #15 |

---

## 9. Glossary

| 용어 | 정의 |
|---|---|
| `continuity_elements_used` | `RenderPromptCard` 의 5 semantic field 중 하나. fixed-element / previous-shot-ref / forward-zoom-target 입력 + cross-shot substitution / ref_usage / view consistency 룰을 deterministic 하게 기술. |
| Cross-shot fixed-element substitution rule (v18:109-120) | fixed_elements[i].character_name → C## 매핑이 id_policy 안에 존재하면 fixed_elements[i].description 안의 보통명사 인물 묘사를 C##/C##O## 로 치환 + 이중 묘사 금지 + 자세 보존 + 반복 금지. |
| Ref usage rule (v18:122-156) | previous_shot_refs[i].ref_usage 에 따라 t2i_prompt 작성 분기. 3 type: zoom_in_detail / exact_background / atmosphere_reference. |
| `zoom_in_detail` ref_usage | 같은 시공간 카메라 이동/확대만. 새 인물·소품·배경·자세·다른 순간 추가 금지. body-part Focus 시 id_policy.body_part_focus_rule 적용. |
| `exact_background` ref_usage | 같은 방 다른 순간/앵글. 인물·동작 새로 묘사하되 배경 일관 유지. ignore/keep 자동 처리. |
| `atmosphere_reference` ref_usage | 다른 공간. 톤만 carry, 가구·벽 layout import 금지. |
| View consistency rule (v18:308-326) | one t2i_prompt = one camera position. 3인칭 + close-up 혼합 금지. Focus-on = 초점 영역 (시점 전환 X). 소품 방향 한 방향만. |
| `cross_shot_id_substitution_rule` | continuity_elements_used sub-field. 6 required keys: applies_when / substitution / double_description_forbidden / preserve_pose_unchanged / no_repeat_after_pose / rationale_summary. |
| `ref_usage_constraints` | continuity_elements_used sub-field. 3 sub-key dict (zoom_in_detail / exact_background / atmosphere_reference), 각 sub-key 5/5/4 required key. |
| `view_consistency` | continuity_elements_used sub-field. 9 required keys: framing_scope_options (2 entries) / single_camera_rule / third_person_handling / close_up_handling / focus_on_is_focus_area_not_view_switch / id_policy_cross_ref_for_body_part_focus / prop_orientation_rule / mixing_forbidden (bool true) / rationale_summary. |
| `continuity_card_snapshot_hash` | continuity_elements_used field 의 partial canonicalize hash. canary pinning 7 field 중 1. baseline / candidate continuity_elements_used shape 동일 검증. **helper `compute_continuity_snapshot_hash(card) = sha256(json.dumps(card["continuity_elements_used"], sort_keys=True, ensure_ascii=False))[:16]`** (Override O-12 명시 — `_card_metadata` 는 envelope sibling 이므로 자연 제외, 추가 strip 불필요. G4.3 `compute_id_policy_snapshot_hash` 패턴 carry). |
| Generic person nouns **(R1+R2 ground-truth source)** | cross-shot substitution rule 적용 시 보통명사 인물 묘사로 인식되는 generic 명사구 ground-truth list. 본 정의가 single source — §2.2 / §6.1 unit test / canary detector 모두 본 list 를 reference. R1+R2 audit 에서 정확 N entry 확정 (초기 후보: `an Asian man` / `a young woman` / `a figure` / `a man` / `a woman` / `an elderly figure` / `a child`). |
| Furniture layout tokens **(R1+R2 ground-truth source)** | atmosphere_reference rule 위반 검출 대상 layout-specific token list. 본 정의가 single source. R1+R2 audit 에서 정확 N entry 확정 (초기 후보: `furniture` / `wall position` / `room layout` / `same chair` / `same table` / `same wall` / `same window`). ambient/lighting mood 표현 (`dim ambient pool` / `cool gray ambient`) 은 제외. |
| Framing scope options **(R1+R2 ground-truth source)** | view_consistency.framing_scope_options 의 2 entry. 본 정의가 single source. entries: `third_person` / `close_up`. |
| Ref usage values | previous_shot_refs[i].ref_usage 의 ground-truth 3 entry: `zoom_in_detail` / `exact_background` / `atmosphere_reference`. v18 prose section B 의 sub-section 3개와 1:1. |
| Cross-card reference | continuity_elements_used 의 sub-field 가 id_policy 의 sub-field 를 string 으로 참조. 예: `view_consistency.id_policy_cross_ref_for_body_part_focus = "id_policy.body_part_focus_rule applies — body-part Focus 시 C##O## 금지 (얼굴 ref 합성 차단)"`. 실제 룰 구현은 id_policy 측 (G4.3 lift) — continuity 측은 reminder string 만 carry. |
| canary | 광범위 적용 전 1 PID / 1 selected segment 로 baseline vs candidate 비교 검증 (G4.2 + G4.3 carry). 결과는 `docs/canary/g4_continuity_<timestamp>.json` 에 저장. |
| G4.3 baseline | G4.3 commit `51d6c5e` 기준의 ID-policy 4 metric (body-part focus / close-framing face / reproduction surface / demographic descriptor present ratio). G4.4 가 4 metric 모두 carry — degradation 차단. |
| 4-way sync | v19 push 시 lockstep 갱신 4 곳: prompts/_base/scene_detail/19.../system.md 디렉토리 + detail_steps.py:105 SCENE_DETAIL_PROMPT_VERSION + version_registry.py:34 MODULE_VERSIONS + version_registry.py:126 _MODULE_INFO. |
| view-mixing pattern | 같은 인물 ID 의 third-person 전신 묘사 (stands / seated / leaning 류) + 같은 인물의 body-part close-up trigger (focus on / close on / tight on / detail on + body-part) 가 동일 t2i_prompt 안 동시 등장. canary `g4_4_view_mixing.py` 가 strict 0 검출. |

> **Override O-19 — t2i_prompt 영어 가정 footnote**: `_CONTINUITY_GENERIC_PERSON_NOUNS`
> / `_CONTINUITY_FURNITURE_LAYOUT_TOKENS` ground-truth list 가 영어 substring
> 만 포함 (e.g. `"an Asian man"` / `"furniture"`) 은 t2i_prompt 영어 가정에
> 의존 — `feedback_t2i_storyboard.md` carry. Korean t2i prompt 도입 시 G4.5
> 에서 generic-noun 패턴 재검토 (한국어 `한 남자` / `한 여자` 등 추가).

---

## 10. Open Questions (Round 1 audit 결과 반영)

Round 1 (R1+R2) 에서 모든 spec-level Open Question 해소 — 결정은 §0 Round
Override 표 O-1 ~ O-19 참조. 본 §10 는 history.

| ID | 질문 | Round 1 결정 |
|---|---|---|
| Q1 | `cross_shot_id_substitution_rule` vs id_policy 의 cross-card reference 깊이 | **continuity 측 단독 유지** — fixed_elements 가 continuity input. id_policy 와 cross-ref 는 paired-string substring (Override O-7). |
| Q2 | `_CONTINUITY_GENERIC_PERSON_NOUNS` ground-truth 목록 정확 N entry | 초기 후보 7 entry 유지 — Wave 1-A 시 `_CONTINUITY_GENERIC_PERSON_NOUNS = ("an Asian man", "a young woman", "a figure", "a man", "a woman", "an elderly figure", "a child")`. 한국어 대응어 제외 (Override O-19). plan-R3+R4 audit 에서 v18 line 117-120 actual prose 검증 시 list 확장 가능. |
| Q3 | `_CONTINUITY_FURNITURE_LAYOUT_TOKENS` ground-truth 목록 정확 N entry | 초기 후보 7 entry 유지 — `("furniture", "wall position", "room layout", "same chair", "same table", "same wall", "same window")`. ambient/lighting tone 표현 (false-positive 가능) 제외. plan-R3+R4 audit 에서 검증. |
| Q4 | view_consistency 가 별도 sub-field 인 게 좋은지, ref_usage_constraints 안에 통합? | **별도 sub-field 유지** (Override O-6 명명 정정 시 확정) — view_consistency 는 모든 shot 적용. |
| Q5 | `view_consistency.mixing_forbidden` 가 redundant — `single_camera_rule` 만으로 충분? | **bool flag 명시 유지** — canary deterministic boolean check 가능. |
| Q6 | `compute_continuity_snapshot_hash(card)` helper 신설 위치 | `render_prompt_card.py` 안 G4.3 `compute_id_policy_snapshot_hash` 옆 (Override O-12). signature 명시 완료. |
| Q7 | unit test count — G4.3 36 vs G4.4 추정치 ~38? | ~32-38 (Override O-16 정합 — 본 §6.1 33 enumerate + 5 buffer). plan-R3+R4 audit 에서 fix. |
| Q8 | token gate threshold system-only `≤ -540` vs system+inject 합산 `≤ -150` 중 어느? | **system-only `≤ -540` binding** (Override O-1). system+inject 합산 = secondary report-only metric. |
| Q9 | `cross_shot_id_substitution_rule.applies_when` string 정확 wording | 초기 draft 유지: `"fixed_elements[i].character_name maps to a C## allowed in id_policy.allowed_base_entity_ids OR a C##O## in id_policy.allowed_outlook_pairs"`. plan-R3+R4 audit 에서 추가 검토. |
| Q10 | Section B introductory line (line 124) 도 삭제 대상? | **삭제** (Override O-10 footnote — section boundary 명시). Wave 1-B v19 drafting 시 line-by-line diff 결정. |

**Round 1 후 잔여 Open**: 0 (모든 spec-level question 해소). plan drafting R3+R4
에서 plan-level audit 진행 — R5+R6 fix loop 까지 추가 항목 발생 시 Round 2
Override row 로 등록.

---

## 11. Override 표 (Round audit 진행 시 채움)

본 섹션은 spec 상단 "## Round Override (audit-driven, applied before body)" 가
진실의 source. 각 round 의 BLOCKING / IMPORTANT / MINOR 결정은 상단 표에 기재되며
본문도 동기화 패치 적용. 본 §11 은 history 추적 용도.

### Round 1 — Codex (R1) + Claude (R2) dual audit 완료 (2026-05-05)

상단 §0 Round Override 표의 **O-1 ~ O-19** decision rows 가 binding source.
본 §11 는 history 추적용 — 결정 자체는 §0 참조.

R1 verdict: `NEEDS_REVISION` (3B + 3I + 2M + carry checks). R2 verdict:
`NEEDS_REVISION` (3B + 5I + 5M).

| Round 1 결정 ID | summary | 적용 위치 |
|---|---|---|
| O-1 | token gate `≤ -540` system-only binding | §3.4 / §5.1 / §5.3 |
| O-2 | compact section line count `≤ 32 lines` strict gate | §3.3 / §6.2 |
| O-3 | detail_steps.py:1555-1563 zoom_in_detail prose 9 줄 Wave 2 삭제 | §1.1 / §2.3 / §3.2 / §3.4 / §4 / §6.2 / §7.2 |
| O-4 | forward_zoom_targets G4.4 lift 대상 아님 | §1.2 / §2.2 |
| O-5 | strict required-keys subset (set superset 검증) | §2.2 / §6.1 |
| O-6 | sub-shape 명명 정정 ("3 new sibling policy dicts + extended constraints") | §1.1 / §2.1 / §2.2 / §3.4 / §4 |
| O-7 | cross-card reference paired-string substring | §2.2 / §3.3 / §6.1 / §6.2 / §8.1 |
| O-8 | silent-fallback 잔존 carry follow-up (G4.5 또는 별도 PR) | §8.2 |
| O-9 | lift_status keyset 정정 (G4.3 4 sub-fields + G4.4 4 신규 키) | §1.1 / §2.2 / §11 / §12 |
| O-10 | Lift mapping line range footnote (blank line 보존) | §4 |
| O-11 | canary multi-ref atmosphere filter | §5.1 / §5.3 / §6.1 / §7.2 step 17 / §8.1 |
| O-12 | compute_continuity_snapshot_hash 동작 명시 | §5.2 / §9 |
| O-13 | producer/consumer matrix 구체 wire 명시 | §2.3 |
| O-14 | §6.2 line-count test 임계 도출 | §6.2 |
| O-15 | §3.3 ✓/✗ 예시 prose 삭제 footnote | §2.4 |
| O-16 | §6.1 unit test count 정합 ("~32-38") | §6.1 |
| O-17 | regex view-mixing canary brittleness footnote | §5.1 / §7.2 step 17 / §8.1 |
| O-18 | Wave 5/6 numbering | §7.2 |
| O-19 | Glossary t2i_prompt 영어 가정 footnote | §9 |

### Round 2 — (예정, plan R3+R4 audit 시점 또는 R5+R6 fix loop 후 추가 row)

### 메타룰

- 본 spec 의 진실 source = 상단 "## Round Override" 표.
- Codex 또는 Claude 리뷰에서 BLOCKING 발견 시 즉시 상단 표에 row 추가 + 본문
  동기화.
- 절대 규칙 위반 (LLM 입력 truncation, 시나리오 의존 고유명사 등) 발견 시
  BLOCKING 최우선.
- silent fallback 패턴 발견 시 BLOCKING (`feedback_no_silent_fallback.md`).
- regex post-processing 의존 발견 시 IMPORTANT (`feedback_no_regex_postprocessing.md`).

---

## 12. Override Table (G4.2 + G4.3 carry — propagate into G4.4)

본 표는 G4.2 + G4.3 audit 결과 중 G4.4 에 동일 적용해야 하는 결정사항. 추가
G4.4-specific row 는 R1+R2 audit 에서 발견 시 추가.

| ID | Source (G4.2/G4.3 audit ref or new) | Override | Why |
|----|-------|----------|-----|
| O-1 | R2-B4 (G4.2) | builder 의 None vs [] fail-fast (`build_continuity_elements_used()` `fixed_elements=None` / `previous_shot_refs=None` / `forward_zoom_targets=None` → AppError) | silent fallback ban (`feedback_no_silent_fallback.md`) |
| O-2 | R1-I12 (G4.2) / G4.3 R3-B2 fixture trap | `_DEFAULT_SENTINEL = object()` fixture pattern (test 측) | fixture default vs None vs [] 구분 — production drift 차단 |
| O-3 | R2-I7 / iter2 B1 (G4.2) | canary pinned-tuple seen_in_cp post-loop 검증 | false STRICT pass 차단 — empty `data.scenes` / pinning 1:1 false-pass |
| O-4 | iter2 B2 (G4.2) | `t2i_variations` 누락 silent absorb 차단 — `t2i_variations=[]` / missing → measurement_failures | gate fail 누락 차단 |
| O-5 | iter3 B3 (G4.2) | variation 내부 `t2i_prompt` 누락 silent absorb 차단 — `var.get("t2i_prompt") or ""` 패턴 금지 | detail_schema.json variation.required = t2i_prompt — strict |
| O-6 | iter1 C3 (G4.2) / G4.3 R5-B1 carry | `or [] / or {}` 패턴 전반 차단 — `(d or {})` / `(card or {})` / `(pair or {}).get(...)` 모두 fail-fast (AppError) 강제 | shots / scenes 흡수 → `total_shots=0` false-pass |
| O-7 | iter1 C2 (G4.2) | gate fail 시 exit 1 강제 (return 0 금지) | CI 인지 가능 |
| O-8 | iter1 I1 (G4.2) | producer-side `_g41_bg_owned = ... or []` 패턴 금지 — None 흡수 차단, builder fail-fast 보장 | builder fail-fast 무력화 차단 |
| O-9 | iter1 I2 (G4.2) | hash 회귀 test gap 방지 — `_card_metadata` 변경 / `continuity_elements_used.constraints` 변경 drift test 의무 | hash drift undetected 차단 |
| O-10 | R1-B1 (G4.2) | `_card_metadata` envelope-sibling top-level only — nested 구조 도입 금지. `canonicalize_render_prompt_card()` top-level pop. | hash 격리 contract |
| O-11 | R2-I5 (G4.2) | `_card_metadata` free-form (extra-key 허용, schema 검증 없음). assert_card_shape envelope 7 field 만 strict. | 미래 G4.x 신규 sub-key 추가 시 validator 변경 0 |
| O-12 | R2-I4 (G4.2) | `detail_steps.py` inject 직전 `_card_metadata` strip 보존 | LLM 입력 token 격리 |
| O-13 | R1-I6 (G4.2) | `CARD_SCHEMA_VERSION` 유지 (= 1) — continuity_elements_used 신규 sub-field 는 envelope shape 변경 아님. `SCENE_DETAIL_SCHEMA_VERSION` 유지 (= 7) — cp shape 변경 아님. | backward compat |
| O-14 | R2-B6 (G4.2) | canary JSON `pid` 가 `pinning` block 안 (top-level redundant 제거) | pinning dict equality 한 번 |
| O-15 | R1-M1 (G4.2) | token measurement = `tiktoken cl100k_base` 실측 (line × 15 estimate 는 spec 참조용) | exit criteria 실측 기준 |
| O-16 | R1-I4 / R2-B5 (G4.2) | strict 0 metric 은 baseline 비0 이어도 candidate 0 강제 | baseline 비0 → P1 follow-up 별 추적 |
| O-17 | plan-R1-I4 (G4.2) | unit test fixture `_make_full_card()` 가 `_DEFAULT_SENTINEL = object()` 적용 — None / [] / default 구분 | G4.1 R3-B2 fixture trap 회피 |
| O-18 | plan-R2-I5 (G4.2) | canary owned/sentinel scripts 가 sentinel 합산 전 `assert_owned_sentinel_shape()` 호출 (G4.4 의 4 metric scripts 도 sentinel 검증 시 동일 pattern) | silent 0 false-pass 차단 |
| O-19 | plan-R2-I6 (G4.2) | commit target 에 `version_registry.py` (4-way sync 일부) 의무 포함 | alignment test green 유지 |
| O-20 | G4.3 R3-B1 ground-truth | ground-truth lists single source — Glossary 항목이 §2.2 / §3.3 / §6.1 unit test / canary detector 모두 reference 하는 single source | drift 차단 |
| O-21 | G4.3 R3-I2 4-way sync | v19 push lockstep 4 곳 (prompt 디렉토리 + detail_steps.py:105 + version_registry.py:34 + version_registry.py:126) — alignment test 가 강제 검증 | 4-way drift 차단 |
| O-22 | G4.3 R3-I3 entry gate split | spec drafting / plan / Wave 1-5 = canary 무관 진행 가능. **commit gate** = G4.3 production canary 6 gate AND G4.4 self-canary 6 gate 둘 다 필수. | 작업 차단 회피 + canary 결과의 commit-gate 역할 명확화 |
| O-23 | G4.3 R3R4-B1 | `_MODULE_INFO` dict access (NOT attribute) + `prompt_dependency` 값 = `"scene_detail/v19"` (major suffix only) | alignment test silent fallback 차단 |
| O-24 | G4.3 R3R4-B3 | compact section line count 정확 (multi-line wrap trap) — bullet 1줄 강제 | token estimate 보정 |
| O-25 | G4.3 R4-B4 | token gate signed convention `token_delta = candidate - baseline`, gate `<= -<value>` | 부호 혼동 차단 |
| O-26 | G4.3 R4-M6 / R5-I3 | Wave subagent hardcoded `[]` fixture trap — 신규 sub-field 는 builder-static, `if visible_entities:` 또는 `if fixed_elements:` truthy guard 안 배치 절대 금지 | production force-loop trap 차단 |
| O-27 | G4.3 R5-I1 | `_card_metadata.rule_source` 키 명세 — namespace 분리되어 top-level field 와 동명 OK (e.g. `cross_shot_id_substitution_rule` 가 continuity sub-field 명 + `_card_metadata.rule_source` 키 모두 OK) | drift 차단 |
| O-28 | G4.3 R5-I2 | alignment test silent fallback 제거 — `MODULE_VERSIONS.get(..., "")` / `_MODULE_INFO.get(..., {})` 금지 — 명시적 키 존재 + 타입 검증 | silent 0 false-pass 차단 |
| **G4.4-new** | (new) R3-B1 ground-truth | **`_CONTINUITY_GENERIC_PERSON_NOUNS`** ground-truth — §9 Glossary single source. §2.2 / §3.3 / §6.1 unit test / canary detector 모두 동일 list reference. R1+R2 audit 시 정확 N entry 확정. | drift 차단 |
| **G4.4-new** | (new) R3-B2 ground-truth | **`_CONTINUITY_FURNITURE_LAYOUT_TOKENS`** ground-truth — §9 Glossary single source. ambient/lighting mood 표현 (false-positive 가능) 제외. R1+R2 audit 에서 정확 N entry 확정. | drift + false-positive 차단 |
| **G4.4-new** | (new) R3-B3 ground-truth | **`_CONTINUITY_FRAMING_SCOPE_OPTIONS = ("third_person", "close_up")`** 2 entries — §9 Glossary single source. | drift 차단 |
| **G4.4-new** | (new) R3-I1 strict scope | 4 metric STRICT scope: double-description = id-mapping shots only (분모 ≥ 5), zoom_in_detail entity 추가 = ref_usage='zoom_in_detail' shots only, atmosphere layout = ref_usage='atmosphere_reference' shots only, view-mixing = 모든 shot | metric overlap / undefined denominator 차단 |
| **G4.4-new** | (new) R3-I2 cross-card reference | `view_consistency.id_policy_cross_ref_for_body_part_focus` + `ref_usage_constraints.zoom_in_detail.body_part_cross_ref` 가 동일 substring `"id_policy.body_part_focus_rule"` carry — unit test 2 곳에서 동시 검증 | G4.3 sub-field 와 sync drift 차단 |
| **G4.4-new** | (new) R3-I3 nested validator | `_assert_continuity_elements_used_shape()` 안에 `_assert_ref_usage_constraints_shape()` nested helper — 3 sub-key 각각 required key set 검증. nested dict 깊이 (3 sub-key × 4-5 sub-sub-key) 검증 누락 위험 차단 | shape contract violation 검출 |
| **G4.4-new** | (new) R3-I4 view_consistency bool literal | `view_consistency.mixing_forbidden` 가 `True` (bool, NOT bool int subclass — G3.2 / G4.3 trap carry: `assert mixing_forbidden is True and type(mixing_forbidden) is bool`) | bool int subclass trap (1 == True) 차단 |
| **G4.4-new** | (new) R3-I5 generic placeholder ban | §2.2 / §3.3 / §6.1 / §9 모든 예시 prose 가 generic Asian-default placeholder 사용 (`an Asian man` / `a young woman` / `a figure` / `a hand` 등) — 시나리오 고유명사 (인물 이름 / 지명 / 작품 명) 절대 금지 | CLAUDE.md 절대 규칙 + `feedback_no_scenario_names.md` |
| **G4.4-new** | (new) R3-I6 cross-card text idempotence | v19 compact "Continuity" section 안 `"id_policy.body_part_focus_rule"` substring 1 곳 보존 — G4.3 의 `## ID Policy` compact section 과 cross-ref 2-way 일관성 | reference 단방향 drift 차단 |
| **G4.4-new** | (new) R3-I7 prompt 의 카메라 angle 어휘 ban carry | v19 system.md 의 다른 섹션 (G4.2/G4.3 외) 는 G4.4 변경 0 — 특히 `## 카메라 앵글/샷 타입 지시 금지` (v18 line 507-513), `## visible_entities 엄격 규칙` (v18 line 496-505) 등 보존 | scope creep 차단 |

---
