# 밀폐 공간 계약에 탈것 전용 낱말이 묶여 있다 (1-D)

## 결함

`confined_structure=true` 로 판별된 샷에 이 문안이 나갔다.

    Before you place anyone, count what the background shows: how many
    steering wheels or control surfaces, how many seats and which way
    each faces, where each mirror sits …

판별은 「좁고 복잡한 실내」 **일반**인데 문안은 **자동차 캐빈 전용**이다.
없는 조종 장치를 세라는 말은 잡음이고, 없는 것을 그리라는 암시가 된다.

★**재는 쪽에도 같은 문구가 있었다.**

    BUILT SPACE — when the shot is inside or around a made structure (a
    car cabin, a wheelhouse, a cockpit, a machine), count and place its
    parts. How many steering wheels or control surfaces are visible …

    … A duplicated control surface (two steering wheels where the
    vehicle has one) …

그리는 쪽과 재는 쪽이 같이 기울면 **재시도해도 안 걸린다** — 그리고 그
재시도는 돈이다. 그래서 **한 커밋에서 둘 다** 올린다.

## 실측 (2026-08-27) — 그림은 한 장도 안 샀다

records 전수. 그 절이 나간 프롬프트 **42건**.

    e716bafb 17 · c7e3b2e7 8 · 5bddbdfc 6 · fdc5f9f0 6 · f1cf927b 4 · da049582 1

대부분은 조종 장치가 실제로 있는 곳(차·버스·기차·조정실)이다. 그 사이에
섞여 있었다:

    Inside the cramped work bay of a bicycle repair shop …
    Inside the compact prosecution-building elevator …
    Inside the compact radio studio at the broadcast microphone …

> ★**감사 보고서의 「7샷 중 4샷」은 재현되지 않는다.** 그 판(골목 끝,
> `da049582`)에서는 **1건**이다. 결함은 실재하지만 그 수치는 아니다.
> 표기 저작 1-A 의 「5/7」과 같은 부류다.

## 고치는 방향 — 종류를 지목하지 않는다

★**structure kind 로 분기하지 않는다.** 보고서는 그렇게 제안했지만, kind
를 받으려면 분류 팩을 새로 저작시켜야 하고 그 값이 또 자가신고다.
**문구를 일반화하면 분기가 필요 없다** — 차에서는 운전대가 「고정 설비」에
포함되고, 자전거 정비소에서는 작업대·공구걸이가 포함된다. 두 경우가 다
나아진다.

    생성  every fixed fitting this space has, how many of each, which way
          each faces, and what any reflective surface among them could
          return from this camera
    판정  Which fixed fittings does this space have, how many of each are
          visible, and is that the right number?
    판정  A duplicated fitting (two of something the space has one of)

이 저장소의 규칙과도 맞는다 — 업종·기종 이름을 계약에 박지 않는다.

★**재료는 남긴다.** 이 절의 목적은 「배경에 있는 설비를 세고 그대로
두라」이고, 그것이 사라지면 좁은 공간에서 설비가 늘거나 움직이는 것을
아무도 안 본다. 시험이 「세라는 말」과 「늘리지 마라」가 양쪽에 남아
있는지 본다.

★**한 낱말도 새로 넣지 않는다.** 처음에 `count what the background
**actually** shows` 로 썼다가 기존 시험이 잡았다 — `actually` 는 이 판의
표적이 아니고, 재지 않은 변경을 섞는 것이다. 원문 그대로 두었다.

## 팩

    still_recipe    15 → **30**  (`GEOM_AUTHORITY_PROMPT_VERSION`)
    multiroll_judge 12 → **14**  (`STILL_JUDGE_PACK_VERSION`)

`multiroll_judge` 는 `"13"` 이 이미 있어 14 로 갔다.

`confined_fp` 는 활성이 v2 이고 **거기엔 이미 그 낱말이 없다** — v1 에만
있었다. 이 판의 대상이 아니다.

## ★한 자리를 고치고 그 낱말만 검사했다 (Codex BLOCK)

처음에 **가운데 문단만** 일반화했다. 같은 파일의 다른 두 줄에 그대로
남아 있었다:

    첫 줄  every panel, control, seat, mirror, window and fixture
    다섯째 which seat the body occupies, which side of the wheel the
           hands are on

`_VEHICLE` 시험이 `steering wheel` 만 봤으니 **bare `wheel` 과 `which
seat` 을 못 잡았다.** 그리고 판정 팩(v14)은 이미 일반화돼 있어 생성·판정
계약이 다시 갈렸다 — 이 판이 막겠다고 한 바로 그 어긋남이다.

