mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
committed by
claude[bot]
parent
ae7fb621e7
commit
6ca9aacebc
@@ -0,0 +1,345 @@
|
||||
# Issue #172 — нулевой Split-разделитель не получает ложную толщину
|
||||
|
||||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/172
|
||||
- **Редакция:** первая редакция для независимого ревью; статус определяется только метками issue
|
||||
- **Тип / приоритет:** bug / P2
|
||||
- **Оценка:** пользовательская ценность 8/10; сложность 6/10; риск 7/10
|
||||
- **Область:** редактор Плана, каноническая геометрия стен, View/static, hidden Iso, clean floor, Glow и солнце
|
||||
- **Модель данных:** без изменений и миграции
|
||||
- **Связано:** #123, #150, `docs/WALL-THICKNESS.md`, `docs/ARCHITECTURE.md`, `docs/TOUCH-SUPPORT.md`
|
||||
|
||||
## 1. Сценарий и персона
|
||||
|
||||
**Персона:** администратор дома, который строит и уточняет архитектуру плана в
|
||||
desktop-редакторе.
|
||||
|
||||
**Поверхность и момент:** на плане есть Г-образная комната с заданной толщиной
|
||||
наружных стен. Пользователь выбирает «Разделить», начинает линию во внутреннем
|
||||
углу и заканчивает на другой стене. Линия почти продолжает одно из плеч угла,
|
||||
но отклоняется от него на доли градуса или несколько градусов.
|
||||
|
||||
Задача поддерживает J4 и J6 из `docs/SCOPE.md`: редактор должен сохранять
|
||||
правдивую геометрию, а штатное разделение комнаты не должно создавать стену,
|
||||
которую пользователь не задавал. Общая физическая модель также сохраняет
|
||||
согласованность J1/J2/J3 между Plan, View и световыми эффектами.
|
||||
|
||||
## 2. Что человек увидит до и после
|
||||
|
||||
**До:** разделитель сохранён без толщины, но визуально вдоль него появляется
|
||||
треугольный клин: на одном конце он почти нулевой, на другом вырастает до
|
||||
толщины примыкающей стены.
|
||||
|
||||
**После:** нулевой разделитель остаётся без кладки по всей длине; существующие
|
||||
толстые стены заканчиваются локально в точках примыкания и не растягиваются
|
||||
вдоль разделителя. Небольшой нарисованный угол сохраняется без автокоррекции.
|
||||
|
||||
## 3. Проблема и подтверждённая причина
|
||||
|
||||
Дефект воспроизведён на `origin/dev` SHA `a05aa5d` с production-параметрами
|
||||
`pitch = 1/240`, `coordScale = 1000`, `cell_cm = 5`, `GRID_PITCH = 1000/240`.
|
||||
|
||||
Fixture — Г-образный полигон
|
||||
`[100,100]–[900,100]–[900,800]–[600,800]–[600,400]–[100,400]`, наружные стены
|
||||
`15 см`, Split от `[600,400]` к `[900,402.5]` или `[900,405]`.
|
||||
|
||||
Слой данных корректен:
|
||||
|
||||
- оба room-owned интервала общей границы классифицируются как `shared`;
|
||||
- их эффективная толщина равна `0`;
|
||||
- положительной `WallEntry` для разделителя нет;
|
||||
- рендер не должен материализовать такую запись.
|
||||
|
||||
Ошибка возникает в `insetContour()` / `outsetContour()`. При стыке положительного
|
||||
offset с нулевым и небольшом отклонении от коллинеарности удалённый mitre
|
||||
отбрасывается по `MITRE_LIMIT`. Bevel-ветка сохраняет только смещённую точку
|
||||
толстой грани и теряет исходную вершину нулевой грани. Следующий участок контура
|
||||
соединяет эту точку с дальним концом разделителя и создаёт taper-клин.
|
||||
|
||||
На красном fixture поперечное сечение ложной кладки растёт вдоль нулевого
|
||||
разделителя приблизительно от `0` до полной глубины стены `15 см`. При точном
|
||||
`0°` специальная коллинеарная ветка уже сохраняет ступень, поэтому дефект легко
|
||||
пропустить тестом только ортогональной геометрии.
|
||||
|
||||
Это не дубликат:
|
||||
|
||||
- #123 сохраняет наружный фасад при Split из вершины;
|
||||
- #150 сохраняет breakpoint между коллинеарными внешними интервалами разной толщины;
|
||||
- #172 убирает ложный taper вдоль нулевой внутренней общей границы.
|
||||
|
||||
## 4. Решения владельца
|
||||
|
||||
Владелец принял defaults Q1–Q4 18.08.2026:
|
||||
https://github.com/Matysh/houseplan-card/issues/172#issuecomment-5329384353
|
||||
|
||||
1. Split-разделитель с `cm = 0` не содержит masonry по всей длине; толстая
|
||||
примыкающая стена заканчивается локальным стыком без taper.
|
||||
2. Инвариант применяется ко всем углам, толщинам, направлениям и winding.
|
||||
3. Фактический угол Split сохраняется; snapping к нормали не добавляется.
|
||||
4. Исправление едино для Plan, View/static, hidden Iso, clean floor/room fills и
|
||||
препятствий Glow/солнца, без миграции конфигурации.
|
||||
|
||||
## 5. Скоуп
|
||||
|
||||
В задачу входят:
|
||||
|
||||
1. переход между положительной толщиной и точным нулевым offset в вершине
|
||||
room wall profile;
|
||||
2. обе последовательности `h → 0` и `0 → h`, прямой и обратный winding;
|
||||
3. выпуклые и вогнутые вершины, острые, тупые, почти коллинеарные и
|
||||
ортогональные допустимые углы;
|
||||
4. все допустимые толщины `1…100 см`;
|
||||
5. Split с одним или двумя endpoint на толстых стенах;
|
||||
6. локальный cap/step без кладки в интерьере нулевого разделителя;
|
||||
7. сохранение существующего mitre/bevel-контракта для пар `h1 ↔ h2`, где обе
|
||||
толщины положительны;
|
||||
8. единая каноническая геометрия всех перечисленных поверхностей;
|
||||
9. корректное отображение уже сохранённых планов без записи конфигурации;
|
||||
10. unit, targeted browser smoke, visual fixture, документация и RU/EN changelog.
|
||||
|
||||
## 6. Не входит в задачу
|
||||
|
||||
- snapping почти перпендикулярного или почти коллинеарного Split;
|
||||
- изменение допустимости, маршрута кликов, диалога или сохранения Split;
|
||||
- автоматическое назначение толщины новой общей границе;
|
||||
- изменение инструмента «Толщина», `Перегородка` или открытых контуров;
|
||||
- переработка модели `rooms`, `walls`, `partitions`, `open_spans` или `wall_columns`;
|
||||
- новые настройки cap/join, пользовательские переключатели или i18n-тексты;
|
||||
- изменение `MITRE_LIMIT` для стыков двух положительных толщин;
|
||||
- миграция, schema version, backend или импорт/экспорт;
|
||||
- публикация изометрии как публичной функции.
|
||||
|
||||
## 7. Контракт поведения
|
||||
|
||||
### 7.1. Нулевой разделитель
|
||||
|
||||
Пусть `D` — общая граница двух комнат после Split, а её эффективная толщина
|
||||
равна `0`.
|
||||
|
||||
- Внутренняя часть `D`, за вычетом локальных областей примыкания существующих
|
||||
толстых стен, не пересекается с канонической masonry geometry.
|
||||
- Толщина вдоль `D` не интерполируется и не образует taper.
|
||||
- В точке перехода `h ↔ 0` сохраняется вершина нулевой грани и формируется
|
||||
локальный cap/step толстой стены.
|
||||
- Локальная область примыкания ограничена физической half-depth примыкающей
|
||||
стены и геометрическим epsilon; она не может расти пропорционально длине `D`.
|
||||
- Правило симметрично относительно порядка комнат, направления границы и winding.
|
||||
|
||||
### 7.2. Существующие стены
|
||||
|
||||
- Толстая наружная или общая стена сохраняет заданную полную глубину до точки
|
||||
примыкания.
|
||||
- Наружный фасад до и после Split сохраняет контракт #123.
|
||||
- Две положительные соседние толщины продолжают использовать действующие
|
||||
mitre/bevel и `MITRE_LIMIT`; исправление нулевого перехода не превращает их
|
||||
в принудительные плоские caps.
|
||||
- Строго коллинеарный переход сохраняет существующую точную ступень.
|
||||
- Проёмы и независимые физические тела не меняют свою ассоциацию или форму.
|
||||
|
||||
### 7.3. Геометрия Split и данные
|
||||
|
||||
- `splitRoomPath()` сохраняет фактически выбранные endpoints и промежуточные
|
||||
точки; угол не округляется к нормали.
|
||||
- Большая и новая комнаты сохраняются по действующему контракту.
|
||||
- Нулевой разделитель не получает `WallEntry` и не материализуется при чтении.
|
||||
- Рендер не мутирует `rooms`, `walls` и другие persisted-поля.
|
||||
- Уже сохранённая конфигурация исправляется вычисляемо сразу после обновления.
|
||||
|
||||
### 7.4. Все потребители
|
||||
|
||||
Один результат wall-body geometry используется в:
|
||||
|
||||
- Plan editor;
|
||||
- View, kiosk и `houseplan-space-card`;
|
||||
- hidden Iso/floor footprint;
|
||||
- paper, room fills и clean-floor contours;
|
||||
- физических препятствиях Glow и солнечных лучей.
|
||||
|
||||
Renderer-specific SVG mask, отдельная геометрия только для Plan или подавление
|
||||
штриховки без исправления физического тела не выполняют контракт.
|
||||
|
||||
## 8. Архитектурные ограничения реализации
|
||||
|
||||
1. Исправление живёт в общей variable-offset логике `src/wall-thickness.ts`, а
|
||||
не в обработчике кликов Split.
|
||||
2. Для стыка, где ровно один из соседних offsets положителен, контур обязан
|
||||
сохранить как смещённую точку толстой грани, так и исходную вершину нулевой
|
||||
грани в детерминированном порядке для inset и outset.
|
||||
3. Обе половины кольца вместе формируют локальный cap; ни одна не соединяет
|
||||
offset-точку с дальним узлом нулевой грани.
|
||||
4. Пары двух положительных offsets проходят прежний bounded-mitre/bevel путь.
|
||||
5. Результат остаётся валидным для boolean union/difference/intersection и не
|
||||
полагается на порядок полигонов.
|
||||
6. Canonical wall geometry остаётся единственным источником для drawing,
|
||||
clean-floor и light/sun occlusion.
|
||||
7. Новых persisted-полей, фоновых writes и renderer-specific caches нет.
|
||||
|
||||
Предполагаемые файлы:
|
||||
|
||||
- `src/wall-thickness.ts`;
|
||||
- `test/wall-thickness.test.mjs`;
|
||||
- новый `demo/smoke_zero_divider_taper.mjs` либо эквивалентное расширение
|
||||
узкого Split-smoke;
|
||||
- `demo/golden/matrix.mjs`, fixture/harness и `test/golden-matrix.test.mjs`,
|
||||
если новая visual-сцена оформляется отдельно;
|
||||
- `docs/WALL-THICKNESS.md`, при необходимости `docs/ARCHITECTURE.md`;
|
||||
- `docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`;
|
||||
- три синхронные поставляемые копии bundle.
|
||||
|
||||
## 9. Модель данных, compatibility и миграция
|
||||
|
||||
Форматы `RoomCfg` и `WallEntry` не меняются. Новых ключей, aliases и
|
||||
compatibility-полей нет.
|
||||
|
||||
- legacy midpoint-only keys продолжают читаться;
|
||||
- lossless `a`/`b` endpoints сохраняют текущий смысл;
|
||||
- чтение и рендер не записывают конфигурацию;
|
||||
- прямая и обратная миграции не нужны;
|
||||
- откат на предыдущую версию не требует восстановления данных, но возвращает
|
||||
визуальный дефект.
|
||||
|
||||
Это соответствует `docs/CONFIG-COMPATIBILITY.md`: меняется только вычисляемая
|
||||
геометрия существующих данных.
|
||||
|
||||
## 10. UX, i18n, accessibility и touch
|
||||
|
||||
Новых controls, текстов, focus/keyboard semantics или ARIA нет. Новые RU/EN
|
||||
i18n-ключи не требуются.
|
||||
|
||||
Split остаётся desktop-first. Touch editor: **best effort / intentionally
|
||||
degraded**, но одна и та же сохранённая геометрия не может зависеть от типа
|
||||
указателя. View, kiosk и static являются блокирующими поверхностями и обязаны
|
||||
совпадать с desktop Plan.
|
||||
|
||||
Темы, `prefers-reduced-motion`, цвет и opacity стен не меняются.
|
||||
|
||||
## 11. Критерии приёмки
|
||||
|
||||
- **AC1 (`unit`):** production-scale fixture из §3 с углами `0,477°` и
|
||||
`0,955°` сохраняет `cm = 0` на обоих room-owned интервалах разделителя, а
|
||||
его внутренняя часть не пересекается с masonry geometry. Тест красный на
|
||||
исходном `dev` из-за taper-клина.
|
||||
- **AC2 (`unit`):** матрица `h = 1, 15, 100 см`, `h → 0` и `0 → h`, углы по
|
||||
обе стороны от точной линии, оба направления и оба winding не создаёт
|
||||
кладку вдоль нулевой грани; размер локального cap ограничен half-depth и
|
||||
epsilon, а не длиной грани.
|
||||
- **AC3 (`unit`):** точный коллинеарный и ортогональный переходы остаются
|
||||
корректными; пары `1 ↔ 15`, `15 ↔ 100` и равные положительные offsets
|
||||
сохраняют действующий mitre/bevel-контракт.
|
||||
- **AC4 (`unit`):** наружная геометрия Г-образной комнаты до/после Split
|
||||
сохраняется, а clean-floor union равен room union минус каноническая кладка;
|
||||
нулевой разделитель не удаляет треугольник пола.
|
||||
- **AC5 (`unit`):** `splitRoomPath()` сохраняет фактический угол fixture без
|
||||
snap, render helpers не меняют сериализованные `rooms`/`walls`, миграция и
|
||||
новые config keys отсутствуют.
|
||||
- **AC6 (`smoke`):** targeted browser-smoke выполняет реальный Split из
|
||||
вогнутого угла с отклонением около `1°`, подтверждает нулевые persisted
|
||||
интервалы и отсутствие taper в SVG Plan.
|
||||
- **AC7 (`smoke`):** тот же smoke доказывает побайтовое/геометрическое
|
||||
совпадение канонической wall-body path в Plan, View/kiosk, static и hidden
|
||||
Iso и совпадение masonry geometry световых препятствий; конфигурация после
|
||||
рендера не меняется.
|
||||
- **AC8 (`golden`):** детерминированная visual-сцена Г-образного Split явно
|
||||
показывает нулевой разделитель без клина и без изменения фасада. Baseline
|
||||
принимается только через `golden:accept -- --reviewed` по полному Linux CI
|
||||
artifact на предрелизном этапе.
|
||||
- **AC9 (`unit` + `smoke`):** существующие регрессии corner Split, wall
|
||||
junctions, wall thickness и opening tunnels остаются зелёными; проёмы и
|
||||
независимые тела не меняются.
|
||||
- **AC10 (`ревью кода`):** исправление находится в общей variable-offset
|
||||
геометрии; renderer-specific masks/branches и изменения persisted schema
|
||||
отсутствуют.
|
||||
- **AC11 (`ревью кода`):** новых DOM-узлов, событий, таймеров, сетевых запросов,
|
||||
HA services и privacy/security surfaces нет.
|
||||
|
||||
## 12. План автотестов и гейтов
|
||||
|
||||
### Unit
|
||||
|
||||
В `test/wall-thickness.test.mjs` добавить:
|
||||
|
||||
1. точный красный fixture §3 с проверкой данных и площади пересечения masonry
|
||||
с внутренней полосой разделителя;
|
||||
2. параметрическую матрицу толщин, углов, направлений и winding;
|
||||
3. negative-regression для двух положительных offsets и exact-collinear step;
|
||||
4. clean-floor/exterior invariants и immutability.
|
||||
|
||||
Проверка должна уметь падать: временное возвращение старой bevel-ветки обязано
|
||||
красить как минимум AC1/AC2. Ревьюер фиксирует эту проверку в code review.
|
||||
|
||||
### Browser smoke
|
||||
|
||||
`node demo/smoke_zero_divider_taper.mjs` (имя можно изменить при сохранении
|
||||
однозначной связи с #172) выполняется локально перед `S7-code-review`. Smoke
|
||||
использует `show_borders: true`, чтобы Plan/View/static/Iso сравнивали реально
|
||||
отрисованные стены.
|
||||
|
||||
### Golden и pre-release
|
||||
|
||||
Новая visual-сцена добавляется в матрицу, но baseline не принимается ради
|
||||
зелёной ветки. Capture/diff рассматриваются на предрелизном Linux-гейте; полный
|
||||
golden, smoke и performance выполняются перед бетой по `PROCESS.md` §11.4.
|
||||
|
||||
### Обязательный implementation loop
|
||||
|
||||
```text
|
||||
npm run typecheck
|
||||
npm test
|
||||
npm run build
|
||||
node demo/smoke_zero_divider_taper.mjs
|
||||
```
|
||||
|
||||
После build три bundle-копии должны совпадать побайтово. Backend/HA harness не
|
||||
нужен: Python и Home Assistant integration не затрагиваются.
|
||||
|
||||
## 13. Риски, performance и security
|
||||
|
||||
| Риск | Мера |
|
||||
|---|---|
|
||||
| Исправление нулевого перехода меняет обычные положительные углы. | AC2/AC3 разделяют `h ↔ 0` и `h1 ↔ h2`; положительные пары сохраняют прежний путь. |
|
||||
| Новый cap создаёт самопересекающийся contour при вогнутом угле. | Матрица convex/concave, оба winding и boolean-validity assertions. |
|
||||
| SVG исправлен, но floor/light продолжают видеть клин. | AC4/AC7 проверяют общий geometry object и потребителей. |
|
||||
| Тест исключает слишком большую endpoint-зону и пропускает taper. | Endpoint allowance ограничен физической half-depth + epsilon, дополнительно проверяются несколько внутренних сечений. |
|
||||
| Golden принимается без review. | Только полный Linux artifact и `--reviewed`; до этого хранится candidate/diff. |
|
||||
|
||||
Performance-влияние практически отсутствует: число вершин variable-offset
|
||||
контура увеличивается максимум на одну точку в каждом переходе `h ↔ 0`.
|
||||
Асимптотика, cache keys и частота вычислений не меняются. Отдельный performance
|
||||
budget не нужен; общий pre-release performance smoke остаётся обязательным.
|
||||
|
||||
Security/privacy влияние отсутствует: нет ввода HTML/CSS, сетевых запросов,
|
||||
service calls, новых разрешений или данных HA.
|
||||
|
||||
## 14. Откат
|
||||
|
||||
Исправление откатывается одним frontend-коммитом вместе с тестами и
|
||||
документацией. Persisted schema не меняется, поэтому обратная миграция и cleanup
|
||||
не нужны. Откат возвращает ложный taper, но не повреждает сохранённые планы.
|
||||
|
||||
Feature flag не добавляется: это исправление общей физической геометрии, а не
|
||||
экспериментальная функция.
|
||||
|
||||
## 15. Release-артефакты
|
||||
|
||||
- пользовательские записи в `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` в том
|
||||
же implementation-коммите (`User-Visible: yes`);
|
||||
- обновление `docs/WALL-THICKNESS.md`; `docs/ARCHITECTURE.md` — только если
|
||||
уточняется общий variable-offset контракт;
|
||||
- targeted unit и browser smoke;
|
||||
- visual fixture/candidate и reviewed golden baseline по правилам §12;
|
||||
- синхронные `dist/houseplan-card.js`, HA frontend и demo bundle;
|
||||
- screenshots вне golden не требуются;
|
||||
- backend, migration и отдельные security/performance artifacts не требуются;
|
||||
- issue закрывается только после включения в опубликованную бету.
|
||||
|
||||
## 16. Принятые технические предположения
|
||||
|
||||
1. Точный нулевой offset — единственный триггер нового cap/step пути; малые
|
||||
положительные толщины не приравниваются к нулю.
|
||||
2. Endpoint allowance в тестах вычисляется из half-depth примыкающей стены и
|
||||
общего geometry epsilon, а не задаётся долей длины разделителя.
|
||||
3. Исправление выполняется симметрично в inset и outset; отдельная post-render
|
||||
маска не допускается.
|
||||
4. Новая visual-сцена может переиспользовать fixture #123, если остаётся
|
||||
однозначно проверяемой и не скрывает L-shaped near-normal case.
|
||||
5. Если во время реализации обнаружится отдельный дефект boolean-библиотеки,
|
||||
он оформляется новой issue; #172 не расширяется до общей замены offset engine.
|
||||
@@ -92,6 +92,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
| [#141](https://github.com/Matysh/houseplan-card/issues/141) Бесшовные стыки перегородок и открытых контуров | [141-wall-junctions.md](141-wall-junctions.md) |
|
||||
| [#157](https://github.com/Matysh/houseplan-card/issues/157) Тип проёма «Открытый проём» | [157-open-passage.md](157-open-passage.md) |
|
||||
| [#150](https://github.com/Matysh/houseplan-card/issues/150) Точная геометрия коллинеарного перепада толщины | [150-wall-thickness-transition.md](150-wall-thickness-transition.md) |
|
||||
| [#172](https://github.com/Matysh/houseplan-card/issues/172) Нулевой Split-разделитель не получает ложную толщину | [172-zero-divider-taper.md](172-zero-divider-taper.md) |
|
||||
|
||||
## Правило актуализации
|
||||
|
||||
|
||||
Reference in New Issue
Block a user