22 KiB
#484 — достаточная внешняя размерная цепь ступенчатого фасада в PDF
Issue: #484
1. Сценарий
Персона — Home admin из docs/SCOPE.md. В View администратор экспортирует
уже поддерживаемое пространство с прямоугольным выступом фасада в одностраничный
PDF и передаёт лист монтажнику, подрядчику или страховщику. По внешней размерной
цепи получатель должен однозначно восстановить положение и глубину выступа без
доступа к интерактивному плану.
2. Что человек увидит до и после
До исправления PDF показывает только высоту выступа; после исправления рядом со ступенчатым фасадом видны расстояния сверху и снизу, высота выступа и один размер его глубины, без диагоналей и линий сквозь посторонние стены.
3. Проблема и подтверждённая причина
После #482 кандидаты внешних размеров проходят две корректные стадии:
dedupeOppositeDimensionEdges()оставляет один из двух одинаковых размеров глубины выступа и не объединяет разные вертикальные участки;groupCollinearDimensionEdges()раскладывает независимые линии фасада по собственным полосам.
Регрессия появляется на следующей стадии в src/pdf/pdf-scene.ts.
externalPlacementTouchesArchitecture() проверяет размерную линию, обе
выносные линии (extension lines) и shelf через общий
pdfSegmentTouchesGeometry(). Параметр allowStartBoundary разрешает только
касание в самой начальной точке. Любое коллинеарное наложение и любое следующее
пересечение границы считаются коллизией.
На обычном выпуклом прямоугольном углу выносная линия сразу уходит наружу. На ступени перпендикуляр от конца измеряемого ребра может сначала совпасть с примыкающим участком фасада или пройти через тело соединённой в этом же узле стены. Это не столкновение с посторонней архитектурой, а неизбежный выход из собственного измеряемого угла. Текущая проверка отвергает его на каждой полосе 0…80 mm, и группа размеров молча не печатается.
4. Цели
- восстановить минимально достаточную внешнюю размерную цепь ортогонального ступенчатого фасада;
- сохранить точный запрет на повторный вход выносной линии в архитектуру;
- сохранить локальную дедупликацию #482 и отсутствие диагональных размеров;
- закрепить регрессию unit-, browser- и визуальным свидетельствами.
5. Не входит в задачу
- изменение геометрии плана, модели стен,
model_versionили сохранённого конфига; - новые настройки PDF или изменение диалога экспорта;
- общий CAD-размерный движок, цепи между произвольными опорными осями, автоматические этажные размерные сетки;
- изменение внутренних размеров комнат, площадей, масштаба, компаса, штриховки, проёмов или других результатов #482;
- разрешение размерным линиям проходить сквозь несвязанную архитектуру;
- принятие golden-эталонов без полного Linux-артефакта и явного визуального ревью.
6. Контракт внешней цепи
6.1 Минимально достаточный набор для прямоугольного выступа
Для внешнего ring
(0,0) → (10,0) → (10,3) → (12,3) → (12,6) → (10,6) → (10,10) → (0,10)
PDF выводит четыре осевых размера, необходимые для положения выступа:
- вертикальное расстояние от верхней границы здания до начала выступа;
- вертикальную высоту выступа;
- вертикальное расстояние от конца выступа до нижней границы здания;
- одну горизонтальную глубину выступа.
Две горизонтальные ступени одинаковой глубины образуют локальную противоположную пару, поэтому печатается ровно один из этих двух размеров. Детерминированный placement score #482 выбирает сторону с безопасной полосой и свободным местом; при полной ничьей действует существующий stable source key.
Равные по числу, но топологически разные вертикальные участки не объединяются. Порядок обхода ring и его winding не меняют множество напечатанных размеров.
6.2 Разрешённый начальный выход из собственного узла
Послабление применяется только к двум внешним extension lines конкретного
DimensionEdge. Полная печатная линия по-прежнему начинается в фактической
точке sourceA/sourceB; визуальный разрыв или перенос начала за стену не
допускается.
Для проверки коллизии extension line делится точными параметрами пересечений с архитектурными rings на последовательные интервалы. Разрешён только её непрерывный начальный префикс от source point до первого строго внешнего интервала. В этот префикс могут входить:
- общая начальная точка измеряемой и примыкающей граней;
- коллинеарное движение вдоль границы стены, инцидентной source point;
- выход через тело физической стены, непрерывно примыкающее к source point.
После того как линия впервые вышла в свободное пространство, любое более позднее касание, пересечение, коллинеарное наложение или вход в физическую архитектуру снова считается коллизией. Это включает другую стену, другой участок того же connected component, колонну и возвращение в исходную стену. Касания в одной и той же точке перехода «собственный префикс → свободное пространство» группируются с физическим epsilon и не считаются повторным входом.
Невалидные координаты, неоднозначная классификация интервала и линия, которая так и не получает свободного участка, обрабатываются fail closed: кандидат считается столкнувшимся.
6.3 Остальные коллизии
Без изменений остаются:
- collision label box с архитектурой и ранее размещёнными подписями;
- collision основной dimension line и shelf с архитектурой;
- пересечения extension lines с чужими label boxes;
- 1 mm clearance, общий lane для коллинеарной группы и поиск полос 0…80 mm;
- безопасный отказ от конкретной группы, если после узкого исключения ни одна полоса не подходит.
Послабление не становится default общего pdfSegmentTouchesGeometry() и не
используется внутренними размерами, декором либо другими PDF-примитивами.
7. Геометрические и визуальные инварианты
- Размеры остаются только horizontal/vertical и near-axis в действующем допуске #482; диагональная хорда не создаётся и не проецируется.
- Прямоугольный внешний ring по-прежнему получает одну горизонтальную и одну вертикальную величину после дедупликации противоположных сторон.
- Внешние подписи центрированы на измеряемом участке; весь group lane сдвигается целиком, а не отдельными фрагментами.
- Допустимый начальный префикс влияет только на collision decision. Цвет, толщина, начало и конец напечатанной extension line не меняются.
- Если посторонняя архитектура перекрывает все полосы, небезопасный размер не печатается. Исправление не заменяет fail-closed поведение на overlap.
8. Модель, совместимость и миграция
config_version, model_version, space schema и PDF option storage не
меняются. Экспорт остаётся read-only и работает поверх временной печатной сцены.
Миграция и downgrade converter не нужны; старые и новые карточки читают один и
тот же план.
9. UX, i18n и touch
Новых элементов управления и строк интерфейса нет; ключи i18n не добавляются. Исправляется только содержимое скачанного PDF при уже включённой опции размеров.
Экспорт вызывается из View и остаётся полностью поддерживаемым на touch. Диалог, tap/pointer-поведение и скачивание не меняются. Результат PDF одинаков для desktop, touch и HA Companion при одинаковом plan/config.
10. Производительность и безопасность
Проверка собственного начального выхода использует конечный набор сегментов
уже подготовленных architectureRings; сетевой ввод, DOM и пользовательский
HTML не появляются. Она выполняется только при явном PDF-экспорте с включёнными
размерами.
Действующий budget #482 — построение large-house scene <200 ms на референсной
Node-среде — сохраняется. Initial View graph и lazy boundary PDF не меняются.
Детерминированность PDF bytes для одинаковых now, plan/config и options
сохраняется.
11. План реализации
- Расширить точный segment/ring collision helper в
src/pdf/pdf-collision.tsотдельным opt-in режимом начального выхода: собрать и отсортировать intersection parameters, классифицировать открытые интервалы и разрешить только непрерывный source-connected prefix. - В
src/pdf/pdf-scene.tsвключить этот режим только для двух внешних extension lines. Dimension line, shelf, labels и внутренние размеры оставить на строгом пути. - Добавить чистые unit-матрицы: обычный выход наружу; начальное коллинеарное наложение; начальный проход через инцидентное тело; касание в точке выхода; повторный вход; касание посторонней стены; invalid/fail-closed.
- Добавить scene fixture ступенчатого фасада в прямом и обратном winding и проверить полный набор четырёх внешних величин, единственность глубины, отсутствие диагонали и неизменный rectangle contract.
- Добавить focused PDF browser/golden fixture либо расширить
pdf-export-polish-lightтак, чтобы выступ и все четыре размера были видимы и проверялись semantic guard, а не только пиксельным снимком. - Обновить канонические документы, changelog и screenshot fingerprints по правилам процесса.
12. Acceptance criteria и доказательства
| AC | Критерий | Обязательное доказательство |
|---|---|---|
| AC1 | Детерминированный stepped-ring fixture печатает верхний вертикальный участок, высоту выступа, нижний вертикальный участок и одну глубину | test/pdf-scene.test.mjs: точные значения и позиции внешних text/line commands для обоих winding |
| AC2 | Из двух равных глубин остаётся ровно одна; rectangle сохраняет одну H и одну V сторону; диагональных размеров нет | unit matrix pdf-dimensions + scene assertions на axis/количество |
| AC3 | Extension line может выйти из собственного узла через начальное коллинеарное/solid-примыкание, печатается полностью от source point и не получает визуального разрыва | test/pdf-collision.test.mjs + scene command endpoints |
| AC4 | После первого выхода наружу любое касание/пересечение/overlap архитектуры остаётся запрещённым; неразмещаемая группа fail closed | отрицательная unit/scene matrix + mutation witness, глобально разрешающий post-exit collision |
| AC5 | Label, dimension line, shelf, внутренние размеры, 1 mm clearance и lane/group contract #482 не меняются | существующие pdf-collision/pdf-scene tests + код-ревью по call sites |
| AC6 | Результат не зависит от winding/порядка ring, PDF остаётся deterministic и large-house scene укладывается в <200 ms> |
unit equality + существующий deterministic writer/scene perf test |
| AC7 | В rasterized focused PDF визуально присутствуют все четыре элемента цепи, подписи не лежат в кладке и никакая extension line не пересекает постороннюю архитектуру | browser PDF smoke + Linux golden/semantic probe с явной приёмкой полного артефакта |
| AC8 | Канонические и пользовательские документы описывают достаточную цепь выступа; оба changelog ссылаются на #484; docs/golden fingerprints актуальны | node scripts/check-docs.mjs, changelog/provenance gates, reviewed Linux docs/golden artifacts |
12.1 Обязательные отрицательные свидетели защитных AC
| Защитный AC | Чем доказывается | Чем обязан краснеть |
|---|---|---|
| AC3 | stepped source-exit unit/scene fixture | мутант pdf-own-exit-prefix-disabled возвращает строгий allowStartBoundary и теряет минимум один обязательный размер |
| AC4 | post-exit re-entry и foreign-wall fixtures | мутант pdf-post-exit-collision-ignored разрешает всю extension line и печатает заведомо небезопасный размер |
Мутанты добавляются в scripts/mutation-gate.mjs, потому что защитные AC живут
в продуктовом коде, а визуальный/browser witness дорог для повторного ручного
снятия на код-ревью.
13. Release artifacts
docs/CHANGELOG.mdиdocs/CHANGELOG.ru.md: короткий bugfix со ссылкой на #484;docs/PDF-EXPORT.md: правило достаточной внешней цепи прямоугольного выступа и узкий source-exit contract;docs/USER-GUIDE.mdиdocs/USER-GUIDE.ru.md: пользовательское обещание размеров ступенчатого фасада;docs/ARCHITECTURE.md: ownership source-aware collision режима внутри lazy PDF runtime;docs/STATUS.md: текущая beta-line после фактического попадания изменения;- focused Linux golden и semantic contract; принимается только
npm run golden:accept -- --reviewed --from=<полный Linux artifact>; - поскольку меняется
src/**, полный Linux docs screenshot artifact обязателен для обновления fingerprint. Пиксельные изменения десяти кадров не ожидаются; любое изменение кадра разбирается отдельно, а не принимается автоматически.
Backend, HA API и security model не меняются; backend pytest не является целевым доказательством.
14. Риски и снижение
| Риск | Снижение |
|---|---|
| Узкое исключение превратится в общее разрешение линиям проходить через стены | state machine «initial prefix → outside → any later hit rejects» и отрицательный мутант AC4 |
| Касание нескольких граней в одной вершине ошибочно станет re-entry | группировка intersection parameters с единым physical epsilon и corner fixtures |
isSolid по-разному классифицирует точку на границе |
интервалы проверяются по внутренним midpoint, коллинеарные интервалы учитываются отдельно |
| Дедупликация снова удалит непарные равные участки | существующая topology matrix #482 плюс exact stepped counts AC1/AC2 |
| Исправление будет видно только в unit, но не в настоящем PDF | browser export + rasterized focused golden + semantic probe AC7 |
| Перебор пересечений замедлит крупный экспорт | используется уже подготовленная архитектура, без повторного model build; сохраняется <200 ms budget AC6 |
15. Откат
Откат — возврат lazy PDF chunk к версии до #484. Данные и настройки не мигрируют, поэтому отдельный rollback данных не нужен. При откате возвращается только известная потеря размеров ступенчатого фасада; обычный экспорт остаётся совместимым.
16. Принято предположительно, можно менять на ревью
Следующие решения технические и не требуют продуктового ответа владельца:
- точный helper может расширить
pdfSegmentTouchesGeometry()opt-in опцией или быть отдельной функцией, если default строгого helper остаётся неизменным; - intersection sweep может хранить пары
tStart/tEndлибо нормализованные breakpoints; внешний контракт задаётся §6.2, а не формой API; - focused visual witness может быть новым golden scenario либо расширением
pdf-export-polish-light, если semantic guard однозначно отличает наличие каждого размера; - название и число внутренних тестовых helpers свободны при полном покрытии AC и двух обязательных мутантов.