
    ƭjH                   2   U d Z ddlmZ ddlmZmZmZmZmZm	Z	m
Z
  eh d      Zded<   dZded	<   d
Zded<   dZded<   dZded<   dZded<    eeeh      Zded<    eddh      Zded<    eh d      Zded<   dZded<   ddddZd ed!<    G d" d#e      Zdd$Z	 	 	 	 dd%Zd&d'	 	 	 	 	 dd(Z	 	 	 	 	 	 dd)Zd&d*	 	 	 	 	 	 	 	 	 	 	 	 	 dd+Zdd,Z	 	 	 	 dd-Z 	 	 	 	 dd.Z!	 	 	 	 	 	 dd/Z"dd0Z#	 	 	 	 dd1Z$	 	 	 	 dd2Z%	 	 	 	 dd3Z&	 	 	 	 	 	 	 	 dd4Z'	 	 	 	 dd5Z(	 	 	 	 	 	 dd6Z)	 	 	 	 	 	 dd7Z*dd8Z+	 	 	 	 	 	 dd9Z,	 	 	 	 	 	 dd:Z-	 	 	 	 dd;Z.	 	 	 	 dd<Z/d&d&d=	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd>Z0	 	 	 	 	 	 dd?Z1	 	 	 	 	 	 	 	 dd@Z2	 	 	 	 	 	 ddAZ3dBd
dCdDZ4d edE<   dFZ5dedG<   	 	 	 	 	 	 	 	 ddHZ6	 	 	 	 	 	 	 	 ddIZ7	 	 	 	 	 	 	 	 ddJZ8ddKZ9dLZ:dMedN<    edOdPh      Z;dedQ<    edRdSh      Z<dTedU<    edVdWh      Z=dTedX<   e:f	 	 	 	 	 	 	 ddYZ>e:f	 	 	 	 	 	 	 ddZZ?	 	 	 	 	 	 	 	 dd[Z@d&d&e:d\	 	 	 	 	 	 	 	 	 	 	 dd]ZAd^ZBded_<   	 	 	 	 dd`ZCdaZDdedb<   d
Zded<   dcdcdddddeZEdfedg<   	 	 	 	 ddhZFdiZGdedj<   dkZHdedl<   dmZIdedn<   dcZJdedo<   	 	 	 	 	 	 ddpZK	 	 	 	 	 	 ddqZL	 	 	 	 	 	 	 	 ddrZMdsZNdedt<   	 	 	 	 	 	 dduZO	 	 	 	 	 	 	 	 	 	 	 	 ddvZPd&dw	 	 	 	 	 	 	 	 	 ddxZQ e       dy	 	 	 	 	 	 	 ddzZR	 	 	 	 	 	 	 	 dd{ZSd|ZT	 	 	 	 dd}ZUd&d&d&d&d&d~	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZVy&)uC  W20B: shot-aware background render plan (LLM planner contract).

Per-fp dwelling-scoped reference graph DAG + per-bg camera + reference
decisions, all LLM-emitted, code-validated. **No image / VLM / DB /
ImageAsset writes here.** The module exposes pure helpers; LLM calls
are dependency-injected via an ``llm_provider`` callable so default
production paths run with zero external API calls.

Inputs (consumed from upstream checkpoints, all exact-ID joined):
  - base_location_dossier (W20A)            — dwelling identity + per-bg facts.
  - floor_plan_geometry_readback (W20A2)    — readback status + cell candidates.
  - floor_plan_overlay_payload (W19B-1)     — already folded into the dossier;
                                              kept available for sanity.
  - background_master_plan                  — bg DAG + applies_to_shots.
  - shot_staging                            — committed shot staging payload.

Output (one entry per fp_id with ≥1 renderable bg):
  - dwelling-scoped graph DAG (ordered nodes).
  - per-bg ``reference_decision`` (LLM mode + selected_refs +
    physical_space_id_per_ref + ...).
  - per-bg ``camera_decision`` (LLM cell + lens picks, validated
    against the W20A2 geometry candidate sets).
  - validators block enumerating which contracts passed/failed +
    structured blockers.
  - real_api_call_counts (image / llm / vlm) — always 0 unless an
    llm_provider is wired AND invoked.

W20 boundaries enforced in this module:
  - exact-ID only — every join uses exact integer / string equality.
  - no semantic label / description parsing.
  - no deterministic top-K reference selection (LLM-owned).
  - no same-space judgement by code (LLM emits
    ``physical_space_id_per_ref``; code only checks set cardinality).
  - max_refs_per_bg = 2.
  - exactly one ``is_dwelling_identity_anchor = True`` per fp_id.
  - synthetic readback never promotes to ``production_ready = True``.
  - shot readiness is a structural gate; **no readiness LLM
    classifier** — if the staging cp does not carry the required
    fields, fail-closed with a precise blocker.
    )annotations)AnyCallableDict	FrozenSetListOptionalTuple>   fp_seeded_anchorreference_derivedrelated_style_new_spacesame_physical_space_viewtwo_refs_distinct_spacesFrozenSet[str]ALLOWED_REF_MODESr   strTWO_REFS_MODE   intMAX_REFS_PER_BGdistinct_visible_spacesTWO_REFS_DEDUP_TOKENrender_new_plateRENDER_ACTION_NEWreuse_existing_plateRENDER_ACTION_REUSEALLOWED_RENDER_ACTIONSr   r   REUSE_ELIGIBLE_MODES>   widenormal	telephotoALLOWED_LENS_ENUMS)visible_space_directivecamera_framing_directivesubject_position_directivestate_cue_directivenegative_continuity_directivezTuple[str, ...]RENDER_GUIDANCE_FIELDSZ   2      )r   r    r!   zDict[str, int]_FALLBACK_LENS_FOV_MAPc                      e Zd ZdZy)ShotAwareBgRenderPlanErrorz+Fail-closed signal for the planner builder.N)__name__
__module____qualname____doc__     d/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pipeline/shot_aware_bg_render_plan.pyr.   r.   j   s    5r4   r.   c                F    t        | xs i j                  d      xs i       S )Ndossiers)dictget)dossier_cp_datas    r5   _index_dossiersr;   s   s"    &B++J7=2>>r4   c                    i }| xs i j                  d      xs i }|j                         D ]   \  }}t        |t              sd|v s|||<   " |S )Nper_fpgeometry)r9   items
isinstancer8   )geometry_cp_dataoutr=   fp_identrys        r5   _index_geometryrE   w   sX     &(C$"))(39rFueT"zU':CJ ' Jr4   Ndiagnosticsc          	     ,   i }d}d}g }g }g }t        | xs i j                  d      xs g       D ]  \  }}	t        |	t              s|j	                  d| d       -|	j                  d      xs |	j                  d      }
t        |
t
              r
|
r|
}|dz  }n|	j                  d      }|	j                  d	      }t        |t              r>t        |t              s.t        |t              rt        |t              sd
| d| }|dz  }n|j	                  d| d|d|       ||v r|j	                  |       |	||<    |||d<   ||d<   ||d<   ||d<   ||d<   |S |s|s|r-ddl}|j                  t              j                  d|||||       |S )u  Build ``shot_id → staging entry`` exact-ID map.

    W20F1 — shot_staging cp 가 ``shot_id`` / ``id`` 를 carry 하지 않고
    ``scene_index`` + ``shot_index`` 정수만 가지는 합법 입력 경로를
    지원한다. fallback 합성 ID 형식 = ``f"S{scene_index}_Shot{shot_index}"``
    (shot_extract / scene_detail violation message 의 명명과 동일,
    Codex 2026-05-28 confirmed). entry 가 명시 ``shot_id`` / ``id`` 를
    들고 있으면 그 값을 그대로 우선 사용. bool 은 int subclass 라
    ``isinstance(x, bool)`` 별도 reject.

    결손/타입불량 entry 는 silent drop 금지 — ``diagnostics`` dict 가
    주어졌으면 카운터에 누적, 없으면 logger.warning 으로 surface 한다.
    r   shotsz<non-dict-at->shot_idid   scene_index
shot_indexS_Shotzraw_idx=z scene_index=z shot_index=Nshot_staging_index_explicit_id!shot_staging_index_synthesized_id shot_staging_index_dropped_no_id)shot_staging_index_dropped_bad_index_typeshot_staging_index_collisionsz]_index_shot_staging: explicit=%d synth=%d dropped_no_id=%r dropped_bad_index=%r collisions=%r)	enumerater9   r@   r8   appendr   r   boollogging	getLoggerr/   warning)shot_staging_cp_datarG   rB   counter_explicit_idcounter_synth_idcounter_dropped_no_id_no_indexcounter_dropped_bad_index_typecounter_collisionraw_idxrD   explicit_sidsidrN   rO   _loggings                  r5   _index_shot_stagingrg      s   $ &(C02"02"#%#%9%?R$D$DW$M$SQST%&*11M'!2LMyy+>uyylC(\C1$))M2K<0J;,ZT5Rz3/
:t8T+eJ<8 A% .55wi}[O D"",1 #:$$S)C5 U6 8K45;K78:X67Ca?@7H34 J *-KO`&x(005# ..! Jr4   c                   g }g }g }| s|j                  d       d|||fS | D ];  }|j                  |      }||j                  |       (|j                  ||d       = |s|j                  d|d       d|||fS d|||fS )u  Structural readiness gate — code only verifies presence of the
    bg's consuming shot_ids in the staging cp. NO LLM classifier.

    W20E6-B contract: ``shot_staging`` is the staged/selected subset of
    the project's shots, not the full ``applies_to_shots`` universe. A
    BG with ANY staged shot is renderable; missing-from-staging shots
    are surfaced as ``omitted_unstaged_shots`` for diagnostic context,
    **not** as structural blockers. Only the empty / fully-unstaged
    cases carry structural blockers (the BG cannot be planned without
    at least one staged consuming shot).

    Returns ``(ok, blockers, surfaced_shot_entries, omitted_unstaged_shots)``.
    zbg has empty applies_to_shotsF)rK   stagingz9bg has no staged consuming shots (omitted_unstaged_shots=)T)rX   r9   )applies_to_shotsstaging_indexblockerssurfacedomittedre   rD   s          r5   _consuming_shot_readinessrp      s      H%'HG78h'11!!#&=NN3 	 	
   
 	''.k4	
 h'118W,,r4   )shot_staging_diagnosticsc                l   t        |       }t        |      }t        ||      }i }	|j                         D ]s  \  }
}|
|vr||
   }|j	                  d      xs i }|j	                  d      xs" |j	                  di       j	                  dd      }|j	                  d      xs i }i }g }d}g }|j                         D ]p  \  }t        |j	                  d	      xs g       }t        ||
      \  }}}}|||||d|<   |rd}|j                         W|j                  fd|D               r |xs i j	                  d      xs i j                         D ci c],  \  }}t        |t              r|j	                  d      |
k(  r||. }}}|
||||||t        |      dg |d|	|
<   v |	S c c}}w )a  Build per-fp planner input bundles.

    Each fp_id present in the dossier checkpoint and the geometry
    checkpoint produces one bundle:

        {
          "fp_id": str,
          "dossier": <dossier dict>,
          "geometry": <geometry dict>,
          "readback_status": str,
          "shot_readiness": {
              "ok": bool,
              "blockers": [str],
              "per_bg": {
                  bg_id: {
                      "applies_to_shots": [str],
                      "surfaced_shots": [{"shot_id": str, "staging": {...}}],
                      "ok": bool,
                      "blockers": [str],
                  }
              }
          },
          "candidate_catalog": [],   # W20B: empty seed catalog; future wave may grow.
          "raw_overlay": <overlay payload subset for this fp>,
        }

    fp_ids missing from either dossier or geometry are not surfaced
    (the planner step turns that into a fp-level skip + diagnostic).
    rF   r>   readback_statusreadbackstatusunknownper_bg_render_facts_by_bg_idFrk   )rk   rl   )rk   surfaced_shotsomitted_unstaged_shotsokrm   Tc              3  .   K   | ]  }d d|   yw)bg_id=: Nr3   ).0bbg_ids     r5   	<genexpr>z*assemble_planner_inputs.<locals>.<genexpr>N  s"      #5@fUIRs+[s   overlaysrC   )rz   rm   per_bgrenderable_bg_ids)rC   dossierr>   rs   shot_readinesscandidate_catalograw_overlay)r;   rE   rg   r?   r9   listrp   rX   extendr@   r8   sorted)r:   rA   overlay_cp_datamaster_plan_cp_datar]   rq   r7   
