
    _jJ                      U d Z ddlmZ ddlZddlZddlmZmZmZm	Z	m
Z
mZ ddlmZmZ  ej                  e      ZdZded<   d	Zded
<   dZdZdZdZded<   dZded<   dZdZeeef   ZdqdZdrdZ	 	 	 	 	 	 	 	 dsdZ 	 	 	 	 	 	 	 	 dtdZ!dd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dudZ"dvdZ#dwdZ$ddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dxd Z%dyd!Z&dzd"Z'	 	 	 	 	 	 d{d#Z(d|d$Z)d}d%Z*d~d&Z+dd'Z,dd(Z-d)Z.d*Z/d+Z0d,Z1dd-Z2dd.Z3dd/Z4dd0Z5dd1Z6dd2Z7dd3Z8	 	 	 	 	 	 dd4Z9	 	 	 	 	 	 	 	 dd5Z:	 	 	 	 dd6Z;dd7Z<	 	 	 	 	 	 	 	 dd8Z=d9d:	 	 	 	 	 	 	 dd;Z>g d<Z?d=ed><   d?Z@d@edA<   dBZAd@edC<   dDZBd@edE<   ddF	 	 	 	 	 ddGZCdHZDdedI<   dJZEdedK<   dLZFdedM<   dNZGdedO<   dPZHdedQ<   ddRZIddSZJddTZKddUZL	 	 	 	 	 	 ddVZMddWZNdXZOdedY<   ddZZP	 	 	 	 	 	 	 	 dd[ZQd\ZRded]<   d^ZSded_<   	 d	 	 	 	 	 dd`ZTddaZU	 	 	 	 	 	 ddbZVddcZW	 	 	 	 	 	 dddZX	 	 	 	 	 	 	 	 ddeZY	 	 d	 	 	 	 	 	 	 	 	 	 	 ddfZZ	 d	 	 	 	 	 ddgZ[dhdhdhdidididjZ\ddkZ]	 	 	 	 	 	 	 	 ddlZ^ddmZ_	 	 	 	 	 	 ddnZ`	 	 	 	 	 	 	 	 ddoZa	 	 	 	 	 	 ddpZby)uM  W21B-W8 (2026-06-12) — outdoor_site_layout deterministic core.

B-run 피드백 "가는 방향/멀어짐 구분 못함" 의 production fix. ablation 실측
(spike_gallery 21~24절): 텍스트 위치 구절이 지배 변수, top-down 이미지는 ref
채널로 무력 → 좌표는 **텍스트 생성의 입력** 으로만 쓴다 (FP→VLM→T2I 패턴).

이 모듈은 순수 deterministic 영역만 담는다:

  - seed 탐지 (LLM 0): selected shot × outdoor(background_classify
    ``is_indoor == false`` structured) × staging ``character_angles`` 2인+.
    제외는 전부 structured 조인 — zoom continuity 멤버
    (shot_dependency_t2i ``ref_usage == "zoom_in_detail"`` pair),
    1인 이동(frame_spatial_contract ``gesture_action == "moves_toward"`` enum).
    이동 동사 regex / 단어 의미 판별 절대 금지.
  - layout 좌표 validator: **shape/범위만** (의미 게이트 없음 — 의미는 visual
    canary. Codex W21B_W8 ⓔ).
  - 카메라-인물 거리/방향 deterministic 공간 요약 (LLM2 입력).
  - revised prompt 토큰 audit (W-C1 ``revised_prompt_new_tokens`` 재사용 +
    기존 토큰 누락 검사 — entity ID 불변 계약).
  - prompt override 명시적 priority merge: custom > zoom > site > original
    (custom 은 별도 분기 선행 승리, original 은 override 부재).

LLM IO 는 ``outdoor_site_layout_provider`` 가 담당. 작문 품질은 deterministic
테스트 비대상 — canary + 육안 gate.

설계: docs/w21b-w8-outdoor-site-layout-production-brief-20260612/ (Codex
W21B_W8_SITE_LAYOUT_DESIGN_REVIEW=APPROVED_WITH_NARROW_SCOPE).
    )annotationsN)AnyDictListOptionalSetTuple)extract_entity_tokensrevised_prompt_new_tokens   intSCHEMA_VERSION   DEFAULT_GROUP_CAPzoom_in_detailmoves_toward	character        float	COORD_MINg      Y@	COORD_MAXzoom_continuity_anchoroutdoor_site_layoutstrc                    |  d| S )N: )scene_index
shot_indexs     c/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/outdoor_site_layout_plan.pyshot_hint_keyr!   :   s    ]!J<((    c                    t               }| xs g D ]f  }|j                  d      xs g D ]L  }|j                  d      }t        |t              s%|s(|j                  d      du s<|j	                  |       N h |S )u   background_classify building_groups → ``is_indoor == false`` loc_id 집합.

    structured field 조인만 — label/summary 텍스트는 보지 않는다.membersloc_id	is_indoorF)setget
isinstancer   add)background_classify_groupsoutgmr%   s        r    outdoor_loc_idsr/   A   ss     EC'-2-y!'R'AUU8_F&#&6aeeK6HE6Q ( .
 Jr"   c                z   i }|xs i j                         D ]I  \  }}t        |t              r|j                  d      dk7  r+|j                  d      }|s?||v rdn|||<   K i }| xs i j                         D ]?  \  }}	t        |t              r|	|xs
 t               vr'|j                  |	      }|s;|||<   A |S )u  B (2026-07-02) — scene_index → outdoor primary location 의 entity UUID.

    scene_director.primary_location(L## short_id)이 outdoor 집합(background_
    classify is_indoor==false)에 속하고 entity_lookup 역매핑(entity_type==
    'location' AND short_id 일치)이 존재할 때만 매핑. structured field 조인만 —
    이름/라벨 텍스트는 보지 않는다. 역매핑 충돌(같은 short_id 2+)은 제외(결정론).entity_typelocationshort_idN)itemsr)   dictr(   r   r'   )
primary_location_by_sceneoutdoor_loc_sidsentity_lookupsid_to_uuiduidentsidr,   sipls
             r    primary_location_uuid_by_scener?   N   s     -/K"(b//1S#t$(>*(Lggj!#&+#543C 2 C,299;B"c"b1A1JSU&Koob!CG < Jr"   c               h    t        | t              sy|xs i j                  |       }|r
||xs i v r|S y)u   B — VE 에 location 이 없어 history lookup 을 놓친 outdoor 샷의 primary
    location UUID fallback. history 에 실재하는 entry 가 있을 때만 반환.N)r)   r   r(   )r   primary_loc_uuid_by_scenelocation_scene_historyr:   s       r    %resolve_outdoor_history_fallback_uuidrC   l   s>     k3'$*
/
/
<C
s-34
r"   F)close_framingc           
     L   t        | t              rt        |t              syt        |      xr3 |xs i j                  |       |v xs t        |xs
 t	               |z        }|sy|ry|dk(  ry|ry|s|syddt        |	      |	r|
nd||	rdndt        |      t        |      ddS )u  B — outdoor prev-frame 의무첨부 진단 (pure). 대상 아니면 None.

    대상 = outdoor location(primary 또는 frame-visible 이 outdoor 집합에 속함)
    AND bg plate(chain_bg) 미첨부 AND zoom 아님 AND close framing 아님(close 는
    close×ref_usage matrix 계약이 prev-shot bg 합성을 의도적으로 금지 — FINDING 9
    W3, 의무 판정하면 구조적 false alarm) AND 연속성/의존 신호(dep_detail entry
    또는 same-scene 선행 selected shot) 존재 — Codex 합의(narrow first,
    cross-scene history 는 의무 판정에 안 넣음). resolved=False 면 reason=
    previous_frame_required_missing (hard fail 아님 — 소비자가 WARNING + still
    pipeline_metadata 로 영속).Nr   outdoorTprevious_frame_required_missing)depsame_scene_prior)laneprevious_frame_requiredresolvedsource_selectionsource_still_idreason_if_missingrequired_signals)r)   r   boolr(   r'   )r   r   r7   r6   ve_location_sidschain_bg_attached	ref_usagehas_dephas_same_scene_priorrL   bytes_source_kindrN   rD   rF   s                 r    build_outdoor_prev_frame_diagrX   |   s    4 {C(Z
C-H#$ 	"	(b--k:>NN 	@!*SU.>>?  $$+#'N19-t*%-T3T=d;O6P

 
r"   c                   t               }| xs g D ]  }|j                  d      |j                  d      }}t        |t              rt        |t              sF|j                  d      xs g D ]  }|j                  d      t        k7  r|j                  d      |j                  d      }}|j                  ||f       t        |t              sbt        |t              ss|j                  ||f         |S )u   shot_dependency_t2i dependencies 에서 zoom_in_detail pair 의 양쪽
    (zoom + source) shot key 집합 — site seed 제외 신호 (이중 재작문 방지;
    merge 우선순위가 2차 방어).r   r   location_refsrT   )r'   r(   r)   r   REF_USAGE_ZOOMr*   )dependenciesr,   rH   r=   shirefsrc_sisrc_shis           r    zoom_member_shotsra      s     C!r!''-(#'',*?C"c"*S#*>77?+1r1Cww{#~5!ggm4cggl6KGFGGRI&#&:gs+C)* 2	 " Jr"   c                    | j                  d      xs g }t        |      dk7  ry| j                  d      xs i j                  d      xs g }t        d |D              S )u   1인 + 이동 — structured enum 조인만 (frame_spatial_contract constraint:
    target_kind == character AND gesture_action == moves_toward).character_anglesr   Fframe_spatial_contractconstraintsc              3     K   | ]6  }|j                  d       t        k(  xr |j                  d      t        k(   8 yw)target_kindgesture_actionN)r(   TARGET_KIND_CHARACTERGESTURE_MOVES_TOWARD).0cs     r    	<genexpr>z3_staging_is_single_figure_moving.<locals>.<genexpr>   sF       A 	
