docs: spec #329 — wall junction limits and an honest sharp apex

Issue: #329
User-Visible: no
This commit is contained in:
Codex
2026-08-27 23:29:49 +03:00
parent 8161394178
commit 5266d2f6be
+151
View File
@@ -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_<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.
- Смок: репро-фикстура #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. Откат
Реверт ветки: ограничения исчезают, рендер возвращается к текущему
поведению. Данные фикс не переписывает (валидация и рендер, не миграция).