geometriesrl   rB   rC   r   
geom_entryr>   rs   per_bg_factsper_bg_readinessfp_blockersany_okrenderable_bg_ids_listfactsappliesrz   bg_blockersrn   ro   r   payloadoverlay_subsets                             `  r5   assemble_planner_inputsr      s#   L /H !12J'*BM &(C"..*w
"&
>>*-3NN,- G~~j"-11(IF 	 KK67=2 	 79!#,.(..0LE5599%78>B?G1J!(2.BXw %,"* +2''U# &--e4 "" #5@# ) 16 !&B++J7=2eg#
#w '4(W[[-AU-J	 7N# 	 
  .'* &,,B%C "$)
E
c +D J3
s   1F0c                    t        | t              rt        |       dk7  ry| D ]$  }t        |t              st        |t              r$ y y)z;``[row, col]`` shape with two non-negative ints (no bools).r   FT)r@   r   lenrY   r   )valuevs     r5   _is_int_pair_listr   s  s>    eT"c%jAoajC&8  r4   c           
        g }t        | t              r| s|j                  d       d|fS t        |       D ]  \  }}d| d}t        |t              s|j                  | d       2|j                  d      }t        |t              r|s|j                  | d       |j                  d      }t        |t              st        |t              r|d	k  r|j                  | d
       |j                  d      }t        |t              r|s|j                  | d       |j                  d      }t        |t              s|j                  | d       |j                  d      }	t        |	t              s|j                  | d       |j                  d      }
t        |
t              s|j                  | d       n|
j                  d      }t        |t              s|j                  | d       nt        |      D ]  \  }}| d| d}t        |t              s|j                  | d       3t        |j                  d      t              r|j                  d      s|j                  | d       t        |j                  d      t              r|j                  d      r|j                  | d        |
j                  d      }t        |t              s|j                  | d       dD ]9  }t        |
j                  |      t              r#|j                  | d| d       ; t        |
j                  d      t              s|j                  | d        |
j                  d!      }t        |t              s|j                  | d"       n=t        |      D ]/  \  }}t        |t              r|r|j                  | d#| d$       1 |j                  d%      }t        |t              s|j                  | d&       n
d'D ]K  }|j                  |      }t        |t              st        |t              r5|j                  | d(| d)       M d*D ]4  }t        |j                  |            r|j                  | d(| d+       6 |j                  d,      }t        |t              r|s|j                  | d-       |j                  d.      }t        |t              st        |t              r|d	k  r|j                  | d/       |j                  d0      }t        |t              s|j                  | d1       t        t        |j                               t        t              z
        }|r&|j                  | d2| d3t        t                      t        D ]K  }|j                  |      }t        |t              r|j                         r5|j                  | d4| d5       M  | |fS )6u  Structural / type-only schema-lite check.

    The W20B prompt pack carries a JSON Schema, but the production
    validator must also fail-closed on missing / wrong-typed fields so
    a malformed LLM output cannot quietly default through the other
    validators. **No semantic parsing** — code checks only that the
    keys exist and the types match. Allowed types mirror the
    ``schema.json`` shape exactly.
    z$graph.nodes must be a non-empty listFzgraph.nodes[]z not a dictr   z.bg_id must be non-empty string
node_indexr   z$.node_index must be non-negative intmodez.mode must be non-empty stringis_dwelling_identity_anchorz).is_dwelling_identity_anchor must be bool	rationalez.rationale must be stringreference_decisionz .reference_decision must be dictselected_refsz..reference_decision.selected_refs must be listz".reference_decision.selected_refs[	ref_bg_idz#.ref_bg_id must be non-empty stringphysical_space_idz+.physical_space_id must be non-empty stringrejected_refsz..reference_decision.rejected_refs must be list)"same_physical_space_dedup_decisionwhy_single_ref_or_two_refslow_delta_reuse_target_bg_idlow_delta_rationalez.reference_decision.z must be string'same_physical_space_low_delta_candidatezH.reference_decision.same_physical_space_low_delta_candidate must be boolphysical_space_id_per_refz:.reference_decision.physical_space_id_per_ref must be listz..reference_decision.physical_space_id_per_ref[z] must be non-empty stringcamera_decisionz.camera_decision must be dict)camera_unitlook_at_unitz.camera_decision.z must be int)camera_celllook_at_cellz must be [int, int]	lens_enumz).camera_decision.lens_enum must be stringfov_degz-.camera_decision.fov_deg must be positive intrender_guidancez.render_guidance must be dictz".render_guidance has unknown keys z
; allowed=z.render_guidance.z must be non-empty string)r@   r   rX   rW   r8   r9   r   rY   r   r   r   setkeysr(   strip)nodesrm   idxnodeprefixr   nir   	is_anchorr   ref_decsels_idxrD   s_prefixrejfnamepsip_idxpcamr   lensfovrg
extra_keysr   s                              r5   _validate_required_shaper   }  s    HeT"%>?hu%	TuA&$%OOvhk23!%%UOOvh&EFGXXl#b$z"c':b1fOOvh&JKLxx$$DOOvh&DEFHH:;	)T*OO(CD HH[)	)S)OOvh&?@A((/0'4(OOvh&FGH++o.Cc4(hLM %.cNLE5"()KE7RSTH%eT2 8*K(@A %eii&<cB%))T_J` 'j(KL &eii0C&DcJRWR[R[\oRp 'j(ST %3 ++o.Cc4(hLM "'++e"4c:OO!("6ug_M EF h K L ++9:Cc4(h # $
 !*#HE1%a-Q %h '99> @78 !/ hh()#t$OOvh&CDE8GGENa&jC.@OO!("3E7,G 9 9(8OO!("3E7 ;% & 9 77;'DdC(hGH '')$C#t$JsC,@C1HhKL XX'("d#OOvh&CDEBGGI5K1L LMJh@!l*T2H-I,JL 0u!%-U[[]OO!("3E7 ;+ , 0u &B L8##r4   c           	     j   g }t               }t        |       D ]  \  }}|j                  d      }t        |t              st        |t
              s|j                  d| d       M||k7  r|j                  d| d| d|        ||v r|j                  d| d       |j                  |        | |fS )z<``node_index`` must equal the entry's position in ``nodes``.r   node[u7   ].node_index missing or non-int — order check skippedz].node_index=z" does not equal its list position znode_index=z  appears more than once in graph)r   rW   r9   r@   rY   r   rX   add)r   rm   seenr   r   r   s         r5   _validate_node_index_orderr     s     HDu%	TXXl#b$z"c':OOuST 9OOuM" .5" :OObT!AB 	! &" L8##r4   c                8   g }g }| D ]8  }|j                  d      }t        |t              s%|s(|j                  |       : t	        |      }t        ||z
        }t        ||z
        }|r|j                  d| d       |r|j                  d| d       | |fS )a  Exact-string set equality between graph node bg_ids and the
    fp's **renderable** bg_ids (W20E6-B).

    Renderable = the BG has at least one staged consuming shot in
    ``shot_staging``. Non-renderable BGs (no staged shots) are
    deliberately excluded from the LLM-emitted graph; this validator
    enforces that contract -- any missing renderable bg_id, or any
    emitted bg_id outside the renderable set, is a hard fail.
    r   z.graph completeness: missing renderable bg_ids zO (dwelling-scoped graph must include every staged-shot renderable BG of the fp)z!graph completeness: extra bg_ids zF (graph emitted bg_ids that are not in the staged-shot renderable set))r9   r@   r   rX   r   r   )	r   r   rm   r   r   r   seen_setmissingextras	            r5   _validate_graph_completenessr   ,  s     HD!eS!eKK  4yH&12G8//0E<WI F' (	

 /w 7 	

 L8##r4   c           	        g }i }t        |       D ]'  \  }}|j                  d      }t        |t              s|j	                  d| d       >||v r|j	                  d|d       X|||<   |j                  d      xs i }|j                  d      xs g D ]  }|j                  d      }t        |t              s|j	                  d|d	       :||k(  r|j	                  d|d
       U|j                  |      }	|	|j	                  d|d|d       |	|k\  s|j	                  d|d|d        * | |fS )z5No cycles, every parent appears earlier in the order.r   r   z].bg_id missing or non-stringr|   z appears twice in graphr   r   r   z ref entry missing ref_bg_idz references itself (self-loop)z references z< which has not appeared earlier in the graph (DAG violation)z2 that is not earlier in walk order (DAG violation))rW   r9   r@   r   rX   )
r   rm   bg_seenr   r   r   r   refref_bg
parent_idxs
             r5   _validate_dagr   P  s|   H Gu%	T!%%OOeC5(EFGGOOfUI-DEF((/06B;;/525CWW[)Ffc*UI%AB UI%CD  V,J!UI\& <H I s"UI\& <@ A' 6 &B L8##r4   c           	        g }| D ]u  }|j                  d      xs i }|j                  d      xs g }t        |      t        kD  s@|j                  d|j                  d      dt        |       dt                w | |fS )Nr   r   r|   r   z selected_refs count z > )r9   r   r   rX   )r   rm   r   r   selecteds        r5   _validate_max_refs_per_bgr   x  s     H((/06B;;/52x=?*OO'*--Bx=/_$57	  L8##r4   c                   g }| D ]b  }|j                  d      }|j                  d      }|j                  d      xs i }|j                  d      }|j                  d      }t        |t              rt        |      ng }t        |t              rt        |      ng }	t        |t              rTt        |t              rDt        |	      t        |      k7  r-|j	                  d|dt        |	       dt        |       d	       t        t        ||	            D ]r  \  }
\  }}t        |t              s|j                  d
      }t        |t              s<t        |t              sM||k7  sS|j	                  d|d|
 d|d|
 d|
       t |t        k(  r7t        |      dk7  rf|j	                  d|dt        dt        |       d	       n=t        |      dk(  r/t        |t              r|j	                  d|d|dt        d	       t        |      dk(  s|D cg c]%  }t        |t              r|j                  d      nd' }}t        d |D              r&|d   |d   k(  r|j	                  d|d|d   d       t        |	      dk(  rt        d |	D              s|j	                  d|d|	d	       n/t        t        |	            dk7  r|j	                  d|d|	d	       |j                  d      }|t        k7  r|j	                  d|dt        d |d	       |j                  d!      }t        |t              r|j                         rN|j	                  d|d"       e | |fS c c}w )#u  Reference-decision consistency.

    Codex W20B narrow patch #3:
      - ``physical_space_id_per_ref`` length must equal
        ``selected_refs`` length for ALL counts (0 / 1 / 2).
      - ``selected_refs[i].physical_space_id`` must equal
        ``physical_space_id_per_ref[i]`` exact-string.
      - ``mode == 'two_refs_distinct_spaces'`` ⇔ ``len(selected_refs) == 2``.
      - When 2 refs:
        * the two ``ref_bg_id`` values are exact-string distinct;
        * the two ``physical_space_id_per_ref`` values are
          exact-string distinct;
        * ``same_physical_space_dedup_decision`` = ``distinct_visible_spaces``;
        * ``why_single_ref_or_two_refs`` is non-empty.

    Code never lexically inspects descriptions; only exact-string
    set / equality checks.
    r   r   r   r   r   r|   z# physical_space_id_per_ref length (z) != selected_refs length (rj   r   z selected_refs[z].physical_space_id=z* does not equal physical_space_id_per_ref[z]=r    mode=z' requires exactly 2 selected_refs (got z has 2 selected_refs but mode=z
 (must be r   Nc              3  D   K   | ]  }t        |t              xr |  y wNr@   r   )r~   rs     r5   r   z5_validate_two_refs_distinct_spaces.<locals>.<genexpr>  s      @Z:a%+!+Z    r   rM   z# two selected_refs share ref_bg_id=z  (must be exact-string distinct)c              3  D   K   | ]  }t        |t              xr |  y wr   r   )r~   ss     r5   r   z5_validate_two_refs_distinct_spaces.<locals>.<genexpr>  s      C(Q
