diff --git a/docs/specs/233-resize-inner-dimensions.md b/docs/specs/233-resize-inner-dimensions.md index a3ad4729..fb774571 100644 --- a/docs/specs/233-resize-inner-dimensions.md +++ b/docs/specs/233-resize-inner-dimensions.md @@ -4,7 +4,7 @@ - Тип: bug · приоритет P2 · ценность 7/10 · сложность 3/10 · риск 4/10 - Issue: [#233](https://github.com/Matysh/houseplan-card/issues/233) - Ветка: `issue/233-resize-inner-dimensions` -- Статус ТЗ: на ревью +- Статус ТЗ: на ревью (заход r2) Канонические документы: `docs/SCOPE.md`, `docs/WALL-THICKNESS.md`, `docs/CANVAS.md`, `docs/USER-GUIDE.ru.md`, `docs/TOUCH-SUPPORT.md`, @@ -101,7 +101,19 @@ innerEdgeSpan(prev, a, b, next, oPrev, oSelf, oNext) -> number 2. внутренняя линия ребра `a→b` пересекается с внутренними линиями соседей; 3. результат — расстояние между двумя точками пересечения. -**Отступления, каждое обязано быть явным:** +**Правило нуля идёт первым и перекрывает всё остальное (находка H1 ревью r1):** +если **сама измеряемая сторона** имеет нулевую толщину — это проём или сторона, +открытая в соседнюю комнату, — возвращается осевая длина `|b−a|` целиком, без +сокращения на соседей. + +Причина не в удобстве, а в согласованности с площадью. У проёма нет внутренней +грани, от которой измеряют, поэтому подпись показывает полную протяжённость +открытой стороны. Ровно так же ведёт себя `insetContour` на стыке нулевой и +ненулевой толщины: там flat cap (`#172`), и открытая сторона не укорачивается. +Без этого правила длина и площадь снова разошлись бы — только на другой границе, +и это был бы тот же дефект, ради которого затевалась задача. + +**Остальные отступления, каждое обязано быть явным:** - сосед параллелен (пересечения нет) либо `oSelf` и оба соседа равны нулю → возвращается осевая длина `|b−a|`; @@ -116,14 +128,33 @@ innerEdgeSpan(prev, a, b, next, oPrev, oSelf, oNext) -> number ## 7. Источник толщин для подписи -Половинные глубины берутся существующим `thicknessCmAt(walls, a, b, pitch, -coordScale)` — тем же способом, которым толщину узнаёт остальной редактор, — и -переводятся в единицы через `wallCmToUnits(cm, cellCm, gridPitch) / 2`. +**`thicknessCmAt` по ребру комнаты не подходит, и это установлено тестом** +(находка H2 ревью r1). `test/wall-thickness.test.mjs` «thicknessCmAt exact-parent +fallback does not leak from partial or unrelated spans» показывает: запрос по +полному ребру против частично заданной толщины возвращает **0**, а не толщину +участка. То есть на ребре со сплит-толщиной подписи молча перестали бы +сокращаться, и мы получили бы новый вид того же расхождения. -Если одно ребро комнаты разрезано на атомарные участки с разной толщиной, -`thicknessCmAt` вернёт толщину участка по середине ребра. Это принятое -упрощение: подпись — одно число на ребро, и «одно число» для стены переменной -толщины не существует. Записано здесь, чтобы ревью не считало это недосмотром. +Источник половинных глубин — **атомарный профиль** +`roomWallProfile(rooms, roomId, walls, openCuts, pitch, cellCm, gridPitch, +coordScale)`, тот самый, из которого `innerContourForRoom` строит контур для +площади. Он возвращает атомарный полигон, `cms` и уже готовые `offsets` +(половинные глубины в единицах, с учётом `kinds`: у виртуальных и открытых +участков там ноль). Длина и площадь одного ребра резолвятся одним механизмом — +это и есть смысл задачи. + +Отображение «ребро комнаты → атомарные участки»: атомарный полигон получен +разрезанием того же контура, поэтому участки ребра `a→b` — это последовательные +атомарные рёбра, лежащие на отрезке `a→b`. Из них берутся: + +- **сокращение у конца `a`** — по атомарному соседу, входящему в вершину `a`; +- **сокращение у конца `b`** — по атомарному соседу, выходящему из вершины `b`; +- **`oSelf`** — по атомарному участку, содержащему середину `a→b`. + +Ребро со участками разной толщины даёт одно число: расстояние между внутренними +гранями стен на его концах. Средний участок другой толщины на это расстояние не +влияет — и это верно физически, а не упрощение. Если весь участок середины +нулевой, срабатывает правило нуля из §6. ## 8. Поверхности @@ -161,6 +192,8 @@ coordScale)` — тем же способом, которым толщину у | AC4 | Ребро с нулевой толщиной соседа (проём, открытая сторона) не сокращается с этого конца | unit | | AC5 | Диагональное ребро: внутренняя длина равна расстоянию между точками пересечения внутренних линий, а не `|b−a| − o − o` | unit | | AC6 | Стены толще комнаты: возвращается 0, подпись не показывает отрицательное число | unit | +| AC6a | Измеряемая сторона — проём (`oSelf = 0`) при соседях-стенах 15 см: подпись показывает полную осевую длину, а не сокращённую (H1) | unit | +| AC6b | Ребро со сплит-толщиной (часть 20 см, часть без записи) резолвится через атомарный профиль: сокращение на концах есть, нуля из `thicknessCmAt` не возникает (H2) | unit | | AC7 | Все три подписи `_rszEdgeLabels` внутренние; габарит `_rszScaleLabels` — bbox внутреннего контура | smoke | | AC8 | Площадь при ресайзе **не изменилась** этой задачей — то же значение, что до правки | smoke | | AC9 | release-артефакты: оба changelog и оба USER-GUIDE в том же коммите | ревью кода | @@ -171,6 +204,8 @@ coordScale)` — тем же способом, которым толщину у |---|---|---| | `resize-labels-show-centreline` | подписи снова считают осевую длину | AC1, AC7 | | `inner-span-ignores-neighbour-thickness` | сокращение считается только по своей толщине, соседи игнорируются | AC2 | +| `inner-span-shortens-a-passage` | правило нуля из §6 снято: проём начинает сокращаться на соседей | AC6a | +| `inner-span-reads-whole-edge-thickness` | источник толщин подменяется на `thicknessCmAt` по ребру комнаты | AC6b | ## 12. План автотестов @@ -198,8 +233,9 @@ coordScale)` — тем же способом, которым толщину у вычитает колонны и перегородки внутри комнаты. Риск в том, что пользователь прочитает это как ошибку. Митигация: расхождение теперь объясняется мебелью в комнате, а не системой измерения; в changelog это сказано словами. -2. **Атомарные участки разной толщины** дают одно число на ребро (§7) — - упрощение, названное явно. +2. **Атомарные участки разной толщины** дают одно число на ребро — расстояние + между гранями стен на его концах (§7). Это физически верно, а не упрощение: + участок другой толщины в середине на это расстояние не влияет. 3. **`insetContour` не сохраняет вершины** — именно поэтому контракт §6 не использует индексы внутреннего контура. Мутант `inner-span-ignores-neighbour-thickness` стережёт, что сокращение считается по