# G4 RenderPromptCard + Scene Detail Prompt Slimming Strategy

본 문서는 `visual_pipeline_contracts_plan` 의 Group 4 후보인 #8
`RenderPromptCard` 도입과 #9 `scene_detail` rule lift 전략을 구체화한다.

- 작성일: 2026-05-04
- 상태: strategy draft
- 적용 대상: `scene_detail` v15 이후, 우선 v16 전략
- 선행 맥락: G1 `AssetReadiness`, G2 `scene_consistency` partial guard,
  G3.1 evidence/inference 4-field, G3.2 background owned object contract
- 기본 원칙: big-bang 금지, prompt section 삭제는 1-3개 단위, 기존 PNG 는 historical
  보존(재생성은 v16 적용 시), 기존 PID 는 schema bump 후 다음 resume 까지 v15 cp
  보존되지만 resume 시 schema mismatch 로 자동 v16 force escalate 가능 (operator
  opt-in 보장 X, R1-B1), 새 contract 는 deterministic builder + verify 로 먼저 고정

## Round 1 Override (2026-05-04)

본 spec 본문보다 우선 적용. 모순 시 Round 1 Override 따름. Codex 라운드 감사 +
lead/user 분석 결과를 본문에 반영하기 전 fast-path 으로 박는다. 본문은 추후
정식 패치에서 동기화하되, 그 전까지 본 표가 진실의 source 다.

### BLOCKING (1)

| ID | 결정 | spec 영향 |
|---|---|---|
| **R1-B1** | AD-5 의 "existing PID opt-in force" 표현 제거. 새 표현: "schema bump (6→7) 는 G4.1 에서 전역 적용. 기존 PID 는 다음 resume 까지 v15 cp 보존되지만, resume 시 schema/config mismatch 로 자동 v16 재실행될 수 있다 (operator opt-in 보장 X)." 근거: `step_runner.py:518, 546, 586` 가 schema/config mismatch 시 자동 `mode="force"` escalate. completed checkpoint 라도 `cp=None` 또는 `verify_completion` fail 시 force-like 격상. | §3 AD-5, §6 G4.1 task 9 line 475, §10 Q4 lines 645-646 |

### IMPORTANT (12)

