mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
482 lines
35 KiB
Markdown
482 lines
35 KiB
Markdown
# Issue #141 — бесшовные стыки перегородок и открытых контуров
|
||
|
||
Статус: **ТЗ на ревью**
|
||
Дата: 2026-08-14
|
||
Тип: `bug` · приоритет: `P2` · оценка ценности: 8/10 · сложность/риск: 7/10
|
||
|
||
Issue: [#141](https://github.com/Matysh/houseplan-card/issues/141)
|
||
Ветка: `issue/141-wall-joints`
|
||
Канонические документы: [SCOPE](../SCOPE.md),
|
||
[WALL-THICKNESS](../WALL-THICKNESS.md), [CANVAS](../CANVAS.md),
|
||
[UX-MODES](../UX-MODES.md), [LIGHT](../LIGHT.md), [SUN](../SUN.md),
|
||
[ISOMETRIC](../ISOMETRIC.md), [TOUCH-SUPPORT](../TOUCH-SUPPORT.md).
|
||
|
||
Решения владельца Q1–Q3 и defaults аналитики приняты 2026-08-14 в issue #141.
|
||
|
||
## 1. Сценарий и продуктовый контекст
|
||
|
||
Основная персона — домашний администратор, который в desktop Plan editor с нуля
|
||
рисует план либо позднее добавляет к нему независимые перегородки. После второго
|
||
клика он должен оценивать уже финальную форму стены, а не ждать замыкания комнаты
|
||
или перехода в View, чтобы обнаружить клин, щель либо зуб в стыке.
|
||
|
||
Это часть J4/J6 из `docs/SCOPE.md`: GUI должен доводить администратора от пустого
|
||
холста до правдивого плана без внешнего редактора и сохранять этот план правдивым
|
||
при дальнейших изменениях. View и киоск не получают новых действий, но обязаны
|
||
показывать ту же физическую кладку и те же световые препятствия.
|
||
|
||
## 2. Что человек увидит до и после
|
||
|
||
**До:** два соединённых толстых сегмента выглядят как наложенные прямоугольники:
|
||
у прямого угла остаётся ступень/зуб, у непрямого — клиновидный разрыв; открытая
|
||
комнатная стена исправляется только после замыкания контура.
|
||
|
||
**После:** намеренно соединённые сегменты сразу, включая live rubber-band,
|
||
образуют одно ровное тело с тем же ограниченным mitre/bevel, которое человек
|
||
видит у готовой комнаты; после клика, замыкания или перехода в View форма не
|
||
прыгает.
|
||
|
||
## 3. Проблема и подтверждённая причина
|
||
|
||
1. `drawWallPreviewD()` использует `outset − inset` только для закрытого
|
||
контура. Открытый путь создаётся как несколько независимых прямоугольников.
|
||
2. `partitionBody()` правильно создаёт один сегмент с плоскими торцами, но
|
||
`physicalBodies()` передаёт набор таких прямоугольников без топологии узлов.
|
||
3. Polygon union удаляет перекрытие прямоугольников, но не может догадаться,
|
||
какой отсутствующий сектор следует достроить в endpoint↔endpoint углу.
|
||
Поэтому прямой и косой угол показывают один механизм в разном масштабе.
|
||
4. Закрытая комната проходит другой путь: смежные рёбра одного polygon получают
|
||
offset-line intersection с `MITRE_LIMIT`, поэтому её углы нормализованы.
|
||
5. Full/static/isometric render уже объединяют независимые тела с кладкой, но
|
||
Glow и солнце местами обходят исходные прямоугольники отдельно. В результате
|
||
визуальная щель может стать реальным световым просветом, а внутренняя грань
|
||
перекрытия — ложным окклюдером.
|
||
6. Предположенный в исходном описании отдельный дефект свободного торца по
|
||
приложенному скриншоту не подтверждён: видимый нижний левый зуб находится в
|
||
стыке двух сегментов. Одиночный корректный сегмент уже имеет плоский cap.
|
||
|
||
## 4. Решения владельца
|
||
|
||
1. Исправление охватывает точные endpoint↔endpoint и endpoint↔line (T)
|
||
соединения, которые могут создать инструменты «Контур» и «Перегородка» после
|
||
#137, между active/saved draft, partition и готовой комнатной стеной.
|
||
2. Rubber-band до клика использует тот же join, что сохранённое тело; клик не
|
||
меняет внешнюю форму стыка.
|
||
3. Свободный конец остаётся плоским. Отдельные round/square cap, настройка cap
|
||
или новый UX не вводятся.
|
||
4. Существующие сегменты не дробятся и сохранённый config не переписывается ради
|
||
вычисляемого T-узла.
|
||
5. Случайное X-пересечение без endpoint сохраняет текущую union-семантику и не
|
||
становится новым persisted node.
|
||
|
||
## 5. Scope
|
||
|
||
В задачу входят:
|
||
|
||
1. вычисляемая топология точных узлов у `room_drafts` и `partitions`;
|
||
2. бесшовные endpoint↔endpoint углы: прямые, острые и тупые;
|
||
3. endpoint↔line T-соединения с partition, saved draft и комнатной стеной;
|
||
4. open-draft preview после размещения сегмента и rubber-band до следующего
|
||
клика/замыкания;
|
||
5. собственная толщина каждого уже размещённого сегмента и текущая толщина
|
||
rubber-band;
|
||
6. одна joined geometry для full View, Plan, static card, hidden isometric,
|
||
clean floor/area, Glow, sun и проверки источника внутри кладки;
|
||
7. сохранение per-object geometry для выбора, hit testing, drag, properties,
|
||
удаления и Undo/Redo;
|
||
8. unit, targeted production-bundle smoke, golden и performance regression
|
||
coverage;
|
||
9. пользовательская и архитектурная документация плюс RU/EN changelog.
|
||
|
||
## 6. Non-scope
|
||
|
||
В задачу не входят:
|
||
|
||
- изменение snap tolerance, приоритетов или overlay из #137;
|
||
- автоматическое дробление room/draft/partition в persisted config;
|
||
- превращение partition в комнатную границу, разбиение комнаты или HA area;
|
||
- новый тип persisted node/junction и миграция старых планов;
|
||
- новый cap/join selector, round cap, декоративные окончания и материалы;
|
||
- изменение opening/open-span, wall thickness, Split или Resize semantics;
|
||
- исправление #138 про замыкание контура по углам существующей комнаты;
|
||
- изменение furniture magnet, opening placement либо selection UX;
|
||
- touch parity редактора сверх действующего safety floor;
|
||
- новые backend API, HA service calls, зависимости или i18n-тексты.
|
||
|
||
## 7. Контракт геометрии и поведения
|
||
|
||
### 7.1. Что считается соединением
|
||
|
||
1. Узел вычисляется только из координат, уже совпавших по действующему
|
||
grid/wall-bound snap и geometry epsilon. Визуально близкие, но разные точки
|
||
автоматически не стягиваются.
|
||
2. Endpoint↔endpoint — общий конец двух или более segment centrelines.
|
||
3. Endpoint↔line — endpoint одного segment точно лежит на сплошном интервале
|
||
другого. Для вычисления тела проходящий segment можно временно представить
|
||
двумя incident rays, но его persisted запись остаётся одной.
|
||
4. Opening и open-span gap не является физической линией комнатной стены и не
|
||
создаёт join с ней. Independent partition/draft не получает проём только
|
||
потому, что совпал с room opening.
|
||
5. Пересечение двух внутренних точек без endpoint не создаёт node metadata;
|
||
наложившиеся физические объёмы по-прежнему соединяются обычным boolean union.
|
||
|
||
### 7.2. Endpoint↔endpoint corner
|
||
|
||
1. Исходные segment bodies растут на `½ thickness` по обе стороны centreline.
|
||
2. Внешние offset faces смежных incident rays пересекаются в mitre point.
|
||
3. Если расстояние до точки пересечения не превышает
|
||
`MITRE_LIMIT × max(adjacent half-depth)`, отсутствующий сектор включается в
|
||
joined body.
|
||
4. Если предел превышен либо устойчивого пересечения нет, применяется bounded
|
||
bevel. Бесконечный spike, self-intersection, щель и незаполненный зуб
|
||
недопустимы.
|
||
5. Для коллинеарных продолжений результат — один прямой wall run без внутреннего
|
||
торца. Для разворота назад/нулевой длины новая неоднозначная кладка не
|
||
создаётся.
|
||
|
||
### 7.3. T-соединения и комнатные стены
|
||
|
||
1. Branch, endpoint которого лежит на другом solid segment, входит в тело
|
||
проходящей стены без щели и без видимой внутренней butt-face.
|
||
2. Для готовой комнатной стены authoritative room ring/exterior shell не
|
||
перестраивается по правилам partition. Joined independent geometry
|
||
объединяется с уже готовой `wallBodiesGeometry()` после opening cuts, как и
|
||
сейчас; partition не пробивается совпавшим room opening.
|
||
3. Подключение к exterior corner не меняет наружный фасад комнаты за пределами
|
||
реального independent body и не возвращает регрессию #123.
|
||
4. Разная толщина incident segments не усредняется и не записывается обратно.
|
||
Joined contour переходит между их реальными offset faces без прозрачной щели.
|
||
|
||
### 7.4. Свободные торцы и сложные узлы
|
||
|
||
1. Узел степени 1 заканчивается текущим плоским cap на исходном endpoint.
|
||
2. В узле степени 3+ объединяется volume всех incident bodies и только
|
||
ограниченные join patches. Алгоритм не должен заполнять произвольный круг
|
||
вокруг узла или расширять кладку дальше bounded mitre/bevel envelope.
|
||
3. Zero-length, non-finite или уже отклонённый schema segment не создаёт body или
|
||
join patch и не ломает остальные валидные тела.
|
||
4. Порядок records, id и направление `a↔b` не меняют результат.
|
||
|
||
### 7.5. Live preview
|
||
|
||
1. После каждого законченного segment открытый контур показывает joined body
|
||
немедленно; замыкание не является первым моментом нормализации.
|
||
2. Если есть cursor candidate, rubber-band присоединяется к предыдущему segment
|
||
и к точному target endpoint/line тем же bounded join.
|
||
3. Предыдущие segment используют сохранённые `room_drafts[].segments[].cm`, а
|
||
rubber-band — текущее session thickness. Изменение поля толщины не
|
||
перерисовывает уже сохранённые segment новой толщиной.
|
||
4. При клике outer contour rubber-band становится committed contour без скачка;
|
||
допустимо только изменение preview/editor styling.
|
||
5. При закрытии простой комнаты все уже существовавшие вершины совпадают с
|
||
нормализованной формой готовой комнаты в пределах geometry epsilon.
|
||
|
||
### 7.6. Единая физическая семантика
|
||
|
||
Для валидного сохранённого плана один structural result определяет:
|
||
|
||
- full Plan/View wall-body path;
|
||
- `houseplan-space-card` wall-body path;
|
||
- hidden isometric wall footprint/faces;
|
||
- subtraction из clean floor и вычисляемой площади;
|
||
- Glow barriers и fail-dark source placement;
|
||
- sun occluders.
|
||
|
||
Ни один consumer не должен заново обходить raw segment rectangles как будто их
|
||
внутренние butt-faces являются наружными стенами. Raw per-object body остаётся
|
||
доступным только там, где требуется identity редактируемой записи: hit,
|
||
selection frame, drag/properties/delete и history snapshot.
|
||
|
||
### 7.7. Ошибки вычисления
|
||
|
||
1. На валидных schema/snap inputs joined pass обязан быть детерминированным.
|
||
2. Malformed legacy input не должен превращать видимую стену в прозрачность:
|
||
fallback остаётся conservative/opaque и не пишет config.
|
||
3. Boolean failure не разрешается маскировать исчезновением кладки, световым
|
||
проходом или миграцией данных. Диагностический fallback может вернуть raw
|
||
bodies с прежним визуальным дефектом, но plan и light должны fail consistently
|
||
opaque.
|
||
|
||
## 8. Архитектурный контракт реализации
|
||
|
||
### 8.1. Structural physical frame
|
||
|
||
Реализация вводит один чистый вычисляемый frame (точное имя свободно), который
|
||
получает room walls/open cuts/openings, partitions, room drafts, columns и scale
|
||
inputs и возвращает как минимум:
|
||
|
||
- raw bodies с source identity для редактора;
|
||
- joined independent geometry;
|
||
- объединённую room + independent masonry geometry для presentation/occlusion;
|
||
- стабильный structural fingerprint либо данные для существующего cache key.
|
||
|
||
Join topology строится из centreline segments и их half-depth, а не из SVG DOM,
|
||
stroke-linejoin или raster measurement. Columns остаются обычными closed bodies
|
||
и участвуют в union, но не становятся incident wall rays.
|
||
|
||
### 8.2. Canonical consumers
|
||
|
||
`wallBodiesGeometry()` остаётся канонической точкой room masonry либо получает
|
||
эквивалентный единый wrapper; full/static/isometric не создают разные join
|
||
алгоритмы. `_lightBarriers` получает outer/hole rings объединённой opaque geometry,
|
||
а не внутренние грани перекрывающихся raw rectangles. Clean-floor и source guard
|
||
используют тот же joined result.
|
||
|
||
Preview использует тот же pure node/join primitive, но может строить только
|
||
активный path плюс локально необходимые target segments. Он не мутирует и не
|
||
инвалидирует saved structural frame на каждый pointermove.
|
||
|
||
### 8.3. Cache и invalidation
|
||
|
||
1. Saved topology пересчитывается только при structural geometry change:
|
||
coordinates, cm, room/opening/open-span topology, `cell_cm` или grid/scale.
|
||
2. HA state, theme, hover, cursor, Glow brightness и animation tick не входят в
|
||
saved geometry key.
|
||
3. Live rubber-band допускает дешёвый локальный расчёт на pointermove; полный
|
||
boolean union всего дома на каждый move запрещён.
|
||
4. Full/static/isometric/light consumers переиспользуют structural result либо
|
||
эквивалентный immutable geometry, а не копируют O(N²) node search.
|
||
5. Cache остаётся bounded и очищается существующими lifecycle hooks.
|
||
|
||
## 9. Модель данных, compatibility и миграция
|
||
|
||
- `rooms`, `walls`, `room_drafts`, `partitions`, `wall_columns`, `openings` и
|
||
`open_spans` не меняют schema.
|
||
- Backend validation, storage version, import/export и Python model не меняются.
|
||
- Старые планы получают исправленную вычисляемую форму при чтении без записи.
|
||
- Hover, preview, render и cache warm-up не вызывают config/layout/storage write.
|
||
- Undo/Redo продолжает хранить существующие geometry snapshots; одна partition
|
||
остаётся одной записью и одним history action.
|
||
- Прямой и обратной миграции нет.
|
||
|
||
## 10. UX, i18n, accessibility и touch
|
||
|
||
Новых controls, диалогов, toasts, focus/keyboard semantics и текстов нет;
|
||
следовательно, новые RU/EN i18n keys не требуются.
|
||
|
||
Plan editor остаётся desktop-first. На touch/coarse pointer новый hover parity не
|
||
обещается, но если существующий tap создаёт segment, его сохранённая geometry
|
||
обязана быть той же; pinch, pointercancel и synthetic click не могут сохранить
|
||
лишний segment. View/киоск остаются полностью поддержанными и получают ту же
|
||
исправленную форму без новых взаимодействий.
|
||
|
||
Selection frame и handles могут показывать границу конкретной редактируемой
|
||
записи поверх общего wall body; это editor chrome, а не альтернативная физика.
|
||
Forced colours и `prefers-reduced-motion` не получают новой ветки поведения.
|
||
|
||
## 11. Производительность и безопасность
|
||
|
||
**Производительность:** large-house fixture уже содержит 60 partitions. Новая
|
||
топология не должна пересчитываться на HA state tick, создавать unbounded cache
|
||
или ослаблять действующие budgets. Перед бетой обязательны exact-SHA performance
|
||
smoke и Full Performance; изменение budget допускается только отдельным решением
|
||
процесса, не в #141.
|
||
|
||
**Безопасность:** HA permissions, services, locks, network, HTML и внешние данные
|
||
не меняются. Главный safety invariant — нарисованная кладка, clean floor и
|
||
окклюдер совпадают. При ошибке вычисления приоритет у непрозрачного fail-closed
|
||
поведения, а не у сохранения декоративной картинки.
|
||
|
||
## 12. Acceptance criteria
|
||
|
||
- **AC1 (`unit` + `golden`; разработчик):** open draft из двух segment с общим
|
||
endpoint при 90°, остром и тупом угле образует один body без gap, tooth,
|
||
overlap seam или неограниченного spike; до замыкания и после замыкания уже
|
||
размещённые вершины геометрически совпадают в пределах epsilon.
|
||
- **AC2 (`unit` + `smoke` + `golden`; разработчик):** rubber-band до клика
|
||
использует тот же bounded mitre/bevel к предыдущему segment и к выбранному
|
||
endpoint/line target; commit не меняет outer contour, кроме preview styling.
|
||
- **AC3 (`unit`; разработчик):** каждый segment сохраняет собственную толщину,
|
||
`MITRE_LIMIT = 4` ограничивает spike, excess angle переходит в bevel,
|
||
collinear continuation не имеет внутреннего cap, а degree-1 endpoint остаётся
|
||
плоским.
|
||
- **AC4 (`unit` + `smoke`; разработчик):** две и более saved partitions с точным
|
||
endpoint↔endpoint соединением дают одинаковую joined geometry для прямого и
|
||
косого угла независимо от record order, id, направления и допустимой разной
|
||
толщины.
|
||
- **AC5 (`unit` + `smoke`; разработчик):** endpoint↔line соединение с partition,
|
||
saved draft и solid room wall образует бесшовный T-body; target record не
|
||
дробится, room/partition/draft config до и после render/hover побайтно
|
||
эквивалентен.
|
||
- **AC6 (`unit`; разработчик):** near-miss вне geometry epsilon остаётся
|
||
раздельным, opening/open-span gap не создаёт room-wall join, incidental X без
|
||
endpoint сохраняет union semantics, zero-length/non-finite segment не портит
|
||
валидные neighbours.
|
||
- **AC7 (`unit` + `smoke` + `golden`; разработчик):** full Plan/View,
|
||
`houseplan-space-card` и hidden iso получают один joined footprint: крупный
|
||
прямой/косой стык не показывает внутреннюю butt-face, зуб или второй hatch.
|
||
- **AC8 (`unit` + `smoke`; разработчик):** clean-floor area, Glow barriers,
|
||
source-inside-body guard и sun occlusion используют joined geometry: свет не
|
||
проходит через corner/T join и не блокируется внутренней гранью бывшего
|
||
overlap.
|
||
- **AC9 (`smoke`; разработчик):** hit/selection/drag/properties/delete и Undo/Redo
|
||
сохраняют identity отдельных partitions/draft segments; joined render не
|
||
объединяет записи в config и не меняет их history granularity.
|
||
- **AC10 (`unit` + code review; разработчик/ревьюер):** room wall openings,
|
||
virtual-T, nested/partial walls, exterior shell #123, columns и одиночные
|
||
partitions сохраняют действующую геометрию; совпавший room opening не режет
|
||
independent wall.
|
||
- **AC11 (`performance` + code review; разработчик/ревьюер):** structural joined
|
||
pass cached/bounded, HA state и pointer hover не запускают full-house topology,
|
||
60-partition fixture проходит существующие budgets без их ослабления.
|
||
- **AC12 (`unit` + backend/schema review; разработчик/ревьюер):** schema,
|
||
serialized config, backend, import/export, i18n, HA/network calls и зависимости
|
||
не меняются; старый plan исправляется без migration/write.
|
||
- **AC13 (`typecheck` + `unit` + `build` + documentation review; разработчик):**
|
||
implementation-loop gates зелёные; три bundle-копии после build побайтно
|
||
одинаковы; RU/EN changelog и перечисленная документация обновлены в том же
|
||
user-visible implementation commit.
|
||
|
||
## 13. План автотестов
|
||
|
||
### 13.1. Unit
|
||
|
||
Добавить geometry regression tests в `test/physical-geometry.test.mjs`,
|
||
`test/wall-thickness.test.mjs` либо отдельный узкий файл:
|
||
|
||
1. сравнить joined L-body с ожидаемым offset envelope для 90°, acute и obtuse;
|
||
2. проверить bounded mitre и bevel threshold вокруг `MITRE_LIMIT`;
|
||
3. проверить equal/unequal thickness, reversed endpoints и permutation records;
|
||
4. проверить collinear run, flat free caps, zero-length и near-miss;
|
||
5. проверить T partition→partition, draft→partition и partition→solid room wall;
|
||
6. проверить gap/open span и X-crossing contract;
|
||
7. сравнить open-preview committed vertices с closed-room ring;
|
||
8. доказать, что предыдущие draft segment сохраняют свой `cm`, когда current
|
||
session thickness меняется;
|
||
9. проверить clean-floor boolean area и united opaque rings для Glow/source/sun;
|
||
10. regression: openings do not cut extras, columns unchanged, #123 exterior,
|
||
virtual-T и nested wall remain green;
|
||
11. проверить immutability inputs и structural fingerprint/cache invalidation.
|
||
|
||
Минимум один новый test должен быть запущен/проверен ревьюером на `origin/dev` и
|
||
краснеть на старой прямоугольной geometry, а не только подтверждать новый helper.
|
||
|
||
### 13.2. Targeted production-bundle smoke
|
||
|
||
Добавить `demo/smoke_wall_junctions.mjs` либо эквивалент:
|
||
|
||
1. открыть Plan editor и нарисовать open draft с прямым и косым join;
|
||
2. проверить rubber-band path до click и отсутствие geometry jump после click;
|
||
3. создать две partitions endpoint↔endpoint и новую partition через line-snap #137
|
||
к середине существующей wall/partition;
|
||
4. проверить единый body path, отсутствие split target record и no-write hover;
|
||
5. переключить Plan → View → static card → hidden iso и сравнить footprint/bbox
|
||
signatures;
|
||
6. проверить selection/drag/Undo/Redo отдельных records;
|
||
7. поставить Glow source и sun case по разные стороны junction и подтвердить
|
||
opaque/no-false-edge result;
|
||
8. проверить pointercancel/pinch/suppressed click — новых records нет.
|
||
|
||
По процессу smoke пишется вместе с кодом, но полный browser-suite запускается
|
||
только перед бетой.
|
||
|
||
### 13.3. Golden
|
||
|
||
Добавить deterministic joined-wall scenario минимум с кадрами:
|
||
|
||
- Plan/light: committed 90° и oblique open-draft joins плюс active rubber-band;
|
||
- Plan/dark: endpoint-to-line T-preview поверх существующей partition/room wall;
|
||
- View/static/iso: saved 90° и oblique partitions с крупно читаемым outer contour;
|
||
- lighting при необходимости: junction рядом с Glow pool, чтобы щель не стала
|
||
световой полосой.
|
||
|
||
Не связанные View baselines с одиночными partitions должны остаться
|
||
pixel-identical. Любые затронутые эталоны принимаются только из полного
|
||
просмотренного Linux artifact через
|
||
`npm run golden:accept -- --reviewed`; локальное принятие ради зелёного CI
|
||
запрещено.
|
||
|
||
### 13.4. Performance и backend
|
||
|
||
Перед бетой прогнать существующий `large-house-v1`/performance smoke с 60
|
||
partitions и Full Performance на точном SHA. Если для локального join-preview
|
||
нужен отдельный профиль, он получает новый id и не переопределяет старый budget.
|
||
|
||
Backend не меняется; отдельный backend test не требуется. Полный Linux Validate
|
||
остаётся release gate.
|
||
|
||
## 14. План реализации
|
||
|
||
1. Выделить чистое представление linear physical segments и exact junction
|
||
topology с source identity.
|
||
2. Построить bounded mitre/bevel node patches и joined independent geometry.
|
||
3. Разделить raw editable bodies и canonical joined presentation/physics frame.
|
||
4. Подключить joined frame к full/static/isometric, clean floor, Glow/source guard
|
||
и sun, сохранив opening ordering.
|
||
5. Перевести open-draft/rubber-band preview на тот же join primitive и per-segment
|
||
thickness.
|
||
6. Добавить unit и targeted smoke; подготовить golden/performance fixtures.
|
||
7. Обновить документы, оба changelog и поставляемые bundle-копии.
|
||
|
||
Точные имена helpers/files не являются продуктовым контрактом.
|
||
|
||
## 15. Release-артефакты
|
||
|
||
Изменение пользовательское. Implementation-коммит имеет `User-Visible: yes` и в
|
||
том же коммите обновляет:
|
||
|
||
- `docs/CHANGELOG.md`;
|
||
- `docs/CHANGELOG.ru.md`;
|
||
- `docs/USER-GUIDE.ru.md` — рисование открытых контуров и partitions;
|
||
- `docs/WALL-THICKNESS.md` — independent joined-body/cap/mitre contract;
|
||
- `docs/ARCHITECTURE.md` — raw editable bodies против canonical joined frame;
|
||
- `docs/LIGHT.md` и при необходимости `docs/SUN.md`/`docs/ISOMETRIC.md` — единый
|
||
opaque geometry consumer;
|
||
- `docs/TESTING.md` — unit/smoke/golden coverage;
|
||
- `docs/STATUS.md` — релизная линия после фактической реализации;
|
||
- три поставляемые bundle-копии, создаваемые build/release-процессом.
|
||
|
||
Перед бетой обязательны exact-SHA Linux Validate, полный smoke-suite, просмотр и
|
||
reviewed acceptance Linux golden artifact, performance smoke и Full Performance,
|
||
а также зелёный code review. Отдельный security report не требуется: новых
|
||
внешних данных, вызовов и разрешений нет. Изменение проходит beta до stable.
|
||
|
||
## 16. Риски и снижение
|
||
|
||
| Риск | Вероятность / ущерб | Снижение |
|
||
|---|---|---|
|
||
| Pairwise patch переполнит узел степени 3+ | средняя / высокий | radial/node envelope units, T/Y matrix, bounded area assertion |
|
||
| Разная толщина усреднится или даст self-intersection | средняя / высокий | per-ray half-depth и boolean difference tests |
|
||
| Plan исправится, а Glow/sun сохранят raw butt-faces | средняя / высокий | один canonical joined frame и occlusion smoke |
|
||
| Opening случайно прорежет coincident partition | средняя / высокий | сохранить union extras after cuts, regression unit |
|
||
| Selection потеряет identity после union | средняя / высокий | raw bodies отдельным editor-only channel, drag/Undo smoke |
|
||
| Room exterior изменится при attached partition | низкая / высокий | #123 exterior regression и room-ring authority |
|
||
| Live preview начнёт делать full union на pointermove | средняя / высокий | local target subset, performance/code review AC11 |
|
||
| Старый near-miss внезапно соединится | средняя / средний | exact epsilon contract и explicit near-miss test |
|
||
| Boolean failure сделает стену прозрачной | низкая / высокий | conservative opaque fallback и malformed fixture |
|
||
| Golden изменятся шире joined cases | средняя / средний | baseline diff audit; unrelated single partitions pixel-identical |
|
||
|
||
## 17. Откат
|
||
|
||
Откат — revert user-visible implementation commit #141 вместе с тестами,
|
||
документацией, changelog и bundle-копиями. Schema и persisted data не меняются,
|
||
поэтому migration/data rollback не нужны; после отката вернётся прежний
|
||
визуальный дефект прямоугольных bodies.
|
||
|
||
Красный golden/performance gate перед бетой блокирует выпуск. Feature flag,
|
||
ослабление budget или принятие baseline без review не используются как обход.
|
||
|
||
## 18. Принятые технические предположения — можно менять без продуктового ревью
|
||
|
||
1. Junction epsilon переиспользует существующий grid/geometry tolerance и не
|
||
вводит пользовательскую настройку.
|
||
2. Рекомендуется immutable `PhysicalGeometryFrame` либо эквивалент с raw и
|
||
joined projections; точное имя и раскладка файлов свободны.
|
||
3. Node patches могут строиться вручную через offset intersections или через
|
||
другую deterministic boolean decomposition, если все AC выполняются.
|
||
4. Room-wall geometry может оставаться внутри `wallBodiesGeometry()` либо быть
|
||
обёрнута общим helper; второй независимый renderer запрещён.
|
||
5. Preview может добавлять локальный patch поверх existing saved body либо
|
||
строить небольшой joined subset; DOM decomposition не важна при отсутствии
|
||
seam/jump.
|
||
6. Cache может использовать `_cfgEpoch` плюс structural fingerprint или полный
|
||
fingerprint; HA state/theme/cursor не входят в saved key.
|
||
7. Selection frame вправе показывать raw body выбранной записи, если base wall
|
||
body остаётся joined и hit order не меняется.
|
||
8. Имена smoke/golden scenarios и точные test files не являются продуктовым
|
||
контрактом.
|
||
9. Для malformed legacy fallback допустимы raw opaque bodies; валидные schema
|
||
inputs обязаны проходить joined path без fallback.
|
||
10. Нет открытых продуктовых вопросов: Q1–Q3 и предложенные 8/10 · P2 приняты
|
||
владельцем 2026-08-14.
|