From 8dce8453975121a89263d44243e49059f7000da7 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 27 Aug 2026 19:29:24 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20spec=20#329=20=E2=80=94=20wall=20juncti?= =?UTF-8?q?on=20limits=20and=20an=20honest=20sharp=20apex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: #329 User-Visible: no --- docs/specs/329-junction-limits.md | 151 ++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 docs/specs/329-junction-limits.md diff --git a/docs/specs/329-junction-limits.md b/docs/specs/329-junction-limits.md new file mode 100644 index 00000000..dcafffba --- /dev/null +++ b/docs/specs/329-junction-limits.md @@ -0,0 +1,151 @@ +# Issue #329 — ограничения стыков стен и честная острая вершина + +Статус: ревизия 1. Решения владельца (чат, 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). Нарушение += отказ всей записи, план не изменён, тост называет конкретное правило. + +- **П1. Минимальный угол:** угол между СОСЕДНИМИ по азимуту стенами одного + узла ≥ **15°**. Считается по осям сегментов, для каждого узла кандидата. +- **П2. Валентность узла:** в одном узле сходятся ≤ **6** стен (контурные + атомы, независимые стены и перегородки считаются вместе). +- **П3. Минимальная длина сегмента:** длина ≥ **max(20 см, толщина + сегмента)**. Для нулевых стен (#306) — ≥ 20 см. +- **П4. Минимальная дистанция:** несмежные узлы — не ближе **5 см** (1 + клетка); узел — не ближе 5 см к стене, которой он не принадлежит + (касание конца о середину чужой стены остаётся T-стыком и валидно — + правило про «почти-касание» без инцидентности). +- **П5. Просвет комнаты:** после вычета тел стен внутренняя область комнаты + непуста и её площадь ≥ **25 см²** (1 клетка 5×5). + +## 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-стык (конец на чужой стене) — проходит. +- **AC5 (П5).** Повторение «шпиля» из фикстуры issue рисованием — отказ по + просвету (даже если каждый угол ≥ 15°, но толщины съедают комнату). +- **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. Откат + +Реверт ветки: ограничения исчезают, рендер возвращается к текущему +поведению. Данные фикс не переписывает (валидация и рендер, не миграция).