# Issue #288 — ограниченный коридор многостенного узла - **Issue:** https://github.com/Matysh/houseplan-card/issues/288 - **Статус:** первая редакция для внешнего ревью; канонический статус задаётся метками issue - **Тип / приоритет:** bug / P1 - **Оценка:** пользовательская ценность 9/10; ценность для разработки 8/10; сложность 7/10; риск 8/10 - **Область:** canonical wall bodies, degree-3+ junction cuts, Plan/View/Static/Iso, real-plan smoke, semantic golden и structural consumers - **Модель данных:** schema, config, layout и Optimize не меняются - **Связано:** #249, #261, #271, #272, #275, #278, #279, #284–#286, `docs/WALL-THICKNESS.md`, `docs/ARCHITECTURE.md`, `docs/TESTING.md` ## 1. Сценарий и подтверждённая причина В beta.9 на реальном втором этаже две общие стены визуально обрываются перед перпендикулярной 30-сантиметровой стеной. В разрыве видны room fills, хотя в модели там нет ни opening, ни virtual span, ни нулевой толщины. Репозиторный smoke `node demo/smoke_real_plan_masonry.mjs` измеряет четыре копии разрыва — по одной с каждой стороны двух общих стен — общей длиной `181.00` шага. Каждый разрыв равен `45.25` шага. Узел `(577, 299)` в шагах решётки содержит finite rays `349 / 120 / 5` с толщинами `30 / 30 / 30` см; соседняя общая стена имеет 20 см. Локальная машинерия использует радиус `MITRE_LIMIT × H = 4 × 15 = 60` шагов, и внешний connector/cut получает мандат далеко за пределами пятишагового support. Из соседней 20-сантиметровой полосы исчезает `60 − 15 = 45` шагов — ровно измеренный дефект. Данные чистые и lattice-exact; исправляется только вычисляемая geometry. ## 2. Пользовательский результат Непрерывные стены доходят до физического узла без 45-шагового провала во всех режимах. Короткий пятишаговый луч остаётся коротким, внешний bounded bevel #249 не возвращает бесконечный mitre, а opening/virtual gaps остаются пустыми. Новых настроек, миграции и видимого действия нет: существующий план начинает рисоваться правильно сразу после обновления. ## 3. Геометрический контракт ### 3.1 Finite ownership Каждый локальный ray material и каждый защищающий/вырезающий corridor обязан быть ограничен реальными `MultiWallNodeRay.supports`. Длина support — физическая граница полномочий узла, а не подсказка после построения mask. Connector, созданный для связи bevel-cut с внешним фоном, не может пересечь чужую finite wall strip дальше собственной полутолщины этой полосы от узла. Иными словами, у соседней стены без opening нельзя удалить продольный участок длиннее её `halfDepth`, если отдельное продуктовое решение явно не разрешило больший cap. ### 3.2 Короткий луч Луч длиной `L < MITRE_LIMIT × H`: - не достраивается до глобального radius; - не создаёт corridor, длина которого выводится только из максимального `H`; - сохраняет свою полную finite strip и не удаляет материал другого ray; - при перекрывающихся mask соседних nodes остаётся ограничен меньшим из физического support и локального безопасного extent. Конкретная boolean-формула остаётся техническим решением, но результат обязан выполнять семантические инварианты, а не только совпасть с raster crop. ### 3.3 Единый источник Исправление применяется в canonical `wallBodiesGeometry` до downstream consumers. Нельзя дорисовать SVG-патч поверх разрыва: `roomGeom`, final `geom`, `paperGeom`, clean floor, room fills/hover, Static/hidden Iso и light/sun barriers должны видеть одну и ту же сплошную physical geometry. Failure isolation #278 сохраняется: ошибка необязательного локального узла не гасит кладку пространства; обязательный structural preflight остаётся fail-dark. ## 4. Scope ### Входит - finite-aware corridor/cut в degree-3+ node; - конфигурация `349 / 120 / 5` и смешанные `30/30/20` см; - оба реальных privacy-minimized плана #285/#286; - semantic vector/browser probes, mutation и targeted golden; - canonical wall/architecture/testing docs и оба changelog. ### Не входит - переписывание persisted rooms/walls либо запуск Optimize; - исправление partial resize #289 или near-axis geometry #290; - изменение `MITRE_LIMIT`, `MULTI_WALL_JOIN_LIMIT` или стиля фаски глобально; - новая модель стен из ADR #282; - generic визуальная маска, расширение pixel-diff tolerance или переприёмка baseline без semantic evidence. ## 5. Acceptance criteria ### AC1. Реальный второй этаж не содержит разрывов `node demo/smoke_real_plan_masonry.mjs` выдаёт для second-floor `gapCount: 0` и `totalGapSteps: 0`. Ожидания `PLANS` меняются на нули в том же implementation-коммите. Проверяются все четыре прежние строки, а не одна выбранная точка. ### AC2. Первый этаж не платит за исправление Тот же smoke сохраняет `gapCount: 0` и `totalGapSteps: 0` для first-floor. Оба плана проходят `npm run invariants` и production geometry preflight. ### AC3. Unit фиксирует полномочия corridor Table-driven unit строит узел с rays `349 / 120 / 5` шагов и physical half-depths, соответствующими 30/30/20 см. На каждом порядке rays, reversed endpoints и `cell_cm: 1/5/30` longitudinal material loss соседней тонкой стены не превышает её собственного half-depth; короткий support не удлиняется. ### AC4. Старые junction-контракты не регрессируют - discarded wedge #249 остаётся внешним и пустым; - retained wedge #261 заполнен; - finite rays #271 не становятся длиннее; - enclosed holes #272 остаются нулевыми; - protected strips #275 не теряют area; - optional union failure #278 остаётся локальным; - near-orthogonal #279 остаётся защищённым. ### AC5. Все consumers согласованы Production-bundle smoke проверяет прежние четыре gap intervals настоящим SVG fill API в Plan и View; structural fingerprints для Static/hidden Iso и light/sun barriers не содержат прохода. Light/dark themes дают одинаковый semantic result. ### AC6. Golden не принимает дефект по общему порогу Targeted real-plan golden выполняет semantic `gapCount === 0` до pixel diff. Если визуальный fingerprint меняется, baseline принимается только из Linux CI artifact со ссылкой на прогон и объяснением изменившихся junction areas. Старые baselines без semantic нуля неприемлемы. ### AC7. Мутант доказывает чувствительность Mutation возвращает corridor, зависящий только от `MITRE_LIMIT × node.halfDepth`, либо игнорирует finite support. AC1 или AC3 обязаны падать при сохранении зелёного unrelated first-floor контроля. ### AC8. Локальные гейты - `npm run typecheck`; - `npm test`; - `npm run build` и bundle parity; - `node scripts/check-docs.mjs`; - targeted real-plan masonry smoke и mutation. Полные golden, smoke, performance и Linux HA harness выполняются перед beta. ## 6. Совместимость, touch, security и performance Schema/model version и read/write compatibility не меняются. View на touch и kiosk получает тот же canonical result; editor touch contract не меняется. Новых HA service calls, URL/HTML и permission boundaries нет. Алгоритм остаётся локальным по rays/supports одного node. Запрещён новый глобальный pairwise pass по стенам. `npm run performance` перед beta обязан сохранить действующие masonry/render budgets; exact fixture дополнительно проверяет детерминизм при permutation без роста числа local patches. ## 7. Риски и меры - Изменение общего `bevelMultiWallBody` может вернуть дефект любого из прежних junction-контрактов #249/#261/#271/#272/#275/#278/#279. Мера: полная table-driven матрица AC4, оба реальных плана и semantic golden без повышения tolerance. - Слишком широкий corridor недосечёт соседнюю стену и оставит наложение; слишком узкий снова вырежет её глубже собственного half-depth. Мера: численные верхние границы AC3, structural consumers AC5 и чувствительный мутант AC7. - Локально правильный polygon может разойтись между Plan/View/Static/Iso. Мера: один canonical source AC5 и запрет consumer-specific post-fix. ## 8. Откат Чистый revert implementation-коммита возвращает прежнюю renderer geometry; feature flag и миграция не требуются, потому что persisted model не меняется. ## 9. Ожидаемые файлы Product code: - `src/wall-thickness.ts`. Tests/evidence: - `test/wall-thickness.test.mjs`; - `demo/smoke_real_plan_masonry.mjs`; - при необходимости targeted golden harness/matrix и mutation registry; - privacy-minimized fixtures остаются без имён/полного пользовательского экспорта. Документация: - `docs/WALL-THICKNESS.md`, `docs/ARCHITECTURE.md`, `docs/TESTING.md`; - `docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`. ## 10. Release и порядок интеграции Implementation-коммит имеет `Issue: #288`, `User-Visible: yes` и оба changelog. Если меняются canonical docs screenshots, они снимаются только штатным Linux workflow после `npm run bundle:sync` и принимаются reviewed командой. Инфраструктурная ветка #260 должна попасть в `dev` до финальной пересъёмки docs screenshots, как требует issue. Она не входит в product scope #288 и не обходится ручным слиянием автора продуктовой задачи. ## 11. Принятые технические предположения 1. Ограничение «не больше half-depth соседней стены» — верхняя граница ущерба, а не требование обязательно вырезать этот half-depth. 2. Правильный способ может полностью убрать connector там, где основной cut уже связан с внешним фоном; контракт задаёт результат, не конкретный polygon. 3. Полный экспорт владельца не коммитится; уже добавленная минимизированная fixture является каноническим exact repro. 4. Touch editor: best effort; View/kiosk rendering fully supported.