| ID | 결정 | spec 영향 |
|---|---|---|
| **R1-I1** | `render_strategy.source="fallback"` 표현 전면 제거 (silent fallback 정책 위반). 두 가지 case 분리: (a) shot 자체에 staging 필수인데 누락 → fail-fast (`AppError(step.contract_violation)`). (b) staging 이 의도적 not applicable → `mode="not_applicable"` 명시. 근거: `feedback_no_silent_fallback.md` + `scene_generation_coordinator.py:70-80, 283-296` 명시 분기 패턴. | §4.1 lines 236-241, §2 Target line 78 |
| **R1-I2** | `continuity_elements_used.fixed_elements` 의 item shape 를 producer field 그대로 lossless 보존. card 안에서 derived hint 가 필요하면 별도 `_card_metadata` 키로만 추가. producer field 변형/drop 금지. 근거: `prompts/_base/scene_consistency/6.202605031033/schema.json:60-62` required 9 필드 + `scene_context_loader.py:171-173` raw pass. | §4.4 lines 333-341 |
| **R1-I3** | canonical card hash helper `canonicalize_render_prompt_card()` 신설 명시. semantically unordered list (`owned_objects`, `allowed_base_entity_ids`, `allowed_outlook_pairs`, `required_refs`, `forbidden_refs`, `fixed_elements`) 모두 정렬 (string list = sorted, dict list = key prefix sorted). list-order regression test 필수. 근거: `_owned_helpers.py:92-101 compute_owned_hash` defensive sort 패턴. `json.dumps(sort_keys=True)` 만으로는 dict key 만 정렬, list 는 caller order. | §6 task 2 lines 460-462, §8 Risk row 4 line 616 |
| **R1-I4** | §5.1 "Legacy compatibility blocks" 표현 강화 — "Card 가 primary contract. 충돌 시 card 가 항상 winner. legacy block 은 G4.2~G4.4 lift 가 완료되지 않은 prose 일 뿐, contract 충돌 시 무시." card-vs-legacy conflict 검증 test 추가. 근거: `step_runner.py:102-126 check_gate()` 가 dependency status 만 검증, semantic content 검증 X. | §5.1 lines 408-421, §2 Target line 78 |
| **R1-I5** | t2i_review post-edit 처리 정정. card hash 는 shot-level 이라 t2i_prompt 수정과 무관 — 재계산 대상 X. variation-level G3.2 sentinel 의 `t2i_prompt_hash` 가 stale → 다음 verify 에서 sentinel_drifted 로 잡힘. §7.2 test 변경: "card preserved after edit" 가 아니라 "post-edit 의 owned_validation 이 stale marker 로 transition + verify 가 partial 처리". 근거: `t2i_review.py:336-355` in-place 수정 + `step_runner.py:681-690 invalidate_downstream_on_edit: False` 기본 disabled. | §7.2 line 580, §8 Risk row 7 line 619 |
| **R1-I6** | §7.2 의 "scene_still_normalizer does not strip card fields" 항목 제거. card 는 G4.1 에서 CP-only debug 필드. DB persistence 가 필요하면 별도 migration scope (G4.6+ 또는 G5). 근거: `scene_still_normalizer.py:151-168` 가 DB row columns 만 만들고 top-level `render_prompt_card` 저장 대상 없음. | §7.2 line 581, §3 AD-1 line 96 |
| **R1-I7** | legacy forward_zoom prompt truncation 처리 (3 단계 분리). **G4.1 requirement**: `RenderPromptCard.continuity_elements_used.forward_zoom_targets` 와 각 `keep_elements` 는 full list 보존. card builder 가 절단 금지. **G4.1 non-goal**: 기존 legacy prompt block (`detail_steps.py:950, 965`) 의 cap 제거는 G4.4 continuity lift 에서 처리. **G4.4 entry criterion**: legacy forward zoom prompt block 을 제거 또는 full-list 방식으로 고치기 전에는 "continuity lift complete" 선언 금지. regression test 추가: forward_zoom_targets count > 6 + keep_elements count > 5 → card payload + card hash 가 모든 entry 보존 검증. 근거: `detail_steps.py:950, 965` 가 LLM-bound continuity context 를 `[:6]` + `[:5]` 로 절단. CLAUDE.md 절대 규칙 위반. line 951 주석은 "truncation 금지" 박혀 모순. | §4.4 lines 352-358, §6 G4.1 (Non-goals 추가), §6 G4.4 (entry criterion 추가), §8 신규 Risk row |
| **R1-I8** | §7 test scope 보강. **unit**: previous_shot_refs 다양 case (zoom_in_detail / continuation), forward_zoom_targets full-list (R1-I7 연결), asset_requirements required/forbidden 분기, _user_edited card hash drift (G3.x 패턴 carry). **integration**: v15→v16 migration smoke (기존 PID resume 시 schema mismatch → force escalate 동작 검증), t2i_review stale-sentinel transition (R1-I5 연결). | §7.1, §7.2 |
| **R1-I9** | §8 risk register 8 row 별 measurable threshold 추가: "Card+legacy prompt token bloat" → G4.1 prompt token count delta < +30%; "LLM ignores card" → card-wins conflict test pass rate 100%; "Hash too sensitive" → permutation test 100% identical; 등 row 별 quantitative gate. | §8 lines 613-620 |
| **R1-I10** | 각 G4.x phase 끝에 "Exit criteria — 다음 phase 진입 전 필수 충족" 섹션 추가. **G4.1**: card payload + hash CP 저장 확인 + drift detection green + canary 1 PID prompt token < +30%. **G4.2**: Rule A/C/E 삭제 후 owned violation rate ≤ G4.1 baseline. **G4.3**: ID violation rate ≤ G4.1 baseline. **G4.4**: legacy forward_zoom truncation cleanup (R1-I7 연결) + continuity violation rate ≤ G4.1 baseline. **G4.5**: heading count ≤ 10 ## sections (pytest enforcement). | §6 G4.2-G4.5 lines 512-552 |
| **R1-I11** | (R1-I1 + R1-M2 합산 정의) card envelope hash payload 정의 sharpening. **Hash payload = entire envelope EXCEPT explicitly excluded keys**: `_card_metadata` (debug-only — schema_version/build_timestamp/builder_version 등) + `render_prompt_card_hash` (the hash itself) 두 개만 제외. **Hash payload INCLUDES**: `schema_version`, `shot_key`, `render_strategy`, `id_policy`, `background_binding`, `continuity_elements_used`, `asset_requirements`. 근거: shot_key 변경 시 hash drift 필수 (다른 shot 은 5 field 동일하더라도 다른 hash); schema_version 변경 시 hash drift 필수 (v1 → v2 transition 감지). `canonicalize_render_prompt_card()` 가 두 제외 키만 strip 후 hash. `source="fallback"` 표현 전체 삭제 (R1-I1 결정 따름). | §4.1 lines 239-241, §4 envelope spec, §6 G4.1 Task 2 hash spec |
| **R1-I12** | **card 입력 경계에서 None vs `[]` 명시 구분 (silent absorb 차단)**. 근거: `feedback_no_silent_fallback.md` + Codex round 1 audit (plan Task 6 `fixed_elements or []` silent absorb 적발). 현 `scene_context_loader.py:180-182` 가 scene_consistency cp 부재 시 warning + empty 처리. 결정: card builder 의 producer field 입력 boundary 에서 — (a) producer missing (`None` from upstream/loader) → `AppError(step.contract_violation, message="...")`. silent default 금지. (b) producer 가 명시적 `[]` (빈 배열 emit) → valid. 빈 card field 로 처리. 영향 범위: §4.4 continuity_elements_used (fixed_elements/previous_shot_refs/forward_zoom_targets) + §4.2 id_policy (allowed_base_entity_ids/allowed_outlook_pairs) + §4.5 asset_requirements (required_refs/forbidden_refs). §6 G4.1 plan Task 6 + Task 4 + Task 7 builder 모두 None reject. loader 수정은 G4.1 OOS — known issue 로 §8 risk register 추가. 근거 코드: `backend/app/core/steps/scene_context_loader.py:180-182` (scene_consistency cp 부재 warning+empty). | §4.2 / §4.4 / §4.5 본문 inline marker + §8 Risk register row 추가 |

### MINOR (1)

| ID | 결정 | spec 영향 |
|---|---|---|
| **R1-M1** | §10 Q1 → §9 Decision Summary 표 row 추가 (top-level shot storage), §10 에서 제거. 미해결 Q2/Q3/Q4 만 §10 유지. 근거: §4 envelope 본문 lines 194-199 에서 이미 결정. Open Questions 잔존 inconsistent. (본문 정정 적용함.) | §10 lines 634-637, §9 |
| **R1-M2** | (R1-I11 로 통합) `_card_metadata` envelope + hash exclusion 정의. | §4.1 lines 239-241 |

---

## 1. Current State

### 1.1 scene_detail prompt 현황