셋을 다 고치고 시험을 넓혔다:

    탈것 낱말   steering wheel · car cabin · cockpit · wheelhouse ·
                the vehicle has · **which seat** · **which side of the wheel** ·
                dashboard · helm
    bare wheel  `\bwheels?\b`
    열거        `(panel|control|seat|mirror|window)s?\s*,\s*\w+\s*,`

열거를 막는 것은 통칭 이행 래칫과 같은 논리다 — 열거는 통칭을 여러
낱말로 펼친 것이고, 그것들을 그리라는 암시가 된다.

> 판정 팩에 남은 `each person standing or seated` 는 **사람의 자세**를
> 말하는 것이라 그대로 둔다. 설비 이름이 아니다.

## PR #9 병합본과의 충돌

`still_recipe.py` 의 selector map 과 상수 자리에서 v29(`HANDLED_OBJECT`)와
v30(`GEOM_AUTHORITY`)이 충돌했다. **둘 다 보존**한다 — 1-C 에서 손 절을
기하 권위 팩에서 **갈라 둔 이유**가 그것이다. `GEOM_AUTHORITY` 가 30 으로
올라가도 `HANDLED_OBJECT` 는 29 에 남는다.

v30 팩에서 `handled_object_clause.md` 를 지웠다 — 그 selector 는 그것을
읽지 않는데 남겨 두면 어느 것이 도는지가 흐려진다.

## 시험이 잠그는 것

    두 쪽이 **함께** 움직였는가   `test_the_two_sides_move_together`
    세라는 재료가 남았는가        `test_both_sides_still_ask_for_the_count`
    발행된 팩은 그대로인가        `test_the_old_packs_are_untouched`

---

# 잔여 (같은 날 두 번째 판) — 팩 안 **다른 stem** 이 그대로였다

## 무엇을 놓쳤나

위 판은 `stage_head_geom`(생성)과 `judge_still`(판정) **두 stem 만**
고쳤다. 그런데 로더는 **stem 별로 최신판을 따로 고른다**. 같은 팩의
나머지는 옛 문안 그대로 살아 있었다:

    still_recipe/conti_label_geom   "which seat each body occupies",
                                    "which side of a control the hands are on"
    still_recipe/sketch_label_geom  "the sketched panels, seats, controls
                                     and openings"
    multiroll_judge/critique        "a duplicated control surface,
                                     a person in the wrong seat"
    multiroll_judge/gq_observe_sys  "control surfaces, seats, mirrors" (+ 같은 절)

위 판의 「★한 자리를 고치고 그 낱말만 검사했다」 절이 바로 이 병을
적어 놓았는데, **한 단계 더 깊은 곳에서 같은 일이 반복됐다** — 이번엔
파일 안이 아니라 팩 안이다.

## 무엇에 닿나 — 넓게 말하지 않는다

`critique` 는 `resolve_judge_texts(..., pack_version=…)` 로 실린다.
**어느 selector 냐에 따라 다른 판이 간다:**

    still_recipe_service.py 4곳   pack_version=STILL_JUDGE_PACK_VERSION → v15 ✔
    plate_multiroll.py      2곳   (안 넘김) → 전역 기본 JUDGE_PACK_VERSION="6"
    outdoor_structure_seed  1곳   (안 넘김) → 전역 기본

즉 이 판이 닿는 것은 **스틸 레시피 경로**다(`still_recipe_mode` 기본
`"v1"`). 배경 플레이트·구조물 씨드는 전역 v6 를 계속 쓰고, v6 의
`critique.md` 에는 그 낱말이 남아 있다.

> 전역 기본을 v6 에 묶어 둔 것은 2026-08-07 Codex 리뷰를 받아들인
> **의도된 결정**이다 — 올리면 인물이 없는 산출까지 통째로 재실행된다.
> 그래서 이 판에서 같이 올리지 않았다. 배경 쪽에도 필요하다는 판단이면
> 별건으로 다룬다.

## 범위 밖으로 둔 것 — 재 보고 뺐다

