From ff623cd49d708d1697e7c29c69ff74f8fee5c4bc Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 25 Aug 2026 20:52:15 +0300 Subject: [PATCH] docs: spec for #309 junction visual mitre limit Issue: #309 User-Visible: no --- docs/specs/309-junction-visual-limit.md | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/specs/309-junction-visual-limit.md diff --git a/docs/specs/309-junction-visual-limit.md b/docs/specs/309-junction-visual-limit.md new file mode 100644 index 00000000..e184b123 --- /dev/null +++ b/docs/specs/309-junction-visual-limit.md @@ -0,0 +1,72 @@ +# ТЗ #309 — Стыковочные узлы: визуальный лимит mitre и устранение паразитных парных патчей + +Issue: https://github.com/Matysh/houseplan-card/issues/309 +Статус: ревизия 1. +База: #302 (полный mitre, веера узлов, детектор «без дыр», сет junction-сцен), #271 (thickLength-лимиты). + +## 1. Проблема + +Полный mitre (#302, решение №5) ограничен только сектором и `MITRE_LIMIT = 4·max(h)`. На экспорте владельца три класса артефактов (все воспроизведены исполнением на dev dc68868): + +- **Шип** — парный стык 10+20 см под острым углом: mitre-вершина `linearWallJoinPatches` уходит за торец (вылет до 4h допустим). +- **Горб** — узел 3×50 см: веер `junctionNodeGeometry` принимает вершину с вылетом 1.95h — пика над крышей узла. +- **Ступенька** — крест 15/15/30/30: `linearWallJoinPatches` строит mitre-патчи для **всех** пар лучей узла; пара «верхний толстый × правый толстый» кладёт квадрат 62.5×62.5 в противоположный квадрант, где сектор принадлежит тонким лучам (полосы 31.3), — контур получает уступ [x −62.5…−31.3]×[y 31.3…62.5] (подтверждено выводом контура union: вершины (−31.2, 62.5)→(−62.5, 62.5)→(−62.5, 31.3) вокруг узла). + +## 2. Решения владельца (2026-08-25) + +1. Визуальный порог вылета mitre: **1.5·max(h)** смежных лучей. Прямые/тупые углы (вылет ≤ ~1.41h) не затрагиваются. +2. Сверх порога сектор закрывается **плоской фаской** перпендикулярно биссектрисе сектора на пороге (miter→bevel), не дугой. +3. Ступенька устраняется: угол закрывается **по границам тонких стен** — паразитные патчи не-соседних пар не рисуются. + +## 3. Контракт + +### 3.1 Визуальный лимит (новая константа) + +`VISUAL_MITRE_LIMIT = 1.5` (src/wall-thickness.ts, рядом с `MITRE_LIMIT`). `MITRE_LIMIT = 4` остаётся верхней санитарной границей построения кандидата; форма среза решается по `VISUAL_MITRE_LIMIT`. + +### 3.2 Парные стыки (`linearWallJoinPatches`) + +Для пары лучей с mitre-кандидатом `hit`: + +- `|hit − node| ≤ 1.5·max(hA, hB)` → патч как сейчас: `[node, pA, hit, pB]`. +- иначе → фаска: патч `[node, pA, cA, cB, pB]`, где `cA`, `cB` — пересечения граней (лучей `pA→hit`, `pB→hit`) с прямой, перпендикулярной биссектрисе сектора и проходящей через точку `node + bis·1.5·max(hA, hB)`. Реализация через клип треугольника полуплоскостью допустима. +- Вырожденные случаи (фаска короче epsilon, cA/cB совпали) деградируют к текущему бевелу `[node, pA, pB]`. + +### 3.3 Узлы ≥3 лучей: только соседние пары + +В точке, где сходятся ≥3 лучей (узел уже покрывается веерами и полосами `junctionNodeGeometry`), `linearWallJoinPatches` не строит патчи для пар, **не соседних по азимуту** в этом узле. Соседние пары либо пропускаются тоже (сектор закрыт веером — предпочтительно, если детектор и golden подтверждают покрытие), либо остаются — решение фиксируется при реализации измерением, лишний слой не оставлять (см. §12 практику #302: мёртвые слои удаляются с доказательством). + +### 3.4 Веера узлов (`junctionNodeGeometry`) + +Правило принятия mitre в веере дополняется тем же порогом: вылет ≤ `1.5·max(hA, hB)` → mitre; больше → веер срезается фаской перпендикулярно биссектрисе сектора на пороге (клип веера полуплоскостью). Рефлексные (наружные) mitre — то же правило. `thickLength`-лимиты (#271) не ослабляются. + +### 3.5 Инварианты + +- **Без дыр (#302):** контрактный детектор `junctionContractHoles` остаётся гейтом; фаска не должна открывать зазор между веером/патчем и полосами. Все 16 junction-сцен + `junction-owner-repro-dark` остаются в наборе; изменившиеся принимаются осознанно (`golden:accept -- --reviewed`) с пересмотром каждой. +- **Без фантомов (#271):** нет новых площадей за пределами `thickLength`. +- Превью рисования (drawWallPreviewD и патчи превью) использует ту же парную логику — форма превью и персиста совпадает. + +## 4. Затрагиваемые поверхности + +`src/wall-thickness.ts` (обе ветки + константа), golden-сет (пересъёмка затронутых junction-сцен + 3 новые сцены), юниты формы, мутационный гейт, `docs/WALL-THICKNESS.md` §3. + +## 5. AC + +1. **Шип:** на паре 10+20 см из экспорта отчёта вылет тела за торец ≤ 1.5·max(h); сектор закрыт фаской. Доказательство: юнит формы (вершины патча) + golden-сцена `junction-309-spike`. +2. **Горб:** на узле 3×50 вылет веера ≤ 1.5·h; фаска перпендикулярна биссектрисе. Доказательство: юнит формы (все вершины вееров узла в радиусе 1.5·h·(1+eps)) + golden-сцена `junction-309-hump`. +3. **Ступенька:** на кресте 15/15/30/30 контур у узла идёт по граням тонких полос (нет вершин контура в зоне [за гранью тонкой, до грани толстой] «чужого» квадранта). Доказательство: юнит по контуру union + golden-сцена `junction-309-step`. +4. **Прямые углы не изменились:** для пары равных толщин под 90° патч байтово идентичен прежнему (вылет 1.41h < 1.5h). Доказательство: юнит. +5. **Без дыр:** `junctionContractHoles` пуст на всех сценах сета и на полном экспорте отчёта (13 комнат/24 перегородки). Доказательство: юнит на фикстуре экспорта + существующий смок-детектор. +6. **Golden:** весь сет зелёный; изменившиеся сцены перечислены в ревью с обоснованием формы. +7. **Мутанты:** (a) `VISUAL_MITRE_LIMIT` обратно в 4 — красный; (b) фаска отключена (всегда mitre) — красный; (c) фильтр не-соседних пар отключён — красный; (d) фаска строится не перпендикулярно биссектрисе (например, хорда pA–pB) — красный. Гварды начинаются с типового пролога tsc+fix-test-build. + +## 6. План тестов + +- Юниты формы: острая пара (шип), 3×50 (горб), крест 15/30 (контур), 90° равных толщин (неизменность), рефлексный сектор. +- Фикстура: `test/fixtures/309-junction-teeth.json` — минимальный вырез трёх узлов из экспорта владельца. +- Golden: 3 новые сцены (билдер junctionNode/сцена из фикстуры, zoomCenter на узлы), пересъёмка затронутых. +- Мутационный гейт: 4 мутанта §5.7. + +## 7. Откат + +Один revert: только рендер-геометрия, ни конфигов, ни миграций.