현재 `prompts/_base/scene_detail/15.202605032354/system.md` 는 716줄,
top-level `##` 섹션 31개, `##`/`###` heading 총 91개다. 주요 rule 이 한
파일에 직접 들어 있다.

대표 섹션:

- 절대 규칙: 시각 스타일 복사 금지, 단일 순간
- ID 정책: `C##O##`, body-part close-up 에서 보통명사 사용, 사진/화면/거울 속
  인물 ID 금지
- background binding: Rule A camera consistency, Rule C owned object no-redraw,
  Rule E close framing background reference skip
- physical/layout guard: Rule F/G/J
- demographic fallback: Rule H
- continuity: fixed_elements, previous shot refs, forward zoom
- prompt syntax: T2I format, prop framing, visible_entities, camera angle text 금지
- G3.1 evidence/inference disclosure

즉 `scene_detail` system prompt 는 최종 prompt compiler 여야 하는데, 현재는
upstream contract reminder, semantic validator instruction, T2I syntax instruction 을
동시에 담당한다.

### 1.2 current code shape

현재 코드상 `scene_detail` 은 이미 card builder 로 옮길 재료를 대부분 갖고 있다.

| source | current location | RenderPromptCard field 후보 |
|---|---|---|
| shot text / selected shot | `SceneDetailStep._analyze_one()` | `render_strategy` |
| visible entities | `ctx.scene_visible`, `ctx.shot_director_ve` | `id_policy`, `asset_requirements` |
| outlook candidates / assignments | `outlook_phase3` lookup in `_analyze_one()` | `id_policy`, `asset_requirements` |
| fixed elements | `SceneContextLoader._load_fixed_elements()` | `continuity_elements_used` |
| previous / forward refs | `ctx.dependencies`, `_compute_forward_zoom_targets()` | `continuity_elements_used`, `render_strategy` |
| staging / camera direction | `ctx.staging_map` | `render_strategy`, `background_binding` |
| chain_bg guide/camera/owned | `ctx.chain_bg_*_by_shot` | `background_binding` |
| owned sentinel/hash | `_owned_helpers.py` | `background_binding` verification |
| asset preflight | scene image service / G1 path | `asset_requirements` |

Key constraints from current implementation:

- `scene_detail` order is `21.70` and depends on `background_prompt`, so it can consume
  G3.2 owned objects.
- `background_prompt` order is `21.60`; `background_render` remains later at `24.72`.
  `RenderPromptCard` must treat `background_prompt` checkpoint as text truth and
  rendered background asset as readiness truth.
- `SceneContextLoader` is intentionally a checkpoint loader DTO boundary. It should not
  become a semantic compiler.
- `StepRunner.check_gate()` only checks dependency status and partial policy. Contract
  shape/content drift must still be verified inside the consumer.
- `t2i_review` is editorial and modifies `scene_detail` checkpoint. Any card/hash stored
  in `scene_detail` must either be preserved or drift-detected after edit.

## 2. Target

Group 4 target is not "rewrite scene_detail in one patch". The target is:

1. Build a deterministic `RenderPromptCard` per selected shot.
2. Inject that card as the primary `scene_detail` input.
3. Reduce `scene_detail` system prompt from 31 top-level sections to at most 10 by
   moving rule-specific data and constraints into the card or upstream steps.
4. Keep LLM output schema stable until the card contract proves useful.
5. Preserve G3.1/G3.2 fail-fast behavior. No silent fallback.

<!-- R1-I1, R1-I4: see Round 1 Override — silent fallback 금지 + card-wins primary contract -->


## 3. Architectural Decisions

### AD-1. RenderPromptCard form

Recommendation: **C first, A later**.

Initial implementation should be a deterministic helper called from `scene_detail`, not a
new pipeline step.

Recommended write scope:

- Create `backend/app/core/steps/render_prompt_card.py`
- Keep `SceneContextLoader` as raw checkpoint loader.
- Call `build_render_prompt_card(ctx, seg, shot_info, prepared_inputs)` inside
  `SceneDetailStep._analyze_one()`.
- Inject one `[RenderPromptCard]` JSON block into `user_prompt`.
- Store the card and its hash in `scene_detail` checkpoint as CP-only fields.

<!-- R1-I6: see Round 1 Override — card 는 G4.1 CP-only debug 필드. DB persistence scope 외 -->


Why not a new step first:

- A new `render_prompt_card_step.py` would need order around `21.69`, new manifest
  dependencies, checkpoint lifecycle, force cascade, sync/readiness behavior, and UI
  status semantics before the card shape is proven.
- The current immediate problem is `scene_detail` prompt complexity, not cross-step card
  reuse yet.
- A helper gives the same LLM input contract with much smaller DAG risk.

Exit criterion for promoting to a new step:

- Card schema is stable across at least one production canary.
- `scene_image_pipeline` or UI needs to consume the card independently of `scene_detail`.
- Card verify/hash drift becomes useful outside `scene_detail`.

### AD-2. Rule lift destination

Recommendation: **field constraints inside RenderPromptCard first**.

`docs/contracts/render.md` style documentation is useful for humans, but a link in
`system.md` is not a runtime contract for the LLM. For Group 4, each lifted rule must
land in one of the five card fields as concrete data plus `constraints`.

Longer-term ownership:

| rule group | Group 4 runtime destination | later upstream home |
|---|---|---|
| ID / C##O## / common noun exceptions | `id_policy.constraints` | entity/outlook contract plus deterministic post-check |
| chain_bg camera / owned / close skip | `background_binding.constraints` | G3.2 helpers plus background contract |
| fixed_elements / previous refs / forward zoom | `continuity_elements_used.constraints` | scene_consistency + shot_dependency_t2i |
| strategy direct/simplify/partial/reframe | `render_strategy` | shot_selection / shot_validator |
| required refs / missing refs / close skip refs | `asset_requirements` | G1 AssetReadiness / Shot Card compiler |

