fix: a room side and a live draft, seen in the right coordinates (#229 r1 H1,H2)

**High-1.** Комнаты хранятся в тех же координатах, что и перегородки:
`roomPoly` отдаёт сырой полигон конфига. Обе обвязки делили его на `NORM_W`
ещё раз, комната уезжала в область ~0.0001, и `junctionAt` не находил ни
одного совпадения. Узел на T-стыке к середине стены комнаты — тот самый
случай, ради которого ТЗ прошло два раунда ревью, — молча исчезал.
Воспроизведено вызовом `optimizePlans`: `partitionsMerged === 1` там, где
ожидается 0.

**High-2.** Завершаемая цепочка к моменту слияния ещё лежит в `room_drafts`:
каждый клик персистит её через `_persistActiveDraftSegment`, а удаляется
черновик строкой ниже вызова слияния. Собственные концы цепочки считались
чужим примыканием, и стык с существующей стеной не срастался. Активный
черновик теперь исключается — ровно так же, как это делает
`plan-snap-overlay` (`activeDraftId`).

Дыры в тестах, которые это пропустили, закрыты по существу, а не заплаткой:

- `demo/smoke_wall_chain_merge.mjs` рисует продолжение реальными кликами через
  `_markupClick`, а не присваиванием `_path`, — то есть исполняет тот путь, на
  котором дефект и жил. Клики задаются в координатах плана и переводятся через
  живой view box, иначе смок целится мимо только что нарисованной стены.
- `test/plan-optimizer.test.mjs` получил комнату с примыканием к середине
  стороны: юниты модуля этого не ловили, потому что передают полигон уже в
  согласованном масштабе, минуя обвязку.
- Мутанты `partition-merge-rescales-rooms` и `chain-merge-sees-own-draft`
  сторожат оба места: проверены применением патча, 1 и 2 падения.

Issue: #229
User-Visible: no
This commit is contained in:
Codex
2026-08-21 13:04:20 +03:00
committed by Codex
parent 10d5b5a288
commit ed13b4a5ca
9 changed files with 97 additions and 26 deletions
+25
View File
@@ -717,6 +717,31 @@ export const MUTANTS = [
replace: ' .dev:not(.unavail):hover {',
}],
},
{
id: 'partition-merge-rescales-rooms',
guard: 'node --test --test-name-pattern="issue 229" test/plan-optimizer.test.mjs',
because: 'комнаты хранятся в тех же координатах, что перегородки: лишнее деление '
+ 'уносит их в угол и примыкание к стене комнаты перестаёт находиться '
+ '(CODE-REVIEW-229-r1, High-1)',
patches: [{
file: 'src/plan-optimizer.ts',
find: ` .filter((poly: number[][] | null): poly is number[][] => !!poly),`,
replace: ` .filter((poly: number[][] | null): poly is number[][] => !!poly)
.map((poly: number[][]) => poly.map((p) => [p[0] / NORM_W, p[1] / NORM_W])),`,
}],
},
{
id: 'chain-merge-sees-own-draft',
guard: 'node demo/smoke_wall_chain_merge.mjs',
because: 'завершаемая цепочка ещё лежит в room_drafts, и её собственные концы '
+ 'нельзя принимать за чужое примыкание — иначе стык с существующей стеной '
+ 'никогда не срастается (CODE-REVIEW-229-r1, High-2)',
patches: [{
file: 'src/houseplan-card.ts',
find: ' if (draft?.id && draft.id === this._activeDraftId) return [];',
replace: ' void 0;',
}],
},
{
id: 'partition-merge-disabled',
guard: 'node demo/smoke_wall_chain_merge.mjs',