m 55 	<EE"#';;	<s   <>)r(   lenany)staginganglesre   s      r     _staging_is_single_figure_movingrr      sh     [[+,2F
6{a;;78>BCCMRXVXK    r"   )r6   single_shot_lane_enabledc                  i }g }i }	|xs i }
t        |       D ]  }t        | |         D ]  }||f}t        fd|j                  |      xs g D              }d}|s|r|
j                  |      }|r	|v r|g}d}|sT|j                  |      xs i }d| d| }||v r|j                  |dd       |j                  d      xs g }t        |      }|d	k  r"|s t	        |      r|j                  |d
d       t        |      dkD  r|j                  |d||d   d       |d   }|j                  |g       j                  |       |j                  d      }t        t        |t              xr |j                  d      xs g       }|dk(  rdn|d	k  rdnd||||j                  d      ||d|j                  d      dufd|dkD  fd|fdt        d |D              ffD cg c]	  \  }}|r| c}}d|	|<     |||	fS c c}}w )u  seed: outdoor selected shot → {loc_id: [shot_key…]}.

    반환: (groups, skipped, seed_meta).

    **legacy(single_shot_lane_enabled=False, default)**: v1 그대로 —
    outdoor × character_angles 2인+ frame-visible location 만. byte-identical.

    **broad single-shot lane(single_shot_lane_enabled=True, 4a fix 2026-07-01,
    Codex 합의)**: 사용자 요구 "한샷이라도 구도/복잡이면 실내외 무조건 마네킹" —
    복잡샷이 seed 에서 조용히 탈락하지 않게 넓게 연다(판정은 judge/QC fail-closed).
      - figure_count>=1 이면 단일 figure 샷도 seed(2인+ 요구 제거).
      - location source: frame_visible(VE 에 L##) → 없으면 primary_location fallback
        (scene_director.primary_location, outdoor 일 때만). location_source 기록.
      - ★코드는 free-text(camera_direction 등) 의미판정 안 함 — 구조필드 존재/count/
        enum 만. seed_meta 에 why_candidate/구조 signal 기록(judge/과발동 튜닝 SOT).
      - figure 0 = no_figure skip(마네킹 대상 아님).

    skipped 진단(silent drop 금지): zoom_continuity_member / single_figure_moving
    (legacy 만) / no_figure(broad) / multiple_outdoor_locations.
    c              3  ,   K   | ]  }|v s|  y wNr   )rk   tokoutdoor_locss     r    rm   z$detect_site_seeds.<locals>.<genexpr>   s       9c\>Q 9s   	frame_visibleprimary_locationSshzoom_continuity_member)shotreasonrc      single_figure_movingr   multiple_outdoor_locationsr   )r~   r   	locationschosenrd   re   single_shot_outdoor_no_figure"single_shot_outdoor_figure_present"cross_shot_or_multi_figure_presentframing_scaleNgaze_directionc              3  >   K   | ]  }|j                  d         yw)gaze_direction_kindNr(   )rk   as     r    rm   z$detect_site_seeds.<locals>.<genexpr>7  s      /JBHQAEE"78&s   )why_candidatelocation_sourcelocation_idfigure_countr   character_angle_countframe_spatial_contract_present!available_structural_signal_names)
sortedr(   appendrn   rr   
setdefaultrQ   r)   r5   ro   )selected_map
ve_by_shotstaging_by_shotrx   zoom_membersr6   rs   groupsskipped	seed_meta_plr=   r]   keylocsr   _plcrp   labelrq   r   r   _fsc_fsc_present_n_presents      `                      r    detect_site_seedsr      s   < (*F$&G/1I
#
)rC\",r*+Cs)C  *s 3 9r 9 D .O4
 wwr{DL0 6D&8O%))#.4"Gt2cUOEl"9QRS[[!34:Fv;La/7@AW'XY 4y1}!:!%"1g	   !WFfb)005;;78D4&JDHH],C,IrLL 8Dq7H3BNQRBR>C#2% ,!(_!=)52> )'++o*Fd*RS+\A-=>1<@)3 /JBH/J ,J K	,6,<2x "  ,6IcN_ , #L 7I%%6s   #Hc                    t        | t        t        f      xr6 t        |       dk(  xr& t	        d | D              xr t	        d | D              S )Nr   c              3  n   K   | ]-  }t        |t        t        f      xr t        |t                / y wrv   )r)   r   r   rQ   rk   vs     r    rm   z_is_point.<locals>.<genexpr>G  s0     WQVA
1sEl+GJq$4G0GGQVs   35c              3  ^   K   | ]%  }t         t        |      cxk  xr	 t        k  nc  ' y wrv   )r   r   r   r   s     r    rm   z_is_point.<locals>.<genexpr>H  s"     BEq	U1X222Es   +-)r)   listtuplern   all)values    r    	_is_pointr   C  sQ    54-( 	CJ!O	CWQVWW	C BEBB	r"   c           	     n   g }t        | t              sdgS | j                  d      }t        |t              s|j	                  d       nt        |      D ]  \  }}|xs i j                  d      }t        |t              r|rt        d |D              s|j	                  d| d       |xs i j                  d      }|lt        |      rx|j	                  d| d	        | j                  d
      }t        |t              r|s|j	                  d       nt        |      D ]  \  }}t        |xs i j                  d      xs g       D ]  \  }	}
t        |
j                  d      t              s|j	                  d| d|	 d       t        |
j                  d            s|j	                  d| d|	 d       |
j                  d      }|t        |      r|j	                  d| d|	 d         | j                  d      }t        |t              r|s|j	                  d       |S t        |      D ]{  \  }}t        |xs i j                  d      t              s|j	                  d| d       dD ]9  }t        |xs i j                  |            r"|j	                  d| d| d       ; } |S )u   shape/좌표 범위만 검증 — 의미 게이트 없음 (Codex ⓔ).

    Returns: violation 문자열 목록 (빈 목록 = 통과).zlayout must be an object	landmarkszlandmarks must be a listpointsc              3  2   K   | ]  }t        |        y wrv   r   rk   ps     r    rm   z'validate_site_layout.<locals>.<genexpr>Z  s      E&,	!f   z
landmarks[z&]: points must be [x,y] pairs in 0-100faces_towardz.]: faces_toward must be [x,y] in 0-100 or nullfiguresz figures must be a non-empty list	positionsr   zfigures[z].positions[z]: shot_index must be intposz]: pos must be [x,y] in 0-100moving_towardz/]: moving_toward must be [x,y] in 0-100 or nullcamerasz cameras must be a non-empty listzcameras[)r   look_atz]: z must be [x,y] in 0-100)	r)   r5   r(   r   r   	enumerater   r   r   )layout
violationsr   ilmr   ftr   figj	pos_entrymtr   camfields                  r    validate_site_layoutr   L  s    Jfd#*++

;'Ii&45y)EArhB^^H-Ffd+6 E&,E B !!Jqc1W"XY (/B~im!! #QRT * jj#Ggt$G<=(FAs )39"//+*F*L" M9!)--"=sC%%<sB[&\] u!56%%<sB_&`a]]?3>)B-%%"1#\!4cd !N ) jj#Ggt$G<=   (FAssybool;SA!!HQC/H"IJ+ #)!78%%3ug=T&UV , ) r"   c                    | j                  d      xs g D ch c]2  }t        |j                  d      t              r|j                  d      4 }}t        |      |z
  S c c}w )Nr   r   )r(   r)   r   r'   )r   member_shot_indicesr   presents       r    camera_missing_shotsr     si    
 ::i(.B..Ccggl+S1 	.  
 "#g--s   7Ac                    t        j                  t        |d         t        | d         z
  t        |d         t        | d         z
        S )Nr   r   )mathhypotr   )r   bs     r    _distr     s=    ::eAaDkE!A$K/qtuQqT{1JKKr"   c                   t        |d         t        | d         z
  t        |d         t        | d         z
  f}t        |d         t        | d         z
  t        |d         t        | d         z
  f}t        j                  | xs d}t        j                  | xs d}|d   |d   z  |d   |d   z  z
  ||z  z  }|dkD  ry|dk  ryy)	u   카메라 시선축 기준 프레임 좌/우/중앙 — 2D 외적 부호 (순수 기하).

    화면 좌표 관례: 시선축 기준 반시계(외적 양수)=왼쪽.r   r         ?g?leftgɿrightcenterr   r   r   )cam_posr   pointaxisrelaxis_nrel_ncrosss           r    _frame_sider     s     '!*gaj 1153DuWUVZGX3XYDq?U71:..eAh%PQ
BS0S
TCZZ%#FJJ#E!Ws1vQ#a& 00Ve^DEt|u}r"   c                   t        j                  |  xs d}t        j                  | xs d}| d   |d   z  | d   |d   z  z   ||z  z  }t        j                  t        j                  t	        dt        d|                        S )Nr   r   r   g      )r   r   degreesacosmaxmin)v1v2n1n2dots        r    _angle_betweenr     sv    	R	CB	R	CBa52a5=2a52a5=(R"W
5C<<		#dCSM":;<<r"   c                    t        | ||      }t        | |      }||dz  k  rdn||dz  k  rdnd}|dk(  rdn| d}| d| d	S )
Ng333333?nearg      @zmid-distancefarr   z side z of the frame)r   r   )r   r   r   	nearest_dsideddepthside_ss           r    _point_labelr     sd    w/DguA9s?*F1	TWCW]bE)X$u~FWAfX]++r"   c                    t        | xs d      j                         }|r#|j                  dd      d   j                         nd}|dv r|S d| S )u   영어 정관사 조립 — label 이 이미 관사로 시작하면 중복 부착하지 않는다
    ("the the sea" 방지). 출력 문장 템플릿 조립이며 의미 판단이 아니다.landmarkNr   r    )ther   anzthe )r   stripsplitlower)r   textheads      r    	_definiter    sW     u"
#))+D-14::dAq!'')rD--4@T$=@r"   g      9@g      @g      >@g      $@c                    | d   | d   df}t        t        |             D ]E  }t        |dz   t        |             D ](  }t        | |   | |         }||d   kD  s| |   | |   |f}* G |S )uG   점 집합의 최장 쌍 = landmark 의 주축 segment (순수 기하).r   r   r   r   )rangern   r   )r   bestr   r   r   s        r    _extent_segmentr
    s|    1Ivbz3'D3v;q1uc&k*AfQi+A47{q	6!9a0 +  
 Kr"   c                X   t        |d         t        |d         t        |d         t        |d         f\  }}}}||z
  ||z
  }}t        j                  ||      xs d}	d}
| D ]C  }t        t        |d         |z
  |z  t        |d         |z
  |z  z
        |	z  }t	        |