### AD-3. Scope split

Recommendation: **#8 first, #9 staged afterward**.

Group 4 should be split into:

- G4.1: RenderPromptCard bootstrap. Add builder, inject card, store/hash/verify, no major
  prompt deletion.
- G4.2: background-binding lift. Move Rule A/C/E reminders into
  `background_binding.constraints`, then reduce those sections in system prompt.
- G4.3: ID-policy lift. Move C##O##, body-part close, photo/screen/reflection rules into
  `id_policy.constraints`, then reduce duplicate prose.
- G4.4: continuity/render-strategy lift. Move fixed elements, ref_usage, forward zoom,
  one-shot-one-scale logic into `continuity_elements_used` and `render_strategy`.
- G4.5: prompt slimming pass. Only after G4.2-G4.4 tests pass, collapse system.md to
  10 or fewer top-level `##` sections.

Do not create one spec per individual Rule A/B/C unless a rule needs a separate code
producer. For most rules, one staged Group 4 spec plus task-level subsections is enough.

### AD-4. PNG regeneration

Recommendation: **existing PNG files stay as historical assets; any v16 scene_detail
result used for current output requires scene PNG regeneration**.

Cases:

| case | action |
|---|---|
| Document/spec only | no regeneration |
| New `scene_detail` v16 prompt/card but no force run | no regeneration |
| Existing PID force-runs `scene_detail` to v16 | regenerate downstream scene images for those shots before treating output as current |
| G4.1 only, no background prompt/render change | background PNG can be reused |
| Later rule lift changes `background_prompt` or background contract | background PNG cascade may be required |

Rationale: RenderPromptCard changes the LLM input distribution and likely changes
`t2i_prompt`. Old scene PNGs can remain in DB/filesystem as legacy assets, but they should
not be considered validated output for the new v16 prompt.

Cascade policy: G4.1 does not add custom cascade rules. It relies on the existing
StepRunner/manifest force invalidation path when `scene_detail` schema/prompt version
changes. Operators may still force downstream image generation explicitly when they want
new PNG output from the v16 prompt.

### AD-5. Migration policy

<!-- R1-B1: see Round 1 Override — opt-in 표현은 사실과 다름. step_runner 가 schema/config mismatch 시 자동 force escalate -->

Recommendation: **new PID default, existing PID opt-in force**.

- New PIDs use v16 once merged.
- Existing PIDs keep v15 checkpoint/output until operator force.
- Canary existing PID path:
  `scene_detail` force -> `shot_dependency_t2i` -> `t2i_review` -> `scene_image_pipeline`.
- Do not auto-delete old scene images.
- Add a manifest/config hash bump so stale v15 is detected when force/re-run occurs.

## 4. RenderPromptCard Contract

The runtime card has a small envelope plus five semantic fields. The LLM-facing contract is
the five fields; envelope metadata is for deterministic verify/debug. The checkpoint stores
`render_prompt_card_hash` beside the card, not inside the hash payload.

Scope split:

- `RenderPromptCard` scope = selected shot contract. It is stored top-level on the
  scene/shot result.
- Variation scope = each `t2i_variations[]` item. G3.2 `owned_validation` sentinel
  continues to detect variation-level `t2i_prompt` drift after `t2i_review`.
- Therefore card hash verifies upstream contract drift; G3.2 sentinel verifies per
  variation prompt drift. Do not move card hash into each variation in G4.1.

```json
{
  "schema_version": 1,
  "shot_key": {"scene_index": 12, "shot_index": 4},
  "render_strategy": {},
  "id_policy": {},
  "background_binding": {},
  "continuity_elements_used": {},
  "asset_requirements": {}
}
```

<!-- R1-I11: see Round 1 Override — Hash payload = entire envelope EXCEPT `_card_metadata` (debug-only) + `render_prompt_card_hash` (hash 자신) 두 개만 제외. INCLUDES: schema_version, shot_key, render_strategy, id_policy, background_binding, continuity_elements_used, asset_requirements. shot_key/schema_version 변경 시 hash drift 필수 -->

### 4.1 render_strategy

Purpose: what kind of render the shot should become. This field owns strategy and
composition intent, not identity or asset existence.

```json
{
  "mode": "direct | simplify | partial_focus | reframe | anchored_reference | close_insert",
  "primary_subject": "the observer at the doorway",
  "framing_scale": "wide | medium | close | insert",
  "moment_lock": "single still moment from the selected shot only",
  "camera_direction": "raw shot_staging camera_direction",
  "lighting_mood": "raw shot_staging lighting_mood",
  "perception_mode": "direct | through_device | hallucination | dream | memory | reflection | projection",
  "constraints": [
    "one prompt captures one still moment",
    "do not combine full-body framing and body-part close-up in one frame"
  ]
}
```

Empty policy:

<!-- R1-I1, R1-I11, R1-M2: see Round 1 Override — `source="fallback"` 표현 전체 삭제. staging 누락 = fail-fast 또는 mode="not_applicable" 명시 분기. card envelope 의 `_card_metadata` 는 hash 입력에서 명시 제외 -->

- `mode` always exists. Default `direct`.
- `framing_scale` is derived from `camera_direction` using the same close-framing detector
  used by generation and verify.
- Staging-missing handling (silent fallback 금지, R1-I1):
  - (a) shot 자체 staging 필수인데 누락 → fail-fast: card builder raises
    `AppError(step.contract_violation, message="shot_staging required for shot_key=…")`.
    `source="fallback"` 표현은 사용 금지.
  - (b) staging 이 의도적 not applicable (예: analysis-only path) → `mode="not_applicable"`
    명시 + 그 이유를 `_card_metadata` 에 기록. silent default 금지.
