fix: straighten near-axis wall geometry

Issue: #290
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-08-24 16:30:11 +03:00
parent 6551c5d53d
commit edf8b068ed
25 changed files with 845 additions and 23 deletions
+9
View File
@@ -548,6 +548,15 @@ scale helpers remain pure-test history only and are tree-shaken from the
production interaction path. Exact `a/b` wall endpoints remain identity and
the quantised midpoint/direction `key` remains only a compatibility index.
Near-axis geometry has one shared classifier in `src/near-axis.ts` (#290).
Walls applies it after architectural/grid resolution and before hover/commit,
moving only the free endpoint. Resize validates that its fixed-topology output
contains no near-axis edge. Explicit Optimize runs the lossy legacy repair only
after grid alignment, moves coincident room endpoint owners atomically, then
reuses ordinary opening projection and wall/open-span rekeying. Unique physical
count, maximum centimetres and skipped candidates stay separate from ordinary
grid movement; no load/save migration invokes this repair.
All physical-geometry writers share the same transaction boundary (#278).
`checkSpacePhysicalGeometry()` validates the exact candidate through canonical
wall and floor builders before history or save. A failed or degraded candidate
+14
View File
@@ -429,6 +429,13 @@ the user is asked to zoom. Exact coincident endpoints are still one node. The
active thick rubber-band always paints its centreline and final node above the
body; an active snap marker replaces, rather than duplicates, that final node.
After architectural snapping, a Walls segment within `0.25°` of a horizontal
or vertical axis is made exact by moving only its free endpoint (#290). Hover,
the active marker and click consume that same point. A nearby saved endpoint
which would require the forbidden one-step slope therefore loses snap
ownership instead of being joined invisibly. Shift-selected 45° rays and true
diagonals outside the shared tolerance are unchanged.
On an axis-aligned wall whose corners are on the grid — every wall the
editor itself draws — the two rules give the same point. An opening is
also kept inside its wall by half its own length.
@@ -491,6 +498,13 @@ touching virtual spans per room pair, compacts consecutive real-wall
intervals of equal thickness and stamps `model_version`. Unknown fields
are preserved and every pass is idempotent.
The explicit pass also repairs pre-existing near-axis room walls, saved wall
chains and independent walls after ordinary grid alignment (#290). Coincident
room-owner copies count as one physical wall and move as one endpoint
equivalence class. The preview reports the unique count, maximum physical
movement and unsafe skipped candidates; only Confirm writes, and Undo restores
the prior geometry. Exact axes and true diagonals are not candidates.
The optimizer deliberately does **not** alter backdrop calibration or saved
view boxes, deduplicate markers, or delete files. It may delete an unattached
layout entry only after classifying its owner against current rooms, marker
+6
View File
@@ -12,6 +12,12 @@
gaps from the real second-floor plan are now closed consistently in every
wall consumer without extending the short arm or changing saved geometry
([#288](https://github.com/Matysh/houseplan-card/issues/288)).
- Walls now stores visually horizontal or vertical segments on an exact axis
instead of allowing an invisible one-grid-step slope. **Optimize plans** can
explicitly straighten the same defect in older plans, counting a shared wall
once, reporting the maximum movement before confirmation and preserving true
diagonals; Cancel and one-deep Undo keep the operation reversible
([#290](https://github.com/Matysh/houseplan-card/issues/290)).
## v1.67.0-beta.9 — 2026-08-24
+7
View File
@@ -18,6 +18,13 @@
шагов на реальном плане второго этажа закрыты во всех потребителях геометрии
стен без удлинения короткого луча и без изменения сохранённого плана
([#288](https://github.com/Matysh/houseplan-card/issues/288)).
- Инструмент «Стены» теперь сохраняет визуально горизонтальный или вертикальный
отрезок строго по оси и не создаёт незаметный уклон в один шаг.
**Оптимизировать планы** может явно выпрямить такой дефект в старом плане:
общая стена считается один раз, максимальный сдвиг показывается до
подтверждения, настоящие диагонали сохраняются, а Cancel и одноуровневый Undo
делают операцию обратимой
([#290](https://github.com/Matysh/houseplan-card/issues/290)).
## v1.67.0-beta.9 — 2026-08-24
+8
View File
@@ -97,6 +97,14 @@ block the repair. Normal read, render, Save and editor paths remain lossless;
only confirmed Optimize applies it, with the ordinary preview and server Undo
(#198, #273).
Explicit Optimize may also straighten a stored wall whose slope is non-zero
but no more than `0.25°` from an axis (#290). This is a confirmed lossy repair,
not a read migration or schema change. All coincident room-owner endpoints move
together, wall/opening identities are reprojected by the canonical pipeline,
and true diagonals survive byte-equivalent. Older clients continue reading the
result as ordinary polygon geometry; reverting code does not require a storage
migration.
## Open-passage opening type (#157)
`space.openings[].type` additionally accepts the literal `passage`. Its
+3 -1
View File
@@ -62,7 +62,9 @@ The production controller reaches only four pure operations in `src/resize.ts`:
planned room and translates each ordinary moving-wall opening once.
4. `validateSafeResize(...)` proves room identity/count, topology, orientation,
simplicity, minimum clearance, exact shared endpoints, foreign-room
relations, physical obstacles and opening jamb clearance.
relations, physical obstacles and opening jamb clearance. It also rejects
any near-axis postcondition: a safe candidate is exact horizontal/vertical,
never a sub-`0.25°` arithmetic slope (#290).
Historical general-transform helpers remain only for old pure-test history.
`houseplan-card.ts` must not import or call `applyRoomScale`,
+12
View File
@@ -2852,6 +2852,18 @@ require hands on real hardware — they remain for the human pass.
onto a longer query, and scale/direction/row order do not change the
resolver [auto: test/wall-thickness.test.mjs + test/open-spans.test.mjs +
smoke_resize_virtual_thick + mutation-gate].
- [ ] **Near-axis authoring and explicit repair (#290)**: the shared
`0.25°` classifier includes `316×1`, excludes `316×2` and 30° diagonals,
and Walls preview/click persist `316×0` without claiming the wrong saved
endpoint. Optimize deduplicates the tracked shared wall across two room
owners, reports one wall and an exact physical maximum, rekeys thickness,
passes production preflight, applies one atomic write, reloads as a no-op
and restores the original through one Undo. Saved drafts and independent
walls use the same classifier; unsafe candidates are counted as skipped
[unit: test/near-axis.test.mjs; auto: smoke_plan_drawing_repairs +
smoke_near_axis_optimize; mutations: `near-axis-threshold-weakened`,
`near-axis-inclusive-boundary-disabled`,
`near-axis-authoring-snap-bypassed`].
- [ ] **Explicit Optimize cleans only an isolated micro-interval (#198)**:
`22 → 15 → 22` with a centre shorter than half a grid step and no
room/opening node becomes one 22 cm run in Preview and Apply; Cancel
+10
View File
@@ -302,6 +302,12 @@ Existing segment endpoints and lines appear above walls while drawing. An
endpoint grows when the next click will join it. A point on a line shows where
the click will create a valid junction.
A segment which is visually horizontal or vertical within `0.25°` is stored as
an exact axis: House Plan moves only the free endpoint, and preview already
shows the final result. A real diagonal remains unchanged. Older invisible
one-grid-step slopes are offered separately by **Optimize plans**, with the
number of walls and maximum movement shown before confirmation.
### Plan tools at a glance
| Tool | Result | Room area | Light and shadow | Main limit |
@@ -335,6 +341,10 @@ small enclosed hole. At a perpendicular T/X junction, the complete physical
width of every participating wall remains solid through the node, including
closely spaced neighbouring junctions. This is a rendering correction: a valid
plan may remain unchanged when **Optimize plans** is run.
Legacy near-axis walls are different: **Optimize plans** may explicitly
straighten them, counts a shared wall once across both rooms and leaves an
unsafe candidate unchanged. Cancel writes nothing and the confirmed batch has
the normal one-deep Undo.
Resize changes one room, or exactly two rooms when their shared wall coincides
endpoint-to-endpoint. The wall stops at the first corner, opening, foreign room
or other position that would change topology; no more than two rooms can change.
+9
View File
@@ -400,6 +400,15 @@ T-соединение входит в проходящую стену без в
соседних стыков. Это исправление отображения: для валидного плана команда
**Оптимизировать планы** может ничего не изменить.
Отрезок, который отклонён от горизонтали или вертикали не больше чем на
`0,25°`, инструмент «Стены» сразу показывает и сохраняет строго по оси: меняется
только свободный конец. Настоящие диагонали не выпрямляются. Старые невидимые
уступы в один шаг предлагается исправить через **Оптимизировать планы**: до
подтверждения показываются число физических стен и максимальный сдвиг, общая
стена двух комнат считается один раз, а небезопасные варианты пропускаются.
Отмена ничего не записывает, подтверждённое исправление можно отменить обычным
Undo оптимизации.
Каждый законченный отрезок цепочки сохраняется сразу. Esc/Ctrl+Z удаляет
последнюю точку. Pan, pinch и `pointercancel` ничего не завершают и не добавляют.
После перезагрузки сохранённый draft можно продолжить кликом по его концу.
+7
View File
@@ -156,6 +156,13 @@ short ray to the global radius and never treats an unrelated outer continuation
as node-owned material. This closes the real `349 / 120 / 5`-step gaps while
preserving the finite-ray and opening contracts from #271.
`NEAR_AXIS_MAX_DEGREES` in `src/near-axis.ts` is the single `0.25°` product
constant (#290). The masonry pair classifier derives its sine tolerance from
that source; Walls authoring and explicit Optimize use the corresponding
minor/major slope. Rendering may tolerate a legacy saved slope, but new Walls
segments are exact-axis and Optimize changes legacy geometry only after its
lossy preview is confirmed.
Clean-floor consumers subtract the cached, repaired canonical room masonry from
their source room and take its outer component. The result is clipped to the
source room on fallback. Openings and independent partitions are deliberately