|      }
E |
S )u   점들이 주축 a→b 무한직선에서 벗어난 최대 수직거리 = landmark 의 '폭'
    (순수 기하). extent/width = 종횡비 → 경로성 판정에 쓴다.r   r   r   r   )r   r   r   absr   )r   r   r   axaybxbydxdydenomwr   r   s                r    _max_perp_widthr    s     1Q4[%!+uQqT{E!A$KGNBB"Wb2gBJJr2%#EAqtr!R'51;+;r*AABUJ1I  Hr"   c                j   t        |d         t        |d         }}t        |d         |z
  t        |d         |z
  }}||z  ||z  z   }|dk(  rt        | |      S t        dt        dt        | d         |z
  |z  t        | d         |z
  |z  z   |z              }t        | |||z  z   |||z  z   f      S )Nr   r   r   r   )r   r   r   r   )	r   r   r   r  r  vxvyseg_len2ts	            r    _point_segment_distr    s    1Q4[%!+B1Q4[2uQqT{R/BBwb H1}Q{CSE!A$K",2eAaDkB6F"5LLPXXYZAR!b&["q2v+.//r"   c                   t        |       dk  ryd}t        t        |             D ]S  }| |   \  }}| |dz   t        |       z     \  }}|t        |      t        |      z  t        |      t        |      z  z
  z  }U t        |      dz  S )N   r   r          @)rn   r  r   r  )r   sr   x1y1x2y2s          r    _polygon_arear$    s    
6{QA3v;BQ#f+-.B	U2Yr"U2Yr%:::   q6C<r"   c                t    t        |       xs d}t        d | D              |z  t        d | D              |z  fS )Nr   c              3  8   K   | ]  }t        |d            ywr   Nr   r   s     r    rm   z_centroid.<locals>.<genexpr>       ,VadV   c              3  8   K   | ]  }t        |d            ywr   Nr(  r   s     r    rm   z_centroid.<locals>.<genexpr>  r)  r*  )rn   sum)r   ns     r    	_centroidr/    s@    FqA,V,,q0,V,,q02 2r"   c                
   t        | d   d         t        | d   d         }}t        | d   d         t        | d   d         }}||z
  ||z
  }}t        j                  ||      xs d}||z  ||z  f}|d   |d    f}	||f||	fS )u  카메라 시선축 forward 단위벡터 + 화면-우 단위벡터 (순수 기하).

    right = (f[1], -f[0]) — 화면-우 양수 컨벤션 (좌표 산술, spike sh6 검증). 이
    프레임으로 점을 투영하면 depth(near→far)+lateral(좌(-)→우(+))이 나온다.r   r   r   r   r   r   )
camerapxpylxlyfxfyr.  fr   s
             r    	_cam_axesr9    s    
 6%=#$eF5M!,<&=B6)$Q'(%y0A!0D*EB"Wb2gB

2r!cA	aaAqTAaD5ME8Qr"   c                    t        |       \  \  }}}}t        |d         |z
  t        |d         |z
  }}||d   z  ||d   z  z   ||d   z  ||d   z  z   fS )u:   점을 카메라 프레임에 투영 → (depth, lateral).r   r   )r9  r   )r1  r   r2  r3  r8  r   r  r  s           r    _projectr;    so    "6*HRa58_r!5q?R#7B!9rAaDy "uQx-"uQx-"???r"   c                   t        fd| j                  d      xs g D        d      }|4t        |j                  d            rt        |j                  d            sy|d   |d   }}g }| j                  d      xs g D ]  }t        fd|j                  d      xs g D        d      }|t        |j                  d            sH|j                  |j                  d	      xs |j                  d
      xs d|j                  d      |d   t	        ||d         |j                  d      +t        |j                  d            r|j                  d      ndd        |sy|j                  d        |d   d   }|xs d}	d dg}
| j                  d      xs g D cg c]*  }|j                  d      rt        d |d   D              r|, }}g }|D ]]  }|j                  d	      xs |j                  d      xs d}|d   }t        |      dk\  rt        |      nd\  }}}||t        t        |||      d      z  nd}||j                  d      dk(  s|t        k\  ro|t        k\  rft	        ||      t	        ||      kD  r||}}|
j                  d | d!t        ||||	       d"t        ||||	       d#       |j                  |||d$       t        d% |D              t        |      z  }t        d& |D              t        |      z  }|
j                  d | d't        ||||f|	       d#       ` |D cg c]  }|j                  d      d(k(  s| }}|
j                  d)       t        |      D ]  \  }}|d	   |j                  d      r	d*|d    d+nd,z   }d | d't        |||d   |	       |dk(  rd-nd,z   g}|dkD  r2|d   dkD  r*|dkD  r%|d   |z  }|j                  d.|d/d0d|z  d1d2       |d   }|[t!        |d         t!        |d   d         z
  t!        |d3         t!        |d   d3         z
  f}d4t        ||||	       g}d}|D ]  }t!        |d5   d         t!        |d6   d         z
  t!        |d5   d3         t!        |d6   d3         z
  f} t#        ||       }!t%        |!d7|!z
        t&        k  slt)        |d   |d6   |d5         t*        k  s|d	   }|j                  d8t-        |               n |D ]I  }"|"|u s|"d   t	        ||"d         t	        |d   |"d         d9z  kD  s3|j                  d:|"d	           K d}#|D ]  }|j                  d	      xs |j                  d      }||k(  r-|d   }t        d; |D              t        |      z  }t        d< |D              t        |      z  }|t!        |d   d         z
  |t!        |d   d3         z
  f}$t#        ||$      d=k\  st/        |      }%|#	|%|#d   kD  s|%|f}# |#r |j                  d>t-        |#d3                 |j                  d?j1                  |             |
j                  d?j1                  |      d#z          
 t        |      d3kD  r(|d@   |d   }'}&|
j                  dA|&d	    dB|'d	    dC       dDj1                  |
      S c c}w c c}w )Eu  layout 좌표 → 해당 shot 의 **프레임 기준 지형+인물 요약** (순수 계산,
    LLM 0). 카메라 또는 인물 위치가 없으면 None.

    R2 사용자 피드백: "길의 방향(왼쪽→오른쪽인지)·정류장 위치 같은 지형을
    설명하면서 방향을 알려줘야 모델이 제대로 안다" — landmark 가 프레임에서
    어떻게 놓이고, 이동 인물의 경로가 무엇을 따라 어느 쪽으로 가며 무엇으로는
    가지 않는지를 좌표 산술로만 서술한다 (의미 추론 0).
    좌/우는 layout 카메라 기준 — LLM1 이 staged screen zone 재현을 강제받고
    step 이 `layout_side_conflicts` 로 검증한다 (충돌 시 retry+진단).c              3  L   K   | ]  }|j                  d       k(  s|  ywr   Nr   rk   rl   r   s     r    rm   z-build_shot_spatial_summary.<locals>.<genexpr>  $     U/q1553F*3T/   $$r   Nr   r   r   c              3  L   K   | ]  }|j                  d       k(  s|  ywr>  r   rk   r   r   s     r    rm   z-build_shot_spatial_summary.<locals>.<genexpr>&  $     X21aeeL6IZ6WQ2rA  r   r   	figure_idfigureentity_tokenr   )r   rG  r   distancer   c                    | d   S )NrH  r   )es    r    <lambda>z,build_shot_spatial_summary.<locals>.<lambda>6  s    q}r"   r   r   rH  r   zSHOT u    SCENE GEOGRAPHY (computed from the site-layout coordinates, frame-relative from this shot's camera — source of truth for where things are, which way paths run, figure depth and relative size):r   r   c              3  2   K   | ]  }t        |        y wrv   r   r   s     r    rm   z-build_shot_spatial_summary.<locals>.<genexpr>H  s     $H<aYq\<r   idr   r   )NNr   r   kindline- z (path): runs from the z to the .)r   r   r   c              3  8   K   | ]  }t        |d            ywr'  r(  r   s     r    rm   z-build_shot_spatial_summary.<locals>.<genexpr>d       .#QU1Q4[#r*  c              3  8   K   | ]  }t        |d            ywr,  r(  r   s     r    rm   z-build_shot_spatial_summary.<locals>.<genexpr>e  rT  r*  z	: at the areazFIGURES: ()r   u!    — NEAREST figure to the camerazabout z.1fzLx as far from the camera as the nearest figure, so they must appear roughly .2fz<x the nearest figure's height (smaller, deeper in the frame)r   z@in motion: already partway along their path, heading toward the r   r   g     f@ztheir path follows g?zmoving away from c              3  8   K   | ]  }t        |d            ywr'  r(  r   s     r    rm   z-build_shot_spatial_summary.<locals>.<genexpr>       2cqtcr*  c              3  8   K   | ]  }t        |d            ywr,  r(  r   s     r    rm   z-build_shot_spatial_summary.<locals>.<genexpr>  r[  r*  g      N@zNOT heading into ; r  zDEPTH CONTRACT: z is farther than z7 and must read visibly smaller and deeper in the frame.
)nextr(   r   r   r   sortr   rn   r
  r   r  _ELONGATED_MIN_EXTENT_PATH_MIN_ASPECTr   r-  r   r   r   r   _FOLLOW_MAX_ANGLEr  _FOLLOW_MAX_OFFSETr  r$  join)(r   r   r1  r   lookentriesr   r   true_nearest_dr   linesr   r   	elongatedr   ptsr   r   extentaspectcxcyarea_landmarksrankrJ  namepartsratior   move_vec
move_partsfollowed_labelaxis_vecangothernot_into_bestto_area	area_sizer   r   s(    `                                      r    build_shot_spatial_summaryr~    s    UFJJy)/R/UF ~Yvzz%'896::V_K`Aa5M6)#4TG %'Gzz)$**X,22X
	 IimmE.B$CWWW%I)=IGGN3U#gy'78}}_-9)--89 ']]?;?C
 	 +  LL,L-
 QZ