- Future priority: when `shot_selection.recommended_strategy` exists, it wins. Builder
  derivation is interim only, and any mismatch must be logged in `_card_metadata`
  (which is excluded from hash payload).

### 4.2 id_policy

Purpose: exactly which entity/outlook identifiers the LLM may use, and when it must use
common nouns instead.

<!-- R1-I12: see Round 1 Override — allowed_base_entity_ids / allowed_outlook_pairs producer 가 None 이면 card builder 가 fail-fast (AppError step.contract_violation). 명시 `[]` 만 valid empty -->

```json
{
  "allowed_base_entity_ids": ["C01", "C02", "L01", "P03"],
  "allowed_outlook_pairs": [
    {"character_id": "C01", "outlook_id": "O02"}
  ],
  "must_use_composite_character_ids": true,
  "common_noun_required_when": [
    "body-part close-up",
    "photo/poster/screen/mirror/reflection/projection depiction",
    "unregistered extra"
  ],
  "demographic_fallback_required": true,
  "constraints": [
    "visible real people with outfit binding use C##O##",
    "do not use C##O## for a reproduced face inside a screen, photo, poster, mirror, or reflection",
    "do not use C##O## for isolated body-part focus"
  ]
}
```

Builder sources:

- `visible` from `scene_director` or `shot_director`
- outlook assignments from `outlook_phase3`
- current `_check_prompts` allowed-set logic

### 4.3 background_binding

Purpose: describe whether a background reference is expected, what it owns, and what the
scene prompt must not redraw.

```json
{
  "mode": "background_ref_attached | skipped_close_framing | background_mode_off | not_applicable",
  "bg_id": "cb_main_room_night_normal",
  "reference_usage": "exact_background | atmosphere_reference | skipped_close_framing | none",
  "owned_objects": ["door", "window", "TV"],
  "camera_reference": {
    "camera_position": "same southeast doorway angle",
    "camera_height": "eye-level standing",
    "lens_hint": "35mm wide",
    "framing_notes": "doorway and TV visible at frame edge"
  },
  "close_framing_skips_background_ref": false,
  "constraints": [
    "do not create new owned_objects",
    "if focusing on an owned object, anchor it as from the reference",
    "if mode is skipped_close_framing, do not mention reference image or existing room"
  ]
}
```

Builder sources:

- `ctx.chain_bg_owned_by_shot`
- `ctx.chain_bg_camera_meta_by_shot`
- `ctx.chain_bg_guide_by_shot`
- close framing detector
- background mode from settings

Important G3.2 carryover:

- Owned list remains English canonical.
- Close framing still skips owned judge and reference assumption.
- Do not add a separate `background_binding_hash` in G4.1. The canonical card hash already
  covers the full `background_binding` field. G3.2 sentinel hashes remain separate because
  they are variation-level drift checks.

Hash relationship:

| hash | scope | catches | owner |
|---|---|---|---|
| `render_prompt_card_hash` | shot-level card | card field drift: id policy, background binding, continuity, asset requirements | G4.1 |
| `owned_validation.t2i_prompt_hash` | variation | `t2i_prompt` edited without rejudge | G3.2 |
| `owned_validation.owned_hash` | variation | owned list changed relative to the judged prompt | G3.2 |
| `owned_validation.camera_direction_hash` | variation | close/non-close or camera context changed | G3.2 |

### 4.4 continuity_elements_used

Purpose: put continuity inputs in a single structured packet so the LLM does not need
multiple prose reminders.

<!-- R1-I2: see Round 1 Override — fixed_elements item shape 는 producer field lossless. 9 required field 보존. derived hint 는 `_card_metadata` 로 분리 -->
<!-- R1-I7: see Round 1 Override — forward_zoom_targets + 각 keep_elements 는 full list 보존. card builder 가 절단 금지. 기존 legacy prompt block (detail_steps.py:950, 965) 의 [:6]/[:5] cap 은 G4.4 lift 시 제거 -->
<!-- R1-I12: see Round 1 Override — fixed_elements / previous_shot_refs / forward_zoom_targets producer 가 None 이면 card builder 가 fail-fast (AppError step.contract_violation). 명시 `[]` 만 valid empty. silent absorb 금지 -->

```json
{
  "fixed_elements": [
    {
      "element_id": "body_full_pose",
      "element_type": "character_state",
      "bound_entity_hint": "C02",
      "description": "an East Asian woman lying motionless on her left side",
      "source_facts": [],
      "visual_inferences": [],
      "confidence": "high"
    }
  ],
  "previous_shot_refs": [
    {
      "scene_index": 12,
      "shot_index": 3,
      "ref_usage": "zoom_in_detail",
      "keep_elements": []
    }
  ],
  "forward_zoom_targets": [
    {
      "scene_index": 12,
      "shot_index": 5,
      "description": "follow-up close-up target must be visible here",
      "keep_elements": []
    }
  ],
  "constraints": [
    "fixed elements are continuity inputs, not source facts",
    "zoom_in_detail must preserve same moment and space"
  ]
}
```

Empty policy:

- Empty `fixed_elements`, `previous_shot_refs`, or `forward_zoom_targets` are valid.
- Empty does not mean fallback; it means the upstream producer had no applicable item.
- Unsafe `scene_consistency` partial/violation results must still be blocked or skipped by
  the existing consumer-safe guard.

### 4.5 asset_requirements

Purpose: tell `scene_detail` what image references are expected or forbidden. This field
does not replace deterministic AssetReadiness; it gives the LLM a concise picture of the
same policy.

