{
  "_doc": "W21B-wave-4 shot_projection_card v0 — DRAFT schema (docs only, NOT a prompt pack yet). text-only dry + strict-schema 검증 통과 후 prompts/_base/shot_projection_card/1.<ts>/ 로 승격. 양측(Claude+Codex) 합의 2026-05-30. 사용자 제약: VLM은 좌표 생성 안 함(서술/분류만) · 작품 고유명사 0 · leakage check는 self-defined token exact-membership(자연어 의미 substring 판정 금지).",

  "_layers": {
    "vlm_output_schema": "VLM 이 emit 하는 structured output. OpenAI strict json_schema 호환(additionalProperties:false, 전 property required, nullable 은 [\"type\",\"null\"], anyOf/minItems/pattern/format 등 strict 미지원 키 금지). semantic_readback 패턴 미러.",
    "card_envelope_shape": "코드(step/helper)가 조립하는 card. provenance + source_hashes + validator_state + card_state + embedded vlm_output. VLM raw self-report(vlm_reported_state)와 validator 산정(validator_state/card_state)을 분리 — Codex 권고."
  },

  "vlm_output_schema": {
    "type": "object",
    "additionalProperties": false,
    "required": [
      "status",
      "fp_id",
      "bg_id",
      "shot_id",
      "camera_pose_source",
      "visible_items",
      "scene_visible_description",
      "bg_plate_visible_description",
      "plate_description_excludes_transient",
      "not_visible_or_occluded_summary",
      "vlm_reported_state",
      "self_consistency",
      "confidence",
      "missing_inputs",
      "diagnostics"
    ],
    "properties": {
      "status": { "type": "string", "enum": ["ok"] },
      "fp_id": { "type": "string" },
      "bg_id": { "type": "string" },
      "shot_id": { "type": "string" },

      "camera_pose_source": {
        "_note": "기존 camera_recommendations / geometry candidate 중 VLM 이 선택·서술한 것의 식별자/라벨. 좌표 생성 X — 후보 선택만.",
        "type": "string"
      },

      "visible_items": {
        "type": "array",
        "items": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "marker_number",
            "marker_layer",
            "expected_label",
            "visibility",
            "horizontal_band",
            "depth_band",
            "occlusion_note",
            "evidence",
            "source_ref",
            "confidence"
          ],
          "properties": {
            "marker_number": { "_note": "internal only — evidence join. prompt 비노출.", "type": "integer" },
            "marker_layer": { "_note": "internal only — overlay union 분류. base=base_marker_inventory(spatial anchor), transient=transient_markers_to_describe(임시 객체), ignored_state_overlay=ignored. prompt/t2i 비노출. validator 가 union 분류와 교차확인.", "type": "string", "enum": ["base", "transient", "ignored_state_overlay"] },
            "expected_label": { "_note": "internal only — dossier verbatim. prompt 비노출.", "type": "string" },
            "visibility": { "type": "string", "enum": ["visible", "partial", "occluded", "out_of_frame"] },
            "horizontal_band": { "type": "string", "enum": ["left", "center", "right"] },
            "depth_band": { "type": "string", "enum": ["foreground", "midground", "background"] },
            "occlusion_note": { "type": "string" },
            "evidence": { "type": "string" },
            "source_ref": { "type": "string" },
            "confidence": { "type": ["number", "null"] }
          }
        }
      },

      "scene_visible_description": {
        "_note": "★ FULL prose (base+transient 혼합) — scene_detail/I2I 레이어가 소비. (구 t2i_visible_description rename, Codex 2026-05-30). 좌표/grid 없는 한국어 자연어. marker 번호·영어 enum literal·card_id 등 internal token 비노출. BG plate 에는 주입 X(transient 포함 때문).",
        "type": "string"
      },
      "bg_plate_visible_description": {
        "_note": "★ BG plate 전용 base-only prose (Codex 2026-05-30). background_prompt vNext 가 이 field 만 plate surface 에 주입. 허용=base markers + 영구 fixtures/furniture/materials/lighting/camera framing. 금지=transient markers, subject actions, event residue, marker 번호, enum/layer/card metadata. 한국어 자연어. 코드가 full prose 를 substring 으로 자르는 게 아니라 VLM 이 marker_layer 를 알고 생성 시 분리 emit.",
        "type": "string"
      },
      "plate_description_excludes_transient": {
        "_note": "★ VLM self-report (Codex 2026-05-30). bg_plate_visible_description 이 transient marker 를 배제했는지 VLM 자기보고. deterministic 한국어 substring 탐색 대신 self-report 로 gate. validator: no→block / uncertain→needs_review / yes→pass.",
        "type": "string",
        "enum": ["yes", "uncertain", "no"]
      },
      "not_visible_or_occluded_summary": {
        "_note": "optional 성격이나 strict 라 빈 문자열 sentinel 허용. 가려지거나 프레임 밖인 것 요약.",
        "type": "string"
      },

      "vlm_reported_state": {
        "_note": "VLM 자기보고. validator_state 와 분리(Codex). 코드가 이걸 그대로 신뢰하지 않고 validator 가 재판정.",
        "type": "string",
        "enum": ["ok", "low_confidence", "insufficient_evidence"]
      },
      "self_consistency": {
        "type": "object",
        "additionalProperties": false,
        "required": ["prose_matches_structured", "notes"],
        "properties": {
          "prose_matches_structured": { "type": "string", "enum": ["consistent", "uncertain", "contradictory"] },
          "notes": { "type": "string" }
        }
      },
      "confidence": { "type": ["number", "null"] },
      "missing_inputs": { "type": "array", "items": { "type": "string" } },
      "diagnostics": { "type": "array", "items": { "type": "string" } }
    }
  },

  "card_envelope_shape": {
    "_note": "코드 조립 — strict json_schema 아님(문서용 shape). VLM output 을 embed + provenance/validator 추가.",
    "card_id": "string (uuid)",
    "schema_version": "int",
    "prompt_version": "string e.g. '1'",
    "model": "string",
    "provider": "string",
    "bg_id": "string",
    "shot_id": "string",
    "fp_id": "string",

    "anchor_role": "enum anchor | sibling  (multi-shot 정책 v0. background_prompt 는 anchor card 의 bg_plate_visible_description 만 사용. sibling card 는 prompt surface 미주입, compatibility diagnostic 만)",
    "anchor_shot_id": "string  (plan vNext 가 선택한 anchor shot. dry 단계 임시 = first_applies_to_shots, production 승격 전 plan vNext anchor field 로 교체)",
    "sibling_shot_ids": "string[]  (같은 bg 의 나머지 applies_to_shots; prompt 비주입)",
    "sibling_compatibility": "object[]  (diagnostic only: {shot_id, plate_consistent: yes|uncertain|no, note} — sibling shot 의 framing 이 anchor plate 와 호환되는지. v0 hard gate 없음, 기록만)",

    "semantic_gate_state": "enum pass | needs_review | needs_fix | not_available  (semantic_readback default OFF → enabled 면 gate, disabled 면 not_available)",
    "substrate_kind": "enum light_fp | detailed_fp | both",
    "substrate_status": "enum ok | fallback_detailed | missing",

    "marker_registry": {
      "_note": "★ input-assembly 가 overlay payload union 으로 deterministic 하게 계산(Codex 권고 2026-05-30). base_marker_inventory 단독이 아니라 overlay_payload 의 base_markers_to_reference ∪ transient_markers_to_describe ∪ ignored_state_overlay_markers. validator 는 자연어 해석 없이 out_of_union_referenced 만 hard block — transient marker 를 base 부재만으로 missing 오판하던 패턴 제거.",
      "base_numbers": "int[] (base_markers_to_reference)",
      "transient_numbers": "int[] (transient_markers_to_describe)",
      "ignored_numbers": "int[] (ignored_state_overlay_markers)",
      "union_numbers": "int[] (base ∪ transient ∪ ignored)",
      "camera_referenced_numbers": "int[] (camera_recommendation 이 참조한 numbered_elements)",
      "out_of_union_referenced": "int[] (camera_referenced − union; 비어있어야 정상. non-empty → marker_contract_gap hard block)"
    },

    "source_hashes": {
      "fp_render_hash": "string",
      "substrate_hash": "string (light_fp 또는 detailed)",
      "overlay_hash": "string",
      "geometry_hash": "string",
      "semantic_hash": "string | 'not_available'",
      "camera_rec_hash": "string",
      "shot_context_hash": "string (normalized — raw shot text 아님)",
      "prompt_version": "string",
      "schema_version": "int",
      "model_provider": "string"
    },

    "vlm_output": "(위 vlm_output_schema 결과 그대로 embed)",

    "validator_state": "enum ok | leak_detected | contradiction | marker_contract_gap | missing | malformed  (validator/helper 산정 — VLM self-report 아님. marker_contract_gap = camera_rec 이 union 밖 marker 참조)",
    "card_state": "enum pass | needs_review | blocked  (validator_state + semantic_gate_state + confidence/missing 조합으로 helper 가 최종 산정)"
  },

  "_gate_v0": {
    "deterministic_hard": [
      "card present",
      "card_hash(source_hashes) match",
      "no internal-token leakage in scene_visible_description / bg_plate_visible_description / not_visible_or_occluded_summary (self-defined token exact-membership: marker 정수값, 영어 enum/layer literal, card_id/hash)",
      "plate self-report: plate_description_excludes_transient == 'no' → hard block / 'uncertain' → needs_review (transient label substring 탐색 안 함)",
      "visible_items 와 prose 가 명백히 모순 아님 (v0 최소: self_consistency != 'contradictory')"
    ],
    "diagnostic_only_v0": [
      "이미지/문장 품질",
      "prose 의 시나리오 충실도"
    ],
    "marker_contract_v0": [
      "marker registry = overlay payload union (base ∪ transient ∪ ignored), base_marker_inventory 단독 아님",
      "camera_rec referenced − union = out_of_union_referenced. non-empty 면 marker_contract_gap → hard block",
      "transient marker 가 base inventory 에 없다는 이유만으로는 block 금지 (union 에 있으면 valid)",
      "transient marker 분리 (Codex 2026-05-30): scene_visible_description=full(base+transient, scene_detail용) / bg_plate_visible_description=base-only(BG plate용). BG plate 에는 후자만 주입. plate_description_excludes_transient self-report 로 gate.",
      "camera_rec plate-safe sanitization (Codex 2026-05-30): background_prompt vNext 는 camera_recommendation 원문 JSON 을 그대로 dump 금지. framing_notes(transient cue '...number 18...')·use_numbered_elements·marker번호 든 camera_position 은 plate surface 에서 숨김/diagnostic-only. lens/height/sub_location 등 plate-safe subset 만 노출(또는 bg_plate prose 가 카메라뷰를 이미 encode 하므로 omit)."
    ],
    "fail_closed_inputs_to_bg": [
      "hard block (validator_state in {leak_detected, contradiction, marker_contract_gap}, stale, semantic needs_fix, substrate missing) → card_state=blocked",
      "soft hold (missing_inputs 비어있지 않음 / confidence 낮음 / semantic needs_review) → card_state=needs_review",
      "둘 다 BG prompt 에 card 주입 안 함 → 기존 v10 prompt path"
    ],
    "fallback": "blocked/needs_review 면 BG prompt 에 card 주입 안 함 → 기존 v10 prompt path"
  },

  "_multishot_anchor_policy_v0": {
    "_doc": "★ multi-shot anchor/sibling 정책 v0 (Claude+Codex 합의 2026-05-30). 한 bg 가 여러 shot 을 가질 때(예: L05B09 같은 same-space, 또는 applies_to_shots 다수) bg_plate_visible_description 을 어느 shot card 에서 가져올지 lock. pack 승격/E2E 전에 doc/validator level 에서 먼저 잠가 edge 재작업 방지.",
    "rule_1_anchor_only": "background_prompt / background_render 는 plan vNext 가 선택한 anchor_shot_id 의 card 의 bg_plate_visible_description 만 plate surface 에 주입한다. 한 bg = 한 plate = 한 anchor card.",
    "rule_2_sibling_diagnostic_only": "같은 bg 의 나머지 sibling shot card 는 prompt surface 에 절대 넣지 않는다. sibling 의 framing 이 anchor plate 와 호환되는지는 sibling_compatibility(diagnostic only)에만 기록. v0 hard gate 없음.",
    "rule_3_dry_temp_anchor": "anchor 선택 권한은 plan vNext(shot_aware_plan)에 있다. 그 전 dry 단계에서는 임시 정책 = first_applies_to_shots(각 bg 의 applies_to_shots[0])를 anchor 로 쓰되 명시한다. production 승격 전 반드시 plan vNext 의 anchor field 로 교체(임시 정책 잔존 금지).",
    "rule_4_scene_detail_per_shot": "scene_detail/I2I 는 plate 와 달리 per-shot 이므로 각 shot 의 scene_visible_description(full)을 그 shot 에서 소비. anchor 제약은 plate(BG) 에만 적용.",
    "open_for_plan_vNext": "anchor 선택 기준(예: dominant_target_unit / camera_unit 대표성 / shot importance)은 plan vNext 설계에서 확정. v0 doc lock 은 '단일 anchor card 만 plate 주입 + sibling diagnostic only' 계약만 고정."
  }
}
