Files
houseplan-card/docs/specs/329-junction-limits.md
T

180 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Issue #329 — ограничения стыков стен и честная острая вершина
Статус: ревизия 3 (r1: M1/M2/L1; r2: M1 — AC7 доказывает каналы §2). Решения владельца (чат, 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_<rule>` в общем формате `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;
смоки каналов отказа: текст объяснения ручки Resize и текст тоста
«Толщины» (AC7a/AC7b).
- Смок: репро-фикстура #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-сцена + пиксельная проверка смоком (нет разрывов
тела выше фаски).
- **AC7a (Resize, канал §2).** Resize, приводящий к нарушению любого из
П1–П5, упирается в последнюю допустимую позицию; при отсутствии
безопасного шага ручка приглушена, и её объяснение (hover/focus/нажатие)
называет нарушенное правило. План байт-неизменен; тост не показывается.
Смок проверяет и байт-неизменность, и текст объяснения ручки.
- **AC7b («Толщина», канал §2).** Значение толщины, нарушающее П3/П5, не
применяется; показывается тост с названием правила (образец отказа нуля
#313). План байт-неизменен. Смок проверяет текст тоста.
- **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. Откат
Реверт ветки: ограничения исчезают, рендер возвращается к текущему
поведению. Данные фикс не переписывает (валидация и рендер, не миграция).