<!-- R1-I12: see Round 1 Override — required_refs / forbidden_refs producer 가 None 이면 card builder 가 fail-fast (AppError step.contract_violation). 명시 `[]` 만 valid empty -->

```json
{
  "required_refs": [
    {"kind": "character_outlook", "id": "C01O02", "policy": "required"},
    {"kind": "background", "id": "cb_main_room_night_normal", "policy": "required"}
  ],
  "forbidden_refs": [
    {"kind": "background", "reason": "close framing skips chain_bg reference"}
  ],
  "readiness_policy": "block_if_missing | skipped_by_policy | not_applicable",
  "constraints": [
    "do not imply a reference image that will not be attached",
    "if a required character ref is missing, generation must block before image stage"
  ]
}
```

Builder sources:

- visible/outlook pairs
- background binding
- G1 AssetReadiness resolver where available
- scene image reference policy

## 5. Prompt Shape

### 5.1 G4.1 user_prompt shape

<!-- R1-I4: see Round 1 Override — Card 가 primary contract. 충돌 시 card 항상 winner. legacy block 은 lift 미완 prose 일 뿐 contract 충돌 시 무시. card-vs-legacy conflict 검증 test 추가 -->

Initial G4.1 should inject the card before existing narrative. This makes the card the
primary source while keeping old instructions as fallback until tests prove behavior.

```text
[RenderPromptCard v1]
{...canonical JSON...}

[Minimal narrative input]
scene summary: ...
selected shot: ...
scene text: ...

[Legacy compatibility blocks]
... existing blocks that have not yet been lifted ...
```

### 5.2 v16 system.md target sections

Initial v16 target: 14 or fewer top-level `##` sections, not final 10 yet.

1. Role: render prompt compiler
2. Read `RenderPromptCard` first
3. Output shape and variation count
4. Single still moment
5. ID policy summary
6. Background binding summary
7. Continuity summary
8. Asset/reference assumption summary
9. Physical/framing consistency
10. T2I prompt style
11. Evidence/inference disclosure
12. Failure handling

Final G4.5 target: 10 or fewer top-level `##` sections after Rule A/C/E, ID exceptions,
and continuity prose are proven redundant.

## 6. Implementation Phases

### G4.1 RenderPromptCard bootstrap

Files:

- Create `backend/app/core/steps/render_prompt_card.py`
- Modify `backend/app/core/steps/detail_steps.py`
- Modify `backend/app/core/dto/scene_analysis.py` only if a reusable field is needed
- Create `prompts/_base/scene_detail/16.<timestamp>/system.md`
- Copy `detail_schema.json` from v15 unless LLM output schema changes
- Add tests under `backend/tests/unit/` and `backend/tests/integration/`

Tasks:

1. Add card dataclass or plain dict builder.
2. Add canonical hash helper: JSON dumps with `sort_keys=True`, `ensure_ascii=False`,
   compact separators, sha256 prefix 16. Hash the card payload only; do not include
   `render_prompt_card_hash` in the hashed object.
   <!-- R1-I3, R1-I11: see Round 1 Override — `canonicalize_render_prompt_card()` 신설.
   semantically unordered list (owned_objects, allowed_base_entity_ids, allowed_outlook_pairs,
   required_refs, forbidden_refs, fixed_elements) 모두 정렬 (string list = sorted, dict list
   = key prefix sorted). list-order regression test 필수. **Hash payload = entire envelope
   EXCEPT `_card_metadata` (debug-only) + `render_prompt_card_hash` (the hash itself)**.
   INCLUDES: `schema_version`, `shot_key`, `render_strategy`, `id_policy`, `background_binding`,
   `continuity_elements_used`, `asset_requirements`. shot_key/schema_version 변경 시 hash drift 필수. -->
3. Build card inside `_analyze_one()` after visible/outlook/staging/fixed inputs are
   known and before `user_prompt` is finalized.
4. Inject `[RenderPromptCard v1]` block.
5. Add CP-only fields to each result:
   - `render_prompt_card`
   - `render_prompt_card_hash`
6. Add `verify_completion()` card shape/hash check.
7. Add `_user_edited` reuse path card drift check: recompute current card from current
   ctx + shot, compare to stored `render_prompt_card_hash`, and reject reuse on mismatch
   (same pattern as G3.1 evidence + G3.2 owned drift).
8. Preserve these fields through `t2i_review`, `scene_still_normalizer`, and checkpoint
   loaders.
9. Bump `SCENE_DETAIL_SCHEMA_VERSION` and `SCENE_DETAIL_PROMPT_VERSION`.
   <!-- R1-B1: see Round 1 Override — schema bump (6→7) 는 G4.1 에서 전역 적용. 기존 PID 는 다음 resume 까지 v15 cp 보존되지만, resume 시 schema/config mismatch 로 자동 v16 재실행될 수 있다 (operator opt-in 보장 X) -->

Modify scope guard:

- `_analyze_one()` changes in G4.1 are limited to input collection for the card, one
  builder call, one user_prompt injection point, and result dict CP-only field assignment.
- Do not rewrite existing visible/outlook/fixed/background branches in G4.1.
- Do not move existing retry, `_check_prompts`, G3.1 evidence, or G3.2 owned validation
  loops in G4.1.

Non-goals:

- No new StepRunner step.
- No large section deletion from `system.md`.
- No scene image generation code change.
- No cascade policy code change. v16 output regeneration uses existing force/cascade
  mechanics and operator-triggered downstream force when needed.