+N%#I 
|  	E ZZ,222rFF8$H2h<$H!H 	2   ')Iw=266$<=:l/23x1}s+BS1f=>]FSa3S99PS 	 =FF6Nf$//F>N4N Wa 5!#44!1LLUG2<qR[3\2] ^&waCDAG u11=>.#..S9B.#..S9BLLUG9\'4"b9%U$VVWX- 6 $-I9Rv&0Hb9NI	LLW%az!%%:Or!N"3!4A6UWXiWdAeHi PQR6:ai2RI
 !8*)nq.@
 jMN2ELLs $//2U{3.? @88
 >beuQuXa['995A<%PQRWPXYZP[J\;\]HRr9=>@J "N!"S'!*-bgaj0AA!"S'!*-bgaj0AAC$Xx8US[)->>+AeHbgr#wGK]]%'[N%%(;In<U;V&WX   !A:!7!CU5\*U1U8U5\-JS-PP%%(9%.9I&JK	 ! !M$w7266$<N*l2c22SX=2c22SX=ahqk 22Bqx{9K4KL!(G4< -c 2I$,	M!<L0L)2E(: % !!$5ia@P6Q5R"STLL:./TYYu%+, &@ 7|aBKTs7|n,=d7m_ MA A	
 99U[J Js   &/[ [[c                   g }|D ]`  \  }t        |      |vrt        fd| j                  d      xs g D        d      }|t        |j                  d            s[|d   }g }| j                  d      xs g D ]  }t        fd|j                  d      xs g D        d      }	|	t        |	j                  d            sHt	        ||	d         }
|	j                  d      }|duxr t        |      xr t	        ||      |
kD  }|j                  |
|f        t        |      d	k  r&t        d
 |D              t        fd|D              sN|j                  |f       c |S )uw  구도 스케치 가이드 **기하 후보** 샷 — 2인+ departing class (좌표 산술만).
    이 함수는 1차(기하) 게이트일 뿐 — caller 가 샷레벨 의미 게이트(열린 외부
    departure 인가)로 한 번 더 거른다.

    조건 (전부 좌표 산술, 의미 추론 0):
      ①해당 샷의 spatial summary 가 존재 (caller 가 side-conflict/skip 샷을
        이미 제외한 ``summary_keys`` 를 전달)
      ②layout 에 그 샷 위치가 있는 figure 2인 이상
      ③**실제로 멀어지는(receding)** figure 가 존재하고 그 figure 가 NEAREST 가
        아님 — moving_toward 가 figure 의 현 위치보다 카메라에서 **더 멀어야**
        한다 (카메라/전경 쪽으로 다가오는 lunge 는 departure 가 아니다 — 공격·
        근접 대치 샷이 좌표상 mover 로 잡히던 오선별 제거).
    c              3  L   K   | ]  }|j                  d       k(  s|  ywr>  r   )rk   rl   r]   s     r    rm   z.composition_guide_shot_keys.<locals>.<genexpr>  s$     R31quu\7Jc7QQ3rA  r   Nr   r   c              3  L   K   | ]  }|j                  d       k(  s|  ywr>  r   )rk   r   r]   s     r    rm   z.composition_guide_shot_keys.<locals>.<genexpr>  s%     U6q!%%:MQT:T6rA  r   r   r   c              3  &   K   | ]	  \  }}|  y wrv   r   rk   r   _s      r    rm   z.composition_guide_shot_keys.<locals>.<genexpr>  s     .gdags   c              3  6   K   | ]  \  }}|xr |kD    y wrv   r   )rk   r   recedingr   s      r    rm   z.composition_guide_shot_keys.<locals>.<genexpr>  s"     EWkax)A	M)Ws   )	r!   r_  r(   r   r   r   rn   r   ro   )r   member_keyssummary_keysselectedr=   r1  r   rg  r   r   d_posr   r  r   r]   s                @@r    composition_guide_shot_keysr    st   $ !HCS!5R

9-33R
 >6::e+<!=-,.::i(.B.CUCGGK06B6UI  	)--2F(G'9U#34E/B $O9R=OU7B5G%5O  NNE8,- / w<!.g..	EWEEOORI&= > Or"   c           
     "   i }| D ](  \  }}|j                  |g       j                  ||f       * g }t        |      D ]O  }t        ||   d       }d}t        |      D ],  \  }}|j                  ||dk(  rdnd|dk(  rdn|||d       |}. Q |S )u  admitted guide 샷들을 **same scene_index** 그룹으로 묶어 shot_index 순으로
    정렬한 뒤 각 샷의 continuity mode/anchor_source/admitted_order 를 산출한다
    (순수 — LLM/좌표 0).

    option C (사용자 정정 + Codex 합의): 같은 장소·같은 scene 의 admitted 샷들을
    하나의 연속성 그룹으로 보고
      - 첫 admitted 샷 = ``mode='sketch'`` — 구도 스케치(마네킹 브리프)로 구도를
        deliberately 확립하고, 그 결과 프레임이 나머지의 연속성 anchor 가 된다
        (이 샷은 직전에 참조할 같은-그룹 완성 프레임이 없다).
      - 이후 admitted 샷 = ``mode='continuity_anchor'`` + ``anchor_source`` = 같은
        그룹의 직전 admitted 샷 키 — 생성 시점에 그 샷의 현재-run 완성 프레임을
        연속성 anchor 로 쓴다 (사전 스케치 미생성).
    **다른 scene 으로는 체이닝하지 않는다** — 같은 location 이 에피소드 후반에 다시
    등장해도 무관한 시퀀스를 연결하지 않기 위함 (Codex 합의: same scene + same
    location + admitted 로 좁게).

    Returns (입력 순서 무관, scene/shot 정렬 순):
        [{"key": (si, shi), "mode": "sketch"|"continuity_anchor",
          "anchor_source": (psi, pshi)|None, "scene_index": si,
          "admitted_order": int}] — caller 가 location_id 와 합쳐 group_id 부여.
    c                    | d   S Nr   r   ks    r    rK  z.composition_continuity_chain.<locals>.<lambda>      QqTr"   rL  Nr   sketchcontinuity_anchor)r   modeanchor_sourcer   admitted_order)r   r   r   r   )	admitted_keysby_scener=   r]   r,   r$   prevorderr   s	            r    composition_continuity_chainr    s    0 *,H CB#**B95 ! "CX">:"&#G,JE3JJ$)QJ4G).!!"'  D -  Jr"   c                   t        |       rt        |      dk\  syt        | d         t        | d         }}d}t        |      }t        |      D ]  }t        ||   d         t        ||   d         }}t        ||dz   |z     d         t        ||dz   |z     d         }
}	||kD  |
|kD  k7  s_|||z
  |	|z
  z  |
|z
  xs dz  z   }||kD  s}| } |S )u  ray-casting 내부 판정 (순수 좌표 산술, 3점 미만 폴리곤은 항상 False).

    3-stage 필수화(2026-07-02) enclosure 관계용 — 경계 위 점의 미세 판정은 중요치
    않다(불확실하면 관계 서술을 생략하는 쪽이 안전, Codex 합의).r  Fr   r   g&.>)r   rn   r   r  )ptpolyxyinsider.  r   r   r!  r"  r#  x_crosss               r    _point_in_polygonr  %  s    
 bMc$i1nA<r!uqAFD	A1XtAwqz"E$q'!*$5BtQUaK(+,eD!a%14Ea4H.IBFQAFrBw/BG3DEEG{#  Mr"   c                   | j                  d      }|j                  d      xs g D ]  }|j                  d      xs ddk7  r|j                  d      xs g D cg c]  }t        |      s| }}t        |      dk  st        ||      se|j                  d      xs |j                  d      xs d	}t        |      rt        ||      sd
| dc S d| dc S  yc c}w )u  figure↔area-landmark 위상 관계 구절 (순수 폴리곤 산술만, 자유문장 파싱 0).

    figure centroid 가 area landmark polygon 내부이고 카메라는 그 밖이면 스케치가
    인물을 개방 지면으로 끌어내는 오류(카메라-피사체 사이 경계면 관계 손실)를 막는
    generic 관계를 서술한다. 라벨은 데이터 label 그대로(템플릿 시나리오 토큰 0).
    둘 다 내부면 'inside'만, 불확실(비폴리곤/판정 불가)이면 빈 문자열 = 서술 생략.r   r   rO  rV  r   r  r   rN  za mapped areaz, INSIDE the mapped area 'u   ' while the camera is OUTSIDE that area — keep the figure within that area; if that area's boundary or opening is visible, show it between the camera and the figurez, inside the mapped area ''r   )r(   r   rn   r  )r1  fig_posr   r   r   r   rk  r   s           r    _enclosure_relation_phraser  9  s     jjGjj%++FF6N$f/66(+1r1C1Qilq1Cs8a<0#>wB266$<B? W&7&E0 80 1 2 ,E7!44 ,   Ds   C+Cg      4@)half_fov_degc                   ! t         j                  d            rt         j                  d            sy j                  d      }d. fd}d/ fd}g }|j                  d      xs g D ]  }|j                  d      xs g D cg c]  }t        |      s| }	}|	s4t         t        |	            \  }
}|
d	k  rR|j                  d
      xs |j                  d      xs d}|j                  d      xs d}|dk(  r ||	      nd} ||	|j                  d            }|j	                  |
|||||f        |j                  d        |sd| dS |d   d   |d   d   c }| z
  xs d!d0 !fd}d1fd}g }|D ]`  \  }
}}}}}|dk(  r&d|v r"|j	                  d| d ||
       d| d       5|j	                  d| d ||
       d |||
       | | d	       b g }|j                  d       xs g D ]  }|j                  d!      xs g D ]  }|j                  d      |k7  st        |j                  d            s2t         |d         \  }}|d	k  rJ|j                  d"      }|j	                  ||t        |      r|nd|d   f         g }|rtt        d# |D              }t        |d$       D ]Q  \  }}}}||xs dz  }|d%nd}t         ||      }|j	                  d& ||       d |||       d'|d(d)| | d
       S d| dd*d+g}||z  }|r|j	                  d,       ||z  }d-j                  |      S c c}w )2u.  layout + 한 카메라 → 그 카메라뷰의 layout/depth/figure 배치 brief (좌표 산술만).

    structure 는 near→far depth ordering + 화면 좌/우, figure 는 상대 스케일만 서술한다.
    카메라 좌표 부재 시 None. (shared-model 가이드 image edit 프롬프트 입력.)r   r   Nr   r   c                    t        |       dk  ryt        |       \  }}}t        
|      \  }}t        
|      \  }}t        ||z
        t        ||z
        }	}|	d|z  k\  ry|d|	z  k\  ryy)ug   장축 화면방향(좌표 산술): 최장 쌍을 카메라에 투영 → horizontal/receding/diagonal.r   r   z< (runs roughly horizontally across the frame, left to right)zC (recedes into the distance, a strong near-to-far perspective line)z# (runs diagonally across the frame))rn   r
  r;  r  )r   r   r   r  d0l0d1l1dddlr1  s             r    	_axis_dirz'compute_camera_brief.<locals>._axis_dira  sy    v;?!&)1a&!$B&!$BR"Ws27|BR<QR<X4r"   c                   t        |      syt        	t        |             \  }}t        	|      \  }}||z
  ||z
  }}t        |      dk  rt        |      dk  ryt        |      t        |      k\  r