살아 있는 팩 전수(모듈별 stem 별 최신판)를 훑어 19곳이 걸렸는데,
문장을 열어 보니 대부분 결함이 아니다.

    scene_detail/system v47   Rule D 의 **예시**다 — "인물이 타고 있는 큰
                              prop 은 본체도 프레임에 보이게". 자전거·배·
                              수레와 나란히 든 예시라 계약이 아니다.
    confined_fp/*             팩 자체가 「조종 장치가 많은 실내」 전용이고
                              `still_confined_fp_enabled=False` 인 데다
                              **서비스에 배선 자체가 없다**.
    entity_extraction 외 5곳  "control, goal," · "window, archway," 같은
                              건물·서사 낱말. 내 열거 정규식의 오탐이다.

## 시험 — 이번엔 팩 **디렉토리 전체**를 훑는다

    test_no_live_stem_in_either_pack_names_a_vehicle

두 팩의 모든 `*.md` 를 로더로 읽어 낱말·bare wheel·열거를 본다.
옛 판(v30/v14)으로 되돌리면 네 자리를 정확히 짚는 것을 확인했다.

★**낱말 경계로 본다.** `"helm" in text` 는 `helmet` 을 잡는다 — 판정
팩의 "costume head, mask, helmet" 이 실제로 걸렸다. substring 으로 뜻을
재면 오탐이 나고, 오탐을 피하려고 목록을 줄이면 이번엔 진짜를 놓친다.

## 팩·지문

    still_recipe    30 → **34**  (`GEOM_AUTHORITY_PROMPT_VERSION`)
    multiroll_judge 14 → **15**  (`STILL_JUDGE_PACK_VERSION`)

둘 다 `PROMPT_VERSION_MAP` / `JUDGE_PACK_VERSION_MAP` 등록이 필요하다 —
빼먹으면 `resolve_prompt_version` 이 `KeyError` 로 선다(실제로 걸렸다).

지문은 `still_recipe_mode="v1"` 에서 움직인다(`2570d6d1…` → `36fb6b42…`).
`"off"` 에서는 안 움직이는데 그 경로는 스틸 판정 팩을 아예 안 읽는다.

---

# 전역 갈래 (세 번째 판) — 배경 플레이트·구조물 씨드

## 앞 판이 못 닿은 곳

v15 는 **스틸 selector** 만 올렸다. `plate_multiroll`·
`outdoor_structure_seed_step` 은 `pack_version` 을 안 넘겨 전역 기본
`JUDGE_PACK_VERSION="6"` 을 쓰는데, v6 의 `judge_still`·`critique` 에는
탈것 낱말이 그대로였다.

## ★내 시험에 구멍이 있었다 — `control surface` 가 목록에 없었다

앞 판에서 「팩 전체를 훑는다」고 넓혀 놓고 정작 **1-D 의 핵심 어구**를
낱말 목록에 안 넣었다. 그래서 v6 팩을 훑었더니 `critique` 가 `ok` 로
나왔다 — "a duplicated control surface" 가 그대로 있는데도.

> ★그리고 앞 판에서 사용자·Codex 에게 「옛 판으로 되돌려 **네 자리**를
> 짚는 것을 확인했다」고 보고했는데 **둘이었다.** pytest 의
> "Left contains 2 more items" 를 「보인 둘 말고 둘 더」로 읽었다.
> 목록이 두 개짜리라는 뜻이었다. 목록을 메운 지금은 실제로 넷을 짚는다.

목록에 넣은 것: `control surface` · `control surfaces` · `wrong seat` ·
`gear lever` · `yoke`.

## v15 를 그대로 전역으로 올리지 않는 이유

v7~v15 는 **스틸 전용** 내용이 쌓인 갈래다(인물 물리 축·수정 범위 축·
편집 참조 라벨). 그것이 배경 플레이트·구조물 씨드로 넘어가면 계약과
재실행 범위가 섞인다 — 2026-08-07 에 이미 내린 결정이다.

그래서 **v6 를 바탕으로 그 두 stem 만** 일반화한 v16 을 낸다.

    multiroll_judge  전역 6 → **16**  (`JUDGE_PACK_VERSION`)

★`fix_ref_label` 을 같이 넣었다 — 내용이 아니라 **계약** 때문이다.
`_load_optional` 이 selector ≥ 7 에서 그 스템을 필수로 보고 fail-closed
로 막는다(v6 에는 없다). 두 소비자 모두 그 값을 안 읽으므로 동작은
그대로지만, **번호를 올리면 딸려 오는 계약**이 있다는 것은 적어 둔다.

## 돈 — 지문은 움직이고, 자동 재구매는 없다

전역 판이 실리는 자리 셋:

    background_render_step.py:338        plate_multiroll_judge_pack
    outdoor_structure_seed_step.py:514   judge_pack
    outdoor_structure_seed_step.py:1164  judge_fix_pack

셋 다 움직인다. 다만 `config_hash` 불일치는 **BLOCK** 이다
(`step_runner._evaluate_contract_drift` — allowlist 는 `entity_t2i` 뿐).
즉 재개하면 멈춰 서고, 다시 사려면 사용자가 명시로 승인해야 한다.

## 시험 — 번호가 아니라 **내용**으로 잠근다

    test_the_global_pack_does_not_carry_the_still_only_axis

원래 `test_selectors_split_still_from_the_rest` 는 `JUDGE_PACK_VERSION
== "6"` 만 못박았다. 그러면 판을 올릴 때마다 **무슨 뜻이었는지 잊고
숫자만 고치게 된다.** 실제 계약을 잰다 — 스틸 판에만 있는 **PHYSICS
축**이 전역 판에는 없어야 한다. 전역을 v15 로 올려 보고 빨강이 되는
것을 확인했다.

`test_no_live_stem_in_either_pack_names_a_vehicle` 도 **전역 갈래**를
같이 훑도록 넓혔다.

---

# canary (2026-08-27) — 유료 판정 2콜, 그림 0장

## 형태 — 새 주행 대신 기존 그림에 판정만 건다

Codex 권고는 「새 주행 2장」이었다. 그런데 **이 판이 바꾼 것은 판정
문안뿐**이다. 생성은 안 바뀌었으니 새로 그릴 이유가 없고, 새 주행을
태우면 그림값이 붙는다. 그래서 기존 그림 두 장에 전역 v16 판정을
직접 걸었다.

경로는 프로덕션 그대로다:

    resolve_judge_texts(2, judge_name="judge_still")   # 인자 없음 = 전역 팩
      → make_gemini_judge_fn(judge_sys=…, judge_schema=build_judge_schema(…))

실측: `judge_sys` 5,308자, 팩 `16.202608280010`.
도구 = `scratchpad/builtspace_canary.py`.

## A · 탈것 아닌 좁은 실내 (어구 창고 — 조종 설비 **없음**)

> 어두운 창고 내부. 왼쪽에 밧줄과 그물 등 어구가 쌓여 있고, 오른쪽
> 벽에는 밖이 내다보이는 먼지 낀 창문이 있다.

★**없는 조종 장치를 하나도 안 지어냈다.** 그림을 눈으로 봤고 왼쪽
밧줄·그물 더미와 오른쪽 먼지 낀 창이 실제로 그대로다.

## B · 조종 설비 있는 실내 (초계정 조타실)

> 순찰정의 비좁은 조타실 내부입니다. 나무로 된 조타륜, 엔진 제어
> 장치, 계기판이 있으며, 바다와 선착장이 내다보이는 큰 창문들과
> 외부 갑판으로 이어지는 열린 문이 있습니다.

★**재료가 안 사라졌다** — 설비를 여전히 세고 이름을 붙인다. 그림에
나무 조타륜·엔진 레버·계기판·큰 창·열린 문이 다 있다.

## 판정

두 목적이 다 됐다.

    ① 일반화가 **없는 것을 지어내게 하지 않는다**   (A)
    ② 일반화가 **있는 것을 못 세게 만들지도 않는다** (B)

②가 없으면 이 수리는 「금지를 쌓아 재료를 없앤 것」이 된다 — 그
함정은 이 저장소에서 여러 번 밟았다.

## 2차 — `critique` 경로 (Codex 지적)

앞 canary 는 `judge_still` 만 태웠다. v16 에서 바꾼 stem 은 **둘**인데
`critique` 가 한 번도 안 돌았다. 같은 A/B 두 장에 프로덕션 조립
(`make_gemini_critique_fn` + `build_critique_schema()`)으로 태웠다.
실측 `critique_sys` 4,738자, 탈것 낱말 0, `duplicated fitting` 있음.

    A 창고   프롬프트에 없는 인물을 지적 — **없는 조종 설비를 안 지어냈다**
    B 조타실 프롬프트에 없는 인물을 지적하며 보존 대상으로
             "helm, wooden controls, windows" 를 이름으로 든다

## ★양성 대조 — 「결함 0」이 「못 세는 것」인지 갈라야 한다

B 에는 **심어 둔 결함이 없다.** 그러면 `issues 0` 이 「설비를 못 센다」인지
「셀 게 없다」인지 안 갈린다 — era 에서 17/17 「비대상」으로 조사가 꺼졌던
함정과 같은 자리다.

그래서 브리프에 **거짓 사실**을 심었다: 「조타륜이 **둘**, 왼쪽 벽에 둥근
창 **셋**」. 그림에는 조타륜 하나, 왼쪽은 사각 창이다.

> • 왼쪽 벽의 창문이 2개의 큰 사각형 형태임 (프롬프트는 3개의 둥근 창문을 요구함).
> • 조타 장치에 나무 조타륜이 **1개만** 있음 (프롬프트는 나란히 있는 2개를 요구함).

★**일반화한 문안이 설비를 여전히 센다.** 개수도 모양도 짚는다.

## 비용 합계

    judge 2콜 + critique 2콜 + 양성 대조 1콜 = 유료 5콜
    이미지 생성 **0장**