- <!-- R1-I7: see Round 1 Override --> 기존 legacy forward_zoom prompt block (`detail_steps.py:950, 965`) 의 `[:6]`/`[:5]` cap 제거는 G4.1 스코프 외. card 내 `forward_zoom_targets` + `keep_elements` 는 full list 보존하되, legacy prompt loop 는 G4.4 continuity lift 에서 제거.

<!-- R1-I10: see Round 1 Override — G4.1 Exit criteria — 다음 phase 진입 전 필수 충족: card payload + hash CP 저장 확인 + drift detection green + canary 1 PID prompt token < +30% -->

### G4.2 background-binding lift

Move these old prompt responsibilities into `background_binding.constraints`:

- Rule A camera consistency
- Rule C owned object no-redraw
- Rule E close framing reference skip

Expected prompt deletion:

- Replace the long Rule A/C/E sections with one concise "Background Binding" section
  that says the specific data and constraints are in the card.

Additional tests:

- non-close owned list appears only in card and prompt obeys reference anchor
- close framing card mode forbids reference wording
- card hash drift when owned/camera changes

Mini-canary:

- Before deleting Rule A/C/E prose broadly, run one PID / one selected segment sample with
  only background-binding lift enabled.
- Capture baseline and candidate prompt packets to `docs/canary/g4_bg_binding_<timestamp>.json`.
- Compare prompt length, owned judge violations, close-framing reference wording, and
  camera consistency wording. Do not mix this with ID-policy or continuity lift changes.

<!-- R1-I10: see Round 1 Override — G4.2 Exit criteria: Rule A/C/E 삭제 후 owned violation rate ≤ G4.1 baseline -->

### G4.3 ID-policy lift

Move these responsibilities into `id_policy.constraints`:

- C##O## composite ID default
- invalid outlook pair rejection
- body-part focus common noun
- screen/photo/poster/mirror/reflection common noun
- demographic fallback for unregistered extras

Keep deterministic `_check_prompts()` or equivalent post-check. The card helps the LLM,
but deterministic ID correctness remains mandatory.

<!-- R1-I10: see Round 1 Override — G4.3 Exit criteria: ID violation rate ≤ G4.1 baseline -->

### G4.4 continuity/render-strategy lift

<!-- R1-I7: see Round 1 Override — G4.4 entry criterion: legacy forward_zoom prompt block (`detail_steps.py:950, 965`) 의 `[:6]`/`[:5]` truncation 제거 또는 full-list 방식 적용. 이게 안 되어 있으면 "continuity lift complete" 선언 금지 -->

Move these responsibilities into `continuity_elements_used` and `render_strategy`:

- fixed_elements injection
- previous shot refs and `zoom_in_detail`
- forward zoom target visibility
- one framing scale per shot
- simplify/partial/reframe strategy hints

This is the highest behavior-risk phase. Run canary before deleting old prose.

<!-- R1-I10: see Round 1 Override — G4.4 Exit criteria: legacy forward_zoom truncation cleanup (R1-I7) + continuity violation rate ≤ G4.1 baseline -->

### G4.5 prompt slimming and documentation sync

After G4.2-G4.4:

- Collapse `system.md` to 10 or fewer top-level `##` sections.
- Add a concise `docs/contracts/render_prompt_card.md` for human reference.
- Add prompt snapshot tests for heading count and forbidden duplicate prose.
- Update version registry / prompt dependency notes.

<!-- R1-I10: see Round 1 Override — G4.5 Exit criteria: heading count ≤ 10 ## sections (pytest enforcement) -->

## 7. Verification Strategy

### 7.1 Unit tests

<!-- R1-I3, R1-I7, R1-I8: see Round 1 Override — 추가 unit test 필수: list-order regression (canonicalize_render_prompt_card permutation), forward_zoom_targets full-list (count > 6 + keep_elements > 5), previous_shot_refs zoom_in_detail/continuation, asset_requirements required/forbidden 분기, _user_edited card hash drift -->

Card builder:

- background mode off -> `background_binding.mode="background_mode_off"`
- non-close bg on -> owned objects and camera meta included
- close framing -> `mode="skipped_close_framing"` and background refs forbidden
- no fixed elements -> empty `fixed_elements` but valid card
- multiple outlooks -> all allowed pairs present
- body-part close -> common noun policy present
- screen/reflection perception -> common noun policy present

Hash:

- stable hash for same canonical card
- hash changes when owned list changes
- hash changes when camera direction changes
- hash changes when allowed outlook pair changes

### 7.2 Integration tests

<!-- R1-I5: see Round 1 Override — t2i_review test 변경: card hash 는 shot-level, t2i_prompt 수정과 무관. variation-level G3.2 sentinel `t2i_prompt_hash` 가 stale 로 transition + verify partial 처리. "preserved" 표현 정정 필요 -->
<!-- R1-I6: see Round 1 Override — scene_still_normalizer 항목 제거 (card 는 G4.1 CP-only debug, DB persistence scope 외) -->
<!-- R1-I8: see Round 1 Override — integration 추가: v15→v16 migration smoke (기존 PID resume schema mismatch → force escalate 동작), t2i_review stale-sentinel transition -->

- `scene_detail` user_prompt includes exactly one RenderPromptCard block.
- `scene_detail` result stores card + hash CP-only.
- `verify_completion()` catches card hash drift.
- `t2i_review` post-edit: card hash 는 shot-level 이라 `t2i_prompt` 수정과 무관하므로
  재계산하지 않는다. 대신 variation-level G3.2 sentinel 의 `t2i_prompt_hash` 가 stale
  로 transition 되어 `verify_completion()` 이 partial 로 마킹하는 것을 검증한다 (R1-I5).
- analysis-only dispatcher path remains valid with `background_prompt` dependency.