|dk  rdnd}n	|dk  rdnd}d| S )	u  faces_toward open_vec(=ft−centroid)을 카메라 프레임에 투영 → 개방/전면 방향구.

        W-A(2026-07-03): 구조물 개방면 방향이 brief 어디에도 없어 스케치가 개방면을
        임의 방향(인접 도로를 등지는 등)으로 그리던 결함 — 지배 성분(깊이 vs 좌우)으로
        4방향 산술 서술. 순수 좌표 산술, 라벨/의미판정 0. faces_toward 부재/무효/영벡터
        → 빈 문자열(구 layout 하위호환 = 방향구 생략).r   gư>r   ztoward the cameraz1away from the camera, into the depth of the frametoward the left of frametoward the right of framez; its open/front side faces )r   r;  r/  r  )
rk  r   d_cl_cd_fl_fr  r  r   r1  s
            r    _faces_phrasez+compute_camera_brief.<locals>._faces_phraseo  s     }FIcN3SFB'SsC#IBr7T>c"gnr7c"g+-6'L  35q&.4 -dV44r"   r         ?r   rN  za structurerO  rV  r   r   c                    | d   S Nr   r   r  s    r    rK  z&compute_camera_brief.<locals>.<lambda>  s    QqTr"   rL  z#The eye-level camera view for shot z'. (no landmarks in front of the camera)r   r  r   c                4    | z
  z  }|dk  rdS |dk  rdS dS )N(\?
foregroundq=
ףp?	midground
backgroundr   )r   rdminspans     r    _bandz#compute_camera_brief.<locals>._band  s-    X 4x|XQXkX<Xr"   c           	         t        j                  t        j                  | t        |d                  }| dz  k  ry|dz  kD  ryy)NgMbP?g?r  r  znear centre of frame)r   r   atan2r   )lateralr   ry  r  s      r    _screenz%compute_camera_brief.<locals>._screen  sH    ll4::gs5$/?@A,$$-##.%r"   horizontallyrQ  z	: in the z, spanning across the framerR  z, r   r   r   c              3  *   K   | ]  \  }}}}|  y wrv   r   r  s      r    rm   z'compute_camera_brief.<locals>.<genexpr>  s     5*JAq!Qa*   c                    | d   S r  r   r  s    r    rK  z&compute_camera_brief.<locals>.<lambda>  s    1r"   z* (in motion, moving deeper into the scene)z- a figure: z, about rY  zx the nearest figure's sizeu   Keep the horizon a single LEVEL, roughly horizontal line across the frame — do not tilt or slant it; this is an eye-level shot.z(Visible structures, ordered near to far:z#Figures (featureless markers only):r^  )r   	List[Any]returnr   )rk  r  r   r   r  r   )r   r   r  r   )r  r   r   r   r  r   )
r   r(   r;  r/  r   r`  r   r   r  re  )"r1  r   r  r]   r  r  r4   r   r   rk  r   r  r   rO  r   facesdmaxr  r  ri  
fig_depthsr   r   latr   	fig_linesnearestfposscalemvencr,   r  r  s"   ` `                             @@r    compute_camera_briefr  V  s    fjj'(Yvzz)7L-M
**\
"C55, <>Ejj%++66(+1r1C1Qilq1C!&)C.9wC<w@266$<@=vvf~'!%y~Rc266.#9:eWeT4?@ , 
JJ>J"5cU ;8 8 	9 q!eBilJD$4KCDY& E490wtT5 6>n4LLUG9U5\N2MeWTUVX LLUG9U5\N"WWe5L4MdVTYSZZ[\^ 5: 79Jzz)$**%++Auu\"c)155<1Hfah/FAsCx'Bq#Yr]rahOP , + I5*55 &z~ FAsBqxC(EAC=UWB -VT6BCuQxj73?*;8+8SEDE !G .cU!4	>2	C 5LC

89y99S>W Ds   MM))         n   )      x   r  )      r     )r  r  r  r  )   r  r  r  )r  r  r  r  zList[Tuple[int, int, int, int]]_BIRDSEYE_AREA_PALETTE)r  r  r  zTuple[int, int, int]_BIRDSEYE_EDGE)r  <   r  _BIRDSEYE_FIGURE)(   r  r  _BIRDSEYE_CAMERA)r   c               &   g }d}| j                  d      xs g D ]  }|j                  d      xs g D cg c])  }t        |      rt        |d         t        |d         g+ }}|sN|j                  d      }|dk(  r1t        |      dk\  r#|j	                  d|t        t              d       t        |      d	k\  rKt        |t        t              z     }|j	                  d
|t        |      t        t              d       |dz  }|j	                  d|dd t        t              d        g }	| j                  d      xs g D ]  }
||
j                  d      |k7  rt        |
j                  d            s5t        |
j                  d            sP|	j	                  |
j                  d      t        |
d   d         t        |
d   d         gt        |
d   d         t        |
d   d         gd        g }| j                  d      xs g D ]  }|j                  d      xs g D ]  }||j                  d      |k7  rt        |j                  d            s5|j                  d      }|j	                  |j                  d      t        |d   d         t        |d   d         gt        |      rt        |d         t        |d         gndd         t        t        g||	|t        t              t        t              dS c c}w )u  layout 좌표 → 결정론 top-down set-map 그리기 spec (순수 데이터, PIL/LLM 0).

    ★재배선 v2(2026-06-29) 에서 **미사용** — PIL birdseye 경로(literal 좌표 렌더 →
    셸터가 도로 위 같은 좌표 품질 노출)를 라벨 마커 항공뷰(T2I base + I2I 블로킹)로
    교체했다. 삭제하지 않고 보존(다른 소비/회귀 참조 가능). 새 경로는
    ``build_aerial_base_prompt`` / ``build_shot_blocking_prompt`` 를 쓴다.

    색은 area 등장 index 로 generic 배정 (시나리오 토큰 0). ``shot_index`` 지정 시
    그 샷의 카메라+figure 위치만 (단일 카메라뷰 가이드의 명확성). landmark 는 고정
    장소이므로 항상 전부. **글자/라벨 필드 없음** — provider 렌더가 텍스트를 그리지
    않게(모델 입력) spec 자체에 인간 가독 라벨을 담지 않는다. serialize-가능 → 해시.r   r   r   r   rO  rP  r   )shaper   edger  polygon)r  r   fillr  r   Nr   r   r   r   )r   r   r   r   r   r   )r   r   r   )coord_ranger   r   r   figure_colorcamera_color)r(   r   r   rn   r   r   r  r  r   r   r  r  )r   r   r   area_idxr   r   rk  rO  r  r   rl   r   fgr   s                 r    build_clean_birdseye_specr    s    ')IHjj%++)/R/B/1IaL adU1Q4[)/ 	 Bvvf~6>c#h!m Cn9MNPX])(S9O5P*PQDyC&*4j$~:NP QMH!S!Wd>>RSU ,$ %'GZZ	"(b(!aeeL&9Z&GQUU5\"yy1A'BNNeeL1ahqk*E!E(1+,>?!!I,q/2E!I,q/4JK 	 ) %'Gjj#)r)$**A%!%%*=*KQUU5\*'BNNeeL1ahqk*E!E(1+,>?%.r] $)A<r!u">8<	  + * "9--.-. WBs   .Lg      A@_GRID_LOg     P@_GRID_HIABCDEFGHIJKLMNOPQRSTUVWXYZ_FIGURE_LETTERSg      (@_CAMERA_AIM_EPSg      ?_CAMERA_DEDUP_EPSc                    | t         k  rdn| t        kD  rdnd}|t         k  rdn|t        kD  rdnd}|dk(  r|dk(  ry| d| j                  dd	      j                  d
d	      S )u   0-100 좌표 → 9-cell grid 라벨 (top/middle/bottom × left/centre/right) — 순수
    3분할 산술 (셀 경계 generic, 의미판정 0).r   r   centretopbottommiddle-zmiddle-r   z-centre)r  r  replace)r  r  colrows       r    
_grid_cellr  ;  sh     L&!h,wHCx<%!h,hHC
h3(?U!C5>!!)R088BGGr"   c                6    ddddj                  | xs dd      S )Nza long line/edgeza filled zone/areaza small fixed marker)rP  rV  r   r   za zoner   )rO  s    r    _shape_wordr
  E  s&    &0D+--0SX-FGr"   c                   | j                  d      xs g D cg c]  }t        |      s| }}|sy| j                  d      dk(  rZt        |      dk\  rLt        |d   d         t        |d   d         z   dz  t        |d   d	         t        |d   d	         z   dz  fS | j                  d      d
k(  r"t        |d   d         t        |d   d	         fS t	        |      S c c}w )uU   landmark 대표점 (순수 기하): line=양끝 중점, point=그 점, area=centroid.r   NrO  rP  r   r   r  r  r   r   )r(   r   rn   r   r/  )r   r   rk  s      r    _landmark_anchorr  J  s    vvh'-2-
?-)A,1-C
?	vvf~CHMs1vay!E#b'!*$55<s1vay!E#b'!*$55<> 	>	vvf~ c!fQi %Aq	"233S> @s
   C!C!c                    ddt        d      }}}t        |d      D ]Z  \  }}t        |      }|t        | |      }||k  s&|t	        |j                  d      xs |j                  d      xs d      |}}}\ ||fS )	u   pos 에 가장 가까운 환경 landmark → (1-based 번호, label). 번호는 base 의 원형
    숫자와 정렬한다 (enumerate 순서 동일). 없으면 (0, "the set").r   zthe setinfr   startr   rN  
an element)r   r   r  r   r   r(   )	r   r   best_i
best_labelbest_dr   r   r   r   s	            r    _nearest_landmarkr  W  s     "#IuU|JF9A.2R 9#qMv:3rvvgF"&&,F,G !'JF / :r"   c                :   g }| j                  d      xs g D ]  }|j                  d      xs g D ]f  }|j                  d      |k7  st        |j                  d            s2|j                  d      }|j                  |d   t        |      r|ndf          |S )uW   이 샷에 위치가 있는 figure 들 → [(pos, moving_toward|None)] (순수 조인).r   r   r   r   r   N)r(   r   r   )r   r   r,   r  r   r   s         r    _figures_at_shotr  f  s     <>Cjj#)r)$**Auu\"j0	!%%,8O'BJJ%	""4@A + * Jr"   c                   | d   | d   }}t        |d         t        |d         z
  t        |d         t        |d         z
  }}|t         k  rdn|t        kD  rdnd}|t        kD  rdn|t         k  rd	nd}d
j                  d ||fD              xs d}t        ||j	                  d      xs g       \  }	}
| d|	 d|
 dS )uk   카메라 pos→look_at 방향을 화면어 + 가장 가까운 환경 원 번호로 서술 (좌표 산술).r   r   r   r   zupward (toward the top)zdownward (toward the bottom)r   zto the rightzto the leftz and c              3  &   K   | ]	  }|s|  y wrv   r   r   s     r    rm   z%_camera_aim_phrase.<locals>.<genexpr>}  s     .&QAq&s   zacross the planr   z, toward circle rW  z) and the lettered figures)r   r  re  r  r(   )r1  r   r   rf  r  r  r   haimninls              r    _camera_aim_phraser  u  s    uvi0C47^eCFm+U47^eCFm-KB&(O+;&;	",.,@
(b / 00-b 
,,.1a&.
.
C2CCtVZZ%<%BCFBU"2$b,FGGr"   u  
The ATTACHED image is a TOP-DOWN INTERIOR FLOOR PLAN of the building that stands on this site — a STRUCTURAL REFERENCE ONLY, not the drawing to edit. Draw the aerial site plan described above as a fresh image. Where that building appears on the site, keep its footprint size, shape, proportions and its door/window openings consistent with the attached floor plan. Do NOT copy the floor plan's text, numbers, colours, furniture symbols or line style into the aerial site plan.BUILDING_FP_AERIAL_GUIDANCEc                T   g d}t        | j                  d      xs g d      D ]`  \  }}|j                  d      xs |j                  d      xs d}|j                  d| d	| d
t        |j                  d             d       b |j                  d       dj	                  |      S )u   Stage A — location 공통 빈 항공뷰 base 프롬프트 (환경=원형 숫자, 엔티티/카메라
    제외). generic — landmark label 만 데이터서 흘러들고 시나리오 토큰 0. group 공통.)u  Draw a CLEAN top-down COLOUR aerial SITE PLAN of ONE outdoor location — a flat schematic blueprint-style map seen straight from directly above (flat colour fills + clean outlines), NOT a photograph and NOT a realistic 3D render: no photo texture, no harsh shadows, no perspective.zLay the elements out physically sensibly: any structure built beside a long path/road runs PARALLEL alongside it, never on top of or across it; any water/coast and road stay separated along the shoreline.zThis is a SHARED EMPTY SET map reused by every camera shot. Draw each fixed ENVIRONMENT element below as a flat schematic shape AND mark it with a small CIRCLE containing its NUMBER at the element's location:r   r   r  r   rN  r  z	- circle z: rW  rO  z).u   Draw NO named characters, NO people, NO props and NO camera. The ONLY text in the image is the single DIGIT inside each numbered circle — no other words, letters or labels.r^  )r   r(   r   r
  re  )r   ri  r   r   r   s        r    build_aerial_base_promptr!    s    E 6::k28bB2w?266$<?<y2eWB{266&>/J.K2NO C 
LL	 99Ur"   c           
        | j                  d      xs g }g }t        t        | |            D ]p  \  }\  }}|t        t              k\  r nVt        |   }t        ||      \  }	}
