TheRoad Scene Lab · W21B-wave-4 · 2026-05-31 · Claude 초안 → Codex cross-review 2회 → doc-only APPROVE (D1/D4/D5 lock)
APPROVED v0.1 — doc-only 코드/DB/prompt/render 변경 0 design brief = contract/schema SOT
shot_aware_bg_render_plan reference graph를 버리지 않고 그 위에 card-aware 필드/계약을 얹는다.
코드 변경 0. 이 brief는 Codex 리뷰 합의 → dry/mock → 구현 순서의 1단계다.
사용자 비전(2026-05-31)을 Claude가 매핑하고 Codex가 합의한 4단계:
| # | 단계 | 현 상태 | 이 wave 범위 |
|---|---|---|---|
| 1 (A) | 전체 씬+샷 종합 → 어떤 FP를 만들지 LLM 판단 | background_master_plan(sub_location 휴리스틱 그룹핑) |
future wave §8 — scene_primary fix로 FP 품질 회복됨, 격상은 별도 wave |
| 2 | FP 생성 | floor_plan_prompt→floor_plan_render | ✅ 완료 |
| 3 (B) | 원본 BG 몇 개 + 각 샷 배정 + BG간 일관성 참조 트리 | shot_aware_bg_render_plan reference graph 골격 존재 |
이 brief의 대상 → 3a + 3b로 분할 |
| 4 (C) | render 시 substrate(FP+card vs card+ref image) 결정 → BG 생성 | background_render(fp+prior_bg) + C4 card→prompt |
3b 계약 이후 §8 — node별 substrate policy로 분리 |
build_render_plan_for_fp의 현재 파이프라인 (실측, shot_aware_bg_render_plan.py):
graph.nodes[], 각 노드 = bg_id + mode + reference_decision + is_dwelling_identity_anchor + camera_decisionvalidate_llm_output → DAG / max_refs_per_bg=2 / two_refs_distinct_spaces / same_fp_only / 정확히 1개 anchor / node_index 순서route_render_actions (deterministic) → 노드마다 canonical render_action ∈ {render_new_plate, reuse_existing_plate} + reuse_target_bg_id stampenrich_nodes_with_projection_cards (C3, deterministic) → 노드마다 7개 projection 필드 stamp| 관심사 | 현재 담는 곳 | 의미 |
|---|---|---|
| plate 공유 (= 같은 원본 BG) | render_action = reuse_existing_plate + reuse_target_bg_id |
이진 결정. reuse 노드는 target의 픽셀을 copy-less alias (image 0) |
| reuse 자격 모드 | REUSE_ELIGIBLE_MODES = {same_physical_space_view, reference_derived} | anchor / related_style_new_space / two_refs 는 reuse 불가 |
| 같은 공간 corroboration | _corroborates_same_space |
geometry만: camera_unit 일치(hard floor) AND (look_at_unit 일치 OR marker overlap) |
| 참조 트리 (DAG) | reference_decision.selected_refs[] + mode + physical_space_id_per_ref |
각 ref는 same-fp earlier bg, 최대 2개, DAG 보장(earlier-only 규칙) |
| render 순서 | node_index 오름차순 | reuse target은 항상 자기 child보다 먼저 결정됨 |
| POV ground truth | C3 projection 필드 (anchor_shot_id, projection_card_id/hash/state …) |
C2 카드의 검증된 same-space POV. 현재 corroboration엔 미반영 |
_corroborates_same_space는 camera_unit / look_at_unit / marker overlap
즉 도면 geometry 신호만 본다. 같은 unit에 앉아 있어도 카메라가 실제로 무엇을 향하고 어떤 base 오브젝트가 프레임에 잡히는지(card POV)는 반영하지 않는다.
→ "같은 공간이지만 전혀 다른 뷰"를 plate로 합쳐버리거나, 반대로 합쳐도 되는 뷰를 놓친다. 이것이 사용자의 "fp와 배경 안 맞음" / BG간 일관성 결여의 한 축.
C2/C3가 이미 카드의 구조화 POV 증거를 만들어 둔다 — 이것이 corroboration에 주입할 결정적 신호다:
visible_items[] 의 marker_layer="base" 항목 = 그 샷에서 실제로 보이는 지속 spatial anchor (transient/event 제외)horizontal_band ∈ {left,center,right} + depth_band ∈ {foreground,midground,background} + visibility = 좌표 없는 구조화 POVcamera_pose_source = VLM이 선택해 서술한 camera recommendation 식별자 (좌표 발명 금지)plate_description_excludes_transient = plate purity 자기보고→ 3a는 이 구조화 band 집합으로 같은-plate 공유 가능 여부를 corroborate한다. 문자/substring 매칭이 아니라 enum band 집합 비교(§11).
| 3a — plate partition | 3b — reference DAG | |
|---|---|---|
| 질문 | "원본 plate 몇 장? 어떤 bg/shot이 같은 base plate를 공유?" | "각 plate를 어떤 순서·어떤 ref 조합으로 렌더?" |
| 기존 골격 | route_render_actions (reuse routing) | reference_decision.selected_refs + mode |
| 주입할 신호 | card POV bands (corroboration 확장) | plate group 결과 + style/space continuity |
| 출력(additive) | plate_group_id / plate_anchor_bg_id / plate_anchor_shot_id / plate_shareability / needs_new_plate |
ref_tree_parents / render_order_index / ref_role_per_parent / max_refs(=2 보존) |
PROJECTION_CARD_FIELDS 패턴 그대로(새 필드 튜플 추가, in-place mutate).camera_unit hard floor가 최종 권위(§9-D1).route_render_actions가 이미 canonical
render_action을 확정한 뒤이고 background_render는 canonical render_action만 신뢰하므로,
그 뒤에 찍는 additive 필드로는 withhold가 실제 렌더에 반영되지 않는다. 반대로 3a가 render_action을 다시 쓰면 §7-B 재정의 금지와 충돌.
→ withhold는 canonical render_action 확정 전/내부에 들어가야 한다. 수정된 순서:
① LLM provider → graph.nodes
② validate_llm_output (DAG / refs / anchor — 무변경)
③ self-card band prep (신규, pure) reuse 무관. 각 노드의 self 카드만
재조인(id==hash==card_id AND cp state==pass)
→ base band 집합 추출. render_action 불필요.
④ card-aware route_render_actions 기존 geometry corroboration
(= route_render_actions_v2) + ③ band corroboration(withhold)를
통합 → canonical render_action /
reuse_target_bg_id 최종 확정 (SOT)
⑤ enrich_nodes_with_projection_cards (C3) route 이후 — reuse inheritance 포함
projection 7필드 stamp (기존 그대로)
⑥ partition fields mirror (3a 출력) canonical render_action을
plate_group_id / needs_new_plate 로 MIRROR
(SOT 아님 — 상위 표현)
⑦ build_reference_dag (3b) fresh plate 노드만 DAG에 넣어 ref tree
SOT 불변: 실제 렌더 SOT는 여전히 canonical render_action / reuse_target_bg_id. 3a의
plate_group_id/needs_new_plate는 그것을 mirror하는 상위 표현일 뿐. not_shareable_card_withheld로
강등된 노드는 router(④)가 render_new_plate로 stamp → W21B-w3 alias 계약 무손상.
route_v2는 reuse를 닫기(withhold)만 하고,
baseline router가 이미 거부한 downstream reuse를 재개방하지 않는다. 예: A→new, B가 A를 reuse하나 card 충돌로 new로 withhold되고
C가 B를 low-delta target하면 — baseline이 이미 C→B를 reuse-of-reuse(LLM contract 위반)로 routing_ok=False+diagnostic 거부했고,
route_v2는 이를 보존(C는 render_new_plate, fail-closed). card evidence가 간접적으로 downstream reuse를 enable하지 않게 하는 v0 안전장치.
withheld target의 downstream reuse 재개방은 future route_v3 / 2-pass router 작업.
위치: shot_aware_bg_render_plan 내부 pure helper 2개 — ③ self-card band prep + ④ card-aware route_render_actions_v2(withhold 통합) + ⑥ mirror_plate_partition(canonical render_action mirror). 별도 step 아님(§9-D4). withhold는 ④ router 내부에서 일어나고, 3a 출력 필드(⑥)는 그 결과를 mirror만 한다.
| 입력 | 출처 |
|---|---|
| card visible base objects | C2 cp visible_items[] where marker_layer="base" → {band 집합} |
| camera pose | 노드 camera_decision.camera_unit / look_at_unit + card camera_pose_source |
| shot intent / 관련성 | per_bg_readiness applies_to_shots + card anchor shot |
| floor-plan group | fp_id (same-fp 경계는 이미 강제됨) |
| reuse candidate inputs | LLM 후보(mode, same_physical_space_low_delta_candidate / low_delta_reuse_target_bg_id) + geometry facts(per_bg_render_facts_by_bg_id, camera_decision). canonical render_action은 ④에서 확정 — 입력 아님. |
| projection state | C3 projection_card_state / projection_card_id / hash |
PLATE_PARTITION_FIELDS = (
"plate_group_id", # 이 bg가 귀속되는 원본 plate의 안정 id
# = render target bg_id (new면 self, reuse면 target)
"plate_anchor_bg_id", # 픽셀을 생산하는 bg (= group 대표)
"plate_anchor_shot_id", # plate POV ground-truth를 정의하는 샷
"plate_shareability", # enum (아래)
"needs_new_plate", # bool — 이 bg가 fresh render anchor면 True,
# group anchor를 alias하면 False
)
plate_shareability vocabulary (fail-closed ladder)| 값 | 의미 | 조건 |
|---|---|---|
exclusive | 혼자 = 자기 plate anchor | render_new_plate AND 공유 멤버 없음 |
shareable_card_corroborated | card POV까지 일치 → 강한 공유 | geometry corroborate ✓ AND 양쪽 모두 pass card AND base band 집합 corroborate ✓ |
shareable_geometry_only | geometry만으로 공유 (현 동작과 동일) | geometry corroborate ✓ 이나 card 증거 부재/비-pass → card는 침묵(withhold 아님) |
not_shareable_card_withheld | card POV가 geometry 공유를 철회 | geometry corroborate ✓ 이나 양쪽 pass card의 base marker가 horizontal left↔right 모순(D5 final) → 병합 거부 → needs_new_plate=True |
not_shareable | 애초에 공유 후보 아님 | geometry corroborate ✗ (camera_unit 불일치 등) |
route_render_actions_v2 내부에서 일어난다 — geometry corroborate ✓ + LLM candidate ✓ 로 reuse가 될 노드를, card band 모순이면 router가 reuse 대신 render_new_plate로 확정한다. shareability 값은 ⑥에서 그 결과를 mirror.shareable_geometry_only → not_shareable_card_withheld 로 강등만 가능. not_shareable(geometry ✗)을 공유로 승격하지 못한다.projection_card_state=pass일 때만. blocked/needs_review 카드 한쪽이라도 있으면 shareable_geometry_only로 떨어진다(현 reuse 동작 보존) — 위조 pass 상속 금지(C3/C4 fail-closed 일관).resolve_projection_plate_injection과 동일하게 id==hash==card_id AND cp card_state==pass 재조인 검증 후에만 base band를 신뢰(③ band prep, §7-C).두 pass 카드의 base 항목을 (expected_label-by-marker_number, horizontal_band, depth_band, visibility, confidence) 튜플 집합으로 본다.
left↔right 모순만. 다음을 모두 만족하는 base marker일 때만:
marker_number가 양쪽 카드에 모두 존재visibility ∈ {visible, partial} (occluded / out_of_frame 제외)confidence ≥ 0.6 (null/low-conf 제외; 임계 config 노출)horizontal_band가 left↔right 정반대 (center 인접은 모순 아님)foreground↔background 모순은 diagnostic-only(depth_band_conflict_diagnostic 기록, canonical render_action 불변). 조건 미달/depth-only면 withhold 안 함 → shareable_geometry_only 유지(보수적 = reuse 보존).
L05B09의 두 샷 카드(S5_Shot1 vs S5_Shot7) 14 공유 base marker 중, horizontal은 전부 일치(둘 다 left)인데 depth만 2건(mn=4, mn=14)이 foreground↔background로 갈렸다 — 양쪽 confidence가 0.80~0.88로 높았는데도. 같은 공간을 두 번 본 VLM의 depth_band instability. 단일 depth marker로 plate를 쪼개면 false split이 된다 → v0 hard-withhold는 horizontal 충돌만, depth는 진단 기록만. (future: depth withhold는 opt-in + conf≥0.85 + 다중 marker 동시 충돌 시에만 재검토.)
marker_number는 evidence join 키로만 쓰고 prose/prompt로 surface 금지(card schema 명시). band는 enum 집합 비교 — 문자열 의미판단 아님(§11).
needs_new_plate=False = 기존 reuse_existing_plate,
plate_anchor_bg_id = 기존 reuse_target_bg_id. 3a는 reuse 의미를 바꾸지 않는다 — 그룹을 명시화하고 병합 결정에 card corroboration을 더할 뿐.
not_shareable_card_withheld로 강등된 노드는 단순히 render_action=render_new_plate로 남는다(기존 경로).
위치: ⑦ — 3a mirror(⑥) 이후 새 deterministic pass build_reference_dag(...). 입력 = 확정된 canonical render_action(④) 기반 fresh plate 집합 + 기존 selected_refs/mode.
plate_group_id / needs_new_plate — fresh render가 필요한 plate 집합(group anchor들)만 DAG 노드reference_decision.selected_refs + physical_space_id_per_ref + mode (style/space continuity의 LLM 신호)REFERENCE_DAG_FIELDS = (
"ref_tree_parents", # List[str] — 이 plate가 조건으로 거는 부모 plate bg_id
# (selected_refs에서 검증·정규화, 최대 2)
"ref_role_per_parent", # {parent_bg_id: "style" | "space_continuity"}
"render_order_index", # int — DAG topological 순서 (alias 멤버는 부모 뒤)
"max_refs", # int = 2 (보존; 계약 상수)
)
node_index로 충분(검증만).fp_seeded_anchor(identity anchor)는 항상 render_order_index=0 후보(루트).ref_role_per_parent — style vs space-continuity| mode | parent role 의미 |
|---|---|
same_physical_space_view | space_continuity — 같은 방 다른 뷰, plate 일관성 유지가 목적 |
reference_derived | space_continuity 또는 style (parent의 physical_space_id로 판정) |
related_style_new_space | style — 새 방이지만 톤/재질 계승 |
two_refs_distinct_spaces | 두 parent = 서로 다른 space (validator가 이미 distinct 강제) |
role은 mode + physical_space_id_per_ref에서 deterministic 도출 — LLM 재호출/문자 판단 없음.
camera_decision은 삭제 금지. 현재 _corroborates_same_space(camera_unit/look_at_unit), validate_llm_output, router가 모두 read한다.
3a는 camera_decision을 계속 hard-floor 신호로 사용하고 그 위에 card band를 얹는다. projection 필드가 forward SOT이지만 camera_decision은 compat/audit/geometry-floor로 살아있다.
background_render_step.py:934~ 의 alias 의미 불변:
render_action=reuse_existing_plate 노드 → image 0, target plate path를 copy-less alias, entry status="ok" + is_reuse=True + reuse_target_bg_id 보존plate_group_id/needs_new_plate는 이 분류의 상위 표현일 뿐 — render_action/reuse_target_bg_id를 재정의하거나 alias provenance(image 0)를 깨지 않는다background_render_active_audit의 active=expected∩ok / missing_reuse_targets 계약 유지card_id == hash == card_id 재조인 검증(C4 part1 guard)을 3a band read 전에 통과해야 함vlm_output.bg_plate_visible_description / visible_items / marker_registry — 이 brief는 visible_items의 base band만 추가 소비background_master_plan의 sub_location 휴리스틱을
LLM 종합 판단으로 격상하는 일. scene_primary fix로 FP 품질이 회복됐으므로 이 wave에서 건드리지 않는다. 3a/3b는 현 master_plan 출력을 입력으로 받는다.
ref_tree_parents/ref_role_per_parent를 입력으로 받는다.
Codex cross-review 1회 — Required(실행 순서/SOT 문구) 반영 완료(§4-order). D1/D4/D5 lock, D2/D3/D6 합의.
| ID | 질문 | 결정 (lock) |
|---|---|---|
| D1 | card POV 증거가 병합을 강제도 할 수 있나, 철회만 하나? | LOCK — 철회만 (fail-closed). card는 geometry-passed 공유를 withhold만, override-merge 불가. 단 withhold는 ④ route_render_actions_v2 내부에서 canonical render_action 확정 전에 적용(Codex Required). |
| D2 | card-corroborated 공유에 양쪽 pass card 필수? 한쪽만 pass면? | 합의 — 양쪽 pass 필수 → 아니면 shareable_geometry_only(현 동작 보존). 위조 pass 상속 0. |
| D3 | 3a/3b 출력을 checkpoint에 새 필드로? SCHEMA_VERSION bump? | 합의 — additive persisted 필드 → SCHEMA_VERSION 8→9 bump, manifest sync. (C3가 7→8 했던 패턴) |
| D4 | 3a/3b를 build_render_plan_for_fp 내부 pass로? 별도 step으로? |
LOCK — 별도 step 반대, 같은 shot_aware_bg_render_plan 내부 pure helper. 순서 = validate → ③ band prep → ④ card-aware route → ⑤ C3 enrich → ⑥ partition mirror → ⑦ 3b DAG (§4-order). |
| D5 | base band 모순 판정 임계 — 무엇을 withhold 근거로? | FINAL LOCK(dry 실측 반영) — v0 hard-withhold = horizontal left↔right만: 같은 base marker 양쪽 존재 AND 양쪽 visibility∈{visible,partial} AND 양쪽 confidence≥0.6 AND horizontal left↔right. depth fg↔bg는 diagnostic-only(canonical 불변). 근거 = dry §10-2 real card에서 같은 bg 두 샷이 conf 0.80~0.88에도 depth만 갈림(VLM depth instability). |
| D6 | card 부재 fp-less / direct-plate BG는 3a에서? | 합의 — projection_card_state ∈ {not_applicable, not_available} → card corroboration skip, geometry-only partition(현 동작). 실패 처리 아님. |
route_v2 offline 계산(runner /tmp/w21b_wave4_3a_route_v2_dry.py, production write 0). 결과 전부 PASS: (A) card 없으면 17노드 baseline과 byte-identical = OFF/no-card는 behavioral no-op, contract만 확장 / (C) synthetic reuse 쌍 horizontal 모순→render_new_plate 강등, depth-only→reuse 유지+diagnostic, occluded/low-conf 가드 / (B) real card depth instability 발견 → D5 final lock(§5). conf 분포 n=45 median 0.78, 임계 0.6→95% eligible._self_card_base_bands(band prep, reuse 무관) + route_render_actions_v2(geometry + card withhold 통합, canonical render_action SOT) + validator + focused TDD (deterministic 영역만)mirror_plate_partition → PLATE_PARTITION_FIELDS(canonical render_action mirror, SOT 아님)build_reference_dag + REFERENCE_DAG_FIELDS + render_order 검증route_render_actions와 동일 출력)visible_items band enum, camera_unit int, projection_card_state enum, physical_space_id). 고유명사/장면문구 없음.horizontal_band/depth_band enum 집합 비교 + marker_number 정수 join. 한국어 prose(bg_plate_visible_description 등)는 읽지도 파싱하지도 않는다.