# Issue #329 — ограничения стыков стен и честная острая вершина Статус: ревизия 2 (r1: M1 — каналы отказа по поверхностям, M2 — AC5 разделён, L1 — пороги абсолютные). Решения владельца (чат, 2026-08-27, зафиксированы в issue): пять ограничений приняты; проверки действуют на записи, легаси читается как есть; для остаточных острых вершин легаси-планов рендер закрывает остриё честной фаской вместо «трезубца». ## 1. Сценарий и пользовательский результат Сегодня комната-треугольник с вершиной ≈9.9° (стены 15 см) рендерится «трезубцем»: три пика и два V-выреза (репродукция в issue, фикстура `houseplan-space-test2-2026-08-27_16-14-48.json`, комната `rmtbq3k5e-0`). Причина класса: при остром угле внутренние грани стен смыкаются на `полутолщина/tan(θ/2)` от вершины (86 см при 10°/15 см) — весь участок выше физически перекрыт телами обеих стен, и объединение тел с вершинными правилами #309/#310 распадается. После задачи: (а) нарисовать такое больше нельзя — запись отклоняется с точным тостом; (б) уже существующие такие планы рендерятся прилично — остриё закрыто одной фаской, без пиков и вырезов. ## 2. Нормативные ограничения (решения владельца) Все проверки выполняются над кандидатом структурной записи (фронт — в общем барьере перед записью; бэкенд — семантическая дельта-валидация §5). Нарушение = отказ всей записи, план не изменён. Канал обратной связи — штатный канал каждой поверхности (M1 spec-ревью r1): - **рисование/завершение контура, черновики, независимые стены, merge/ split** — тост с конкретным правилом и значением (текущий канал отказов рисования); - **Resize** — существующий контракт ручек (USER-GUIDE «Изменение размеров»): движение упирается в последнюю допустимую позицию; если безопасного шага нет, ручка приглушена и объясняет запрет по hover/focus/нажатию — текст объяснения называет нарушенное правило П1–П5; тост не используется; - **инструмент «Толщина»** — отказ значением в трее по образцу существующего отказа нуля для независимой стены (#313): значение не применяется, причина показывается тостом с названием правила (П3/П5). - **П1. Минимальный угол:** угол между СОСЕДНИМИ по азимуту стенами одного узла ≥ **15°**. Считается по осям сегментов, для каждого узла кандидата. - **П2. Валентность узла:** в одном узле сходятся ≤ **6** стен (контурные атомы, независимые стены и перегородки считаются вместе). - **П3. Минимальная длина сегмента:** длина ≥ **max(20 см, толщина сегмента)**. Для нулевых стен (#306) — ≥ 20 см. - **П4. Минимальная дистанция:** несмежные узлы — не ближе **5 см**; узел — не ближе 5 см к стене, которой он не принадлежит (касание конца о середину чужой стены остаётся T-стыком и валидно — правило про «почти-касание» без инцидентности). Порог абсолютный, в сантиметрах, и НЕ зависит от `cell_cm` пространства. - **П5. Просвет комнаты:** после вычета тел стен внутренняя область комнаты непуста и её площадь ≥ **25 см²**. Порог абсолютный и не зависит от `cell_cm`. ## 3. Граница применения - Проверяются только записи, ИЗМЕНЯЮЩИЕ соответствующую геометрию: рисование и завершение контура, черновики, независимые стены/перегородки, Resize, инструмент «Толщина» (толщина участвует в П3/П5), merge/split. - Существующие данные наследуются: запись, не трогающая нарушающий элемент, проходит; миграция (#306/#316), импорт и восстановление из бэкапа не блокируются никогда. - Optimize (выравнивание/канонизация) не обязан чинить легаси-нарушения и не имеет права создавать новые (пост-условие прогона). ## 4. Рендер остаточных острых вершин (легаси) Для пары стен вершины, чьи внутренние грани смыкаются на расстоянии `d` от вершины, меньшем длины обеих стен (эквивалентно: угол мал настолько, что существует зона полного перекрытия тел): - остриё обрезается **одной плоской фаской**, перпендикулярной биссектрисе, проходящей через точку смыкания внутренних граней; - выше фаски тел нет вовсе (никаких пиков), ниже — обычная кладка; V-вырезы запрещены; штриховка непрерывна; - правило дополняет #309/#310 и включается только в вырожденной зоне (`d` внутри длины стен); обычные острые пары (#310, остриё вне зоны перекрытия) не меняются. ## 5. Бэкенд Семантическая дельта-валидация по образцу существующих (например, jamb margin): правила П1–П5 применяются к элементам, которых касается дельта записи (новые/изменённые сегменты, узлы, комнаты); унаследованные нарушения нетронутых элементов проходят. Ошибки — стабильные коды `junction_limit_` в общем формате `vol.Invalid`/семантических ошибок. Питоновское зеркало обязано давать решения, идентичные фронту. ## 6. Затронутые поверхности и артефакты - `src/wall-thickness.ts` / `src/wall-segment-model.ts` — геометрия проверок П1–П5 (чистые функции) и фаска §4 в построении тел/вершинных правил. - `src/houseplan-card.ts` — вызовы проверок в общем барьере записи, тосты. - `src/i18n/en.json`, `src/i18n/ru.json` — по ключу на правило: `junction.limit_angle`, `junction.limit_valence`, `junction.limit_length`, `junction.limit_distance`, `junction.limit_clearance` (с параметрами значений) + заголовок отказа. - `custom_components/houseplan/validation.py` — дельта-правила §5; `tests_backend` — кейсы принятия унаследованного и отказа нового. - Тесты фронта: юниты на каждую формулу П1–П5 (границы: 14.9°/15°, 6/7 стен, 19/20 см, 4/5 см, пустой/непустой просвет), мутанты в гейте на П1 и §4. - Смок: репро-фикстура #329 — новая попытка нарисовать «шпиль» отклоняется; легаси-конфиг рендерится по §4. - Golden: новая сцена `sharp-apex-legacy-dark` по фикстуре issue (рендер §4); существующие сцены не должны измениться — `golden:verify` обязан подтвердить (в матрице нет вершин острее 15° — проверить прогоном). - `docs/USER-GUIDE.md`/`.ru.md` — раздел об ограничениях рисования (таблица П1–П5); CHANGELOG RU+EN (`User-Visible: yes`). - `docs/CONFIG-COMPATIBILITY.md` — абзац: ограничения действуют на записи, унаследованные данные валидны на чтение/миграцию. - Новых полей конфига нет — `config-field-registry` не меняется. ## 7. Производительность и touch Проверки П1–П4 — O(узлы+сегменты) на запись, вне горячего пути рендера. П5 использует уже вычисляемые физические тела (кэш) на кандидате записи — допускается только на commit, не на каждый жест курсора. Touch-контракт не затрагивается (правки — не жесты). ## 8. AC - **AC1 (П1).** Завершение контура с вершиной 14° отклоняется, тост называет минимум 15° и узел; 16° — проходит. Юнит на границе 15.0°. - **AC2 (П2).** Седьмая стена в узел — отказ; шесть — проходит. - **AC3 (П3).** Сегмент 19 см — отказ; 20 см — проходит; сегмент 25 см при толщине 30 см — отказ (длина < толщины). - **AC4 (П4).** Узел в 4 см от несмежного узла или чужой стены — отказ; 5 см — проходит; T-стык (конец на чужой стене) — проходит. - **AC5a (сквозной сценарий фикстуры).** Повторение «шпиля» из фикстуры issue рисованием отклоняется первым же нарушенным правилом (П1, вершина ≈9.9° < 15°) — сквозной смок поверх AC1. - **AC5b (П5 независим от П1).** Равносторонний треугольник со стороной 60 см и стенами 33 см: все углы 60° ≥ 15°, но inradius ≈ 17.3 см меньше полутолщины 16.5 см плюс минимального просвета — внутренняя область после вычета тел < 25 см², запись отклоняется по П5. Тот же треугольник со стороной 120 см (inradius ≈ 34.6 см) проходит. Юнит с этими числами. - **AC6 (§4).** Легаси-фикстура issue рендерится без «трезубца»: одна фаска, ноль V-вырезов; golden-сцена + пиксельная проверка смоком (нет разрывов тела выше фаски). - **AC7.** Resize/«Толщина», приводящие к нарушению любого из П1–П5, — отказ fail-closed, план байт-неизменен. - **AC8.** Миграция/импорт/restore фикстуры с нарушениями проходят. - **AC9 (§5).** Бэкенд: эхо-запись унаследованного нарушения проходит; запись, добавляющая новое нарушение, отклоняется стабильным кодом. - **AC10.** Идемпотентность: повторный commit валидного конфига байт-иден- тичен; Optimize на легаси-фикстуре не создаёт новых нарушений (AC-пост- условие §3). ## 9. Принято предположительно (поменять свободно) - Угол П1 считается между соседними по азимуту осями в узле (не все пары). - Порог просвета 25 см² и способ его вычисления по кэшу физических тел. - Дельта-семантика §5: «элемент дельты» = сегмент/узел/комната, чьи данные изменились относительно previous. - Имена i18n-ключей и коды ошибок бэкенда. - Фаска §4 перпендикулярна биссектрисе в точке смыкания внутренних граней. ## 10. Риски - Ложные отказы на живых планах у границы правил (особенно П4 при плотной геометрии) — смягчается точными тостами и тем, что унаследованное не блокируется. - П5 на больших пространствах — стоимость union; смягчается кэшем тел и проверкой только на commit. - §4 меняет пиксели вырожденных вершин — по построению матрицы golden таких сцен нет (verify обязан подтвердить), новая сцена фиксирует контракт. ## 11. Откат Реверт ветки: ограничения исчезают, рендер возвращается к текущему поведению. Данные фикс не переписывает (валидация и рендер, не миграция).