d| dt        |  d|	 d|
 d	}|r|dt        |  z  }|j                  d|z   d	z          r d
j                  |      xs d}|d   }d| dt        |  dt        ||        dS )u   Stage B — 공통 base 위 I2I 블로킹 프롬프트: 엔티티=원형 글자, 카메라 1개. base
    set 보존 강제. generic — 위치는 grid + 환경 원 상대, 인물은 익명 글자(이름 누출 0).r   zcircle z: a figure at the z of the plan, beside circle rW  rX  z, moving toward the z   - rR  r^  z   - (none)r   u  The attached image is the SHARED empty site-plan base for this location: the numbered circles mark the fixed environment. KEEP the whole base EXACTLY as it is — same layout, shapes, numbered circles, colours, proportions and orientation; do NOT redraw, move, recolour or restyle anything.
ADD on top, for THIS ONE shot only:
1) ENTITIES — for each, draw a small CIRCLE containing a CAPITAL LETTER at its position (these lettered circles are NEW; the numbered circles stay unchanged):
uQ   
2) THE CAMERA — exactly ONE camera: a clearly recognizable CAMERA icon at the zY of the plan, with a BOLD direction ARROW and a translucent triangular view-CONE opening z~, so the shot's viewpoint and aim are unmistakable.
Add NO text other than the existing numbers and these new capital letters.)
r(   r   r  rn   r  r  r  r   re  r  )r   r1  r   r   	ent_linesidxr   r   letterr  r  clause	ent_blockcposs                 r    build_shot_blocking_promptr)    s'   
 

;'-2II#$4VZ$HIYc2#o&& %"3	2BF8#5j#6F5G H##%$bA/,Z_,=>>F6)C/0 J 		)$5I%=D	\ + [t
 ((:66(J'K LU	UVr"   u  The attached image is a TOP-DOWN diagram of one outdoor location seen from directly above: numbered circles mark fixed environment elements. If the diagram also contains lettered circles, those mark the figures present in this shot, and a camera icon with a view-cone shows the camera position and the direction it looks. Read it ONLY to understand which elements are in shot and their relative placement.
Now draw a ROUGH eye-level STORYBOARD COMPOSITION SKETCH of what THAT camera sees — a clean MONOCHROME pencil/marker line-art storyboard frame, NOT a finished render and NOT a top-down map. Use a normal horizontal EYE-LEVEL viewpoint, never a bird's-eye.
Lay out the frame exactly by this depth / left-right brief:
{brief}
If the brief states a figure is INSIDE a mapped area while the camera is OUTSIDE that area, keep the figure within that area as seen from the camera; if the area's boundary or opening is visible, draw it between the camera and the figure — do NOT relocate the figure outside its area.
If the brief states which way a structure's open or front side faces, draw that structure opening exactly that way relative to the camera — never mirror or flip its orientation.
Keep real-world human scale throughout: a doorway reads as adult height, a bench seat as knee height, a road as wider than a car. If figures are present, size every structure against them; with no figure, still keep adult-human proportions — never shrink a structure into a miniature or toy-sized box.
Draw ONLY the environment elements the diagram and the brief list — do NOT add any structure or object that is not in them, and never draw a second copy of a listed structure.
Draw each figure as a simple featureless mannequin/placeholder at its mapped screen position and depth. Draw environment elements as simple line shapes only. This sketch defines ONLY framing, placement, depth and viewpoint — do NOT invent materials, colours, style, architecture, faces or surface finish.
The diagram's NUMBERED circles, LETTERED circles, the camera icon, view-cone and arrow are spatial annotations only — do NOT copy ANY of them into the sketch. The output must contain NO numbers, letters, circles, arrows, labels or text anywhere._BLOCKING_SKETCH_SYSTEMuQ  The attached image is a TOP-DOWN diagram of one outdoor location seen from directly above: numbered circles mark fixed environment elements. If the diagram also contains lettered circles, those mark the figures present in this shot, and a camera icon with a view-cone shows the camera position and the direction it looks. Read it ONLY to understand which elements are in shot and their relative placement.
Now draw a ROUGH STORYBOARD COMPOSITION SKETCH of what THAT camera sees — a clean MONOCHROME pencil/marker line-art storyboard frame, NOT a finished render and NOT a top-down map. Draw it from the exact CAMERA ANGLE and viewpoint the FRAMING brief below describes (eye-level, a high or steep overhead angle, a low angle, etc.); do NOT default to a level eye-level view when the brief calls for a different angle.
FRAMING / SET GEOMETRY (source of truth for the camera angle, viewpoint, depth, left-right placement and which elements are in frame):
{brief}
If the framing brief states a figure is INSIDE a mapped area while the camera is OUTSIDE that area, keep the figure within that area as seen from the camera; if the area's boundary or opening is visible, draw it between the camera and the figure — do NOT relocate the figure outside its area.
If the brief states which way a structure's open or front side faces, draw that structure opening exactly that way relative to the camera — never mirror or flip its orientation.
Keep real-world human scale throughout: a doorway reads as adult height, a bench seat as knee height, a road as wider than a car. Size every structure against the mannequin figures — never shrink a structure into a miniature or toy-sized box.
Draw ONLY the environment elements the diagram and the brief list — do NOT add any structure or object that is not in them, and never draw a second copy of a listed structure.
BODY POSE AND ACTION of each figure (source of truth for posture, gesture and which way each body faces — draw the bodies EXACTLY like this, do NOT default to a plain standing pose):
{pose}
Draw every PERSON as a posed artist's wooden MANNEQUIN — a smooth featureless articulated mannequin (clear ball joints at shoulders, elbows, hips and knees; no clothing; no face) posed EXACTLY in the posture and action stated above, so the body POSE, stance and limb positions are unambiguous. Construct each mannequin's head with the LOOMIS METHOD (a sphere with the side plane sliced flat, a vertical centerline and a horizontal brow line) so the head's facing direction is explicit. Place each figure at its mapped screen position and depth. Draw environment elements as simple line shapes only.
When the sources conflict: the FRAMING brief governs camera angle, depth and left-right placement; the POSE lines govern each body's posture, action and orientation; the top-down diagram governs only relative set placement. This sketch fixes ONLY framing, placement, depth, viewpoint and body pose — it does NOT define identity, clothing, faces, materials, colours, architecture or surface finish, which come from the final character and background references, not this sketch; do NOT invent any of them.
The diagram's NUMBERED circles, LETTERED circles, the camera icon, view-cone and arrow are spatial annotations only — do NOT copy ANY of them into the sketch. The output must contain NO numbers, letters, circles, arrows, labels or text anywhere._POSED_SKETCH_SYSTEMc                    |r9|j                         r)t        j                  | xs d|j                               S t        j                  | xs d      S )u0  Stage C — 블로킹(or base) ref + 좌표 산술 camera brief (+선택 pose brief) → 카메라뷰
    스케치 프롬프트. pose_brief(자세/동작 SOT) 있으면 posed mannequin 프롬프트, 없으면
    (pose LLM 실패 degrade / 다른 소비) 기존 featureless 프롬프트(byte-identical).z
(no brief))briefpose)r-  )r   r+  formatr*  )camera_brief
pose_briefs     r    build_blocking_sketch_promptr2  +  sX     j&&(#**.,Z5E5E5G + I 	I"))0L)MMr"   c                J   | xs i j                  d      xs g }g }|D ]o  }t        |t              st        |j                  d      xs d      j	                         }t        |j                  d      xs d      j	                         j                         }|r|j                         dk(  s|dk(  rt        |j                  d      xs d      j	                         xs d}t        |j                  d	      xs d      j	                         }t        |j                  d
      xs d      j	                         }t        |j                  d      xs d      j	                         }	t        |j                  d      xs d      j	                         }
