# Task 2 report — geometry 팩 v4 발행 + selector 배선

Status: **DONE_WITH_CONCERNS** (모두 구현·검증 완료, 아래 concern 2건은 후속
판단 대상이지 미완 작업 아님)

Branch: `feat/w19-w20-bg-planning-cleanup`
Commit: (아래 "커밋" 절)

## 생성한 팩 디렉터리

`prompts/_base/outdoor_marker_geometry/4.202607270248/`

- `3.202607251321/` 의 사본 — `user_template.md` 는 **byte-identical**
  (`diff -q` = SAME).
- `system.md` 는 브리프가 준 20줄을 facing 문단(`... it always lies
  clearly away from it.`) 바로 뒤, `2. camera:` 앞에 그대로 삽입한 것이
  유일한 차이(`diff` 결과 `29a30,49` 한 hunk).
- 기존 팩 디렉터리는 어느 것도 건드리지 않았다(덮어쓰기 0).

## 파일별 변경

### `backend/app/modules/pipeline/outdoor_marker_map.py`

1. `PROMPT_VERSION_MAP` 에 `"4": "4.202607270248"` + 한국어 주석(무엇이
   늘었고 v3 stem 을 그대로 승계한다는 사실).
2. `_ANCHOR_GEOMETRY_PACKS = {"2", "3", "4"}`,
   `_VIEW_GEOMETRY_PACKS = {"3", "4"}` — v4 가 v3 계약을 승계.
3. `MARKER_GEOMETRY_CONTRACT_VERSION` **미변경(3 유지)** + 왜 bump 하지
   않았는지 한국어 주석 추가(Correction B). 요지: 해석된 팩명이 이미 lane
   지문의 `geometry_pack` 으로 실려 v4 샷은 그것만으로 무효화되고, 이 상수는
   팩과 무관하게 모든 lane 샷에 스탬프되므로 올리면 구 팩 프로젝트까지
   소득 없이 전량 재저작한다.
4. `run_marker_geometry_shot`:
   - `_related = prompt_version in _FACING_RELATION_GEOMETRY_PACKS`
   - `build_marker_geometry_schema(..., include_facing_relation=_related)`
   - `shot_block = build_shot_block(shot)` 을 **로컬로 hoist** 해서
     템플릿 채우기(`"shot_block": shot_block`)와 검증
     (`shot_text=shot_block`)이 **같은 문자열**을 쓰게 했다 (Correction A).
   - `validate_marker_geometry(..., require_facing_relation=_related,
     shot_text=shot_block)`
   - docstring 에 v4 대면 관계 절 추가 — 인용 대조 원문이 프롬프트에 실린
     SHOT BLOCK 그대로여야 하는 이유(갈라지면 카메라 메모 줄 인용이
     '원문에 없음'으로 거부돼 재시도 소진)를 명시.

   ※ 브리프 Step 6 의 `shot_text_for_relation: str = ""` 파라미터는 추가하지
   않았다(Correction A). 시그니처 무변경.

### `backend/app/core/steps/shot_conti_light_step.py`

- `LANE_GEOMETRY_PACK_VERSION = "3"` → `"4"` + v4 한국어 주석(주변
  버전 로그와 같은 밀도·어투).
- 호출부는 **무변경** — Correction A 로 `shot_text_for_relation=` 플러밍이
  사라졌다. `run_marker_geometry_shot(...)` 인자 목록 그대로.

### `backend/tests/pipeline/test_outdoor_marker_map.py`

기존 단언 2건 갱신(팩 집합이 늘어 필연):
- `test_v2_geometry_schema_versioning_contract`:
  `_ANCHOR_GEOMETRY_PACKS == {"2", "3", "4"}` + `MARKER_GEOMETRY_
  CONTRACT_VERSION == 3` 이 **의도된 유지**임을 주석으로 고정.
- `test_v3_schema_and_validator_require_view_and_facing`:
  `_VIEW_GEOMETRY_PACKS == {"3", "4"}`.

신규 테스트 5건:
- `test_geometry_pack_v4_gates_facing_relation` — 브리프 Step 3 그대로
  (팩 집합 3종 + `resolve_prompt_version("4")` 해석).
- `test_geometry_pack_v4_system_declares_relation_contract` — 브리프
  Step 3 + **v3 팩 불변** 단언 추가(신규 계약이 구 팩에 새지 않음).
- `test_run_v4_uses_shot_block_as_evidence_source` — ★Correction A 회귀
  가드. 인용 문구를 `description` 이 아니라 `camera_direction` 줄에만 두고
  저작 경로가 1회에 통과하는지 본다. `description` 만 대조하는 구현이면
  재시도 소진 → `AppError`. 같은 테스트가 스키마 게이트(required 2필드)도
  확인한다.
- `test_run_v4_rejects_relation_contradicted_by_coordinates` — 좌표와
  어긋난 선언이 실제로 저작 경로에서 거부되는지(게이트가 켜졌는지).
