feat: smooth vacuum trails (#209)

Issue: #209
User-Visible: yes
This commit is contained in:
Matysh
2026-08-28 23:16:58 +03:00
parent 2890b9a1ab
commit 9d2ef12c96
43 changed files with 578 additions and 268 deletions
+8
View File
@@ -242,6 +242,14 @@ priority decision. `resolveVacSource()` is sticky for saved sources and limits
automatic selection to compatible entities on the same HA device; the card
adds registry status through the shared `resolveHaBindingStatus()` authority.
`smoothVacPath()` is the shared pure presentation step for current and previous
runs. It consumes calibrated flat plan coordinates and returns typed
`move|line|quadratic` commands with a caller-supplied physical radius; the card
then applies flat/isometric scene projection and SVG serialization. Each corner
uses a quadratic inside the adjacent-segment convex hull, bounded by half of
both segment lengths, so the 17.5 cm product limit, exact endpoints and literal
subpath gaps are structural rather than renderer-specific accidents.
Room auto-calibration uses the same shoelace `areaCentroid()` for plan polygons
and robot outlines. Residuals are converted through resolved grid pitch and
cell centimetres; matrices above the 40 cm threshold remain proposals until an
+6
View File
@@ -2,6 +2,12 @@
## Unreleased
- Robot-vacuum trails now follow the same recorded points with softly rounded
corners instead of a visibly segmented polyline. Current and previous runs
use the same bounded curve, preserve telemetry gaps and never deviate by more
than 17.5 cm from the recorded route
([#209](https://github.com/Matysh/houseplan-card/issues/209)).
- A robot vacuum that reports its live position no longer freezes the card
on a freshly opened page: the map-id lookup ran inside the render cycle
and required editor code that a cold tab has not loaded yet, so the very
+6
View File
@@ -8,6 +8,12 @@
## Не выпущено
- След пылесоса теперь повторяет те же записанные точки с мягко скруглёнными
поворотами вместо заметной ломаной. Текущая и предыдущая уборки используют
одну ограниченную кривую, сохраняют разрывы телеметрии и не отклоняются от
записанного маршрута более чем на 17,5 см
([#209](https://github.com/Matysh/houseplan-card/issues/209)).
- Робот-пылесос с живой позицией больше не замораживает карточку на только
что открытой странице: определение id карты выполнялось внутри цикла
отрисовки и требовало код редактора, который холодная вкладка ещё не
+1 -1
View File
@@ -33,7 +33,7 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
| Furniture | #159 replaces the flat ~30-item picker with a two-level category/variant palette and 56 top-view symbols. The reviewed 77-SVG MIT source pack is vendored under `assets/furniture/houseplan-0.3.0`; plan art stays in the initial View graph, front-view category art stays in the lazy editor graph, and existing saved geometry is unchanged. |
| Tests | Four layers: Node unit (`npm test`: frontend pure modules + tooling policy), pure backend (`pytest tests_backend`, runs anywhere), HA-harness backend (same folder, CI only — needs py3.13 + pytest-homeassistant-custom-component), and browser smokes (`demo/smoke_*.mjs`, headless chromium). **Counts are not written down here** — they went stale within two releases while the version line beside them was kept current, which reads as less coverage than exists (review R5-2). Run `npm run inventory` for the current numbers, or read them off the last CI run |
| Input support | Owner's rule since 2026-08-08: View and kiosk are fully supported and release-blocking on touch. All three editors are desktop-first; touch editing is best effort and may be awkward, reduced or absent when parity is expensive. `docs/TOUCH-SUPPORT.md` defines the non-negotiable safety floor and documentation/test rules |
| Vacuums | Live puck, server-side trails and fit calibration are shipped. The local v1.61 Stage 1 contract in docs/VACUUM.md adds explicit Dreame/XCME/Valetudo coverage, registry-less source selection, capability diagnostics, path-gap preservation and source-health warnings; #205's issue branch resumes one ended same-map run through an inclusive 30-minute station/pause grace. Roomba remains Stage 2 |
| Vacuums | Live puck, server-side trails and fit calibration are shipped. The local v1.61 Stage 1 contract in docs/VACUUM.md adds explicit Dreame/XCME/Valetudo coverage, registry-less source selection, capability diagnostics, path-gap preservation and source-health warnings; #205 resumes one ended same-map run through an inclusive 30-minute station/pause grace. #209 renders current and previous trails through the same bounded 17.5 cm rounded-corner curve without changing stored points or gaps. Roomba remains Stage 2 |
| Demo stand | **https://demo.houseplan.tech** — public, login `demo`/`demo`, resets to a pristine synthetic home every hour. **https://dev.houseplan.tech** — closed (basic auth), auto-deploys the `dev` branch every 10 min. Host: `ssh -i ~/.ssh/hp_stand hp@135.106.166.146`; layout, seeds and gotchas in the memory note `houseplan-demo-stand`. Since 2026-07-31 the stand covers most of the manual checklist: a scripted robot vacuum (`demo/stand/demo_robot` — Tasshack-shaped map sensor, serpentine run, pre-solved calibration, seeded server trail), Zigbee-style LQI template sensors, hand/auto-triggered leak+smoke alarms, an hvac_action climate marker and working script/scene/automation targets for tap-run. The stand-specific how-to-check guide is **docs/TESTING-DEMO.md** |
| Community | **Telegram chat: https://t.me/ha_houseplan** (created 2026-07-27) — the primary user-facing support channel; GitHub issues stay for bugs/features. Link it from any new release notes and posts |
| Product scope | `docs/SCOPE.md` is the feature guard rail; `docs/TOUCH-SUPPORT.md` is the input-support contract — check both before accepting interaction work |
+20
View File
@@ -37,6 +37,26 @@
мутантам с браузерным гвардом; компиляция тестов в worktree стартует с
тёплого `test-build/` основного дерева.
## Vacuum trail smoothing (#209)
- [ ] Pure `smoothVacPath` tests prove exact endpoints, separate subpaths,
degenerate/reversal fallbacks, the 17.5 cm sampled deviation bound and
the 64-subpath/4000-point `≤2N` command budget
[unit: `test/vacuum.test.mjs`].
- [ ] The production bundle renders current and previous runs through paired
case/core `<path>` elements with quadratic commands, unchanged source
authority/modes, live-target trim and puck tip
[auto: `demo/smoke_vacuum.mjs`].
- [ ] Flat and dormant-Iso projections retain the same curved live layer on
touch and across HA updates [auto: `demo/smoke_isometric_live_touch.mjs`].
- [ ] `vacuum-trail-smoothing-dark` contains two current subpaths plus a stored
previous run; its semantic guard requires two `M` sections, quadratic
commands and byte-identical case/core geometry before PNG comparison.
The baseline is accepted only from reviewed Linux CI
[golden: `demo/golden/matrix.mjs`].
- [ ] Replacing the quadratic corner with a straight vertex makes the focused
unit guard red [mutation: `vacuum-trail-smoothing-disabled`].
## Stable wall-segment identity (#282)
- [ ] Wall junction limits (#329): drawing refuses an apex under 15°, a seventh
+3
View File
@@ -1414,6 +1414,9 @@ Map Extractor, dreame-vacuum (Tasshack) и Valetudo-подобные камер
- Позиция старше 60 секунд визуально считается устаревшей.
- Разрывы integration path сохраняются отдельными участками и не соединяются
ложной линией. Хранятся максимум 64 рисуемых участка и 4000 точек.
- Текущий и предыдущий следы автоматически показываются плавными кривыми. Они
сохраняют исходные концы и разрывы маршрута и отклоняются от записанной
ломаной не более чем на 17,5 см независимо от масштаба и вида плана.
- Для текущего пути приоритет таков: путь интеграции → серверный текущий run →
локальный буфер. Пустой или одноточечный участок приоритет не перехватывает.
- Остановка, пауза или визит на базу сразу скрывают текущий след в режиме
+7
View File
@@ -106,6 +106,13 @@ two points are discarded before limits are applied. The newest 64 drawable
subpaths are kept, with at most 4000 total points; both endpoints of every kept
subpath survive deterministic proportional thinning.
Current and previous trails use the same bounded rounded-corner curve. The
curve preserves every subpath endpoint and never leaves the recorded polyline
by more than 17.5 cm in physical plan coordinates. Smoothing happens after
vacuum calibration and before flat/isometric projection, so zoom, viewport,
DPR and projection do not change that limit. The live target is still trimmed
before rendering, and smoothing never bridges an integration data gap.
| Display mode | While moving | After movement stops |
|---|---|---|
| `never` | Hidden | Hidden |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 KiB

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 116 KiB

+22 -22
View File
@@ -2,8 +2,8 @@
"version": 1,
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"oxipng": null,
"sourceFingerprint": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"oxipng": "oxipng 10.2.0",
"sourceFingerprint": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"captureScriptSha256": "fe76c8e2e27501e9a377d7aa15dd6cba6a018d55c07deb602dbee097f0a0a5ba",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -15,8 +15,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "36223106c073f07d8cc3ecf8eaab37192ebb2687daba65c5c21047d0b7890de0"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "f8c9541c9d7fde462d0efba63dff70944dc03df76ce988eecaf2b269e2863a96"
},
"view-touch": {
"file": "02-view-touch.png",
@@ -26,8 +26,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "a4aa3af48578ee41594a9b1a6e98b4d39f3324a949a9fd9e63d5854576d5db85"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "21c6fe7d79ff5c878ade8e98a64cc90dfd7bcc66338c864fb5a0bd411471313d"
},
"space-create": {
"file": "03-space-create.png",
@@ -37,8 +37,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "0dc024726327f60f4a9ceaf3044381691f81f1090af81d1812e870f22d9343ba"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "f6d190f0b2ac9125cea68aea2ebbcdf9a7081ae2a3565ff253fb1ed0c4a56483"
},
"room-contour-close": {
"file": "04-room-contour-close.png",
@@ -48,8 +48,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "44cfaa95bd51e2cd628400e28db0ad8b2f0cd904385845bd402494f3f5c0d93c"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "ca8bb143d0d6679ff09f8e9c1f5ad68b2ca7cfd96a4740a4a650c251f55dfba5"
},
"plan-context-tray": {
"file": "05-plan-context-tray.png",
@@ -59,8 +59,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "e0662429b423fb74151b583dcc2c8635b001b637d03bbf7a6b16aec46399c3f8"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "06b67c5108f9ee29a226cc2920484ccffe0c79840aec09721072b25b91ef8f03"
},
"device-editor": {
"file": "06-device-editor.png",
@@ -70,8 +70,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "d0ffd31ce80bfde21ab75da356a5fc1af38246f2b301030880320620c228d89d"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "9e435a6fe89564b9215b82a61fb7eae998266dd05483524ffa84160c35630e0c"
},
"device-display-preview": {
"file": "06-device-display-preview.png",
@@ -81,8 +81,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "2cdabae1f89c3286e4fac0ce30f757ee1690b707ab8a5488748b7cd420626160"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "cc5abe1b3364bf9812bea6ea5cfa84f35120ff9639ed3c4c92958cb646a8caf7"
},
"background-editor": {
"file": "07-background-editor.png",
@@ -92,8 +92,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "30147bb00a90eea7136b4cee30995f6e6a9217b5132f3e8d3ad7471413b1af8a"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "7e308a2b105fa6a9f788449ca30b8183375a32f0d0f8726f4a131e4f35527f57"
},
"room-card": {
"file": "08-room-card.png",
@@ -103,8 +103,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "f1bc10b478f7afc68e70cb4c37148f680b0bbd41d50a7d63a21067fc952ff134"
},
"device-info": {
"file": "09-device-info.png",
@@ -114,8 +114,8 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "0896c482848c70ed7071b4e2396294fd83072352403a859447b53fc3c10cc6ae",
"imageSha256": "dd492f53150b7149085daada5cce9eeae9bde9e7ea1d86679a54b3041f72f517"
"sourceSha256": "4b183589451e9a238ceeab91d847fcc1806215e85ee64f2c27b4b1cb167f6181",
"imageSha256": "2634cd7ead5720ce46ea4d6fa8f31006b740c520a73ddc97afe9a6d9d3b49778"
}
}
}