| d| g}|r$|j                         dk7  r|j                  |       |	r&|	j                         dvr|j                  d|	        |
r'|
j                         dk7  r|j                  d|
        |rS|j                         j                  d      r|t        d      d j	                         n|}|r|j                  d|        |j                  ddj                  |      z   dz          r |sydj                  |      S )u  structured pose brief(figures[])를 Stage C 스케치용 텍스트 블록으로 렌더(순수).

    자세 확정(body_posture != unknown) + confidence medium/high 인 figure 만 포함한다.
    하나도 없으면 None → step 이 fail-closed(가이드 미부착) 해서 generic standing 마네킹
    재발(무-가이드보다 나쁜 오염)을 막는다(Codex 가드). 슬롯/동작은 generic role·동작만
    (이름/의상/색/소품 고유명사는 스키마/프롬프트 단계에서 차단).r   body_posturer   
confidenceunknownlowslotza figurelimb_actionhead_body_orientationcontact_or_supportinteraction_target_rolez is none)r=  r   zresting on/against z	engaging zfacing NrQ  r]  rR  r^  )
r(   r)   r5   r   r   r  r   
startswithrn   re  )pose_resultfigsri  r8  postureconfr8  limborientsupporttargetrs  os                r    render_pose_brief_textrH  7  sC    2""9-3DE!T"aeeN+1r288:155&,"-335;;='--/Y6$%-155=&B'--/=:155'-2.446QUU239r:@@Baee017R8>>@QUU45;<BBD6gY'(DJJLF*LLw}}l:LL.wi89flln.LL9VH-.39<<>3L3L4s9~'--/ & wqc]+TDIIe,,s233 4 99Ur"   c                T    t        fd| j                  d      xs g D        d       S )Nc              3     K   | ]N  }|j                  d       k(  r8t        |j                  d            rt        |j                  d            r| P yw)r   r   r   Nr(   r   r?  s     r    rm   z#_camera_for_shot.<locals>.<genexpr>k  sK      	F/qEE,:-quuU|$1553C)D 
/s   AAr   )r_  r(   )r   r   s    `r    _camera_for_shotrL  g  s6     	FFJJy)/R/ 	F 		 r"   c                    t        t        |             D ]^  }t        |dz   t        |             D ]A  }| |   | |   }}t        |d   |d         t        kD  st        |d   |d         t        kD  s@  y ` y)u:  카메라 목록에 좌표가 사실상 동일하지 않은(non-identical geometry) 쌍이 하나라도
    있으면 True. _CAMERA_DEDUP_EPS 는 좌표 중복 제거용 기술 tolerance — '다른 앵글/
    연출'이라는 의미 단정이 아니다 (그 판단은 judge route). 순수 기하, 의미판정 0.r   r   r   TF)r  rn   r   r  )r   r   r   r   r   s        r    _cameras_non_identicalrN  r  sy     3w< q1uc'l+A1:wqzqAah%),==Qy\1Y<8;LL	 , ! r"   c                   t        | |      }t        |      }|dk(  rdn|dk(  rdnd}t        d |D              }d}t        | |      }||r|D 	cg c]  \  }}	t	        ||      d    }
}}	|
D cg c]
  }|dkD  s	| }
}|
rSt        |
      t        |
      }}||z
  xs d}|
D ch c]  }||z
  |z  d	k  rdn||z
  |z  d
k  rdnd  }}t        |      }||||dS c c}	}w c c}w c c}w )u   per-shot 구조 signal (순수 좌표) — judge 입력 + Stage B 트리거. 이름/토큰 누출 0.

    entity_count_bucket(none/single/multiple), depth_planes(figure 가 점유하는 카메라
    depth band 수 0-3), has_motion.r   r=  r   singlemultiplec              3  *   K   | ]  \  }}|d u  y wrv   r   )rk   r  r   s      r    rm   z*shot_complexity_signals.<locals>.<genexpr>  s     62Rt^r  r  r   r  r  r   )entity_count_bucketentity_countdepth_planes
has_motion)r  rn   ro   rL  r;  r   r   )r   r   r@  r.  bucketrV  planesr1  r   r  depthsr   r  r  r  bandss                   r    shot_complexity_signalsr[    s1    FJ/DD	AAvVQHJF666JFfj1Fd9=>vsA(63'*>#/Vq3w!V/Vc&k$D4K'CDJPRJPQ t8t+d2Q!"TT 1D 81aAJP  RZF% 	  ?/Rs   C1
C$<C$'#C)c                @    t        d  j                  d      xs g D              }i }|D ]D  \  }}t        ||      |v st         |      s"|j	                  |g       j                  ||f       F g }t        |      D ]  }t        ||   d       }t        |      dk  r# fd|D        D 	cg c]  }	|	|	 }
}	t        |
      sI|dk  rO|j                  |||d   d	||D ci c]  \  }}t        ||      t         |       c}}d
        |S c c}	w c c}}w )u  cross-shot continuity **결정론 후보** (좌표/구조만, 의미판정 0).

    member_keys(이 location 그룹의 selected 멤버)를 scene_index 로 묶어, 각 scene
    서브그룹이 (a) summary 존재 + valid 카메라 member 2+ (b) 카메라가 non-identical
    geometry 1쌍+ (좌표 중복 제외) (c) layout 에 공통 landmark 1+ 면 후보. 단일샷
    복잡도/의미는 판단하지 않는다 (judge route + signal). anchor = shot_index 최소 멤버.

    Returns: [{"scene_index", "member_keys":[(si,shi)..], "anchor_key":(si,shi),
    "cameras_non_identical":bool, "landmark_count":int, "per_shot":{hint_key: signals}}].
    c              3     K   | ]5  }|j                  d       xs g D cg c]  }t        |      s| c}rd 7 yc c}w wr   r   NrK  rk   r   r   s      r    rm   z0shared_model_candidate_groups.<locals>.<genexpr>  F      1bx(.B.@.!9Q<A.@ 	
1@   A==Ar   c                    | d   S r  r   r  s    r    rK  z/shared_model_candidate_groups.<locals>.<lambda>  r  r"   rL  r   c              3  <   K   | ]  \  }}t        |        y wrv   )rL  )rk   r  r]   r   s      r    rm   z0shared_model_candidate_groups.<locals>.<genexpr>  s     Pfa,VS9s   r   r   T)r   r  
anchor_keycameras_non_identicallandmark_countper_shot)
r-  r(   r!   rL  r   r   r   rn   rN  r[  )r   r  r  rf  r  r=   r]   r,   r$   rl   camsm_sim_shis   `            r    shared_model_candidate_groupsrk    sV     JJ{+1r1 N *,HCS!\16Fvs6SB'..Cy9  !#CX">:w<!PP "Pa= P "%d+A

"!!*%), $+#*KD% dE*,CFE,RR#*

 
	 * J#"s   /D&!Dc                @   |xs
 t               }t        d | j                  d      xs g D              }g }t        |      D ]  \  }}	||	f|v rt	        ||	      |vrt        | |	      s)t        | |	      }
|s"t        |
j                  d      xs d      dk  rY|
j                  d      dk(  xs= t        |
j                  d      xs d      d	k\  xs t        |
j                  d
            }|s|s|j                  |||	fg||	fd|dt	        ||	      |
id        |S )u9  단일 샷 복잡도 **후보**(좌표/구조 pre-filter — 의미판정은 LLM judge).

    사용자 결정(2026-07-01): 실외도 실내처럼 단일 복잡샷에 aerial→blocking→sketch 가이드를
    발동(복잡도/구도 복잡은 LLM 이 판단). cross-shot 그룹에 이미 든 샷(exclude_keys)은
    제외해 중복 발동을 막는다. pre-filter = valid 카메라 + summary 존재 + figure>=1 +
    구조 복잡 신호(multiple figures OR depth_planes>=2 OR has_motion). 최종 attach 여부는
    judge route(single_shot_complexity) + _evaluate 가 결정한다.

    ★broad=True (4a fix 2026-07-01, Codex 합의): is_complex 구조 게이트를 **제거**하고
    figure>=1 이면 모두 candidate 로 올린다(사용자 "한샷이라도 구도/복잡이면 무조건 마네킹"
    — 좁은 구조 prefilter 는 frame_spatial_contract 없는 복잡 구도샷[S15/S19]을 구조적으로
    누락). 복잡/구도 판정은 downstream shared-model judge 가 camera_direction/frame 구조
    필드로 결정(코드 free-text 파싱 0). is_complex 는 signal 로 계속 계산해 per_shot 에
    남기되 candidate 여부 게이트로는 쓰지 않는다. broad=False = 기존 게이트(byte-identical).

    cross-shot 후보와 동일 shape 로 반환(step 루프 공용) + is_single_shot=True.
    c              3     K   | ]5  }|j                  d       xs g D cg c]  }t        |      s| c}rd 7 yc c}w wr^  rK  r_  s      r    rm   z4single_shot_complexity_candidates.<locals>.<genexpr>  r`  ra  r   rT  r   r   rS  rQ  rU  r   rV  FT)r   r  rd  re  rf  is_single_shotrg  )
r'   r-  r(   r   r!   rL  r[  r   rQ   r   )r   r  r  exclude_keysbroadexcluderf  r,   r=   r]   sig
is_complexs               r    !single_shot_complexity_candidatesrt    sH   0 #ceG JJ{+1r1 N !#C+&C9S!5,%fc2 SWW^49:Q>GG)*j8 +377>*/a0A5+CGGL)* 	
 Z

