diff --git a/docs/specs/229-merge-collinear-partitions.md b/docs/specs/229-merge-collinear-partitions.md index ec5034a5..59025b00 100644 --- a/docs/specs/229-merge-collinear-partitions.md +++ b/docs/specs/229-merge-collinear-partitions.md @@ -31,7 +31,7 @@ ## 3. Подтверждённая причина -`_finishWallChain` (`houseplan-card.ts:6558`) режет цепочку по числу поставленных +`_finishWallChain` (`houseplan-card.ts:6538`) режет цепочку по числу поставленных точек и добавляет **по одной независимой перегородке на сегмент**: ```ts @@ -72,9 +72,9 @@ run of one thickness», оптимизатор её вызывает (`plan-opti ## 6. Scope - Чистый модуль слияния (`src/wall-merge.ts`) — правила без DOM, проверяемые юнитами. -- Вызов при завершении цепочки (`_finishWallChain`). +- Вызов при завершении цепочки (`_finishWallChain`, `houseplan-card.ts:6538`). - Вызов в «Оптимизировать планы» с новым счётчиком в отчёте. -- Пересчёт `host.t` проёмов, висящих на сращиваемых перегородках. +- Пересчёт `host.t` и материализованной проекции `x/y/angle` проёмов, висящих на сращиваемых перегородках. - i18n строки счётчика (en + ru), оба changelog. ## 7. Не входит в задачу @@ -112,6 +112,13 @@ run of one thickness», оптимизатор её вызывает (`plan-opti независимы и пересекаться могут без общей вершины — такой случай не является стыком и в слиянии не участвует. +**Все четыре причины проверяются одним допуском `EPS_JOIN`** — тем же, которым +проверяется совпадение концов самих перегородок (§8.3). Отдельного допуска для +ребра комнаты, колонны или черновика нет: иначе один и тот же зазор считался бы +стыком в одном случае и не считался в другом, а AC2 перестал бы быть однозначным. +Точка стыка берётся у ребра комнаты — ближайшая вершина полигона, у колонны — +её центр, у черновика — конец сохранённого контура. + ### 8.3. Допуски Один источник истины на обе проверки, объявленный константами в модуле: @@ -138,18 +145,49 @@ run of one thickness», оптимизатор её вызывает (`plan-opti 2. пересчитывается в долю новой длины с учётом того, какой конец стал началом; 3. если хозяин поглощён — `host.id` переписывается на выжившую запись. +4. **пересчитывается материализованная проекция** `x/y/angle` того же проёма. + +`docs/CONFIG-COMPATIBILITY.md` (#132) объявляет legacy `x/y/angle` обязательной +компаньонкой хоста: «the legacy `x/y/angle` siblings remain a materialized +compatibility projection for older readers», и прямо требует, чтобы full export, +plan-only export, merge и оптимизация сохраняли согласованность. В коде это уже +норма: после любого изменения хозяина вызывается `materializePartitionOpening` +(`houseplan-card.ts:7824` — перетаскивание перегородки, `:12007` — правка +проёма). Слияние — такое же изменение хозяина и обязано делать то же самое. + **Инвариант:** координаты центра проёма в единицах плана до и после слияния -совпадают с точностью до `EPS_JOIN`. Именно это проверяет AC3 — не «поле -пересчитано», а «дверь физически не сдвинулась». +совпадают с точностью до `EPS_JOIN` — **и в резолвленном виде, и в +материализованной проекции**. Именно это проверяет AC3: не «поле пересчитано», а +«дверь физически не сдвинулась», причём для обоих читателей — текущего фронтенда +и того, который умеет читать только `x/y/angle`. ### 8.5. Границы применения -- при завершении цепочки сращиваются только записи **этого пространства**; - слияние применяется многократно до стабилизации: три отрезка подряд дают одну запись, а не две; - порядок обхода не влияет на результат (проверяется перестановочным тестом, как в #218). +### 8.6. Что именно сращивается при завершении цепочки + +Решения §4.1 и §4.2 делят работу: рисование чинит **свой** шов, накопленное +чинит «Оптимизировать планы» — там для этого есть отчёт и отмена. + +Поэтому при завершении цепочки слияние затрагивает только записи, связанные с +этой цепочкой: сегменты самой цепочки и те существующие перегородки, с которыми +она имеет общий конец, — далее транзитивно, пока цепочка стыков не оборвётся. +Иными словами, компонента связности по общим концам, содержащая хотя бы один +новый сегмент. + +Перегородки, не связанные с новой цепочкой, не трогаются, даже если между собой +они образуют шов, подлежащий слиянию. Такой шов дождётся оптимизатора. + +**Почему не «всё пространство».** В пространстве владельца из #228 уже лежит +коллинеарная пара `#0`/`#4`. Если дорисовать стену в другом углу, «слияние по +всему пространству» молча починило бы и эту пару — то есть правка старых данных +без отчёта и без выделенной отмены, ровно то, что §4.2 обещает делать только по +явной команде. + ## 9. Данные, i18n, a11y, privacy, security - **Данные:** формат перегородки не меняется; меняется их количество и `host.id` @@ -182,10 +220,13 @@ run of one thickness», оптимизатор её вызывает (`plan-opti 2. **AC2 — узел с причиной остаётся.** Слияния не происходит, если на общем конце есть третья перегородка, ребро комнаты, колонна или конец черновика — четыре отдельных случая. **Доказательство:** `unit`. -3. **AC3 — проём не двигается.** Перегородка с дверью посередине сращивается с - соседней; координаты центра проёма в единицах плана до и после совпадают, - `host.id` указывает на существующую запись, `host.t` в пределах `[0,1]`. - Тест красный до реализации §8.4. **Доказательство:** `unit`. +3. **AC3 — проём не двигается, в обоих представлениях.** Перегородка с дверью + посередине сращивается с соседней; координаты центра, полученные через + `resolvePartitionOpening`, до и после совпадают, `host.id` указывает на + существующую запись, `host.t` в пределах `[0,1]`. Одновременно материализованные + `x/y/angle` того же проёма пересчитаны и согласованы с резолвленной позицией + (#132). Тест красный до реализации §8.4 — и остаётся красным, если пересчитан + только `host.t`, а проекция устарела. **Доказательство:** `unit`. 4. **AC4 — разная толщина не сращивается.** Два коллинеарных отрезка с разными `cm` остаются двумя записями. **Доказательство:** `unit`. 5. **AC5 — допуски.** Отрезки, разведённые на расстояние больше `EPS_JOIN`, не @@ -198,8 +239,10 @@ run of one thickness», оптимизатор её вызывает (`plan-opti отчёт показывает их число, повторный запуск ничего не меняет (идемпотентность). **Доказательство:** `unit` (`optimizePlans`). 8. **AC8 — ничего лишнего.** Стены комнат, колонны и черновики контуров не - изменяются; количество комнат и их геометрия те же. - **Доказательство:** `unit` + diff review. + изменяются; количество комнат и их геометрия те же. Отдельно: завершение + цепочки не трогает перегородки вне её компоненты связности — пространство с + готовым швом в стороне после рисования новой стены сохраняет этот шов до + запуска оптимизатора (§8.6). **Доказательство:** `unit` + diff review. 9. **AC9 — release-артефакты.** Оба changelog, строка счётчика в обоих языках, `dist`/demo/integration бандлы идентичны друг другу. **Доказательство:** diff + сверка копий бандла. @@ -223,6 +266,8 @@ run of one thickness», оптимизатор её вызывает (`plan-opti | `partition-merge-ignores-thickness` | сращивать при разном `cm` | юнит AC4 | | `partition-merge-ignores-junction` | сращивать через примыкание третьей стены | юнит AC2 | | `partition-merge-keeps-relative-t` | не пересчитывать `host.t` | юнит AC3 | +| `partition-merge-skips-materialization` | не обновлять `x/y/angle` проёма | юнит AC3 | +| `chain-merge-sweeps-whole-space` | сращивать все перегородки пространства, а не компоненту цепочки | юнит AC8 | ## 15. Release-артефакты @@ -248,6 +293,8 @@ run of one thickness», оптимизатор её вызывает (`plan-opti бы оно было детерминированным и не зависело от времени; это — самое простое. 3. **Конкретные значения `EPS_ANGLE` и `EPS_JOIN`** подбираются при реализации; ТЗ фиксирует только их природу (доли шага сетки) и границы (§8.3). -4. **Слияние при завершении цепочки применяется ко всему пространству**, а не - только к новым записям: цепочка могла примкнуть к нарисованному ранее отрезку, - и шов на стыке — тот же самый случай. +4. **Область слияния при завершении цепочки** описана нормативом §8.6, а не + здесь: это не свободное предположение, а граница между двумя решениями + владельца (§4.1 и §4.2). Ревизия r2: прежняя редакция говорила «применяется ко + всему пространству» — сращивало бы и старые швы в другом углу, обходя обещанный + для них отчёт и отмену (находка M1 ревью r1).