Files
houseplan-card/docs/specs/329-junction-limits.md
T
Codex f514fb27fe feat: junction limits refuse the write in every editing surface (#329)
П3 measures the WALL, not the catalogue atom: a short filler segment that
compensates a thickness step (owner's fixture, 5 cm = (30-20)/2) is a legal
continuation of a long same-thickness wall, so the rule walks the maximal
collinear run through the shared nodes before judging the length.

Resize stops at the last allowed position and names the broken rule instead
of the generic "geometry cannot be saved"; the Thickness dialog refuses
through its own toast. Both channels are pinned by demo/smoke_junction_limits
plus three mutants (angle threshold, write barrier, degenerate apex bevel).

Issue: #329
User-Visible: yes
2026-08-27 23:31:42 +03:00

18 KiB

Issue #329 — ограничения стыков стен и честная острая вершина

Статус: ревизия 6 (r1: M1/M2/L1; r2: M1 — AC7; поправки владельца 2026-08-27: §4 — острая вершина без фаски и без зазубрин на гранях; П3 меряет СТЕНУ, а не атом — кейс компенсации перепада толщин). Решения владельца (чат, 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 см. Меряется НЕ отдельный атом каталога, а вся стена: максимальный коллинеарный (±1°) прогон соседних атомов ОДНОЙ толщины, склеенных через общие узлы. Иначе правило запрещало бы законный приём — короткий доборный участок, компенсирующий перепад толщин соседних стен (владелец, 2026-08-27, фикстура houseplan-space-2-2026-08-27_18-24-23.json: атомы 5 см = (30−20)/2 и 14 см, каждый — продолжение стены той же толщины в 349 см). Такой атом короче 20 см законен ровно потому, что стена, частью которой он является, длиннее 20 см.

  • П4. Минимальная дистанция: несмежные узлы — не ближе 5 см; узел — не ближе 5 см к стене, которой он не принадлежит (касание конца о середину чужой стены остаётся T-стыком и валидно — правило про «почти-касание» без инцидентности). Порог абсолютный, в сантиметрах, и НЕ зависит от cell_cm пространства.

  • П5. Просвет комнаты: после вычета тел стен внутренняя область комнаты непуста и её площадь ≥ 25 см². Порог абсолютный и не зависит от cell_cm.

3. Граница применения

  • Проверяются только записи, ИЗМЕНЯЮЩИЕ соответствующую геометрию: рисование и завершение контура, черновики, независимые стены/перегородки, Resize, инструмент «Толщина» (толщина участвует в П3/П5), merge/split.
  • Существующие данные наследуются: запись, не трогающая нарушающий элемент, проходит; миграция (#306/#316), импорт и восстановление из бэкапа не блокируются никогда.
  • Optimize (выравнивание/канонизация) не обязан чинить легаси-нарушения и не имеет права создавать новые (пост-условие прогона).

4. Рендер остаточных острых вершин (легаси)

Поправка владельца (чат, 2026-08-27): плоского среза вершины быть не должно — у остриё обычная нормальная острая вершина.

Для корня «трезубца» доказано исполнением: у вершины ≈9.85° внешний контур падал на плоскую фаску (лимит mitre 4·h против выноса 87 см), а внутренний контур складывался «бабочкой» — вырезы рождались вычитанием этой складки. Контракт:

  • вырожденная вершина (угол < 15° и точка смыкания внутренних граней внутри обеих стен) даёт одну точку внешнего контура ровно в вершине плана: ни двухточечной фаски, ни иглы mitre, уходящей на десятки сантиметров за стены, которые её образуют;
  • внутренний контур сходится в СВОЮ вершину (одна точка mitre) вместо двухточечной фаски, поэтому «бабочки» не возникает вовсе: вычитать нечего, V-вырезов нет, кладка выше сплошная, штриховка непрерывна;
  • на внешних гранях между внутренней и внешней вершиной не остаётся ступенек (вторая поправка владельца): кольцо комнаты — ровно треугольник, каждая сторона длиннее полутолщины, микро-вершин и «щепок» нет;
  • обычные и просто острые пары (#310, вершина вне зоны перекрытия) не меняются: их полный mitre сохраняется.

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 см — отказ (длина < толщины).
  • AC3b (П3, компенсация перепада толщин). Атом 5 см, коллинеарный продолжению стены той же толщины, проходит: правило меряет прогон, а не атом. Проверяется юнитом на фикстуре владельца — 0 нарушений П3.
  • 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 рендерится без «трезубца»: у вершины ровно одна точка внешнего контура, стоящая в вершине плана; внутренний контур — треугольник (ровно 3 точки, не «бабочка»); внешнее кольцо тела — ровно 3 различные вершины без ступенек и микро-сегментов; 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. Откат

Реверт ветки: ограничения исчезают, рендер возвращается к текущему поведению. Данные фикс не переписывает (валидация и рендер, не миграция).