I;s)%*,"&r3/5
 	) ': Jr"   c                    | j                  d      dk(  ryt        | j                  d      xs d      dk\  ry|rd|v ryy)	u   Stage B(블로킹) 권고 (순수) — 구조 신호(다수 figure or depth_planes≥2) OR judge
    reasons 에 figure 배치 연속성 플래그. False = 단순 구도 → base→C 직행(B 생략).rS  rQ  TrU  r   r   figure_placement_continuityF)r(   r   )signalsjudge_reasonss     r    shot_blocking_recommendedry    sH    
 {{()Z7
7;;~&+!,16-Gr"   r   r   )
upper_leftmiddle_left
lower_leftupper_rightmiddle_rightlower_rightc                <   i }| xs i j                  d      xs i j                  d      xs g }|D ]h  }t        j                  |j                  d      xs d      }|j                  d      }|s?|j                  d      t        k(  sX|s[||t        |      <   j |S )u   staging frame_spatial_contract → {entity_token(C##): "left"|"right"}.

    target_kind=character + target_id 있는 constraint 만 (deterministic 조인).rd   re   screen_zoner   	target_idrg   )r(   
_ZONE_SIDEri   r   )rp   r,   re   rl   r   rF  s         r    staged_sides_for_shotr  $  s     CMr&&'?@FBKKMZ`^`K~~aeeM28b9{#AEE-(,AAf#CF	 
 Jr"   c                f   |sg S t        fd| j                  d      xs g D        d      }|4t        |j                  d            rt        |j                  d            sg S g }| j                  d      xs g D ]  }|j                  d      }|r|j                  t        |            nd}|s5t        fd|j                  d	      xs g D        d      }|t        |j                  d            szt	        |d   |d   |d         }	|	d
k7  s|	|k7  s|j                  |||	d        |S )u   layout 카메라 기준 인물 좌/우가 staged screen_zone 과 다른 항목.

    조인 키 = figure.entity_token ↔ constraint.target_id (C##). 충돌은 caller
    가 LLM1 retry 신호 + diagnostic 으로 쓴다 (lateral SOT=staging).c              3  L   K   | ]  }|j                  d       k(  s|  ywr>  r   r?  s     r    rm   z(layout_side_conflicts.<locals>.<genexpr>>  r@  rA  r   Nr   r   r   rG  c              3  L   K   | ]  }|j                  d       k(  s|  ywr>  r   rC  s     r    rm   z(layout_side_conflicts.<locals>.<genexpr>J  rD  rA  r   r   )r   rG  staged_sidelayout_side)r_  r(   r   r   r   r   )
r   r   staged_sidesr1  	conflictsr   tokenexpectedr   actuals
    `        r    layout_side_conflictsr  2  s?    	UFJJy)/R/UF ~Yvzz%'896::V_K`Aa	&(Izz)$**'38<##CJ/dX,22X
	 IimmE.B$CVE]F9,=y?OPX&H"4( %'%	  +& r"   c                    i }t        | |      }|rt        |      |d<   t        |       t        |      z
  }|rt        |      |d<   |S )u   entity ID 불변 계약 — 신규 토큰 도입(W-C1 audit 재사용) + 기존 토큰 누락
    둘 다 위반. 비어있지 않으면 caller 는 revised 를 폐기하고 diagnostic 기록
    (비차단).
new_tokensmissing_tokens)r   r   r
   )originalrevisedr,   r  missings        r    revised_prompt_token_violationsr  ]  sS     !#C*8W=J":.L#H-0Eg0NNG &wJr"   c                   i }t        |       t        |      z  D ](  }i }i }i }t        |j                  |      ft        | j                  |      ffD ]  \  }}|s	|j                  d      xs i j	                         D ]X  \  }	}
t        |	      }	|
||	<   |||	<   |j                  d      xs i j                  t        |	            }|K||t        |	      <   Z  |s| j                  |      xs i }|j                  |      xs i }t        |      }|||j                  d      xs |j                  d      ||   |d||<   + |S )u  image-phase prompt override 의 **명시적 priority merge** —
    custom > zoom > site > original (Codex ⓒ).

    custom_prompt 는 소비자의 별도 분기가 이 merge 이전에 승리하고, original 은
    override 부재(빈 맵)다 — 이 함수는 zoom/site 두 단계만 (shot, variation_index)
    단위로 합친다: 같은 index 는 zoom 이 승리, site 는 빈 index 만 채운다.

    entry shape 는 zoom ``source_overrides`` 와 호환 (revised{int: str} /
    provenance{str: {...}} / group_id) + ``prompt_source`` /
    ``prompt_source_by_index`` stamp — override 적용 로그가 출처를 기록한다.
    둘 다 빈 맵이면 빈 맵 (소비자 no-op, default 경로 byte-identical).
    r  
provenancegroup_id)r  r  r  prompt_sourceprompt_source_by_index)r'   PROMPT_SOURCE_SITEr(   PROMPT_SOURCE_ZOOMr4   r   r   r   )zoom_overridessite_overridesmergedr   r  r  source_by_indexsource_labelentryvipromptprov
zoom_entry
site_entry	first_idxs                  r    merge_prompt_overridesr  n  s`     -/F>"S%88"$02
*,!3!3C!89!3!3C!89$
L% $yy39r@@B
FW$&2#		,/52::3r7C#*.Js2w' C$
 #'',2
#'',2
L	$"z2PjnnZ6P,Y7&5
s- 9: Mr"   c                    t         | ||dS )N)schema_versionr   prompt_overridesdiagnostics)r   )r   r  r  s      r    build_manifestr    s     ),"	 r"   c           
        g }t               }| j                  d      xs g D ]  }|j                  d      s|j                  d       d |j                  d      xs g D        D ]  \  }}t        |t              rt        |t              s(|j                  |j                  d       d||f       N||j                  |t                     vr)|j                  |j                  d       d| d| d	       |j                  t        ||               | j                  d
      xs i D ]  }||vs|j                  d| d        |S )u_   조인 무결성만 — 의미 게이트 없음. override key 는 멤버 shot 이어야 한다.r   r   zgroup without location_idc              3  2   K   | ]  }t        |        y wrv   )r   )rk   r.   s     r    rm   z)validate_site_manifest.<locals>.<genexpr>  s     F*EQa*Er   member_shotsz: invalid member shot z
: member Sr|   z not a selected shotr  zprompt_overrides[z] is not a group member shot)r'   r(   r   r)   r   r*   r!   )manifestr   r   r  r-   r=   r]   r   s           r    validate_site_manifestr    sO   
 JEK\\(#)r)uu]#9:F!%%*?*E2*EFGBb#&jc.B!!QUU=%9$::PRTVYQZP]"^_,**2su55!!uu]+,Jrd"SEAUV OOM"c23 G * ||./525k! 1#6RST 6 r"   )r   r   r   r   r  r   )r+   List[Dict[str, Any]]r  Set[str])r6   Dict[int, str]r7   r  r8   Dict[str, Dict[str, Any]]r  r  )r   Optional[int]rA   r  rB   Dict[str, Any]r  Optional[str])r   r  r   r  r7   r  r6   r  rR   r  rS   rQ   rT   r   rU   rQ   rV   rQ   rL   rQ   rW   r   rN   r  rD   rQ   r  Optional[Dict[str, Any]])r\   r  r  Set[ShotKey])rp   r  r  rQ   )r   Dict[int, Set[int]]r   zDict[ShotKey, List[str]]r   Dict[ShotKey, Dict[str, Any]]rx   r  r   r  r6   zOptional[Dict[int, str]]rs   rQ   r  zTTuple[Dict[str, List[ShotKey]], List[Dict[str, Any]], Dict[ShotKey, Dict[str, Any]]])r   r   r  rQ   )r   r   r  	List[str])r   r  r   Set[int]r  r  )r   r   r   r   r  r   )r   r   r   r   r   r   r  r   )r   Tuple[float, float]r   r  r  r   )
r   r   r   r   r   r   r   r   r  r   )r   r   r  r   )r   r  r  zTuple[Any, Any, float])r   r  r   r   r   r   r  r   )r   r   r   r   r   r   r  r   )r   r  r  r   )r   r  r  r  )r1  r  r  zDTuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]])r1  r  r   r   r  r  )r   r  r   r   r  r  )r   r  r  List[ShotKey]r  r  r  r  )r  r  r  r  )r  r   r  r  r  rQ   )r1  r  r  r   r   r  r  r   )r1  r  r   r  r  r   r  r  )r   r  r   r  r  r  )r  r   r  r   r  r   )rO  r  r  r   )r   r  r  zOptional[Tuple[float, float]])r   r   r   r  r  zTuple[int, str])r   r  r   r   r  z/List[Tuple[List[float], Optional[List[float]]]])r1  r  r   r  r  r   )r   r  r  r   )r   r  r1  r  r   r   r  r   rv   )r0  r   r1  r  r  r   )r?  r  r  r  )r   r  r   r   r  r  )r   r  r  rQ   )r   r  r   r   r  r  )r   r  r  r  r  r  r  r  )NF)r   r  r  r  r  r  ro  zOptional[Set[ShotKey]]rp  rQ   r  r  )rw  r  rx  zOptional[List[str]]r  rQ   )rp   r  r  Dict[str, str])r   r  r   r   r  r  r  r  )r  r   r  r   r  zDict[str, List[str]])r  r  r  r  r  r  )r   r  r  r  r  r  r  r  )r  r  r   r  r  r  )c__doc__
__future__r   loggingr   typingr   r   r   r   r   r	   2app.modules.pipeline.visual_continuity_anchor_planr
   r   	getLogger__name__loggerr   __annotations__r   r[   rj   ri   r   r   r  r  r   ShotKeyr!   r/   r?   rC   rX   ra   rr   r   r   r   r   r   r   r   r   r  ra  rb  rc  rd  r
  r  r  r$  r/  r9  r;  r~  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r
  r  r  r  r  r  r!  r)  r*  r+  r2  rH  rL  rN  r[  rk  rt  ry  r  r  r  r  r  r  r  r   r"   r    <module>r     s  8 #   8 8
 
		8	$  3 !% # 	5 	5 - * 
S/)
- - 	<  . +	
 <  44 4 	4
  .4 4 4 4 4 4 4 4 #4 4 4n&* ;?%*i&%i& )i& 3	i&
 i& i&  8i& #i& Zi&^6r..19..L =,A     02@ff(+ffX222 2 	2p( ((h(%(2@< NR||$2|EJ||D; 7  (7$ 6)6 & 6)6 & 6 <@CC+8CC` % % 3 3   5 HG

(+4
H$, S 2 V V$2 V@C V VNY  !R.Y c 0h 48	N	N#0	N	N%`(+
(+<... . 	.j ,0;;; ; )	;
 ; ;~ CG,?	& vGG
%%% !% 	%V".1.1. #.h
 
/
  
 	
% r"   