1c*0q0(r   zR two-ref mode requires exactly 2 non-empty physical_space_id_per_ref entries (got zF physical_space_id_per_ref entries are not exact-string distinct (got r   z: two-ref mode requires same_physical_space_dedup_decision=z (got r   z; two-ref mode requires non-empty why_single_ref_or_two_refs)r9   r@   r   r   rX   rW   zipr8   r   r   allr   r   r   )r   rm   r   r   r   r   r   r   sel_listpsi_listi	sel_entrypsi_idsel_psie
ref_bg_idsdedupwhys                     r5   "_validate_two_refs_distinct_spacesr     s   * H!xx((/06B;;/kk56
 &0$%?4>R *3 5492 h%*S$*?8}H-UI &H 'Ha) '0Hh0G&H"A"	6i.mm$78G'3'Jvs,Cf$OO 	 <--4K 8556Cr&E 'I = 8}!UIVM+< =447M?!E ]aJtS$9OO	!?x H),A/ x=A "!A '1D&9k"tC!   @Z@@a=JqM1OO 	 *%%/]$55UW H"C(CCUI &$<q*
 S]#q(UI &::BQH
 KK DEE,,UI &:+.fUIQ@
 ++:;Cc3'syy{UI &1 2m t L8##Os   *M0c           	        g }| j                  d      xs g }i }|D ]  }t        |t              s|j                  d      }|j                  d      }t        |t              rt        |t              sW|j                  |      }||||<   p||k7  sv|j                  d|d| d|         |st        t              |fS ||fS )uA  Build a ``lens_enum → fov_deg`` map from the geometry checkpoint.

    Source-of-truth is ``geometry.view_cone_records`` (W20A2 carries
    one record per ``(direction, lens)`` pair). When a lens appears
    in multiple records with **disagreeing** fov_deg, that is a
    hard fail-closed signal — geometry shape is broken upstream.

    When ``view_cone_records`` is missing or empty (e.g. fewer than
    two units observed), the fallback ``_FALLBACK_LENS_FOV_MAP`` is
    returned with a diagnostic so the consumer knows the map is a
    default, not an observation.
    view_cone_recordsr   r   z@geometry.view_cone_records carries conflicting fov_deg for lens=r}   z vs )r9   r@   r8   r   r   rX   r,   )r>   rm   recordsby_lensr   r   r   priors           r5   _derive_lens_fov_mapr     s     Hll./52G G!T"uu[!eeI$$JsC,@D!=GDMc\OO$$(82eWD?   *+X55Hr4   c           	     r   g }|D ]  }|j                  d      }t        |t              r||vr|j                  d|d|        |j                  d      xs i }|j                  d      xs g D ]A  }|j                  d      }t        |t              s%||vs*|j                  d|d|d       C  | |fS )	Nr   r|   z' in graph is not a same-fp bg of fp_id=r   r   r   z references ref_bg_id=z outside same-fp set)r9   r@   r   rX   )	rC   r   same_fp_bg_idsrm   r   r   r   r   r   s	            r5   _validate_same_fp_onlyr  #  s     H!eS!e>&AOO	 "	# ((/06B;;/525CWW[)F&#&6+GUI%;F: F* + 6  L8##r4   c                T   g }| D cg c]  }|j                  d      s| }}t        |      dk7  r|j                  dt        |       d       nO| r| d   nd }|d   |ur|j                  d       |d   j                  d      }|dk7  r|j                  d	|d       | |fS c c}w )
Nr   rM   z.graph requires exactly one anchor node (found rj   r   z6anchor node must be the first node in graph walk orderr   r   z*anchor mode must be fp_seeded_anchor (got )r9   r   rX   )r   rm   nanchors
first_nodeanchor_modes         r5   _validate_anchor_exactly_oner  <  s     HH%Q155)F#Gq%GH
7|q'l^1&	

 "'U1XD
1:Z'OOH ajnnV,,,OO#a) L8##% Is
   B%B%c                ^   |dg fS g }| D cg c]  }|j                  d      s| }}t        |      dk7  rdg fS |d   j                  d      }t        |t              r|sdg fS |s|j	                  d       d|fS ||vr%|j	                  d|d	t        |      d
       d|fS d|fS c c}w )u  W20E7-C: anchor BG must be drawn from the dossier's clean anchor
    candidate surface (``anchor_selection_metadata.candidate_bg_ids`` —
    every BG in that list satisfies ``clean_background_expected=True``).

    Caller contract:
      - ``clean_anchor_candidate_bg_ids is None``: validator is a no-op
        (back-compat for unit-test call sites pre-dating the W20E7-C
        wiring). Production paths NEVER pass ``None`` — they always
        derive the set from the dossier.
      - ``clean_anchor_candidate_bg_ids == frozenset()``: empty surface
        is a hard fail (no clean anchor candidate exists for this fp;
        the LLM cannot legitimately pick an anchor).
      - non-empty set: the anchor's ``bg_id`` MUST be an exact-string
        member of the set.

    Cross-validator interaction: when
    ``_validate_anchor_exactly_one`` already fails (zero / multiple
    anchors, or anchor not at index 0), this validator no-ops so the
    operator sees the primary anchor shape error without a redundant
    diagnostic chain.
    Tr   rM   r   r   zclean anchor candidate set is empty -- dossier surfaced no clean_background_expected=True bg_ids for this fp; the LLM cannot pick an anchor without a clean candidate.Fzanchor bg_id=z* is not in the clean anchor candidate set z; the anchor MUST be drawn from the dossier's anchor_selection_metadata.candidate_bg_ids subset (clean_background_expected=True only).)r9   r   r@   r   rX   r   )r   clean_anchor_candidate_bg_idsrm   r  r  anchor_bg_ids         r5   '_validate_anchor_in_clean_candidate_setr  U  s    4 %,RxHH%Q155)F#Gq%GH
7|qRx1:>>'*LlC(Rx(?	

 h88L+ ,#$ABE F56	
 h>1 Is
   B*B*c                   g }|j                  d      xs i }|j                  d      xs i }t        |      \  }}|j                  |       | D ]t  }|j                  d      xs i }|j                  d      }	|j                  d      }
|j                  d      }|j                  d      }|j                  d	      }|j                  d
      }t        |
t              st        |
t
              s|j                  d|	d       t        |
      }||vr|j                  d|	d|
 d       ||   xs g D cg c]  }t        |       }}t        |t              rt        |      dk(  rt        |      |v s|j                  d|	d|d| d       |j                  |      xs g D cg c]  }t        |       }}t        |t              rt        |      dk(  rt        |      |v s|j                  d|	d|d| d       |t        vr)|j                  d|	d|dt        t                      |j                  |      }||j                  d|	d|d       t        |t
              rt        |t              r |j                  d|	d|d| d|d	       Q||k7  sX|j                  d|	d| d| d|       w | |fS c c}w c c}w )Ncamera_cell_candidates_per_unit look_at_cell_candidates_per_unit)r>   r   r   r   r   r   r   r   r|   z$ camera_decision.camera_unit not intz camera_unit=z' not in camera_cell_candidates_per_unitr   z camera_cell=z( not in camera_cell_candidates_per_unit[r   z look_at_cell=z) not in look_at_cell_candidates_per_unit[z lens_enum= not in z= has no fov mapping in geometry.view_cone_records or fallbackz	 fov_deg=z not int (expected z
 for lens=rj   z does not match expected z for lens_enum=)r9   r   r   r@   rY   r   rX   r   tupler   r   r"   r   )r   r>   rm   	cam_cands
look_candslens_fov_maplens_map_blockersr   r   r   cam_unitcam_cell	look_cellr   r   unit_keycvalid_cam_cellsvalid_look_cellsexpected_fovs                       r5   _validate_camera_in_candidatesr    s+   
 H>?E2I@AGRJ&:H&M#L#OO%&hh()/R!77=)77=)GGN+	GGK(	'')$h%Z#-FOO	!EF x=9$OO	xj 92 3 -6x-@-FB-FG-F58-FGx&H"h?2OO	xl ;33;*A? /9nnX.F.L".LM.LE!H.LMy$'I!#i $44OO		} =44<:Q@ ..OO	YM,-.0
 (++I6L#UI[ >L M
 7C(GT1JUIYwk :!!-jQH L(UIYwi 8  ,~_YMK{ B L8##Y H Ns   7K(Kc                j    t        | t              xr" t        |       dk(  xr t        d | D              S )uH   A grid cell is a ``[int, int]`` pair (bool excluded — bool is an int).r   c              3  b   K   | ]'  }t        |t              xr t        |t                ) y wr   r@   r   rY   r~   r   s     r5   r   z_is_int_cell.<locals>.<genexpr>  s*     M1
1c">:a+>'>>   -/)r@   r   r   r   )cells    r5   _is_int_cellr%    s8     	4 	NIN	NMMMr4   c                    t        |       sy|xs g D cg c]B  }t        |t        t        f      r*t	        |      dk(  rt        d |D              r
|d   |d   fD }}|sy| d   | d   f|v ryt        |fd      }|d   |d   gS c c}w )u  Nearest valid candidate to ``cell`` (Manhattan distance, stable
    tie-break), or ``None`` when no snap should happen.

    Returns ``None`` (no repair — left for the validator to fail-fast) when:
      - ``cell`` is not a ``[int, int]`` pair (malformed),
      - the candidate set is empty,
      - ``cell`` is already a valid candidate (no-op).

    Tie-break is ``(distance, row, col)`` so the choice is deterministic.
    Nr   c              3  b   K   | ]'  }t        |t              xr t        |t                ) y wr   r!  r"  s     r5   r   z(_snap_cell_to_nearest.<locals>.<genexpr>  s*     J1
1c">:a+>'>>r#  r   rM   c                h    t        | d   d   z
        t        | d   d   z
        z   | d   | d   fS )Nr   rM   )abs)candcell_ts    r5   <lambda>z'_snap_cell_to_nearest.<locals>.<lambda>  sB    Q&)#$s47VAY+>'??GG