### 7.3 Prompt tests

- v16 system has the RenderPromptCard priority section.
- v16 schema remains compatible with v15 output if no schema change is intended.
- v16 prompt heading count is lower than v15, but G4.1 should not enforce final 10.
- later G4.5 enforces target heading count with pytest: read `system.md`, count lines
  matching `^## `, assert count <= 10, and snapshot forbidden duplicate prose markers.

### 7.4 Canary

Use one existing PID only by explicit force:

1. Run current v15 baseline prompt/card-free sample capture to
   `docs/canary/g4_baseline_<timestamp>.json`.
2. Force `scene_detail` v16 for a small selected subset.
3. Compare:
   - prompt length
   - ID policy violations
   - owned object judge violations
   - close framing reference wording
   - scene PNG visual pass rate after regeneration

Do not judge G4.1 only by image quality. G4.1 is a contract-shape change. Quality
evaluation starts after G4.2/G4.3 reduce duplicate prose.

## 8. Risk Register

<!-- R1-I3, R1-I5, R1-I7, R1-I9: see Round 1 Override — row 별 measurable threshold 추가 + forward_zoom truncation 잔존 risk 신규 row + Hash row 는 list ordering 명시 + t2i_review row 는 sentinel transition 으로 정정 -->

| risk | mitigation |
|---|---|
| Card plus old narrative increases prompt size in G4.1 | G4.1 is temporary; measure token length and remove background/ID sections in G4.2/G4.3 |
| LLM ignores card and follows old prose | Put card first, system says card wins, remove duplicate prose after tests |
| Card builder duplicates `_analyze_one()` complexity | Extract pure helper inputs gradually; do not move all `_analyze_one()` logic at once |
| Hash too sensitive to ordering | Canonical JSON and sorted lists for deterministic fields |
| Empty continuity treated as missing | Encode empty as valid with explicit empty arrays |
| New step too early creates DAG churn | Helper-first approach; promote to StepRunner only after stable |
| t2i_review edits create stale card assumptions | Store card hash and verify drift; preserve card fields |
| Existing PID output churn (v15 cp 가 다음 resume 시 `step_runner.py:518-586` schema mismatch detect → 자동 `mode='force'` escalate, R1-B1) | canary 1 PID 로 force escalate 동작 검증 + downstream (verify_completion / t2i_review / scene_still_normalizer) 영향 측정 후 광범위 적용. operator opt-in 표현은 사실과 달라 사용 금지 |
| Legacy forward_zoom truncation 잔존 (`detail_steps.py:950, 965` `[:6]`/`[:5]`) | G4.1 card 는 full-list 보존 (R1-I7). G4.4 entry criterion 으로 legacy block 제거. 그 전까지 legacy prompt 와 card 의 list length 불일치 known issue |
| loader (`scene_context_loader.py:180-182`) 가 scene_consistency cp 부재 시 silent warning+empty — card builder None reject 와 충돌 가능 (R1-I12) | G4.1 card builder 가 first line of defense (None reject, AppError step.contract_violation). loader 정정은 G4.1 OOS, 별도 follow-up plan 또는 G4.4 continuity lift 시 처리. 그 사이 caller 가 명시 `[]` 보장 |

## 9. Decision Summary

<!-- R1-M1: see Round 1 Override — Q1 (top-level shot storage) 을 §10 Open Questions 에서 §9 Decision Summary 로 이동. 본문 정정 적용함 -->

| question | recommended decision |
|---|---|
| RenderPromptCard form | scene_detail helper first, new step later only if independent consumers need it |
| Rule lift destination | card field constraints first; docs for humans only; upstream ownership later |
| Scope split | #8 bootstrap first, #9 staged lift by rule group |
| PNG regeneration | existing PNG historical; v16 prompt adoption requires scene PNG regeneration |
| Migration | new PID default, existing PID opt-in force/canary |
| Card storage scope (ex-Q1) | top-level result field per shot — card describes the shot, not each variation. Variation-level drift remains owned by G3.2 sentinel fields on `t2i_variations[]` |

## 10. Open Questions

<!-- R1-M1: see Round 1 Override — ex-Q1 (top-level shot storage) 은 §9 Decision Summary 로 이동. Q2~Q4 → Q1~Q3 으로 renumber 적용 -->
<!-- R1-B1: see Round 1 Override — ex-Q4 (current Q3) 의 "operator opt-in" 표현은 사실과 다름. step_runner 가 schema/config mismatch 시 자동 force escalate -->

1. Should `render_strategy.mode` come from `shot_selection.recommended_strategy` now, or be
   derived inside builder until shot_selection schema grows? Recommendation: derive inside
   builder in G4.1, add upstream field later. Once the upstream field exists, it wins and
   builder derivation becomes fallback only.
2. Should `asset_requirements` call the full AssetReadiness resolver during
   `scene_detail`? Recommendation: not in G4.1. Use expected refs only; the image stage
   remains the hard readiness gate.
3. Should `schema_version` bump even if LLM output schema is unchanged? Recommendation:
   yes, because CP shape gains `render_prompt_card` and hash verify semantics.

## 11. Minimal First Patch Shape

If this strategy is accepted, the first implementation spec should be narrow:

- Add `render_prompt_card.py` helper and tests.
- Add v16 `scene_detail` prompt with card priority section.
- Inject card block, store card/hash, verify hash.
- Preserve all existing G3.1/G3.2 validators.
- No rule deletion except obviously duplicate one-line reminders.

That gives a measurable contract before we spend risk budget on prompt slimming.

Next process step: run a Codex round audit on this strategy document before writing the
implementation plan, mirroring the G3.1/G3.2 audit/fix loop.
