# W21B-wave-5 — FP over-scale / over-rooming fix (scale-fidelity) — design brief

**Date:** 2026-06-01 · **Status:** design + FP-only canary (code uncommitted, default OFF) ·
**Author:** Claude (peer design with Codex %0, gpt-5.5 xhigh)

## 1. Problem — confirmed with production data, not assertion

The floor plan (FP) step **over-scales / over-rooms** non-apartment and small spaces:
it draws a one-room rooftop unit as a multi-room apartment. This is distinct from the
v8 space-type issue (open-vs-enclosed); it is about **how many rooms/zones** the FP draws.

Ground truth (PROJ `149c554a`, EP1; scenario 옥탑방 L04 = living + small window +
sink + dining table + **one room**):

- **master_plan `floor_plans[].scope` for L04 main** =
  `"Main rooftop unit interior: living area, entry, bedrooms, bathroom circulation and key walls"`
  — a generic apartment boilerplate, not grounded in the actual scene extent.
- **Production FP output for `fp_l04_main`** expanded that scope into **6 structural
  units** (entry threshold / shared living / circulation strip / **primary bedroom** /
  **secondary bedroom** / bathroom service zone) and **22 base markers** = a full
  two-bedroom apartment.
- The FP receives `scene_segments` verbatim but trusts `scope` as the scale source of
  truth, so it inflates. v8's "no invented rooms" wording did not help because the scope
  itself names the rooms.
- Contrast: L03 rooftop `scope = "Rooftop exterior with water tank, clothesline..."`
  reads correctly → over-scale concentrates on enclosed-dwelling scopes.

## 2. North star (user-stated)

The FP exists to render a space **accurately and consistently across every shot** that
uses it. Over-scale is the most damaging error because **every** downstream background
t2i image is drawn against this diagram, so a wrong scale propagates *identically* into
every shot (all rendered as the same wrong apartment). User corrections:

1. Realistic basics (e.g. a bathroom) **should** be included — scene-literal "invent
   nothing" is wrong.
2. No house/dwelling assumption — rooftops and boats are not houses.
3. Generic across scenarios; no hardcoding, no scenario-specific terms.

## 3. Fix locus — FP-primary (master_plan scope untouched this wave)

`master_plan floor_plans[].scope` is consumed in **exactly one place**:
`floor_plan_prompt.py:208` (`build_fp_user_prompt`). Background prompt / BG render do
**not** read `fp.scope`; they read the FP **output** (rendered FP png +
`numbered_elements` + `camera_recommendations`). Therefore:

- The over-scale reaches BG **through the FP output**, not through the scope string.
- Fixing the **FP output** cleans the entire downstream — `scope` need not change for
  the canary.
- FP already has verbatim `scene_segments` + shots = richer ground truth than the lossy
  ≤100-char `scope`.
- Blast radius: changing master_plan regenerates bg catalog / FP / render (large
  cascade). FP-only reuses the existing master_plan cp → regenerate FP + render only
  (isolated, cheap canary).

**Decision (Codex-agreed):** FP-primary. master_plan scope cleanup is a deferred
**layer-2**, taken only if the canary shows FP cannot overcome the polluted scope.

## 4. Design — v9 prompt pack (opt-in, default OFF)

New pack `prompts/_base/floor_plan_prompt/9.202606011400/`:
- `schema.json`, `user_template.md`: **byte-identical to v8** (no new input — FP already
  receives scenes/shots). Output contract unchanged → v6-family validators apply.
- `system.md`: keeps v8's space-type relay (orthogonal trait) and adds:
  - a new **"Spatial scale fidelity"** section (applied second, after space-type), and
  - scale-fidelity folded into **Rule 11** (self-fidelity).

### Scale-fidelity contract (the new rules — generic, no template)
- **`scope` is a coverage label, not a room inventory.** Never multiply rooms from its
  words — "bedrooms" in scope ≠ draw two bedrooms.
- **Count rooms/zones from evidence:** a room/zone earns a place only if scenes/shots
  enter/use/reference it, or realistic plausibility requires it at the described scale.
- **Realistic minimal support — include but do not inflate.** A compact single-room
  dwelling = one main room + only essential support (small bathroom, kitchenette);
  never a second bedroom / hallway / extra private rooms. Test for each added space:
  *does it keep shots spatially consistent and plausible, or does it invent unseen real
  estate?*
- **No house/office/vehicle template** — match the actual described extent.

This is **not** a hardcoded "bathroom yes, 2nd bedroom no" rule; it is the generic
**plausibility-support vs invented-real-estate** distinction, keyed on the
scene-derived identity + scale.

### Wiring (already generic — selector value only)
- `PROMPT_VERSION_MAP["9"] = "9.202606011400"`
- `_V6_COMPATIBLE_SELECTORS = {6,7,8,9}`
- `config.floor_plan_prompt_version: Literal[...,"9"] = "5"` (default unchanged)
- step already reads the selector + calls `derive_fp_space_roles` (v8 path reused).
- Tests: unknown-selector case moved "9" → "10".

## 5. Canary (completion = visual, per absolute rule)

FP-only, production cp unchanged, PNG in `/tmp/fp_v9_canary/`. Targets:
- **fp_l04_main** (옥탑방, PRIMARY) — must drop from 6 units / 2BR to ~1 room + small
  bathroom (+ kitchenette).
- fp_l03_rooftop_exterior (옥상) — stays open.
- fp_l17_deck + fp_l18_wheelhouse (배) — deck open, wheelhouse single room.
- fp_police_office_main (경찰서), fp_l10_roadside_site (대조).

Mechanical proxy (NOT the gate): `base_structural_unit` count before→after. Gate = user
visual A/B on the gallery.

**Escalation:** if L04 still drifts to bedrooms (scope too strong), add layer-2
(master_plan scope boilerplate removal) and re-canary.

## 6. Boundaries / absolute-rule compliance
- STEP5-B `floor_plan_light_sidecar` (skeleton render) **untouched**.
- Default selector unchanged ("5") → production byte-identical; v9 is opt-in.
- v7/v8 packs not overwritten; v9 is a new dir.
- No master_plan / render / schema change in this wave.
- **No code meaning-decision** (prompt-only); **no char/substring pattern**; **no
  scenario hardcoding**. Completion judged by visual canary + iteration, not TDD.
- commit/push 0 until user GO.
