diff --git a/docs/specs/150-wall-thickness-transition.md b/docs/specs/150-wall-thickness-transition.md index 4dce6e98..7cf6fe26 100644 --- a/docs/specs/150-wall-thickness-transition.md +++ b/docs/specs/150-wall-thickness-transition.md @@ -1,8 +1,8 @@ # Issue #150 — Точная геометрия коллинеарного перепада толщины стен - **Issue:** https://github.com/Matysh/houseplan-card/issues/150 -- **Редакция:** первая редакция для независимого ревью; статус задачи определяется - только метками issue +- **Редакция:** r2 после `SPEC-REVIEW-150-r1`; статус задачи определяется только + метками issue - **Тип / приоритет:** bug / P2 - **Оценка:** пользовательская ценность 8/10; ценность для разработки 8/10; сложность 7/10, риск 8/10 @@ -21,15 +21,24 @@ Editor. пользователь выбирает внешнюю стену одной дочерней комнаты, задаёт `10 см` и нажимает «Применить ко всем стенам комнаты». -**До → после, без терминов реализации:** сейчас стены выбранной комнаты рядом -с разделителем выглядят примерно вдвое тоньше указанного значения; после -исправления они имеют полные 10 см до самой точки разделителя, соседняя комната -остаётся без толщины, а переход между участками виден как точная ступень. +**До → после, без терминов реализации:** сейчас после Split толщина выбранной +комнаты сохраняется правильно, но у точки разделителя не заканчивается и +визуально продолжается по внешним стенам соседней комнаты, для которых задано +`0`. После исправления выбранные участки имеют полные 10 см до точки +разделителя, соседняя комната остаётся без толщины, а граница видна как точная +ступень. + +Первоначальное описание «примерно 5 вместо 10» уточнено после ревью. На +скриншоте внутренняя половина внешней стены действительно занимает 5 см, потому +что действующий контракт стены — `centreline ±½`; внешняя половина лежит на +белом поле и читается по штриховке. Полная глубина выбранного участка равна +10 см. Реальный дефект скриншота — отсутствие границы `10 → 0`: штриховка +10-сантиметрового body продолжается снаружи вдоль нулевого фасада соседа. Задача поддерживает J4 и J6 из `docs/SCOPE.md`: штатное редактирование должно создавать измеримо правдивую и стабильно читаемую архитектуру. -## 2. Воспроизведение и подтверждённая причина +## 2. Красное воспроизведение на `dev` и подтверждённая причина На конфигурационном уровне команда уже работает правильно: @@ -37,15 +46,40 @@ Editor. - общий разделитель получает `10 см` как одна физическая стена; - внешние стены соседней комнаты остаются `0`. -Ошибка появляется при построении канонической геометрии. После Split общий -внешний фасад является одной collinear boundary, но на ней встречаются соседние -atomic intervals `10 → 0`. Boolean union комнат схлопывает child-room vertex, а -последующая variable-offset exterior envelope не сохраняет локальный перепад -как жёсткую поперечную ступень. В результате wall shell у endpoint разделителя -сужается/интерполируется и выглядит как половина заданной толщины. +Повторная проверка после `SPEC-REVIEW-150-r1` выполнена на текущем `dev` +`a05aa5d` двумя путями: через фактические обработчики Plan Editor +`_splitClick()` → `_wallThickClick()` → `_wallThickApply(true)` и через +исполняемые `setWallThicknessForRoom()` → `wallIntervals()` → +`wallBodiesGeometry()` с production-параметром `coordScale = 1000`. -Это не дефект сохранения команды «Применить ко всем» и не исправляется -дублированием wall key, CSS stroke или визуальной накладкой только в Plan. +Красный fixture: прямоугольник `[100,100]–[900,900]`, вертикальный Split в +`x = 500`, слева `10 см`, справа `0`, разделитель `10 см`. Данные дают: + +- левый верхний interval: `cm = 10`; +- правый верхний interval: `cm = 0`; +- divider: `cm = 10`; +- ожидаемая полная глубина: `8.333333` render units. + +Но текущий masonry path содержит верхнюю грань +`M 95.833… 95.833… L 900 95.833…`: глубина 10 см протянута через весь правый +нулевой interval до `x = 900`. Поэтому probe на правом участке, например +`[700, 96]`, ошибочно находится внутри masonry, хотя обязан быть вне его. +Сечение выбранного левого участка при этом полное — `8.333333` units; тест, +проверяющий только толстую сторону, дефект не обнаруживает. + +Подтверждённая причина находится в `exteriorBoundaryProfile()`. Коэффициент +`t` точки разбиения нормирован на диапазон `0…1`, но сравнивается с `eps` в +render units. При обычных `pitch = 1/240` и `coordScale = 1000` используемый +`eps` равен примерно `0.6667`; проверки `t > eps && t < 1 - eps` становятся +невыполнимыми, а дедупликация `t` тем же допуском также размерностно неверна. +Breakpoint `x = 500` после boolean union не материализуется, и весь +коллинеарный edge получает depth того interval, на который попал его midpoint. +Воспроизведение обязано использовать production scale; проверка с +`coordScale = 1` не ловит ошибку. + +Это не дефект сохранения команды «Применить ко всем», не изменение семантики +`centreline ±½` и не исправляется дублированием wall key, CSS stroke или +визуальной накладкой только в Plan. ## 3. Решения владельца @@ -269,46 +303,56 @@ interface WallEntry { Controls, dialogs, tool sequence, labels и focus не меняются. Новые locale keys не требуются. -Plan Editor остаётся desktop-first. Touch editor — best effort, но safety floor -обязателен: тот же сохранённый config на touch не может получить другую -физическую геометрию из-за pointer type. View и kiosk fully supported и обязаны -показывать тот же полный stepped body. +Plan Editor остаётся desktop-first. **Touch editor: best effort / intentionally +degraded.** Safety floor обязателен: тот же сохранённый config на touch не может +получить другую физическую геометрию из-за pointer type. View и kiosk fully +supported и обязаны показывать тот же полный stepped body. `prefers-reduced-motion` не затрагивается. Исправление geometry не добавляет animation и не использует цвет как единственное доказательство толщины. ## 12. Acceptance criteria -1. Точный сценарий issue сохраняет data profile `10 см` на всех внешних стенах - выбранной комнаты и shared divider, `0` на остальных внешних стенах соседа. -2. Нормальное сечение каждого 10-сантиметрового участка до endpoint имеет - полные 10 см: 5 см наружу и 5 см внутрь, в численном пересчёте render units. -3. На endpoint разделителя фасад меняется `10 → 0` точной поперечной ступенью; - нет taper, half-depth strip, переноса 10 см на соседа, зуба или щели. -4. Матрица `0 ↔ 10`, `10 ↔ 20`, `1 ↔ 100` и равных толщин корректна в обоих - направлениях, при обратном winding и перестановке/переименовании комнат. -5. Shared divider сохраняет свою полную толщину, соединяется внутри и не +1. **[unit]** Production-scale fixture из §2 сохраняет data profile `10 см` на + внешних стенах выбранной комнаты и shared divider, `0` на внешних стенах + соседа; тест явно передаёт `coordScale = 1000`. +2. **[unit]** Нормальное сечение каждого 10-сантиметрового участка до endpoint + имеет полные 10 см: 5 см наружу и 5 см внутрь, в численном пересчёте render + units. +3. **[unit + golden]** На endpoint разделителя фасад меняется `10 → 0` точной + поперечной ступенью; probes на нулевой стороне находятся вне masonry, нет + taper, переноса 10 см на соседа, зуба или щели. +4. **[unit]** Матрица `0 ↔ 10`, `10 ↔ 20`, `1 ↔ 100` и равных толщин корректна + в обоих направлениях, при обратном winding и перестановке/переименовании + комнат. +5. **[unit]** Shared divider сохраняет полную толщину, соединяется внутри и не расширяет фасад наружу. -6. Paper, clean floor и displayed area следуют локальным depths и точной - ступени без клина/утечки пола. -7. Plan, View/kiosk, static и hidden Iso имеют одинаковый wall/floor outline; - Glow и солнце блокируются ровно нарисованным body. -8. Openings около/через transition используют локальные depths и не создают - ложных cuts или двойной fill. -9. Регрессии #123 (corner Split facade) и #141 (independent junctions) остаются - закрытыми; nested/courtyard/open-span fixtures не меняются ошибочно. -10. Старый config исправляется вычисляемо без save/migration и остаётся - совместимым с rollback. -11. Structural geometry кешируется; ordinary HA tick не повторяет topology. -12. Boolean failure остаётся fail-closed, не показывает известную ошибочную - half-depth геометрию. +6. **[unit]** Paper, clean floor и displayed area следуют локальным depths и + точной ступени без клина/утечки пола. +7. **[smoke + golden]** Plan, View/kiosk, static и hidden Iso имеют одинаковый + wall/floor outline; Glow и солнце блокируются ровно нарисованным body. +8. **[unit + smoke]** Openings около/через transition используют локальные + depths и не создают ложных cuts или двойной fill. +9. **[unit + smoke]** Регрессии #123 (corner Split facade) и #141 (independent + junctions) остаются закрытыми; nested/courtyard/open-span fixtures не + меняются ошибочно. +10. **[unit + ревью кода]** Старый config исправляется вычисляемо без + save/migration и остаётся совместимым с rollback. +11. **[unit + ревью кода]** Structural geometry кешируется; ordinary HA tick не + повторяет topology. +12. **[unit + ревью кода]** Boolean failure остаётся fail-closed и не + воскрешает raw per-room rings. ## 13. Проверки и доказательства ### Unit — данные и точная геометрия -- fixture из issue: Split rectangle, all walls `0`, all-room `10` для одной - child room; assert effective intervals и shared wall ownership; +- красный production-scale fixture из §2: Split rectangle, all walls `0`, + all-room `10` для одной child room; assert effective intervals, shared wall + ownership, полную толстую сторону и отсутствие masonry probes на нулевой + стороне; +- отдельная нормализованная вариация с `coordScale = 1` не заменяет fixture с + `coordScale = 1000`; - cross-section/point containment на interior участках доказывает exact `±h_i`, а не только сравнивает bbox/скриншот; - transition faces совпадают с divider endpoint и перпендикулярны centreline;