{
  "type": "object",
  "properties": {
    "shots": {
      "type": "array",
      "items": {
        "type": "object",
          "properties": {
            "scene_index": {"type": "integer"},
            "shot_index": {"type": "integer"},
            "framing_scale": {
              "type": "string",
              "enum": ["close", "medium", "wide", "insert"],
              "description": "Primary framing scale of this shot. close = primary subject fills most of the frame. medium = upper body or mid-distance subject with some surrounding context. wide = full body or environment/layout visible. insert = isolated detail without surrounding context. Judge by camera distance and primary subject visibility, not vocabulary."
            },
            "perspective": {"type": "string", "description": "Camera POV: subjective_pov, over_shoulder, observer, omniscient, object_pov, voyeur"},
          "pov_character": {"type": "string", "description": "For subjective POV: the character whose eyes we see through (must NOT appear in image). Empty string if not subjective."},
          "perception_mode": {"type": "string", "enum": ["direct", "hallucination", "dream", "memory", "reflection", "mirror", "through_device", "projection"], "description": "How the scene is perceived. 8-token enum (canonical literal order = listed). direct = normal vision. hallucination = distorted/altered sight. dream = dreamlike state. memory = recollection. reflection = general reflective surface (water/glass/metal). mirror = a mirror surface specifically (left-right flip + reflective-surface quality + mirror frame). through_device = seen via a device screen/lens (CCTV/phone/binoculars). projection = projected onto a projector/screen. Affects visual treatment."},
          "camera_direction": {"type": "string", "description": "Creative camera placement, framing, physical layout, and composition including POV (2-3 sentences, English)"},
          "lighting_mood": {"type": "string", "description": "Lighting technique and color mood (1 sentence, English)"},
          "character_angles": {
            "type": "array",
            "description": "Camera-relative angle, body pose, gaze direction, target, and state for each visible character (exclude POV character)",
            "items": {
              "type": "object",
              "properties": {
                "character": {"type": "string", "description": "Character name (as registered)"},
                "angle": {"type": "string", "description": "Camera-relative direction: facing_camera, back_to_camera, profile_left, profile_right, three_quarter_left, three_quarter_right, over_shoulder, looking_away"},
                "body_pose": {"type": "string", "description": "Specific body posture in English (2-5 words). Must NOT use 'standing' alone — use action-grounded or state-grounded poses such as 'one foot on pedal', 'leaning against doorframe', 'crouching behind desk', 'slumping into chair', 'caught mid-stride'. See system prompt 'body_pose 다양화' for full guidance."},
                "gaze_direction_kind": {
                  "type": "string",
                  "enum": ["camera", "down", "up", "distant", "closed_eyes", "off_screen", "looks_at_character", "looks_at_object"],
                  "description": "Eye direction kind. Closed-world enum — LLM judges intent class only. closed_eyes = eye-closed only (not state). off_screen = eyes outside camera frame, no registered target resolved (NOT visibility decision)."
                },
                "gaze_target_id": {
                  "type": ["string", "null"],
                  "description": "Always emit. Registered short_id when gaze_direction_kind ∈ {looks_at_character, looks_at_object}; null otherwise. Shape (Q7 closed): registered short_id only — ^C\\d{2,3}$ for looks_at_character; ^P\\d{2,3}$ or ^B\\d{2,3}$ for looks_at_object. outlook O## / canonical name / Korean noun / ASCII noun fallback 모두 금지. Non-registered target case = looks_at_* 사용 금지, off_screen/distant 등 사용. kind↔target pairing은 shot_staging 코드측 validate_pairing이 강제(OpenAI strict 호환을 위해 schema combinator 미사용)."
                },
                "subject_state": {
                  "type": "string",
                  "enum": ["alive", "unconscious", "dead", "severely_injured"],
                  "description": "Character state — separate axis from gaze direction. 'alive' explicit; missing = schema violation. immobility (의식 있지만 못 움직임) = 별도 차원 (character_state element)."
                }
              },
              "required": ["character", "angle", "body_pose", "gaze_direction_kind", "gaze_target_id", "subject_state"],
              "additionalProperties": false
            }
          },
          "key_bg_elements": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "element": {"type": "string", "description": "Background element name"},
                "state": {"type": "string", "description": "State in this shot (open/closed, clean/dirty, on/off, etc.)"},
                "orientation": {"type": "string", "description": "Natural-language description tied to directionality_class. For content_surface: which face/side is visible to camera and what is on it. For reflective_surface: what is being reflected and how. For transparent_surface: surface state and what is seen through it. For directional_3d: which side faces camera. For non_directional: empty allowed. content_surface and reflective_surface MUST have a non-empty orientation."},
                "camera_use": {"type": "string", "description": "How this element is used in the shot (foreground frame, focal point, light source, etc.)"},
                "directionality_class": {
                  "type": "string",
                  "enum": ["content_surface", "reflective_surface", "transparent_surface", "directional_3d", "non_directional"],
                  "description": "Semantic classification of this element. content_surface = thin object that carries content on one face; which face is visible changes the meaning. reflective_surface = object whose surface reflects content; what is reflected determines meaning. transparent_surface = object the camera sees through; surface state and what lies beyond determine meaning. directional_3d = 3D object with multiple faces where the camera-facing side matters. non_directional = texture/surface/ambient element with no directional meaning. Judge by meaning, not by surface vocabulary or specific examples (non-exhaustive examples, do not classify by this list)."
                }
              },
              "required": ["element", "state", "orientation", "camera_use", "directionality_class"],
              "additionalProperties": false
            }
          },
          "frame_spatial_contract": {
            "type": ["object", "null"],
            "properties": {
              "reason": {
                "type": "string",
                "enum": ["movement_direction", "points_to_anchor", "looks_to_anchor",
                         "shared_space_relation", "required_background_position",
                         "primary_subject_isolation"],
                "description": "Single primary trigger — see system prompt 'Frame Spatial Contract' section."
              },
              "constraints": {
                "type": "array",
                "minItems": 1,
                "maxItems": 3,
                "items": {
                  "type": "object",
                  "properties": {
                    "target_kind": {"type": "string", "enum": ["character", "prop", "background"]},
                    "target_id": {"type": "string", "description": "character → C##, prop → P##, background → empty string"},
                    "label": {"type": "string", "minLength": 1, "description": "Human-readable label"},
                    "screen_zone": {"type": "string", "enum": [
                      "upper_left", "upper_center", "upper_right",
                      "middle_left", "middle_center", "middle_right",
                      "lower_left", "lower_center", "lower_right"]},
                    "depth_plane": {"type": "string", "enum": ["foreground", "midground", "background"]},
                    "gesture_action": {"type": "string", "enum": [
                      "none", "points_to", "reaches_for", "looks_toward", "moves_toward"]},
                    "gesture_target_label": {"type": "string",
                      "description": "non-empty when gesture_action != 'none', empty string when gesture_action == 'none'"}
                  },
                  "required": ["target_kind", "target_id", "label", "screen_zone",
                               "depth_plane", "gesture_action", "gesture_target_label"],
                  "additionalProperties": false
                }
              }
            },
            "required": ["reason", "constraints"],
            "additionalProperties": false,
            "description": "Opt-in frame-space contract. null when not needed; object form is type-array nullable (OpenAI strict 호환 — combinator 미사용). See system prompt 'Frame Spatial Contract' section for emit gating."
          },
          "subject_reference_policy": {
            "type": "array",
            "description": "Per-subject identity reference policy (exceptions-first). Default 'id_and_outlook_required' if subject omitted. Empty array allowed.",
            "items": {
              "type": "object",
              "properties": {
                "subject_id": {
                  "type": "string",
                  "pattern": "^C\\d{2,3}(?:O\\d{2,3})?$",
                  "description": "Base C## recommended. C##O## form is accepted and normalized to base (outlook context not available in shot_staging input)."
                },
                "policy_type": {
                  "type": "string",
                  "enum": ["identity_reference"],
                  "description": "v1 single value. Other values forbidden (envelope future hook only)."
                },
                "policy": {
                  "type": "string",
                  "enum": [
                    "id_and_outlook_required",
                    "base_id_required",
                    "generic_descriptor_allowed"
                  ]
                },
                "reason": {
                  "type": "string",
                  "minLength": 1
                }
              },
              "required": ["subject_id", "policy_type", "policy", "reason"],
              "additionalProperties": false
            }
          }
        },
        "required": ["scene_index", "shot_index", "framing_scale", "perspective", "pov_character", "perception_mode", "camera_direction", "lighting_mood", "character_angles", "key_bg_elements", "frame_spatial_contract", "subject_reference_policy"],
        "additionalProperties": false
      }
    }
  },
  "required": ["shots"],
  "additionalProperties": false
}
