docs: address issue 150 spec review

Issue: #150
User-Visible: no
This commit is contained in:
Sergey Matyunin
2026-08-18 17:36:54 +03:00
parent 2667f71bd6
commit fd0b52f3a9
+87 -43
View File
@@ -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;