- `test_run_v3_unchanged_by_v4_gate` — 구 팩 소비 byte-identical.
- `test_lane_step_selector_raised_to_v4` — 팩 발행=selector 배선까지가
  한 단위라는 규칙 자체를 테스트로 고정.

픽스처는 전부 시나리오 중립(`SAMPLE_FIXTURE_SHOT_TEXT` 및 일반 명사구).
새 정규식·substring 의미 판단 없음.

## 검증

요구된 명령:

```
$ cd backend && .venv/bin/python -m pytest tests/pipeline/test_outdoor_marker_map.py \
    tests/core/test_shot_conti_light_lane.py tests/pipeline/test_outdoor_lane_plan.py -q
........................................................................ [ 69%]
...............................                                          [100%]
103 passed in 0.40s
```

TDD 순서 준수: 팩 생성 → 테스트 5건 작성 → 실패 확인(5 failed, 마지막이
`assert '3' == '4'`) → 구현 → 통과.

### 전체 회귀 (풀 유닛)

```
$ .venv/bin/python -m pytest tests -q
64 failed, 7580 passed, 53 skipped, 6 deselected, 7 warnings in 423.43s
```

실패 64건 = 기존군과 동수(MEMORY 인덱스 기록치 64와 일치)이며 전부
step manifest 카운트·experiment slice·gate 계열로 이번 변경과 무관
(`marker_map|conti_light|lane_plan` 매칭 0건).

**엄밀 대조**: 실패 파일 25개를 (a) `git stash` 로 변경 제거한 baseline
HEAD, (b) 변경 적용 상태에서 각각 같은 부분집합으로 돌려 실패 목록을 diff
→ **IDENTICAL_TO_BASELINE (54건 동일)**. 즉 이번 변경이 새로 깬 테스트 0.
(풀 스위트에서 10건 더 실패하는 것은 부분집합 실행과의 차이 = 기존 풀
스위트 상호작용 아티팩트로, baseline 풀 실행 기록치 64와 동수.)

## 브리프에서 틀렸거나 각색이 필요했던 것

1. **Step 6/7 의 `shot_text_for_relation` 플러밍 (사전 고지된 Correction A)**
   — 채택하지 않았다. `build_shot_block` 이 만드는 프롬프트 텍스트에는
   `description` 외에 `등장 인물:` / `연출 카메라 메모 (참고):` /
   `연출 배경 요소 (참고):` 줄이 함께 실린다. 모델이 카메라 메모 줄에서
   인용하면 `description` 대조는 전량 거부 → 재시도 소진. hoist 한 로컬
   `shot_block` 을 그대로 넘겨 "모델이 본 텍스트 == 대조 원문" 을 구조적으로
   보장했다. 부수 효과로 스텝 호출부 변경이 0이 됐다.
2. **Step 5 는 기존 단언 2건을 깬다** — 브리프에 언급이 없다.
   `test_v2_geometry_schema_versioning_contract` 가
   `_ANCHOR_GEOMETRY_PACKS == {"2","3"}` 를, `test_v3_schema_and_
   validator_require_view_and_facing` 가 `_VIEW_GEOMETRY_PACKS == {"3"}` 를
   정확 일치로 잠그고 있어 함께 갱신했다.
3. **Step 2 의 삽입 위치 문구** — 브리프가 지목한 "facing 문단"은
   항목 1(entity_placements) 본문의 마지막 문단이라 삽입 결과가 항목 1 안에
   들어간다. 텍스트가 `the CAMERA you author in (2)` 로 아래 항목을
   전방 참조하므로 의도대로 읽힌다. 위치 변경 없이 그대로 넣었다.
4. **Step 4/8 의 `python -m pytest`** — 그 인터프리터엔 pytest 가 없다.
   지시대로 `.venv/bin/python -m pytest` 로 실행했다.
5. `MARKER_GEOMETRY_CONTRACT_VERSION` 은 Correction B 대로 유지 —
   기존 테스트가 `== 3` 을 이미 잠그고 있어 정합.

## Concerns

1. **인용 corpus 가 SHOT BLOCK 으로 한정된다.** user_template 은 SHOT
   BLOCK 과 함께 `FULL SCENE TEXT` 전문도 보여주는데, provenance 대조 원문은
   SHOT BLOCK 뿐이다. 모델이 씬 전문에서 인용하면 "원문에 없음"으로
   거부된다 — 재시도 힌트에 위반 문구와 문제의 인용이 실려 회복 가능하지만
   (max_attempts=3), 첫 시도 실패율은 올라갈 수 있다. 캔ary 에서 재시도
   횟수를 보고, 잦으면 (a) 팩에 "quote from THE SHOT block" 을 명시하거나
   (b) 대조 corpus 를 shot_block + scene_text 로 넓히는 선택지가 있다.
   corpus 확대는 provenance 보증을 약화시키므로 Task 1 설계 의도와 함께
   판단할 사안이라 이번 커밋에서는 손대지 않았다.
2. **LLM 저작 품질은 미검증.** 결정론 계약(스키마 게이트·좌표 정합·인용
   대조)만 테스트했다. v4 가 실제로 대면 관계를 옳게 저작하는지는 lane
   캔ary/E2E 육안으로만 확인 가능하다 — 유닛 통과를 동작 검증으로 읽지 말 것.
