mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 19:58:50 +00:00
71 lines
11 KiB
Markdown
71 lines
11 KiB
Markdown
# ТЗ #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: чистая рендер-геометрия.
|