14 KiB
CODE-REVIEW-711-r1
Issue: #711 · «2.5D: значки не сдвигаются от состояния устройства; HA-обновление не переразмещает оверлеи»
Трек: show (владелец задал поведение решением от 2026-09-30)
Материал: a372b354c6f0e47902dbf81ed383e4e3ca637034, один коммит поверх dev@9caa3c23
Заход: r1 · блокирующих циклов израсходовано 0 из 2
Скоуп
Часть #694 (регрессия производительности 2.5D после #651/beta.4). Три AC:
- AC1 — раскладка устройства видит state-free плитку (без текста значения, бейджей, доп. метрик); смена состояния не двигает значки и не запускает поиск столкновений; видимая ширина (с бейджем) по-прежнему учитывается в границах сцены.
- AC2 — поиск жёстких групп отсекает кандидатов по лексикографической границе (комната → стены → перекрытие) без изменения результата.
- AC3 —
stateUpdateнаisometric-stage3-dense-v1не выше, чем у v1.77.0.
Отношение к docs/SCOPE.md: правит J1 («live spatial overview») и
детерминированность узкого исключения 2.5D (#89 — «то же состояние/действия/
геометрия, что и в плоском виде»); дрейф позиции значка от состояния нарушал
именно этот принцип. В скоупе.
Как проверялось
Прочитаны: docs/SCOPE.md, AGENTS.md, docs/process/REVIEWER.md,
docs/ISOMETRIC.md (канон подсистемы), тело issue #711 и оба комментария
(«Взял», «Сделано»). Дифф разобран построчно (git diff origin/dev...HEAD)
по обоим правленым файлам src/iso-scene-render.ts и src/iso-overlays.ts.
Отдельно проверил цепочку типов и вызовов:
- откуда берётся
layoutHalfSizeдля устройства (stateFreePresentation→isoRaisedOverlayHalfSize) и чтоisoRaisedOverlayHalfSizeдляkind: 'device'зависит только отvalueText,valueFullText,valueBadge,legacySupplementalMetrics(которая читаетtempText/humText) —stateFreePresentationобнуляет ровно эти пять полей (src/iso-scene- render.ts:150-155,src/iso-scene-render.ts:290-310,src/device-face.ts:35-48). Пропущенных состояние-зависимых полей, влияющих на размер, не нашёл. - что
layoutHalfSizeOf(entry), а неentry.screenHalfSize, идёт вplace()(раскладка/коллизии) и вcollisionSignature, тогда какscreenHalfSize(визуальный, состояние-зависимый) остаётся источником для границ сцены (overlayEntryPoints,src/iso-scene-render.ts:651-668) — то есть ровно то разделение, которое требует AC1. - быстрый путь «HA-only change» (
src/iso-scene-render.ts:1001-1023): срабатывает только когдаcollisionSignature(поlayoutHalfSizeOf, местам и владению комнатой) не изменился; обновляет толькоscreenHalfSizeу затронутых записей и возвращает тот жеMapdevices/rooms/locksбез обращения кresolveIsoOverlayRigidGroups— то есть действительно «поиск столкновений не запускается». - пруннинг в
resolveIsoOverlayRigidGroups(src/iso-overlays.ts:1883-1968): проверил как branch-and-bound относительноrigidFallbackOrder(roomViolations, wallViolations, overlapPenalty, offset,src/iso- overlays.ts:1690-1694). Отсечение поboundстрого монотонно (кандидат хуже текущего fallback по primary/secondary измерению не может ни статьchosen, ни заменитьfallback), кандидат с 0/0 нарушений никогда не отсекается — победитель не может быть потерян отсечением. Логика соответствует комментарию AC2.
Исполнение, не только чтение, для AC1: временно подменил
src/iso-scene-render.ts на версию origin/dev (без переключения HEAD,
без коммита), пересобрал (npx tsc -p tsconfig.test.json →
node scripts/fix-test-build.mjs) и прогнал только новый тест:
node --test --test-name-pattern="#711" test/iso-scene-render.test.mjs
Результат на коде до фикса: # fail 1 — падает на
assert.strictEqual(on.devices.get(id), off.devices.get(id)) для lamp
(объект раскладки — новый экземпляр, полная рекомпоновка сработала). Затем
вернул файл материала обратно, пересобрал и прогнал тот же тест повторно:
# pass 1. Рабочая копия после эксперимента чиста (git status --short
пусто, git rev-parse HEAD не менялся) — эксперимент не оставил следов в
репозитории.
Проверил, что мутант iso-device-layout-follows-state-again
(scripts/mutation-registry.mjs) целится ровно в изменённую строку
(presentation: stateFreePresentation(presentation) → presentation) —
совпадает с тем, что тест выше проверил вручную.
Для AC2 «чем краснеет» — существующий (не изменённый в этом диффе) тест
test/iso-overlays.test.mjs:179 «rigid fallback prioritizes room, then wall,
then overlap inside the 48px cap» уже кодирует именно порядок приоритетов,
который защищает пруннинг; он входит в зелёный npm test из Validate на этом
SHA.
Проверил трейлеры коммита: Issue: #711, User-Visible: yes, оба
docs/CHANGELOG.md/docs/CHANGELOG.ru.md правлены в том же коммите,
формулировки согласованы с docs/USER-GUIDE.ru.md («Объёмный вид плана
(2.5D)», docs/USER-GUIDE.ru.md:60,345).
Числа (§8, «один источник»): единственные пользовательские/оценочные числа —
таблица бенчмарка (stateUpdate 208/522/89 мс и т.д.) — присутствуют только
в комментарии «Сделано» и в теле коммита, и совпадают между собой; ни в
docs/CHANGELOG*.md, ни в docs/ISOMETRIC.md эти числа не повторяются —
дублирования с риском расхождения нет.
Что проверено и корректно
- AC1 — доказан исполнением (тест красный на
dev-версии файла, зелёный на материале) плюс чтением, что раскладка/коллизии видят state-free размер, а видимая ширина с бейджем — по-прежнему в границах сцены (тот же тест проверяетisoOverlaySceneBounds(on).w >= isoOverlaySceneBounds(off).w). Быстрый путь при неизменной раскладке действительно не вызываетresolveIsoOverlayRigidGroups. - AC2 — доказан чтением (пруннинг математически корректен относительно
объявленного лексикографического порядка) плюс исполнением существующего
теста на приоритет room→wall→overlap (часть зелёного
npm test). Автоматической регрессии именно на «хэш плотной сцены не меняется» нет — это ручной разовый замер автора в комментарии (−2128229368), не закреплённый тестом; для AC с формулировкой «Проверка: существующие тесты… и хэш одинаков» это ожидаемый уровень доказательства (AC не требовал нового теста на хэш), не нахожу это находкой. - AC3 — задокументирован таблицей замера в комментарии «Сделано»: одна
песочница, 3 сэмпла,
stateUpdate89 мс против 208 мс у v1.77.0 (ниже порога, AC выполнен с запасом). Способ доказательства AC3 сам по себе — «замер в «Сделано»», не автотест; замер приведён с названным профилем (isometric-stage3-dense-v1) и числами по трём итерациям, соответствует формату §8. - Трейлеры коммита, оба changelog, терминология из USER-GUIDE — в порядке.
- Мутант зарегистрирован и целится точно в защищаемую AC1 строку.
- Изменение не трогает модель геометрии (
src/plan-geometry-preflightи т. п.) — только раскладку/коллизии оверлеев в 2.5D;npm run invariantsне относится к этому диффу.
Находки
Нет ни High, ни Medium, ни Low.
Чего не проверял
- Полный
npx tsc --noEmit/npm test/npm run buildс сверкой трёх копий бандла — не гонял заново: Validate на этом SHA (a372b354) зелёный (https://github.com/Matysh/houseplan-card/actions/runs/36686903130), §8/AGENTS.md разрешают на него сослаться. Прогнал только точечныйnode --testдля одного файла (см. выше) — это не замена полного набора, а точечная проверка «умеет ли тест падать». - Браузерные смоки не гонял.
node scripts/smoke-select.mjs --base origin/dev --head HEADвернул НЕОПРЕДЕЛЁННОСТЬ (дифф исполняемый, но ни один смок не связан доказуемо; новые символы вродеlayoutHalfSizeOf,isoRaisedOverlayHalfSize,RigidCandidateне встречаются ни в одном смоке). Тело issue #711 не называет ни одного смока в AC, так что запуск Chromium не требуется по правилам этого прогона. В комментарии «Сделано» автор называет команды и зелёный результат для визуального минимума (8 смоков) иsmoke_isometric_contract,smoke_isometric_live_touch— это засчитывается как доказательство (команда и результат названы), но я не переисполнял их независимо. npm run golden:verify— меткиci:goldenна этой задаче нет, дифф не содержит правокdemo/golden/**.python -m pytest tests_backend— дифф не касаетсяcustom_components/**/*.py.npm run invariants— дифф не касается геометрии модели плана (комнаты/ стены/маркеры), только раскладку 2.5D-оверлеев; не применимо.- Мутации по диффу (в т. ч.
iso-device-layout-follows-state-again) не гонял — по правилам трекаshowмутанты в разработке не гоняются, их ловит ночной прогон (#709); я лишь сверил регистрацию и цель патча. - Продуктовое поведение с бейджами, наезжающими на соседний значок в плотном ряду (риск, названный самим автором) — не проверял визуально/скриншотом; это заявленное и принятое владельцем следствие правила «положение не зависит от состояния», не дефект этой задачи.
Вердикт
Зелёный. Оба изменённых файла реализуют ровно то, что заявлено в AC; AC1 доказан исполнением в обе стороны (красный → зелёный), AC2 — чтением плюс существующим регрессионным тестом на порядок приоритетов, AC3 — замером с названным профилем. Трейлеры, changelog и терминология в порядке. Находок нет.
Материал раунда
- Ветка:
issue/711-iso-overlay-state-free-layout, коммитa372b354c6f0— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
25a16d0f804ada19deba6c12c8a2dfa6a19f48fbgit log --all --format='%H %T' | grep 25a16d0f804a - Тело issue:
14d76d92f923bb2922a3dc0c35ce91a8927a24a572e525d513a116db2c51d319 - Вердикт конвейера:
green· High 0