docs: spec for #310 pair apex and butt-end trim

Issue: #310
User-Visible: no
This commit is contained in:
Codex
2026-08-25 22:46:25 +03:00
parent f2460dc81d
commit c8d56b6c4d
+70
View File
@@ -0,0 +1,70 @@
# ТЗ #310 — Парный острый стык: полное остриё и срез выступающего торца
Issue: https://github.com/Matysh/houseplan-card/issues/310
Статус: ревизия 1.
База: #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) на фикстуре и сценах; юнит на неизменность тела вне окрестности узла.
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. **Без дыр**: детектор `junctionContractHoles` пуст на фикстуре и сценах (юнит + смок).
6. **90° равных толщин**: патч байтово прежний, трим пуст (юнит #309 AC4 остаётся зелёным).
7. **Мутанты**: (a) фаска возвращена в парную ветку — красный; (b) торцевой трим отключён — красный; (c) трим не ограничен окрестностью узла (режет всё тело) — красный. Гварды с типовым прологом tsc+fix-test-build.
## 8. План тестов
Юниты: пара 10+20 (остриё+зубец+адресность), 90°, почти-параллельная пара (бевел-фолбэк), пара с коротким толстым саппортом (#271+#310), детектор на фикстуре. Мутанты §7.7. Golden: пересъёмка spike-сцены, verify остальных.
## 9. Откат
Один revert: чистая рендер-геометрия.