Files
houseplan-card/docs/specs/310-pair-apex.md
T

11 KiB

ТЗ #310 — Парный острый стык: полное остриё и срез выступающего торца

Issue: https://github.com/Matysh/houseplan-card/issues/310 Статус: ревизия 2 (после SPEC-REVIEW-310-r1: контракт «без дыр» для узлов-двоек — собственная сеточная проверка, детектор #302 их не видит). База: #309 (визуальный лимит и фаска), #302 (веера, детектор дыр), #271 (адресный латеральный трим).

0. Сценарий

Владелец сводит две стены под острым углом (перегородки, свободные стены). На чертеже такие стены сходятся в точку — остриё легитимно. #309 ошибочно срезал остриё фаской, а настоящий дефект — маленький зубец: прямоугольный торец толстой стены выступает вбок за грань тонкой у самого узла — остался (он виден и на исходном скрине владельца при старом полном mitre, и как ступенька на грани в junction-309-spike-dark).

До: остриё срезано плоско (фаска 1.5·h), на грани у узла ступенька-зубец от торца толстой стены. После: чистый клин: остриё доведено до пересечения граней, зубец срезан продолжением грани тонкой стены. Узлы ≥3 лучей не меняются.

1. Решения владельца (2026-08-25, чат)

  1. Узел ровно двух лучей: полный mitre без визуального лимита — вершина на пересечении внешних граней. Фаска VISUAL_MITRE_LIMIT (#309) остаётся только в веерах узлов ≥3 канонических лучей.
  2. Зубец торца срезается по граням острия: выступающая за наружную грань тонкой стены часть тела толстой в окрестности узла обрезается продолжением этой грани (столярный mitre-joint), не по биссектрисе.

2. Контракт

2.1 Парная ветка linearWallJoinPatches

Для узла ровно двух лучей (после мультиволл-скипа #309 в эту ветку попадают только такие) mitre-патч строится как до #309: [node, pA, hit, pB] при существующем hit — без проверки VISUAL_MITRE_LIMIT и без chamferApex; фолбэк [node, pA, pB] для параллельных/вырожденных пар сохраняется. MITRE_LIMIT в парной ветке не применяется (санитарная граница остаётся только в веерах, где её сопровождает фаска).

2.2 Срез выступающего торца (новый адресный трим)

В узле ровно двух лучей с принятым mitre для каждой стены пары строится вычитаемый клин торца: часть её тела, лежащая снаружи наружной грани соседа (полуплоскость грани, образующей сторону острия) и в окрестности узла — вдоль оси стены не дальше 2·halfDepth(собственной) от узла. Практически выступ существует только у более толстой стены; правило симметрично и у тонкой даёт пустое множество. Реализация — второе адресное вычитание рядом с тримом #271 (той же фазой в потребителях), с теми же требованиями: строгая локальность, никакого влияния на тела вне окрестности узла.

Инвариант #302 AC6 обновляется: «вычитания из тела — только два адресных трима: латеральный #271 и торцевой #310»; правка фиксируется в docs/WALL-THICKNESS.md.

2.3 Узлы ≥3 лучей и всё остальное — не меняются

Веера с фаской 1.5·h (#309), их мутанты и сцены junction-309-step/hump-dark, 16 junction-сцен, owner-repro — байтово прежние. Механизм #249 (MULTI_WALL_JOIN_LIMIT, multiWallBevelCutsAt, mitre контуров комнат) не тронут. Превью цепочки использует ту же парную логику — форма превью и персиста совпадает.

3. Скоуп и не-скоуп

Скоуп: парная ветка linearWallJoinPatches, новый торцевой трим и его прокладка в потребителях парных патчей (physicalBodyParts, превью, карта), golden junction-309-spike-dark, юниты/мутанты, docs/WALL-THICKNESS.md. Не-скоуп: веера и фаска #309, узловая карта, #249-механизм, #271-трим, модель данных, hit-зоны, снап, touch (форма кладки, слушателей нет).

4. UX, данные, i18n

Нет новых контролов; нет миграций и полей; нет строк i18n. Меняется только форма кладки у парных узлов на всех поверхностях (общий структурный кэш).

5. Риски

  1. Возврат «хвостов»: полный mitre на почти-параллельных встречных парах формально даёт очень длинное остриё — это осознанное решение владельца (стены, сходящиеся под 5°, сходятся далеко); вырожденные пары (hit нет) сохраняют бевел.
  2. Новое вычитание — дыры на границе среза. Детектор #302 узлы-двойки не видит (buildMultiWallNodeMap требует ≥3 лучей): гейт для пар — парный сеточный контракт из AC5; для узлов ≥3 — прежний детектор. Плюс юнит на неизменность тела вне окрестности узла.
  3. Двойной трим (#271 + #310) в одном узле — интерференция; покрыть юнитом на коротком толстом саппорте пары.

6. Release-артефакты

CHANGELOG RU+EN (User-Visible: yes), docs/WALL-THICKNESS.md (§ mitre-joint пары + инвариант о двух тримах), fingerprint скриншотов, пересъёмка junction-309-spike-dark (--reviewed).

7. AC

  1. Остриё: на паре 10+20 (фикстура 309-junction-teeth) патч — полный mitre [node, pA, hit, pB], вершина на пересечении граней (юнит формы; вылет больше 1.5·h — юнит доказывает отсутствие фаски).
  2. Зубец срезан: контур узла-двойки — чистый клин; проба в прежней зоне зубца (снаружи грани тонкой, внутри прежнего тела толстой у торца) пуста (юнит по physicalBodySet.geometry + пересмотренная сцена junction-309-spike-dark).
  3. Адресность: тело толстой стены на расстоянии > 2·halfDepth от узла байтово прежнее (юнит сравнения колец).
  4. Узлы ≥3 не тронуты: junction-309-step/hump-dark и 16 junction-сцен + owner-repro байтово прежние (golden verify).
  5. Без дыр: два гейта по типам узлов. Узлы ≥3 лучей — прежний junctionContractHoles (фикстура, сцены). Узлы-двойки в него не попадают по построению (buildMultiWallNodeMap отбрасывает узлы <3 лучей) — для них вводится парный сеточный контракт в юните: в квадратной окрестности узла (радиус 3·max(h), шаг ≤ h/4) точка обязана быть кладкой ⇔ она лежит в (полоса A ∪ полоса B ∪ mitre-патч) − (клинья #310); сверка с фактическим physicalBodySet.geometry на узле spike фикстуры 309-junction-teeth и на синтетических парах (острая, 90°, почти-параллельная). Обоснование корректности вычитания: клин лежит строго снаружи наружной грани соседа, а полоса соседа и патч — не снаружи её, поэтому контракт достижим без дыр.
  6. 90° равных толщин: патч байтово прежний, трим пуст (юнит #309 AC4 остаётся зелёным).
  7. Мутанты: (a) фаска возвращена в парную ветку — красный; (b) торцевой трим отключён — красный; (c) трим не ограничен окрестностью узла (режет всё тело) — красный. Гварды с типовым прологом tsc+fix-test-build.

8. План тестов

Юниты: пара 10+20 (остриё+зубец+адресность), 90°, почти-параллельная пара (ограничение reach и бевел-фолбэк), пара с коротким толстым саппортом (#271+#310), парный сеточный контракт (AC5) на spike-узле и синтетике, детектор #302 на фикстуре (узлы ≥3). Мутанты §7.7. Golden: пересъёмка spike-сцены, verify остальных.

9. Откат

Один revert: чистая рендер-геометрия.