3. `MARKER_GEOMETRY_CONTRACT_VERSION` 미bump 의 결과로, 이미 v4(=현
   selector)로 산출을 만든 프로젝트가 생긴 뒤 v4 팩 자체를 재발행하면
   `geometry_pack` 문자열이 바뀌어 자연 무효화된다. 반대로 **팩은 그대로 둔 채
   validator 계약만** 바꾸는 후속 변경이 온다면 그때는 반드시 상수를 올려야
   한다(주석에 근거를 남겨 뒀다).

## 커밋

로컬 커밋만 수행, push 안 함. 팩 발행과 selector 상향을 같은 커밋에 뒀다.

## 리뷰 지적 수정

리뷰 4건 반영. 팩 `4.202607270248` 은 **미소비**(캔ary/E2E 이력 없음, 직전
미푸시 커밋에서 생성) 이므로 "소비된 팩 불변" 규칙 대상이 아니라고 보고
v5 신설 없이 in-place 수정했다.

### 1. 팩 문구 ≠ validator 측정량 (system.md)

팩은 `away_from_camera` 를 "facing 점이 카메라 origin 에서 **더 멀다**"(반경
거리)로 서술했으나, validator 는 광축 투영
`unit(facing−subject)·unit(look_target−origin)` 을 ±0.25 와 대조한다. 축에서
벗어난 배치에서 두 양은 갈라진다 — 반례: origin(0.5,0.9)/look_target(0.5,0.4)/
subject(0.25,0.5)/facing(0.05,0.45) 은 반경거리 0.636 vs 0.472 로 팩 문구를
만족하지만 dep≈0.243 이라 거부된다. 즉 팩을 그대로 따른 모델이 재시도를
태우는 구조였다.

세 절을 validator 의 측정량 — **몸이 향하는 방향을 카메라 시선
(origin→look_target) 을 따라 읽은 성분** — 으로 재서술했다: away=시선을 따라
더 깊이, toward=시선에 맞서 렌즈 쪽으로, profile=시선을 가로질러. 더해서
"카메라로부터의 거리가 판정 기준이 아니다"를 한 문장으로 못박았다(반례가
정확히 그 오독에서 나온다). 주변 문장("you MUST declare the matching
relation", 거부 경고)은 유지.

### 2. 인용 출처 명시 (system.md)

프롬프트는 `THE SHOT` 과 `FULL SCENE TEXT` 를 모두 보여주지만 provenance
대조 corpus 는 shot block 뿐이다. evidence 지시를 "the exact words from
**THE SHOT block above**", 대조 문구를 "checked against the shot text of THE
SHOT block, so never quote the FULL SCENE TEXT instead" 로 좁혔다. **코드 측
corpus 는 넓히지 않았다** — 직전 리포트 concerns #1 의 선택지 (a) 를 취하고
(b)(corpus 확대)는 폐기.

주의: 기존 테스트가 `"checked against the shot text"` 를 부분문자열로 검사해
문구를 줄바꿈으로 쪼개면 실패한다(실측 1회). 팩 재작문 시 이 구절은 한 줄
안에 두어야 한다.

### 3. 프롬프트 실착 어서션 (test_run_v4_uses_shot_block_as_evidence_source)

기존 테스트는 validator 가 카메라 메모 줄을 받는 것만 증명했다 — `{shot_block}`
치환이 깨져도 전 테스트가 통과하면서 모델은 **본 적 없는 텍스트를 인용하라는
요구**를 받는다(같은 괴리의 반대편). fake 에서 `user` 파트를 포획해 인용
문구가 실제 프롬프트 text 파트에 실렸는지 어서트.

판별력 실측: `"shot_block": shot_block` → `""` 로 일시 파손 시 해당 테스트
FAIL(`assert '뒤에서 따라가는 와이드' in '...THE SHOT ...:\n\n\nFULL SCENE
TEXT...'` — shot 섹션이 빈 채로 남는다), 원복 후 PASS. validator 는 파손
상태에서도 통과했으므로 새 어서션이 유일한 감지 지점임이 확인된다.

### 4. v4 의 v3 계약 상속 고정 (test_geometry_pack_v4_system_declares_relation_contract)

신규 relation 토큰만 검사하던 테스트에 `anchor_en`/`view_left`/
`facing_anchor_en` 존재 어서션 추가. 스키마는 v4 에서도 이 필드들을 required
로 요구하므로, v4 계보 후속 팩이 anchor/wedge 지시만 흘리면 모델이 근거 없이
필수 필드를 지어내야 한다.

### 검증

`.venv/bin/python -m pytest tests/pipeline/test_outdoor_marker_map.py
tests/core/test_shot_conti_light_lane.py -q` → **78 passed**.
LLM 저작 품질은 여전히 미검증(직전 concerns #2 유효) — dep 문구 재서술이
실제 저작 정확도를 올리는지는 lane 캔ary 육안 확인 사안이다.