r4   key)r%  r@   r   r  r   r   min)r$  
candidatesr  validbestr+  s        @r5   _snap_cell_to_nearestr3    s      """Aa$'FaKJJJ	 
1qt" 
  1gtAwF
D GT!W)s   ABc                   |j                  d      xs i }|j                  d      xs i }g }g }| D ]  }t        |      }t        |j                  d      xs i       }|j                  d      }	t        |	t              rt        |	t              st        |	      }
d|fd|ffD ]  \  }}|j                  |      }t        ||j                  |
            }|5|j                  |j                  d      ||	|d   |d	   g|t        |d   |d   z
        t        |d	   |d	   z
        z   d
       |||<    ||d<   |j                  |        ||fS )a  Deterministically repair out-of-candidate ``camera_cell`` /
    ``look_at_cell`` to the nearest valid candidate.

    PURE: returns ``(repaired_nodes, repairs)`` and never mutates the input
    nodes. ``repairs`` carries one entry per snapped field::

        {"bg_id", "field", "unit", "from", "to", "distance"}

    The function repairs ONLY the constrained-choice cell fields and ONLY
    when a snap is unambiguous (valid ``camera_unit``, non-empty candidate
    set, ``[int, int]`` cell). Malformed cells / invalid units / empty
    candidate sets are left untouched so the validator fail-fasts. It is
    the caller's responsibility to gate this behind a camera-only failure
    so non-camera defects are never masked.
    r  r  r   r   r   r   r   r   rM   )r   fieldunitfromtodistance)	r9   r8   r@   r   rY   r   r3  rX   r)  )r   r>   r  r  repairedrepairsr   new_noder   r  r  r5  cand_mapr$  snappeds                  r5   snap_camera_cells_to_candidatesr?  
  sl   ( >?E2I@AGRJ%'H$&G:488-.4"577=)h$Z$-G8}H	*,$x wwu~/hll86LM&NN%)XXg%6%*$,%)!Wd1g$6") #GAJa$8 9"%gaj47&:";!<
 ")CJ)$* '*"#!9 : Wr4   c                h   g }| D ]  }|j                  d      }t        |t              r|j                         s$|j	                  d|j                  d      d       |j                  d      }|t
        vsr|j	                  d|j                  d      d|dt        t
                       | |fS )Nr   r|   r   z rationale missing or emptyr   r   r  )r9   r@   r   r   rX   r   r   )r   rm   r   r   r   s        r5   _validate_rationale_nonemptyrA  B  s     HHH[)	)S)1BOO'*--HI xx((OO'*-VD88+,-/  L8##r4   c                &    | dk7  r	dd| dgfS dg fS )u   When readback is synthetic, the plan must NOT be marked
    production-clear. We surface that as a *constraint diagnostic* —
    the plan can still be authored, but a real smoke is blocked.rz   Fzreadback_status=ud    — plan cannot be promoted to production_ready until a real VLM readback (status='ok') is wired upTr3   rs   s    r5   "_validate_synthetic_readback_blockrD  U  s;     $1 2( )
 
 	

 8Or4   )r   r
  c                j   g }t        |      \  }}	|j                  |	       ||n|}
t        ||
      \  }}	|j                  |	       t        |      \  }}	|j                  |	       t	        |      \  }}	|j                  |	       t        |      \  }}	|j                  |	       t        |      \  }}	|j                  |	       t        | ||      \  }}	|j                  |	       t        |      \  }}	|j                  |	       t        ||      \  }}	|j                  |	       t        ||      \  }}	|j                  |	       t        |      \  }}	|j                  |	       t        |      \  }}	|j                  |	       t        |||||||||||g      }||||||||||||||dS )a'  Validate the LLM-emitted shot-aware bg render plan graph.

    W20E6-B: ``renderable_bg_ids`` is the staged-shot subset of
    ``same_fp_bg_ids`` and drives graph-completeness. When omitted (no
    caller supplied a subset), the broader ``same_fp_bg_ids`` is used
    as a backward-compatible default -- preserves call sites that have
    not yet been updated.

    W20E7-C: ``clean_anchor_candidate_bg_ids`` is the dossier's
    ``anchor_selection_metadata.candidate_bg_ids`` (the
    ``clean_background_expected=True`` subset). When provided, the
    anchor's bg_id MUST be a member; an empty set fails closed (no
    clean anchor available). When ``None``, the validator is a no-op
    (back-compat for unit tests pre-dating the wiring). Production
    paths always pass a frozenset, possibly empty.
    )r   )r   r   )rC   r   r  )r   r
  )r   r>   rC  )required_shape_okgraph_completeness_oknode_index_order_okdag_okmax_refs_per_bg_oktwo_refs_distinct_spaces_oksame_fp_only_okanchor_exactly_one_ok anchor_in_clean_candidate_set_okcamera_in_candidates_okrationale_and_mode_ok#synthetic_readback_production_clearall_validators_passedrG   )r   r   r   r   r   r   r   r  r  r  r  rA  rD  r   )rC   r   r  r>   rs   r   r
  rG   shape_okr   completeness_targetcompleteness_oknode_index_okrI  refs_oktwo_okfp_ok	anchor_okanchor_clean_okcam_okrat_oksynth_okall_oks                          r5   validate_llm_outputr`  e  s   4  K*7KHaq /:  6':OQ q1>M1qE*IFAq*7JGQq2?IFAq%5HE1 q/e<LIqq@&COQ q.hIFA q,59IFAq4'KHa q	
F" &!0,%'- !*,;#)!'/7!'" r4   c                   t        |t              s
t               S | j                  |      xs i }t	               }|j                  d      xs g D ]5  }t        |t
              st        |t              r%|j                  |       7 |j                  d      }t        |t
              r!t        |t              s|j                  |       t        |      S )zDeterministic target-unit marker fingerprint for a bg, drawn from
    the dossier's ``per_bg_render_facts_by_bg_id`` (the SOT). LLM
    ``physical_space_id`` is diagnostic only and never consulted here.target_unit_marker_numbers"dominant_target_unit_marker_number)r@   r   	frozensetr9   r   r   rY   r   )r   r   r   rB   r  doms         r5   _low_delta_marker_setrf    s     eS!{U#)rEuCYY34::ajD&9GGAJ ; ))8
9C#sJsD$9S>r4   c           	        | j                  d      xs i j                  d      }|j                  d      xs i j                  d      }t        |t              s5t        |t              s%t        |t              rt        |t              r||k7  ry| j                  d      xs i j                  d      }|j                  d      xs i j                  d      }t        |t              xr t        |t               xr ||k(  }t        t	        || j                  d            t	        ||j                  d            z        }|xs |S )u  Deterministic same-physical-space corroboration between a reuse
    candidate and its target plate.

    ``camera_unit`` equality is the hard floor — a reuse may never cross
    units. On top of that we require EITHER the same ``look_at_unit`` OR
    an overlap in the dossier target-unit markers, so that two plates that
    sit in the same unit but face genuinely different walls/targets (the
    "same space, very different view" failure mode) are NOT collapsed.
    r   r   Fr   r   )r9   r@   rY   r   rf  )	currenttargetr   cur_camtgt_camcur_looktgt_look	same_lookoverlaps	            r5   _corroborates_same_spacerp    s&    {{,-388GGzz+,277FG7D!gt$'3''3'g-.4"99.IH

,-388HH8S! 	!8T**	!  
 lGKK,@A
fjj.A
B	CG r4   c           
         |xs i j                  d      xs i }g }d}d}d# fd}t        t        t                     |      }dgt               z  }i }	|D ]<  }
t	         |
         }|j                  d      xs i }|j                  d      }|j                  d	      }|j                  d
      du }|j                  d      du }|j                  d      }|j                  d      }t        |t              r|nd}t        |t              r|nd}t        |d<   d|d<   |r|sd}|j                  d|d       |s|s|rd}|j                  d|d       |t        v r=|j                  d      }t        |t              r|sd}|j                  d|d|d       |r|r|s|t        vr|j                  d|d| d|d       n||k(  rd}|j                  d|d       n|	j                  |      }|d}|j                  d|d|d       nh|j                  d      t        k7  r|j                  d|d|d       n7t        |||      s|j                  d|d        nt        |d<   ||d<   |d!z  }|||
<   t        |t              s8||	|<   ? t        |      D cg c]  \  }}||nt	         |          }}}|||||d"fS c c}}w )$u=  Stamp canonical ``render_action`` / ``reuse_target_bg_id`` onto each
    plan node after LLM-output validation, before the checkpoint write.

    A node is routed to ``reuse_existing_plate`` ONLY when ALL hold:
      - it is not the dwelling identity anchor,
      - its ``mode`` is in :data:`REUSE_ELIGIBLE_MODES`,
      - the LLM set ``same_physical_space_low_delta_candidate=true`` with a
        non-empty ``low_delta_reuse_target_bg_id`` (LLM signal — required),
      - that target is an EARLIER same-fp node whose own canonical
        ``render_action`` is ``render_new_plate`` (no reuse-of-reuse; the
        earlier-only rule makes the reuse graph acyclic by construction),
      - deterministic same-space corroboration passes (code signal —
        required). Neither the LLM flag nor the code overlap may trigger
        reuse on its own.

    Otherwise the node stays ``render_new_plate`` with an empty
    ``reuse_target_bg_id``. Contract/structure violations
    (candidate↔target coupling, invalid target, text-only same-space
    follow-up) fail the routing (``render_action_routing_ok=False``); a
    valid-but-uncorroborated candidate, an over-signalled ineligible
    node, **or a reuse-of-reuse request** is a non-fail downgrade with a
    diagnostic — the node keeps its references and renders a new plate.
    rw   Tr   c                t    |    j                  d      }t        |t              rt        |t              s|S | S )Nr   r9   r@   r   rY   )r   r   r   s     r5   _idx_keyz&route_render_actions.<locals>._idx_key5  s2    1X\\,'C(B1ErL1Lr4   r-  Nr   r   r   r   r   r   r    render_actionreuse_target_bg_idFr|   zE candidate=true but low_delta_reuse_target_bg_id is empty (malformed)z` candidate=false but low_delta target/rationale non-empty (v2 empty-sentinel contract violation)r   r   zN requires a non-empty selected_refs (text-only same-space follow-up forbidden)z6 low-delta candidate on reuse-ineligible node (anchor=z, mode=u$   ) → downgraded to render_new_platez/ reuse target is self (must be an earlier node)z reuse target z is not an earlier same-fp nodeuU    is itself a reuse node (reuse-of-reuse forbidden) → downgraded to render_new_plate)rh  ri  r   u    LLM low-delta candidate not corroborated by deterministic same-space overlap (camera_unit + look_at_unit / target-marker) → downgraded to render_new_platerM   )render_action_routing_okreuse_plate_countreuse_countrG   )r   r   returnr   )r9   r   ranger   r8   r@   r   r   rX   r   r   rp  r   rW   )r   r   r   rG   
routing_okrz  rt  orderrouteddecided_by_bgr   r   r   r   r   r   	candidate	target_idld_rationaler   tgtr   r  routed_nodess   `                       r5   route_render_actionsr    s   4 Mr&&'EFL"LKJKM 5U$(3E.2Vc%j-@F/1ME!H~hh+,2!xxHH:;tC	GG=>$F 	 GG:;	ww45!+Is!;I	'1,'D|" 1_%'!" YJ	 "D E 	\J	 "  ''''/*Cc4("
""UIVD8 4! " D(<<""UI &//8k I9:
 e#"
""UI &$ %
 $''	2;!&J&& 		} E6 7 WW_-1BB  && 		} E= >
 2 <  && 	 *= > -@D)1:D-.1$Kq	eS!#'M% G N  ''FC ]U3Z 00'   $.(""	  	s   'K   rM   )passneeds_reviewblocked_CARD_STATE_PRIORITYanchor_shot_idprojection_card_idprojection_card_hashprojection_card_stateprojection_card_fallback_reasonprojection_card_source_bg_idprojection_card_inheritedPROJECTION_CARD_FIELDSc                p   |xs i j                  |       xs i }|j                  |       xs i j                  d      xs g }d}d}d}|D ]g  }|j                  t        |            }	t        |	t              s.t        j                  |	j                  d      d      }
|
|kD  sY|
}t        |      }|	}i ||fS )aI  Pick the anchor ``(shot_id, card)`` for one bg from its shot cards.

    Among the bg's ``applies_to_shots`` that carry a card entry, choose the
    best ``card_state`` (pass > needs_review > blocked); ties break on
    ``applies_to_shots`` order (earliest wins). Returns ``(None, None)`` when
    the bg has no card target.
    rk   N
card_stater   )r9   r   r@   r8   r  )r   
card_indexr   by_shotr   	best_shot	best_cardbest_prirK   cardpris              r5   _select_anchor_cardr    s     R$$U+1rG##E*0b556HIORG#I*.IH{{3w<($%"&&txx'=qA>HGII  ir4   c                V   |
ddddd| ddS t        | ||      \  }}|
ddddd| ddS |j                  d	      }t        |j                  d
      xs d      }t        |j                  d      xs d      }|t        vr	d}|xs d}n|dv r|sd}d}n
|dk(  rd}n|}|xs d||||| ddS )z?Resolve the non-reuse (self) projection fields for one bg node.ru  not_available projection_subsystem_unavailableFr  r   r  r   not_applicableno_projection_card_targetr  card_idfallback_reasonr  projection_card_invalid)r  r  missing_projection_card_idr  )r  r9   r   r  )	r   r  r   r  r  stater  raw_fallbackfallbacks	            r5   _resolve_self_projectionr    s     "$$&%4/Q,1).
 	
 /
=MND | "$$&%5/J,1).
 	
 HH\"E$((9%+,Gtxx 128b9L(( <#<	*	*7
 /	&(.B% !(!&+3(-%*
 
r4   c           	        i }i }t        | d       D ]  }t        |j                  d      xs d      }|j                  d      }t        |j                  d      xs d      }|t        k(  rL|rJ|j                  |      }	|	ddddd|d	d
}
n:|	d   t        vrddddd|d	d
}
n$t        |	      }
||
d<   d	|
d<   nt        |||      }
t        D ]
  }|
|   ||<    |
||<   |
d   }|j                  |d      dz   ||<    | d|ifS )u  Stamp per-node projection-card fields onto a routed plan (C3).

    ``card_index``: ``{bg_id: {shot_id: {card_state, card_id,
    fallback_reason}}}`` assembled by the step from the
    ``shot_projection_card`` checkpoint, or ``None`` when the projection
    subsystem is unavailable (card step OFF / checkpoint absent). No card
    content is re-derived here — the C2 gate already ran.

    Reuse inheritance (wiring brief §8 decision-lock, Codex C3 consensus
    #2) is FAIL-CLOSED: a ``reuse_existing_plate`` node copies the resolved
    projection fields of its render target (the bg that actually produced
    the pixels), then re-points provenance with
    ``projection_card_source_bg_id=reuse_target_bg_id`` and
    ``projection_card_inherited=true``. It NEVER forges a ``pass`` — a
    blocked / needs_review target is inherited verbatim, and a target whose
    projection cannot be resolved yields ``not_available``.

    Returns ``(nodes, summary)``. Nodes are mutated in place — additive
    fields only; the existing node shape is untouched. Iteration runs in
    ``node_index`` order so a reuse target (always an earlier
    render_new_plate node, per the router) is resolved before any child
    that inherits from it.
    c                &    | j                  dd      S Nr   r   r9   )r  s    r5   r,  z4enrich_nodes_with_projection_cards.<locals>.<lambda>P  s    AEE,,Br4   r-  r   ru  rv  rw  r  reuse_target_missingTr  r  reuse_target_projection_missingr  r  r  r   rM   projection_card_state_counts)r   r   r9   r   r  r8   r  r  )r   r  r   resolved_by_bgcountsr   r   rv  reuse_targetri  fieldsr.  r  s                r5   "enrich_nodes_with_projection_cardsr  1  s]   : 13NFu"BCDHHW%+,1488$89?R@//L#''5F~ ')*,,.-<7M4@15 /08LL ')*,,.-<7X4@15 f9E566:23-%!1F
 *CsDI * &u./

5!,q0ua Db 16:::r4   c                   | xs i j                  d      xs i j                  d      xs i }i }|j                  d      xs g D ]  }|j                  d      dk7  r|j                  d      }t        |t              rt        |t              rJ|j                  d      |j                  d      |j                  d	      |j                  d
      f||<    |S )z{marker_number: (horizontal_band, depth_band, visibility, confidence)} for
    the ``base`` markers of a C2 card envelope. transient / ignored_state_overlay
    markers are dropped; only integer marker_number entries are kept.r  
vlm_outputvisible_itemsmarker_layerbasemarker_numberhorizontal_band
depth_band
visibility
confidencers  )
card_entryvlmrB   itmns        r5   extract_base_bandsr    s     "!!&)/R
4
4\
B
HbC02Cww'-2-66.!V+VVO$"c"jT&:FF$%FF< FF< FF< 	
B . Jr4   g333333?floatBAND_CONFIDENCE_THRESHOLDvisiblepartial_BAND_VISIBILITY_OK)leftright)r  r  zFrozenSet[Tuple[str, str]]_HORIZONTAL_OPPOSITE)
foreground
background)r  r  _DEPTH_OPPOSITEc           	     Z   t        |       t        |      z  }g }g }g }t        |      D ]  }| |   \  }}	}
}||   \  }}}}||ft        v }|	|ft        v }|s|s0|
t        vs|t        vr|j                  |d|
|f       Vt        |t        t        f      r@t        |t              s0t        |t        t        f      rt        |t              s
||k  s||k  r|j                  |d||f       |r|j                  |d||	f||f||ff       |j                  |d||	f||f||ff        |||fS )u?  D5 corroboration between two cards' base bands.

    Returns ``(hard, depth_diagnostics, guarded)``:
      - ``hard``: horizontal ``left↔right`` contradictions that pass the D5 gate
        (same base marker both sides, both visibility∈{visible,partial}, both
        confidence>=threshold). These DO withhold a reuse.
      - ``depth_diagnostics``: depth ``foreground↔background`` contradictions that
        pass the same gate. DIAGNOSTIC-ONLY — canonical render_action unchanged.
      - ``guarded``: opposite-band markers excluded by visibility / confidence.
    r  r  
horizontaldepth)
r   r   r  r  r  rX   r@   r   r  rY   )	cur_bands	tgt_bandsconf_thresholdsharedhard
depth_diagguardedr  h1d1v1c1h2d2v2c2opp_hopp_ds                     r5   base_band_contradictionsr    sU    ^c)n,FD JGVn"2BB"2BBR00RO+((B6I,INNBb"562U|,"d#b3,/"d#N"N"NNBb"56KK\B8b"XBxHIr7RHr2hRIJ1 2 W$$r4   c                Z   d}t        |       t        |      z  D ]  }| |   \  }}}}||   \  }	}
}}|t        vs|t        vr(t        |t        t        f      r@t        |t
              s0t        |t        t        f      rt        |t
              s
||k  s||k  r||	ft        v r|dz  } |S )u	  Count base markers that POSITIVELY corroborate two cards' shared POV.

    A marker counts only when it is present on both sides, both
    visibility∈{visible,partial}, both confidence>=threshold, and its
    ``horizontal_band`` does NOT conflict (left↔right). This is the positive
    evidence ``shareable_card_corroborated`` requires (brief §5) — two pass cards
    with no shared base marker, or only guarded markers, yield 0 (geometry-only).
    depth agreement is not required (depth is diagnostic-only).
    r   rM   )r   r  r@   r   r  rY   r  )r  r  r  countr  r  _d1r  r  r  _d2r  r  s                r5    count_corroborating_base_markersr    s     E)ns9~-#B-CR#B-CR((B6I,I2U|,"d#b3,/"d#N"N"8++
! ." Lr4   c                   t        | ||      \  }}t        |t              sy|j                  d      dk7  ry|j                  d      }|j                  d      xs i }|r|j                  d      |k7  ryt	        |      }|xs dS )u  ③ self-card band prep — base bands of one bg's anchor card, or None.

    Returns ``None`` (band evidence unavailable → withhold impossible, fail-safe to
    reuse) UNLESS the anchor card is a trustworthy ``pass`` card whose envelope
    ``card_id`` re-joins the index entry's ``card_id`` (brief §7-C, D2: pass only,
    no forged-pass band). Reuse is decided BEFORE C3 enrich, so this selects the
    anchor itself rather than reading a stamped ``anchor_shot_id``.
    r  Nr  r  r  r  )r  r@   r8   r9   r  )r   card_content_indexr   _shotrD   r  envelopebandss           r5   _self_card_base_bandsr    s     ' 2EULE5 eT"yy&(ii	"Gyy &BHhll9-8u%E=Dr4   )r  r   r  c           
        t        | |      \  }}d|d<   s||fS |xs i i dfd}g }|D ]  }	|	j                  d      t        k7  r ||	j                  d            }
 ||	j                  d            }|
r|sOt        |
||      \  }}}|rZ|	j                  d      }t        |	d<   d|	d<   d	|	d
<   |j                  |	j                  d      ||D cg c]  }|d   	 c}d       t        |
||      dkD  rd|	d<   |s|D cg c]  }|d   	 c}|	d<    t        |      |d<   |r!||d<   t        d |D              }||d<   ||d<   ||fS c c}w c c}w )uk  ④ card-aware router — geometry route + horizontal-band withhold.

    Runs the deterministic geometry/LLM-signal router first, then (only when a
    projection card content index is available) re-decides each
    ``reuse_existing_plate`` node: if the reuse node's anchor card and its render
    target's anchor card carry a D5 horizontal ``left↔right`` contradiction, the
    canonical render_action is WITHHELD to ``render_new_plate`` (the withhold is
    the canonical SOT — brief §4 order ④). depth conflicts are recorded as a
    diagnostic only. With no card index this is a behavioral no-op == baseline
    ``route_render_actions`` (brief §10 default byte-identical).

    A reuse node is never the reuse target of another node (router earlier-only
    rule), so withholding it never invalidates a downstream inheritance.

    WITHHOLD-ONLY (Finding 2, 2a lock): this pass only CLOSES reuses; it never
    re-opens a reuse the baseline router already rejected. If A→new, B reuses A
    but is card-withheld to new, and C low-delta-targets B, the baseline router
    has already downgraded C→B as reuse-of-reuse (render_new_plate + diagnostic,
    2026-09-19: **내림이지 실패가 아니다**); that is PRESERVED here — C stays
    render_new_plate, fail-closed. Re-opening a
    withheld target's downstream reuse is future route_v3 / 2-pass router work.
    )r   r   r   card_withhold_countc                ^    t        | t              r| sy | vrt        |       | <   |    S )N)r   r  r   )r@   r   r  )bg
band_by_bgr  	readinesss    r5   _bandsz'route_render_actions_v2.<locals>._bandsG  s=    "c""Z2-?!*JrN "~r4   rv  r   rw  ru  horizontal_band_conflictcard_withhold_reason)r   withheld_reuse_target_bg_idmarkerscard_corroboratedplate_corroborationdepth_band_conflict_diagnosticcard_withhold_eventsc              3  R   K   | ]  }|j                  d       t        k(  sd ! ywrv  rM   Nr9   r   r~   r  s     r5   r   z*route_render_actions_v2.<locals>.<genexpr>v  s%      
!!7;N!NAv   ''ry  rz  )r  zOptional[str]r{  .Optional[Dict[int, Tuple[Any, Any, Any, Any]]])	r  r9   r   r  r   rX   r  r   sum)r   r   r  r   r  r  summaryr  eventsr  cur_btgt_br  r  _guardedr  hdfinal_reuser  r  s     `                @@r5   route_render_actions_v2r  !  s   < +HOFG%&G!"w &BILNJ $&F55!%88quuW~&quu123E%=5.&
"j( %%,-C!2Ao&(A"#(BA$%MMw/2*./$QAaD$/  0unMPQQ+>'(EO6PZqtZ6P239 < &)[G!"*0&'  

 
 (3#$!,7?/ 0 7Qs   EE)plate_group_idplate_anchor_bg_idplate_anchor_shot_idplate_shareabilityneeds_new_platePLATE_PARTITION_FIELDSc                   i }i }| D ]  }|j                  d      }|j                  d      t        k(  }|r|j                  d      n|}t        |t              r|r|n|}||d<   ||d<   | |d<   |r"|j	                  |g       j                  |       |j                  d      r||k(  s|||<    | D ]  }|d   }|j                  |      }|rt        |j                  d      xs d	      nt        |j                  d      xs d	      |d
<   |j                  d      rd|d<   q|j                  |      xs g }|d   r$|sd|d<   t        d |D              }	|	rdnd|d<   |j                  d      dk(  rdnd|d<    ddlm}
 t        | D ch c]  }|d   	 c}      t         |
d | D                    d}| |fS c c}w )u  Stamp PLATE_PARTITION_FIELDS as a mirror of the canonical render_action.

    ``plate_group_id`` / ``plate_anchor_bg_id`` = the bg that produces the plate
    pixels (self for ``render_new_plate``, the reuse target for
    ``reuse_existing_plate``). ``needs_new_plate`` = ``render_action ==
    render_new_plate``. ``plate_shareability`` (brief §5 vocab):
      - ``not_shareable_card_withheld`` — a node carrying ``card_withhold_reason``
        (a reuse that horizontal-band conflict flipped to render_new_plate),
      - ``exclusive`` — a solo render_new_plate group (no reuse member),
      - ``shareable_card_corroborated`` / ``shareable_geometry_only`` — a group
        with ≥1 reuse member; ``card_corroborated`` iff any member resolved its
        reuse via a passing card on both sides (``plate_corroboration`` stamped by
        ``route_render_actions_v2``), else geometry-only.
    Nodes are mutated in place (additive). The render_action SOT is untouched.
    r   rv  rw  r  r  r  r  r  ru  r  not_shareable_card_withheldr  	exclusivec              3  D   K   | ]  }|j                  d       dk(    yw)r   r  Nr  )r~   ms     r5   r   z)mirror_plate_partition.<locals>.<genexpr>  s(      #$ EE/04GG$r   shareable_card_corroboratedshareable_geometry_onlyr   r  r   Counterc              3  &   K   | ]	  }|d      ywr  Nr3   r  s     r5   r   z)mirror_plate_partition.<locals>.<genexpr>  s     <eQ+,es   )plate_group_countplate_shareability_counts)r9   r   r@   r   
setdefaultrX   anycollectionsr!  r   r8   )r   anchor_by_groupmembers_by_groupr  r   is_reusegroupanchormemberscorroborated_Counterr
  s               r5   mirror_plate_partitionr1    s   & 24O8:g55)-@@/7*+U#E3/Eu#
"'
#+|
''r299!<-.~)*&   "# $$U+7=C

+,23&'-2. 	

 ! 55'(&CA"#"&&u-3*5&'" #$#  
 6B12 &' 55./3FF .. "#1 < 0 u!Eu!!$4"5u!EF%)<e<<&
G '> "Fs   F>)ref_tree_parentsref_role_per_parentrender_order_indexmax_refsREFERENCE_DAG_FIELDSspace_continuitystyle)r   r   r   r   zDict[str, str]_MODE_REF_ROLEc           	     P   | D cg c]  }|j                  d      s| }}dd}t        t        ||            D ]
  \  }}||d<    | D ci c]K  }t        |j                  d      t              r*|d   r%|d   t	        |j                  d      xs |d         M }}|D ]  }|j                  d      xs i }|j                  d      xs g }t
        j                  |j                  d	            }|j                  d      }	g }
i }|D ]n  }|j                  d
      }t        |t              r|s'|j                  ||      }||	k(  s||
v rC|
j                  |       |r|||<   t        |
      t        k\  sn n |
|d<   ||d<   t        |d<    | dt        |      ifS c c}w c c}w )uf  Stamp REFERENCE_DAG_FIELDS onto the fresh-plate nodes (⑦).

    Only ``needs_new_plate`` nodes are DAG nodes. ``ref_tree_parents`` are the
    ``selected_refs`` canonicalized to their rendered-plate anchor bg ids (each
    ref mapped through ⑥ ``plate_anchor_bg_id`` so a reuse alias resolves to the
    fresh plate it copies), deduped and capped at ``MAX_REFS_PER_BG`` distinct
    parents; ``ref_role_per_parent`` maps each parent to ``style`` /
    ``space_continuity`` derived deterministically from the node ``mode``;
    ``render_order_index`` is the node_index topological order among fresh plates.
    r  c                l    | j                  d      }t        |t              rt        |t              s|S dS r  rs  r  r   s     r5   _idxz!build_reference_dag.<locals>._idx  -    UU< C(B1ErL1Lr4   r-  r4  r   r  r   r   r   r   r2  r3  r5  dag_node_countr  Dict[str, Any]r{  r   )	r9   rW   r   r@   r   r9  rX   r   r   )r   r  freshr=  order_iplate_anchor_by_bgr   r   roleself_bgparentsrolesr   rbcanons                  r5   build_reference_dagrK    s    :1'8!9QE:M  u$ 78
")
 9 *AaeeGnc*qz 	
'
C23AqzBB  *
 %%,-3;;/52!!!%%-0%%. "C%Bb#&b&**2r2E 5G#3NN5!#e7|.  !(
#(
 '*/ 2 #SZ000_ ;"*s   FFAF#)
rv  rw  r  r2  r3  r4  r5  r  r  r  _PARTITION_CANONICAL_FIELDSspace_partition_planRENDER_ACTION_SOURCE_PARTITIONgeometry_routeRENDER_ACTION_SOURCE_GEOMETRY_PARTITION_REF_ROLEc                   t        | t              sy| j                  d      }t        |t              r|sy|D cg c]	  }||vs| }}|r"dddj                  t	        |             dd fS | j                  d	      }t        |t              sy
|D cg c]	  }||vs| }}|r"dddj                  t	        |             dd fS | j                  d      xs g D ch c]4  }t        |t              r"|j                  d      r|j                  d      6 }}t	        |D ch c]  }||   	 c}|z
        }	|	r'dddj                  t        t        |	             dd fS yc c}w c c}w c c}w c c}w )u_  Decide whether the plan can drive the canonical surface (fail-closed).

    Usable iff it is a dict whose ``render_actions`` AND ``node_assignments``
    both cover EVERY plan-node bg, AND ``plate_groups`` provides an anchor for
    every group those bgs are assigned to. The override consumes all three, so
    checking only ``render_actions`` would let a partial plan proceed with silent
    fallback-like defaults (``gid=bg`` / ``anchor=bg``) — a half-consume that can
    mis-group a reuse node (Codex review hardening). Returns ``(usable,
    fallback_reason)``; the reason is empty when usable.
    )Fpartition_plan_missingrender_actions)F partition_plan_no_render_actionsFzpartition_plan_uncovered_bgs:,N   node_assignments)F"partition_plan_no_node_assignmentszpartition_plan_unassigned_bgs:plate_groupsr  r  z$partition_plan_missing_group_anchor:Tru  )r@   r8   r9   joinr   mapr   )
rM  node_bg_idsrT  r  	uncoveredrX  
unassignedganchored_groupsmissing_groupss
             r5   _partition_plan_is_usablerd  Y  s    *D1.)--.>?Nnd+>8)FkR~-EkIF5chhvi?P6Q5RSTXUXYYY+//0BC&-:*I{b8H.H"{JI6sxxz@R7S6TUVZWZ[[[ '**>:@b@@Aa155#8 	
@  
 (34"	"	4FN <SXXc#~F^=_<`abfcfggg) G J 	5s#   	E#E#	E(E(9E-#E2c                ^   t        d | D              }t        ||      \  }}|s%| D ]  }t        |d<   ||d<    | t        | d|      fS |}|j	                  d      xs i }|j	                  d      xs i }i }	i }
|j	                  d      xs g D ]i  }|j	                  d	      }t        |t              s%t        |j	                  d
      xs d      |	|<   dt        |j	                  d      xs g       z   |
|<   k | D ]  }|j	                  d      }|j	                  d      xs t        }t        |j	                  d      xs d      }t        D ]  }|j	                  |      |d| d<    |j	                  |      }t        |t              r|r|n|}|	j	                  ||      }d}|t        k(  r<|r:|j	                  |      }t        |t              r||k(  r	t        |}}nt        d}}d}nt        d}}|t        k(  }||d<   ||d<   | |d<   ||d	<   ||d<   |j	                  |      xs g D cg c]  }t        |t              r|r| c}dt         }||d<   |D ci c]	  }|t         c}|d<   t        |d<   |
j	                  |d      dkD  rdnd|d<   t        |d<   d|d<   ||d<    | D cg c]  }|j	                  d      s| }}d d}| D ]  }|j	                  d      rd|d<    t        t        ||            D ]
  \  }}||d<    | t        | dd      fS c c}w c c}w c c}w )!u  ⑧ Apply the space-partition plan. Partition owns grouping / anchor lineage /
    cross-group reuse veto; shot_aware (camera-aware) owns the render_action.

    When ``space_partition_plan`` is usable (covers every node), per node it
    preserves the prior geometry/wave4 value of each ``_PARTITION_CANONICAL_FIELDS``
    field under ``geometry_<field>_diagnostic`` and then —
      * ``render_action`` / ``reuse_target_bg_id`` / ``needs_new_plate`` KEEP the
        shot_aware camera-aware decision; partition NEVER promotes
        ``render_new_plate`` -> reuse. It only vetoes a reuse whose target lands in a
        DIFFERENT partition group (cross-zone false reuse) back to render_new_plate.
        Same-group reuse is preserved; same-group NEW stays NEW with anchor lineage
        (= derive: same physical space, different angle, fresh plate referencing the
        anchor). ``partition_cross_group_reuse_vetoed`` flags a vetoed node.
      * ``plate_group_id`` from ``plan.node_assignments``; ``plate_anchor_bg_id``
        = the group's anchor,
      * ``ref_tree_parents`` from ``plan.ref_tree_parents`` (already capped at
        max 2 anchors, copy-less reuse aliases excluded), ``ref_role_per_parent``
        = ``space_continuity`` per parent, ``max_refs`` = 2,
      * ``plate_shareability`` = ``exclusive`` (solo group) / ``shareable_partition``
        (grouped),
      * ``render_order_index`` recomputed topologically over the NEW fresh set.
    Each node is stamped ``render_action_source = space_partition_plan``.

    fail-closed: an unusable / uncovered plan leaves every field exactly as the
    wave4 pipeline produced it and stamps ``render_action_source = geometry_route``
    + ``partition_fallback_reason`` (Codex condition 4 — no half-consume).

    Returns ``(nodes, summary)`` where summary carries the post-partition canonical
    counts (Codex condition 3) plus the applied/fallback decision.
    c              3     K   | ]E  }t        |j                  d       t              r$|j                  d       r|j                  d        G ywr   Nr@   r9   r   r  s     r5   r   z-apply_space_partition_plan.<locals>.<genexpr>  ;      , %1aeeGnc*quuW~ 	
g   AArender_action_sourcepartition_fallback_reasonFappliedr  r2  rX  rZ  r  r  ru  rM   member_bg_idsr   rv  rw  	geometry__diagnosticTr  r  Nr3  r5  shareable_partitionr  r  "partition_cross_group_reuse_vetoedc                l    | j                  d      }t        |t              rt        |t              s|S dS r  rs  r<  s     r5   r=  z(apply_space_partition_plan.<locals>._idx  r>  r4   r4  r-  r@  )rd  rd  rP  _partition_summaryr9   r@   r   r   r   rL  r   r   rQ  rN  rW   r   )r   rM  r^  usabler  r  planplan_ref_parentsrX  r)  size_by_groupra  gidr  	sa_action	sa_targetfr-  veto_cross_group
target_gidfinal_actionfinal_targetr+  r   rG  rB  r=  rC  s                               r5   apply_space_partition_planr    s   D #, , %, #K 8kFO  A(EA$%-<A)*  (9HJ J 	J  D (,xx0B'C'Ir'+xx0B'C'Ir&(O$&MXXn%++ee$%#s#"155#8#>B?QUU?%;%Ar!BBc , UU7^ EE/*?.?	239r:	,A,-EE!HA	!K() - ""2&S)ccr $$S"- !++	)--i8J*c*zS/@-@)l->l#' ):B,L#66)/".
#+|
!
"(
  033B7=2= 0= C(Q = 00@B '
DK#LGqA':$:G#L
 '*%2%6%6sA%>%B! 	

 %C
 !)+
%&2B
./g n :1'8!9QE:M
 uu&'&*A"#   u$ 78
")
 9 $UD"MMM70 $M ;s   -L L%.L*L*c                   ddl m} t        d | D              }t        d | D              }||||t        | D ch c]  }|j	                  d       c}      |t         |d | D                    dS c c}w )us   Post-partition canonical counts (Codex condition 3) recomputed from the
    FINAL nodes — never the legacy basis.r   r   c              3  R   K   | ]  }|j                  d       t        k(  sd ! ywr  r  r  s     r5   r   z%_partition_summary.<locals>.<genexpr>	  "     R5aAEE/$:>Q$Q5r  c              3  D   K   | ]  }|j                  d       sd  ywr  rM   Nr  r  s     r5   r   z%_partition_summary.<locals>.<genexpr>	       =5aAEE*;$<5     r  c              3  >   K   | ]  }|j                  d         ywr#  r  r  s     r5   r   z%_partition_summary.<locals>.<genexpr>	       @%QQUU/0%   )space_partition_appliedspace_partition_fallback_reasonry  rz  r$  r?  r%  )r(  r!  r	  r   r9   r8   )r   rn  r  r0  reuserB  r  s          r5   ru  ru  	  s}    
 0R5RRE=5==E#*+:" 5!I5a!%%(8"95!IJ%)@%@@&

 

 "Js   A1dwelling_zone_mapRENDER_ACTION_SOURCE_ZONE_MAPc                x   t        | t              sy| j                  d      ry| j                  d      }t        |t              r|sy|D cg c]	  }||vs| }}|r"dddj                  t	        |             d	d
 fS |D cg c]Y  }t        |j                  |      t              r6t        ||   j                  d      t
              r||   j                  d      s|[ }}|r"dddj                  t	        |             d	d
 fS |D ch c]
  }||   d    }}t        |      dk  ryyc c}w c c}w c c}w )u  Decide whether the dwelling zone map can drive the canonical surface.

    Usable iff ``zone_plan`` is a NON-synthetic dict whose
    ``bg_zone_assignments`` cover EVERY plan-node bg with a non-None
    ``zone_id``, AND those node bgs span >= 2 DISTINCT zones. A single zone has
    nothing to consolidate, so the legacy ``apply_space_partition_plan`` path
    stays byte-identical (Codex: single_zone skip). The synthetic fixture
    asserts nothing and must never drive the surface. Judged over the node bgs
    only — a zone_map may carry bgs with no staged shot. Returns ``(usable,
    fallback_reason)``; the reason is empty when usable.
    )Fzone_plan_missing	synthetic)Fzone_plan_syntheticbg_zone_assignments)Fzone_plan_no_assignmentsFzzone_plan_uncovered_bgs:rV  NrW  zone_idzzone_plan_null_zone:r   )Fzone_plan_single_zoner[  )r@   r8   r9   r\  r   r   r   )	zone_planr^  	bg_assignr  r_  	null_zonedistinct_zoness          r5   _zone_plan_is_usabler  6	  sU    i&)}}[!+34Ii&i0)AkRy-@kIA0&:K1L0MNtPSTTT By}}R($/9R=,,Y7="!!), 	   ,SXXfY6G-H,IJ4CPPP9DE2imI.NE
>Q-# B Fs   	D-D-AD2D7c                    dfdd	fd}t        fd| D              s|j                  d|        t        t        | |      d   j	                  d            S )
u  Pick the single plate anchor for one zone from its member nodes.

    Preference (each tier breaks the prior tie), per the Phase 2 design:
      1. a dossier clean-background candidate (the anchor plate must be clean);
      2. a node already routed to a fresh plate — an identity anchor or a
         ``render_new_plate`` render_action — over one routed to reuse;
      3. lower ``node_index`` (stable, earlier in the graph walk);
      4. higher VLM zone-assignment confidence;
      5. stable ``bg_id``.
    When NO member is a clean candidate this does NOT fail — it still returns a
    stable anchor and appends ``zone_anchor_no_clean_candidate:<zone_id>`` to
    ``diagnostics`` so the visual gate sees it (never hidden like a synthetic).
    c                    j                  |       xs i j                  d      }t        |t        t        f      rt        |t              st        |      S dS )Nr  g        )r9   r@   r   r  rY   )r  r  r  s     r5   _confz!select_zone_anchor.<locals>._confu	  sI     $$R(.B33LA%a#u6z!T?RuQx[X[[r4   c                P   | j                  d      }|v }t        | j                  d            xs | j                  d      t        k(  }| j                  d      }t        |t              rt        |t              s|nd}|rdnd|rdnd| t        |             t        |      fS )Nr   r   rv  r   r   rM   )r9   rY   r   r@   r   r   )r  r  in_cleanis_freshr   r  r
  s        r5   _rankz!select_zone_anchor.<locals>._ranky	  s    UU7^66;<= 
EE/"&77 	 UU< b#&z"d/CRAqAq3r7^OG
 	
r4   c              3  D   K   | ]  }|j                  d       v   ywrg  r  )r~   r  r
  s     r5   r   z%select_zone_anchor.<locals>.<genexpr>	  s#      AHAg77s    zzone_anchor_no_clean_candidate:r-  r   r   )r  r   r{  r  )r  rA  r{  r
   )r'  rX   r   r   r9   )r.  r  r
  r  rG   r  r  s     ``  @r5   select_zone_anchorr  _	  s_    ,\
   AH  	<WIFGvg5)!,009::r4   )anchor_diagnosticsc               
   ddl m} t        d | D              }t        d | D              }||||t        | D ch c]  }|j	                  d       c}      |t         |d | D                    |t        |xs g       d	S c c}w )u   Post-application counts — keys mirror ``_partition_summary`` so the
    build_render_plan_for_fp consumer reads either path uniformly, plus the
    zone-specific ``zone_map_applied`` / ``zone_anchor_diagnostics``.r   r   c              3  R   K   | ]  }|j                  d       t        k(  sd ! ywr  r  r  s     r5   r   z _zone_summary.<locals>.<genexpr>	  r  r  c              3  D   K   | ]  }|j                  d       sd  ywr  r  r  s     r5   r   z _zone_summary.<locals>.<genexpr>	  r  r  r  c              3  >   K   | ]  }|j                  d         ywr#  r  r  s     r5   r   z _zone_summary.<locals>.<genexpr>	  r  r  )	r  r  ry  rz  r$  r?  r%  zone_map_appliedzone_anchor_diagnostics)r(  r!  r	  r   r9   r8   r   )r   rn  r  r  r0  r  rB  r  s           r5   _zone_summaryr  	  s     0R5RRE=5==E#*+:" 5!I5a!%%(8"95!IJ%)@%@@&
 $#'(:(@b#A 
 "Js   B r
  c                  t        d | D              }t        ||      \  }}|s%| D ]  }t        |d<   ||d<    | t        | d|      fS |d   }i }| D ]:  }||j	                  d         d   }	|j                  |	g       j                  |       < g }
i }t        |      D ]  }	t        ||	   |	|||
	      ||	<    | D ]  }|j	                  d      }||   d   }	||	   }t        D ]  }|j	                  |      |d
| d<    ||k(  }|rt        nt        |d<   |rdn||d<   ||d<   |	|d<   ||d<   g |d<   i |d<   t        |d<   t        ||	         dkD  rdnd|d<   t        |d<   d|d<   |	|d<   ||d<    | D cg c]  }|j	                  d      s| }}d d}| D ]  }|j	                  d      rd|d<    t        t        ||            D ]
  \  }}||d<    | t        | dd|
      fS c c}w )!uh  Apply the dwelling zone map: EXACTLY ONE plate per zone (Phase 2).

    Per zone, ``select_zone_anchor`` picks the single plate anchor. The anchor
    renders a fresh plate (``render_new_plate`` / ``needs_new_plate=True``);
    every same-zone non-anchor bg ALIASES it (``reuse_existing_plate``,
    ``reuse_target_bg_id`` = anchor) and carries NO ``ref_tree_parents`` — the
    alias is a reference, not a derive (the user's hard rule: one physical space
    = one plate; per-shot angle/character is Phase 3 i2i off the one plate).

    Each node snapshots its prior canonical fields under ``geometry_<field>
    _diagnostic`` (same visual-gate traceability as the partition path) and is
    stamped ``render_action_source = dwelling_zone_map``. Additionally each node
    gets the Phase 3 contract ``zone_id`` + ``zone_plate_bg_id`` (the one plate
    every bg in the zone resolves to). ``render_order_index`` is recomputed over
    the fresh anchor set only; aliases are not DAG nodes (``None``).

    fail-closed: an unusable / uncovered / single-zone / synthetic plan is a
    NO-OP — every field is left as the upstream pipeline produced it and stamped
    ``render_action_source = geometry_route`` + ``partition_fallback_reason``.
    (In production this path is selected only when the plan is usable; the NO-OP
    is a defensive guard.) Returns ``(nodes, summary)``.
    c              3     K   | ]E  }t        |j                  d       t              r$|j                  d       r|j                  d        G ywrg  rh  r  s     r5   r   z/apply_dwelling_zone_map_plan.<locals>.<genexpr>	  ri  rj  rk  rl  Frm  r  r   r  )r.  r  r
  r  rG   rp  rq  rv  ru  rw  r  r  r  r2  r3  r5  rM   rr  r  r  zone_plate_bg_idc                l    | j                  d      }t        |t              rt        |t              s|S dS r  rs  r<  s     r5   r=  z*apply_dwelling_zone_map_plan.<locals>._idx
  r>  r4   Nr4  r-  T)rn  r  r  r@  )rd  r  rP  r  r9   r&  rX   r   r  rL  r   r   r   r   r  rW   )r   r  r
  r^  rv  r  r  r  members_by_zonezidr  anchor_by_zoner  r-  r}  r   rB  r=  rC  s                      r5   apply_dwelling_zone_map_planr  	  s   8 #, , %, #K 39kJFOA(EA$%-<A)*  m5/
 
 	
 !**? @I 8:Og'	2""3+2215 
 %'%'No&0#C(*G )*
s ' UU7^mI&$,A,-EE!HA	!K() -&L	2;.AT/(1"v
(
!
"(
 !#
#%
 '*%()=%>%B! 	

 %B
 !)+
%&) &
1 6 :1'8!9QE:M uu&'&*A"#   u$ 78
")
 9 -tR-   ;s   6G)G)c           	     D    | |sdnddg idt        |      ddddd|dg dS )	Nr  failedr   FrR  rG   r   imagellmr  rC    shot_aware_bg_render_plan_statusgraph
validatorsreal_api_call_countsrs   production_clearrG   )r   rC   rs   rm   s      r5   _empty_planr  
  sE     DL,<2%*>
 +,Aa @*! r4   no_viable_camera_look_at_pairc                   t        | t              s"t        dt        |       j                   d      i }dD ]@  }| j                  |      }t        |t              s%t        d| dt        |      j                   d      i }|j                         D ]  \  }}d}t        |t              r	 t        |      }t        |      |k(  }|st        d| d|d	      t        |t              s(t        d| d
|dt        |      j                   d      |D ]M  }	t        |	t        t        f      r!t        |	      dk(  rt        d |	D              r:t        d| d
|d|	d       t        |      ||<    |||<   C |d   }
|d   t        fd|
j                         D              }t!        |      ||
dS # t        t        f$ r Y w xY w)uP  viable unit 존재 여부 — validator 와 동일한 same-unit pair SOT.

    validator 는 한 노드의 camera_unit 하나로 camera/look_at 두 후보
    배열을 함께 조회하므로, 전역 합계가 아니라 **같은 u** 에서 양쪽이
    비어 있지 않은 unit 이 하나는 있어야 계획 가능하다 (Codex: camera=
    {1:[..]}, look_at={2:[..]} 처럼 전역 nonzero 여도 공통 unit 0 이면
    불가). malformed(비 dict/비 list/비 정수쌍 cell)=ShotAwareBgRender
    PlanError fail-closed — 상류 계약 위반을 not_applicable 로 세탁 금지.
    z'geometry malformed: expected dict, got z (fail-closed))r  r  z	geometry.z malformed: expected dict, got u@    (fail-closed — upstream contract failure, not not_applicable)Fz
 unit key ur    malformed: expected canonical integer string (validator str(camera_unit) 조회로 도달 불가 — fail-closed)[z ] malformed: expected list, got r   c              3  p   K   | ].  }t        |t              xr t        |t               xr |d k\   0 ywr   Nr!  )r~   xs     r5   r   z1assess_camera_lookat_viability.<locals>.<genexpr>w
  sF       "&A #1c* # *1d 33#F# "&s   46z] malformed cell uZ   : expected [int>=0, int>=0] — 응답 schema minimum=0 이라 선택 불가 (fail-closed)r  r  c              3  \   K   | ]#  \  }}|d kD  rj                  |d       d kD  r| % ywr  r  )r~   ur  look_countss      r5   r   z1assess_camera_lookat_viability.<locals>.<genexpr>
  s6      (daq5[__Q*Q. 	
(s   ),)viableviable_unitscamera_candidate_countslook_at_candidate_counts)r@   r8   r.   typer/   r9   r?   r   r   	TypeError
ValueErrorr   r  r   r   r   rY   )r>   r  namemappingper_unitr6  cells	canonicalparsed_unitr$  
cam_countsr  r  s               @r5   assess_camera_lookat_viabilityr  8
  sD   $ h%(5H~&&'~7
 	
 )+F ,,t$'4(,D6!@=))* +88 
 $&"==?KD% I$$9"%d)K !$K 0D 8I0vZx 8P P 
 eT*0vQth /!!%e!5!5 6nF  tdE]3D	Q  "&	  5#D64(2C( #NN    !ZHTNO +P  tij 9:J;<K  &&( L
 |$$#-$/	 O ":. s   -F88G
G)llm_providerprojection_card_indexr  rM  r  c           
     	   |d   }|d   }|j                  dd      }	|d   }
t        |j                  d      xs i j                               }|
j                  d      }t        |t        t
        f      rt        d |D              }n3t        d	 |
j                  d
      xs i j                         D              }g }|
j                  d      r|sJ|j                  d       |j                  |
j                  d      xs g        t        | |	|      }d|d<   |S t        |      }|d   s-t        | |	dg      }d|d<   t        |d<   |d   |d   d|d<   |S |t        | |	dg      }d|d<   |S 	  || |||
|j                  d      xs g       }t        |t"              s"t%        d't        |      j                    d(      |j                  d)      xs i j                  d      xs g }t        d* |j                  d+      xs i j                  d,      xs g D              }t'        | ||||	||-      }t)        ||||
j                  d
      xs i .      \  }}|d/   |d/<   |d0   |d0<   |j                  d1d$      |d1<   t+        |||
j                  d
      xs i 2      \  }}|d3   |d3<   t-        |      \  }}t/        |      \  }}|d4   |d4<   |d5   |d5<   |d6   |d6<   t        d7 |D              }|!t1        ||      d$   rt3        |||8      \  }}nt5        ||      \  }}|d9   |d9<   |d:   |d:<   |d9   r-|d0   |d0<   |d4   |d4<   |d5   |d5<   |d6   |d6<   d;|d/<   i |d/d;i}g }d<|v r+|d<   |d<<   t	        |j                  d=      xs g       }||d=<   t	        |j                  d>      xs g       |d>   z   |z   |d><   t7        |d?   xr |d/         |d?<   |d?   rdnd}|j9                  d@d      }g } d}!t        |t"              r| j                  dA|i       dB}!|j9                  dCd      }"t        |"t"              r| j                  dD|"i       dB}!| |dt	        |      i|d$|!d$d%|	t7        |d?   xr |dE         | d&S # t        $ rN}t        t        |dd            }| ddg id d!t        |      j                    d"| gd#d$|d$d%|	d g d&cY d}~S d}~ww xY w)Fu  Build the per-fp shot-aware bg render plan.

    ``llm_provider`` is a dependency-injected callable. When ``None``
    (default in production) the planner returns a structurally-empty
    plan with ``status='not_applicable'`` and a diagnostic — i.e. no
    LLM call is made. When provided, it is invoked as::

        llm_provider(
            fp_id=...,
            dossier=...,
            geometry=...,
            shot_readiness=...,
            candidate_catalog=...,
        ) -> {"graph": {"nodes": [...]}}

    The output is validated against ``validate_llm_output``; on
    failure the plan is marked ``failed`` with the validator
    diagnostics surfaced.

    ``projection_card_index`` (metadata-only ``{bg:{shot:{card_state, card_id,
    fallback_reason}}}``) feeds the C3 enrich pass (⑤). ``card_content_index``
    (the envelope-bearing ``{bg:{shot:{card_state, card_id, card}}}``) feeds the
    card-aware router (④) for D5 horizontal-band withhold. Both default ``None``
    (projection subsystem OFF) — the router is then a baseline no-op and enrich
    stamps ``not_available``. The 3a/3b mirrors (⑥/⑦) run regardless.
    r   r>   rs   rv   r   rw   r   c              3  B   K   | ]  }t        |t              s|  y wr   r   )r~   bgids     r5   r   z+build_render_plan_for_fp.<locals>.<genexpr>
  s      6
,T
40ED_s   c              3  H   K   | ]  \  }}|j                  d       r|  yw)rz   Nr  )r~   r  rD   s      r5   r   z+build_render_plan_for_fp.<locals>.<genexpr>
  s.      &
 = e yy	 <s    "r   rz   zmshot_readiness gate skipped (no BG has at least one staged consuming shot in shot_staging cp; not_applicable)rm   r  r  r  r  u   no viable camera/look_at candidate pair in any unit — camera geometry cannot satisfy the plan contract for any node (not_applicable; provider not called)not_applicable_reason_coder  r  )r  r  camera_lookat_viabilityNu   no llm_provider wired — W20B production default; plan left empty by design. Wire a provider via the step wrapper for mock/dry runs or for a future real smoke behind explicit approval.r   )rC   r   r>   r   r   completion_call_countrM   r  r   Fzllm_provider raised: r}   r  r   r  r  z llm_provider returned non-dict (rj   r  c              3  D   K   | ]  }t        |t              r|r|  y wr   r   )r~   bids     r5   r   z+build_render_plan_for_fp.<locals>.<genexpr>;  s-      	>  c3C 	r   anchor_selection_metadatacandidate_bg_ids)rC   r   r  r>   rs   r   r
  )r   r   r  r   rx  ry  r  )r   r  r   r  r$  r%  r?  c              3     K   | ]E  }t        |j                  d       t              r$|j                  d       r|j                  d        G ywrg  rh  r  s     r5   r   z+build_render_plan_for_fp.<locals>.<genexpr>  s;      1 %1aeeGnc*quuW~ 	
grj  r  r  r  Tr  r  rG   rR  _w20f9_retry_metadataw20f9_camera_validator_retryr   _graph_anchor_retry_metadata#w20f10_graph_anchor_validator_retryrQ  )r9   rd  r   r@   r   r  r?   rX   r   r  r  #CAMERA_LOOKAT_NOT_APPLICABLE_REASON	Exceptionr   getattrr  r/   r8   r.   r`  r  r  r1  rK  r  r  r  rY   pop)#rC   planner_inputr  r  r  rM  r  r   r>   rs   r   r  renderable_listr   rm   rB   	viability
llm_outputexc_fail_llm_callsr   r
  r  routingprojection_summarypartition_summarydag_summaryzone_node_bg_ids
sp_summary
zone_diagsru   
retry_metadiagnostics_blockllm_call_countga_retry_metas#                                      r5   build_render_plan_for_fpr  
  s   T I&GZ(H#''(99EO"#34N%.	3	4	:@@B&N %(()<=O/D%=1,5 6
,6
 -
 & &
  ##H-3::<&
 
 Hd#+< 	H	
 	**:6<"=+

 3C./

 /x8IX+A
 3C.// 	() (1)(+(1*),*
%& 
+,	
 3C./

!)+//0CDJ

: j$'(.tJ/?/H/H.IK
 	
 ^^G$*//8>BE
 5> 	> [[45;@@"  	> 	5! %%'+&CJ --(,,X6<"	NE7 .5".J)* '..A&BJ"#(/4I1(MJ$% !C((,,X6<"!E
 2D&2J-.  6e<E,U3E;&78K&LJ"#.?#/J*+ $//?#@J  (1 1 %1 ( !5#""	 99*G
z
 7'
z -77P,QJ()4>)5J01 +, +55H*I
&'*45H*I
&'2<'3

./ (22B'C
#$15
-.?W?8$? JZ')34F)G
%&*..)BCIrJ
0:
,-Z^^M*0b1
-
 	!
	 }
 +/*+ 	0./+J&' -. 	   7>J.0N*d#  "@*!MN NN#A4HM-&  2MB	D,24;' *+N1 M* !./ B@A
 ) _  
 gc+BAFG08r]).+DI,>,>+?r#G  ?1%  / %
 	
	
s   0 Q? ?	SASSS)r:   rA  r{  Dict[str, Dict[str, Any]])rA   rA  r{  r  )r]   rA  rG   Optional[Dict[str, Any]]r{  r  )rk   	List[str]rl   r  r{  z7Tuple[bool, List[str], List[Dict[str, Any]], List[str]])r:   rA  rA   rA  r   rA  r   rA  r]   rA  rq   r  r{  r  )r   r   r{  rY   )r   List[Dict[str, Any]]r{  Tuple[bool, List[str]])r   r
  r   r   r{  r  )r>   rA  r{  z Tuple[Dict[str, int], List[str]])rC   r   r   r
  r  r   r{  r  )r   r
  r
  Optional[FrozenSet[str]]r{  r  )r   r
  r>   rA  r{  r  )r$  r   r{  rY   )r$  r   r0  r   r{  zOptional[List[int]])r   r
  r>   rA  r{  z1Tuple[List[Dict[str, Any]], List[Dict[str, Any]]])rs   r   r{  r  )rC   r   r   r
  r  r   r>   rA  rs   r   r   r  r
  r  r{  rA  )r   rA  r   r   r{  zFrozenSet[int])rh  rA  ri  rA  r   rA  r{  rY   )r   r
  r   rA  r{  +Tuple[List[Dict[str, Any]], Dict[str, Any]])r   r   r  $Dict[str, Dict[str, Dict[str, Any]]]r   r  r{  z.Tuple[Optional[str], Optional[Dict[str, Any]]])r   r   r  .Optional[Dict[str, Dict[str, Dict[str, Any]]]]r   r  r{  rA  )r   r
  r  r  r   r  r{  r  )r  rA  r{  $Dict[int, Tuple[Any, Any, Any, Any]])r  r  r  r  r  r  r{  z,Tuple[List[Tuple], List[Tuple], List[Tuple]])r  r  r  r  r  r  r{  r   )r   r   r  r  r   r  r{  r  )r   r
  r   rA  r  r  r   z#Optional[Dict[str, Dict[str, Any]]]r  r  r{  r  )r   r
  r{  r  )rM  r  r^  r   r{  Tuple[bool, str])r   r
  rM  r  r{  r  )r   r
  rn  rY   r  r   r{  rA  )r  r  r^  r   r{  r  )r.  r
  r  r   r
  r   r  rA  rG   r	  r{  r   )
r   r
  rn  rY   r  r   r  zOptional[List[str]]r{  rA  )r   r
  r  r  r
  r   r{  r  )rC   r   rs   r   rm   r	  r{  rA  )r>   rA  r{  rA  )rC   r   r  rA  r  z'Optional[Callable[..., Dict[str, Any]]]r  r  r  r  rM  r  r  r  r{  rA  )Wr2   
__future__r   typingr   r   r   r   r   r	   r
   rd  r   __annotations__r   r   r   r   r   r   r   r"   r(   r,   r  r.   r;   rE   rg   rp   r   r   r   r   r   r   r   r   r   r  r  r  r  r%  r3  r?  rA  rD  r`  rf  rp  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r1  r6  r9  rK  rL  rN  rP  rQ  rd  r  ru  r  r  r  r  r  r  r  r  r  r3   r4   r5   <module>r     s  'P # H H H %.% >  0s / 5 c 5 , 3 +1 S 1)2+,*   (1!45( n  &//N%O N O+   *  6 6?$ -1E(E *E 	EP/-"/-3L/-</-r :>o#o %o $	o
 (o )o 7o onR$"R$R$j$"$$4!$"!$7E!$!$H%$P$"$$p$"p$p$f&&%&R$$  $ #	$
 $2$"$$255 $<5 	5pK$K$ K$ 	K$\#
###L55 5 7	5p$"$$& . 37>Bmm  m #	m
 m m 0m $<m mj ),&$ $  $  !	$ 
 
$ NW"W-;W0Wd 121QR'S n S+     5  0	 
 4 <<< ?< 0	<
 <~P;P; ?P; 0	P;
 1P;@, $' 5 &&/I0F&G ^ G3<)*4 0  /8!#?@/+  6,%3,%3,% ,% 2	,%d 6 3 3    		 F = 0	
 4D JN<@5ZZ Z G	Z
 :Z Z 1ZL+  JJ0Jn) o    2+& '	" <1<10<1b0 _  '=  <%5 s 5- S -&2&AO&&RGNGN2GN 1GNT-1DGN &9 s 8&'&6D&&R.;!.; .; $2	.;
 (.; .; 	.;l /3  	
 , B 5>K	gg'g $2	g
 1g^$'3<2 'F #XXXB 	 	 	59*.EE "E	EEE 3E (E Er4   